Skip to content

Bootstrap

When you subscribe with bootstrap: true, the server sends a complete snapshot of all existing entities before transitioning to live notifications. This ensures the subscriber starts from a consistent and complete view.

The server uses a subscribe-before-scan approach:

  1. The subscription is registered for live notifications.
  2. The server begins scanning all entities of the subscribed kind.
  3. Scanned entities are sent as Bootstrap notifications.
  4. When the scan is complete, the server sends a BOOTSTRAP_END marker.

Because the subscription is registered before the scan begins:

  • No gaps. Every entity that exists at subscribe time, or is created/updated after, is delivered at least once. You will never miss a change.
  • Duplicates are possible. An entity that changes during the scan window may arrive both as a Bootstrap notification and as a live Created or Updated notification. This is by design. Gaps would mean stale data, while duplicates are easily resolved.

Notification interleaving during bootstrap

Section titled “Notification interleaving during bootstrap”

During the bootstrap window, you may receive Bootstrap and live notifications interleaved. The server sends them on the same TCP connection in order, but bootstrap frames from different partitions may interleave with live notifications from the flush cycle.

Do not assume that all Bootstrap notifications arrive before any live notifications. Process all notifications the same way regardless of type.

Every notification carries a Version that is monotonically increasing per entity. The correct merge rule is: highest version wins, regardless of notification type or arrival order.

If you receive:

  • A Bootstrap for entity X at version 3, then a live Updated for entity X at version 4: apply version 4.
  • A live Updated for entity X at version 4, then a Bootstrap for entity X at version 3: ignore the bootstrap (you already have newer data).
var state = new Dictionary<ReadOnlyMemory<byte>, (ulong Version, Player Entity),
EntityIdComparer.Instance>();
await foreach (var change in players.SubscribeAsync(bootstrap: true, ct: ct))
{
switch (change.Type)
{
case NotificationType.Bootstrap:
case NotificationType.Created:
case NotificationType.Updated:
{
if (change.Entity is not { } entity)
break;
// Apply only if this is newer than what we have.
if (state.TryGetValue(change.EntityId, out var existing)
&& existing.Version >= change.Version)
break; // Stale duplicate, ignore.
state[change.EntityId] = (change.Version, entity);
break;
}
case NotificationType.Deleted:
{
// Deleted wins over same-version bootstrap/created.
if (state.TryGetValue(change.EntityId, out var existing)
&& existing.Version > change.Version)
break; // We have a newer resurrection, ignore delete.
state.Remove(change.EntityId);
break;
}
}
}

The SubscriptionHandle exposes a BootstrapStatus property that transitions through:

NotRequested -> InProgress -> Complete

var sub = await players.SubscribeAsync(bootstrap: true, ct: ct);
sub.BootstrapStatusChanged += status =>
{
if (status == BootstrapState.Complete)
Console.WriteLine("Bootstrap complete. Local state is now current.");
};
await foreach (var change in sub.ReadAllAsync(ct))
{
ApplyChange(state, change); // same version-wins logic
}

Important: Do not use BootstrapStatus to filter notifications. Process all notifications the same way (version-wins merge) regardless of whether bootstrap is in progress. The status is informational: it tells you when you have a complete snapshot, not when to start processing differently.

If you need a point-in-time snapshot without maintaining a live subscription, use BootstrapSnapshotAsync:

IReadOnlyList<Player> snapshot = await players.BootstrapSnapshotAsync();

This subscribes with bootstrap: true, waits for bootstrap to complete, collects all entities, and unsubscribes. It is useful for periodic reconciliation tasks.

  1. Always apply version-wins. Bootstrap, Created, and Updated all carry entity data and a version. Highest version wins, regardless of notification type or arrival order.
  2. Never ignore live notifications during bootstrap. They may carry newer state than the scan.
  3. Treat Bootstrap and Created identically for merge purposes. Both mean “here is the current state of this entity.”
  4. Deleted removes the entity unless you already have a higher version (which would mean the entity was re-created after the delete).
  5. BootstrapStatus == Complete means the server has finished scanning. After this point, your local state is fully caught up (assuming you applied all notifications).