@@ -1274,6 +1274,7 @@ type distMetrics struct {
12741274 writeQuorumFailures atomic.Int64 // number of write operations that failed quorum
12751275 writeAcks atomic.Int64 // cumulative replica write acks (includes primary)
12761276 writeAttempts atomic.Int64 // total write operations attempted (Set)
1277+ writeForwardPromotion atomic.Int64 // times a Set forward to primary failed and the local replica self-promoted
12771278 rebalancedKeys atomic.Int64 // keys migrated during rebalancing
12781279 rebalanceBatches atomic.Int64 // number of batches processed
12791280 rebalanceThrottle atomic.Int64 // times rebalance was throttled due to concurrency limits
@@ -1334,6 +1335,7 @@ type DistMetrics struct {
13341335 WriteQuorumFailures int64
13351336 WriteAcks int64
13361337 WriteAttempts int64
1338+ WriteForwardPromotion int64
13371339 RebalancedKeys int64
13381340 RebalanceBatches int64
13391341 RebalanceThrottle int64
@@ -1409,6 +1411,7 @@ func (dm *DistMemory) Metrics() DistMetrics {
14091411 WriteQuorumFailures : dm .metrics .writeQuorumFailures .Load (),
14101412 WriteAcks : dm .metrics .writeAcks .Load (),
14111413 WriteAttempts : dm .metrics .writeAttempts .Load (),
1414+ WriteForwardPromotion : dm .metrics .writeForwardPromotion .Load (),
14121415 RebalancedKeys : dm .metrics .rebalancedKeys .Load (),
14131416 RebalanceBatches : dm .metrics .rebalanceBatches .Load (),
14141417 RebalanceThrottle : dm .metrics .rebalanceThrottle .Load (),
@@ -3657,6 +3660,8 @@ func (dm *DistMemory) handleForwardPrimary(ctx context.Context, owners []cluster
36573660 if len (owners ) > 1 {
36583661 for _ , oid := range owners [1 :] {
36593662 if oid == dm .localNode .ID && dm .ownsKeyInternal (item .Key ) {
3663+ dm .metrics .writeForwardPromotion .Add (1 )
3664+
36603665 return true , nil
36613666 }
36623667 }
@@ -3823,6 +3828,8 @@ func (dm *DistMemory) setImpl(ctx context.Context, item *cache.Item, span trace.
38233828
38243829 span .SetAttributes (attribute .Int ("dist.owners.count" , len (owners )))
38253830
3831+ promoted := false
3832+
38263833 if owners [0 ] != dm .localNode .ID { // attempt forward; may promote
38273834 proceedAsPrimary , ferr := dm .handleForwardPrimary (ctx , owners , item )
38283835 if ferr != nil {
@@ -3832,6 +3839,8 @@ func (dm *DistMemory) setImpl(ctx context.Context, item *cache.Item, span trace.
38323839 if ! proceedAsPrimary { // forwarded successfully; nothing else to do
38333840 return nil
38343841 }
3842+
3843+ promoted = true
38353844 }
38363845
38373846 // primary path: assign version & timestamp
@@ -3840,7 +3849,19 @@ func (dm *DistMemory) setImpl(ctx context.Context, item *cache.Item, span trace.
38403849 item .LastUpdated = time .Now ()
38413850 dm .applySet (ctx , item , false )
38423851
3843- acks := 1 + dm .replicateTo (ctx , item , owners [1 :])
3852+ // When we promoted, the original primary is unreachable but still
3853+ // a listed owner. Pass the full owners list (not owners[1:]) so
3854+ // replicateTo's existing failure-path queues a hint for the dead
3855+ // primary — its post-restart convergence is bounded by hint-replay
3856+ // (~200ms by default) rather than waiting for the next merkle tick.
3857+ // replicateTo already skips the local node, so adding owners[0]
3858+ // back in doesn't cause a self-forward.
3859+ replicas := owners [1 :]
3860+ if promoted {
3861+ replicas = owners
3862+ }
3863+
3864+ acks := 1 + dm .replicateTo (ctx , item , replicas )
38443865 dm .metrics .writeAcks .Add (int64 (acks ))
38453866
38463867 span .SetAttributes (attribute .Int ("dist.acks" , acks ))
@@ -4192,6 +4213,11 @@ var distMetricSpecs = []distMetricSpec{
41924213 desc : "Set operations that failed quorum" ,
41934214 get : func (m DistMetrics ) int64 { return m .WriteQuorumFailures },
41944215 },
4216+ {
4217+ name : "dist.write.forward_promotion" , unit : unitOp , counter : true ,
4218+ desc : "Set forwards that promoted the local replica because the primary was unreachable" ,
4219+ get : func (m DistMetrics ) int64 { return m .WriteForwardPromotion },
4220+ },
41954221
41964222 // --- Rebalance ---
41974223 {
0 commit comments