npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@zamurai/wal

v0.4.0

Published

@zamurai/wal is an efficient write-ahead log implementation for Node.js

Downloads

306

Readme

WAL.js

Status Type Auther License

waljs is an efficient write-ahead log implementation for Node.js.

The main goal of a Write-ahead Log (WAL) is to make the application more durable, so it does not lose data in case of a crash. WALs are used in applications such as database systems to flush all written data to disk before the changes are written to the database. In case of a crash, the WAL enables the application to recover lost in-memory changes by reconstructing all required operations from the log.

Table of Contents

Installation

Just simply run the following command

npm install --save @zamurai/wal

, Or if you're using yarn

yarn add @zamurai/wal

Initialization

Use the following steps to initialize an instance of a wal.

// Fist register the entries factory functions
EntryRegistry.register(() => EntryExample1());
EntryRegistry.register(() => EntryExample2());
EntryRegistry.register(() => EntryExample3());

// Initialize the wall
const wal = new WAL(walDirPath);
await wal.init();

Usage

Write

Use write to write an entry to the wal.

await wal.write(new EntryExample1(data1));
await wal.write(new EntryExample1(data2));
await wal.write(new EntryExample2(data3));

commit

Use commit/commitUpTo to commit the entries to the wal.

await wal.commit(index); // Will commit entry at the given index.

// Or, you can use the following to commit all entries up to the given index.
await wal.commitUpTo(index); 

Recovery

You can also recover the WAL using the following call.

await wal.recover(); // Will remove all uncommitted entries.

// Or you can use the following to recover the WAL and do something with the uncommitted entries.
await wal.recover(async (index, entry): boolean => {
  // Do something with the recovered entry.
  return true; // Return false to stop the recovery process.
}); // Will recover all entries.

Compaction

Compact

You can compact the WAL using the following call.

await wal.compact(); // Will remove all committed entries and segments.
// Method returns a boolean indicating if the compact was done.

This will remove all committed entries from the WAL and all dead segments and keep the uncommitted entries.

Archive

Or if you don't want to delete older entries and keep them on the side for later you can use the following to archive the WAL into a separate archive directory while keeping the uncommitted entries and active segments in the WAL directory.

const archived = await wal.archive(archivePath); // Will move all uncommitted entries and segments to the archive directory.
// Method returns a boolean indicating if the archive was done.

[!NOTE] This library takes no automatic action to compact/archive the WAL. You need to call these methods manually based on your application's requirements.

We encourage you to either use the compact or archive method regularly to keep the WAL size in check.

[!IMPORTANT] archive and compact methods will return boolean indicating if the operation was successful or not.

If the operation was not successful, it means that the WAL is in a state where it cannot be compacted or archived. i.e. there are not enough committed entries to compact or there are no dead segments (Segments that has all its entries committed) to archive.

You can control the minimum number of entries required for compaction using the minEntriesForCompaction configuration described below.

Closing

When you're done using the WAL, you can stop it using the following call.

await wal.close();

Configuration

{
  // log function used to log internal messages.
  // Default is NOOP.
  logger?: (level: string, msg: string, attrs?: Record<string, unknown>) => void;

  // The maximum size of a single WAL segment file in bytes. 
  // Default is 10MB.
  maxSegmentSize?: number;

  // The minimum number of committed entries required to be ready for compaction.
  // Default is 1000 entries.
  minEntriesForCompaction?: number;

  // Configuration for metadata file.
  meta?: {
    // If buffering is enabled, the WAL will buffer **METADATA** writes (i.e. head, commitIndex) in memory before writing them to disk. 
    // Note that this does not affect the WAL entries themselves, which are always written to disk immediately.
    // Also note that buffering may cause metadata data loss in case of a crash.
    // Default is true.
    bufferingEnabled?: boolean;

    // The maximum number of the metadata updates buffer.
    // When this size is reached, the WAL will flush the buffer to disk. Even if the autoSyncInterval is not reached.
    // Default 1024 updates.
    maxBufferSize?: number;

    // The interval in milliseconds at which the WAL will sync the metadata to disk. 
    // Default is 1000ms.
    autoSyncInterval?: number;
  };
};

How it works

Each WAL.write(…) call creates a binary encoding of the passed IEntry which we call the entry's payload. This payload is written to disk together with some metadata such as the entry type, a CRC checksum and an offset number.

The full binary layout looks like the following:

// Every Entry is written, using the following binary layout (big endian format):
//
//	  ┌─────────────┬───────────┬──────────┬─────────┐
//	  │ Offset (4B) │ Type (1B) │ CRC (4B) │ Payload │
//	  └─────────────┴───────────┴──────────┴─────────┘
//
//		- Offset = 32bit WAL entry number for each record in order to implement a low-water mark
//		- Type = Type of WAL entry
//		- CRC = 32bit hash computed over the payload using CRC
//		- Payload = The actual WAL entry payload data

This data is appended to a file and the WAL makes sure that it is actually written to non-volatile storage rather than just being stored in a memory-based write cache that would be lost if power failed (see [fsynced][fsync]).

When the WAL file reaches a configurable maximum size, it is closed and the WAL starts to append its records to a new and empty file. These files are called WAL segments. Typically, the WAL is split into multiple segments to enable other processes to take care of cleaning old segments, implement WAL segment backups and more. When the WAL is started, it will resume operation at the end of the last open segment file.

Benchmarks

These benchmarks are run on a machine with the following specifications:

  • OS: MacOS
  • CPU: Apple M1 Pro Chip
  • RAM: 16GB
  • Node: v18.15.0

WAL write

| name | ops | margin | | -------------------------- | ------- | ------ | | WAL write with sync | 56317 | ±4.95% |

Contributing

Please read CONTRIBUTING.md for details on our code of conduct and on the process for submitting pull requests to this repository.

Versioning

THIS SOFTWARE IS STILL IN ALPHA AND THERE ARE NO GUARANTEES REGARDING API STABILITY YET.

All significant (e.g. breaking) changes are documented in the CHANGELOG.md.

After the v1.0 release we plan to use SemVer for versioning. For the versions available, see the releases page.

Acknowledgments

This work was inspired by the WAL implementation in Go by fgrosse.

What's next?

  • [x] Add support for WAL Recovery
  • [x] Add support for WAL compaction
  • [ ] Add support for WAL compression
  • [ ] Add support for WAL encryption