Skip to content

Commit 83ab238

Browse files
authored
Merge pull request #3 from gosuda/perf/conflict-engine-integration
perf(epaxos): integrate conflict engine; remove O(n) index
2 parents 643dc75 + eb7a517 commit 83ab238

11 files changed

Lines changed: 453 additions & 290 deletions

epaxos/bootstrap.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2098,7 +2098,6 @@ func (n *RawNode) proposeMembershipAt(ref InstanceRef, command Command) error {
20982098
return ErrInvalidConfig
20992099
}
21002100
n.installInstance(inst)
2101-
n.indexConflicts(record)
21022101
n.enqueueRecord(record)
21032102
if n.slowQuorumForConf(ref.Conf) == 1 {
21042103
n.commit(inst, record.Attributes())

epaxos/conflict_engine.go

Lines changed: 102 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,58 @@ func walkRadixDesc(node *radixNode, prefix, from InstanceNum, limited bool, yiel
278278
return true
279279
}
280280

281+
// walkGlobalRadixDesc descends only subtrees that contain a global-eligible instance
282+
// (node.globalMax != 0 and within the from prefix), so unrelated residents are not visited.
283+
func walkGlobalRadixDesc(node *radixNode, prefix, from InstanceNum, limited bool, yield func(InstanceNum, laneSlot) bool) bool {
284+
if node == nil || node.globalMax == 0 {
285+
return true
286+
}
287+
if node.level == 0 {
288+
last := uint(radixFanout - 1)
289+
if limited {
290+
last = uint(from) & (radixFanout - 1)
291+
}
292+
for idx := int(last); idx >= 0; idx-- {
293+
if node.leaf.present&(uint64(1)<<uint(idx)) == 0 {
294+
continue
295+
}
296+
instance := prefix | InstanceNum(idx)
297+
if limited && instance > from {
298+
continue
299+
}
300+
slot := node.leaf.slots[idx]
301+
if !slot.global() || !slot.eligible() {
302+
continue
303+
}
304+
if !yield(instance, slot) {
305+
return false
306+
}
307+
}
308+
return true
309+
}
310+
shift := radixBits * node.level
311+
last := uint(radixFanout - 1)
312+
if limited {
313+
last = uint(from>>shift) & (radixFanout - 1)
314+
}
315+
for idx := int(last); idx >= 0; idx-- {
316+
child := node.children[idx]
317+
if child == nil || child.globalMax == 0 {
318+
continue
319+
}
320+
childPrefix := prefix | InstanceNum(idx)<<shift
321+
// Prune children whose entire key range is > from when limited.
322+
if limited && childPrefix > from {
323+
continue
324+
}
325+
childLimited := limited && uint(idx) == last
326+
if !walkGlobalRadixDesc(child, childPrefix, from, childLimited, yield) {
327+
return false
328+
}
329+
}
330+
return true
331+
}
332+
281333
func (t *laneTree) slot(instance InstanceNum) (laneSlot, bool) {
282334
node := t.root
283335
if node == nil || radixLevel(instance) > node.level {
@@ -641,6 +693,50 @@ func (e *conflictEngine) keyMax(conf ConfID, key []byte, lane instanceLane) (res
641693
return entry.postings.max(), entry.retiredFloor
642694
}
643695

696+
// walkKeyDesc yields resident instances for one (conf,key,lane) in descending Instance order,
697+
// only visiting that key's posting tree (not unrelated lane residents).
698+
func (e *conflictEngine) walkKeyDesc(conf ConfID, key []byte, lane instanceLane, from InstanceNum, yield func(InstanceNum, laneSlot) bool) {
699+
keys := e.byKey[conf]
700+
if keys == nil {
701+
return
702+
}
703+
lanes := keys[string(key)]
704+
if lanes == nil {
705+
return
706+
}
707+
entry := (*lanes)[lane]
708+
if entry == nil || entry.postings.root == nil {
709+
return
710+
}
711+
index := e.laneIndex[lane]
712+
if index == nil {
713+
return
714+
}
715+
if from == 0 {
716+
from = entry.postings.max()
717+
}
718+
if from == 0 {
719+
return
720+
}
721+
walkPostingDesc(entry.postings.root, 0, from, true, func(instance InstanceNum) bool {
722+
slot, ok := index.resident.slot(instance)
723+
if !ok {
724+
return true
725+
}
726+
return yield(instance, slot)
727+
})
728+
}
729+
730+
// walkGlobalDesc yields global-scope eligible residents for a lane descending from from.
731+
// Descent prunes radix subtrees with globalMax==0 so unrelated residents are not visited.
732+
func (e *conflictEngine) walkGlobalDesc(lane instanceLane, from InstanceNum, yield func(InstanceNum, laneSlot) bool) {
733+
index := e.laneIndex[lane]
734+
if index == nil || index.resident.root == nil || from == 0 {
735+
return
736+
}
737+
walkGlobalRadixDesc(index.resident.root, 0, from, radixLevel(from) <= index.resident.root.level, yield)
738+
}
739+
644740
func (e *conflictEngine) keyLaneSet(conf ConfID, keys [][]byte, yield func(instanceLane) bool) {
645741
for lane := range e.laneIndex {
646742
if lane.conf != conf {
@@ -671,28 +767,26 @@ func (e *conflictEngine) lanes(conf ConfID, yield func(instanceLane) bool) {
671767
}
672768
}
673769

674-
func (e *conflictEngine) maxEligibleAny(lane instanceLane) InstanceNum {
770+
func (e *conflictEngine) maxEligibleAny(lane instanceLane) (resident, retired InstanceNum) {
675771
index := e.laneIndex[lane]
676772
if index == nil {
677-
return 0
773+
return 0, 0
678774
}
679-
resident := InstanceNum(0)
680775
if index.resident.root != nil {
681776
resident = index.resident.root.maxEligibleAny
682777
}
683-
return max(resident, index.retiredEligibleAny)
778+
return resident, index.retiredEligibleAny
684779
}
685780

686-
func (e *conflictEngine) globalMax(lane instanceLane) InstanceNum {
781+
func (e *conflictEngine) globalMax(lane instanceLane) (resident, retired InstanceNum) {
687782
index := e.laneIndex[lane]
688783
if index == nil {
689-
return 0
784+
return 0, 0
690785
}
691-
resident := InstanceNum(0)
692786
if index.resident.root != nil {
693787
resident = index.resident.root.globalMax
694788
}
695-
return max(resident, index.retiredGlobal)
789+
return resident, index.retiredGlobal
696790
}
697791

698792
func (e *conflictEngine) foldRecord(rec InstanceRecord) {

epaxos/conflict_engine_test.go

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -191,11 +191,11 @@ func assertConflictEngineMatchesModel(t *testing.T, engine *conflictEngine, mode
191191
wantGlobal = max(wantGlobal, ref.Instance)
192192
}
193193
}
194-
if got := engine.maxEligibleAny(lane); got != wantMax {
195-
t.Fatalf("step %d lane %v: max eligible=%d, want %d", step, lane, got, wantMax)
194+
if resident, retired := engine.maxEligibleAny(lane); max(resident, retired) != wantMax {
195+
t.Fatalf("step %d lane %v: max eligible=%d/%d, want %d", step, lane, resident, retired, wantMax)
196196
}
197-
if got := engine.globalMax(lane); got != wantGlobal {
198-
t.Fatalf("step %d lane %v: global max=%d, want %d", step, lane, got, wantGlobal)
197+
if resident, retired := engine.globalMax(lane); max(resident, retired) != wantGlobal {
198+
t.Fatalf("step %d lane %v: global max=%d/%d, want %d", step, lane, resident, retired, wantGlobal)
199199
}
200200
for _, through := range []InstanceNum{0, 1, 63, 64, 4_096, InstanceNum(1)<<60 + 63, ^InstanceNum(0)} {
201201
want := modelPrefixMaxSeq(model, lane, through)
@@ -265,9 +265,9 @@ func engineModelAttrs(engine *conflictEngine, conf ConfID, cmd Command) modelAtt
265265
engine.lanes(conf, func(lane instanceLane) bool {
266266
var dep InstanceNum
267267
if commandHasGlobalConflictScope(cmd.Kind) {
268-
dep = engine.maxEligibleAny(lane)
268+
r, ret := engine.maxEligibleAny(lane); dep = max(r, ret)
269269
} else {
270-
dep = engine.globalMax(lane)
270+
r, ret := engine.globalMax(lane); dep = max(r, ret)
271271
for _, key := range cmd.ConflictKeys {
272272
resident, retired := engine.keyMax(conf, key, lane)
273273
dep = max(dep, resident, retired)
@@ -417,11 +417,11 @@ func TestConflictEnginePostFoldDomination(t *testing.T) {
417417
t.Fatalf("query %+v decreased across fold: before=%+v after=%+v", query, beforeAttrs, afterAttrs)
418418
}
419419
}
420-
if got := engine.globalMax(lane); got != 3 {
421-
t.Fatalf("retired global max=%d, want 3", got)
420+
if resident, retired := engine.globalMax(lane); max(resident, retired) != 3 {
421+
t.Fatalf("retired global max=%d, want 3", max(resident, retired))
422422
}
423-
if got := engine.maxEligibleAny(lane); got != 4 {
424-
t.Fatalf("retired eligible max=%d, want 4", got)
423+
if resident, retired := engine.maxEligibleAny(lane); max(resident, retired) != 4 {
424+
t.Fatalf("retired eligible max=%d, want 4", max(resident, retired))
425425
}
426426
if err := engine.verify(); err != nil {
427427
t.Fatal(err)
@@ -471,8 +471,8 @@ func TestConflictEngineNoopMutationDropsMax(t *testing.T) {
471471
if resident != lower.Ref.Instance || retired != 0 {
472472
t.Fatalf("key max after noop mutation=(%d,%d), want (%d,0)", resident, retired, lower.Ref.Instance)
473473
}
474-
if got := engine.maxEligibleAny(lane); got != lower.Ref.Instance {
475-
t.Fatalf("max eligible after noop mutation=%d, want %d", got, lower.Ref.Instance)
474+
if resident, retired := engine.maxEligibleAny(lane); max(resident, retired) != lower.Ref.Instance {
475+
t.Fatalf("max eligible after noop mutation=%d/%d, want %d", resident, retired, lower.Ref.Instance)
476476
}
477477
if err := engine.verify(); err != nil {
478478
t.Fatal(err)
@@ -641,3 +641,38 @@ func assertExactKeyPostings(t *testing.T, e *conflictEngine, records map[Instanc
641641
}
642642
}
643643
}
644+
645+
646+
func TestWalkGlobalDescSkipsUnrelatedResidents(t *testing.T) {
647+
t.Parallel()
648+
var engine conflictEngine
649+
lane := instanceLane{conf: 1, replica: 1}
650+
// One old global at instance 1, then many ordinary residents.
651+
global := InstanceRecord{
652+
Ref: InstanceRef{Conf: 1, Replica: 1, Instance: 1},
653+
Status: StatusCommitted, Seq: 1,
654+
Command: Command{Kind: CommandConfChange, Payload: []byte("cfg")},
655+
}
656+
engine.apply(nil, global)
657+
for i := InstanceNum(2); i <= 200; i++ {
658+
rec := InstanceRecord{
659+
Ref: InstanceRef{Conf: 1, Replica: 1, Instance: i},
660+
Status: StatusCommitted, Seq: uint64(i),
661+
Command: Command{Kind: CommandUser, Payload: []byte("u"), ConflictKeys: [][]byte{[]byte("k")}},
662+
}
663+
engine.apply(nil, rec)
664+
}
665+
visits := 0
666+
var seen []InstanceNum
667+
engine.walkGlobalDesc(lane, 200, func(instance InstanceNum, slot laneSlot) bool {
668+
visits++
669+
seen = append(seen, instance)
670+
if !slot.global() {
671+
t.Fatalf("yielded non-global instance %d", instance)
672+
}
673+
return true
674+
})
675+
if visits != 1 || len(seen) != 1 || seen[0] != 1 {
676+
t.Fatalf("walkGlobalDesc visits=%d seen=%v, want only global instance 1", visits, seen)
677+
}
678+
}

epaxos/conflict_ordering_test.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,15 @@ func TestConflictIndexScopesConfigurationsAndExcludedLaneMaximum(t *testing.T) {
9898
}
9999
for _, rec := range records {
100100
rn.instances[rec.Ref] = &instance{rec: rec, phase: phasePreAccept}
101-
rn.indexConflicts(rec)
101+
rn.engine.apply(nil, rec)
102102
}
103-
if got := rn.conflictIndex(1, key)[instanceLane{conf: 1, replica: 2}]; got != records[0].Ref {
104-
t.Fatalf("configuration 1 index=%s, want %s", got, records[0].Ref)
103+
lane2c1 := instanceLane{conf: 1, replica: 2}
104+
if resident, _ := rn.engine.keyMax(1, key, lane2c1); resident != records[0].Ref.Instance {
105+
t.Fatalf("configuration 1 index=%d, want %s", resident, records[0].Ref)
105106
}
106-
if got := rn.conflictIndex(2, key)[instanceLane{conf: 2, replica: 2}]; got != records[2].Ref {
107-
t.Fatalf("configuration 2 index=%s, want latest %s", got, records[2].Ref)
107+
lane2c2 := instanceLane{conf: 2, replica: 2}
108+
if resident, _ := rn.engine.keyMax(2, key, lane2c2); resident != records[2].Ref.Instance {
109+
t.Fatalf("configuration 2 index=%d, want latest %s", resident, records[2].Ref)
108110
}
109111

110112
newCommand := Command{Payload: []byte("new"), ConflictKeys: [][]byte{key}}

0 commit comments

Comments
 (0)