1
1
// Package autobatch provides a go-datastore implementation that
2
2
// automatically batches together writes by holding puts in memory until
3
- // a certain threshold is met.
3
+ // a certain threshold is met. It also acts as a debounce.
4
4
package autobatch
5
5
6
6
import (
7
+ "log"
8
+ "sync"
9
+ "time"
10
+
7
11
ds "github.com/ipfs/go-datastore"
8
12
dsq "github.com/ipfs/go-datastore/query"
9
13
)
@@ -12,9 +16,13 @@ import (
12
16
type Datastore struct {
13
17
child ds.Batching
14
18
15
- // TODO: discuss making ds.Batch implement the full ds.Datastore interface
16
- buffer map [ds.Key ]op
17
- maxBufferEntries int
19
+ mu sync.RWMutex
20
+ buffer map [ds.Key ]op
21
+
22
+ maxWrite int
23
+ maxDelay time.Duration
24
+ newWrite chan struct {}
25
+ exit chan struct {}
18
26
}
19
27
20
28
type op struct {
@@ -23,28 +31,79 @@ type op struct {
23
31
}
24
32
25
33
// NewAutoBatching returns a new datastore that automatically
26
- // batches writes using the given Batching datastore. The size
27
- // of the memory pool is given by size.
28
- func NewAutoBatching (d ds.Batching , size int ) * Datastore {
29
- return & Datastore {
30
- child : d ,
31
- buffer : make (map [ds.Key ]op , size ),
32
- maxBufferEntries : size ,
34
+ // batches writes using the given Batching datastore. The maximum number of
35
+ // write before triggering a batch is given by maxWrite. The maximum delay
36
+ // before triggering a batch is given by maxDelay.
37
+ func NewAutoBatching (child ds.Batching , maxWrite int , maxDelay time.Duration ) * Datastore {
38
+ d := & Datastore {
39
+ child : child ,
40
+ buffer : make (map [ds.Key ]op , maxWrite ),
41
+ maxWrite : maxWrite ,
42
+ maxDelay : maxDelay ,
43
+ newWrite : make (chan struct {}),
44
+ exit : make (chan struct {}),
45
+ }
46
+ go d .runBatcher ()
47
+ return d
48
+ }
49
+
50
+ func (d * Datastore ) addOp (key ds.Key , op op ) {
51
+ d .mu .Lock ()
52
+ d .buffer [key ] = op
53
+ d .mu .Unlock ()
54
+ d .newWrite <- struct {}{}
55
+ }
56
+
57
+ func (d * Datastore ) runBatcher () {
58
+ var timer <- chan time.Time
59
+
60
+ write := func () {
61
+ timer = nil
62
+
63
+ b , err := d .prepareBatch (nil )
64
+ if err != nil {
65
+ log .Println (err )
66
+ return
67
+ }
68
+ err = b .Commit ()
69
+ if err != nil {
70
+ log .Println (err )
71
+ return
72
+ }
73
+ }
74
+
75
+ for {
76
+ select {
77
+ case <- d .exit :
78
+ return
79
+ case <- timer :
80
+ write ()
81
+ case <- d .newWrite :
82
+ d .mu .RLock ()
83
+ ready := len (d .buffer )
84
+ d .mu .RUnlock ()
85
+ if ready > d .maxWrite {
86
+ write ()
87
+ }
88
+ if timer == nil {
89
+ timer = time .After (d .maxDelay )
90
+ }
91
+ }
33
92
}
34
93
}
35
94
36
95
// Delete deletes a key/value
37
96
func (d * Datastore ) Delete (k ds.Key ) error {
38
- d .buffer [k ] = op {delete : true }
39
- if len (d .buffer ) > d .maxBufferEntries {
40
- return d .Flush ()
41
- }
97
+ d .addOp (k , op {delete : true })
42
98
return nil
43
99
}
44
100
45
101
// Get retrieves a value given a key.
46
102
func (d * Datastore ) Get (k ds.Key ) ([]byte , error ) {
103
+ d .mu .RLock ()
47
104
o , ok := d .buffer [k ]
105
+ d .mu .RUnlock ()
106
+
48
107
if ok {
49
108
if o .delete {
50
109
return nil , ds .ErrNotFound
@@ -57,69 +116,67 @@ func (d *Datastore) Get(k ds.Key) ([]byte, error) {
57
116
58
117
// Put stores a key/value.
59
118
func (d * Datastore ) Put (k ds.Key , val []byte ) error {
60
- d .buffer [k ] = op {value : val }
61
- if len (d .buffer ) > d .maxBufferEntries {
62
- return d .Flush ()
63
- }
119
+ d .addOp (k , op {value : val })
64
120
return nil
65
121
}
66
122
67
123
// Sync flushes all operations on keys at or under the prefix
68
124
// from the current batch to the underlying datastore
69
125
func (d * Datastore ) Sync (prefix ds.Key ) error {
70
- b , err := d .child . Batch ( )
126
+ b , err := d .prepareBatch ( & prefix )
71
127
if err != nil {
72
128
return err
73
129
}
74
-
75
- for k , o := range d .buffer {
76
- if ! (k .Equal (prefix ) || k .IsDescendantOf (prefix )) {
77
- continue
78
- }
79
-
80
- var err error
81
- if o .delete {
82
- err = b .Delete (k )
83
- } else {
84
- err = b .Put (k , o .value )
85
- }
86
- if err != nil {
87
- return err
88
- }
89
-
90
- delete (d .buffer , k )
91
- }
92
-
93
130
return b .Commit ()
94
131
}
95
132
96
133
// Flush flushes the current batch to the underlying datastore.
97
134
func (d * Datastore ) Flush () error {
98
- b , err := d .child . Batch ( )
135
+ b , err := d .prepareBatch ( nil )
99
136
if err != nil {
100
137
return err
101
138
}
139
+ return b .Commit ()
140
+ }
141
+
142
+ func (d * Datastore ) prepareBatch (prefix * ds.Key ) (ds.Batch , error ) {
143
+ b , err := d .child .Batch ()
144
+ if err != nil {
145
+ return nil , err
146
+ }
147
+
148
+ d .mu .Lock ()
102
149
103
150
for k , o := range d .buffer {
151
+ if prefix != nil && ! (k .Equal (* prefix ) || k .IsDescendantOf (* prefix )) {
152
+ continue
153
+ }
154
+
104
155
var err error
105
156
if o .delete {
106
157
err = b .Delete (k )
107
158
} else {
108
159
err = b .Put (k , o .value )
109
160
}
110
161
if err != nil {
111
- return err
162
+ d .mu .Unlock ()
163
+ return nil , err
112
164
}
165
+
166
+ delete (d .buffer , k )
113
167
}
114
- // clear out buffer
115
- d .buffer = make (map [ds.Key ]op , d .maxBufferEntries )
116
168
117
- return b .Commit ()
169
+ d .mu .Unlock ()
170
+
171
+ return b , nil
118
172
}
119
173
120
174
// Has checks if a key is stored.
121
175
func (d * Datastore ) Has (k ds.Key ) (bool , error ) {
176
+ d .mu .RLock ()
122
177
o , ok := d .buffer [k ]
178
+ d .mu .RUnlock ()
179
+
123
180
if ok {
124
181
return ! o .delete , nil
125
182
}
@@ -129,7 +186,10 @@ func (d *Datastore) Has(k ds.Key) (bool, error) {
129
186
130
187
// GetSize implements Datastore.GetSize
131
188
func (d * Datastore ) GetSize (k ds.Key ) (int , error ) {
189
+ d .mu .RLock ()
132
190
o , ok := d .buffer [k ]
191
+ d .mu .RUnlock ()
192
+
133
193
if ok {
134
194
if o .delete {
135
195
return - 1 , ds .ErrNotFound
@@ -155,6 +215,18 @@ func (d *Datastore) DiskUsage() (uint64, error) {
155
215
return ds .DiskUsage (d .child )
156
216
}
157
217
218
+ func (d * Datastore ) Batch () (ds.Batch , error ) {
219
+ b , err := d .child .Batch ()
220
+ if err != nil {
221
+ return nil , err
222
+ }
223
+ return & batch {
224
+ parent : d ,
225
+ child : b ,
226
+ toDelete : make (map [ds.Key ]struct {}),
227
+ }, nil
228
+ }
229
+
158
230
func (d * Datastore ) Close () error {
159
231
err1 := d .Flush ()
160
232
err2 := d .child .Close ()
@@ -164,5 +236,32 @@ func (d *Datastore) Close() error {
164
236
if err2 != nil {
165
237
return err2
166
238
}
239
+ close (d .exit )
240
+ close (d .newWrite )
167
241
return nil
168
242
}
243
+
244
+ type batch struct {
245
+ parent * Datastore
246
+ child ds.Batch
247
+ toDelete map [ds.Key ]struct {}
248
+ }
249
+
250
+ func (b * batch ) Put (key ds.Key , value []byte ) error {
251
+ delete (b .toDelete , key )
252
+ return b .child .Put (key , value )
253
+ }
254
+
255
+ func (b * batch ) Delete (key ds.Key ) error {
256
+ b .toDelete [key ] = struct {}{}
257
+ return b .child .Delete (key )
258
+ }
259
+
260
+ func (b * batch ) Commit () error {
261
+ b .parent .mu .Lock ()
262
+ for key := range b .toDelete {
263
+ delete (b .parent .buffer , key )
264
+ }
265
+ b .parent .mu .Unlock ()
266
+ return b .child .Commit ()
267
+ }
0 commit comments