Advanced Features Guide
View SourceThis guide gives practical Erlang examples for two of Barrel DocDB's advanced features: replication and clock synchronization. All calls run in-process against the barrel_docdb, barrel_rep, and barrel_rep_tasks modules.
HTTP access
barrel_docdb has no built-in HTTP server. To drive replication or reach a database over the wire, run the
barrel_serverapp. For the sync wire and over-the-network setup, see the umbrella guidesdocs/guides/synchronization.mdanddocs/guides/rest-server.md, and this app's Replication Guide.
Replication
Replication synchronizes documents between databases. Documents are transferred with their full revision history, enabling automatic conflict detection.
One-Shot Replication
Copy all documents from source to target within the same VM:
%% Create source and target databases
{ok, _} = barrel_docdb:create_db(<<"source">>),
{ok, _} = barrel_docdb:create_db(<<"target">>),
%% Add documents to source
{ok, _} = barrel_docdb:put_doc(<<"source">>, #{
<<"id">> => <<"user1">>,
<<"name">> => <<"Alice">>, <<"role">> => <<"admin">>, <<"active">> => true
}),
{ok, _} = barrel_docdb:put_doc(<<"source">>, #{
<<"id">> => <<"user2">>,
<<"name">> => <<"Bob">>, <<"role">> => <<"user">>, <<"active">> => true
}),
%% Replicate source -> target
{ok, Result} = barrel_rep:replicate(<<"source">>, <<"target">>).Result is a statistics map:
#{
ok => true,
docs_read => 2,
docs_written => 2,
last_seq => ...
}Replication to a Remote Node
Replicate to another node by using the HTTP transport for the remote endpoint. The remote must run barrel_server, which serves the /db/:db/_sync/* wire:
{ok, Result} = barrel_rep:replicate(
<<"source">>,
<<"http://remote-node:8080/db/target">>,
#{
source_transport => barrel_rep_transport_local,
target_transport => barrel_rep_transport_http
}).Credentials come from auth => #{token => Bin} on the endpoint or the {barrel_docdb, sync_auth, #{Origin => Token}} app env, so secrets never land in persisted task configs. See the Replication Guide for transport details.
Filtered Replication by Path
Replicate only documents matching specific path patterns:
{ok, _} = barrel_docdb:put_doc(<<"source">>, #{
<<"id">> => <<"order1">>,
<<"type">> => <<"order">>, <<"total">> => 150, <<"customer">> => <<"alice">>
}),
{ok, _} = barrel_docdb:put_doc(<<"source">>, #{
<<"id">> => <<"invoice1">>,
<<"type">> => <<"invoice">>, <<"amount">> => 150, <<"customer">> => <<"alice">>
}),
%% Replicate only orders (filter by the type/order path)
{ok, Result} = barrel_rep:replicate(<<"source">>, <<"orders_only">>, #{
filter => #{paths => [<<"type/order">>]}
}).Filtered Replication by Query
Replicate documents matching query conditions:
%% Replicate only active users
{ok, Result} = barrel_rep:replicate(<<"source">>, <<"active_users">>, #{
filter => #{
query => #{
where => [
{path, [<<"role">>], <<"user">>},
{path, [<<"active">>], true}
]
}
}
}).Path and query filters combine with AND logic: a document must match all specified filters to be replicated.
Bidirectional Replication
Sync changes in both directions by running replication each way:
%% Replicate A -> B
{ok, _} = barrel_rep:replicate(<<"db_a">>, <<"db_b">>),
%% Replicate B -> A
{ok, _} = barrel_rep:replicate(<<"db_b">>, <<"db_a">>).Continuous Replication
barrel_rep:replicate/2,3 runs once and returns. For replication that keeps running as changes happen and survives node restarts, start a task with barrel_rep_tasks:
{ok, TaskId} = barrel_rep_tasks:start_task(#{
source => <<"source">>,
target => <<"http://remote-node:8080/db/target">>,
mode => continuous,
target_transport => barrel_rep_transport_http
}),
ok = barrel_rep_tasks:pause_task(TaskId),
ok = barrel_rep_tasks:resume_task(TaskId),
{ok, Tasks} = barrel_rep_tasks:list_tasks().Verify Replication with the Changes Feed
Check that documents were replicated by comparing changes feeds:
%% Changes from source
{ok, SourceChanges, _} = barrel_docdb:get_changes(<<"source">>, first),
%% Changes from target (should match)
{ok, TargetChanges, _} = barrel_docdb:get_changes(<<"target">>, first).Clock Synchronization (HLC)
Barrel DocDB uses Hybrid Logical Clocks (HLC) to maintain causal ordering across distributed nodes. HLC combines physical wall clock time with a logical counter, ensuring that events are correctly ordered even when physical clocks drift.
Reading and Advancing the Clock
%% Current HLC timestamp
Ts = barrel_docdb:get_hlc(),
%% Generate a new timestamp (advances the clock)
NewTs = barrel_docdb:new_hlc().Synchronizing with a Remote Clock
When you receive an HLC from another node, fold it into the local clock:
{ok, SyncedTs} = barrel_docdb:sync_hlc(RemoteHlc).Clock synchronization also happens automatically during replication: every exchange between peers piggybacks the current clock value, so causal ordering across nodes is preserved without an explicit sync step.
Why HLC Matters
HLC ensures:
- Causal ordering: Events that causally depend on each other are correctly ordered
- Conflict detection: During replication, conflicts are detected based on HLC timestamps
- Change feed consistency: The changes feed returns events in HLC order
- Cross-node consistency: All nodes agree on event ordering regardless of clock drift
HLC in the Changes Feed
get_changes/2,3 returns the last HLC it observed. Use it to resume the feed from where you left off:
{ok, Changes, LastHlc} = barrel_docdb:get_changes(<<"mydb">>, first),
%% ... process Changes ...
%% Resume from the last HLC
{ok, NewChanges, NewHlc} = barrel_docdb:get_changes(<<"mydb">>, LastHlc).Troubleshooting
Count Documents
{ok, Count} = barrel_docdb:fold_docs(<<"mydb">>,
fun(_Doc, Acc) -> {ok, Acc + 1} end, 0).Inspect the Changes Feed
{ok, Changes, LastHlc} = barrel_docdb:get_changes(<<"mydb">>, first),
Total = length(Changes).Database Info
{ok, Info} = barrel_docdb:db_info(<<"mydb">>).