Ferricstore.Store.CompoundKey (ferricstore v0.11.1)

Copy Markdown View Source

Encodes and decodes compound keys for data structure storage in Bitcask.

FerricStore stores data structure sub-elements (hash fields, list positions, set members, sorted set entries) as individual Bitcask entries using compound keys. This avoids the need for per-key Bitcask instances while still allowing individual field/member access without reading entire structures.

Key Format

Each compound key has the format:

PREFIX:encoded_redis_key\0sub_key

Where:

  • PREFIX is a single uppercase letter identifying the data type:

    • H for Hash fields
    • L for List elements (sub_key is a float64 position)
    • S for Set members
    • Z for Sorted Set entries
    • T for Type metadata (no sub_key)
  • encoded_redis_key is the binary-safe escaped user-facing Redis key name

  • \0 (null byte) is the separator between the Redis key and sub-key

  • sub_key is the type-specific sub-key (field name, position, member, etc.)

Redis keys are binary-safe, so the logical key component escapes % and null bytes before the null delimiter is appended. Sub-keys remain untouched.

Type Metadata

A type index entry T:keyname stores the type as a simple string value ("hash", "list", "set", "zset", "stream"). This is used by the TYPE command and for WRONGTYPE enforcement -- attempting to use hash commands on a set key returns an error.

Summary

Functions

Decodes a position string back to a float64.

Decodes a stored type string back to the data type atom.

Encodes a float64 position as a fixed-width string that sorts lexicographically in the same order as the numeric values.

Encodes a data type atom to its stored string representation.

Extracts the Redis-level key from a compound key.

Extracts the sub-key portion from a compound key given a known prefix.

Builds a compound key for a hash field.

Returns the scan prefix for all fields of a hash.

Returns true if the given Bitcask key is an internal compound key (starts with a known type prefix like H:, L:, S:, Z:, T:).

Builds a compound key for a list element at a given float64 position.

Builds the metadata compound key for a list.

Returns the scan prefix for all elements of a list.

Builds a compound key for a set member.

Returns the scan prefix for all members of a set.

Builds the durable consumer-group metadata key for a stream group.

Returns the scan prefix for all durable consumer-group metadata of a stream.

Builds the durable metadata key for a stream's length and last-generated-id.

Returns the scan prefix for all entries of a stream.

Builds the type metadata compound key for a Redis key.

Filters a list of raw ETS/store keys to only user-visible logical keys.

Builds a compound key for a sorted set member-to-score mapping.

Returns the scan prefix for all members of a sorted set.

Types

data_type()

@type data_type() ::
  :hash | :list | :set | :zset | :stream | :bloom | :cms | :cuckoo | :topk

Functions

decode_position(encoded)

@spec decode_position(binary()) :: number()

Decodes a position string back to a float64.

Examples

iex> pos = 1000.5
iex> encoded = Ferricstore.Store.CompoundKey.encode_position(pos)
iex> Ferricstore.Store.CompoundKey.decode_position(encoded)
1000.5

decode_type(arg1)

@spec decode_type(binary()) :: data_type()

Decodes a stored type string back to the data type atom.

Examples

iex> Ferricstore.Store.CompoundKey.decode_type("hash")
:hash

encode_position(position)

@spec encode_position(number()) :: binary()

Encodes a float64 position as a fixed-width string that sorts lexicographically in the same order as the numeric values.

Negative positions use a complement encoding so that lexicographic order is preserved.

Examples

iex> a = Ferricstore.Store.CompoundKey.encode_position(1.0)
iex> b = Ferricstore.Store.CompoundKey.encode_position(2.0)
iex> a < b
true

encode_type(atom)

@spec encode_type(data_type()) :: binary()

Encodes a data type atom to its stored string representation.

Examples

iex> Ferricstore.Store.CompoundKey.encode_type(:hash)
"hash"

extract_redis_key(key)

@spec extract_redis_key(binary()) :: binary()

Extracts the Redis-level key from a compound key.

For compound keys like S:myset\0member, returns "myset". For type metadata keys like T:myset, returns "myset". For plain (non-compound) keys, returns the key as-is.

Used by fetch-or-compute ownership fencing to determine which Redis key a compound Raft write affects.

Examples

iex> Ferricstore.Store.CompoundKey.extract_redis_key("S:myset" <> <<0>> <> "member")
"myset"

iex> Ferricstore.Store.CompoundKey.extract_redis_key("H:hash" <> <<0>> <> "field")
"hash"

iex> Ferricstore.Store.CompoundKey.extract_redis_key("T:mykey")
"mykey"

iex> Ferricstore.Store.CompoundKey.extract_redis_key("plain_key")
"plain_key"

extract_subkey(compound_key, prefix)

@spec extract_subkey(binary(), binary()) :: binary()

Extracts the sub-key portion from a compound key given a known prefix.

Examples

iex> prefix = Ferricstore.Store.CompoundKey.hash_prefix("user:123")
iex> compound = Ferricstore.Store.CompoundKey.hash_field("user:123", "name")
iex> Ferricstore.Store.CompoundKey.extract_subkey(compound, prefix)
"name"

hash_field(redis_key, field)

@spec hash_field(binary(), binary()) :: binary()

Builds a compound key for a hash field.

Examples

iex> Ferricstore.Store.CompoundKey.hash_field("user:123", "name")
<<"H:user:123", 0, "name">>

hash_prefix(redis_key)

@spec hash_prefix(binary()) :: binary()

Returns the scan prefix for all fields of a hash.

Examples

iex> Ferricstore.Store.CompoundKey.hash_prefix("user:123")
<<"H:user:123", 0>>

internal_key?(arg1)

@spec internal_key?(binary()) :: boolean()

Returns true if the given Bitcask key is an internal compound key (starts with a known type prefix like H:, L:, S:, Z:, T:).

Used to filter internal keys out of user-facing KEYS and DBSIZE results.

Examples

iex> Ferricstore.Store.CompoundKey.internal_key?("H:user:123" <> <<0>> <> "name")
true

iex> Ferricstore.Store.CompoundKey.internal_key?("my_plain_key")
false

list_element(redis_key, position)

@spec list_element(binary(), float()) :: binary()

Builds a compound key for a list element at a given float64 position.

The position is encoded as a fixed-width, zero-padded float string to ensure lexicographic ordering matches numeric ordering. Format is {sign}{integer_part}.{fractional_part} with 20 digits before and 17 digits after the decimal point.

Examples

iex> key = Ferricstore.Store.CompoundKey.list_element("mylist", 1000.0)
iex> String.starts_with?(key, "L:mylist" <> <<0>>)
true

list_meta_key(redis_key)

@spec list_meta_key(binary()) :: binary()

Builds the metadata compound key for a list.

list_prefix(redis_key)

@spec list_prefix(binary()) :: binary()

Returns the scan prefix for all elements of a list.

Examples

iex> Ferricstore.Store.CompoundKey.list_prefix("mylist")
<<"L:mylist", 0>>

set_member(redis_key, member)

@spec set_member(binary(), binary()) :: binary()

Builds a compound key for a set member.

The member name IS the sub-key; the value stored is "1" (presence marker).

Examples

iex> Ferricstore.Store.CompoundKey.set_member("tags:post:789", "elixir")
<<"S:tags:post:789", 0, "elixir">>

set_prefix(redis_key)

@spec set_prefix(binary()) :: binary()

Returns the scan prefix for all members of a set.

Examples

iex> Ferricstore.Store.CompoundKey.set_prefix("tags:post:789")
<<"S:tags:post:789", 0>>

stream_group(redis_key, group)

@spec stream_group(binary(), binary()) :: binary()

Builds the durable consumer-group metadata key for a stream group.

stream_group_prefix(redis_key)

@spec stream_group_prefix(binary()) :: binary()

Returns the scan prefix for all durable consumer-group metadata of a stream.

stream_meta_key(redis_key)

@spec stream_meta_key(binary()) :: binary()

Builds the durable metadata key for a stream's length and last-generated-id.

stream_prefix(redis_key)

@spec stream_prefix(binary()) :: binary()

Returns the scan prefix for all entries of a stream.

type_key(redis_key)

@spec type_key(binary()) :: binary()

Builds the type metadata compound key for a Redis key.

Examples

iex> Ferricstore.Store.CompoundKey.type_key("user:123")
"T:user:123"

user_visible_keys(raw_keys)

@spec user_visible_keys([binary()]) :: [binary()]

Filters a list of raw ETS/store keys to only user-visible logical keys.

  • Rejects all internal compound keys (H:, L:, S:, Z:, V:, VM:, PM:, LM:)
  • Extracts logical keys from type registry entries (T:key -> key)
  • Keeps plain string keys as-is
  • Deduplicates (a key may exist both as a plain entry and a T: entry)

zset_member(redis_key, member)

@spec zset_member(binary(), binary()) :: binary()

Builds a compound key for a sorted set member-to-score mapping.

Stores member -> score for O(1) score lookups by member.

Examples

iex> Ferricstore.Store.CompoundKey.zset_member("leaderboard", "alice")
<<"Z:leaderboard", 0, "alice">>

zset_prefix(redis_key)

@spec zset_prefix(binary()) :: binary()

Returns the scan prefix for all members of a sorted set.

Examples

iex> Ferricstore.Store.CompoundKey.zset_prefix("leaderboard")
<<"Z:leaderboard", 0>>