@luanpdd/kit-mcp 1.30.2 → 1.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +168 -168
- package/gates/agent-no-recursive-dispatch.md +84 -82
- package/kit/COMANDOS.md +138 -138
- package/kit/COMPATIBILITY.md +5 -0
- package/kit/README.md +76 -76
- package/kit/agents/advisor-researcher.md +107 -106
- package/kit/agents/ai-mutation-tester.md +1 -0
- package/kit/agents/assumptions-analyzer.md +108 -107
- package/kit/agents/audit-log-implementer.md +314 -313
- package/kit/agents/auditor-consistencia-isolamento.md +414 -413
- package/kit/agents/b2b-saas-architect.md +157 -156
- package/kit/agents/burn-rate-forecaster.md +1 -0
- package/kit/agents/cascading-failures-auditor.md +299 -298
- package/kit/agents/codebase-mapper.md +769 -768
- package/kit/agents/crm-pipeline-implementer.md +257 -256
- package/kit/agents/debugger.md +814 -813
- package/kit/agents/detector-tenant-quente.md +338 -337
- package/kit/agents/evolution-go-integrator.md +201 -200
- package/kit/agents/example-reviewer.md +22 -21
- package/kit/agents/executor.md +565 -564
- package/kit/agents/golden-signals-instrumenter.md +1 -0
- package/kit/agents/incident-investigator.md +1 -0
- package/kit/agents/integration-checker.md +201 -200
- package/kit/agents/invite-flow-implementer.md +190 -189
- package/kit/agents/legacy-characterizer.md +369 -368
- package/kit/agents/lgpd-compliance-auditor.md +296 -295
- package/kit/agents/load-shedding-instrumenter.md +1 -0
- package/kit/agents/multi-tenant-isolation-auditor.md +254 -253
- package/kit/agents/multi-tenant-rls-writer.md +341 -340
- package/kit/agents/nyquist-auditor.md +179 -178
- package/kit/agents/observability-coverage-auditor.md +316 -315
- package/kit/agents/observability-instrumenter.md +1 -0
- package/kit/agents/omm-auditor.md +1 -0
- package/kit/agents/org-onboarding-implementer.md +224 -223
- package/kit/agents/payload-capture-instrumenter.md +274 -273
- package/kit/agents/phase-researcher.md +697 -696
- package/kit/agents/plan-checker.md +273 -272
- package/kit/agents/planner.md +923 -922
- package/kit/agents/postmortem-writer.md +1 -0
- package/kit/agents/project-researcher.md +653 -652
- package/kit/agents/prr-conductor.md +1 -0
- package/kit/agents/refactor-safety-auditor.md +405 -404
- package/kit/agents/release-pipeline-auditor.md +1 -0
- package/kit/agents/research-synthesizer.md +246 -245
- package/kit/agents/roadmapper.md +678 -677
- package/kit/agents/schema-checker.md +1 -0
- package/kit/agents/seam-finder.md +360 -359
- package/kit/agents/shotgun-surgery-detector.md +350 -349
- package/kit/agents/slo-engineer.md +1 -0
- package/kit/agents/storytelling-analyst.md +1 -0
- package/kit/agents/supabase-architect.md +1 -0
- package/kit/agents/supabase-auth-bootstrapper.md +16 -1
- package/kit/agents/supabase-auth-hook-writer.md +418 -0
- package/kit/agents/supabase-branching-architect.md +563 -562
- package/kit/agents/supabase-cicd-pipeline-implementer.md +778 -777
- package/kit/agents/supabase-column-privileges-writer.md +400 -399
- package/kit/agents/supabase-edge-fn-tester.md +2 -1
- package/kit/agents/supabase-edge-fn-writer.md +2 -1
- package/kit/agents/supabase-mfa-implementer.md +439 -0
- package/kit/agents/supabase-migration-writer.md +386 -385
- package/kit/agents/supabase-oauth-server-implementer.md +507 -0
- package/kit/agents/supabase-rbac-implementer.md +393 -392
- package/kit/agents/supabase-realtime-implementer.md +364 -363
- package/kit/agents/supabase-rls-hardener.md +522 -521
- package/kit/agents/supabase-rls-writer.md +324 -323
- package/kit/agents/supabase-roles-implementer.md +356 -355
- package/kit/agents/supabase-social-auth-implementer.md +451 -0
- package/kit/agents/supabase-sso-saml-architect.md +549 -0
- package/kit/agents/supabase-storage-implementer.md +1 -0
- package/kit/agents/super-admin-implementer.md +282 -281
- package/kit/agents/toil-auditor.md +1 -0
- package/kit/agents/ui-auditor.md +438 -437
- package/kit/agents/ui-checker.md +303 -302
- package/kit/agents/ui-researcher.md +356 -355
- package/kit/agents/user-profiler.md +176 -175
- package/kit/agents/validador-evolucao-schema.md +336 -335
- package/kit/agents/verifier.md +729 -728
- package/kit/commands/adicionar-backlog.md +75 -75
- package/kit/commands/adicionar-fase.md +42 -42
- package/kit/commands/adicionar-tarefa.md +45 -45
- package/kit/commands/adicionar-testes.md +41 -41
- package/kit/commands/ajuda.md +21 -21
- package/kit/commands/atualizar.md +37 -37
- package/kit/commands/auditar-cascading.md +111 -111
- package/kit/commands/auditar-marco.md +179 -179
- package/kit/commands/auditar-observabilidade-cobertura.md +183 -183
- package/kit/commands/auditar-refactor.md +219 -219
- package/kit/commands/auditar-release.md +109 -109
- package/kit/commands/auditar-uat.md +23 -23
- package/kit/commands/autonomo.md +40 -40
- package/kit/commands/branch-pr.md +24 -24
- package/kit/commands/burn-rate-status.md +408 -408
- package/kit/commands/capturar-payloads.md +193 -193
- package/kit/commands/caracterizar.md +212 -212
- package/kit/commands/concluir-marco.md +247 -247
- package/kit/commands/configuracoes.md +36 -36
- package/kit/commands/dados-distribuidos.md +188 -188
- package/kit/commands/definir-perfil.md +10 -10
- package/kit/commands/depurar.md +190 -190
- package/kit/commands/detectar-duplicacao.md +197 -197
- package/kit/commands/discutir-fase.md +131 -131
- package/kit/commands/encontrar-seams.md +136 -136
- package/kit/commands/entrar-discord.md +17 -17
- package/kit/commands/estatisticas.md +18 -18
- package/kit/commands/example-greeting.md +33 -33
- package/kit/commands/executar-fase.md +58 -58
- package/kit/commands/expresso.md +56 -56
- package/kit/commands/fase-ui.md +34 -34
- package/kit/commands/fazer.md +57 -57
- package/kit/commands/fio.md +125 -125
- package/kit/commands/fluxos-trabalho.md +64 -64
- package/kit/commands/forense.md +176 -176
- package/kit/commands/gerenciador.md +38 -38
- package/kit/commands/inserir-fase.md +31 -31
- package/kit/commands/legacy.md +263 -263
- package/kit/commands/limpeza.md +17 -17
- package/kit/commands/listar-hipoteses-fase.md +45 -45
- package/kit/commands/listar-workspaces.md +18 -18
- package/kit/commands/load-shedding.md +117 -117
- package/kit/commands/mapear-codebase.md +70 -70
- package/kit/commands/multi-tenant.md +163 -163
- package/kit/commands/nota.md +33 -33
- package/kit/commands/novo-marco.md +43 -43
- package/kit/commands/novo-projeto.md +41 -41
- package/kit/commands/novo-workspace.md +43 -43
- package/kit/commands/pausar-trabalho.md +37 -37
- package/kit/commands/perfil-usuario.md +45 -45
- package/kit/commands/pesquisar-fase.md +195 -195
- package/kit/commands/planejar-fase.md +67 -67
- package/kit/commands/planejar-lacunas.md +33 -33
- package/kit/commands/plantar-ideia.md +25 -25
- package/kit/commands/progresso.md +24 -24
- package/kit/commands/proximo.md +30 -30
- package/kit/commands/publicar.md +490 -490
- package/kit/commands/rapido.md +35 -35
- package/kit/commands/reaplicar-patches.md +124 -124
- package/kit/commands/refactor-seguro.md +321 -321
- package/kit/commands/relatorio-sessao.md +19 -19
- package/kit/commands/remover-fase.md +31 -31
- package/kit/commands/remover-workspace.md +26 -26
- package/kit/commands/resumo-marco.md +50 -50
- package/kit/commands/retomar-trabalho.md +40 -40
- package/kit/commands/revisar-backlog.md +60 -60
- package/kit/commands/revisar-ui.md +32 -32
- package/kit/commands/revisar.md +37 -37
- package/kit/commands/saude.md +21 -21
- package/kit/commands/setup-notion.md +93 -93
- package/kit/commands/storytelling.md +179 -179
- package/kit/commands/supabase.md +21 -1
- package/kit/commands/sync-main.md +68 -68
- package/kit/commands/validar-fase.md +35 -35
- package/kit/commands/verificar-tarefas.md +44 -44
- package/kit/commands/verificar-trabalho.md +64 -64
- package/kit/file-manifest.json +100 -84
- package/kit/framework/bin/lib/commands.cjs +959 -959
- package/kit/framework/bin/lib/config.cjs +442 -442
- package/kit/framework/bin/lib/core.cjs +1230 -1230
- package/kit/framework/bin/lib/frontmatter.cjs +336 -336
- package/kit/framework/bin/lib/init.cjs +1442 -1442
- package/kit/framework/bin/lib/milestone.cjs +252 -252
- package/kit/framework/bin/lib/model-profiles.cjs +68 -68
- package/kit/framework/bin/lib/phase.cjs +888 -888
- package/kit/framework/bin/lib/profile-output.cjs +952 -952
- package/kit/framework/bin/lib/profile-pipeline.cjs +539 -539
- package/kit/framework/bin/lib/roadmap.cjs +329 -329
- package/kit/framework/bin/lib/security.cjs +382 -382
- package/kit/framework/bin/lib/state.cjs +1031 -1031
- package/kit/framework/bin/lib/template.cjs +222 -222
- package/kit/framework/bin/lib/uat.cjs +282 -282
- package/kit/framework/bin/lib/verify.cjs +888 -888
- package/kit/framework/bin/lib/workstream.cjs +491 -491
- package/kit/framework/bin/tools.cjs +918 -918
- package/kit/framework/commands/workstreams.md +63 -63
- package/kit/framework/references/checkpoints.md +778 -778
- package/kit/framework/references/continuation-format.md +249 -249
- package/kit/framework/references/decimal-phase-calculation.md +64 -64
- package/kit/framework/references/git-integration.md +295 -295
- package/kit/framework/references/git-planning-commit.md +38 -38
- package/kit/framework/references/model-profile-resolution.md +36 -36
- package/kit/framework/references/model-profiles.md +139 -139
- package/kit/framework/references/phase-argument-parsing.md +61 -61
- package/kit/framework/references/planning-config.md +202 -202
- package/kit/framework/references/questioning.md +162 -162
- package/kit/framework/references/tdd.md +263 -263
- package/kit/framework/references/ui-brand.md +160 -160
- package/kit/framework/references/user-profiling.md +657 -657
- package/kit/framework/references/verification-patterns.md +612 -612
- package/kit/framework/references/workstream-flag.md +58 -58
- package/kit/framework/templates/DEBUG.md +164 -164
- package/kit/framework/templates/UAT.md +265 -265
- package/kit/framework/templates/UI-SPEC.md +100 -100
- package/kit/framework/templates/VALIDATION.md +76 -76
- package/kit/framework/templates/claude-md.md +122 -122
- package/kit/framework/templates/codebase/architecture.md +185 -185
- package/kit/framework/templates/codebase/concerns.md +205 -205
- package/kit/framework/templates/codebase/conventions.md +204 -204
- package/kit/framework/templates/codebase/integrations.md +192 -192
- package/kit/framework/templates/codebase/stack.md +158 -158
- package/kit/framework/templates/codebase/structure.md +199 -199
- package/kit/framework/templates/codebase/testing.md +301 -301
- package/kit/framework/templates/config.json +44 -44
- package/kit/framework/templates/context.md +352 -352
- package/kit/framework/templates/continue-here.md +78 -78
- package/kit/framework/templates/copilot-instructions.md +7 -7
- package/kit/framework/templates/debug-subagent-prompt.md +91 -91
- package/kit/framework/templates/dev-preferences.md +20 -20
- package/kit/framework/templates/discovery.md +146 -146
- package/kit/framework/templates/discussion-log.md +63 -63
- package/kit/framework/templates/milestone-archive.md +123 -123
- package/kit/framework/templates/milestone.md +115 -115
- package/kit/framework/templates/phase-prompt.md +610 -610
- package/kit/framework/templates/planner-subagent-prompt.md +117 -117
- package/kit/framework/templates/project.md +186 -186
- package/kit/framework/templates/requirements.md +231 -231
- package/kit/framework/templates/research-project/ARCHITECTURE.md +204 -204
- package/kit/framework/templates/research-project/FEATURES.md +147 -147
- package/kit/framework/templates/research-project/PITFALLS.md +200 -200
- package/kit/framework/templates/research-project/STACK.md +120 -120
- package/kit/framework/templates/research-project/SUMMARY.md +170 -170
- package/kit/framework/templates/research.md +419 -419
- package/kit/framework/templates/retrospective.md +54 -54
- package/kit/framework/templates/roadmap.md +202 -202
- package/kit/framework/templates/state.md +176 -176
- package/kit/framework/templates/summary-complex.md +59 -59
- package/kit/framework/templates/summary-minimal.md +41 -41
- package/kit/framework/templates/summary-standard.md +48 -48
- package/kit/framework/templates/summary.md +209 -209
- package/kit/framework/templates/user-profile.md +146 -146
- package/kit/framework/templates/user-setup.md +256 -256
- package/kit/framework/templates/verification-report.md +258 -258
- package/kit/framework/workflows/add-phase.md +112 -112
- package/kit/framework/workflows/add-tests.md +351 -351
- package/kit/framework/workflows/add-todo.md +158 -158
- package/kit/framework/workflows/audit-milestone.md +340 -340
- package/kit/framework/workflows/audit-uat.md +109 -109
- package/kit/framework/workflows/autonomous.md +891 -891
- package/kit/framework/workflows/check-todos.md +177 -177
- package/kit/framework/workflows/cleanup.md +152 -152
- package/kit/framework/workflows/complete-milestone.md +696 -696
- package/kit/framework/workflows/diagnose-issues.md +231 -231
- package/kit/framework/workflows/discovery-phase.md +289 -289
- package/kit/framework/workflows/discuss-phase-assumptions.md +653 -653
- package/kit/framework/workflows/discuss-phase.md +784 -784
- package/kit/framework/workflows/do.md +104 -104
- package/kit/framework/workflows/execute-phase.md +838 -838
- package/kit/framework/workflows/execute-plan.md +510 -510
- package/kit/framework/workflows/fast.md +102 -102
- package/kit/framework/workflows/forensics.md +265 -265
- package/kit/framework/workflows/health.md +181 -181
- package/kit/framework/workflows/help.md +619 -619
- package/kit/framework/workflows/insert-phase.md +130 -130
- package/kit/framework/workflows/list-phase-assumptions.md +178 -178
- package/kit/framework/workflows/list-workspaces.md +56 -56
- package/kit/framework/workflows/manager.md +362 -362
- package/kit/framework/workflows/map-codebase.md +377 -377
- package/kit/framework/workflows/milestone-summary.md +223 -223
- package/kit/framework/workflows/new-milestone.md +486 -486
- package/kit/framework/workflows/new-project.md +1159 -1159
- package/kit/framework/workflows/new-workspace.md +237 -237
- package/kit/framework/workflows/next.md +97 -97
- package/kit/framework/workflows/node-repair.md +92 -92
- package/kit/framework/workflows/note.md +156 -156
- package/kit/framework/workflows/pause-work.md +176 -176
- package/kit/framework/workflows/plan-milestone-gaps.md +273 -273
- package/kit/framework/workflows/plan-phase.md +765 -765
- package/kit/framework/workflows/plant-seed.md +169 -169
- package/kit/framework/workflows/pr-branch.md +129 -129
- package/kit/framework/workflows/profile-user.md +450 -450
- package/kit/framework/workflows/progress.md +507 -507
- package/kit/framework/workflows/quick.md +757 -757
- package/kit/framework/workflows/remove-phase.md +155 -155
- package/kit/framework/workflows/remove-workspace.md +90 -90
- package/kit/framework/workflows/research-phase.md +82 -82
- package/kit/framework/workflows/resume-project.md +326 -326
- package/kit/framework/workflows/review.md +228 -228
- package/kit/framework/workflows/session-report.md +146 -146
- package/kit/framework/workflows/settings.md +283 -283
- package/kit/framework/workflows/ship.md +228 -228
- package/kit/framework/workflows/stats.md +60 -60
- package/kit/framework/workflows/transition.md +671 -671
- package/kit/framework/workflows/ui-phase.md +302 -302
- package/kit/framework/workflows/ui-review.md +165 -165
- package/kit/framework/workflows/update.md +323 -323
- package/kit/framework/workflows/validate-phase.md +174 -174
- package/kit/framework/workflows/verify-phase.md +252 -252
- package/kit/framework/workflows/verify-work.md +637 -637
- package/kit/hooks/check-update.js +118 -118
- package/kit/hooks/context-monitor.js +163 -163
- package/kit/hooks/kit-attribution-reminder.cjs +29 -50
- package/kit/hooks/kit-router.cjs +137 -0
- package/kit/hooks/prompt-guard.js +103 -103
- package/kit/hooks/statusline.js +125 -125
- package/kit/hooks/workflow-guard.js +101 -101
- package/kit/settings.json +45 -45
- package/kit/skills/ai-prompt-characterization/SKILL.md +335 -335
- package/kit/skills/armadilhas-sistemas-distribuidos/SKILL.md +447 -447
- package/kit/skills/audit-log-multi-tenant/SKILL.md +340 -340
- package/kit/skills/b2b-saas-architecture/SKILL.md +300 -300
- package/kit/skills/consistencia-leitura-replica/SKILL.md +385 -385
- package/kit/skills/crm-lead-pipeline-patterns/SKILL.md +343 -343
- package/kit/skills/escolha-modelo-consistencia/SKILL.md +494 -494
- package/kit/skills/evolucao-schema-compativel/SKILL.md +448 -448
- package/kit/skills/evolution-go-whatsapp-integration/SKILL.md +322 -322
- package/kit/skills/example-skill/SKILL.md +42 -42
- package/kit/skills/legacy-api-only-applications/SKILL.md +358 -358
- package/kit/skills/legacy-characterization-tests/SKILL.md +330 -330
- package/kit/skills/legacy-effect-analysis/SKILL.md +331 -331
- package/kit/skills/legacy-extract-class/SKILL.md +203 -203
- package/kit/skills/legacy-programming-by-difference/SKILL.md +252 -252
- package/kit/skills/legacy-seams-and-test-harness/SKILL.md +460 -460
- package/kit/skills/legacy-shotgun-surgery/SKILL.md +286 -286
- package/kit/skills/legacy-sprout-wrap-techniques/SKILL.md +434 -434
- package/kit/skills/legacy-storytelling-naked-crc/SKILL.md +270 -270
- package/kit/skills/lgpd-multi-tenant-compliance/SKILL.md +340 -340
- package/kit/skills/member-invite-flow/SKILL.md +305 -305
- package/kit/skills/member-management-react-shadcn/SKILL.md +328 -328
- package/kit/skills/multi-tenant-performance-scaling/SKILL.md +316 -316
- package/kit/skills/multi-tenant-rls-hierarchy/SKILL.md +342 -342
- package/kit/skills/org-onboarding-flow/SKILL.md +257 -257
- package/kit/skills/org-switcher-react-pattern/SKILL.md +349 -349
- package/kit/skills/permission-gate-react-pattern/SKILL.md +271 -271
- package/kit/skills/postgres-isolamento-concorrencia/SKILL.md +552 -552
- package/kit/skills/pre-refactor-characterization/SKILL.md +421 -421
- package/kit/skills/rbac-permissions-matrix-supabase/SKILL.md +338 -338
- package/kit/skills/streams-eventos-cdc/SKILL.md +711 -711
- package/kit/skills/supabase-auth-hardening/SKILL.md +674 -0
- package/kit/skills/supabase-auth-hooks/SKILL.md +875 -0
- package/kit/skills/supabase-auth-methods/SKILL.md +486 -0
- package/kit/skills/supabase-auth-sessions/SKILL.md +579 -0
- package/kit/skills/supabase-auth-ssr/SKILL.md +60 -14
- package/kit/skills/supabase-branching-workflow/SKILL.md +544 -544
- package/kit/skills/supabase-ci-cd-github-actions/SKILL.md +880 -880
- package/kit/skills/supabase-column-level-security/SKILL.md +426 -426
- package/kit/skills/supabase-config-toml-remotes/SKILL.md +807 -807
- package/kit/skills/supabase-custom-claims-rbac/SKILL.md +472 -472
- package/kit/skills/supabase-edge-functions/SKILL.md +1 -1
- package/kit/skills/supabase-edge-functions-auth/SKILL.md +1 -1
- package/kit/skills/supabase-edge-functions-limits/SKILL.md +1 -1
- package/kit/skills/supabase-edge-functions-mcp-server/SKILL.md +1 -1
- package/kit/skills/supabase-edge-functions-testing/SKILL.md +1 -1
- package/kit/skills/supabase-edge-runtime-builtins/SKILL.md +1 -1
- package/kit/skills/supabase-enterprise-sso-saml/SKILL.md +545 -0
- package/kit/skills/supabase-jwt-signing-keys/SKILL.md +399 -0
- package/kit/skills/supabase-mfa/SKILL.md +488 -0
- package/kit/skills/supabase-migration-repair/SKILL.md +823 -823
- package/kit/skills/supabase-migrations/SKILL.md +297 -297
- package/kit/skills/supabase-oauth-server/SKILL.md +537 -0
- package/kit/skills/supabase-pgtap-testing/SKILL.md +1053 -1053
- package/kit/skills/supabase-postgres-roles/SKILL.md +392 -392
- package/kit/skills/supabase-realtime/SKILL.md +460 -460
- package/kit/skills/supabase-rls-defense-in-depth/SKILL.md +418 -418
- package/kit/skills/supabase-rls-policies/SKILL.md +635 -635
- package/kit/skills/supabase-social-oauth/SKILL.md +480 -0
- package/kit/skills/supabase-third-party-auth/SKILL.md +450 -0
- package/kit/skills/super-admin-platform-pattern/SKILL.md +326 -326
- package/kit/skills/tenant-quente-mitigacao/SKILL.md +605 -605
- package/kit/skills/whatsapp-conversation-state-machine/SKILL.md +287 -287
- package/package.json +1 -1
- package/src/core/kit.js +216 -216
- package/src/core/reflect.js +247 -247
- package/src/core/reverse-sync.js +372 -372
- package/src/core/sync.js +437 -418
- package/src/core/watch.js +121 -121
- package/src/mcp-server/index.js +794 -746
|
@@ -1,385 +1,385 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: consistencia-leitura-replica
|
|
3
|
-
description: Use ao usar Supabase read replicas via Supavisor (porta 6543) ou ao combinar Realtime broadcast + leitura DB…
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Consistência Leitura Réplica — Supabase + Supavisor + Realtime
|
|
7
|
-
|
|
8
|
-
## Quando usar
|
|
9
|
-
|
|
10
|
-
LLM carrega esta skill ao usar Supabase Pro+ com **read replicas** ou ao combinar Realtime broadcast com leitura subsequente do DB. Trigger phrases:
|
|
11
|
-
|
|
12
|
-
- "Supabase read replica", "réplica de leitura"
|
|
13
|
-
- "porta 6543 vs 5432", "Supavisor session vs transaction"
|
|
14
|
-
- "read-after-write", "leitura após escrita inconsistente"
|
|
15
|
-
- "monotonic reads", "leituras não-monotônicas"
|
|
16
|
-
- "consistent prefix reads", "prefixo causal violado"
|
|
17
|
-
- "replication lag Supabase", "atraso de replicação"
|
|
18
|
-
- "broadcast Realtime + SELECT stale"
|
|
19
|
-
- "pg_last_wal_replay_lsn", "WAL position detection"
|
|
20
|
-
|
|
21
|
-
Esta skill aplica **DDIA Ch 5 "Problems With Replication Lag"** ao stack Supabase. Cross-referenciada por `supabase-realtime` (v1.8) ao bundlear broadcast + leitura, e por `multi-tenant-performance-scaling` (v1.21) ao escalar Postgres em Pro+.
|
|
22
|
-
|
|
23
|
-
Termos canônicos (`read-after-write consistency`, `monotonic reads`, `consistent prefix reads`, `replication lag`, `leader-follower replication`) definidos em [`_shared-dados-distribuidos/glossary.md`](../_shared-dados-distribuidos/glossary.md) seções (a) e (b) — esta skill **não duplica**, apenas linka.
|
|
24
|
-
|
|
25
|
-
## Regras absolutas
|
|
26
|
-
|
|
27
|
-
**REGRA #1 (read-after-write own data):** Para leituras do **próprio dado do usuário** dentro de janela de **5s após write**, rotear para **porta 5432 (líder)** — não 6543. Sem isso, usuário cria post → GET retorna 404 → percepção de bug. Justify: DDIA p. 156 "always read the user's own profile from the leader".
|
|
28
|
-
|
|
29
|
-
**REGRA #2 (sticky session monotonic):** Para usuários ativos lendo de réplicas, escolher réplica via `hash(user_id) mod N` — **não round-robin**. Round-robin viola monotonic reads (DDIA p. 158). Mitigação obrigatória: fallback para líder se réplica down.
|
|
30
|
-
|
|
31
|
-
**REGRA #3 (broadcast trust payload):** Após receber Realtime broadcast com `payload.record`, **NÃO** fazer SELECT subsequente. Confiar no payload — server é a fonte canônica do evento. SELECT pode atingir réplica que ainda não replicou (lag típico 50-500ms).
|
|
32
|
-
|
|
33
|
-
**REGRA #4 (causal partition):** Writes causalmente relacionados (pergunta + resposta em chat, parent + child em árvore) **DEVEM** ir para a mesma partição lógica. Em Supabase: usar mesmo `org_id` ou `conversation_id` como partition key. DDIA p. 159 "any writes which are causally related to each other are written to the same partition".
|
|
34
|
-
|
|
35
|
-
**REGRA #5 (LSN wait com timeout):** Quando usar `pg_last_wal_replay_lsn() >= captured_lsn`, **SEMPRE** com timeout (3-5s). Sem timeout, query trava se réplica falhou. Após timeout, fallback para líder.
|
|
36
|
-
|
|
37
|
-
## Patterns canônicos
|
|
38
|
-
|
|
39
|
-
### Problema 1: Read-after-write inconsistente (DDIA Ch 5, p. 156)
|
|
40
|
-
|
|
41
|
-
**Cenário canônico:** usuário cria post via form submit → tela mostra "criado com sucesso" → usuário clica "ver post" → request vai para réplica → réplica ainda não replicou → 404. Da perspectiva do usuário: "perdi meu dado".
|
|
42
|
-
|
|
43
|
-
```
|
|
44
|
-
User 1234 ──INSERT─→ Leader (5432) ┐
|
|
45
|
-
│ │ replication lag (50-500ms)
|
|
46
|
-
└──WAL──────────────→ Follower (6543, replica)
|
|
47
|
-
│
|
|
48
|
-
User 1234 ─SELECT────────────────────────────→ ❌ 404
|
|
49
|
-
(vai para replica via Supavisor)
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
**Solução A — leitura no líder após write do mesmo usuário:**
|
|
53
|
-
|
|
54
|
-
```typescript
|
|
55
|
-
// PT-BR: client mantém timestamp do último write em memória
|
|
56
|
-
class SupabaseRouter {
|
|
57
|
-
private lastWriteAt: Map<string, number> = new Map() // userId → timestamp ms
|
|
58
|
-
private readonly STICKY_WINDOW_MS = 5000 // 5s leitura no líder
|
|
59
|
-
|
|
60
|
-
async write(userId: string, table: string, payload: unknown) {
|
|
61
|
-
const result = await this.leaderClient.from(table).insert(payload)
|
|
62
|
-
this.lastWriteAt.set(userId, Date.now())
|
|
63
|
-
return result
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async read(userId: string, table: string, filter: object) {
|
|
67
|
-
const lastWrite = this.lastWriteAt.get(userId) ?? 0
|
|
68
|
-
const elapsedMs = Date.now() - lastWrite
|
|
69
|
-
|
|
70
|
-
// PT-BR: dentro da janela 5s, ler do líder (porta 5432)
|
|
71
|
-
if (elapsedMs < this.STICKY_WINDOW_MS) {
|
|
72
|
-
return this.leaderClient.from(table).select().match(filter)
|
|
73
|
-
}
|
|
74
|
-
// PT-BR: fora da janela, OK ler de replica via pooler 6543
|
|
75
|
-
return this.replicaClient.from(table).select().match(filter)
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
**Trade-off:** dentro da janela, perde benefício do read scaling. DDIA recomenda janela curta (1-5s) — cobre 99% dos casos UX sem sobrecarregar líder.
|
|
81
|
-
|
|
82
|
-
### Problema 2: Leituras não-monotônicas (DDIA Ch 5, p. 158)
|
|
83
|
-
|
|
84
|
-
**Cenário canônico:** usuário 2345 abre lista de comentários — primeira leitura vai para réplica 1 (lag 100ms) → vê comentário X. Segundo refresh vai para réplica 2 (lag 800ms) → comentário X **desapareceu**. Da perspectiva do usuário: "dado voltou no tempo".
|
|
85
|
-
|
|
86
|
-
```
|
|
87
|
-
User 2345 ─SELECT(1)────→ Replica 1 (lag 100ms) → 1 result ✅
|
|
88
|
-
User 2345 ─SELECT(2)────→ Replica 2 (lag 800ms) → 0 results ❌ (parecia ter sumido)
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
**Solução B — sticky session por `user_id` em routing:**
|
|
92
|
-
|
|
93
|
-
```typescript
|
|
94
|
-
// PT-BR: hash determinístico do user_id → escolhe replica fixa para esse user
|
|
95
|
-
import { createHash } from 'node:crypto'
|
|
96
|
-
|
|
97
|
-
function pickReplica(userId: string, replicas: ReadonlyArray<SupabaseClient>): SupabaseClient {
|
|
98
|
-
const hash = createHash('sha256').update(userId).digest()
|
|
99
|
-
const idx = hash.readUInt32BE(0) % replicas.length
|
|
100
|
-
return replicas[idx]
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// PT-BR: usage com fallback para líder se replica falhar
|
|
104
|
-
async function readWithStickyReplica(userId: string, query: () => Promise<Result>) {
|
|
105
|
-
const replica = pickReplica(userId, REPLICAS)
|
|
106
|
-
try {
|
|
107
|
-
return await query.call(replica)
|
|
108
|
-
} catch (err) {
|
|
109
|
-
if (isReplicaDownError(err)) {
|
|
110
|
-
// PT-BR: fallback obrigatório — REGRA #2
|
|
111
|
-
return await query.call(LEADER)
|
|
112
|
-
}
|
|
113
|
-
throw err
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
**Pitfall:** réplica fica down → todos os usuários alocados a ela ficam sem leitura. Mitigação: detectar via health check + reroute para líder até réplica voltar.
|
|
119
|
-
|
|
120
|
-
### Problema 3: Prefixo causal violado (DDIA Ch 5, p. 159)
|
|
121
|
-
|
|
122
|
-
**Cenário canônico chat:** Mr Poons pergunta "How far into the future can you see, Mrs Cake?" → write vai para partição A. Mrs Cake responde "About ten seconds usually, Mr Poons." → write vai para partição B. Observador lê de B (lag baixo) e A (lag alto): **vê resposta antes da pergunta**.
|
|
123
|
-
|
|
124
|
-
```
|
|
125
|
-
Partição A (lag 800ms): "How far into the future..."
|
|
126
|
-
Partição B (lag 100ms): "About ten seconds..."
|
|
127
|
-
|
|
128
|
-
Observer lê B → vê resposta ✅
|
|
129
|
-
Observer lê A → vê pergunta ✅
|
|
130
|
-
Observer ordem percebida: resposta → pergunta ❌
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
**Solução parcial — particionamento por chave causal:**
|
|
134
|
-
|
|
135
|
-
```sql
|
|
136
|
-
-- PT-BR: ambas msgs (pergunta + resposta) particionadas por conversation_id
|
|
137
|
-
-- garante mesma partição lógica = mesma ordem WAL
|
|
138
|
-
create table public.messages (
|
|
139
|
-
id uuid primary key default gen_random_uuid(),
|
|
140
|
-
conversation_id uuid not null,
|
|
141
|
-
author_id uuid not null,
|
|
142
|
-
body text not null,
|
|
143
|
-
created_at timestamptz not null default now()
|
|
144
|
-
) partition by hash (conversation_id);
|
|
145
|
-
|
|
146
|
-
-- PT-BR: indexes ajudam ordering
|
|
147
|
-
create index messages_conv_created_idx
|
|
148
|
-
on public.messages (conversation_id, created_at);
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
**Limitação:** garante consistent prefix dentro de uma partição. Cross-partition (ex: usuário em duas conversações simultâneas), DDIA p. 159 conclui "in general, ensuring consistent prefix reads requires snapshot isolation". Em Supabase prático: **manter conversação em uma tabela com chave causal explícita**.
|
|
152
|
-
|
|
153
|
-
### Solução C — Detecção stale via `pg_last_wal_replay_lsn()`
|
|
154
|
-
|
|
155
|
-
Quando precisa de garantia "este read viu meu write" sem rotear ao líder:
|
|
156
|
-
|
|
157
|
-
```sql
|
|
158
|
-
-- PT-BR: capturar LSN no líder após write (chamada do app via RPC)
|
|
159
|
-
create or replace function public.get_current_lsn()
|
|
160
|
-
returns text
|
|
161
|
-
language sql
|
|
162
|
-
security invoker
|
|
163
|
-
set search_path = ''
|
|
164
|
-
as $$
|
|
165
|
-
-- PT-BR: pg_current_wal_lsn() retorna posição atual do WAL no líder
|
|
166
|
-
select pg_current_wal_lsn()::text;
|
|
167
|
-
$$;
|
|
168
|
-
|
|
169
|
-
-- PT-BR: na replica, esperar até replay alcançar o LSN capturado
|
|
170
|
-
create or replace function public.wait_for_lsn(target_lsn text, timeout_ms int default 5000)
|
|
171
|
-
returns boolean
|
|
172
|
-
language plpgsql
|
|
173
|
-
security invoker
|
|
174
|
-
set search_path = ''
|
|
175
|
-
as $$
|
|
176
|
-
declare
|
|
177
|
-
start_at timestamptz := clock_timestamp();
|
|
178
|
-
elapsed_ms int;
|
|
179
|
-
begin
|
|
180
|
-
loop
|
|
181
|
-
-- PT-BR: pg_last_wal_replay_lsn() na replica = última posição replayed
|
|
182
|
-
if pg_last_wal_replay_lsn() >= target_lsn::pg_lsn then
|
|
183
|
-
return true;
|
|
184
|
-
end if;
|
|
185
|
-
|
|
186
|
-
elapsed_ms := extract(milliseconds from (clock_timestamp() - start_at))::int;
|
|
187
|
-
if elapsed_ms >= timeout_ms then
|
|
188
|
-
return false; -- PT-BR: REGRA #5 — timeout sem bloquear infinito
|
|
189
|
-
end if;
|
|
190
|
-
|
|
191
|
-
perform pg_sleep(0.05); -- PT-BR: 50ms entre polls
|
|
192
|
-
end loop;
|
|
193
|
-
end;
|
|
194
|
-
$$;
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
**Uso típico no client:**
|
|
198
|
-
|
|
199
|
-
```typescript
|
|
200
|
-
// PT-BR: 1) write no líder, captura LSN
|
|
201
|
-
const { data: lsn } = await leaderClient.rpc('get_current_lsn')
|
|
202
|
-
await leaderClient.from('orders').insert(order)
|
|
203
|
-
|
|
204
|
-
// PT-BR: 2) read no líder, espera replay
|
|
205
|
-
const { data: ready } = await replicaClient.rpc('wait_for_lsn', {
|
|
206
|
-
target_lsn: lsn,
|
|
207
|
-
timeout_ms: 3000,
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
if (ready) {
|
|
211
|
-
// PT-BR: replica caught up, leitura é safe
|
|
212
|
-
return replicaClient.from('orders').select().eq('id', order.id)
|
|
213
|
-
}
|
|
214
|
-
// PT-BR: timeout — fallback para líder (REGRA #5)
|
|
215
|
-
return leaderClient.from('orders').select().eq('id', order.id)
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
### Supavisor read replica routing
|
|
219
|
-
|
|
220
|
-
| Porta | Modo | Use case | Connection string |
|
|
221
|
-
|---|---|---|---|
|
|
222
|
-
| **6543** | Transaction (default Pro+) | Apps com pooler já configurado, edge runtimes, serverless | `postgresql://postgres.[ref]:pwd@aws-0-region.pooler.supabase.com:6543/postgres` |
|
|
223
|
-
| **5432** | Session (líder) | Reads críticas (read-after-write), writes, prepared statements, advisory locks | `postgresql://postgres.[ref]:pwd@aws-0-region.pooler.supabase.com:5432/postgres` |
|
|
224
|
-
| `pooler.read.*` | Réplica routing | Read-heavy workloads em Pro+ com replicas habilitadas | (futuro Supabase feature — placeholder hoje) |
|
|
225
|
-
|
|
226
|
-
**Decisão por tipo de query:**
|
|
227
|
-
|
|
228
|
-
```
|
|
229
|
-
SELECT do próprio dado dentro 5s do write? → 5432 (líder, REGRA #1)
|
|
230
|
-
SELECT cross-user, sem janela sticky? → 6543 (replica via Supavisor)
|
|
231
|
-
INSERT / UPDATE / DELETE? → 5432 (sempre líder)
|
|
232
|
-
SELECT FOR UPDATE / advisory lock? → 5432 (transaction precisa session mode)
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
Cross-ref ATIVO: [`multi-tenant-performance-scaling/SKILL.md`](../multi-tenant-performance-scaling/SKILL.md) (v1.21) cobre Supavisor REGRA #1 sob lente de connection pooling — esta skill cobre a mesma porta sob lente de consistência.
|
|
236
|
-
|
|
237
|
-
### Realtime broadcast + leitura DB — padrão "ler o próprio broadcast"
|
|
238
|
-
|
|
239
|
-
**Cenário canônico:** client A faz INSERT em `orders` → server emite Realtime broadcast `new_order` no canal `org:orders:org_42` → client B recebe broadcast → client B faz SELECT para refresh da lista. **Pode receber dado stale** (replica não replicou ainda).
|
|
240
|
-
|
|
241
|
-
**Sequência do bug:**
|
|
242
|
-
|
|
243
|
-
```
|
|
244
|
-
t=0ms Client A INSERT → Leader
|
|
245
|
-
t=10ms Server emite broadcast → todos clients no canal recebem
|
|
246
|
-
t=15ms Client B recebe broadcast → triggers re-fetch
|
|
247
|
-
t=20ms Client B SELECT → Replica (lag ainda 80ms)
|
|
248
|
-
t=20ms Replica retorna lista SEM o novo order ❌
|
|
249
|
-
t=80ms Replica finalmente replicou (mas client B já desenhou stale)
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
**Padrão correto — confiar no payload broadcast:**
|
|
253
|
-
|
|
254
|
-
```typescript
|
|
255
|
-
// PT-BR: client confia no payload, NÃO faz SELECT subsequente
|
|
256
|
-
import { createClient } from '@supabase/supabase-js'
|
|
257
|
-
|
|
258
|
-
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
|
|
259
|
-
|
|
260
|
-
const channel = supabase
|
|
261
|
-
.channel('org:orders:org_42', { config: { private: true } })
|
|
262
|
-
.on('broadcast', { event: 'new_order' }, ({ payload }) => {
|
|
263
|
-
// PT-BR: REGRA #3 — confie no payload, NÃO faça SELECT
|
|
264
|
-
setOrders((prev) => [...prev, payload.record])
|
|
265
|
-
})
|
|
266
|
-
.subscribe()
|
|
267
|
-
|
|
268
|
-
// PT-BR: cleanup obrigatório — pattern de supabase-realtime v1.8
|
|
269
|
-
return () => {
|
|
270
|
-
supabase.removeChannel(channel)
|
|
271
|
-
}
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
**Server side — emitir payload completo no broadcast:**
|
|
275
|
-
|
|
276
|
-
```typescript
|
|
277
|
-
// PT-BR: Edge Function que cria order e broadcast com record completo
|
|
278
|
-
Deno.serve(async (req) => {
|
|
279
|
-
const supabase = createClient(SUPABASE_URL, SERVICE_ROLE_KEY)
|
|
280
|
-
const order = await req.json()
|
|
281
|
-
|
|
282
|
-
// PT-BR: 1) INSERT no líder
|
|
283
|
-
const { data: created } = await supabase
|
|
284
|
-
.from('orders')
|
|
285
|
-
.insert(order)
|
|
286
|
-
.select()
|
|
287
|
-
.single()
|
|
288
|
-
|
|
289
|
-
// PT-BR: 2) broadcast com record completo — clients confiam neste payload
|
|
290
|
-
await supabase
|
|
291
|
-
.channel(`org:orders:${order.org_id}`)
|
|
292
|
-
.send({
|
|
293
|
-
type: 'broadcast',
|
|
294
|
-
event: 'new_order',
|
|
295
|
-
payload: { record: created }, // PT-BR: payload canônico
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
return new Response(JSON.stringify(created), { status: 201 })
|
|
299
|
-
})
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
**Cross-ref ATIVO:** [`supabase-realtime/SKILL.md`](../supabase-realtime/SKILL.md) (v1.8) define padrão de canal (`scope:entity:id`, `private:true`, `removeChannel` cleanup). Esta skill estende com o padrão `payload.record` específico para evitar replica lag bug.
|
|
303
|
-
|
|
304
|
-
## Anti-patterns
|
|
305
|
-
|
|
306
|
-
### Anti-pattern 1: Round-robin entre réplicas para o mesmo usuário
|
|
307
|
-
|
|
308
|
-
**Errado:**
|
|
309
|
-
```typescript
|
|
310
|
-
// PT-BR: pegar replica aleatoriamente cada read
|
|
311
|
-
const replica = REPLICAS[Math.floor(Math.random() * REPLICAS.length)]
|
|
312
|
-
return replica.from('messages').select()
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
**Por quê:** viola monotonic reads (DDIA p. 158). User vê mensagem X em uma leitura, depois X some na próxima (replica 2 ainda não replicou). Leituras "voltam no tempo".
|
|
316
|
-
|
|
317
|
-
**Certo:** sticky session por `hash(user_id) mod N` (Solução B acima).
|
|
318
|
-
|
|
319
|
-
### Anti-pattern 2: Re-fetch após broadcast
|
|
320
|
-
|
|
321
|
-
**Errado:**
|
|
322
|
-
```typescript
|
|
323
|
-
.on('broadcast', { event: 'new_order' }, async () => {
|
|
324
|
-
// PT-BR: re-fetch que pode atingir replica stale
|
|
325
|
-
const { data } = await supabase.from('orders').select() // ❌
|
|
326
|
-
setOrders(data)
|
|
327
|
-
})
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
**Por quê:** broadcast chega em 10-15ms, mas replication lag tipicamente 50-500ms. SELECT no callback **garantido** vai chegar antes da replica replicar. Bug "intermittent missing data".
|
|
331
|
-
|
|
332
|
-
**Certo:** confiar no `payload.record` enviado pelo server (REGRA #3 + Solução padrão "ler o próprio broadcast").
|
|
333
|
-
|
|
334
|
-
### Anti-pattern 3: `pg_last_wal_replay_lsn()` sem timeout
|
|
335
|
-
|
|
336
|
-
**Errado:**
|
|
337
|
-
```sql
|
|
338
|
-
-- PT-BR: loop infinito se replica falhou
|
|
339
|
-
do $$
|
|
340
|
-
begin
|
|
341
|
-
loop
|
|
342
|
-
if pg_last_wal_replay_lsn() >= captured_lsn::pg_lsn then exit; end if;
|
|
343
|
-
perform pg_sleep(0.05);
|
|
344
|
-
end loop;
|
|
345
|
-
end$$;
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
**Por quê:** se replica desconectou do WAL stream (network partition, disk full), `pg_last_wal_replay_lsn()` nunca alcança o LSN do líder. Query trava indefinidamente, esgota connection pool.
|
|
349
|
-
|
|
350
|
-
**Certo:** timeout 3-5s + fallback explícito para líder (REGRA #5 + função `wait_for_lsn` acima).
|
|
351
|
-
|
|
352
|
-
### Anti-pattern 4: Cross-partition para conversação causal
|
|
353
|
-
|
|
354
|
-
**Errado:**
|
|
355
|
-
```sql
|
|
356
|
-
-- PT-BR: messages particionadas por created_at (range temporal)
|
|
357
|
-
create table public.messages (...) partition by range (created_at);
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
**Por quê:** pergunta e resposta em uma conversação podem cair em partições diferentes (se virada de mês entre as duas). Viola consistent prefix reads — observador vê resposta antes da pergunta.
|
|
361
|
-
|
|
362
|
-
**Certo:** particionar por `conversation_id` (HASH), garante que toda a conversação fica na mesma partição = mesma ordem WAL = consistent prefix.
|
|
363
|
-
|
|
364
|
-
### Anti-pattern 5: Porta 6543 para `SELECT FOR UPDATE`
|
|
365
|
-
|
|
366
|
-
**Errado:**
|
|
367
|
-
```typescript
|
|
368
|
-
// PT-BR: tentando lock pessimista via Supavisor transaction mode
|
|
369
|
-
const { data } = await client6543.rpc('lock_order', { id })
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
**Por quê:** Supavisor 6543 (transaction mode) não preserva sessão entre statements — `SELECT FOR UPDATE` libera o lock na próxima query. Lock vira no-op silencioso.
|
|
373
|
-
|
|
374
|
-
**Certo:** porta 5432 (session mode) para qualquer operação que precisa estado de sessão (locks, prepared statements, `SET LOCAL`, advisory locks).
|
|
375
|
-
|
|
376
|
-
## Ver também
|
|
377
|
-
|
|
378
|
-
- [`_shared-dados-distribuidos/glossary.md`](../_shared-dados-distribuidos/glossary.md) — termos canônicos `read-after-write consistency`, `monotonic reads`, `consistent prefix reads`, `replication lag`, `leader-follower replication` (Phase 117)
|
|
379
|
-
- [`supabase-realtime/SKILL.md`](../supabase-realtime/SKILL.md) — broadcast com `private:true`, naming `scope:entity:id`, cleanup `removeChannel` (v1.8)
|
|
380
|
-
- [`multi-tenant-performance-scaling/SKILL.md`](../multi-tenant-performance-scaling/SKILL.md) — Supavisor connection string canônica, REGRA #1 porta 6543 (v1.21)
|
|
381
|
-
- [`supabase-database-functions/SKILL.md`](../supabase-database-functions/SKILL.md) — padrões PG functions (security invoker, search_path) usados em `get_current_lsn` e `wait_for_lsn`
|
|
382
|
-
- [Designing Data-Intensive Applications, Martin Kleppmann (O'Reilly 2017)](https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/) — Ch 5 "Problems With Replication Lag" (p. 155-160)
|
|
383
|
-
- [PostgreSQL Documentation — pg_last_wal_replay_lsn](https://www.postgresql.org/docs/current/functions-admin.html#FUNCTIONS-RECOVERY-INFO)
|
|
384
|
-
- [Supabase Read Replicas](https://supabase.com/docs/guides/platform/read-replicas)
|
|
385
|
-
- [Supabase Supavisor 1M Connections](https://supabase.com/blog/supavisor-1-million)
|
|
1
|
+
---
|
|
2
|
+
name: consistencia-leitura-replica
|
|
3
|
+
description: Use ao usar Supabase read replicas via Supavisor (porta 6543) ou ao combinar Realtime broadcast + leitura DB…
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Consistência Leitura Réplica — Supabase + Supavisor + Realtime
|
|
7
|
+
|
|
8
|
+
## Quando usar
|
|
9
|
+
|
|
10
|
+
LLM carrega esta skill ao usar Supabase Pro+ com **read replicas** ou ao combinar Realtime broadcast com leitura subsequente do DB. Trigger phrases:
|
|
11
|
+
|
|
12
|
+
- "Supabase read replica", "réplica de leitura"
|
|
13
|
+
- "porta 6543 vs 5432", "Supavisor session vs transaction"
|
|
14
|
+
- "read-after-write", "leitura após escrita inconsistente"
|
|
15
|
+
- "monotonic reads", "leituras não-monotônicas"
|
|
16
|
+
- "consistent prefix reads", "prefixo causal violado"
|
|
17
|
+
- "replication lag Supabase", "atraso de replicação"
|
|
18
|
+
- "broadcast Realtime + SELECT stale"
|
|
19
|
+
- "pg_last_wal_replay_lsn", "WAL position detection"
|
|
20
|
+
|
|
21
|
+
Esta skill aplica **DDIA Ch 5 "Problems With Replication Lag"** ao stack Supabase. Cross-referenciada por `supabase-realtime` (v1.8) ao bundlear broadcast + leitura, e por `multi-tenant-performance-scaling` (v1.21) ao escalar Postgres em Pro+.
|
|
22
|
+
|
|
23
|
+
Termos canônicos (`read-after-write consistency`, `monotonic reads`, `consistent prefix reads`, `replication lag`, `leader-follower replication`) definidos em [`_shared-dados-distribuidos/glossary.md`](../_shared-dados-distribuidos/glossary.md) seções (a) e (b) — esta skill **não duplica**, apenas linka.
|
|
24
|
+
|
|
25
|
+
## Regras absolutas
|
|
26
|
+
|
|
27
|
+
**REGRA #1 (read-after-write own data):** Para leituras do **próprio dado do usuário** dentro de janela de **5s após write**, rotear para **porta 5432 (líder)** — não 6543. Sem isso, usuário cria post → GET retorna 404 → percepção de bug. Justify: DDIA p. 156 "always read the user's own profile from the leader".
|
|
28
|
+
|
|
29
|
+
**REGRA #2 (sticky session monotonic):** Para usuários ativos lendo de réplicas, escolher réplica via `hash(user_id) mod N` — **não round-robin**. Round-robin viola monotonic reads (DDIA p. 158). Mitigação obrigatória: fallback para líder se réplica down.
|
|
30
|
+
|
|
31
|
+
**REGRA #3 (broadcast trust payload):** Após receber Realtime broadcast com `payload.record`, **NÃO** fazer SELECT subsequente. Confiar no payload — server é a fonte canônica do evento. SELECT pode atingir réplica que ainda não replicou (lag típico 50-500ms).
|
|
32
|
+
|
|
33
|
+
**REGRA #4 (causal partition):** Writes causalmente relacionados (pergunta + resposta em chat, parent + child em árvore) **DEVEM** ir para a mesma partição lógica. Em Supabase: usar mesmo `org_id` ou `conversation_id` como partition key. DDIA p. 159 "any writes which are causally related to each other are written to the same partition".
|
|
34
|
+
|
|
35
|
+
**REGRA #5 (LSN wait com timeout):** Quando usar `pg_last_wal_replay_lsn() >= captured_lsn`, **SEMPRE** com timeout (3-5s). Sem timeout, query trava se réplica falhou. Após timeout, fallback para líder.
|
|
36
|
+
|
|
37
|
+
## Patterns canônicos
|
|
38
|
+
|
|
39
|
+
### Problema 1: Read-after-write inconsistente (DDIA Ch 5, p. 156)
|
|
40
|
+
|
|
41
|
+
**Cenário canônico:** usuário cria post via form submit → tela mostra "criado com sucesso" → usuário clica "ver post" → request vai para réplica → réplica ainda não replicou → 404. Da perspectiva do usuário: "perdi meu dado".
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
User 1234 ──INSERT─→ Leader (5432) ┐
|
|
45
|
+
│ │ replication lag (50-500ms)
|
|
46
|
+
└──WAL──────────────→ Follower (6543, replica)
|
|
47
|
+
│
|
|
48
|
+
User 1234 ─SELECT────────────────────────────→ ❌ 404
|
|
49
|
+
(vai para replica via Supavisor)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Solução A — leitura no líder após write do mesmo usuário:**
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// PT-BR: client mantém timestamp do último write em memória
|
|
56
|
+
class SupabaseRouter {
|
|
57
|
+
private lastWriteAt: Map<string, number> = new Map() // userId → timestamp ms
|
|
58
|
+
private readonly STICKY_WINDOW_MS = 5000 // 5s leitura no líder
|
|
59
|
+
|
|
60
|
+
async write(userId: string, table: string, payload: unknown) {
|
|
61
|
+
const result = await this.leaderClient.from(table).insert(payload)
|
|
62
|
+
this.lastWriteAt.set(userId, Date.now())
|
|
63
|
+
return result
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async read(userId: string, table: string, filter: object) {
|
|
67
|
+
const lastWrite = this.lastWriteAt.get(userId) ?? 0
|
|
68
|
+
const elapsedMs = Date.now() - lastWrite
|
|
69
|
+
|
|
70
|
+
// PT-BR: dentro da janela 5s, ler do líder (porta 5432)
|
|
71
|
+
if (elapsedMs < this.STICKY_WINDOW_MS) {
|
|
72
|
+
return this.leaderClient.from(table).select().match(filter)
|
|
73
|
+
}
|
|
74
|
+
// PT-BR: fora da janela, OK ler de replica via pooler 6543
|
|
75
|
+
return this.replicaClient.from(table).select().match(filter)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Trade-off:** dentro da janela, perde benefício do read scaling. DDIA recomenda janela curta (1-5s) — cobre 99% dos casos UX sem sobrecarregar líder.
|
|
81
|
+
|
|
82
|
+
### Problema 2: Leituras não-monotônicas (DDIA Ch 5, p. 158)
|
|
83
|
+
|
|
84
|
+
**Cenário canônico:** usuário 2345 abre lista de comentários — primeira leitura vai para réplica 1 (lag 100ms) → vê comentário X. Segundo refresh vai para réplica 2 (lag 800ms) → comentário X **desapareceu**. Da perspectiva do usuário: "dado voltou no tempo".
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
User 2345 ─SELECT(1)────→ Replica 1 (lag 100ms) → 1 result ✅
|
|
88
|
+
User 2345 ─SELECT(2)────→ Replica 2 (lag 800ms) → 0 results ❌ (parecia ter sumido)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Solução B — sticky session por `user_id` em routing:**
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// PT-BR: hash determinístico do user_id → escolhe replica fixa para esse user
|
|
95
|
+
import { createHash } from 'node:crypto'
|
|
96
|
+
|
|
97
|
+
function pickReplica(userId: string, replicas: ReadonlyArray<SupabaseClient>): SupabaseClient {
|
|
98
|
+
const hash = createHash('sha256').update(userId).digest()
|
|
99
|
+
const idx = hash.readUInt32BE(0) % replicas.length
|
|
100
|
+
return replicas[idx]
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// PT-BR: usage com fallback para líder se replica falhar
|
|
104
|
+
async function readWithStickyReplica(userId: string, query: () => Promise<Result>) {
|
|
105
|
+
const replica = pickReplica(userId, REPLICAS)
|
|
106
|
+
try {
|
|
107
|
+
return await query.call(replica)
|
|
108
|
+
} catch (err) {
|
|
109
|
+
if (isReplicaDownError(err)) {
|
|
110
|
+
// PT-BR: fallback obrigatório — REGRA #2
|
|
111
|
+
return await query.call(LEADER)
|
|
112
|
+
}
|
|
113
|
+
throw err
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Pitfall:** réplica fica down → todos os usuários alocados a ela ficam sem leitura. Mitigação: detectar via health check + reroute para líder até réplica voltar.
|
|
119
|
+
|
|
120
|
+
### Problema 3: Prefixo causal violado (DDIA Ch 5, p. 159)
|
|
121
|
+
|
|
122
|
+
**Cenário canônico chat:** Mr Poons pergunta "How far into the future can you see, Mrs Cake?" → write vai para partição A. Mrs Cake responde "About ten seconds usually, Mr Poons." → write vai para partição B. Observador lê de B (lag baixo) e A (lag alto): **vê resposta antes da pergunta**.
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
Partição A (lag 800ms): "How far into the future..."
|
|
126
|
+
Partição B (lag 100ms): "About ten seconds..."
|
|
127
|
+
|
|
128
|
+
Observer lê B → vê resposta ✅
|
|
129
|
+
Observer lê A → vê pergunta ✅
|
|
130
|
+
Observer ordem percebida: resposta → pergunta ❌
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Solução parcial — particionamento por chave causal:**
|
|
134
|
+
|
|
135
|
+
```sql
|
|
136
|
+
-- PT-BR: ambas msgs (pergunta + resposta) particionadas por conversation_id
|
|
137
|
+
-- garante mesma partição lógica = mesma ordem WAL
|
|
138
|
+
create table public.messages (
|
|
139
|
+
id uuid primary key default gen_random_uuid(),
|
|
140
|
+
conversation_id uuid not null,
|
|
141
|
+
author_id uuid not null,
|
|
142
|
+
body text not null,
|
|
143
|
+
created_at timestamptz not null default now()
|
|
144
|
+
) partition by hash (conversation_id);
|
|
145
|
+
|
|
146
|
+
-- PT-BR: indexes ajudam ordering
|
|
147
|
+
create index messages_conv_created_idx
|
|
148
|
+
on public.messages (conversation_id, created_at);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Limitação:** garante consistent prefix dentro de uma partição. Cross-partition (ex: usuário em duas conversações simultâneas), DDIA p. 159 conclui "in general, ensuring consistent prefix reads requires snapshot isolation". Em Supabase prático: **manter conversação em uma tabela com chave causal explícita**.
|
|
152
|
+
|
|
153
|
+
### Solução C — Detecção stale via `pg_last_wal_replay_lsn()`
|
|
154
|
+
|
|
155
|
+
Quando precisa de garantia "este read viu meu write" sem rotear ao líder:
|
|
156
|
+
|
|
157
|
+
```sql
|
|
158
|
+
-- PT-BR: capturar LSN no líder após write (chamada do app via RPC)
|
|
159
|
+
create or replace function public.get_current_lsn()
|
|
160
|
+
returns text
|
|
161
|
+
language sql
|
|
162
|
+
security invoker
|
|
163
|
+
set search_path = ''
|
|
164
|
+
as $$
|
|
165
|
+
-- PT-BR: pg_current_wal_lsn() retorna posição atual do WAL no líder
|
|
166
|
+
select pg_current_wal_lsn()::text;
|
|
167
|
+
$$;
|
|
168
|
+
|
|
169
|
+
-- PT-BR: na replica, esperar até replay alcançar o LSN capturado
|
|
170
|
+
create or replace function public.wait_for_lsn(target_lsn text, timeout_ms int default 5000)
|
|
171
|
+
returns boolean
|
|
172
|
+
language plpgsql
|
|
173
|
+
security invoker
|
|
174
|
+
set search_path = ''
|
|
175
|
+
as $$
|
|
176
|
+
declare
|
|
177
|
+
start_at timestamptz := clock_timestamp();
|
|
178
|
+
elapsed_ms int;
|
|
179
|
+
begin
|
|
180
|
+
loop
|
|
181
|
+
-- PT-BR: pg_last_wal_replay_lsn() na replica = última posição replayed
|
|
182
|
+
if pg_last_wal_replay_lsn() >= target_lsn::pg_lsn then
|
|
183
|
+
return true;
|
|
184
|
+
end if;
|
|
185
|
+
|
|
186
|
+
elapsed_ms := extract(milliseconds from (clock_timestamp() - start_at))::int;
|
|
187
|
+
if elapsed_ms >= timeout_ms then
|
|
188
|
+
return false; -- PT-BR: REGRA #5 — timeout sem bloquear infinito
|
|
189
|
+
end if;
|
|
190
|
+
|
|
191
|
+
perform pg_sleep(0.05); -- PT-BR: 50ms entre polls
|
|
192
|
+
end loop;
|
|
193
|
+
end;
|
|
194
|
+
$$;
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Uso típico no client:**
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
// PT-BR: 1) write no líder, captura LSN
|
|
201
|
+
const { data: lsn } = await leaderClient.rpc('get_current_lsn')
|
|
202
|
+
await leaderClient.from('orders').insert(order)
|
|
203
|
+
|
|
204
|
+
// PT-BR: 2) read no líder, espera replay
|
|
205
|
+
const { data: ready } = await replicaClient.rpc('wait_for_lsn', {
|
|
206
|
+
target_lsn: lsn,
|
|
207
|
+
timeout_ms: 3000,
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
if (ready) {
|
|
211
|
+
// PT-BR: replica caught up, leitura é safe
|
|
212
|
+
return replicaClient.from('orders').select().eq('id', order.id)
|
|
213
|
+
}
|
|
214
|
+
// PT-BR: timeout — fallback para líder (REGRA #5)
|
|
215
|
+
return leaderClient.from('orders').select().eq('id', order.id)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Supavisor read replica routing
|
|
219
|
+
|
|
220
|
+
| Porta | Modo | Use case | Connection string |
|
|
221
|
+
|---|---|---|---|
|
|
222
|
+
| **6543** | Transaction (default Pro+) | Apps com pooler já configurado, edge runtimes, serverless | `postgresql://postgres.[ref]:pwd@aws-0-region.pooler.supabase.com:6543/postgres` |
|
|
223
|
+
| **5432** | Session (líder) | Reads críticas (read-after-write), writes, prepared statements, advisory locks | `postgresql://postgres.[ref]:pwd@aws-0-region.pooler.supabase.com:5432/postgres` |
|
|
224
|
+
| `pooler.read.*` | Réplica routing | Read-heavy workloads em Pro+ com replicas habilitadas | (futuro Supabase feature — placeholder hoje) |
|
|
225
|
+
|
|
226
|
+
**Decisão por tipo de query:**
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
SELECT do próprio dado dentro 5s do write? → 5432 (líder, REGRA #1)
|
|
230
|
+
SELECT cross-user, sem janela sticky? → 6543 (replica via Supavisor)
|
|
231
|
+
INSERT / UPDATE / DELETE? → 5432 (sempre líder)
|
|
232
|
+
SELECT FOR UPDATE / advisory lock? → 5432 (transaction precisa session mode)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Cross-ref ATIVO: [`multi-tenant-performance-scaling/SKILL.md`](../multi-tenant-performance-scaling/SKILL.md) (v1.21) cobre Supavisor REGRA #1 sob lente de connection pooling — esta skill cobre a mesma porta sob lente de consistência.
|
|
236
|
+
|
|
237
|
+
### Realtime broadcast + leitura DB — padrão "ler o próprio broadcast"
|
|
238
|
+
|
|
239
|
+
**Cenário canônico:** client A faz INSERT em `orders` → server emite Realtime broadcast `new_order` no canal `org:orders:org_42` → client B recebe broadcast → client B faz SELECT para refresh da lista. **Pode receber dado stale** (replica não replicou ainda).
|
|
240
|
+
|
|
241
|
+
**Sequência do bug:**
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
t=0ms Client A INSERT → Leader
|
|
245
|
+
t=10ms Server emite broadcast → todos clients no canal recebem
|
|
246
|
+
t=15ms Client B recebe broadcast → triggers re-fetch
|
|
247
|
+
t=20ms Client B SELECT → Replica (lag ainda 80ms)
|
|
248
|
+
t=20ms Replica retorna lista SEM o novo order ❌
|
|
249
|
+
t=80ms Replica finalmente replicou (mas client B já desenhou stale)
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
**Padrão correto — confiar no payload broadcast:**
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
// PT-BR: client confia no payload, NÃO faz SELECT subsequente
|
|
256
|
+
import { createClient } from '@supabase/supabase-js'
|
|
257
|
+
|
|
258
|
+
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
|
|
259
|
+
|
|
260
|
+
const channel = supabase
|
|
261
|
+
.channel('org:orders:org_42', { config: { private: true } })
|
|
262
|
+
.on('broadcast', { event: 'new_order' }, ({ payload }) => {
|
|
263
|
+
// PT-BR: REGRA #3 — confie no payload, NÃO faça SELECT
|
|
264
|
+
setOrders((prev) => [...prev, payload.record])
|
|
265
|
+
})
|
|
266
|
+
.subscribe()
|
|
267
|
+
|
|
268
|
+
// PT-BR: cleanup obrigatório — pattern de supabase-realtime v1.8
|
|
269
|
+
return () => {
|
|
270
|
+
supabase.removeChannel(channel)
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Server side — emitir payload completo no broadcast:**
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
// PT-BR: Edge Function que cria order e broadcast com record completo
|
|
278
|
+
Deno.serve(async (req) => {
|
|
279
|
+
const supabase = createClient(SUPABASE_URL, SERVICE_ROLE_KEY)
|
|
280
|
+
const order = await req.json()
|
|
281
|
+
|
|
282
|
+
// PT-BR: 1) INSERT no líder
|
|
283
|
+
const { data: created } = await supabase
|
|
284
|
+
.from('orders')
|
|
285
|
+
.insert(order)
|
|
286
|
+
.select()
|
|
287
|
+
.single()
|
|
288
|
+
|
|
289
|
+
// PT-BR: 2) broadcast com record completo — clients confiam neste payload
|
|
290
|
+
await supabase
|
|
291
|
+
.channel(`org:orders:${order.org_id}`)
|
|
292
|
+
.send({
|
|
293
|
+
type: 'broadcast',
|
|
294
|
+
event: 'new_order',
|
|
295
|
+
payload: { record: created }, // PT-BR: payload canônico
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
return new Response(JSON.stringify(created), { status: 201 })
|
|
299
|
+
})
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Cross-ref ATIVO:** [`supabase-realtime/SKILL.md`](../supabase-realtime/SKILL.md) (v1.8) define padrão de canal (`scope:entity:id`, `private:true`, `removeChannel` cleanup). Esta skill estende com o padrão `payload.record` específico para evitar replica lag bug.
|
|
303
|
+
|
|
304
|
+
## Anti-patterns
|
|
305
|
+
|
|
306
|
+
### Anti-pattern 1: Round-robin entre réplicas para o mesmo usuário
|
|
307
|
+
|
|
308
|
+
**Errado:**
|
|
309
|
+
```typescript
|
|
310
|
+
// PT-BR: pegar replica aleatoriamente cada read
|
|
311
|
+
const replica = REPLICAS[Math.floor(Math.random() * REPLICAS.length)]
|
|
312
|
+
return replica.from('messages').select()
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**Por quê:** viola monotonic reads (DDIA p. 158). User vê mensagem X em uma leitura, depois X some na próxima (replica 2 ainda não replicou). Leituras "voltam no tempo".
|
|
316
|
+
|
|
317
|
+
**Certo:** sticky session por `hash(user_id) mod N` (Solução B acima).
|
|
318
|
+
|
|
319
|
+
### Anti-pattern 2: Re-fetch após broadcast
|
|
320
|
+
|
|
321
|
+
**Errado:**
|
|
322
|
+
```typescript
|
|
323
|
+
.on('broadcast', { event: 'new_order' }, async () => {
|
|
324
|
+
// PT-BR: re-fetch que pode atingir replica stale
|
|
325
|
+
const { data } = await supabase.from('orders').select() // ❌
|
|
326
|
+
setOrders(data)
|
|
327
|
+
})
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**Por quê:** broadcast chega em 10-15ms, mas replication lag tipicamente 50-500ms. SELECT no callback **garantido** vai chegar antes da replica replicar. Bug "intermittent missing data".
|
|
331
|
+
|
|
332
|
+
**Certo:** confiar no `payload.record` enviado pelo server (REGRA #3 + Solução padrão "ler o próprio broadcast").
|
|
333
|
+
|
|
334
|
+
### Anti-pattern 3: `pg_last_wal_replay_lsn()` sem timeout
|
|
335
|
+
|
|
336
|
+
**Errado:**
|
|
337
|
+
```sql
|
|
338
|
+
-- PT-BR: loop infinito se replica falhou
|
|
339
|
+
do $$
|
|
340
|
+
begin
|
|
341
|
+
loop
|
|
342
|
+
if pg_last_wal_replay_lsn() >= captured_lsn::pg_lsn then exit; end if;
|
|
343
|
+
perform pg_sleep(0.05);
|
|
344
|
+
end loop;
|
|
345
|
+
end$$;
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
**Por quê:** se replica desconectou do WAL stream (network partition, disk full), `pg_last_wal_replay_lsn()` nunca alcança o LSN do líder. Query trava indefinidamente, esgota connection pool.
|
|
349
|
+
|
|
350
|
+
**Certo:** timeout 3-5s + fallback explícito para líder (REGRA #5 + função `wait_for_lsn` acima).
|
|
351
|
+
|
|
352
|
+
### Anti-pattern 4: Cross-partition para conversação causal
|
|
353
|
+
|
|
354
|
+
**Errado:**
|
|
355
|
+
```sql
|
|
356
|
+
-- PT-BR: messages particionadas por created_at (range temporal)
|
|
357
|
+
create table public.messages (...) partition by range (created_at);
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**Por quê:** pergunta e resposta em uma conversação podem cair em partições diferentes (se virada de mês entre as duas). Viola consistent prefix reads — observador vê resposta antes da pergunta.
|
|
361
|
+
|
|
362
|
+
**Certo:** particionar por `conversation_id` (HASH), garante que toda a conversação fica na mesma partição = mesma ordem WAL = consistent prefix.
|
|
363
|
+
|
|
364
|
+
### Anti-pattern 5: Porta 6543 para `SELECT FOR UPDATE`
|
|
365
|
+
|
|
366
|
+
**Errado:**
|
|
367
|
+
```typescript
|
|
368
|
+
// PT-BR: tentando lock pessimista via Supavisor transaction mode
|
|
369
|
+
const { data } = await client6543.rpc('lock_order', { id })
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**Por quê:** Supavisor 6543 (transaction mode) não preserva sessão entre statements — `SELECT FOR UPDATE` libera o lock na próxima query. Lock vira no-op silencioso.
|
|
373
|
+
|
|
374
|
+
**Certo:** porta 5432 (session mode) para qualquer operação que precisa estado de sessão (locks, prepared statements, `SET LOCAL`, advisory locks).
|
|
375
|
+
|
|
376
|
+
## Ver também
|
|
377
|
+
|
|
378
|
+
- [`_shared-dados-distribuidos/glossary.md`](../_shared-dados-distribuidos/glossary.md) — termos canônicos `read-after-write consistency`, `monotonic reads`, `consistent prefix reads`, `replication lag`, `leader-follower replication` (Phase 117)
|
|
379
|
+
- [`supabase-realtime/SKILL.md`](../supabase-realtime/SKILL.md) — broadcast com `private:true`, naming `scope:entity:id`, cleanup `removeChannel` (v1.8)
|
|
380
|
+
- [`multi-tenant-performance-scaling/SKILL.md`](../multi-tenant-performance-scaling/SKILL.md) — Supavisor connection string canônica, REGRA #1 porta 6543 (v1.21)
|
|
381
|
+
- [`supabase-database-functions/SKILL.md`](../supabase-database-functions/SKILL.md) — padrões PG functions (security invoker, search_path) usados em `get_current_lsn` e `wait_for_lsn`
|
|
382
|
+
- [Designing Data-Intensive Applications, Martin Kleppmann (O'Reilly 2017)](https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/) — Ch 5 "Problems With Replication Lag" (p. 155-160)
|
|
383
|
+
- [PostgreSQL Documentation — pg_last_wal_replay_lsn](https://www.postgresql.org/docs/current/functions-admin.html#FUNCTIONS-RECOVERY-INFO)
|
|
384
|
+
- [Supabase Read Replicas](https://supabase.com/docs/guides/platform/read-replicas)
|
|
385
|
+
- [Supabase Supavisor 1M Connections](https://supabase.com/blog/supavisor-1-million)
|