Skip to content

Conversation

@joamaki
Copy link
Contributor

@joamaki joamaki commented Jan 20, 2026

Add reverse-iteration support across StateDB: new Table APIs (AllReverse, ListReverse, PrefixReverse + watch variants), index-level reverse plumbing, and efficient reverse iterators for part and lpm.

Example usage:

// Reverse full table scan
for obj := range myObjects.AllReverse(txn) {
    // ...
}

// Reverse list on a secondary index
for obj := range myObjects.ListReverse(txn, tagsIndex.Query("odd")) {
    // ...
}

// Reverse prefix scan
for obj := range myObjects.PrefixReverse(txn, IDIndex.Query(0x1000_0000)) {
    // ...
}

// Reverse prefix scan with watch
seq, watch := myObjects.PrefixReverseWatch(txn, IDIndex.Query(0x1000_0000))
for obj := range seq { /* ... */ }
<-watch

Benchmarks:

BenchmarkDB_FullIteration_All-8                      978           1189033 ns/op          84101978 objects/sec       104 B/op          4 allocs/op
BenchmarkDB_FullIteration_AllReverse-8               650           1848977 ns/op          54083963 objects/sec       136 B/op          5 allocs/op
BenchmarkDB_FullIteration_Prefix-8                  1018           1182365 ns/op          84576225 objects/sec       136 B/op          5 allocs/op
BenchmarkDB_FullIteration_PrefixReverse-8            738           1637465 ns/op          61070025 objects/sec       136 B/op          5 allocs/op

Add support for iterating over the tree or prefix search in reverse.

Signed-off-by: Jussi Maki <jussi.maki@isovalent.com>
Add the AllReverse and PrefixReverse methods to iterate over the results
in reverse order.

Signed-off-by: Jussi Maki <jussi.maki@isovalent.com>
Add [AllReverse], [AllReverseWatch], [ListReverse], [ListReverseWatch],
[PrefixReverse] and [PrefixReverseWatch] queries to the [Table] interface.

Signed-off-by: Jussi Maki <jussi.maki@isovalent.com>
Benchmark results:
BenchmarkDB_FullIteration_All-8                      978           1189033 ns/op          84101978 objects/sec       104 B/op          4 allocs/op
BenchmarkDB_FullIteration_AllReverse-8               650           1848977 ns/op          54083963 objects/sec       136 B/op          5 allocs/op
BenchmarkDB_FullIteration_Prefix-8                  1018           1182365 ns/op          84576225 objects/sec       136 B/op          5 allocs/op
BenchmarkDB_FullIteration_PrefixReverse-8            738           1637465 ns/op          61070025 objects/sec       136 B/op          5 allocs/op

Signed-off-by: Jussi Maki <jussi.maki@isovalent.com>
Signed-off-by: Jussi Maki <jussi.maki@isovalent.com>
@joamaki joamaki requested a review from bimmlerd January 20, 2026 11:06
@joamaki joamaki requested a review from a team as a code owner January 20, 2026 11:06
@github-actions
Copy link

$ make
go build ./...
go: downloading go1.24.0 (linux/amd64)
go: downloading go.yaml.in/yaml/v3 v3.0.3
go: downloading github.com/cilium/hive v0.0.0-20250731144630-28e7a35ed227
go: downloading golang.org/x/time v0.5.0
go: downloading github.com/spf13/cobra v1.8.0
go: downloading github.com/spf13/pflag v1.0.5
go: downloading github.com/cilium/stream v0.0.0-20240209152734-a0792b51812d
go: downloading github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de
go: downloading github.com/spf13/viper v1.18.2
go: downloading go.uber.org/dig v1.17.1
go: downloading golang.org/x/term v0.16.0
go: downloading github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
go: downloading github.com/mitchellh/mapstructure v1.5.0
go: downloading golang.org/x/sys v0.17.0
go: downloading golang.org/x/tools v0.17.0
go: downloading github.com/fsnotify/fsnotify v1.7.0
go: downloading github.com/sagikazarmark/slog-shim v0.1.0
go: downloading github.com/spf13/afero v1.11.0
go: downloading github.com/spf13/cast v1.6.0
go: downloading golang.org/x/text v0.14.0
go: downloading github.com/subosito/gotenv v1.6.0
go: downloading github.com/hashicorp/hcl v1.0.0
go: downloading gopkg.in/ini.v1 v1.67.0
go: downloading github.com/magiconair/properties v1.8.7
go: downloading github.com/pelletier/go-toml/v2 v2.1.0
go: downloading gopkg.in/yaml.v3 v3.0.1
STATEDB_VALIDATE=1 go test ./... -cover -vet=all -test.count 1
go: downloading github.com/stretchr/testify v1.8.4
go: downloading go.uber.org/goleak v1.3.0
go: downloading golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
go: downloading github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
ok  	github.com/cilium/statedb	415.467s	coverage: 78.1% of statements
ok  	github.com/cilium/statedb/index	0.005s	coverage: 28.7% of statements
ok  	github.com/cilium/statedb/internal	0.025s	coverage: 42.9% of statements
ok  	github.com/cilium/statedb/lpm	3.792s	coverage: 74.0% of statements
ok  	github.com/cilium/statedb/part	59.524s	coverage: 87.3% of statements
ok  	github.com/cilium/statedb/reconciler	0.200s	coverage: 89.1% of statements
	github.com/cilium/statedb/reconciler/benchmark		coverage: 0.0% of statements
	github.com/cilium/statedb/reconciler/example		coverage: 0.0% of statements
go test -race ./... -test.count 1
ok  	github.com/cilium/statedb	36.554s
ok  	github.com/cilium/statedb/index	1.014s
ok  	github.com/cilium/statedb/internal	1.026s
ok  	github.com/cilium/statedb/lpm	2.740s
ok  	github.com/cilium/statedb/part	32.068s
ok  	github.com/cilium/statedb/reconciler	1.321s
?   	github.com/cilium/statedb/reconciler/benchmark	[no test files]
?   	github.com/cilium/statedb/reconciler/example	[no test files]
go test ./... -bench . -benchmem -test.run xxx
goos: linux
goarch: amd64
pkg: github.com/cilium/statedb
cpu: AMD EPYC 9V74 80-Core Processor                
BenchmarkDB_WriteTxn_1-4                      	  680540	      1640 ns/op	    609733 objects/sec	    1032 B/op	      17 allocs/op
BenchmarkDB_WriteTxn_10-4                     	 1827157	       656.0 ns/op	   1524302 objects/sec	     523 B/op	       8 allocs/op
BenchmarkDB_WriteTxn_100-4                    	 2356758	       543.3 ns/op	   1840484 objects/sec	     490 B/op	       7 allocs/op
BenchmarkDB_WriteTxn_1000-4                   	 2103207	       563.6 ns/op	   1774217 objects/sec	     447 B/op	       7 allocs/op
BenchmarkDB_WriteTxn_100_SecondaryIndex-4     	  859952	      1240 ns/op	    806146 objects/sec	    1007 B/op	      22 allocs/op
BenchmarkDB_WriteTxn_CommitOnly_100Tables-4   	 1000000	      1094 ns/op	    1112 B/op	       5 allocs/op
BenchmarkDB_WriteTxn_CommitOnly_1Table-4      	 1715269	       699.0 ns/op	     224 B/op	       5 allocs/op
BenchmarkDB_NewWriteTxn-4                     	 1869174	       641.1 ns/op	     200 B/op	       4 allocs/op
BenchmarkDB_WriteTxnCommit100-4               	 1092284	      1097 ns/op	    1096 B/op	       5 allocs/op
BenchmarkDB_NewReadTxn-4                      	564879292	         2.114 ns/op	       0 B/op	       0 allocs/op
BenchmarkDB_Modify-4                          	    1928	    625556 ns/op	   1598577 objects/sec	  479680 B/op	    8073 allocs/op
BenchmarkDB_GetInsert-4                       	    1773	    677057 ns/op	   1476981 objects/sec	  455678 B/op	    8073 allocs/op
BenchmarkDB_RandomInsert-4                    	    2084	    578778 ns/op	   1727779 objects/sec	  447665 B/op	    7073 allocs/op
BenchmarkDB_RandomReplace-4                   	     480	   2492659 ns/op	    401178 objects/sec	 1924808 B/op	   31104 allocs/op
BenchmarkDB_SequentialInsert-4                	    2108	    556529 ns/op	   1796851 objects/sec	  447666 B/op	    7073 allocs/op
BenchmarkDB_SequentialInsert_Prefix-4         	     489	   2426673 ns/op	    412087 objects/sec	 3563157 B/op	   45544 allocs/op
BenchmarkDB_Changes_Baseline-4                	    1724	    700133 ns/op	   1428301 objects/sec	  507841 B/op	    9167 allocs/op
BenchmarkDB_Changes-4                         	    1008	   1181173 ns/op	    846616 objects/sec	  709174 B/op	   12316 allocs/op
BenchmarkDB_RandomLookup-4                    	   25592	     46630 ns/op	  21445437 objects/sec	       0 B/op	       0 allocs/op
BenchmarkDB_SequentialLookup-4                	   24745	     48449 ns/op	  20640222 objects/sec	       0 B/op	       0 allocs/op
BenchmarkDB_Prefix_SecondaryIndex-4           	    7381	    149715 ns/op	   6679357 objects/sec	  124922 B/op	    1026 allocs/op
BenchmarkDB_FullIteration_All-4               	     867	   1277758 ns/op	  78262085 objects/sec	     104 B/op	       5 allocs/op
BenchmarkDB_FullIteration_AllReverse-4        	     589	   1988420 ns/op	  50291183 objects/sec	     136 B/op	       6 allocs/op
BenchmarkDB_FullIteration_Prefix-4            	     856	   1358989 ns/op	  73584121 objects/sec	     136 B/op	       6 allocs/op
BenchmarkDB_FullIteration_PrefixReverse-4     	     655	   1868935 ns/op	  53506406 objects/sec	     136 B/op	       6 allocs/op
BenchmarkDB_LPM_PrefixReverse-4               	     408	   3086429 ns/op	  16199953 objects/sec	     136 B/op	       7 allocs/op
BenchmarkDB_FullIteration_Get-4               	     195	   6109773 ns/op	  16367221 objects/sec	       0 B/op	       0 allocs/op
BenchmarkDB_FullIteration_Get_Secondary-4     	      87	  11503708 ns/op	   8692849 objects/sec	       0 B/op	       0 allocs/op
BenchmarkDB_FullIteration_ReadTxnGet-4        	     205	   5842980 ns/op	  17114553 objects/sec	       0 B/op	       0 allocs/op
BenchmarkDB_PropagationDelay-4                	  667234	      1633 ns/op	        14.00 50th_µs	        16.00 90th_µs	        41.00 99th_µs	    1127 B/op	      20 allocs/op
BenchmarkDB_WriteTxn_100_LPMIndex-4           	  514149	      2217 ns/op	    451060 objects/sec	    1778 B/op	      37 allocs/op
BenchmarkDB_WriteTxn_1_LPMIndex-4             	  126660	     14686 ns/op	     68093 objects/sec	   15703 B/op	      81 allocs/op
BenchmarkDB_LPMIndex_Get-4                    	     284	   3665761 ns/op	   2727947 objects/sec	       0 B/op	       0 allocs/op
BenchmarkWatchSet_4-4                         	 2465815	       481.4 ns/op	     320 B/op	       5 allocs/op
BenchmarkWatchSet_16-4                        	  811584	      1477 ns/op	    1096 B/op	       5 allocs/op
BenchmarkWatchSet_128-4                       	   86290	     13568 ns/op	    8904 B/op	       5 allocs/op
BenchmarkWatchSet_1024-4                      	    8691	    138956 ns/op	   73744 B/op	       5 allocs/op
PASS
ok  	github.com/cilium/statedb	46.990s
PASS
ok  	github.com/cilium/statedb/index	0.004s
goos: linux
goarch: amd64
pkg: github.com/cilium/statedb/internal
cpu: AMD EPYC 9V74 80-Core Processor                
Benchmark_SortableMutex-4   	 5792155	       206.9 ns/op	       0 B/op	       0 allocs/op
PASS
ok  	github.com/cilium/statedb/internal	1.202s
goos: linux
goarch: amd64
pkg: github.com/cilium/statedb/lpm
cpu: AMD EPYC 9V74 80-Core Processor                
Benchmark_txn_insert/batchSize=1-4         	    2002	    578857 ns/op	   1727542 objects/sec	  838424 B/op	   13975 allocs/op
Benchmark_txn_insert/batchSize=10-4        	    3296	    364433 ns/op	   2743990 objects/sec	  385201 B/op	    6669 allocs/op
Benchmark_txn_insert/batchSize=100-4       	    3358	    351603 ns/op	   2844120 objects/sec	  345618 B/op	    6028 allocs/op
Benchmark_txn_delete/batchSize=1-4         	    1588	    749884 ns/op	   1333540 objects/sec	 1286486 B/op	   13976 allocs/op
Benchmark_txn_delete/batchSize=10-4        	    3268	    365314 ns/op	   2737370 objects/sec	  372421 B/op	    5769 allocs/op
Benchmark_txn_delete/batchSize=100-4       	    3639	    339293 ns/op	   2947306 objects/sec	  286756 B/op	    5038 allocs/op
Benchmark_LPM_Lookup-4                     	    7693	    155163 ns/op	   6444840 objects/sec	       0 B/op	       0 allocs/op
Benchmark_LPM_All-4                        	  136292	      8608 ns/op	 116176030 objects/sec	      32 B/op	       1 allocs/op
Benchmark_LPM_Prefix-4                     	  132970	      9007 ns/op	 111023468 objects/sec	      32 B/op	       1 allocs/op
Benchmark_LPM_LowerBound-4                 	  245774	      4799 ns/op	 104198607 objects/sec	     288 B/op	       2 allocs/op
PASS
ok  	github.com/cilium/statedb/lpm	11.917s
goos: linux
goarch: amd64
pkg: github.com/cilium/statedb/part
cpu: AMD EPYC 9V74 80-Core Processor                
Benchmark_Uint64Map_Random-4                  	    1509	    753745 ns/op	   1326708 items/sec	 2522736 B/op	    6040 allocs/op
Benchmark_Uint64Map_Sequential-4              	    1884	    628089 ns/op	   1592130 items/sec	 2216745 B/op	    5755 allocs/op
Benchmark_Uint64Map_Sequential_Insert-4       	    2133	    565034 ns/op	   1769806 items/sec	 2208740 B/op	    4754 allocs/op
Benchmark_Uint64Map_Sequential_Txn_Insert-4   	   10000	    105679 ns/op	   9462628 items/sec	   86353 B/op	    2028 allocs/op
Benchmark_Uint64Map_Random_Insert-4           	    1714	    686828 ns/op	   1455969 items/sec	 2517100 B/op	    5028 allocs/op
Benchmark_Uint64Map_Random_Txn_Insert-4       	    7558	    161385 ns/op	   6196370 items/sec	  118656 B/op	    2417 allocs/op
Benchmark_Insert_RootOnlyWatch-4              	   10000	    104716 ns/op	   9549666 objects/sec	   71505 B/op	    2033 allocs/op
Benchmark_Insert-4                            	    8007	    150236 ns/op	   6656202 objects/sec	  186939 B/op	    3060 allocs/op
Benchmark_Modify-4                            	   10000	    100734 ns/op	   9927128 objects/sec	   58225 B/op	    1007 allocs/op
Benchmark_GetInsert-4                         	    9726	    120664 ns/op	   8287509 objects/sec	   58224 B/op	    1007 allocs/op
Benchmark_Replace-4                           	17199238	        68.67 ns/op	  14561890 objects/sec	      48 B/op	       1 allocs/op
Benchmark_Replace_RootOnlyWatch-4             	 3044550	       416.2 ns/op	   2402544 objects/sec	     209 B/op	       2 allocs/op
Benchmark_txn_1-4                             	 5465452	       210.7 ns/op	   4746573 objects/sec	     168 B/op	       3 allocs/op
Benchmark_txn_10-4                            	 9593289	       123.8 ns/op	   8075380 objects/sec	      86 B/op	       2 allocs/op
Benchmark_txn_100-4                           	12865776	        91.48 ns/op	  10931361 objects/sec	      80 B/op	       2 allocs/op
Benchmark_txn_1000-4                          	11300298	       103.6 ns/op	   9651323 objects/sec	      65 B/op	       2 allocs/op
Benchmark_txn_delete_1-4                      	 5072146	       237.7 ns/op	   4207124 objects/sec	     664 B/op	       4 allocs/op
Benchmark_txn_delete_10-4                     	11913573	        99.48 ns/op	  10052632 objects/sec	     106 B/op	       1 allocs/op
Benchmark_txn_delete_100-4                    	14396589	        81.89 ns/op	  12212249 objects/sec	      47 B/op	       1 allocs/op
Benchmark_txn_delete_1000-4                   	16313454	        73.01 ns/op	  13696792 objects/sec	      24 B/op	       1 allocs/op
Benchmark_Get-4                               	   45001	     26540 ns/op	  37678434 objects/sec	       0 B/op	       0 allocs/op
Benchmark_All-4                               	  174507	      7112 ns/op	 140610096 objects/sec	       0 B/op	       0 allocs/op
Benchmark_Iterator_All-4                      	  167210	      7169 ns/op	 139493653 objects/sec	       0 B/op	       0 allocs/op
Benchmark_ReverseIterator_All-4               	   86516	     13911 ns/op	  71887815 objects/sec	       0 B/op	       0 allocs/op
Benchmark_Iterator_Next-4                     	  139489	      8604 ns/op	 116227256 objects/sec	     896 B/op	       1 allocs/op
Benchmark_ReverseIterator_Next-4              	   79784	     15093 ns/op	  66255383 objects/sec	    1152 B/op	       1 allocs/op
Benchmark_Prefix_All-4                        	  311521	      4091 ns/op	 122233029 objects/sec	       0 B/op	       0 allocs/op
Benchmark_Prefix_Next-4                       	  254012	      4628 ns/op	 108033714 objects/sec	     896 B/op	       1 allocs/op
Benchmark_PrefixReverse_All-4                 	  157940	      7363 ns/op	  67908581 objects/sec	       0 B/op	       0 allocs/op
Benchmark_PrefixReverse_Next-4                	  147171	      8006 ns/op	  62449555 objects/sec	    1152 B/op	       1 allocs/op
Benchmark_Hashmap_Insert-4                    	   17029	     70091 ns/op	  14267234 objects/sec	   74265 B/op	      20 allocs/op
Benchmark_Hashmap_Get_Uint64-4                	  153046	      7820 ns/op	 127877839 objects/sec	       0 B/op	       0 allocs/op
Benchmark_Hashmap_Get_Bytes-4                 	  123484	      9724 ns/op	 102834458 objects/sec	       0 B/op	       0 allocs/op
Benchmark_Delete_Random-4                     	      67	  15513183 ns/op	   6446131 objects/sec	 2111853 B/op	  102364 allocs/op
Benchmark_find16-4                            	242978120	         4.932 ns/op	       0 B/op	       0 allocs/op
Benchmark_findIndex16-4                       	100000000	        14.42 ns/op	       0 B/op	       0 allocs/op
Benchmark_find4-4                             	412094707	         2.915 ns/op	       0 B/op	       0 allocs/op
Benchmark_findIndex4-4                        	340393644	         3.525 ns/op	       0 B/op	       0 allocs/op
PASS
ok  	github.com/cilium/statedb/part	46.353s
PASS
ok  	github.com/cilium/statedb/reconciler	0.005s
?   	github.com/cilium/statedb/reconciler/benchmark	[no test files]
?   	github.com/cilium/statedb/reconciler/example	[no test files]
go run ./reconciler/benchmark -quiet
1000000 objects reconciled in 2.06 seconds (batch size 1000)
Throughput 485386.63 objects per second
817MB total allocated, 6015182 in-use objects, 338MB bytes in use

@bimmlerd
Copy link
Member

Before I read the code - can you outline why this is needed?

@joamaki
Copy link
Contributor Author

joamaki commented Jan 21, 2026

Before I read the code - can you outline why this is needed?

There isn't an immediate use case, but it's something I've been wanting to add from the beginning. It's also a nicely incremental change that doesn't touch existing code paths that much. So the strongest rational right now is just that I've always wanted this to be part of the API.

Some of the useful things with this that I can imagine are e.g. iterating over the revision index in newly changed first order (to for example reconcile new objects first) or iterating over some sort of priority/cost/etc. index in reverse order.

Oh and just realized that for debugging we might want the db/* commands to support reverse iteration. And we probably want the ability to query the revision index from the db commands (that's another PR though), e.g. to be able to say db/show foo --revision --reverse --count=10 (show 10 newly changed objects).

EDIT: Oh wait, we can't really iterate the revision index in reverse order nicely currently. Can't give the index name to AllReverse and not easy to prefix search the revision index as ByRevision builds the full 8 byte key. Will need to think about extending the methods to take maybe an optional options, e.g. WithIndex or some such.

Copy link
Member

@bimmlerd bimmlerd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leaving review here at first commit - API wants to marinate a bit according to Jussi

// Iterator returns an iterator for all objects.
Iterator() Iterator[T]

// ReverseIterator returns an iterator for all objects in reverse order.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: "reverse order" suggests that Iterator has a defined order. Is that the case and should it be documented?

tree := New[int]()
it := tree.ReverseIterator()
for range it.All {
t.Fatalf("All yielded value from empty tree")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: same test for Next?

for j := uint64(1); j <= numObjectsToInsert; j++ {
_, _, tree = tree.Insert(uint64Key(j), j)
}
b.ResetTimer()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: reset timer etc ;)

@joamaki joamaki marked this pull request as draft January 21, 2026 13:28
@joamaki
Copy link
Contributor Author

joamaki commented Jan 21, 2026

Drafted until I've thought through how to change the API to allow reverse iterating over the revision index nicely...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants