Partitioner

Assigns an aggregate to a partition. The hash is part of the storage format: it is fixed across JVMs, processes and versions (64-bit over context.aggregate/tenant/id, never String.hashCode), so every node and every release agrees, and changing it means repartitioning the log.

Slots. An aggregate hashes first to one of SLOTS fixed slots, slot = floorMod(hash, 4096), which never changes; its partition is slot mod P. P must be a power of two that divides SLOTS (1, 2, 4, …, 4096, isValidPartitions): a log, a command topic or a host configured with another count fails at start. Since P divides 4096, floorMod(hash, 4096) mod P = floorMod(hash, P), so the slots assign every aggregate to the partition it had before they existed: logs, checkpoints and Kafka command partitions are unaffected. They are there for the online splits of 10.x, which double P: partition p splits into p and p + P by the next bit of the slot, so an aggregate only ever moves from its partition to that partition's own child, and history stays where it is.

Distribution. A slot is the low 12 bits of the hash, and FNV-1a's low bits depend only on the low bits of each byte (bit k of the hash on bits 0..k of the bytes): the 12 bits of a slot see every bit of every byte, but a partition of a small P sees only the low bits of each character, so keys that differ only in a character's high bits (a and A differ in bit 5) share a partition when P is at most 32. Over keys as ids are made (sequences, UUIDs, snowflakes) the slots and partitions stay uniform; PartitionerTest checks it. A mixer would fix the low bits, but it would change every assignment: a new format, not this one.

Properties

Link copied to clipboard
const val SLOTS: Int = 4096

The fixed number of slots aggregates hash to; a valid partition count divides it.

Functions

Link copied to clipboard
fun hash(text: String): Long

64-bit FNV-1a over the UTF-8 bytes of text.

Link copied to clipboard
fun isValidPartitions(partitions: Int): Boolean

Whether partitions is a valid partition count: a power of two that divides SLOTS.

Link copied to clipboard

The text the partition is hashed from; part of the format, and deliberately independent of AggregateRef.toString, which escapes its separators: this key keeps them raw, so an aggregate whose tenant or id holds a / or a % stays in the partition its stored streams are in. That two refs may share a key only puts them in one partition.

Link copied to clipboard
fun partitionOf(ref: AggregateRef, partitions: Int): Int

The partition of ref among partitions: its slotOf modulo partitions, a valid count (requirePartitions).

Link copied to clipboard
fun requirePartitions(partitions: Int): Int

Returns partitions when it is a valid partition count (isValidPartitions); fails with an IllegalArgumentException that says why otherwise. Every log, command topic and host configuration checks its partition count with it at start.

Link copied to clipboard

The slot of ref, in 0 until SLOTS: fixed whatever the partition count.