Skip to content

feat(m3): durabilidad del relay — persist-before-broadcast + fsync (CHARTER-14) - #30

Merged
montfort merged 1 commit into
mainfrom
charter/15-durabilidad-relay
Jul 18, 2026
Merged

feat(m3): durabilidad del relay — persist-before-broadcast + fsync (CHARTER-14)#30
montfort merged 1 commit into
mainfrom
charter/15-durabilidad-relay

Conversation

@montfort

Copy link
Copy Markdown
Contributor

Último Charter del vaciado del backlog antes del publish (T060). Cierra FU-010: invierte el orden del relay a persist-before-broadcast (default), añade fsync al store de referencia y la cobertura de carga del relay que no existía. Backlog: 2 open → 1 (FU-015, bloqueado upstream) — el vaciado está completo salvo lo que depende de terceros.

Decisiones en AIDEC-2026-07-16-001 (supersede de §5 de AIDEC-2026-07-13-001). Tres las fijó el operador ex-ante: fsync en scope, default persist-first, cobertura de carga.

La premisa del follow-up era falsa — y eso lo simplificó

FU-010 temía que persist-before-broadcast metiera I/O en el hot path del actor. No es así: AppendUpdateAsync ya se espera en el receive loop antes de leer el siguiente frame, así que invertir el orden no añade I/O a ningún hot path — solo mueve el broadcast a después del append.

Medido (carga del relay, ambos modos, FileSystemDocumentStore con fsync):

Modo p50 p99 max
PersistThenBroadcast (default) 2.1ms 2.2ms 18.6ms
BroadcastThenPersist (legacy) 2.1ms 2.3ms 2.5ms

El coste del orden seguro es ~0 en p50/p99; solo el max sube por picos ocasionales de fsync. La evidencia respalda el default con datos, no con criterio.

Cambios

  • WeftServerOptions.Durability (default PersistThenBroadcast; BroadcastThenPersist como válvula de escape opt-in).
  • DocumentSession.ApplyAndCaptureDeltaAsync: aplica y devuelve el delta como valor de retorno del turno — race-free frente a conexiones concurrentes del mismo hub. El hub deja de usar el evento UpdateApplied para el broadcast (refinamiento sobre el plan; AIDEC Decisión 4).
  • Fallo de append → DisconnectAll + cierre 1011 → reconexión resincroniza desde el servidor autoritativo. Es el único modo de equivocarse en persist-first (un update aplicado pero no difundido dejaría pares callados para siempre), así que es obligatorio.
  • FileSystemDocumentStore: Flush(flushToDisk:true) + fsync del directorio (POSIX). Sin esto, el orden protege solo del crash de proceso, no de máquina — y el trigger de FU-010 pide «SLA de no-pérdida».
  • Weft.LoadTest modo --relay: editor→observador vía TestServer real, p50/p99 por modo.
  • RelayTests +4: fallo inyectado no observado + 1011, reconexión resincroniza, orden en ambos modos.

Verificación

Check Resultado
Suite .NET 155/155 (Server 70→74)
Contract suite (IDocumentStore) intacta — el contrato no cambia
Carga del relay PASS, ambos modos 200/200 convergen
Convergencia real (default nuevo) 2 clientes y-websocket"Hello from A. And B too." (el reorden no rompe Yjs)
straymark validate 37/37

Nota: el drift marca Weft.LoadTest.csproj como no declarado pese a estarlo — es el FP conocido de StrayMark #354 (el matcher no casa .csproj), documentado en el AILOG.

🤖 Generated with Claude Code

…HARTER-14)

Cierra FU-010, último Charter del vaciado del backlog antes del publish: de 2 open
a 1 (FU-015, bloqueado upstream). Invierte el orden del relay a persist-before-
broadcast (default), añade fsync al store de referencia y la cobertura de carga del
relay que no existía. Decisiones en AIDEC-2026-07-16-001 (supersede de §5 de
AIDEC-2026-07-13-001).

La premisa del follow-up sobre el coste era falsa: AppendUpdateAsync ya se espera en
el receive loop, así que invertir el orden no añade I/O a ningún hot path — solo mueve
el broadcast a después del append. MEDIDO (carga del relay, ambos modos, fsync real):
p50/p99 idénticos (2.1ms), el coste del orden seguro es ~0; solo el max sube por picos
de fsync. La evidencia respalda el default con datos.

Cambios:
- WeftServerOptions.Durability (default PersistThenBroadcast; BroadcastThenPersist como
  válvula de escape opt-in).
- DocumentSession.ApplyAndCaptureDeltaAsync: aplica y devuelve el delta como valor de
  retorno del turno (race-free frente a conexiones concurrentes del mismo hub). El hub
  deja de usar el evento UpdateApplied para el broadcast.
- Fallo de append → DisconnectAll + cierre 1011 → reconexión resincroniza desde el
  servidor autoritativo (el único modo de equivocarse en persist-first; obligatorio).
- FileSystemDocumentStore: Flush(flushToDisk:true) + fsync del directorio (POSIX).
- Weft.LoadTest modo --relay: editor→observador vía TestServer real, p50/p99 por modo.
- RelayTests +4: fallo inyectado no observado + 1011, reconexión, orden en ambos modos.

El contrato de IDocumentStore NO cambia (contract suite intacta). Verificado: 155/155
tests, convergencia real de 2 clientes y-websocket con el default nuevo (reorden no
rompe Yjs), carga del relay PASS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@montfort
montfort merged commit 3352ec9 into main Jul 18, 2026
16 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 18, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant