forked from dgraph-io/badger
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create a WriteBatch API to allow efficient serial writes (dgraph-io#608)
**Breaking change** - This change removes the ability to provide a callback in Commit. Instead, it creates another function called CommitWith, which takes a callback. - Previously, a user-specified callback might or might not get executed. This PR fixes that by guaranteeing that the commit callback is always executed. - Create a WriteBatch API to batch up multiple updates into a single txn. The txn gets Committed via callback, so the user gets a simple efficient way to do a lot of writes. - Run only a few goroutines to execute these user callbacks. Previously, we were shooting off one goroutine per Commit callback.
- Loading branch information
1 parent
fc94c57
commit 6daccf9
Showing
10 changed files
with
383 additions
and
150 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
/* | ||
* Copyright 2018 Dgraph Labs, Inc. and Contributors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package badger | ||
|
||
import ( | ||
"sync" | ||
) | ||
|
||
type WriteBatch struct { | ||
sync.Mutex | ||
txn *Txn | ||
db *DB | ||
wg sync.WaitGroup | ||
err error | ||
} | ||
|
||
// NewWriteBatch creates a new WriteBatch. This provides a way to conveniently do a lot of writes, | ||
// batching them up as tightly as possible in a single transaction and using callbacks to avoid | ||
// waiting for them to commit, thus achieving good performance. This API hides away the logic of | ||
// creating and committing transactions. Due to the nature of SSI guaratees provided by Badger, | ||
// blind writes can never encounter transaction conflicts (ErrConflict). | ||
func (db *DB) NewWriteBatch() *WriteBatch { | ||
return &WriteBatch{db: db, txn: db.newTransaction(true, true)} | ||
} | ||
|
||
func (wb *WriteBatch) callback(err error) { | ||
// sync.WaitGroup is thread-safe, so it doesn't need to be run inside wb.Lock. | ||
defer wb.wg.Done() | ||
if err == nil { | ||
return | ||
} | ||
|
||
wb.Lock() | ||
defer wb.Unlock() | ||
if wb.err != nil { | ||
return | ||
} | ||
wb.err = err | ||
} | ||
|
||
// Set is equivalent of Txn.SetWithMeta. | ||
func (wb *WriteBatch) Set(k, v []byte, meta byte) error { | ||
wb.Lock() | ||
defer wb.Unlock() | ||
|
||
if err := wb.txn.SetWithMeta(k, v, meta); err != ErrTxnTooBig { | ||
return err | ||
} | ||
// Txn has reached it's zenith. Commit now. | ||
if cerr := wb.commit(); cerr != nil { | ||
return cerr | ||
} | ||
// This time the error must not be ErrTxnTooBig, otherwise, we make the | ||
// error permanent. | ||
if err := wb.txn.SetWithMeta(k, v, meta); err != nil { | ||
wb.err = err | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
// Delete is equivalent of Txn.Delete. | ||
func (wb *WriteBatch) Delete(k []byte) error { | ||
wb.Lock() | ||
defer wb.Unlock() | ||
|
||
if err := wb.txn.Delete(k); err != ErrTxnTooBig { | ||
return err | ||
} | ||
if err := wb.commit(); err != nil { | ||
return err | ||
} | ||
if err := wb.txn.Delete(k); err != nil { | ||
wb.err = err | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
// Caller to commit must hold a write lock. | ||
func (wb *WriteBatch) commit() error { | ||
if wb.err != nil { | ||
return wb.err | ||
} | ||
// Get a new txn before we commit this one. So, the new txn doesn't need | ||
// to wait for this one to commit. | ||
wb.wg.Add(1) | ||
wb.txn.CommitWith(wb.callback) | ||
wb.txn = wb.db.newTransaction(true, true) | ||
wb.txn.readTs = 0 // We're not reading anything. | ||
return wb.err | ||
} | ||
|
||
// Flush must be called at the end to ensure that any pending writes get committed to Badger. Flush | ||
// returns any error stored by WriteBatch. | ||
func (wb *WriteBatch) Flush() error { | ||
wb.Lock() | ||
_ = wb.commit() | ||
wb.txn.Discard() | ||
wb.Unlock() | ||
|
||
wb.wg.Wait() | ||
// Safe to access error without any synchronization here. | ||
return wb.err | ||
} | ||
|
||
// Error returns any errors encountered so far. No commits would be run once an error is detected. | ||
func (wb *WriteBatch) Error() error { | ||
wb.Lock() | ||
defer wb.Unlock() | ||
return wb.err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
/* | ||
* Copyright 2018 Dgraph Labs, Inc. and Contributors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package badger | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestWriteBatch(t *testing.T) { | ||
key := func(i int) []byte { | ||
// b := make([]byte, 8) | ||
// binary.BigEndian.PutUint64(b, uint64(i)) | ||
// return b | ||
return []byte(fmt.Sprintf("%10d", i)) | ||
} | ||
val := func(i int) []byte { | ||
return []byte(fmt.Sprintf("%128d", i)) | ||
} | ||
|
||
runBadgerTest(t, nil, func(t *testing.T, db *DB) { | ||
wb := db.NewWriteBatch() | ||
N, M := 50000, 1000 | ||
start := time.Now() | ||
|
||
for i := 0; i < N; i++ { | ||
require.NoError(t, wb.Set(key(i), val(i), 0)) | ||
} | ||
for i := 0; i < M; i++ { | ||
require.NoError(t, wb.Delete(key(i))) | ||
} | ||
require.NoError(t, wb.Flush()) | ||
t.Logf("Time taken for %d writes (w/ test options): %s\n", N+M, time.Since(start)) | ||
|
||
err := db.View(func(txn *Txn) error { | ||
itr := txn.NewIterator(DefaultIteratorOptions) | ||
defer itr.Close() | ||
|
||
i := M | ||
for itr.Rewind(); itr.Valid(); itr.Next() { | ||
item := itr.Item() | ||
require.Equal(t, string(key(i)), string(item.Key())) | ||
valcopy, err := item.ValueCopy(nil) | ||
require.NoError(t, err) | ||
require.Equal(t, val(i), valcopy) | ||
i++ | ||
} | ||
require.Equal(t, N, i) | ||
return nil | ||
}) | ||
require.NoError(t, err) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.