malachi

A node just failed. How many orders were lost? None.

A topic split in two. How many records were copied? None.

A node restarted. How many cursors broke? None.

Open source reimplementation of LinkedIn's NorthGuard

Clients speak topics, keys and opaque cursors, never partitions or offsets, so Malachi can split, merge and restripe its storage underneath without breaking them.

What the code promises.

The model

A range grows, divides, and seals what it leaves behind.

Each dot is a record landing on a key; the keyspace runs top to bottom, time runs left to right. A range takes appends into its active segment. When its load passes a threshold it splits into two buddy halves of the keyspace, and the parent's segment is sealed first, behind a fence, before either child takes a write. When load falls, buddies merge back. The cursor a client holds never changes shape.

  • active segment
  • sealed segment
  • seal fence
  • range boundary

Inside a topic

See where every record lives.

Highlight

Topic

A topic is a keyspace divided into ranges. Its active ranges cover the keyspace exactly once, and they split and merge at runtime. Here, range 1 split into ranges 2 and 3.

  • active
  • sealed
  • write position · split point

What you get

The NorthGuard shape, on the BEAM.

Ranges instead of partitions

A record's key hashes to a position in the topic's keyspace, and that position falls in exactly one range. Split and merge are metadata operations: segments are never physically combined or copied, and a merge happens only between buddy ranges. Same-key ordering survives resharding.

Children per split
2 buddies
Segments copied by a split or merge
0
Wire nouns
topic · key · cursor

How it works

Three steps, one cursor.

  1. 01

    Produce to a range

    The key routes each record to the one range that owns its slice of the keyspace. When a range gets busy it splits at the midpoint of its keyspace: its segment is sealed, and each half goes to a new child range.

    Records land on keys (circles). The busy range splits at its midpoint (accent line); its records are sealed (squares) and the two halves take new writes.
  2. 02

    Replicate and seal

    The primary ships each batch to its followers and acknowledges it once a majority has fsynced it. A full, old or failed segment is sealed on every copy and never edited again.

    active ─ 1 GB · 1 h · failure ─▶ sealed
    Top row: the primary; below, two followers. A record turns solid on the primary once a second copy has it. A full segment seals on all three.
  3. 03

    Consume from a cursor

    A fetch returns records and an opaque cursor; pass it back to resume. After a split, the read drains the parent's sealed records first, then follows its key into the child.

    The cursor (accent line) reads the sealed parent, stops, resumes where it left off, and continues into the child range.

Compared

How Malachi differs from the logs you know.

Grow a hot topic without moving its keys.

One key in accent. Left: a fourth partition is added and the key's new records go elsewhere. Right: the range splits and the key stays in the half that contains it.

Apache Kafka

  • Partitions can be added but not removed.
  • Adding partitions does not move existing records, so a key's new records can go to a different partition than its old ones.
  • Consumers track one offset per partition.
  • A write commits once every in-sync replica (ISR) has it.

Malachi

  • Ranges split and merge at runtime.
  • A key always maps to the one range that contains it, and reads drain the parent before the child.
  • Clients hold an opaque cursor.
  • A write is acknowledged once a majority of copies have fsynced it.

Sources: Kafka docs, Basic operations · Kafka docs, Design · Confluent Platform docs

Apache Kafka, Apache Pulsar and Redpanda are trademarks of their owners. Malachi is not affiliated with or endorsed by them. Every claim above was checked against the linked documentation.

Run Malachi on your machine.

Malachi listens on 4040 and the dashboard on 4041. It is in active development and not production-ready.

Quickstart
# Set your own admin password (12 characters or more) in the shell first:
# export MALACHI_ADMIN_PASS='your own password'
docker run \
  -p 127.0.0.1:4040:4040 \
  -p 127.0.0.1:4041:4041 \
  -e MALACHI_ADMIN_PASS \
  -e MALACHI_REQUIRE_TLS=false \
  hectorcardoso/malachi:latest