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_keyWhere:
PREFIXis a single uppercase letter identifying the data type:Hfor Hash fieldsLfor List elements (sub_key is a float64 position)Sfor Set membersZfor Sorted Set entriesTfor Type metadata (no sub_key)
encoded_redis_keyis the binary-safe escaped user-facing Redis key name\0(null byte) is the separator between the Redis key and sub-keysub_keyis 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
Functions
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
Decodes a stored type string back to the data type atom.
Examples
iex> Ferricstore.Store.CompoundKey.decode_type("hash")
:hash
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
Encodes a data type atom to its stored string representation.
Examples
iex> Ferricstore.Store.CompoundKey.encode_type(:hash)
"hash"
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"
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"
Builds a compound key for a hash field.
Examples
iex> Ferricstore.Store.CompoundKey.hash_field("user:123", "name")
<<"H:user:123", 0, "name">>
Returns the scan prefix for all fields of a hash.
Examples
iex> Ferricstore.Store.CompoundKey.hash_prefix("user:123")
<<"H:user:123", 0>>
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
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
Builds the metadata compound key for a list.
Returns the scan prefix for all elements of a list.
Examples
iex> Ferricstore.Store.CompoundKey.list_prefix("mylist")
<<"L:mylist", 0>>
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">>
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>>
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.
Examples
iex> Ferricstore.Store.CompoundKey.type_key("user:123")
"T:user:123"
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)
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">>
Returns the scan prefix for all members of a sorted set.
Examples
iex> Ferricstore.Store.CompoundKey.zset_prefix("leaderboard")
<<"Z:leaderboard", 0>>