From d570cd65291ecddfaf584c36f6c30d2dd740c156 Mon Sep 17 00:00:00 2001 From: bootjp Date: Tue, 7 Jul 2026 07:19:59 +0900 Subject: [PATCH] docs: promote implemented design docs --- adapter/redis.go | 6 ++-- adapter/s3_admin.go | 2 +- adapter/s3_admin_test.go | 2 +- adapter/sqs_catalog.go | 2 +- adapter/sqs_throttle.go | 2 +- cmd/elastickv-snapshot-encode/main.go | 4 +-- docs/admin_deployment.md | 2 +- docs/admin_ui_key_visualizer_design.md | 2 +- .../2026_04_20_implemented_lease_read.md | 2 +- .../2026_04_24_implemented_admin_dashboard.md | 2 +- ...6_implemented_sqs_per_queue_throttling.md} | 6 ++-- ...4_27_implemented_keyviz_cluster_fanout.md} | 2 +- ..._27_implemented_keyviz_spa_integration.md} | 2 +- ...emented_admin_delete_bucket_safety_net.md} | 2 +- ...4_28_implemented_keyviz_adapter_labels.md} | 4 +-- ..._04_29_partial_snapshot_logical_decoder.md | 9 +++--- .../2026_04_29_proposed_logical_backup.md | 2 +- ...5_implemented_keyviz_subrange_sampling.md} | 2 +- ...5_implemented_snapshot_logical_encoder.md} | 28 ++++++++----------- ..._05_28_implemented_keyviz_hot_key_topk.md} | 6 ++-- ...implemented_sqs_side_record_derivation.md} | 12 ++++---- ..._06_01_implemented_snapshot_encode_cli.md} | 10 +++---- ...6_03_implemented_s3_collision_reversal.md} | 10 +++---- ...plemented_sqs_partitioned_fifo_encoder.md} | 14 +++++----- ...mented_redis_onephase_dedup_default_on.md} | 7 ++--- ...sed_hotspot_split_milestone3_automation.md | 4 +-- ...6_implemented_sqs_dlq_redrive_admin_ui.md} | 2 +- .../2026_06_23_proposed_scaling_roadmap.md | 8 +++--- internal/admin/keyviz_fanout.go | 2 +- internal/admin/keyviz_handler.go | 2 +- internal/admin/keyviz_hotkeys_fanout.go | 2 +- internal/admin/server.go | 2 +- internal/backup/encode.go | 2 +- internal/backup/encode_dynamodb.go | 2 +- internal/backup/encode_info.go | 2 +- internal/backup/encode_redis.go | 2 +- internal/backup/encode_s3.go | 2 +- internal/backup/encode_s3_collision.go | 2 +- internal/backup/encode_sqs.go | 2 +- internal/backup/encode_sqs_side.go | 2 +- internal/s3keys/keys.go | 2 +- internal/s3keys/keys_test.go | 2 +- keyviz/hot_keys.go | 2 +- keyviz/sampler.go | 10 +++---- keyviz/space_saving.go | 2 +- main.go | 4 +-- scripts/rolling-update.env.example | 4 +-- 47 files changed, 101 insertions(+), 105 deletions(-) rename docs/design/{2026_04_26_proposed_sqs_per_queue_throttling.md => 2026_04_26_implemented_sqs_per_queue_throttling.md} (98%) rename docs/design/{2026_04_27_proposed_keyviz_cluster_fanout.md => 2026_04_27_implemented_keyviz_cluster_fanout.md} (99%) rename docs/design/{2026_04_27_proposed_keyviz_spa_integration.md => 2026_04_27_implemented_keyviz_spa_integration.md} (99%) rename docs/design/{2026_04_28_proposed_admin_delete_bucket_safety_net.md => 2026_04_28_implemented_admin_delete_bucket_safety_net.md} (99%) rename docs/design/{2026_04_28_proposed_keyviz_adapter_labels.md => 2026_04_28_implemented_keyviz_adapter_labels.md} (99%) rename docs/design/{2026_05_25_proposed_keyviz_subrange_sampling.md => 2026_05_25_implemented_keyviz_subrange_sampling.md} (99%) rename docs/design/{2026_05_25_partial_snapshot_logical_encoder.md => 2026_05_25_implemented_snapshot_logical_encoder.md} (96%) rename docs/design/{2026_05_28_proposed_keyviz_hot_key_topk.md => 2026_05_28_implemented_keyviz_hot_key_topk.md} (99%) rename docs/design/{2026_05_30_proposed_sqs_side_record_derivation.md => 2026_05_30_implemented_sqs_side_record_derivation.md} (94%) rename docs/design/{2026_06_01_proposed_snapshot_encode_cli.md => 2026_06_01_implemented_snapshot_encode_cli.md} (97%) rename docs/design/{2026_06_03_proposed_s3_collision_reversal.md => 2026_06_03_implemented_s3_collision_reversal.md} (97%) rename docs/design/{2026_06_03_proposed_sqs_partitioned_fifo_encoder.md => 2026_06_03_implemented_sqs_partitioned_fifo_encoder.md} (97%) rename docs/design/{2026_06_10_proposed_redis_onephase_dedup_default_on.md => 2026_06_10_implemented_redis_onephase_dedup_default_on.md} (98%) rename docs/design/{2026_06_16_proposed_sqs_dlq_redrive_admin_ui.md => 2026_06_16_implemented_sqs_dlq_redrive_admin_ui.md} (99%) diff --git a/adapter/redis.go b/adapter/redis.go index bcf044c02..73eb0e5d1 100644 --- a/adapter/redis.go +++ b/adapter/redis.go @@ -204,7 +204,7 @@ type RedisServer struct { // docs/design/2026_05_21_proposed_txn_secondary_idempotency.md). The // FSM probe ships on every node in production, satisfying R5 (FSM // determinism across a rolling upgrade), so the gate now defaults on - // per docs/design/2026_06_10_proposed_redis_onephase_dedup_default_on.md. + // per docs/design/2026_06_10_implemented_redis_onephase_dedup_default_on.md. // Set ELASTICKV_REDIS_ONEPHASE_DEDUP=0 (or WithOnePhaseTxnDedup(false)) // to opt out β€” kept as a one-env-var operator rollback. onePhaseTxnDedup bool @@ -231,7 +231,7 @@ type RedisServerOption func(*RedisServer) // WithOnePhaseTxnDedup enables (or disables) the option-2 one-phase // idempotency dedup on list-push and MULTI/EXEC retries // (see RedisServer.onePhaseTxnDedup). On by default since the rollout -// recorded in docs/design/2026_06_10_proposed_redis_onephase_dedup_default_on.md; +// recorded in docs/design/2026_06_10_implemented_redis_onephase_dedup_default_on.md; // pass false to opt out from code, or set ELASTICKV_REDIS_ONEPHASE_DEDUP=0 // to opt out from the environment. The constructor option trumps the env var. // Standalone SET requires the separate WithStandaloneSetDedup gate; see @@ -493,7 +493,7 @@ func NewRedisServer(listen net.Listener, redisAddr string, store store.MVCCStore // onePhaseTxnDedup defaults on β€” the parent design's R5 rolling-upgrade // constraint is discharged (FSM probe shipped on every node months ago, // 12 consecutive green dedup-mode Jepsen runs 2026-05-31 β†’ 2026-06-10). - // See docs/design/2026_06_10_proposed_redis_onephase_dedup_default_on.md. + // See docs/design/2026_06_10_implemented_redis_onephase_dedup_default_on.md. // ELASTICKV_REDIS_ONEPHASE_DEDUP=0 opts out; the WithOnePhaseTxnDedup // constructor option still trumps the env var. onePhaseTxnDedup: os.Getenv("ELASTICKV_REDIS_ONEPHASE_DEDUP") != "0", diff --git a/adapter/s3_admin.go b/adapter/s3_admin.go index 058ade34e..de11762fa 100644 --- a/adapter/s3_admin.go +++ b/adapter/s3_admin.go @@ -379,7 +379,7 @@ func (s *S3Server) AdminPutBucketAcl(ctx context.Context, principal AdminPrincip // orphans left by any PutObject that committed // chunks/manifest between the empty-probe and the // Phase-1 commit. See design doc -// 2026_04_28_proposed_admin_delete_bucket_safety_net.md +// 2026_04_28_implemented_admin_delete_bucket_safety_net.md // Β§6.2 for the original single-OperationGroup design // and the dispatch-shape rejection that forced the // two-phase split. diff --git a/adapter/s3_admin_test.go b/adapter/s3_admin_test.go index 3cece589f..fb1430242 100644 --- a/adapter/s3_admin_test.go +++ b/adapter/s3_admin_test.go @@ -348,7 +348,7 @@ func TestS3Server_AdminListBuckets_PaginatesPastSinglePage(t *testing.T) { // TestS3Server_AdminDeleteBucket_SweepsOrphansAcrossAllPerBucketPrefixes // is the regression test for the AdminDeleteBucket TOCTOU race -// (design doc 2026_04_28_proposed_admin_delete_bucket_safety_net.md; +// (design doc 2026_04_28_implemented_admin_delete_bucket_safety_net.md; // coderabbitai πŸ”΄/🟠 on PR #669). The race lands when a concurrent // PutObject inserts data between AdminDeleteBucket's empty-probe // scan (at readTS) and its commit (at a later commitTS). Without diff --git a/adapter/sqs_catalog.go b/adapter/sqs_catalog.go index e450cfe9f..5e4f8af53 100644 --- a/adapter/sqs_catalog.go +++ b/adapter/sqs_catalog.go @@ -533,7 +533,7 @@ var sqsAttributeAppliers = map[string]attributeApplier{ "DeduplicationScope must be 'messageGroup' or 'queue'") }, // Throttle* are non-AWS extensions for per-queue rate limiting, - // see docs/design/2026_04_26_proposed_sqs_per_queue_throttling.md. + // see docs/design/2026_04_26_implemented_sqs_per_queue_throttling.md. // Each accepts a non-negative float64; the cross-attribute // validation that enforces both-zero-or-both-positive on each // (capacity, refill) pair, capacity β‰₯ refill, hard ceiling, and diff --git a/adapter/sqs_throttle.go b/adapter/sqs_throttle.go index a726c7847..1552b8a74 100644 --- a/adapter/sqs_throttle.go +++ b/adapter/sqs_throttle.go @@ -11,7 +11,7 @@ import ( ) // Per-queue throttling β€” token-bucket store that hangs off *SQSServer. -// See docs/design/2026_04_26_proposed_sqs_per_queue_throttling.md for +// See docs/design/2026_04_26_implemented_sqs_per_queue_throttling.md for // the full design and rollout context. This file implements Β§3.1 (bucket // store + token bucket), Β§3.3 (charging model), Β§3.4 (Throttling // envelope helpers) and the cache-invalidation primitives Β§3.1 calls diff --git a/cmd/elastickv-snapshot-encode/main.go b/cmd/elastickv-snapshot-encode/main.go index d3905d769..a1dd6c812 100644 --- a/cmd/elastickv-snapshot-encode/main.go +++ b/cmd/elastickv-snapshot-encode/main.go @@ -1,6 +1,6 @@ // Command elastickv-snapshot-encode is the Phase 0b M6 snapshot encoder -// described in docs/design/2026_06_01_proposed_snapshot_encode_cli.md -// (parent: docs/design/2026_05_25_partial_snapshot_logical_encoder.md). +// described in docs/design/2026_06_01_implemented_snapshot_encode_cli.md +// (parent: docs/design/2026_05_25_implemented_snapshot_logical_encoder.md). // // It reads a vendor-independent per-adapter directory tree (produced by // elastickv-snapshot-decode or by a future Phase 1 live extractor) and diff --git a/docs/admin_deployment.md b/docs/admin_deployment.md index 42792f31b..f8ddc388b 100644 --- a/docs/admin_deployment.md +++ b/docs/admin_deployment.md @@ -331,7 +331,7 @@ is tombstoned at the same `commitTS` rather than left as an unreachable orphan. The behaviour is intentional β€” the alternative was orphan objects that no API can enumerate or remove. See -[`docs/design/2026_04_28_proposed_admin_delete_bucket_safety_net.md`](design/2026_04_28_proposed_admin_delete_bucket_safety_net.md) +[`docs/design/2026_04_28_implemented_admin_delete_bucket_safety_net.md`](design/2026_04_28_implemented_admin_delete_bucket_safety_net.md) for the full race analysis. Operationally: diff --git a/docs/admin_ui_key_visualizer_design.md b/docs/admin_ui_key_visualizer_design.md index 1e24e6623..dee2d65a6 100644 --- a/docs/admin_ui_key_visualizer_design.md +++ b/docs/admin_ui_key_visualizer_design.md @@ -320,7 +320,7 @@ Because writes are recorded by Raft leaders and follower-local reads are recorde | 0 | `cmd/elastickv-admin` skeleton, token-protected `Admin` gRPC service stub, empty SPA shell, CI wiring. | Binary builds, `/api/cluster/overview` returns live data from a real node only when the configured admin token is supplied. | | 1 | Overview, Routes, Raft Groups, Adapters pages. `LiveSummary` added. No sampler. | All read-only pages match `grpcurl` ground truth. | | 2-A | Key Visualizer MVP server side: in-memory sampler with adaptive sub-sampling, leader writes, leader/follower reads, static matrix API with virtual-bucket metadata. | Benchmark gate green; Β±5% / 95%-CI accuracy SLO holds under synthetic bursts; matrix endpoint returns the local node's view. | -| 2-B | KeyViz SPA integration into `web/admin/`: heatmap page, series picker, row budget, manual + auto refresh. See `docs/design/2026_04_27_proposed_keyviz_spa_integration.md`. | Heatmap shows synthetic hotspot within ~5 s of `make client` driving traffic against `make run`; type check (`tsc -b --noEmit`) clean. | +| 2-B | KeyViz SPA integration into `web/admin/`: heatmap page, series picker, row budget, manual + auto refresh. See `docs/design/2026_04_27_implemented_keyviz_spa_integration.md`. | Heatmap shows synthetic hotspot within ~5 s of `make client` driving traffic against `make run`; type check (`tsc -b --noEmit`) clean. | | 2-C | Cluster fan-out: admin RPC that aggregates each node's local sampler view so the SPA shows a cluster-wide heatmap rather than the local node's slice. | Fan-out returns complete view with 1 node down; SPA renders aggregate within the Β§10 budget. | | 3 | Drill-down, split/merge continuity, namespace-isolated persistence of compacted columns distributed **per owning Raft group**, lineage recovery, and retention GC. | Heatmap remains continuous across a live `SplitRange`; restart preserves last 7 days; expired data and stale lineage records are collected; no single Raft group sees more than its share of KeyViz writes. | | 4 (deferred) | Mutating admin operations (`SplitRange` from UI), browser login, RBAC, and identity-provider integration. Out of scope for this design; a follow-up design will cover it. | β€” | diff --git a/docs/design/2026_04_20_implemented_lease_read.md b/docs/design/2026_04_20_implemented_lease_read.md index efc4ce483..36c73b5cd 100644 --- a/docs/design/2026_04_20_implemented_lease_read.md +++ b/docs/design/2026_04_20_implemented_lease_read.md @@ -1,6 +1,6 @@ # Lease Read Design -Status: Proposed +Status: Implemented Author: bootjp Date: 2026-04-20 diff --git a/docs/design/2026_04_24_implemented_admin_dashboard.md b/docs/design/2026_04_24_implemented_admin_dashboard.md index a06b8b966..bcd209898 100644 --- a/docs/design/2026_04_24_implemented_admin_dashboard.md +++ b/docs/design/2026_04_24_implemented_admin_dashboard.md @@ -14,7 +14,7 @@ | **P3** β€” React SPA + embed | βœ… shipped | #649, #650 | | **P4** β€” TLS, read-only role, CSRF, `docs/admin.md`, deployment runbook + `scripts/rolling-update.sh` admin support | βœ… shipped | TLS / role / CSRF live in P1; operator doc + runbook + script wiring in #674 / #669 / #678 | -The AdminDeleteBucket TOCTOU is fully resolved: see [`2026_04_28_proposed_admin_delete_bucket_safety_net.md`](2026_04_28_proposed_admin_delete_bucket_safety_net.md) for the safety-net design and [`docs/admin_deployment.md`](../admin_deployment.md) Β§4.6 for the operator-side contract (a `PutObject` 200-OK landing during the race window can be swept by the concurrent admin delete; pause writes before delete to retain in-flight writes). +The AdminDeleteBucket TOCTOU is fully resolved: see [`2026_04_28_implemented_admin_delete_bucket_safety_net.md`](2026_04_28_implemented_admin_delete_bucket_safety_net.md) for the safety-net design and [`docs/admin_deployment.md`](../admin_deployment.md) Β§4.6 for the operator-side contract (a `PutObject` 200-OK landing during the race window can be swept by the concurrent admin delete; pause writes before delete to retain in-flight writes). ### Out-of-scope follow-ups diff --git a/docs/design/2026_04_26_proposed_sqs_per_queue_throttling.md b/docs/design/2026_04_26_implemented_sqs_per_queue_throttling.md similarity index 98% rename from docs/design/2026_04_26_proposed_sqs_per_queue_throttling.md rename to docs/design/2026_04_26_implemented_sqs_per_queue_throttling.md index 91e66be67..7c0ffe7dc 100644 --- a/docs/design/2026_04_26_proposed_sqs_per_queue_throttling.md +++ b/docs/design/2026_04_26_implemented_sqs_per_queue_throttling.md @@ -1,6 +1,6 @@ # Per-Queue Throttling and Tenant Fairness for the SQS Adapter -**Status:** Proposed +**Status:** Implemented **Author:** bootjp **Date:** 2026-04-26 @@ -8,7 +8,7 @@ ## 1. Background and Motivation -elastickv's SQS adapter currently has **no per-queue rate limiting**. A single tenant's runaway producer can: +Before this work, elastickv's SQS adapter had **no per-queue rate limiting**. A single tenant's runaway producer could: 1. Saturate the leader's Raft proposal pipeline (one OCC dispatch per `SendMessage`), pushing latency on every other queue's writes through the same shard. 2. Exhaust the receive-path's visibility-index scan budget (`sqsVisScanPageLimit = 1024`), causing other tenants' `ReceiveMessage` calls to time out empty. @@ -16,7 +16,7 @@ elastickv's SQS adapter currently has **no per-queue rate limiting**. A single t Phase 3.C in [`docs/design/2026_04_24_partial_sqs_compatible_adapter.md`](2026_04_24_partial_sqs_compatible_adapter.md) Β§16.5 marks this as TODO. AWS itself enforces per-account / per-API limits ("standard request throttle of 3000 RPS per region per AWS account" plus per-API limits like 300 TPS for batch APIs); operators running elastickv as a multi-tenant SQS facade need an equivalent control plane. Without it, the only knobs are (a) shard-level capacity (too coarse β€” adding a shard requires a Raft membership change) and (b) external load-balancer rate limiting (no visibility into per-queue cost). -This document proposes per-queue token-bucket throttling, configured per-queue in queue meta, evaluated at the SQS-adapter layer on the leader, and surfaced as the same `Throttling.Sender` error AWS uses (so existing SDK retry/backoff logic engages naturally). +This document describes per-queue token-bucket throttling, configured per-queue in queue meta, evaluated at the SQS-adapter layer on the leader, and surfaced as the same `Throttling.Sender` error AWS uses (so existing SDK retry/backoff logic engages naturally). --- diff --git a/docs/design/2026_04_27_proposed_keyviz_cluster_fanout.md b/docs/design/2026_04_27_implemented_keyviz_cluster_fanout.md similarity index 99% rename from docs/design/2026_04_27_proposed_keyviz_cluster_fanout.md rename to docs/design/2026_04_27_implemented_keyviz_cluster_fanout.md index 6e1b8f762..ad864e503 100644 --- a/docs/design/2026_04_27_proposed_keyviz_cluster_fanout.md +++ b/docs/design/2026_04_27_implemented_keyviz_cluster_fanout.md @@ -1,5 +1,5 @@ --- -status: proposed +status: implemented phase: 2-C parent_design: docs/admin_ui_key_visualizer_design.md date: 2026-04-27 diff --git a/docs/design/2026_04_27_proposed_keyviz_spa_integration.md b/docs/design/2026_04_27_implemented_keyviz_spa_integration.md similarity index 99% rename from docs/design/2026_04_27_proposed_keyviz_spa_integration.md rename to docs/design/2026_04_27_implemented_keyviz_spa_integration.md index 3a6b05fd2..4a6bead23 100644 --- a/docs/design/2026_04_27_proposed_keyviz_spa_integration.md +++ b/docs/design/2026_04_27_implemented_keyviz_spa_integration.md @@ -1,5 +1,5 @@ --- -status: proposed +status: implemented phase: 2-B parent_design: docs/admin_ui_key_visualizer_design.md date: 2026-04-27 diff --git a/docs/design/2026_04_28_proposed_admin_delete_bucket_safety_net.md b/docs/design/2026_04_28_implemented_admin_delete_bucket_safety_net.md similarity index 99% rename from docs/design/2026_04_28_proposed_admin_delete_bucket_safety_net.md rename to docs/design/2026_04_28_implemented_admin_delete_bucket_safety_net.md index 4565527b8..385da36b9 100644 --- a/docs/design/2026_04_28_proposed_admin_delete_bucket_safety_net.md +++ b/docs/design/2026_04_28_implemented_admin_delete_bucket_safety_net.md @@ -1,6 +1,6 @@ # AdminDeleteBucket Orphan-Object Safety Net -**Status:** Proposed +**Status:** Implemented **Author:** bootjp **Date:** 2026-04-28 diff --git a/docs/design/2026_04_28_proposed_keyviz_adapter_labels.md b/docs/design/2026_04_28_implemented_keyviz_adapter_labels.md similarity index 99% rename from docs/design/2026_04_28_proposed_keyviz_adapter_labels.md rename to docs/design/2026_04_28_implemented_keyviz_adapter_labels.md index 88ad5e4ef..723762649 100644 --- a/docs/design/2026_04_28_proposed_keyviz_adapter_labels.md +++ b/docs/design/2026_04_28_implemented_keyviz_adapter_labels.md @@ -1,5 +1,5 @@ --- -status: proposed +status: implemented phase: 2-C+ parent_design: docs/admin_ui_key_visualizer_design.md author: bootjp @@ -579,7 +579,7 @@ from PR #694: Claude bot critical, Gemini high.) The fan-out aggregator's per-cell merge key gains the label: - Phase 2-C (current): `(bucketID, raftGroupID, leaderTerm, - windowStart)` per design `2026_04_27_proposed_keyviz_cluster_fanout.md` + windowStart)` per design `2026_04_27_implemented_keyviz_cluster_fanout.md` Β§4. - With labels: same tuple β€” but `bucketID` itself now carries the label via the Β§5 composite (`route:1:dynamo`). The merge key diff --git a/docs/design/2026_04_29_partial_snapshot_logical_decoder.md b/docs/design/2026_04_29_partial_snapshot_logical_decoder.md index b4523017c..686b18599 100644 --- a/docs/design/2026_04_29_partial_snapshot_logical_decoder.md +++ b/docs/design/2026_04_29_partial_snapshot_logical_decoder.md @@ -6,9 +6,10 @@ Date: 2026-04-29 > **Lifecycle (2026-05-25):** Phase 0a (decoder) has fully shipped β€” > `internal/backup/` + `cmd/elastickv-snapshot-decode` (PRs #790, -> #791, #792, #806, #810). Phase 0b (encoder) is specified in detail -> in `2026_05_25_proposed_snapshot_logical_encoder.md` and is not yet -> implemented. Phase 0c (operator integration) is open. This doc +> #791, #792, #806, #810). Phase 0b (encoder) has shipped and is +> specified in detail in +> `2026_05_25_implemented_snapshot_logical_encoder.md`. Phase 0c +> (operator integration) is open. This doc > remains the format owner; the encoder doc owns the reverse-direction > wire-format reconstruction. @@ -30,7 +31,7 @@ terminates on a clean EOF at the start of a key-length field `ReadSnapshot`). A CRC32C footer exists only on the *MVCC streaming restore* path (`store/lsm_store.go` `readStreamingMVCCRestoreHeader`), which is a different framing the decoder/encoder do not touch. See -`2026_05_25_proposed_snapshot_logical_encoder.md` Β§"Why a separate +`2026_05_25_implemented_snapshot_logical_encoder.md` Β§"Why a separate design doc" item 3. Snapshots are taken automatically every `defaultSnapshotEvery = 10000` diff --git a/docs/design/2026_04_29_proposed_logical_backup.md b/docs/design/2026_04_29_proposed_logical_backup.md index 24c033cd8..39350d188 100644 --- a/docs/design/2026_04_29_proposed_logical_backup.md +++ b/docs/design/2026_04_29_proposed_logical_backup.md @@ -1570,7 +1570,7 @@ Scope: out of this proposal; mentioned only to draw the boundary. - Concurrent multi-cluster fan-out (one logical backup spanning shards on different physical clusters) β€” depends on the `Distribution` control plane being fan-out-aware (see - `2026_04_27_proposed_keyviz_cluster_fanout.md`). + `2026_04_27_implemented_keyviz_cluster_fanout.md`). ## Required Tests diff --git a/docs/design/2026_05_25_proposed_keyviz_subrange_sampling.md b/docs/design/2026_05_25_implemented_keyviz_subrange_sampling.md similarity index 99% rename from docs/design/2026_05_25_proposed_keyviz_subrange_sampling.md rename to docs/design/2026_05_25_implemented_keyviz_subrange_sampling.md index c4ec828a6..6bc5e6ed4 100644 --- a/docs/design/2026_05_25_proposed_keyviz_subrange_sampling.md +++ b/docs/design/2026_05_25_implemented_keyviz_subrange_sampling.md @@ -1,5 +1,5 @@ --- -status: proposed +status: implemented phase: 2-A+ parent_design: docs/admin_ui_key_visualizer_design.md author: bootjp diff --git a/docs/design/2026_05_25_partial_snapshot_logical_encoder.md b/docs/design/2026_05_25_implemented_snapshot_logical_encoder.md similarity index 96% rename from docs/design/2026_05_25_partial_snapshot_logical_encoder.md rename to docs/design/2026_05_25_implemented_snapshot_logical_encoder.md index 39b5ee779..3eafc1e7d 100644 --- a/docs/design/2026_05_25_partial_snapshot_logical_encoder.md +++ b/docs/design/2026_05_25_implemented_snapshot_logical_encoder.md @@ -1,8 +1,8 @@ # Snapshot Logical Encoder (Phase 0b) -Status: Partial (M1–M5 merged via PRs #807 / #841 / #847 / #849 / #864 / #846 / #892; M6 CLI in review at PR #896) +Status: Implemented (M1-M6 merged; external-sort remains a future optimization) Author: bootjp -Date: 2026-05-25 (promoted proposed β†’ partial 2026-06-01 in PR #896) +Date: 2026-05-25 ## Background @@ -22,9 +22,9 @@ specifics the parent doc left at sketch level (parent Β§"Encoder: The parent doc is the format owner and remains authoritative for the directory-tree shape, filename encoding, and `MANIFEST.json`. This doc owns the **reverse-direction wire-format reconstruction** and the -decisions that only arise on the encode side. On landing this -proposal, the parent doc is promoted `proposed` β†’ `partial` (Phase 0a -shipped) via `git mv`. +decisions that only arise on the encode side. The Phase 0b v1 encoder +is implemented through the CLI milestone; the external-sort path +remains a future optimization outside the implemented v1 surface. ## Why a separate design doc @@ -266,14 +266,11 @@ covered by a cross-check test that asserts the encoder's derived index rows are byte-identical to the live adapter's output for a shared fixture. -> **Scope note / open question.** GSI and SQS-side-record derivation -> are the heaviest pieces. If the cross-check tests show the live -> builders are impractical to mirror offline within Phase 0b, the -> fallback is to emit only the user records + cheap indexes (TTL, -> generation) and document that GSI/SQS-side-state rebuild lazily on -> first adapter access after restart. The recommended path is full -> reconstruction; the fallback is called out in Β§"Milestones" as a -> per-adapter decision gate. +> **Scope note.** GSI and SQS-side-record derivation were the heaviest +> pieces. The implemented path follows the full-reconstruction option: +> derived GSI and SQS side rows are emitted offline and pinned by +> cross-check tests against the live key builders. The lazy-rebuild +> fallback below was not chosen for Phase 0b v1. > > **The fallback is not zero-cost transparency.** A missing GSI row > makes a DynamoDB GSI query return empty *silently* (no error); a @@ -399,9 +396,8 @@ output file, so a node never receives an unloadable `.fsm`. ## Milestones (per-adapter PRs, mirroring Phase 0a) -Doc-first: this proposal lands as its own PR before any code. Then, -in order (each its own PR, each with the cross-check + round-trip -tests for that adapter): +Phase 0b v1 landed in the following order (each with cross-check and +round-trip coverage for the adapter slice): 1. **Encoder core** β€” `encode.go`: MANIFEST read/validate, MVCC re-encoding, in-memory sort, EKVPBBL1 writer, in-process round-trip diff --git a/docs/design/2026_05_28_proposed_keyviz_hot_key_topk.md b/docs/design/2026_05_28_implemented_keyviz_hot_key_topk.md similarity index 99% rename from docs/design/2026_05_28_proposed_keyviz_hot_key_topk.md rename to docs/design/2026_05_28_implemented_keyviz_hot_key_topk.md index 7d93bd64b..4df02e062 100644 --- a/docs/design/2026_05_28_proposed_keyviz_hot_key_topk.md +++ b/docs/design/2026_05_28_implemented_keyviz_hot_key_topk.md @@ -1,8 +1,8 @@ --- -status: proposed +status: implemented phase: 2-A++ parent_design: docs/admin_ui_key_visualizer_design.md -related_design: docs/design/2026_05_25_proposed_keyviz_subrange_sampling.md +related_design: docs/design/2026_05_25_implemented_keyviz_subrange_sampling.md author: bootjp date: 2026-05-28 --- @@ -13,7 +13,7 @@ date: 2026-05-28 Phase 2-A+ sub-range sampling (#836 + #841) shows hot **sub-ranges**: the heatmap tells you *which region of the key space* is busy. The -sub-range design (`2026_05_25_proposed_keyviz_subrange_sampling.md` Β§2.2 +sub-range design (`2026_05_25_implemented_keyviz_subrange_sampling.md` Β§2.2 / Β§8) explicitly deferred naming the actual hottest **keys** β€” "`Top-K can layer on later as a drill-down`." This is that follow-up. diff --git a/docs/design/2026_05_30_proposed_sqs_side_record_derivation.md b/docs/design/2026_05_30_implemented_sqs_side_record_derivation.md similarity index 94% rename from docs/design/2026_05_30_proposed_sqs_side_record_derivation.md rename to docs/design/2026_05_30_implemented_sqs_side_record_derivation.md index cac7f6821..48b280c63 100644 --- a/docs/design/2026_05_30_proposed_sqs_side_record_derivation.md +++ b/docs/design/2026_05_30_implemented_sqs_side_record_derivation.md @@ -1,10 +1,10 @@ -# SQS side-record derivation (Phase 0b M5-2) β€” proposed +# SQS side-record derivation (Phase 0b M5-2) β€” implemented -**Status:** Proposed (no implementation yet). -**Parent:** [`2026_05_25_proposed_snapshot_logical_encoder.md`](2026_05_25_proposed_snapshot_logical_encoder.md) β€” this resolves the Β§"Per-adapter reverse encoders / SQS per-message side records" decision gate that M5-1 (`PR #846`) explicitly deferred. +**Status:** Implemented. +**Parent:** [`2026_05_25_implemented_snapshot_logical_encoder.md`](2026_05_25_implemented_snapshot_logical_encoder.md) β€” this resolves the Β§"Per-adapter reverse encoders / SQS per-message side records" decision gate that M5-1 (`PR #846`) explicitly deferred. **Predecessor on disk:** M5-1 emits `!sqs|queue|meta|`, `!sqs|queue|gen|`, `!sqs|queue|seq|`, `!sqs|msg|data|` for every queue + message in the dump. The **side records** below (vis / byage / dedup; group is intentionally not emitted β€” see table) are not produced by M5-1; restoring without the emitted three silently breaks FIFO dedup, the by-age reaper scan, and visibility resumption. -## What needs to land +## Implemented behavior For every `!sqs|msg|data|...` record M5-1 already writes, the encoder must also write the **derived** side records the live adapter would have written: @@ -59,7 +59,7 @@ The parent doc (Β§"Re-derivable indexes" β†’ scope note) allows a per-adapter fa ## Deferred (partitioned-FIFO) -M5-2 inherits M5-1's `ErrSQSEncodeUnsupportedPartitioned` gate β€” any queue with `PartitionCount > 1` is rejected by `encodeQueue` before `encodeQueueMessages` is reached, so the side-record walk is never invoked for partitioned queues. Lifting that gate requires coordinated changes to BOTH M5-1 (decoder dump format + encoder iteration over partitions) AND M5-2 (partitioned-key constructors for vis/byage/dedup); the project hasn't sequenced that work yet. +M5-2 inherited M5-1's `ErrSQSEncodeUnsupportedPartitioned` gate at the time this slice was written. The coordinated decoder+encoder lift for partitioned FIFO queues later landed in [`2026_06_03_implemented_sqs_partitioned_fifo_encoder.md`](2026_06_03_implemented_sqs_partitioned_fifo_encoder.md). When the partitioned slice (provisionally M5-3) lands, it should add these key shapes β€” sourced from `SqsPartitionedMsg{Vis,ByAge,Dedup}Prefix` + `sqsPartitionedMsg{...}Key` in `adapter/sqs_keys.go`: @@ -94,7 +94,7 @@ Wire into `encode_sqs.go`'s existing `encodeQueueMessages` walk: after `addMessa `TestSQSEncodeSideRecordsCrossCheckClassic` is the Β§"Encoder cross-check" pattern the parent doc Β§"Per-adapter reverse encoders" mandates. The two restore round-trip tests pin the end-to-end correctness rationale for choosing full reconstruction over lazy. -## Self-review (5 passes) β€” proposed +## Self-review (5 passes) 1. **Data loss.** Full reconstruction (not fallback) eliminates the silent-redeliver + invisible-message classes called out above. Restore-time state matches what the live writer would have produced for the same send sequence. 2. **Concurrency / distributed failures.** Pure offline derivation; no Raft, no lock ordering. The restored cluster's first sends/receives go through the normal OCC path against the restored state. diff --git a/docs/design/2026_06_01_proposed_snapshot_encode_cli.md b/docs/design/2026_06_01_implemented_snapshot_encode_cli.md similarity index 97% rename from docs/design/2026_06_01_proposed_snapshot_encode_cli.md rename to docs/design/2026_06_01_implemented_snapshot_encode_cli.md index 4ae6b029d..be22ff855 100644 --- a/docs/design/2026_06_01_proposed_snapshot_encode_cli.md +++ b/docs/design/2026_06_01_implemented_snapshot_encode_cli.md @@ -1,9 +1,9 @@ -# `cmd/elastickv-snapshot-encode` CLI (Phase 0b M6) β€” proposed +# `cmd/elastickv-snapshot-encode` CLI (Phase 0b M6) β€” implemented -**Status:** Proposed (no implementation yet). -**Parent:** [`2026_05_25_partial_snapshot_logical_encoder.md`](2026_05_25_partial_snapshot_logical_encoder.md) β€” this resolves the Β§"Encoder: `cmd/elastickv-snapshot-encode`" + Β§"Round-trip self-test" milestone (M6) the parent doc left at sketch level. Adapter slices M1–M5 are merged (#807/#841/#847/#849/#864/#846/#892); the parent doc was promoted `proposed` β†’ `partial` in v6 (#896); the CLI is the capstone that exposes them. +**Status:** Implemented. +**Parent:** [`2026_05_25_implemented_snapshot_logical_encoder.md`](2026_05_25_implemented_snapshot_logical_encoder.md) β€” this resolves the Β§"Encoder: `cmd/elastickv-snapshot-encode`" + Β§"Round-trip self-test" milestone (M6) the parent doc left at sketch level. Adapter slices M1-M5 are merged (#807/#841/#847/#849/#864/#846/#892); the CLI is the capstone that exposes them. -## What needs to land +## Implemented surface A single binary `cmd/elastickv-snapshot-encode` that: @@ -174,7 +174,7 @@ cmd/elastickv-snapshot-encode/main_test.go # CLI-level tests (exit codes, w **P1 cluster-boot test deferred.** Parent doc's P1 test table includes `TestEncoderProducesLoadableSnapshot` β€” an end-to-end test that boots a single-node cluster from the encoded `.fsm` and reads back every adapter's data. That test is deferred from M6 because: (a) it requires the production server entrypoint + Pebble engine, not the encoder package; (b) `TestEncodeSnapshotLibraryRoundTrip` above already exercises the encoder β†’ decoder library round-trip exactly, and the `TestCLIRoundTripSelfTestAllAdapters` integration test exercises the full CLI; (c) the cluster-boot path is owned by `store/lsm_store.go`'s native restore code, which has its own coverage. M6 tracks the cluster-boot test as a follow-up task in the M6 PR description, not in this design. -## Self-review (5 passes) β€” proposed +## Self-review (5 passes) 1. **Data loss.** The CLI is a wrapper over already-merged + tested encoder slices. The only new write paths are the `.fsm` output and `ENCODE_INFO.json`; both use the WriteManifest-style fsync-then-close discipline (gemini r1 medium on #810). The self-test gate is a defense-in-depth check that catches encoder regressions before a restore operator does. 2. **Concurrency / distributed failures.** Pure offline. No Raft, no HLC issuance, no cluster contact. The output `.fsm` is loaded by a STOPPED node via stop-replace-restart (parent Β§"Restore via stop-replace-restart"); concurrency surfaces only on the receiving cluster's restart path, which is owned by the node's existing snapshot loader. diff --git a/docs/design/2026_06_03_proposed_s3_collision_reversal.md b/docs/design/2026_06_03_implemented_s3_collision_reversal.md similarity index 97% rename from docs/design/2026_06_03_proposed_s3_collision_reversal.md rename to docs/design/2026_06_03_implemented_s3_collision_reversal.md index fa4d4e9e3..0b196e55c 100644 --- a/docs/design/2026_06_03_proposed_s3_collision_reversal.md +++ b/docs/design/2026_06_03_implemented_s3_collision_reversal.md @@ -1,10 +1,10 @@ -# S3 collision-rename reversal (Phase 0b M4-2b) β€” proposed +# S3 collision-rename reversal (Phase 0b M4-2b) β€” implemented -**Status:** Proposed (no implementation yet). -**Parent:** [`2026_05_25_partial_snapshot_logical_encoder.md`](2026_05_25_partial_snapshot_logical_encoder.md) β€” this resolves Β§"S3 β€” bodies re-chunked + manifest + collision/suffix reversal" by adding the inverse of the decoder's rename-collisions write path. +**Status:** Implemented. +**Parent:** [`2026_05_25_implemented_snapshot_logical_encoder.md`](2026_05_25_implemented_snapshot_logical_encoder.md) β€” this resolves Β§"S3 β€” bodies re-chunked + manifest + collision/suffix reversal" by adding the inverse of the decoder's rename-collisions write path. **Predecessor on disk:** M4-1 (`PR #847`) emits `!s3|bucket|meta|` + `!s3|bucket|gen|`. M4-2a (`PR #864`) emits `!s3|obj|head|` + `!s3|blob|` for every object whose key is recoverable from the on-disk path alone. Both currently fail closed via `ErrS3EncodeUnsupportedCollision` whenever a bucket's dump tree carries a `KEYMAP.jsonl` collision-tracker β€” i.e., any dump that exercised the decoder's rename-collisions path is rejected without a partial restore (`internal/backup/encode_s3_objects.go:95-98`). -## What needs to land +## Implemented behavior For every bucket whose decoder run wrote a per-bucket `KEYMAP.jsonl`, the encoder must read that file and use it to translate on-disk dump filenames back to original S3 object keys before emitting `!s3|obj|head|` / `!s3|blob|` records. @@ -126,7 +126,7 @@ The slice ships as a single PR since the keymap loader, segment resolver, and in ## References -- Parent: `2026_05_25_partial_snapshot_logical_encoder.md` Β§"S3" +- Parent: `2026_05_25_implemented_snapshot_logical_encoder.md` Β§"S3" - Decoder write path: `internal/backup/s3.go:611-700` (`flushObjectWithCollision`, `closeBucketKeymap`) - Keymap format: `internal/backup/keymap.go` (single source of truth for `KeymapRecord`) - Sibling encoder pattern: `internal/backup/encode_redis.go:113-132` (Redis's `loadKeymap`) diff --git a/docs/design/2026_06_03_proposed_sqs_partitioned_fifo_encoder.md b/docs/design/2026_06_03_implemented_sqs_partitioned_fifo_encoder.md similarity index 97% rename from docs/design/2026_06_03_proposed_sqs_partitioned_fifo_encoder.md rename to docs/design/2026_06_03_implemented_sqs_partitioned_fifo_encoder.md index 1986bc948..8bff3eeda 100644 --- a/docs/design/2026_06_03_proposed_sqs_partitioned_fifo_encoder.md +++ b/docs/design/2026_06_03_implemented_sqs_partitioned_fifo_encoder.md @@ -1,10 +1,10 @@ -# SQS partitioned-FIFO reverse encoder (Phase 0b M5-3) β€” proposed +# SQS partitioned-FIFO reverse encoder (Phase 0b M5-3) β€” implemented -**Status:** Proposed (no implementation yet). -**Parent:** [`2026_05_25_partial_snapshot_logical_encoder.md`](2026_05_25_partial_snapshot_logical_encoder.md) β€” this lifts the Β§"SQS" decision gate that M5-1 (`PR #849`) and M5-2 (`PR #892`) deferred for `partition_count > 1`. -**Predecessor on disk:** M5-1 emits `!sqs|queue|meta|`, `!sqs|queue|gen|`, `!sqs|queue|seq|`, `!sqs|msg|data|` for classic queues. M5-2 adds `!sqs|msg|vis|`, `!sqs|msg|byage|`, `!sqs|msg|dedup|`. Both reject `PartitionCount > 1` via `ErrSQSEncodeUnsupportedPartitioned` (`internal/backup/encode_sqs.go:162`); the M5-2 doc explicitly defers partitioned-FIFO support to "M5-3." +**Status:** Implemented. +**Parent:** [`2026_05_25_implemented_snapshot_logical_encoder.md`](2026_05_25_implemented_snapshot_logical_encoder.md) β€” this lifts the Β§"SQS" decision gate that M5-1 (`PR #849`) and M5-2 (`PR #892`) deferred for `partition_count > 1`. +**Predecessor on disk:** M5-1 emits `!sqs|queue|meta|`, `!sqs|queue|gen|`, `!sqs|queue|seq|`, `!sqs|msg|data|` for classic queues. M5-2 adds `!sqs|msg|vis|`, `!sqs|msg|byage|`, `!sqs|msg|dedup|`. The M5-3 implementation removes the earlier `PartitionCount > 1` rejection and emits the partitioned key families below. -## What needs to land +## Implemented behavior For every queue with `partition_count > 1` in `_queue.json`, the encoder must read each message's partition assignment from the dump and emit the **partitioned** key family instead of the classic family: @@ -159,8 +159,8 @@ The slice ships as a single PR β€” the decoder format change and encoder partiti ## References -- Parent: `2026_05_25_partial_snapshot_logical_encoder.md` Β§"SQS" -- M5-2 doc (decision gate template, classic side records): `2026_05_30_proposed_sqs_side_record_derivation.md` +- Parent: `2026_05_25_implemented_snapshot_logical_encoder.md` Β§"SQS" +- M5-2 doc (decision gate template, classic side records): `2026_05_30_implemented_sqs_side_record_derivation.md` - M5-1 PR: #849 - M5-2 PR: #892 - Live partitioned constructors: `adapter/sqs_keys.go:337+` (`sqsPartitionedMsgDataKey` and siblings) diff --git a/docs/design/2026_06_10_proposed_redis_onephase_dedup_default_on.md b/docs/design/2026_06_10_implemented_redis_onephase_dedup_default_on.md similarity index 98% rename from docs/design/2026_06_10_proposed_redis_onephase_dedup_default_on.md rename to docs/design/2026_06_10_implemented_redis_onephase_dedup_default_on.md index 53aa2bc19..943ba4ea2 100644 --- a/docs/design/2026_06_10_proposed_redis_onephase_dedup_default_on.md +++ b/docs/design/2026_06_10_implemented_redis_onephase_dedup_default_on.md @@ -1,12 +1,11 @@ # Redis one-phase txn dedup β€” flip default to on -Status: Proposed +Status: Implemented Author: bootjp Date: 2026-06-10 -> **Status: proposed.** Flip -> `adapter.RedisServer.onePhaseTxnDedup` from default-off to default-on, -> closing the rollout of the option-2 idempotency design landed by +> **Status: implemented.** `adapter.RedisServer.onePhaseTxnDedup` is +> default-on, closing the rollout of the option-2 idempotency design landed by > [`2026_05_21_proposed_txn_secondary_idempotency.md`][parent]. No new > mechanism β€” the FSM probe (`dedupProbeOnePhase` in `kv/fsm.go`), the > wire change (`TxnMeta.PrevCommitTS`, V2-only when non-zero), and every diff --git a/docs/design/2026_06_11_proposed_hotspot_split_milestone3_automation.md b/docs/design/2026_06_11_proposed_hotspot_split_milestone3_automation.md index 6636f3f7a..3c025d6ac 100644 --- a/docs/design/2026_06_11_proposed_hotspot_split_milestone3_automation.md +++ b/docs/design/2026_06_11_proposed_hotspot_split_milestone3_automation.md @@ -28,7 +28,7 @@ The parent doc Β§1 states the central gap, and the code confirms it: - **`RecordAccess` is never called from a real request path.** Its only callers are `distribution/engine_test.go` and `distribution/watcher_test.go` (verified by `grep -rn RecordAccess --include='*.go'`). No code in `kv/`, `adapter/`, or `main.go` invokes it. The whole engine-counter detection path β€” counters, threshold check, `splitRange` β€” is dead code. - The `splitRange` it would call is midpoint-only (`midpoint(r.Start, r.End)` appends a `0x00` byte, `distribution/engine.go:541-547`) and mutates the **in-memory** engine routes directly, bypassing the catalog. That violates the repo convention that all catalog mutations go through `SplitRange` so the version bumps and watchers fan out (CLAUDE.md "Route catalog mutations must go through `SplitRange`"). It also contradicts the parent doc Β§3.1.3 requirement "Choose split keys from observed key distribution (not midpoint-only)." -Meanwhile, a **second**, fully-wired load-observation pipeline already exists: the keyviz sampler (`keyviz/sampler.go`). The coordinator calls `Sampler.Observe` per resolved `(RouteID, key)` on both the write path (`groupMutations β†’ observeMutation`, `kv/sharded_coordinator.go:1844` and `:1795-1800`) and the linearizable/lease read path (`observeRead`, `kv/sharded_coordinator.go:1819-1824`). The sampler keeps per-route read/write op counts and byte counts, windowed at a flush interval (`keyviz/flusher.go:19` `RunFlusher`), with an optional per-route Top-K hot-key sketch (`docs/design/2026_05_28_proposed_keyviz_hot_key_topk.md`) and order-preserving sub-range buckets (`docs/design/2026_05_25_proposed_keyviz_subrange_sampling.md`). It is allocation-free on the hot path and already behind `--keyvizEnabled`. +Meanwhile, a **second**, fully-wired load-observation pipeline already exists: the keyviz sampler (`keyviz/sampler.go`). The coordinator calls `Sampler.Observe` per resolved `(RouteID, key)` on both the write path (`groupMutations β†’ observeMutation`, `kv/sharded_coordinator.go:1844` and `:1795-1800`) and the linearizable/lease read path (`observeRead`, `kv/sharded_coordinator.go:1819-1824`). The sampler keeps per-route read/write op counts and byte counts, windowed at a flush interval (`keyviz/flusher.go:19` `RunFlusher`), with an optional per-route Top-K hot-key sketch (`docs/design/2026_05_28_implemented_keyviz_hot_key_topk.md`) and order-preserving sub-range buckets (`docs/design/2026_05_25_implemented_keyviz_subrange_sampling.md`). It is allocation-free on the hot path and already behind `--keyvizEnabled`. M3's job is to close the gap **without building a third counter pipeline**: drive detection off the keyviz sampler (the already-wired, already-sampled signal), feed candidates into a detector with hysteresis/cooldown, and have a scheduler invoke the existing `SplitRange` RPC. The engine's vestigial `RecordAccess`/`splitRange` path is retired (see Β§3.4). @@ -142,7 +142,7 @@ Options considered: **Follower-forwarded write caveat (codex P2).** `ShardedCoordinator.observeMutation` runs on the node that received the client request before `ShardRouter.Commit` forwards (`kv/sharded_coordinator.go:1795-1844`), and the follower's `Internal.Forward` handler calls `transactionManager.Commit` directly (`adapter/internal.go:52`) rather than re-entering the leader's `ShardedCoordinator` observation hook. Therefore even a route whose shard group is led by the default-group leader can be **under-observed** if most clients connect through followers. M3 does not treat "missing local rows" as evidence of low load: local evidence can promote a split, but absence of local evidence only means "unknown" and is fail-closed for target selection (Β§6.2) and conservative for candidacy (fewer splits). Full coverage for follower-forwarded writes needs follower reports / fan-out into the same detector interface and is tracked under OQ-5. -**Why not the keyviz cluster fan-out** (`docs/design/2026_04_27_proposed_keyviz_cluster_fanout.md`): that aggregates across nodes for the admin heatmap UI and dedupes write samples by `(groupID, leaderTerm)`. Pulling cross-node data in would add latency and a dependency on the fan-out being configured, so M3 stays leader-local and fail-closed on unknown load. If a future milestone wants complete cluster-wide scoring, including follower-forwarded writes, it can read the same fan-out aggregate behind the same detector interface (Β§5) β€” recorded as OQ-5. +**Why not the keyviz cluster fan-out** (`docs/design/2026_04_27_implemented_keyviz_cluster_fanout.md`): that aggregates across nodes for the admin heatmap UI and dedupes write samples by `(groupID, leaderTerm)`. Pulling cross-node data in would add latency and a dependency on the fan-out being configured, so M3 stays leader-local and fail-closed on unknown load. If a future milestone wants complete cluster-wide scoring, including follower-forwarded writes, it can read the same fan-out aggregate behind the same detector interface (Β§5) β€” recorded as OQ-5. **Known limitation β€” multi-leader (multi-group) deployments.** Leader-local consumption is only authoritative for routes whose shard group is led by the **same node** that runs the detector β€” i.e. the default-group leader (where the scheduler must run, Β§6.1). In a multi-group cluster (`--raftGroups` with G1, G2, …), the default-group leader is **not necessarily** the leader of G1/G2/…: each node runs a single `ShardedCoordinator` and `MemSampler` and only `Observe`s traffic it *receives* (`kv/sharded_coordinator.go:1795-1824`). Concretely, in a 3-node cluster where node A leads the default group and node B leads G1, **node A's sampler has no data for routes in G1 served through node B**, so the detector on A cannot score or split them. This is **broader than "heavy follower-forwarded reads"**: it is a structural gap for any route whose group leader differs from the default-group leader, not just an edge case of read forwarding. diff --git a/docs/design/2026_06_16_proposed_sqs_dlq_redrive_admin_ui.md b/docs/design/2026_06_16_implemented_sqs_dlq_redrive_admin_ui.md similarity index 99% rename from docs/design/2026_06_16_proposed_sqs_dlq_redrive_admin_ui.md rename to docs/design/2026_06_16_implemented_sqs_dlq_redrive_admin_ui.md index 5143813d7..1bb93a8b9 100644 --- a/docs/design/2026_06_16_proposed_sqs_dlq_redrive_admin_ui.md +++ b/docs/design/2026_06_16_implemented_sqs_dlq_redrive_admin_ui.md @@ -1,6 +1,6 @@ # SQS DLQ Redrive Policy and Admin Configuration UI -Status: Proposed +Status: Implemented Author: bootjp Date: 2026-06-16 diff --git a/docs/design/2026_06_23_proposed_scaling_roadmap.md b/docs/design/2026_06_23_proposed_scaling_roadmap.md index 91afcdfcf..fe95f5a35 100644 --- a/docs/design/2026_06_23_proposed_scaling_roadmap.md +++ b/docs/design/2026_06_23_proposed_scaling_roadmap.md @@ -269,9 +269,9 @@ memory each group's private cache/memtable pins. - **keyviz** β€” the per-route load sampler is wired and allocation-free on the write hot path (`observeMutation`, `kv/sharded_coordinator.go:1841-1846`), with proposed extensions for cluster fan-out - (`2026_04_27_proposed_keyviz_cluster_fanout.md`), - subrange sampling (`2026_05_25_proposed_keyviz_subrange_sampling.md`), - hot-key top-K (`2026_05_28_proposed_keyviz_hot_key_topk.md`), and per-cell + (`2026_04_27_implemented_keyviz_cluster_fanout.md`), + subrange sampling (`2026_05_25_implemented_keyviz_subrange_sampling.md`), + hot-key top-K (`2026_05_28_implemented_keyviz_hot_key_topk.md`), and per-cell conflict (implemented). It is the detection signal M3 reuses. Current adapter-direct Redis/DynamoDB/S3 reads that hit `MVCCStore.GetAt` bypass this coordinator sampler, so read-heavy hotspots remain invisible until read-path @@ -289,7 +289,7 @@ memory each group's private cache/memtable pins. command worker pool, optional Raft-thread pinning, per-client admission, XREAD O(N)β†’O(new)), S3 PUT admission control (`2026_04_25_proposed_s3_admission_control.md`), SQS per-queue throttling - (`2026_04_26_proposed_sqs_per_queue_throttling.md`). These bound *one + (`2026_04_26_implemented_sqs_per_queue_throttling.md`). These bound *one workload's* impact so it cannot starve the shared runtime / Raft control plane; they scale a deployment by making it predictable under adversarial or unbalanced load rather than by raising raw capacity. diff --git a/internal/admin/keyviz_fanout.go b/internal/admin/keyviz_fanout.go index 3a245f67a..431f19807 100644 --- a/internal/admin/keyviz_fanout.go +++ b/internal/admin/keyviz_fanout.go @@ -71,7 +71,7 @@ const keyVizMergeBucketHint = 64 // a stable row order; Responded counts ok=true entries; Expected is // the configured peer count plus self. // -// See docs/design/2026_04_27_proposed_keyviz_cluster_fanout.md 5. +// See docs/design/2026_04_27_implemented_keyviz_cluster_fanout.md 5. type FanoutResult struct { Nodes []FanoutNodeStatus `json:"nodes"` Responded int `json:"responded"` diff --git a/internal/admin/keyviz_handler.go b/internal/admin/keyviz_handler.go index 778fe6054..ae672e8b9 100644 --- a/internal/admin/keyviz_handler.go +++ b/internal/admin/keyviz_handler.go @@ -56,7 +56,7 @@ const keyVizRowBudgetCap = 1024 // // Fanout is non-nil when the handler is configured for cluster-wide // fan-out (Phase 2-C): it carries per-node status so the SPA can -// surface degraded responses inline (see design 2026_04_27_proposed_keyviz_cluster_fanout.md). +// surface degraded responses inline (see design 2026_04_27_implemented_keyviz_cluster_fanout.md). // The field is omitted from the wire form when fan-out is disabled // so old clients keep working unchanged. type KeyVizMatrix struct { diff --git a/internal/admin/keyviz_hotkeys_fanout.go b/internal/admin/keyviz_hotkeys_fanout.go index 23522f500..ac43d7fe4 100644 --- a/internal/admin/keyviz_hotkeys_fanout.go +++ b/internal/admin/keyviz_hotkeys_fanout.go @@ -23,7 +23,7 @@ import ( // per-node status surfaced in the response. // // Merge semantics differ from the matrix fan-out β€” see -// docs/design/2026_05_28_proposed_keyviz_hot_key_topk.md Β§6: +// docs/design/2026_05_28_implemented_keyviz_hot_key_topk.md Β§6: // // - count per key: SUM across peers (responses already carry // scaled-to-true estimates from buildHotKeysResponse). diff --git a/internal/admin/server.go b/internal/admin/server.go index af68d367f..00f672130 100644 --- a/internal/admin/server.go +++ b/internal/admin/server.go @@ -68,7 +68,7 @@ type ServerDeps struct { KeyVizFanout *KeyVizFanout // KeyVizHotKeys backs the per-cell Top-K drill-down handler at - // /admin/api/v1/keyviz/hotkeys (design 2026_05_28_proposed_keyviz_hot_key_topk). + // /admin/api/v1/keyviz/hotkeys (design 2026_05_28_implemented_keyviz_hot_key_topk). // Optional: nil makes the route 503 keyviz_disabled, the same shape // as the matrix route's disabled response. A non-nil source whose // HotKeysOptions() reports enabled=false serves 503 hotkeys_disabled diff --git a/internal/backup/encode.go b/internal/backup/encode.go index 88c571b9a..f8f366f8c 100644 --- a/internal/backup/encode.go +++ b/internal/backup/encode.go @@ -10,7 +10,7 @@ import ( ) // encode.go is the Phase 0b encoder core (design: -// docs/design/2026_05_25_proposed_snapshot_logical_encoder.md). It is +// docs/design/2026_05_25_implemented_snapshot_logical_encoder.md). It is // the inverse of decode.go: per-adapter reverse encoders (added in the // Redis/DynamoDB/S3/SQS milestones) hand reconstructed internal // (userKey, userValue, expireAt) records to a snapshotBuilder, which diff --git a/internal/backup/encode_dynamodb.go b/internal/backup/encode_dynamodb.go index 35bbe6b5d..1c5b89abe 100644 --- a/internal/backup/encode_dynamodb.go +++ b/internal/backup/encode_dynamodb.go @@ -13,7 +13,7 @@ import ( // encode_dynamodb.go is the Phase 0b DynamoDB reverse encoder β€” the // inverse of the DDBEncoder decoder in dynamodb.go (design: -// docs/design/2026_05_25_proposed_snapshot_logical_encoder.md +// docs/design/2026_05_25_implemented_snapshot_logical_encoder.md // Β§"DynamoDB"). // // This commit covers TABLE SCHEMAS: it reconstructs the diff --git a/internal/backup/encode_info.go b/internal/backup/encode_info.go index 5ae2773c4..7b3e7627c 100644 --- a/internal/backup/encode_info.go +++ b/internal/backup/encode_info.go @@ -29,7 +29,7 @@ type EncodeInfoSelfTest struct { } // EncodeInfo is the on-disk shape of .encode_info.json. Schema -// pinned by docs/design/2026_06_01_proposed_snapshot_encode_cli.md +// pinned by docs/design/2026_06_01_implemented_snapshot_encode_cli.md // Β§"ENCODE_INFO.json". Restore operators rely on this for "encoded for // the right cluster, by the right encoder version, against this exact // file" confirmation; tag changes are a breaking schema bump. diff --git a/internal/backup/encode_redis.go b/internal/backup/encode_redis.go index e0f465309..ca8958113 100644 --- a/internal/backup/encode_redis.go +++ b/internal/backup/encode_redis.go @@ -13,7 +13,7 @@ import ( // encode_redis.go is the Phase 0b Redis reverse encoder β€” the inverse // of the RedisDB decoder in redis_*.go (design: -// docs/design/2026_05_25_proposed_snapshot_logical_encoder.md Β§"Redis"). +// docs/design/2026_05_25_implemented_snapshot_logical_encoder.md Β§"Redis"). // It walks a decoded redis/db_/ subtree and feeds the reconstructed // internal records to a snapshotBuilder, which MVCC-frames and writes // them. diff --git a/internal/backup/encode_s3.go b/internal/backup/encode_s3.go index e79fce190..0e09055d4 100644 --- a/internal/backup/encode_s3.go +++ b/internal/backup/encode_s3.go @@ -14,7 +14,7 @@ import ( // encode_s3.go is the Phase 0b S3 reverse encoder β€” the inverse of the // S3 decoder in s3.go (type S3Encoder, which turns internal !s3|* records // into an s3// dump tree). Design: -// docs/design/2026_05_25_proposed_snapshot_logical_encoder.md Β§"S3". +// docs/design/2026_05_25_implemented_snapshot_logical_encoder.md Β§"S3". // // This slice (M4-1) covers BUCKET metadata: it reconstructs the // !s3|bucket|meta| record (JSON s3LiveBucketMeta) and the diff --git a/internal/backup/encode_s3_collision.go b/internal/backup/encode_s3_collision.go index 41eea8430..43ceaf7c1 100644 --- a/internal/backup/encode_s3_collision.go +++ b/internal/backup/encode_s3_collision.go @@ -16,7 +16,7 @@ import ( // turns that file back into a map[encoded-rel-path]KeymapRecord the // object walk consults to recover original S3 keys. // -// Design: docs/design/2026_06_03_proposed_s3_collision_reversal.md. +// Design: docs/design/2026_06_03_implemented_s3_collision_reversal.md. // // File-system safety: loadBucketKeymap re-runs the read-side // Lstat β†’ refuseHardLink β†’ Open sequence rather than reusing diff --git a/internal/backup/encode_sqs.go b/internal/backup/encode_sqs.go index cadd76653..3223d0174 100644 --- a/internal/backup/encode_sqs.go +++ b/internal/backup/encode_sqs.go @@ -15,7 +15,7 @@ import ( // encode_sqs.go is the Phase 0b SQS reverse encoder β€” the inverse of the // SQS decoder in sqs.go (type SQSEncoder, which turns internal !sqs|* // records into an sqs// dump tree). Design: -// docs/design/2026_05_25_proposed_snapshot_logical_encoder.md Β§"SQS". +// docs/design/2026_05_25_implemented_snapshot_logical_encoder.md Β§"SQS". // // This slice (M5-1) covers the QUEUE config and the MESSAGE data records, // plus the generation and (FIFO) sequence counters: diff --git a/internal/backup/encode_sqs_side.go b/internal/backup/encode_sqs_side.go index 2f079ac96..4a33ca3f0 100644 --- a/internal/backup/encode_sqs_side.go +++ b/internal/backup/encode_sqs_side.go @@ -170,7 +170,7 @@ func resolveDedupID(rec *sqsMessageRecord, meta *sqsQueueMetaPublic) string { // addSideRecords emits the vis + byage + (conditional) dedup rows that the // live adapter would have written alongside the !sqs|msg|data| record M5-1 // already stages. No !sqs|msg|group| rows are emitted at any time β€” see -// docs/design/2026_05_30_proposed_sqs_side_record_derivation.md "Families" +// docs/design/2026_05_30_implemented_sqs_side_record_derivation.md "Families" // table for why (loadFifoGroupLock treats key presence alone as "lock held"; // any value would permanently block every group post-restore). // diff --git a/internal/s3keys/keys.go b/internal/s3keys/keys.go index fe4f892c4..682105219 100644 --- a/internal/s3keys/keys.go +++ b/internal/s3keys/keys.go @@ -197,7 +197,7 @@ func ObjectManifestPrefixForBucket(bucket string, generation uint64) []byte { // BlobPrefixForBucket / GCUploadPrefixForBucket / RoutePrefixForBucket // each isolate to a single bucket+generation tuple. Used by // AdminDeleteBucket's DEL_PREFIX safety net (design doc -// 2026_04_28_proposed_admin_delete_bucket_safety_net.md): the bucket- +// 2026_04_28_implemented_admin_delete_bucket_safety_net.md): the bucket- // must-be-empty empty-probe is racy against concurrent PutObject, so // the delete commit also DEL_PREFIXes every per-bucket key family to // sweep anything that snuck in during the readTS β†’ commitTS window. diff --git a/internal/s3keys/keys_test.go b/internal/s3keys/keys_test.go index cbf6dfe51..e5e5fca5a 100644 --- a/internal/s3keys/keys_test.go +++ b/internal/s3keys/keys_test.go @@ -278,7 +278,7 @@ func TestBlobPrefixForUpload_IsPrefixOfBlobKeys(t *testing.T) { // TestPerBucketPrefixes_IsolateByBucketAndGeneration covers the // new *PrefixForBucket helpers used by AdminDeleteBucket's // DEL_PREFIX safety net (design doc -// 2026_04_28_proposed_admin_delete_bucket_safety_net.md). For each +// 2026_04_28_implemented_admin_delete_bucket_safety_net.md). For each // of the six per-bucket key families, the test pins three // invariants: // diff --git a/keyviz/hot_keys.go b/keyviz/hot_keys.go index b96fadbf0..e1b45f41e 100644 --- a/keyviz/hot_keys.go +++ b/keyviz/hot_keys.go @@ -7,7 +7,7 @@ import ( "time" ) -// Per-cell hot-key drill-down (design 2026_05_28_proposed_keyviz_hot_key_topk). +// Per-cell hot-key drill-down (design 2026_05_28_implemented_keyviz_hot_key_topk). // Off by default; opt in via MemSamplerOptions.HotKeysEnabled. Disabled-case // overhead is one early-return branch on Observe; enabled-case overhead is a // length check, the splitmix64 `nextSampleRoll() % R == 0` sample gate (see diff --git a/keyviz/sampler.go b/keyviz/sampler.go index 5a9c124ae..b9f8eb644 100644 --- a/keyviz/sampler.go +++ b/keyviz/sampler.go @@ -71,7 +71,7 @@ const ( type Sampler interface { // Observe records a single request against a route. Op identifies // the counter family. The key drives sub-range bucketing (the hot - // key/sub-range heatmap, see docs/design/2026_05_25_proposed_keyviz_subrange_sampling.md); + // key/sub-range heatmap, see docs/design/2026_05_25_implemented_keyviz_subrange_sampling.md); // len(key) and valueLen are summed into the matching *Bytes // counter; pass valueLen 0 for read-only ops where the response // size is irrelevant. Implementations must no-op (not panic) when @@ -92,7 +92,7 @@ const ( ) // Defaults / caps for the per-cell hot-key drill-down knobs (see -// docs/design/2026_05_28_proposed_keyviz_hot_key_topk.md Β§8). All +// docs/design/2026_05_28_implemented_keyviz_hot_key_topk.md Β§8). All // off-by-default; HotKeysEnabled=false is the binary's existing // behaviour β€” no extra hot-path cost, no real key bytes retained. const ( @@ -161,7 +161,7 @@ type MemSamplerOptions struct { KeyBucketsPerRoute int // HotKeysEnabled opts in to per-route Top-K hot-key tracking that // backs the heatmap drill-down (Phase 2-A++; see - // docs/design/2026_05_28_proposed_keyviz_hot_key_topk.md). When + // docs/design/2026_05_28_implemented_keyviz_hot_key_topk.md). When // false the hot path adds one early-return branch and nothing else // β€” disabled-case behaviour is byte-identical to today. When true // the sampler retains REAL key bytes in memory and exposes them on @@ -236,7 +236,7 @@ type MemSampler struct { virtualIDCounter atomic.Uint64 // hotKeys is the per-route Top-K aggregator (design 2026_05_28 - // _proposed_keyviz_hot_key_topk). nil when HotKeysEnabled is false + // _implemented_keyviz_hot_key_topk). nil when HotKeysEnabled is false // β€” the Observe hot path then skips the feature with one branch. // Read from the hot path; assigned exactly once at construction. hotKeys *hotKeysAggregator @@ -379,7 +379,7 @@ type routeSlot struct { subBuckets []subCounter // hotKeysSnap is the most-recently-published per-route Top-K - // snapshot (design 2026_05_28_proposed_keyviz_hot_key_topk Β§4). + // snapshot (design 2026_05_28_implemented_keyviz_hot_key_topk Β§4). // nil until the hot-keys aggregator's first publish for this route. // Drill-down handlers load it lock-free; the aggregator is the // single writer (Store) and deep-copies snapshot contents so a diff --git a/keyviz/space_saving.go b/keyviz/space_saving.go index e2e18bc2d..66edf2723 100644 --- a/keyviz/space_saving.go +++ b/keyviz/space_saving.go @@ -2,7 +2,7 @@ package keyviz // Space-Saving sketch (Metwally et al.) β€” Misra–Gries-equivalent // heavy-hitter detector backing the per-cell hot-key drill-down (Phase -// 2-A++; see docs/design/2026_05_28_proposed_keyviz_hot_key_topk.md +// 2-A++; see docs/design/2026_05_28_implemented_keyviz_hot_key_topk.md // Β§3). Maintains at most `capacity` (= `m`) entries; a key with // frequency f over the observed stream of length N is reported with // error ≀ N/m, and any key with f > N/m is guaranteed in the tracked diff --git a/main.go b/main.go index 742af112d..e7bcf3227 100644 --- a/main.go +++ b/main.go @@ -196,7 +196,7 @@ var ( keyvizHistoryColumns = flag.Int("keyvizHistoryColumns", keyviz.DefaultHistoryColumns, "Maximum matrix columns retained in the keyviz ring buffer (each column = one Step)") keyvizKeyBucketsPerRoute = flag.Int("keyvizKeyBucketsPerRoute", keyviz.DefaultKeyBucketsPerRoute, "Order-preserving sub-range buckets per individual route for the hot-key heatmap; 1 disables sub-bucketing (route-granular, today's behaviour). Capped at 256; memory is ~K*32 bytes/route, so K_max ~= memBudget/(32*keyvizMaxTrackedRoutes)") - // Hot-key drill-down (Phase 2-A++; design 2026_05_28_proposed_keyviz_hot_key_topk). + // Hot-key drill-down (Phase 2-A++; design 2026_05_28_implemented_keyviz_hot_key_topk). // Off by default β€” the disabled-case adds one early-return branch // to Observe and retains zero real key bytes. When enabled, the // sampler retains actual hot key bytes in memory and exposes them @@ -211,7 +211,7 @@ var ( // HTTP endpoints (host:port or scheme://host:port). When set, // the admin keyviz handler aggregates the local matrix with // peer responses; when empty, behaviour is unchanged - // (single-node view). See docs/design/2026_04_27_proposed_keyviz_cluster_fanout.md. + // (single-node view). See docs/design/2026_04_27_implemented_keyviz_cluster_fanout.md. keyvizFanoutNodes = flag.String("keyvizFanoutNodes", "", "Comma-separated peer admin endpoints (host:port) for keyviz cluster-wide fan-out; empty disables") keyvizFanoutTimeout = flag.Duration("keyvizFanoutTimeout", keyvizFanoutDefaultTimeout, "Per-peer timeout for keyviz fan-out HTTP calls") ) diff --git a/scripts/rolling-update.env.example b/scripts/rolling-update.env.example index d47805b0e..6f8a78ff2 100644 --- a/scripts/rolling-update.env.example +++ b/scripts/rolling-update.env.example @@ -122,7 +122,7 @@ ADMIN_ENABLED="false" # role allow-lists must be configured cluster-wide. Peers without # --adminEnabled expose an unauthenticated keyviz endpoint and # respond unconditionally. -# See docs/design/2026_04_27_proposed_keyviz_cluster_fanout.md for the +# See docs/design/2026_04_27_implemented_keyviz_cluster_fanout.md for the # full design. KEYVIZ_ENABLED="false" # KEYVIZ_FANOUT_NODES="10.0.0.1:8080,10.0.0.2:8080,10.0.0.3:8080" @@ -152,7 +152,7 @@ KEYVIZ_ENABLED="false" # KEYVIZ_HOT_KEYS_MAX_KEY_LEN β€” keys longer than this are skipped # BEFORE the sample gate and increment `skipped_long_keys`. # Default 1024, max 4096. -# See docs/design/2026_05_28_proposed_keyviz_hot_key_topk.md Β§8 for the +# See docs/design/2026_05_28_implemented_keyviz_hot_key_topk.md Β§8 for the # full memory / overhead table. KEYVIZ_HOT_KEYS_ENABLED="false" # KEYVIZ_HOT_KEYS_PER_ROUTE="64"