Packages
rocksdb
2.4.0
3.1.1
3.1.0
3.0.0
2.6.2
2.6.1
retired
2.6.0
retired
2.5.0
2.4.1
2.4.0
2.3.0
2.2.0
2.1.0
2.0.0
1.9.0
1.8.0
1.7.0
1.6.0
1.5.1
1.5.0
1.4.0
1.3.2
1.3.1
1.3.0
1.2.0
1.1.1
1.1.0
1.0.0
0.26.2
0.26.1
0.26.0
0.25.0
0.24.0
0.23.3
0.23.2
0.23.1
0.23.0
0.22.0
0.21.0
0.20.1
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.0
0.14.0
0.13.1
0.13.0
0.12.0
0.11.0
0.10.0
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.1
0.7.0
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
RocksDB for Erlang
Current section
Files
Jump to
Current section
Files
guides/posting_lists.md
# Posting Lists
Posting lists are used for inverted indexes, search engines, and document tagging systems. erlang-rocksdb provides a specialized merge operator for efficient posting list management.
## Overview
A posting list stores a set of keys (e.g., document IDs) associated with a term. The merge operator allows efficient append operations and handles key deletion using tombstones that are automatically cleaned up during merge operations (reads and compaction).
## Binary Format
Each posting list value is a concatenation of entries:
| Field | Size | Description |
|-------|------|-------------|
| KeyLength | 4 bytes (big-endian) | Length of the key data |
| Flag | 1 byte | 0 = normal, non-zero = tombstone |
| KeyData | KeyLength bytes | The key binary |
Example entry for key `<<"doc1">>`:
- Normal: `<<4:32/big, 0, "doc1">>`
- Tombstone: `<<4:32/big, 1, "doc1">>`
## Setup
Open a database with the posting list merge operator:
```erlang
{ok, Db} = rocksdb:open("mydb", [
{create_if_missing, true},
{merge_operator, posting_list_merge_operator}
]).
```
## Adding Keys
Use merge with `{posting_add, Key}`:
```erlang
ok = rocksdb:merge(Db, <<"term:erlang">>, {posting_add, <<"doc1">>}, []),
ok = rocksdb:merge(Db, <<"term:erlang">>, {posting_add, <<"doc2">>}, []),
ok = rocksdb:merge(Db, <<"term:erlang">>, {posting_add, <<"doc3">>}, []).
```
## Deleting Keys
Use merge with `{posting_delete, Key}` to add a tombstone:
```erlang
ok = rocksdb:merge(Db, <<"term:erlang">>, {posting_delete, <<"doc2">>}, []).
```
The key is logically deleted but remains in the binary until compaction.
## Reading Posting Lists
```erlang
{ok, Binary} = rocksdb:get(Db, <<"term:erlang">>, []).
```
The binary contains all entries including tombstones. Use helper functions to process it.
## Helper Functions
### Decode to List
```erlang
Entries = rocksdb:posting_list_decode(Binary),
%% [{<<"doc1">>, false}, {<<"doc2">>, false}, {<<"doc3">>, false}, {<<"doc2">>, true}]
```
### Get Active Keys
Returns deduplicated keys with tombstones filtered out (NIF for efficiency):
```erlang
Keys = rocksdb:posting_list_keys(Binary),
%% [<<"doc1">>, <<"doc3">>]
```
### Check if Key is Active
```erlang
true = rocksdb:posting_list_contains(Binary, <<"doc1">>),
false = rocksdb:posting_list_contains(Binary, <<"doc2">>). % tombstoned
```
### Find Key
Returns `{ok, IsTombstone}` or `not_found`:
```erlang
{ok, false} = rocksdb:posting_list_find(Binary, <<"doc1">>),
{ok, true} = rocksdb:posting_list_find(Binary, <<"doc2">>), % tombstoned
not_found = rocksdb:posting_list_find(Binary, <<"unknown">>).
```
### Count Active Keys
```erlang
Count = rocksdb:posting_list_count(Binary).
```
### Convert to Map
Get the full state as a map:
```erlang
Map = rocksdb:posting_list_to_map(Binary),
%% #{<<"doc1">> => active, <<"doc2">> => tombstone, <<"doc3">> => active}
```
### Fold Over Entries
```erlang
Count = rocksdb:posting_list_fold(
fun(_Key, _IsTombstone, Acc) -> Acc + 1 end,
0,
Binary
).
```
## Tombstone Cleanup
Tombstones are automatically cleaned up by the merge operator:
- **During reads**: When reading a key, the merge operator combines all entries and removes tombstoned keys from the result.
- **During compaction**: The merge operator consolidates entries, keeping only active (non-tombstoned) keys.
This means you don't need a separate compaction filter for tombstone removal - it's built into the merge operator.
## Use Cases
- **Inverted Index**: Map terms to document IDs for full-text search
- **Tagging System**: Map tags to item IDs
- **Graph Adjacency**: Store outgoing edges for each node
- **Set Membership**: Efficient set union via merge operations
## Performance Tips
1. **Batch Writes**: Use write_batch to add multiple keys atomically
2. **Periodic Compaction**: Run compaction to reclaim space from tombstones
3. **Large Lists**: For very large posting lists, consider sharding by key prefix
4. **NIF Functions**: Use `posting_list_keys/1`, `posting_list_contains/2`, `posting_list_find/2`, `posting_list_count/1`, and `posting_list_to_map/1` for efficient access - they are implemented as NIFs and process the binary in C++
## Example: Inverted Index
```erlang
%% Index a document
index_document(Db, DocId, Terms) ->
lists:foreach(fun(Term) ->
ok = rocksdb:merge(Db, <<"term:", Term/binary>>, {posting_add, DocId}, [])
end, Terms).
%% Remove document from index
remove_document(Db, DocId, Terms) ->
lists:foreach(fun(Term) ->
ok = rocksdb:merge(Db, <<"term:", Term/binary>>, {posting_delete, DocId}, [])
end, Terms).
%% Search for documents containing a term
search(Db, Term) ->
case rocksdb:get(Db, <<"term:", Term/binary>>, []) of
{ok, Binary} -> rocksdb:posting_list_keys(Binary);
not_found -> []
end.
%% Check if document contains term
has_term(Db, Term, DocId) ->
case rocksdb:get(Db, <<"term:", Term/binary>>, []) of
{ok, Binary} -> rocksdb:posting_list_contains(Binary, DocId);
not_found -> false
end.
```