Skip to content

Metadata Passthrough

ConvergeDB supports attaching opaque metadata to ASSERT, PATCH, and RETRACT operations. Metadata describes the cause of a mutation (for example, which user triggered it or which reducer produced it), not the result.

  • Transient. Metadata is not stored in the entity table or WAL. It lives only in the accumulator during the coalescing window and is forwarded to subscribers in the resulting notification.
  • Last-write-wins. If multiple writes to the same entity within a coalescing window carry metadata, only the metadata from the last write is forwarded.
  • Not in queries. Metadata is absent from QUERY responses, bootstrap snapshots, and scan results. It is only available in live subscriber notifications.
  • Size limit. Maximum 65,535 bytes per operation (u16 length prefix). See Limits.

Attach metadata to any write operation. The EntityMetadata constructor accepts key-value tuples for concise inline usage:

var metadata = new EntityMetadata(("ci", "player-123"), ("rd", "FillOrder"));
await players.AssertAsync(player, metadata: metadata);
await players.PatchAsync(patch, metadata: metadata);
await players.RetractAsync(entityId, metadata: metadata);

For dynamic metadata or binary values, use MetadataBuilder:

var metadata = new MetadataBuilder()
.Set("ci", callerIdentityBytes)
.Set("rd", "UpdatePlayerPosition")
.Build();

Metadata is available on notifications and events via the Metadata property. Parse it with MetadataReader to extract key-value pairs:

await foreach (var change in players.SubscribeAsync(bootstrap: false))
{
if (change.Metadata.Length > 0)
{
var meta = MetadataReader.Parse(change.Metadata);
string? caller = meta.GetString("ci");
string? reducer = meta.GetString("rd");
// Use metadata for routing, filtering, or audit logging.
if (reducer == "TradeExecuted")
await RecordTradeAsync(change, caller);
}
}

Metadata works the same way on event stream notifications (EntityEvent<T>.Metadata).

Metadata is encoded as a trailing metadata_len(u16) + metadata(bytes) on ASSERT, PATCH, and RETRACT frames. Old clients that do not send metadata simply omit the trailing bytes. Old subscribers that do not understand metadata ignore the trailing bytes in STATE notifications. This makes metadata backward-compatible in both directions.

Metadata is useful when subscribers need to know why an entity changed, not just what changed:

  • Audit trails. Attach the caller identity and the operation that triggered the mutation.
  • Reducer attribution. In game server architectures, attach the reducer name so subscribers can distinguish between different game logic paths that produced the same entity state.
  • Correlation IDs. Attach a request ID for distributed tracing across services.

If you only need to know what changed, use ChangedFields instead.