mirror of
https://github.com/taigrr/bitcask
synced 2025-01-18 04:03:17 -08:00
custom high-performance encoder implementation (#52)
This commit is contained in:
parent
755b1879b5
commit
fd179b4a86
10
bitcask.go
10
bitcask.go
@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/gofrs/flock"
|
"github.com/gofrs/flock"
|
||||||
|
|
||||||
"github.com/prologic/bitcask/internal"
|
"github.com/prologic/bitcask/internal"
|
||||||
|
"github.com/prologic/bitcask/internal/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -36,10 +37,6 @@ var (
|
|||||||
// ErrDatabaseLocked is the error returned if the database is locked
|
// ErrDatabaseLocked is the error returned if the database is locked
|
||||||
// (typically opened by another process)
|
// (typically opened by another process)
|
||||||
ErrDatabaseLocked = errors.New("error: database locked")
|
ErrDatabaseLocked = errors.New("error: database locked")
|
||||||
|
|
||||||
// ErrCreatingMemPool is the error returned when trying to configurate
|
|
||||||
// the mempool fails
|
|
||||||
ErrCreatingMemPool = errors.New("error: creating the mempool failed")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Bitcask is a struct that represents a on-disk LSM and WAL data structure
|
// Bitcask is a struct that represents a on-disk LSM and WAL data structure
|
||||||
@ -242,7 +239,7 @@ func (b *Bitcask) put(key, value []byte) (int64, int64, error) {
|
|||||||
b.curr = curr
|
b.curr = curr
|
||||||
}
|
}
|
||||||
|
|
||||||
e := internal.NewEntry(key, value)
|
e := model.NewEntry(key, value)
|
||||||
return b.curr.Write(e)
|
return b.curr.Write(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -457,8 +454,6 @@ func Open(path string, options ...Option) (*Bitcask, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal.ConfigureMemPool(bitcask.config.maxConcurrency)
|
|
||||||
|
|
||||||
locked, err := bitcask.Flock.TryLock()
|
locked, err := bitcask.Flock.TryLock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -491,3 +486,4 @@ func Merge(path string, force bool) error {
|
|||||||
|
|
||||||
return db.Merge()
|
return db.Merge()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -495,9 +495,8 @@ func TestLocking(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type benchmarkTestCase struct {
|
type benchmarkTestCase struct {
|
||||||
name string
|
name string
|
||||||
size int
|
size int
|
||||||
withPool bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkGet(b *testing.B) {
|
func BenchmarkGet(b *testing.B) {
|
||||||
@ -513,24 +512,15 @@ func BenchmarkGet(b *testing.B) {
|
|||||||
defer os.RemoveAll(testdir)
|
defer os.RemoveAll(testdir)
|
||||||
|
|
||||||
tests := []benchmarkTestCase{
|
tests := []benchmarkTestCase{
|
||||||
{"128B", 128, false},
|
{"128B", 128},
|
||||||
{"128BWithPool", 128, true},
|
{"256B", 256},
|
||||||
{"256B", 256, false},
|
{"512B", 512},
|
||||||
{"256BWithPool", 256, true},
|
{"1K", 1024},
|
||||||
{"512B", 512, false},
|
{"2K", 2048},
|
||||||
{"512BWithPool", 512, true},
|
{"4K", 4096},
|
||||||
{"1K", 1024, false},
|
{"8K", 8192},
|
||||||
{"1KWithPool", 1024, true},
|
{"16K", 16384},
|
||||||
{"2K", 2048, false},
|
{"32K", 32768},
|
||||||
{"2KWithPool", 2048, true},
|
|
||||||
{"4K", 4096, false},
|
|
||||||
{"4KWithPool", 4096, true},
|
|
||||||
{"8K", 8192, false},
|
|
||||||
{"8KWithPool", 8192, true},
|
|
||||||
{"16K", 16384, false},
|
|
||||||
{"16KWithPool", 16384, true},
|
|
||||||
{"32K", 32768, false},
|
|
||||||
{"32KWithPool", 32768, true},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@ -544,9 +534,6 @@ func BenchmarkGet(b *testing.B) {
|
|||||||
WithMaxKeySize(len(key)),
|
WithMaxKeySize(len(key)),
|
||||||
WithMaxValueSize(tt.size),
|
WithMaxValueSize(tt.size),
|
||||||
}
|
}
|
||||||
if tt.withPool {
|
|
||||||
options = append(options, WithMemPool(1))
|
|
||||||
}
|
|
||||||
db, err := Open(testdir, options...)
|
db, err := Open(testdir, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
@ -592,15 +579,14 @@ func BenchmarkPut(b *testing.B) {
|
|||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
tests := []benchmarkTestCase{
|
tests := []benchmarkTestCase{
|
||||||
{"128B", 128, false},
|
{"128B", 128},
|
||||||
{"256B", 256, false},
|
{"256B", 256},
|
||||||
{"512B", 512, false},
|
{"1K", 1024},
|
||||||
{"1K", 1024, false},
|
{"2K", 2048},
|
||||||
{"2K", 2048, false},
|
{"4K", 4096},
|
||||||
{"4K", 4096, false},
|
{"8K", 8192},
|
||||||
{"8K", 8192, false},
|
{"16K", 16384},
|
||||||
{"16K", 16384, false},
|
{"32K", 32768},
|
||||||
{"32K", 32768, false},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
10
doc.go
10
doc.go
@ -1,13 +1,3 @@
|
|||||||
// Package bitcask implements a high-performance key-value store based on a
|
// Package bitcask implements a high-performance key-value store based on a
|
||||||
// WAL and LSM.
|
// WAL and LSM.
|
||||||
//
|
|
||||||
// By default, the client assumes a default configuration regarding maximum key size,
|
|
||||||
// maximum value size, maximum datafile size, and memory pools to avoid allocations.
|
|
||||||
// Refer to Constants section to know default values.
|
|
||||||
//
|
|
||||||
// For extra performance, configure the memory pool option properly. This option
|
|
||||||
// requires to specify the maximum number of concurrent use of the package. Failing to
|
|
||||||
// set a high-enough value would impact latency and throughput. Likewise, overestimating
|
|
||||||
// would yield in an unnecessary big memory footprint.
|
|
||||||
// The default configuration doesn't use a memory pool.
|
|
||||||
package bitcask
|
package bitcask
|
||||||
|
@ -8,7 +8,6 @@ func Example_withOptions() {
|
|||||||
opts := []Option{
|
opts := []Option{
|
||||||
WithMaxKeySize(1024),
|
WithMaxKeySize(1024),
|
||||||
WithMaxValueSize(4096),
|
WithMaxValueSize(4096),
|
||||||
WithMemPool(10),
|
|
||||||
}
|
}
|
||||||
_, _ = Open("path/to/db", opts...)
|
_, _ = Open("path/to/db", opts...)
|
||||||
}
|
}
|
||||||
|
1
go.mod
1
go.mod
@ -4,7 +4,6 @@ require (
|
|||||||
github.com/derekparker/trie v0.0.0-20190805173922-4e1a77fb815d
|
github.com/derekparker/trie v0.0.0-20190805173922-4e1a77fb815d
|
||||||
github.com/gofrs/flock v0.7.1
|
github.com/gofrs/flock v0.7.1
|
||||||
github.com/gogo/protobuf v1.2.1
|
github.com/gogo/protobuf v1.2.1
|
||||||
github.com/golang/protobuf v1.3.2
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||||
github.com/magiconair/properties v1.8.1 // indirect
|
github.com/magiconair/properties v1.8.1 // indirect
|
||||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c
|
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c
|
||||||
|
2
go.sum
2
go.sum
@ -43,8 +43,6 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
|
|||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
|
113
internal/codec/codec.go
Normal file
113
internal/codec/codec.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package codec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/prologic/bitcask/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
KeySize = 4
|
||||||
|
ValueSize = 8
|
||||||
|
checksumSize = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewEncoder creates a streaming Entry encoder.
|
||||||
|
func NewEncoder(w io.Writer) *Encoder {
|
||||||
|
return &Encoder{w: bufio.NewWriter(w)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encoder wraps an underlying io.Writer and allows you to stream
|
||||||
|
// Entry encodings on it.
|
||||||
|
type Encoder struct {
|
||||||
|
w *bufio.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode takes any Entry and streams it to the underlying writer.
|
||||||
|
// Messages are framed with a key-length and value-length prefix.
|
||||||
|
func (e *Encoder) Encode(msg model.Entry) (int64, error) {
|
||||||
|
var bufKeyValue = make([]byte, ValueSize)
|
||||||
|
|
||||||
|
bufKeySize := bufKeyValue[:KeySize]
|
||||||
|
binary.BigEndian.PutUint32(bufKeySize, uint32(len(msg.Key)))
|
||||||
|
if _, err := e.w.Write(bufKeySize); err != nil {
|
||||||
|
return 0, errors.Wrap(err, "failed writing key length prefix")
|
||||||
|
}
|
||||||
|
|
||||||
|
bufValueSize := bufKeyValue[:ValueSize]
|
||||||
|
binary.BigEndian.PutUint64(bufValueSize, uint64(len(msg.Value)))
|
||||||
|
if _, err := e.w.Write(bufValueSize); err != nil {
|
||||||
|
return 0, errors.Wrap(err, "failed writing value length prefix")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := e.w.Write([]byte(msg.Key)); err != nil {
|
||||||
|
return 0, errors.Wrap(err, "failed writing key data")
|
||||||
|
}
|
||||||
|
if _, err := e.w.Write(msg.Value); err != nil {
|
||||||
|
return 0, errors.Wrap(err, "failed writing value data")
|
||||||
|
}
|
||||||
|
|
||||||
|
bufChecksumSize := make([]byte, checksumSize)
|
||||||
|
binary.BigEndian.PutUint32(bufChecksumSize, msg.Checksum)
|
||||||
|
if _, err := e.w.Write(bufChecksumSize); err != nil {
|
||||||
|
return 0, errors.Wrap(err, "failed writing checksum data")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := e.w.Flush(); err != nil {
|
||||||
|
return 0, errors.Wrap(err, "failed flushing data")
|
||||||
|
}
|
||||||
|
|
||||||
|
return int64(KeySize + ValueSize + len(msg.Key) + len(msg.Value) + checksumSize), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDecoder creates a streaming Entry decoder.
|
||||||
|
func NewDecoder(r io.Reader) *Decoder {
|
||||||
|
return &Decoder{r: r}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decoder wraps an underlying io.Reader and allows you to stream
|
||||||
|
// Entry decodings on it.
|
||||||
|
type Decoder struct {
|
||||||
|
r io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Decoder) Decode(v *model.Entry) (int64, error) {
|
||||||
|
prefixBuf := make([]byte, KeySize+ValueSize)
|
||||||
|
|
||||||
|
_, err := io.ReadFull(d.r, prefixBuf)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
actualKeySize, actualValueSize := GetKeyValueSizes(prefixBuf)
|
||||||
|
buf := make([]byte, actualKeySize+actualValueSize+checksumSize)
|
||||||
|
if _, err = io.ReadFull(d.r, buf); err != nil {
|
||||||
|
return 0, errors.Wrap(translateError(err), "failed reading saved data")
|
||||||
|
}
|
||||||
|
|
||||||
|
DecodeWithoutPrefix(buf, actualValueSize, v)
|
||||||
|
return int64(KeySize + ValueSize + actualKeySize + actualValueSize + checksumSize), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetKeyValueSizes(buf []byte) (uint64, uint64) {
|
||||||
|
actualKeySize := binary.BigEndian.Uint32(buf[:KeySize])
|
||||||
|
actualValueSize := binary.BigEndian.Uint64(buf[KeySize:])
|
||||||
|
|
||||||
|
return uint64(actualKeySize), actualValueSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeWithoutPrefix(buf []byte, valueOffset uint64, v *model.Entry) {
|
||||||
|
v.Key = buf[:valueOffset]
|
||||||
|
v.Value = buf[valueOffset : len(buf)-checksumSize]
|
||||||
|
v.Checksum = binary.BigEndian.Uint32(buf[len(buf)-checksumSize:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func translateError(err error) error {
|
||||||
|
if err == io.EOF {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
@ -6,25 +6,21 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/oxtoacart/bpool"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/exp/mmap"
|
"golang.org/x/exp/mmap"
|
||||||
|
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/prologic/bitcask/internal/codec"
|
||||||
pb "github.com/prologic/bitcask/internal/proto"
|
"github.com/prologic/bitcask/internal/model"
|
||||||
"github.com/prologic/bitcask/internal/streampb"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DefaultDatafileFilename = "%09d.data"
|
DefaultDatafileFilename = "%09d.data"
|
||||||
prefixSize = 8
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrReadonly = errors.New("error: read only datafile")
|
ErrReadonly = errors.New("error: read only datafile")
|
||||||
ErrReadError = errors.New("error: read error")
|
ErrReadError = errors.New("error: read error")
|
||||||
|
|
||||||
memPool *bpool.BufferPool
|
|
||||||
mxMemPool sync.RWMutex
|
mxMemPool sync.RWMutex
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -36,8 +32,8 @@ type Datafile struct {
|
|||||||
ra *mmap.ReaderAt
|
ra *mmap.ReaderAt
|
||||||
w *os.File
|
w *os.File
|
||||||
offset int64
|
offset int64
|
||||||
dec *streampb.Decoder
|
dec *codec.Decoder
|
||||||
enc *streampb.Encoder
|
enc *codec.Encoder
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDatafile(path string, id int, readonly bool) (*Datafile, error) {
|
func NewDatafile(path string, id int, readonly bool) (*Datafile, error) {
|
||||||
@ -73,8 +69,8 @@ func NewDatafile(path string, id int, readonly bool) (*Datafile, error) {
|
|||||||
|
|
||||||
offset := stat.Size()
|
offset := stat.Size()
|
||||||
|
|
||||||
dec := streampb.NewDecoder(r)
|
dec := codec.NewDecoder(r)
|
||||||
enc := streampb.NewEncoder(w)
|
enc := codec.NewEncoder(w)
|
||||||
|
|
||||||
return &Datafile{
|
return &Datafile{
|
||||||
id: id,
|
id: id,
|
||||||
@ -126,7 +122,7 @@ func (df *Datafile) Size() int64 {
|
|||||||
return df.offset
|
return df.offset
|
||||||
}
|
}
|
||||||
|
|
||||||
func (df *Datafile) Read() (e pb.Entry, n int64, err error) {
|
func (df *Datafile) Read() (e model.Entry, n int64, err error) {
|
||||||
df.Lock()
|
df.Lock()
|
||||||
defer df.Unlock()
|
defer df.Unlock()
|
||||||
|
|
||||||
@ -138,20 +134,10 @@ func (df *Datafile) Read() (e pb.Entry, n int64, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (df *Datafile) ReadAt(index, size int64) (e pb.Entry, err error) {
|
func (df *Datafile) ReadAt(index, size int64) (e model.Entry, err error) {
|
||||||
var n int
|
var n int
|
||||||
|
|
||||||
var b []byte
|
b := make([]byte, size)
|
||||||
if memPool == nil {
|
|
||||||
b = make([]byte, size)
|
|
||||||
} else {
|
|
||||||
poolSlice := memPool.Get()
|
|
||||||
if poolSlice.Cap() < int(size) {
|
|
||||||
poolSlice.Grow(int(size) - poolSlice.Cap())
|
|
||||||
}
|
|
||||||
defer memPool.Put(poolSlice)
|
|
||||||
b = poolSlice.Bytes()[:size]
|
|
||||||
}
|
|
||||||
|
|
||||||
if df.w == nil {
|
if df.w == nil {
|
||||||
n, err = df.ra.ReadAt(b, index)
|
n, err = df.ra.ReadAt(b, index)
|
||||||
@ -166,14 +152,13 @@ func (df *Datafile) ReadAt(index, size int64) (e pb.Entry, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = proto.Unmarshal(b[prefixSize:], &e)
|
valueOffset, _ := codec.GetKeyValueSizes(b)
|
||||||
if err != nil {
|
codec.DecodeWithoutPrefix(b[codec.KeySize+codec.ValueSize:], valueOffset, &e)
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (df *Datafile) Write(e pb.Entry) (int64, int64, error) {
|
func (df *Datafile) Write(e model.Entry) (int64, int64, error) {
|
||||||
if df.w == nil {
|
if df.w == nil {
|
||||||
return -1, 0, ErrReadonly
|
return -1, 0, ErrReadonly
|
||||||
}
|
}
|
||||||
@ -183,7 +168,7 @@ func (df *Datafile) Write(e pb.Entry) (int64, int64, error) {
|
|||||||
|
|
||||||
e.Offset = df.offset
|
e.Offset = df.offset
|
||||||
|
|
||||||
n, err := df.enc.Encode(&e)
|
n, err := df.enc.Encode(e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, 0, err
|
return -1, 0, err
|
||||||
}
|
}
|
||||||
@ -191,15 +176,3 @@ func (df *Datafile) Write(e pb.Entry) (int64, int64, error) {
|
|||||||
|
|
||||||
return e.Offset, n, nil
|
return e.Offset, n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigureMemPool configurate the mempool accordingly
|
|
||||||
func ConfigureMemPool(maxConcurrency *int) {
|
|
||||||
mxMemPool.Lock()
|
|
||||||
defer mxMemPool.Unlock()
|
|
||||||
if maxConcurrency == nil {
|
|
||||||
memPool = nil
|
|
||||||
} else {
|
|
||||||
memPool = bpool.NewBufferPool(*maxConcurrency)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"hash/crc32"
|
|
||||||
|
|
||||||
pb "github.com/prologic/bitcask/internal/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewEntry(key, value []byte) pb.Entry {
|
|
||||||
checksum := crc32.ChecksumIEEE(value)
|
|
||||||
|
|
||||||
return pb.Entry{
|
|
||||||
Checksum: checksum,
|
|
||||||
Key: key,
|
|
||||||
Value: value,
|
|
||||||
}
|
|
||||||
}
|
|
23
internal/model/entry.go
Normal file
23
internal/model/entry.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"hash/crc32"
|
||||||
|
)
|
||||||
|
// Entry represents a key/value in the database
|
||||||
|
type Entry struct {
|
||||||
|
Checksum uint32
|
||||||
|
Key []byte
|
||||||
|
Offset int64
|
||||||
|
Value []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func NewEntry(key, value []byte) Entry {
|
||||||
|
checksum := crc32.ChecksumIEEE(value)
|
||||||
|
|
||||||
|
return Entry{
|
||||||
|
Checksum: checksum,
|
||||||
|
Key: key,
|
||||||
|
Value: value,
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +0,0 @@
|
|||||||
package proto
|
|
||||||
|
|
||||||
//go:generate protoc --go_out=. entry.proto
|
|
@ -1,97 +0,0 @@
|
|||||||
package streampb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// prefixSize is the number of bytes we preallocate for storing
|
|
||||||
// our big endian lenth prefix buffer.
|
|
||||||
prefixSize = 8
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewEncoder creates a streaming protobuf encoder.
|
|
||||||
func NewEncoder(w io.Writer) *Encoder {
|
|
||||||
return &Encoder{w: bufio.NewWriter(w)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encoder wraps an underlying io.Writer and allows you to stream
|
|
||||||
// proto encodings on it.
|
|
||||||
type Encoder struct {
|
|
||||||
w *bufio.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode takes any proto.Message and streams it to the underlying writer.
|
|
||||||
// Messages are framed with a length prefix.
|
|
||||||
func (e *Encoder) Encode(msg proto.Message) (int64, error) {
|
|
||||||
prefixBuf := make([]byte, prefixSize)
|
|
||||||
|
|
||||||
buf, err := proto.Marshal(msg)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
binary.BigEndian.PutUint64(prefixBuf, uint64(len(buf)))
|
|
||||||
|
|
||||||
if _, err := e.w.Write(prefixBuf); err != nil {
|
|
||||||
return 0, errors.Wrap(err, "failed writing length prefix")
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := e.w.Write(buf)
|
|
||||||
if err != nil {
|
|
||||||
return 0, errors.Wrap(err, "failed writing marshaled data")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = e.w.Flush(); err != nil {
|
|
||||||
return 0, errors.Wrap(err, "failed flushing data")
|
|
||||||
}
|
|
||||||
|
|
||||||
return int64(n + prefixSize), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDecoder creates a streaming protobuf decoder.
|
|
||||||
func NewDecoder(r io.Reader) *Decoder {
|
|
||||||
return &Decoder{r: r}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decoder wraps an underlying io.Reader and allows you to stream
|
|
||||||
// proto decodings on it.
|
|
||||||
type Decoder struct {
|
|
||||||
r io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode takes a proto.Message and unmarshals the next payload in the
|
|
||||||
// underlying io.Reader. It returns an EOF when it's done.
|
|
||||||
func (d *Decoder) Decode(v proto.Message) (int64, error) {
|
|
||||||
prefixBuf := make([]byte, prefixSize)
|
|
||||||
|
|
||||||
_, err := io.ReadFull(d.r, prefixBuf)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
n := binary.BigEndian.Uint64(prefixBuf)
|
|
||||||
|
|
||||||
buf := make([]byte, n)
|
|
||||||
|
|
||||||
idx := uint64(0)
|
|
||||||
for idx < n {
|
|
||||||
m, err := d.r.Read(buf[idx:n])
|
|
||||||
if err != nil {
|
|
||||||
return 0, errors.Wrap(translateError(err), "failed reading marshaled data")
|
|
||||||
}
|
|
||||||
idx += uint64(m)
|
|
||||||
}
|
|
||||||
return int64(idx + prefixSize), proto.Unmarshal(buf[:n], v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func translateError(err error) error {
|
|
||||||
if err == io.EOF {
|
|
||||||
return io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
12
options.go
12
options.go
@ -31,7 +31,6 @@ type config struct {
|
|||||||
maxDatafileSize int
|
maxDatafileSize int
|
||||||
maxKeySize int
|
maxKeySize int
|
||||||
maxValueSize int
|
maxValueSize int
|
||||||
maxConcurrency *int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *config) MarshalJSON() ([]byte, error) {
|
func (c *config) MarshalJSON() ([]byte, error) {
|
||||||
@ -102,14 +101,3 @@ func WithMaxValueSize(size int) Option {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithMemPool configures usage of a memory pool to avoid allocations
|
|
||||||
func WithMemPool(maxConcurrency int) Option {
|
|
||||||
return func(cfg *config) error {
|
|
||||||
if maxConcurrency <= 0 {
|
|
||||||
return ErrMaxConcurrencyLowerEqZero
|
|
||||||
}
|
|
||||||
cfg.maxConcurrency = &maxConcurrency
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user