Summary
The replication-receive thread builds its own in-memory classic shared-structures dictionary (its replication-stream / full-copy encounter order) without reconciling through RecordEncoder.saveStructures. Under concurrent v4→v5 upgrade write load this order diverges from the canonical durable order the local HTTP-write thread commits, and because the divergent dictionary is never persisted (and never reloaded — see #1506), every subsequent decode on that thread hits a present-but-wrong structure → Unexpected end of MessagePack data / Data read, but end of buffer not reached → reads return null and the node wedges.
This is the creation path for the divergence that #1506 recovers from. #1506 makes the condition self-healing (durable-wins reload on decode); this issue is the prevention.
Evidence
4-node 4.7.34 → 5.1.14 in-place rolling upgrade @ 100 w/s. On the wedged node, structure-level instrumentation showed:
- The forking thread is the replication-receive thread (
[replication] Connected to wss://…, Requesting full copy of database data, received and broadcasting committed update from …).
- That thread never appears in
saveStructures (no CAS commit for the table) yet holds a complete in-memory named dictionary that is a permutation of durable (same shapes, different ids) — e.g. in-memory id0 = [tags,id,name,…] vs durable id0 = [level,items,child].
- The local HTTP-write thread does all the
saveStructures commits, monotonic and faithful → durable is canonical.
Proposed fix
Make the replication-apply / full-copy re-encode path reconcile structures through the same CAS every other writer uses — i.e. adopt the durable dictionary before minting, and persist any new structure via saveStructures (so all workers converge on one order) — rather than building a private in-memory order. Pairs with the #1455 seed fix (separate issue) so workers start from one canonical order.
Refs #1506, #1453, #1445
— filed by KrAIs (Claude) on Kris's behalf
Summary
The replication-receive thread builds its own in-memory classic shared-structures dictionary (its replication-stream / full-copy encounter order) without reconciling through
RecordEncoder.saveStructures. Under concurrent v4→v5 upgrade write load this order diverges from the canonical durable order the local HTTP-write thread commits, and because the divergent dictionary is never persisted (and never reloaded — see #1506), every subsequent decode on that thread hits a present-but-wrong structure →Unexpected end of MessagePack data/Data read, but end of buffer not reached→ reads return null and the node wedges.This is the creation path for the divergence that #1506 recovers from. #1506 makes the condition self-healing (durable-wins reload on decode); this issue is the prevention.
Evidence
4-node
4.7.34 → 5.1.14in-place rolling upgrade @ 100 w/s. On the wedged node, structure-level instrumentation showed:[replication] Connected to wss://…,Requesting full copy of database data,received and broadcasting committed update from …).saveStructures(no CAS commit for the table) yet holds a complete in-memory named dictionary that is a permutation of durable (same shapes, different ids) — e.g. in-memoryid0 = [tags,id,name,…]vs durableid0 = [level,items,child].saveStructurescommits, monotonic and faithful → durable is canonical.Proposed fix
Make the replication-apply / full-copy re-encode path reconcile structures through the same CAS every other writer uses — i.e. adopt the durable dictionary before minting, and persist any new structure via
saveStructures(so all workers converge on one order) — rather than building a private in-memory order. Pairs with the #1455 seed fix (separate issue) so workers start from one canonical order.Refs #1506, #1453, #1445
— filed by KrAIs (Claude) on Kris's behalf