Bedrock.DataPlane.CommitProxy.Finalization (bedrock v0.7.0)
View SourceTransaction finalization pipeline that handles conflict resolution and log persistence.
Version Chain Integrity
CRITICAL: This module maintains the Lamport clock version chain established by the sequencer.
The sequencer provides both last_commit_version and commit_version as a proper chain link:
last_commit_version: The actual last committed version from the sequencercommit_version: The new version assigned to this batch
Always use the exact version values provided by the sequencer through the batch to maintain proper MVCC conflict detection and transaction ordering. Version gaps can exist due to failed transactions, recovery scenarios, or system restarts.
Summary
Types
Applies a batch's committed metadata window to the commit proxy's routing
state, serialized in commit-version order, and returns the routing snapshot
the batch must push with. The window's entries are plain
{version, [mutation]} - verdicts have already been resolved.
Functions
Executes the complete transaction finalization pipeline for a batch of transactions.
The privatized copies a committed mutation must also put on the stream.
Pushes transactions directly to logs and waits for acknowledgement from ALL log servers.
Types
@type abort_reply_fn() :: ([Bedrock.DataPlane.CommitProxy.Batch.reply_fn()] -> :ok)
@type async_stream_fn() :: (enumerable :: Enumerable.t(), fun :: (term() -> term()), opts :: keyword() -> Enumerable.t())
@type finalization_error() :: resolution_error() | storage_coverage_error() | log_push_error() | recovery_required_error()
@type log_push_batch_fn() :: (last_commit_version :: Bedrock.version(), transactions_by_log :: %{ required(Bedrock.DataPlane.Log.id()) => Bedrock.DataPlane.Transaction.encoded() }, commit_version :: Bedrock.version(), opts :: [ log_services: %{ required(Bedrock.DataPlane.Log.id()) => pid() | {atom(), node()} }, timeout: Bedrock.timeout_in_ms(), async_stream_fn: async_stream_fn() ] -> :ok | {:error, log_push_error() | recovery_required_error()})
@type log_push_error() :: {:log_failures, [{Bedrock.DataPlane.Log.id(), term()}]} | :log_push_failed
@type log_push_single_fn() :: (Bedrock.ControlPlane.Config.ServiceDescriptor.t(), binary(), Bedrock.version() -> :ok | {:error, :unavailable})
@type metadata_apply_fn() :: (commit_version :: Bedrock.version(), Bedrock.DataPlane.Resolver.metadata_window() -> {:ok, Bedrock.DataPlane.CommitProxy.RoutingData.t()} | {:error, term()})
Applies a batch's committed metadata window to the commit proxy's routing
state, serialized in commit-version order, and returns the routing snapshot
the batch must push with. The window's entries are plain
{version, [mutation]} - verdicts have already been resolved.
@type metadata_mutations() :: [Bedrock.Internal.TransactionBuilder.Tx.mutation()]
@type recovery_required_error() :: {:recovery_required, log_push_error()}
@type resolution_error() :: :timeout | :unavailable | {:resolver_unavailable, term()}
@type resolver_fn() :: (Bedrock.DataPlane.Resolver.ref(), Bedrock.epoch(), Bedrock.version(), Bedrock.version(), [Bedrock.DataPlane.Transaction.encoded()], [metadata_mutations()], keyword() -> {:ok, [non_neg_integer()], Bedrock.DataPlane.Resolver.metadata_window()} | {:error, term()})
@type sequencer_notify_fn() :: (Bedrock.DataPlane.Sequencer.ref(), Bedrock.epoch(), Bedrock.version(), opts :: keyword() -> :ok | {:error, term()})
@type storage_coverage_error() :: {:storage_team_coverage_error, binary()}
@type success_reply_fn() :: ([ {Bedrock.DataPlane.CommitProxy.Batch.reply_fn(), non_neg_integer(), non_neg_integer()} ], Bedrock.version() -> :ok)
Functions
@spec create_finalization_plan(Bedrock.DataPlane.CommitProxy.Batch.t()) :: Bedrock.DataPlane.CommitProxy.Finalization.FinalizationPlan.t()
@spec extract_result_or_handle_error( Bedrock.DataPlane.CommitProxy.Finalization.FinalizationPlan.t(), keyword() ) :: {:ok, non_neg_integer(), non_neg_integer()} | {:error, finalization_error()}
@spec finalize_batch( Bedrock.DataPlane.CommitProxy.Batch.t(), opts :: [ epoch: Bedrock.epoch(), sequencer: pid(), resolver_layout: Bedrock.DataPlane.CommitProxy.ResolverLayout.t(), resolver_fn: resolver_fn(), resolver_timeout_in_ms: non_neg_integer(), proxy_id: pid(), metadata_apply_fn: metadata_apply_fn(), batch_log_push_fn: log_push_batch_fn(), abort_reply_fn: abort_reply_fn(), success_reply_fn: success_reply_fn(), async_stream_fn: async_stream_fn(), log_push_fn: log_push_single_fn(), sequencer_notify_fn: sequencer_notify_fn(), timeout: non_neg_integer() ] ) :: {:ok, n_aborts :: non_neg_integer(), n_oks :: non_neg_integer()} | {:error, finalization_error()}
Executes the complete transaction finalization pipeline for a batch of transactions.
This function processes a batch through a multi-stage pipeline: conflict resolution, abort notification, log preparation, log persistence, sequencer notification, and success notification. The pipeline maintains transactional consistency by ensuring all operations complete successfully or all pending clients are notified of failure.
Pipeline Stages
- Conflict Resolution: Calls resolvers to determine which transactions must be aborted
- Abort Notification: Immediately notifies clients of aborted transactions
- Log Preparation: Distributes successful transaction mutations to appropriate logs
- Log Persistence: Pushes transactions to ALL log servers and waits for acknowledgment
- Sequencer Notification: Reports successful commit version to the sequencer
- Success Notification: Notifies clients of successful transactions with commit version
Metadata Distribution
During conflict resolution, metadata mutations (keys with \xFF prefix) are
extracted from each transaction and sent to every resolver along with the
stable proxy identity. Each resolver returns the proxy's exact metadata
window. The batch then makes one
metadata_apply_fn call: the commit proxy server applies the window - plus,
in sharded mode, the batch's own globally-committed metadata - serialized in
batch-sequence order, and returns the immutable routing snapshot the batch
pushes with.
With SHARDED resolvers no resolver knows the merged global abort set - and
none needs to: every resolver receives every transaction's metadata and
records it with its LOCAL verdict; the window merge ANDs the verdicts
positionally into the exact global verdict (see
Bedrock.DataPlane.Resolver).
Parameters
batch: Transaction batch with commit version details from the sequenceropts: Required configuration (epoch, sequencer, resolver_layout, routing_data) plus optional functions for testing and configuration overrides
Returns
{:ok, n_aborts, n_successes}- Pipeline completed{:error, finalization_error()}- Pipeline failed; all pending clients notified of failure
Error Handling
On any pipeline failure, all transactions that haven't been replied to are automatically notified with abort responses before returning the error.
@spec mutation_to_key_or_range( {:set, Bedrock.key(), Bedrock.value()} | {:clear, Bedrock.key()} | {:clear_range, Bedrock.key(), Bedrock.key()} | {:atomic, atom(), Bedrock.key(), Bedrock.value()} ) :: Bedrock.key() | {Bedrock.key(), Bedrock.key()}
@spec notify_sequencer( Bedrock.DataPlane.CommitProxy.Finalization.FinalizationPlan.t(), Bedrock.DataPlane.Sequencer.ref(), keyword() ) :: Bedrock.DataPlane.CommitProxy.Finalization.FinalizationPlan.t()
@spec notify_successes( Bedrock.DataPlane.CommitProxy.Finalization.FinalizationPlan.t(), keyword() ) :: Bedrock.DataPlane.CommitProxy.Finalization.FinalizationPlan.t()
@spec prepare_for_logging( Bedrock.DataPlane.CommitProxy.Finalization.FinalizationPlan.t() ) :: Bedrock.DataPlane.CommitProxy.Finalization.FinalizationPlan.t()
@spec privatized_mutations(term()) :: [{term(), non_neg_integer()}]
The privatized copies a committed mutation must also put on the stream.
A membership CLEAR retires the worker it names, and the worker must
learn it IN-BAND — from the stream it already follows — rather than
waiting for the next recovery push. So the proxy emits a second copy of
the mutation, prefixed past end_of_keyspace/0 and addressed to the
shard tag the key itself carries.
The prefix does two jobs, both FDB's (ApplyMetadataMutation.cpp:291-317
privatizes serverKeys with withPrefix(systemKeys.begin) and
toCommit->addTag(tag)): it moves the key outside every shard's range
so no materializer can ever store it, and it makes the notice
unforgeable, because commit ingress rejects that range in BOTH modes —
only this function, running after validation, can produce one.
Only removals travel. A worker gaining membership learns it from the distributor that recruited it, so a privatized SET would be a mutation with no reader.
@spec push_to_logs( Bedrock.DataPlane.CommitProxy.Finalization.FinalizationPlan.t(), keyword() ) :: Bedrock.DataPlane.CommitProxy.Finalization.FinalizationPlan.t()
@spec push_transaction_to_logs_direct( last_commit_version :: Bedrock.version(), %{ required(Bedrock.DataPlane.Log.id()) => Bedrock.DataPlane.Transaction.encoded() }, commit_version :: Bedrock.version(), opts :: [ log_services: %{ required(Bedrock.DataPlane.Log.id()) => pid() | {atom(), node()} }, async_stream_fn: async_stream_fn(), log_push_fn: (pid() | {atom(), node()}, binary(), Bedrock.version() -> :ok | {:error, term()}), timeout: non_neg_integer() ] ) :: :ok | {:error, log_push_error() | recovery_required_error()}
Pushes transactions directly to logs and waits for acknowledgement from ALL log servers.
This function takes transactions that have already been built per log and pushes them to the appropriate log servers. Each log receives its pre-built transaction. All logs must acknowledge to maintain durability guarantees.
Parameters
last_commit_version: The last known committed version; used to ensure consistency in log ordering.transactions_by_log: Map of log_id to transaction for that log. May be empty transactions if all transactions were aborted.commit_version: The version assigned by the sequencer for this batch.opts: Optional configuration for testing and customization.
Options
:log_services- Map of log_id to service ref (pid or {name, node}) - REQUIRED:async_stream_fn- Function for parallel processing (default: Task.async_stream/3):log_push_fn- Function for pushing to individual logs (default: try_to_push_transaction_to_log_direct/3):timeout- Timeout for log push operations (default: 5_000ms)
Returns
:okif acknowledgements have been received from ALL log servers.{:error, log_push_error()}if any log has not successfully acknowledged the push within the timeout period or another non-fatal error occurs.{:error, recovery_required_error()}if a log reports that the current transaction-system epoch cannot safely continue.
@spec reject_invalid_transactions( Bedrock.DataPlane.CommitProxy.Finalization.FinalizationPlan.t() ) :: Bedrock.DataPlane.CommitProxy.Finalization.FinalizationPlan.t()
@spec reply_to_all_clients_with_aborted_transactions([ Bedrock.DataPlane.CommitProxy.Batch.reply_fn() ]) :: :ok
@spec resolve_conflicts( Bedrock.DataPlane.CommitProxy.Finalization.FinalizationPlan.t(), Bedrock.epoch(), Bedrock.DataPlane.CommitProxy.ResolverLayout.t(), keyword() ) :: Bedrock.DataPlane.CommitProxy.Finalization.FinalizationPlan.t()
@spec send_reply_with_commit_version_and_index( [ {Bedrock.DataPlane.CommitProxy.Batch.reply_fn(), non_neg_integer(), non_neg_integer()} ], Bedrock.version() ) :: :ok
@spec try_to_push_transaction_to_log( Bedrock.ControlPlane.Config.ServiceDescriptor.t(), binary(), Bedrock.version(), Bedrock.version() | nil ) :: :ok | {:error, :unavailable}
@spec try_to_push_transaction_to_log_direct( pid() | {atom(), node()}, binary(), Bedrock.version(), Bedrock.version() | nil ) :: :ok | {:error, term()}