-
Notifications
You must be signed in to change notification settings - Fork 807
Add pebble database implementation #1999
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 20 commits
d24ee4c
d578d00
db2eeeb
0983042
528cc2d
1bcfa65
73a934f
1e094b4
5f39300
d937e6c
721275e
5a8d855
9f7665b
1db44c4
7e4cf61
4051e88
4bc0630
662975f
cd0a8e2
261e710
59a8615
a710f54
f21c81d
cc7a15e
f9192ed
155edc9
72a249d
4b08b25
b8fa698
a1b7fba
6b93c90
610e7f8
fc91cd1
5aff591
49499fa
bba3048
3ffc6a8
a8ff7b8
c6bacc7
b886790
f771665
3412216
f0e3f2e
c4d1a87
d379e77
8eea9eb
c350116
b64de08
b1768c1
afd88e3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. | ||
// See the file LICENSE for licensing terms. | ||
|
||
package pebble | ||
|
||
import ( | ||
"fmt" | ||
"sync/atomic" | ||
|
||
"github.com/cockroachdb/pebble" | ||
|
||
"github.com/ava-labs/avalanchego/database" | ||
) | ||
|
||
var _ database.Batch = (*batch)(nil) | ||
|
||
// Not safe for concurrent use. | ||
type batch struct { | ||
batch *pebble.Batch | ||
db *Database | ||
size int | ||
|
||
// True iff [batch] has been written to the database | ||
// since the last time [Reset] was called. | ||
written atomic.Bool | ||
} | ||
|
||
func (db *Database) NewBatch() database.Batch { | ||
return &batch{ | ||
db: db, | ||
batch: db.pebbleDB.NewBatch(), | ||
} | ||
} | ||
|
||
func (b *batch) Put(key, value []byte) error { | ||
b.size += len(key) + len(value) + pebbleByteOverHead | ||
return b.batch.Set(key, value, pebble.Sync) | ||
} | ||
|
||
func (b *batch) Delete(key []byte) error { | ||
b.size += len(key) + pebbleByteOverHead | ||
return b.batch.Delete(key, pebble.Sync) | ||
} | ||
|
||
func (b *batch) Size() int { | ||
return b.size | ||
} | ||
|
||
// Assumes [b.db.lock] is not held. | ||
func (b *batch) Write() error { | ||
b.db.lock.RLock() | ||
defer b.db.lock.RUnlock() | ||
|
||
// Committing to a closed database makes pebble panic | ||
// so make sure [b.db] isn't closed. | ||
if b.db.closed { | ||
return database.ErrClosed | ||
} | ||
|
||
if b.written.CompareAndSwap(false, true) { | ||
// This batch has not been written to the database yet. | ||
return updateError(b.batch.Commit(pebble.Sync)) | ||
} | ||
|
||
// pebble doesn't support writing a batch twice so we have to clone | ||
// [b] and commit the clone. | ||
batchClone := b.db.pebbleDB.NewBatch() | ||
|
||
// Copy the batch. | ||
if err := batchClone.Apply(b.batch, nil); err != nil { | ||
return err | ||
} | ||
Comment on lines
+73
to
+75
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know it isn't used - but wondering if this would be cleaner if we passed in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm...I don't feel super strongly. If we pass in |
||
|
||
// Commit the new batch. | ||
return updateError(batchClone.Commit(pebble.Sync)) | ||
} | ||
|
||
func (b *batch) Reset() { | ||
b.batch.Reset() | ||
b.written.Store(false) | ||
b.size = 0 | ||
} | ||
|
||
func (b *batch) Replay(w database.KeyValueWriterDeleter) error { | ||
reader := b.batch.Reader() | ||
for { | ||
kind, k, v, ok := reader.Next() | ||
if !ok { | ||
return nil | ||
} | ||
switch kind { | ||
case pebble.InternalKeyKindSet: | ||
if err := w.Put(k, v); err != nil { | ||
return err | ||
} | ||
case pebble.InternalKeyKindDelete: | ||
if err := w.Delete(k); err != nil { | ||
return err | ||
} | ||
default: | ||
return fmt.Errorf("%w: %v", ErrInvalidOperation, kind) | ||
} | ||
} | ||
} | ||
|
||
func (b *batch) Inner() database.Batch { | ||
return b | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. | ||
// See the file LICENSE for licensing terms. | ||
|
||
package pebble | ||
|
||
import ( | ||
"os" | ||
"testing" | ||
|
||
"github.com/prometheus/client_golang/prometheus" | ||
|
||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/ava-labs/avalanchego/utils/logging" | ||
) | ||
|
||
// Note: TestInterface tests other batch functionality. | ||
func TestBatch(t *testing.T) { | ||
require := require.New(t) | ||
dirName := os.TempDir() | ||
defer os.Remove(dirName) | ||
danlaine marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
||
db, err := New(dirName, DefaultConfig, logging.NoLog{}, "", prometheus.NewRegistry()) | ||
require.NoError(err) | ||
|
||
batchIntf := db.NewBatch() | ||
batch, ok := batchIntf.(*batch) | ||
require.True(ok) | ||
|
||
require.False(batch.written.Load()) | ||
|
||
key1, value1 := []byte("key1"), []byte("value1") | ||
require.NoError(batch.Put(key1, value1)) | ||
require.Equal(len(key1)+len(value1)+pebbleByteOverHead, batch.Size()) | ||
|
||
require.NoError(batch.Write()) | ||
|
||
require.True(batch.written.Load()) | ||
|
||
got, err := db.Get(key1) | ||
require.NoError(err) | ||
require.Equal(value1, got) | ||
|
||
batch.Reset() | ||
require.False(batch.written.Load()) | ||
require.Zero(batch.Size()) | ||
} |
Uh oh!
There was an error while loading. Please reload this page.