Giter VIP home page Giter VIP logo

writeahead's Introduction

Actions Status codecov License: MIT

A write-ahead log.

What Where
Discussion #1
Documentation https://bigeasy.github.io/writeahead
Source https://github.com/bigeasy/writeahead
Issues https://github.com/bigeasy/writeahead/issues
CI https://travis-ci.org/bigeasy/writeahead
Coverage: https://codecov.io/gh/bigeasy/writeahead
License: MIT

WriteAhead installs from NPM.

npm install writeahead

Living README.md

This README.md is also a unit test using the Proof unit test framework. We'll use the Proof okay function to assert out statements in the readme. A Proof unit test generally looks like this.

require('proof')(4, async okay => {
    okay('always okay')
    okay(true, 'okay if true')
    okay(1, 1, 'okay if equal')
    okay({ value: 1 }, { value: 1 }, 'okay if deep strict equal')
})

You can run this unit test yourself to see the output from the various code sections of the readme.

git clone [email protected]:bigeasy/writeahead.git
cd writeahead
npm install --no-package-lock --no-save
node test/readme.t.js

Overview

WriteAhead exports a single WriteAhead class.

const WriteAhead = require('writeahead')

WriteAhead is designed to be used in concert with the structure concurrency libraries Turnstile, Fracture, and Destructible.

If you're not interested in getting into the details of any of that, I don't blame you, don't worry. Use of WriteAhead only requires a superficial use of these libraries. You need to require them into your module but then you'll use them according to a boilerplate.

const Turnstile = require('turnstile')
const Fracture = require('fracture')
const Destructible = require('destructible')

A WriteAhead instance requires and instance of Destructible and Turnstile. Here we create a destructible and give it a meaningful name. We then create a Trunstile using a sub-destructible as its only argument. You can copy and paste this boilerplate into your module.

The WriteAhead construction is itself a two step process of creating an opening instance that is used to construct the WriteAhead object.

const path = require('path')

const destructible = new Destructible('writeahead.t')
const turnstile = new Turnstile(destructible.durable('turnstile'))

const open = await WriteAhead.open({ directory: path.join(__dirname, 'tmp', 'writeahead') })
const writeahead = new WriteAhead(destructible.durable('writeahead'), turnstile, open)

The first argument to 'writeahead.writeis a Fracture stack which you can create usingFracture.stack()`.

The second argument is an array of objects that contain a buffer to write to the write-ahead log and a set of one or more JSON serializable keys that you can use to select the records out of the write-ahead log when reading.

The final argument is the sync flag which determines if the entries should be written to disk with with an fsync. The fsync will ensure that all the entries are synced to the underlying storage medium when the write function returns.

const entries = [{
    keys: [ 0, 1 ],
    buffer: Buffer.from('a')
}, {
    keys: [ 0 ],
    buffer: Buffer.from('b')
}, {
    keys: [ 1 ],
    buffer: Buffer.from('c')
}]

await writeahead.write(Fracture.stack(), entries, true)

If you give a sync value of false the entries are written without subsequently calling fsync. You may want to do this if you want to write a series of entries that are part of a transaction and then write a final entry that commits that transaction. fsync is somewhat expensive of if you can forgo unnecssary calls to fsync your performance may improve.

To read we create an asynchronous iterator that returns the buffers for the entries that match a given key in the order in which they were written to the write-ahead log.

const gathered = []
for await (const block of writeahead.get(1)) {
    gathered.push(block.toString())
}

okay(gathered, [ 'a', 'c' ], 'read from file')

TODO Show the 0 key.

I decided to use an asynchronous iterator instead of implementing a Readable stream. I don't need to stream the contents of my write-ahead logs, I'm not pipeing them to a file or a socket. I'm parsing each block as it is returned and using the contents to update a b-tree or r-tree. The streaming interface is a lot of overheadto use an asynchronous iterator instead of implementing a Readable stream. I don't need to stream the contents of my write-ahead logs, I'm not pipeing them to a file or a socket. I'm parsing each block as it is returned and using the contents to update a b-tree or r-tree. The streaming interface is a lot of overhead and the async iterator provides a surrogate for back-pressure by only pulling a block at a time from the write-ahead log.

If you really want to stream from a write-ahead log, there are utilities on NPM like async-iterator-to-stream that convert an async iterator to a stream. I do not have experience with this module, merely noting that it is a candidate.

When you are finished with the WriteAhead instance call the destroy method of the Destructible used to create the Turnstile and WriteAhead. It will wait for any queue writes to flush and close the write-ahead log.

destructible.destroy()

Reopening

Reopening a write-ahead log is the same as creating it for the first time. Here we reopen the write-ahead log we created above.

const path = require('path')

const destructible = new Destructible('writeahead.t')
const turnstile = new Turnstile(destructible.durable('turnstile'))

const open = await WriteAhead.open({ directory: path.join(__dirname, 'tmp', 'writeahead') })
const writeahead = new WriteAhead(destructible.durable('writeahead'), turnstile, open)

The records we wrote above are still there.

const gathered = []
for await (const block of writeahead.get(1)) {
    gathered.push(block.toString())
}

okay(gathered, [ 'a', 'c' ], 'read from reopened file')

Don't forge to close the write-ahead log when you're done with it.

destructible.destroy()

Concurrency

The write method of WriteAhead is synchronous, even though it is an async function, the initial write is to an in memory queue and the writes are immediately available when the write function returns its Promise.

This means that you can use a single write-ahead log in your program. All the writes will be ordered in the order in which the write method was called. There will be no interleaved writes.

Let's reopen our write-ahead log again.

const path = require('path')

const destructible = new Destructible('writeahead.t')
const turnstile = new Turnstile(destructible.durable('turnstile'))

const open = await WriteAhead.open({ directory: path.join(__dirname, 'tmp', 'writeahead') })
const writeahead = new WriteAhead(destructible.durable('writeahead'), turnstile, open)

Now we write to the write ahead log but we do not await the returned Promise. We hold onto it for a bit.

const entries = [{
    keys: [ 0 ],
    buffer: Buffer.from('d')
}, {
    keys: [ 0, 1 ],
    buffer: Buffer.from('e')
}]

const promise = writeahead.write(Fracture.stack(), entries, true)

We can see that even though the asynchronous write has not completed we are still able to read the written buffers from the in memory write queue. We know that the writes are not written to disk. They can't be. We have to perform an asynchronous write to the file and that cannot be performed until we await the Promise.

const gathered = []
for await (const block of writeahead.get(1)) {
    gathered.push(block.toString())
}

okay(gathered, [ 'a', 'c', 'e' ], 'read from in memory queue')

Of course, the written buffers will also be there after we await the Promise returned from write.

await promise

gathered.length = 0
for await (const block of writeahead.get(1)) {
    gathered.push(block.toString())
}

okay(gathered, [ 'a', 'c', 'e' ], 'read after write to file')

TODO Maybe don't open and close so much? It may make the documentation easier to follow.

destructible.destroy()

Rotating

The write-ahead log cannot grow indefinately. We need to trim it eventually. To do so we rotate the log, creating a new log file where new writes will be written.

Let's reopen our write-ahead log again.

const path = require('path')

const destructible = new Destructible('writeahead.t')
const turnstile = new Turnstile(destructible.durable('turnstile'))

const open = await WriteAhead.open({ directory: path.join(__dirname, 'tmp', 'writeahead') })
const writeahead = new WriteAhead(destructible.durable('writeahead'), turnstile, open)

Rotate.

await writeahead.rotate(Fracture.stack())

Write to rotate.

const writes = [{
    keys: [ 1 ],
    buffer: Buffer.from('f')
}, {
    keys: [ 0 ],
    buffer: Buffer.from('g')
}]

await writeahead.write(Fracture.stack(), writes, true)

Everything is still there.

const gathered = []
for await (const block of writeahead.get(1)) {
    gathered.push(block.toString())
}

okay(gathered, [ 'a', 'c', 'e', 'f' ], 'added after rotate')

Now we can shift.

await writeahead.shift(Fracture.stack())

Everything before the rotate is gone.

gathered.length = 0
for await (const block of writeahead.get(1)) {
    gathered.push(block.toString())
}

okay(gathered, [ 'f' ], 'vacuumed after shift')

Let's close our write-ahead log.

destructible.destroy()

TODO Go look at Addendum and come back and write about how you use this IRL.

writeahead's People

Contributors

flatheadmill avatar

Watchers

Alan Gutierrez avatar James Cloos avatar  avatar

Forkers

rjcc

writeahead's Issues

Release WriteAhead version 0.0.0.

  • Add release title to package.json.
  • Add synchronization.
  • Upgrade interrupt to 11.0.0-alpha.11.
  • Replace stream.ReadStream with async iterator.
  • Use Interrupt stack poker across callbacks.
  • Import implementation from R-Tree. Closes #6.
  • Build with Actions, ship coverage to Codecov. Closes #4. Closes #5.
  • Build with Travis CI. Closes #3.
  • Rename writeahead.js. See #2.
  • Fix names in package.json. See #2.
  • Fix links in README.md. See #2.
  • Set correct dates on LICENSE. See #2.
  • Initial commit. Closes #2.

Release WriteAhead version 0.0.40.

  • Upgrade operation to 6.0.0-alpha.8.
  • Upgrade fracture to 0.3.0-alpha.62.
  • Upgrade destructible to 7.0.0-alpha.63.
  • Upgrade turnstile to 6.0.0-alpha.74.

Release WriteAhead version 0.0.18.

  • Add sync property to write fracture value.
  • Sync is now a parameter to write.
  • Upgrade destructible to 7.0.0-alpha.45.
  • Upgrade fracture to 0.3.0-alpha.39.
  • Upgrade turnstile to 6.0.0-alpha.55.

Release WriteAhead version 0.0.25.

  • Upgrade operation to 6.0.0-alpha.6.
  • Upgrade fracture to 0.3.0-alpha.47.
  • Upgrade destructible to 7.0.0-alpha.48.
  • Upgrade turnstile to 6.0.0-alpha.57.
  • Upgrade interrupt to 11.0.0-alpha.18.
  • Remove unused require.

Release WriteAhead version 0.0.42.

  • Upgrade fracture to 0.3.0-alpha.65.
  • Upgrade destructible to 7.0.0-alpha.65.
  • Upgrade turnstile to 6.0.0-alpha.76.
  • Spell check readme.t.js.

Release WriteAhead version 0.0.27.

  • Upgrade fracture to 0.3.0-alpha.50.
  • Upgrade fracture to 0.3.0-alpha.49.
  • Upgrade destructible to 7.0.0-alpha.49.
  • Upgrade turnstile to 6.0.0-alpha.58.
  • Get rid of isDestroyedIfDestroyed.

Release WriteAhead version 0.0.37.

  • Upgrade fracture to 0.3.0-alpha.60.
  • Upgrade destructible to 7.0.0-alpha.61.
  • Upgrade perhaps to 0.0.9.
  • Upgrade turnstile to 6.0.0-alpha.72.

Release WriteAhead version 0.0.39.

  • Destructible.rescue is gone.
  • Upgrade fracture to 0.3.0-alpha.61.
  • Upgrade destructible to 7.0.0-alpha.62.
  • Upgrade turnstile to 6.0.0-alpha.73.

Release WriteAhead version 0.0.10.

  • Upgrade perhaps to 0.0.6.
  • Upgrade fracture to 0.3.0-alpha.30.
  • Upgrade destructible to 7.0.0-alpha.36.
  • Upgrade turnstile to 6.0.0-alpha.48.
  • Internalize Turnstile.

Release WriteAhead version 0.0.30.

  • Upgrade fracture to 0.3.0-alpha.53.
  • Upgrade operation to 6.0.0-alpha.7.
  • Upgrade destructible to 7.0.0-alpha.54.
  • Upgrade turnstile to 6.0.0-alpha.65.
  • Upgrade interrupt to 11.0.0-alpha.20.

Release WriteAhead version 0.0.41.

  • Upgrade fracture to 0.3.0-alpha.63.
  • Upgrade destructible to 7.0.0-alpha.64.
  • Upgrade turnstile to 6.0.0-alpha.75.
  • Remove Perhaps dependency.

Release WriteAhead version 0.0.5.

  • Update LICENSE for 2021.
  • Upgrade interrupt to 11.0.0-alpha.16.
  • Upgrade transcript to 0.1.4.
  • Caret pin Keyify.
  • Remove Staccato dependency.
  • sync is not the fastest.
  • Thoughts on in-memory log.

Release WriteAhead version 0.0.34.

  • Remove Destructible.copacetic calls.
  • Upgrade fracture to 0.3.0-alpha.57.
  • Upgrade destructible to 7.0.0-alpha.56.
  • Upgrade turnstile to 6.0.0-alpha.69.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.