···3838 opts.NumCompactors = 8
39394040 // use low ValueThreshold to force operation values into the value logs
4141- // sadly this means they won't be compressed at all (TODO implement compression ourselves, maybe based on a fixed zstd dictionary?)
4141+ // sadly this means they won't be compressed at all, so we're compressing the operations ourselves
4242+ // (with the benefit of being able to use a custom dictionary for much better compression ratios)
4243 // because these large values don't seem to change (unlike the non-leaf nodes within the iavl tree, which are changing all the time),
4344 // this makes compaction much faster and decreases SSD thrashing
4444- opts.ValueThreshold = 256
4545+ opts.ValueThreshold = 192
4546 return NewBadgerDBWithOptions(opts)
4647}
4748
+54-3
store/tree.go
···2233import (
44 "context"
55+ _ "embed"
56 "encoding/base32"
67 "encoding/binary"
78 "iter"
···1415 ics23 "github.com/cosmos/ics23/go"
1516 "github.com/did-method-plc/go-didplc"
1617 cbornode "github.com/ipfs/go-ipld-cbor"
1818+ "github.com/klauspost/compress/zstd"
1719 "github.com/palantir/stacktrace"
1820 "github.com/polydawn/refmt/obj/atlas"
1921 "github.com/samber/lo"
···39414042 AuthoritativeImportProgress(tx transaction.Read) (uint64, error)
4143 SetAuthoritativeImportProgress(tx transaction.Write, nextCursor uint64) error
4444+4545+ ProduceOperationExamples(tx transaction.Read, interval, count int) iter.Seq[[]byte]
4246}
43474448var _ PLCTreeStore = (*TreeStore)(nil)
···4650// TreeStore exists just to groups methods nicely
4751type TreeStore struct{}
48525353+//go:embed zstddict/plcops
5454+var plcOpsZstdDict []byte
5555+5656+// Using SpeedDefault appears to cause the processing time for ExecuteOperation to double on average
5757+// Using SpeedBetterCompression appears to cause the processing time to double again
5858+// By using SpeedFastest we seem to give up on like 5% size reduction, it's not worth using the slower speeds
5959+var zstdOpEncoder, _ = zstd.NewWriter(nil, zstd.WithEncoderDict(plcOpsZstdDict), zstd.WithEncoderLevel(zstd.SpeedFastest))
6060+var zstdOpDecoder, _ = zstd.NewReader(nil, zstd.WithDecoderDicts(plcOpsZstdDict))
6161+6262+func (t *TreeStore) ProduceOperationExamples(tx transaction.Read, interval, count int) iter.Seq[[]byte] {
6363+ return func(yield func([]byte) bool) {
6464+ for i := 0; i < count*interval; i += interval {
6565+ key := marshalOperationKey(uint64(i))
6666+6767+ value, err := tx.Tree().Get(key)
6868+ if err != nil {
6969+ continue
7070+ }
7171+7272+ if !yield(slices.Clone(value)) {
7373+ return
7474+ }
7575+ }
7676+ }
7777+7878+}
7979+4980func (t *TreeStore) AuditLog(ctx context.Context, tx transaction.Read, did string, withProof bool) ([]types.SequencedLogEntry, *ics23.CommitmentProof, error) {
5081 didBytes, err := DIDToBytes(did)
5182 if err != nil {
···259290 if err != nil {
260291 return stacktrace.Propagate(err, "")
261292 }
262262- operationValue = slices.Clone(operationValue)
263263- operationValue[0] = 1
293293+ operationValue, err = markCompressedOperationValueNullified(operationValue)
294294+ if err != nil {
295295+ return stacktrace.Propagate(err, "")
296296+ }
264297265298 updated, err := tx.Tree().Set(opKey, operationValue)
266299 if err != nil {
···444477 binary.BigEndian.PutUint64(o[16:24], ts)
445478 copy(o[24:], opAsBytes)
446479447447- return o
480480+ return zstdOpEncoder.EncodeAll(o, make([]byte, 0, len(o)))
448481}
449482450483func unmarshalOperationValue(value []byte) (bool, string, time.Time, didplc.OpEnum, error) {
484484+ // passing a nil output buffer to DecodeAll means it'll optimistically start by allocating len(value)*2
485485+ // but we observe compression ratios better than 50% sometimes, so allocate len(value)*3 instead
486486+ value, err := zstdOpDecoder.DecodeAll(value, make([]byte, 0, len(value)*3))
487487+ if err != nil {
488488+ return false, "", time.Time{}, didplc.OpEnum{}, stacktrace.Propagate(err, "")
489489+ }
490490+451491 nullified := value[0] != 0
452492453493 did, err := bytesToDID(value[1:16])
···464504 return false, "", time.Time{}, didplc.OpEnum{}, stacktrace.Propagate(err, "")
465505 }
466506 return nullified, did, createdAt, opEnum, nil
507507+}
508508+509509+func markCompressedOperationValueNullified(value []byte) ([]byte, error) {
510510+ value, err := zstdOpDecoder.DecodeAll(value, make([]byte, 0, len(value)*3))
511511+ if err != nil {
512512+ return nil, stacktrace.Propagate(err, "")
513513+ }
514514+515515+ value[0] = 1
516516+517517+ return zstdOpEncoder.EncodeAll(value, make([]byte, 0, len(value))), nil
467518}
468519469520func unmarshalLogEntry(operationKey, operationValue []byte) (types.SequencedLogEntry, error) {