Skip to content

Commit e538a48

Browse files
author
Geoffrey Ragot
committed
refactor(reads): remove leader consistency selector
1 parent 94e9664 commit e538a48

14 files changed

Lines changed: 25 additions & 211 deletions

File tree

cmd/ledgerctl/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,8 @@ func newRootCommand() *cobra.Command {
119119
rootCmd.PersistentFlags().String("response-verify-key", "", "Path to Ed25519 seed file for verifying server response signatures (env: LEDGERCTL_RESPONSE_VERIFY_KEY)")
120120

121121
// Add persistent flag for read consistency level.
122-
rootCmd.PersistentFlags().String("consistency", "", "Read consistency level: stale, leader, or linearizable (default) (env: LEDGERCTL_CONSISTENCY)")
123-
cmdutil.RegisterEnumCompletion(rootCmd, "consistency", "stale", "leader", "linearizable")
122+
rootCmd.PersistentFlags().String("consistency", "", "Read consistency level: stale or linearizable (default) (env: LEDGERCTL_CONSISTENCY)")
123+
cmdutil.RegisterEnumCompletion(rootCmd, "consistency", "stale", "linearizable")
124124

125125
// Add persistent flag for bearer token authentication.
126126
rootCmd.PersistentFlags().String("auth-token", "", "Bearer token for authentication (JWT string or @path-to-file) (env: LEDGERCTL_AUTH_TOKEN)")

docs/ops/cli.md

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ These flags are available for all commands:
4141
| `--signing-key` | | Path to Ed25519 private key file (seed: 32 bytes raw or hex-encoded) |
4242
| `--signing-key-id` | `default` | Key ID for request signatures |
4343
| `--response-verify-key` | | Path to Ed25519 public key file for verifying server response signatures |
44-
| `--consistency` | | Read consistency level: `stale`, `leader`, or `linearizable` (default) |
44+
| `--consistency` | | Read consistency level: `stale` or `linearizable` (default) |
4545
| `--auth-token` | | Bearer token for authentication (JWT string or `@path-to-file`) |
4646

4747
### TLS server name (verifying by name while dialing by IP)
@@ -88,20 +88,13 @@ make independently asynchronous projections linearizable; endpoint sections
8888
document those exceptions. The barrier can block during maintenance windows
8989
(e.g. mirror sync or snapshot creation) when the FSM is frozen.
9090

91-
Two alternative consistency levels are available:
91+
One alternative consistency level is available:
9292

9393
- **`stale`** — Skip the Raft ReadIndex barrier and read from local state, which
9494
may lag behind the latest committed index. A read can still wait for an
9595
explicitly requested `--min-log-sequence` or for mandatory secondary-index
9696
alignment. Useful for monitoring, dashboards, and non-critical queries where
9797
quorum-confirmed freshness is unnecessary.
98-
- **`leader`** — Route the read to the node currently considered leader. When
99-
the request is forwarded, the remote node applies its default ReadIndex
100-
barrier. When the receiving node already considers itself leader, it serves
101-
the read locally without a barrier. Because `CheckQuorum` is disabled, an
102-
isolated former leader can therefore return stale state in this mode; use the
103-
default `linearizable` mode when quorum-confirmed freshness is required.
104-
10598
`chapters list` is leader-routed independently of this selector and does not
10699
perform a ReadIndex barrier. If it reaches an isolated node that still considers
107100
itself leader, it can return stale chapter rows. Filtered `audit list` also has
@@ -111,9 +104,6 @@ an endpoint-specific asynchronous-index caveat; see its consistency note below.
111104
# Stale read (no Raft barrier; may lag)
112105
ledgerctl --consistency stale ledgers get my-ledger
113106

114-
# Leader-routed read (may be stale from an isolated former leader)
115-
ledgerctl --consistency leader ledgers list
116-
117107
# Default linearizable read
118108
ledgerctl ledgers list
119109
```

docs/technical/architecture/data-flows.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -556,10 +556,9 @@ config:
556556
node currently considered leader. `ListChapters` bypasses the normal read
557557
consistency selector and does not perform a ReadIndex barrier; a node that still
558558
considers itself leader serves its persisted chapter rows locally. Other live
559-
reads normally use the ReadIndex mechanism (see below), but callers can
560-
explicitly select `x-consistency: leader`. That mode also routes the read to the
561-
perceived leader; if the receiving node already considers itself leader, the
562-
local shortcut does not perform a ReadIndex barrier.
559+
reads normally use the ReadIndex mechanism (see below). The unreleased v3
560+
`x-consistency: leader` selector was removed by EN-1946; callers choose between
561+
the default linearizable route and an explicit `stale` local read.
563562

564563
### Forwarding Flow
565564

docs/technical/architecture/subsystems/consensus/raft-consensus.md

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -574,15 +574,6 @@ indexes need their own progress barrier:
574574
- `x-consistency: stale` bypasses `ReadIndex` and reads the local store directly;
575575
it may return an older view, but any projection it uses is still aligned to
576576
the fixed applied index of that local main-store snapshot.
577-
- `x-consistency: leader` routes the read to the node currently considered
578-
leader. A call forwarded to a remote node does not propagate the consistency
579-
metadata, so the remote call defaults to linearizable mode and performs its
580-
quorum barrier. However, if the receiving node already considers itself
581-
leader, `getLeaderCtrl` returns the local controller directly and skips
582-
`ReadIndex`. Because `CheckQuorum` is disabled, an isolated former leader can
583-
therefore serve stale local state in this mode. Projection-backed reads still
584-
align to that local main-store snapshot even though no quorum horizon `R` is
585-
available.
586577
- If a non-leader node is syncing or cannot complete its local barrier,
587578
`RoutedController` can transparently retry the read against the leader. The
588579
forwarded attempt can still fail when the leader is unavailable. If

docs/technical/architecture/subsystems/read-path/prepared-queries.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ A compile error at FSM time is hash-bound as an `AuditFailure`, so a checker run
7272
1. Opens the fixed main-store snapshot, then reads both the ledger schema and prepared query from that snapshot. This prevents a concurrent query/schema update or deletion from being combined with entities from a newer state. Because the stored definition determines whether an index will be used, the executor briefly reserves the event-history floor before opening the snapshot and releases it immediately when the loaded shape needs no index alignment.
7373
2. Verifies the requested mode is compatible with the query's `target` (e.g. `AGGREGATE_VOLUMES` only makes sense for accounts).
7474
3. Enters the standard read route. Default live consistency establishes a
75-
`ReadIndexAndWait` horizon; `stale`, leader-local, and checkpoint reads have
76-
the documented exceptions. The executor waits for read-index alignment only
75+
`ReadIndexAndWait` horizon; `stale` and checkpoint reads have the documented
76+
exceptions. The executor waits for read-index alignment only
7777
when `AlignmentOwed` is true — a non-nil filter or a LOGS target — then calls
7878
`Compile(indexSnap, kb, pq.GetFilter(), ...)` and executes the iterator.
7979
4. For `LIST`, streams the matching entities through the standard cursor pipeline (see [query-pipeline.md](query-pipeline.md)).

docs/technical/architecture/subsystems/read-path/query-pipeline.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ was used.
8282

8383
Once both succeed, the local Pebble snapshot reflects state at least as fresh as the moment the request reached the cluster. This guarantees **linearizable reads on any node**: a read started after a successful write returns at least that write's effects, regardless of which node serves the read.
8484

85-
If the node is syncing or otherwise unable to confirm `ReadIndex`, the call fails — callers either retry or forward to the leader. The explicit `leader` consistency mode remains available at this stage: a remote hop defaults to the linearizable path, while a node that already considers itself leader serves locally without `R` and still aligns every projection it uses to the fixed local `H` below.
85+
If the node is syncing or otherwise unable to confirm `ReadIndex`, the call fails — callers either retry or forward to the leader.
8686

8787
## Projection alignment — fixed Raft horizon
8888

docs/technical/architecture/subsystems/read-path/query-profile.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ which:
107107

108108
- **`min_log_sequence` catch-up.** `waitMinLogSequence` charges the
109109
`WaitForSequence` wait regardless of consistency level, and it runs before
110-
routing — so `--consistency leader --min-log-sequence N` yields a non-zero
110+
routing — so `--consistency stale --min-log-sequence N` yields a non-zero
111111
barrier on a perfectly healthy cluster, with no failed attempt anywhere.
112112
- **A failed `ReadIndex` attempt.** The syncing-follower fallback in
113113
`RoutedController.readCtrl` records the attempt and may then forward after it
@@ -245,9 +245,9 @@ locally-served one.
245245

246246
## Known gaps
247247

248-
- **Leader-forwarded reads report only the local hop.** When a follower forwards
249-
a read (`ConsistencyLeader`, or the syncing-node fallback in
250-
`RoutedController.readCtrl`), the upstream RPC is charged to the local
248+
- **Leader-forwarded reads report only the local hop.** When a syncing follower
249+
falls back to the leader in `RoutedController.readCtrl`, the upstream RPC is
250+
charged to the local
251251
`execute` phase, so `execute` there conflates network hops, leader-side
252252
prepare, leader-side barrier and leader-side execution. `barrier_duration_us`
253253
covers only what this node attempted locally.

internal/adapter/grpc/consistency.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@ const (
1515
// ConsistencyStale skips the ReadIndex barrier and reads from the local store directly.
1616
// Data may lag behind the latest committed index.
1717
ConsistencyStale = "stale"
18-
// ConsistencyLeader routes the read to the node currently considered leader.
19-
// A remote leader applies the default ReadIndex barrier, but a node that
20-
// already considers itself leader serves the read locally without one.
21-
ConsistencyLeader = "leader"
2218
)
2319

2420
const metadataKeyConsistency = "x-consistency"
@@ -86,7 +82,7 @@ func extractConsistency(ctx context.Context) context.Context {
8682

8783
level := strings.ToLower(strings.TrimSpace(vals[0]))
8884
switch level {
89-
case ConsistencyStale, ConsistencyLeader:
85+
case ConsistencyStale:
9086
return WithConsistency(ctx, level)
9187
default:
9288
return ctx

internal/adapter/grpc/consistency_test.go

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,6 @@ func TestWithConsistency(t *testing.T) {
1616
require.Equal(t, ConsistencyStale, level)
1717
}
1818

19-
func TestWithConsistency_Leader(t *testing.T) {
20-
t.Parallel()
21-
22-
ctx := WithConsistency(context.Background(), ConsistencyLeader)
23-
level := ConsistencyFromContext(ctx)
24-
require.Equal(t, ConsistencyLeader, level)
25-
}
26-
2719
func TestConsistencyFromContext_Default(t *testing.T) {
2820
t.Parallel()
2921

@@ -82,15 +74,15 @@ func TestExtractConsistency_Stale(t *testing.T) {
8274
require.Equal(t, ConsistencyStale, level)
8375
}
8476

85-
func TestExtractConsistency_Leader(t *testing.T) {
77+
func TestExtractConsistency_LeaderIsNotSupported(t *testing.T) {
8678
t.Parallel()
8779

8880
md := metadata.New(map[string]string{metadataKeyConsistency: "leader"})
8981
ctx := metadata.NewIncomingContext(context.Background(), md)
9082

9183
ctx = extractConsistency(ctx)
9284
level := ConsistencyFromContext(ctx)
93-
require.Equal(t, ConsistencyLeader, level)
85+
require.Equal(t, ConsistencyLinearizable, level)
9486
}
9587

9688
func TestExtractConsistency_CaseInsensitive(t *testing.T) {
@@ -119,10 +111,10 @@ func TestExtractConsistency_Linearizable(t *testing.T) {
119111
func TestExtractConsistency_WhitespaceHandling(t *testing.T) {
120112
t.Parallel()
121113

122-
md := metadata.New(map[string]string{metadataKeyConsistency: " leader "})
114+
md := metadata.New(map[string]string{metadataKeyConsistency: " stale "})
123115
ctx := metadata.NewIncomingContext(context.Background(), md)
124116

125117
ctx = extractConsistency(ctx)
126118
level := ConsistencyFromContext(ctx)
127-
require.Equal(t, ConsistencyLeader, level)
119+
require.Equal(t, ConsistencyStale, level)
128120
}

internal/bootstrap/controller_routed.go

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ func (b *RoutedController) getLeaderCtrl() (ctrl.Controller, error) {
5353
// The consistency level is determined from the context (set by the gRPC interceptor):
5454
// - linearizable (default): ReadIndex+WaitForApplied barrier on the local node
5555
// - stale: skip the barrier and read from the local store directly
56-
// - leader: route the read to the node currently considered leader; the local
57-
// leader shortcut does not perform a ReadIndex barrier
5856
//
5957
// For linearizable reads, if the local node is still syncing the read is
6058
// transparently forwarded to the leader.
@@ -65,30 +63,10 @@ func (b *RoutedController) readCtrl(ctx context.Context) (ctrl.Controller, *node
6563
trace.WithAttributes(attribute.String("consistency", consistency)))
6664
defer span.End()
6765

68-
switch consistency {
69-
case grpcadp.ConsistencyStale:
66+
if consistency == grpcadp.ConsistencyStale {
7067
span.SetAttributes(attribute.String("route", "local_stale"))
7168

7269
return b.localController, nil, nil
73-
case grpcadp.ConsistencyLeader:
74-
span.SetAttributes(attribute.String("route", "leader"))
75-
76-
c, err := b.getLeaderCtrl()
77-
if err != nil {
78-
return nil, nil, err
79-
}
80-
81-
// When getLeaderCtrl returns a remote controller, that node runs its own
82-
// ReadIndex barrier (x-consistency is not propagated, so it defaults to
83-
// linearizable there). The remote barrier and execution are invisible to
84-
// this profile — the whole remote cost arrives as row-production time
85-
// inside the local execute phase, and this node's barrier_duration_us stays
86-
// 0. Flag it so a reader does not take that 0 to mean "no barrier was
87-
// needed" (EN-1859). When this node already considers itself leader,
88-
// getLeaderCtrl returns the local controller and no barrier is performed.
89-
b.markForwardedIfRemote(ctx, c)
90-
91-
return c, nil, nil
9270
}
9371

9472
// The ReadIndex quorum round-trip plus the local WaitForApplied catch-up is
@@ -154,15 +132,6 @@ func (b *RoutedController) finishLeaderFallback(ctx context.Context, selected ct
154132
return selected, nil, nil
155133
}
156134

157-
// markForwardedIfRemote preserves the query-profile contract that Forwarded
158-
// means another node served the read. The explicit leader-consistency path can
159-
// resolve to the local controller when this node considers itself leader.
160-
func (b *RoutedController) markForwardedIfRemote(ctx context.Context, selected ctrl.Controller) {
161-
if selected != b.localController {
162-
query.ProfileFromContext(ctx).MarkForwarded()
163-
}
164-
}
165-
166135
func (b *RoutedController) withLocalBarrierHorizon(ctx context.Context, selected ctrl.Controller, barrier *node.ReadBarrierInfo) context.Context {
167136
if selected == b.localController && barrier != nil {
168137
return query.WithReadBarrierHorizon(ctx, barrier.CommitIndex)

0 commit comments

Comments
 (0)