forked from cosmos/iavl
-
Notifications
You must be signed in to change notification settings - Fork 0
/
compress.go
144 lines (120 loc) · 3.43 KB
/
compress.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package iavl
import (
"encoding/binary"
"fmt"
)
type NodeExporter interface {
Next() (*ExportNode, error)
}
type NodeImporter interface {
Add(*ExportNode) error
}
// CompressExporter wraps the normal exporter to apply some compressions on `ExportNode`:
// - branch keys are skipped
// - leaf keys are encoded with delta compared with the previous leaf
// - branch node's version are encoded with delta compared with the max version in it's children
type CompressExporter struct {
inner NodeExporter
lastKey []byte
versionStack []int64
}
var _ NodeExporter = (*CompressExporter)(nil)
func NewCompressExporter(exporter NodeExporter) NodeExporter {
return &CompressExporter{inner: exporter}
}
func (e *CompressExporter) Next() (*ExportNode, error) {
n, err := e.inner.Next()
if err != nil {
return nil, err
}
if n.Height == 0 {
// apply delta encoding to leaf keys
n.Key, e.lastKey = deltaEncode(n.Key, e.lastKey), n.Key
e.versionStack = append(e.versionStack, n.Version)
} else {
// branch keys can be derived on the fly when import, safe to skip
n.Key = nil
// delta encode the version
maxVersion := maxInt64(e.versionStack[len(e.versionStack)-1], e.versionStack[len(e.versionStack)-2])
e.versionStack = e.versionStack[:len(e.versionStack)-1]
e.versionStack[len(e.versionStack)-1] = n.Version
n.Version -= maxVersion
}
return n, nil
}
// CompressImporter wraps the normal importer to do de-compressions before hand.
type CompressImporter struct {
inner NodeImporter
lastKey []byte
minKeyStack [][]byte
versionStack []int64
}
var _ NodeImporter = (*CompressImporter)(nil)
func NewCompressImporter(importer NodeImporter) NodeImporter {
return &CompressImporter{inner: importer}
}
func (i *CompressImporter) Add(node *ExportNode) error {
if node.Height == 0 {
key, err := deltaDecode(node.Key, i.lastKey)
if err != nil {
return err
}
node.Key = key
i.lastKey = key
i.minKeyStack = append(i.minKeyStack, key)
i.versionStack = append(i.versionStack, node.Version)
} else {
// use the min-key in right branch as the node key
node.Key = i.minKeyStack[len(i.minKeyStack)-1]
// leave the min-key in left branch in the stack
i.minKeyStack = i.minKeyStack[:len(i.minKeyStack)-1]
// decode branch node version
maxVersion := maxInt64(i.versionStack[len(i.versionStack)-1], i.versionStack[len(i.versionStack)-2])
node.Version += maxVersion
i.versionStack = i.versionStack[:len(i.versionStack)-1]
i.versionStack[len(i.versionStack)-1] = node.Version
}
return i.inner.Add(node)
}
func deltaEncode(key, lastKey []byte) []byte {
var sizeBuf [binary.MaxVarintLen64]byte
shared := diffOffset(lastKey, key)
n := binary.PutUvarint(sizeBuf[:], uint64(shared))
return append(sizeBuf[:n], key[shared:]...)
}
func deltaDecode(key, lastKey []byte) ([]byte, error) {
shared, n := binary.Uvarint(key)
if n <= 0 {
return nil, fmt.Errorf("uvarint parse failed %d", n)
}
key = key[n:]
if shared == 0 {
return key, nil
}
newKey := make([]byte, shared+uint64(len(key)))
copy(newKey, lastKey[:shared])
copy(newKey[shared:], key)
return newKey, nil
}
// diffOffset returns the index of first byte that's different in two bytes slice.
func diffOffset(a, b []byte) int {
var off int
var l int
if len(a) < len(b) {
l = len(a)
} else {
l = len(b)
}
for ; off < l; off++ {
if a[off] != b[off] {
break
}
}
return off
}
func maxInt64(a, b int64) int64 {
if a > b {
return a
}
return b
}