@@ -17,6 +17,7 @@ import (
1717 "testing"
1818
1919 badgerds "github.com/dgraph-io/badger/v4"
20+ badgeropts "github.com/dgraph-io/badger/v4/options"
2021 "github.com/stretchr/testify/require"
2122
2223 "github.com/sourcenetwork/corekv/badger"
@@ -25,17 +26,24 @@ import (
2526 acpDB "github.com/sourcenetwork/defradb/internal/db/acp"
2627)
2728
28- // newOnDiskDB opens a DB on a badger directory with stock options. It returns a close func
29- // rather than registering cleanup so a caller can release it per iteration. An in-memory
30- // store has no value log and different write amplification, so it cannot stand in for a
31- // deployed node when the cost of a write path is what is being measured.
29+ // newOnDiskDB opens a DB on a badger directory with the options a node uses, so the value
30+ // threshold and compression match a deployment. It returns a close func rather than
31+ // registering cleanup so a caller can release it per iteration. An in-memory store has no
32+ // value log and different write amplification, so it cannot stand in for a node when the
33+ // cost of a write path is what is being measured.
3234func newOnDiskDB (b * testing.B , ctx context.Context ) (* DB , func ()) {
3335 b .Helper ()
3436
3537 dir , err := os .MkdirTemp ("" , "purgebench" )
3638 require .NoError (b , err )
3739
38- rootstore , err := badger .NewDatastore (dir , badgerds .DefaultOptions (dir ))
40+ // Mirrors node/store_badger.go. ZSTDCompressionLevel is left alone because badger
41+ // already defaults it to the value a node sets.
42+ opts := badgerds .DefaultOptions (dir )
43+ opts .ValueThreshold = 1 << 8
44+ opts .Compression = badgeropts .ZSTD
45+
46+ rootstore , err := badger .NewDatastore (dir , opts )
3947 require .NoError (b , err )
4048
4149 adminInfo , err := acpDB .NewNACInfo (ctx , "" , false )
@@ -80,7 +88,8 @@ func setupIndexedCollection(b *testing.B, ctx context.Context, db *DB) client.Co
8088}
8189
8290// addRecords writes n documents and returns their IDs. The payload field is unindexed and
83- // exists only to give each document enough size to reach the value log.
91+ // exists to carry each document past the value threshold, so writes reach the value log the
92+ // way they do on a node.
8493func addRecords (b * testing.B , ctx context.Context , col client.Collection , n int ) []client.DocID {
8594 b .Helper ()
8695
@@ -98,37 +107,46 @@ func addRecords(b *testing.B, ctx context.Context, col client.Collection, n int)
98107}
99108
100109// BenchmarkPurgeByDocIDsChunkSize measures how long it takes to purge a fixed set of
101- // documents as the number of them sharing a transaction changes, so purgeChunkSize can be
102- // chosen from a measurement rather than assumed.
110+ // documents as the number of them sharing a transaction changes.
111+ //
112+ // Both pruneHistory branches are swept. The CLI and HTTP defaults are false and true adds
113+ // the per-document DAG walk, so a size measured under one branch is not automatically right
114+ // for the other. Documents are written locally, so their DAGs are one commit deep; a
115+ // document built up over many merges walks further and costs more than this measures.
103116//
104- // pruneHistory is on because that is the deployed setting and it adds the per-document DAG
105- // walk. Documents are written locally, so their DAGs are one commit deep; a document built
106- // up over many merges walks further and costs more than this measures.
117+ // The sweep runs below purgeChunkSize so the low end is measured rather than assumed to be
118+ // the endpoint.
119+ //
120+ // This purges single-threaded against an idle store, so it shows how cost scales with chunk
121+ // size and not how a size holds up under concurrent merge traffic, which is what the choice
122+ // of constant turns on.
107123func BenchmarkPurgeByDocIDsChunkSize (b * testing.B ) {
108124 const docs = 2000
109125
110- for _ , chunkSize := range []int {8 , 25 , 50 , 100 , 200 } {
111- b .Run (fmt .Sprintf ("chunk=%d" , chunkSize ), func (b * testing.B ) {
112- ctx := context .Background ()
113-
114- for b .Loop () {
115- b .StopTimer ()
116- db , closeDB := newOnDiskDB (b , ctx )
117- col := setupIndexedCollection (b , ctx , db )
118- docIDs := addRecords (b , ctx , col , docs )
119- concrete , ok := col .(* collection )
120- require .True (b , ok )
121- b .StartTimer ()
122-
123- for i := 0 ; i < len (docIDs ); i += chunkSize {
124- end := min (i + chunkSize , len (docIDs ))
125- require .NoError (b , concrete .purgeChunk (ctx , docIDs [i :end ], true ))
126+ for _ , pruneHistory := range []bool {true , false } {
127+ for _ , chunkSize := range []int {1 , 2 , 4 , 8 , 25 , 50 , 100 , 200 } {
128+ b .Run (fmt .Sprintf ("prune=%v/chunk=%d" , pruneHistory , chunkSize ), func (b * testing.B ) {
129+ ctx := context .Background ()
130+
131+ for b .Loop () {
132+ b .StopTimer ()
133+ db , closeDB := newOnDiskDB (b , ctx )
134+ col := setupIndexedCollection (b , ctx , db )
135+ docIDs := addRecords (b , ctx , col , docs )
136+ concrete , ok := col .(* collection )
137+ require .True (b , ok )
138+ b .StartTimer ()
139+
140+ for i := 0 ; i < len (docIDs ); i += chunkSize {
141+ end := min (i + chunkSize , len (docIDs ))
142+ require .NoError (b , concrete .purgeChunk (ctx , docIDs [i :end ], pruneHistory ))
143+ }
144+
145+ b .StopTimer ()
146+ closeDB ()
147+ b .StartTimer ()
126148 }
127-
128- b .StopTimer ()
129- closeDB ()
130- b .StartTimer ()
131- }
132- })
149+ })
150+ }
133151 }
134152}
0 commit comments