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 bytes to any write operation:

var metadata = new MetadataBuilder()
.Set("ci", callerIdentityBytes)
.Set("rd", "UpdatePlayerPosition")
.Build();
await players.AssertAsync(player, metadata: metadata);
await players.PatchAsync(patch, metadata: metadata);
await players.RetractAsync(entityId, metadata: metadata);

Metadata is available on the ChangeNotification via the Metadata property:

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");
Console.WriteLine($"Changed by {caller} via {reducer}");
}
}

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.