mirror of
https://github.com/gogrlx/bitcask.git
synced 2026-04-17 02:25:04 -07:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
962e53af17 | ||
|
|
7a427a237a | ||
|
|
8bf169c96f | ||
|
|
c1488fed2a | ||
|
|
d6e806e655 | ||
|
|
2d9bfbb408 | ||
|
|
d8a48f9eea | ||
|
|
65e7877bdf | ||
|
|
5711478dd6 | ||
|
|
336795285e | ||
|
|
7fba9bd4b7 | ||
|
|
e117ffd2e9 |
@@ -12,11 +12,3 @@ steps:
|
|||||||
settings:
|
settings:
|
||||||
token:
|
token:
|
||||||
from_secret: codecov-token
|
from_secret: codecov-token
|
||||||
|
|
||||||
- name: notify
|
|
||||||
image: plugins/webhook
|
|
||||||
urls: https://msgbus.mills.io/ci.mills.io
|
|
||||||
when:
|
|
||||||
status:
|
|
||||||
- success
|
|
||||||
- failure
|
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ builds:
|
|||||||
binary: bitcask
|
binary: bitcask
|
||||||
main: ./cmd/bitcask
|
main: ./cmd/bitcask
|
||||||
flags: -tags "static_build"
|
flags: -tags "static_build"
|
||||||
ldflags: -w -X .Version={{.Version}} -X .Commit={{.Commit}}
|
ldflags: -w -X github.com/prologic/bitcask/internal.Version={{.Version}} -X github.com/prologic/bitcask/internal.Commit={{.Commit}}
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
-
|
-
|
||||||
binary: bitcaskd
|
binary: bitcaskd
|
||||||
main: ./cmd/bitcaskd
|
main: ./cmd/bitcaskd
|
||||||
flags: -tags "static_build"
|
flags: -tags "static_build"
|
||||||
ldflags: -w -X .Version={{.Version}} -X .Commit={{.Commit}}
|
ldflags: -w -X github.com/prologic/bitcask/internal.Version={{.Version}} -X github.com/prologic/bitcask/internal.Commit={{.Commit}}
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
sign:
|
sign:
|
||||||
|
|||||||
1
Makefile
1
Makefile
@@ -25,6 +25,7 @@ generate:
|
|||||||
|
|
||||||
install: build
|
install: build
|
||||||
@go install ./cmd/bitcask/...
|
@go install ./cmd/bitcask/...
|
||||||
|
@go install ./cmd/bitcaskd/...
|
||||||
|
|
||||||
image:
|
image:
|
||||||
@docker build -t prologic/bitcask .
|
@docker build -t prologic/bitcask .
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -6,13 +6,13 @@
|
|||||||
[](https://godoc.org/github.com/prologic/bitcask)
|
[](https://godoc.org/github.com/prologic/bitcask)
|
||||||
[](https://sourcegraph.com/github.com/prologic/bitcask?badge)
|
[](https://sourcegraph.com/github.com/prologic/bitcask?badge)
|
||||||
|
|
||||||
A Bitcask (LSM+WAL) Key/Value Store written in Go.
|
A high performance Key/Value store written in [Go](https://golang.org) with a predictable read/write performance and high throughput. Uses a [Bitcask](https://en.wikipedia.org/wiki/Bitcask) on-disk layout (LSM+WAL) similar to [Riak](https://riak.com/).
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* Embeddable
|
* Embeddable (`import "github.com/prologic/bitcask"`)
|
||||||
* Builtin CLI
|
* Builtin CLI (`bitcask`)
|
||||||
* Builtin Redis-compatible server
|
* Builtin Redis-compatible server (`bitcaskd`)
|
||||||
* Predictable read/write performance
|
* Predictable read/write performance
|
||||||
* Low latecny
|
* Low latecny
|
||||||
* High throughput (See: [Performance](README.md#Performance)
|
* High throughput (See: [Performance](README.md#Performance)
|
||||||
@@ -34,16 +34,13 @@ $ go get github.com/prologic/bitcask
|
|||||||
```#!go
|
```#!go
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import "github.com/prologic/bitcask"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/prologic/bitcask"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
db, _ := bitcask.Open("/tmp/db")
|
db, _ := bitcask.Open("/tmp/db")
|
||||||
|
defer db.Close()
|
||||||
db.Set("Hello", []byte("World"))
|
db.Set("Hello", []byte("World"))
|
||||||
db.Close()
|
val, _ := db.Get("hello")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
27
bitcask.go
27
bitcask.go
@@ -1,7 +1,7 @@
|
|||||||
package bitcask
|
package bitcask
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"errors"
|
||||||
"hash/crc32"
|
"hash/crc32"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -15,6 +15,14 @@ import (
|
|||||||
"github.com/prologic/bitcask/internal"
|
"github.com/prologic/bitcask/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrKeyNotFound = errors.New("error: key not found")
|
||||||
|
ErrKeyTooLarge = errors.New("error: key too large")
|
||||||
|
ErrValueTooLarge = errors.New("error: value too large")
|
||||||
|
ErrChecksumFailed = errors.New("error: checksum failed")
|
||||||
|
ErrDatabaseLocked = errors.New("error: database locked")
|
||||||
|
)
|
||||||
|
|
||||||
type Bitcask struct {
|
type Bitcask struct {
|
||||||
*flock.Flock
|
*flock.Flock
|
||||||
|
|
||||||
@@ -49,7 +57,7 @@ func (b *Bitcask) Get(key string) ([]byte, error) {
|
|||||||
|
|
||||||
item, ok := b.keydir.Get(key)
|
item, ok := b.keydir.Get(key)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("error: key not found %s", key)
|
return nil, ErrKeyNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
if item.FileID == b.curr.FileID() {
|
if item.FileID == b.curr.FileID() {
|
||||||
@@ -65,18 +73,23 @@ func (b *Bitcask) Get(key string) ([]byte, error) {
|
|||||||
|
|
||||||
checksum := crc32.ChecksumIEEE(e.Value)
|
checksum := crc32.ChecksumIEEE(e.Value)
|
||||||
if checksum != e.Checksum {
|
if checksum != e.Checksum {
|
||||||
return nil, fmt.Errorf("error: checksum falied %s %d != %d", key, e.Checksum, checksum)
|
return nil, ErrChecksumFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.Value, nil
|
return e.Value, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Bitcask) Has(key string) bool {
|
||||||
|
_, ok := b.keydir.Get(key)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
func (b *Bitcask) Put(key string, value []byte) error {
|
func (b *Bitcask) Put(key string, value []byte) error {
|
||||||
if len(key) > b.config.MaxKeySize {
|
if len(key) > b.config.MaxKeySize {
|
||||||
return fmt.Errorf("error: key too large %d > %d", len(key), b.config.MaxKeySize)
|
return ErrKeyTooLarge
|
||||||
}
|
}
|
||||||
if len(value) > b.config.MaxValueSize {
|
if len(value) > b.config.MaxValueSize {
|
||||||
return fmt.Errorf("error: value too large %d > %d", len(value), b.config.MaxValueSize)
|
return ErrValueTooLarge
|
||||||
}
|
}
|
||||||
|
|
||||||
offset, err := b.put(key, value)
|
offset, err := b.put(key, value)
|
||||||
@@ -346,7 +359,7 @@ func Open(path string, options ...option) (*Bitcask, error) {
|
|||||||
|
|
||||||
bitcask := &Bitcask{
|
bitcask := &Bitcask{
|
||||||
Flock: flock.New(filepath.Join(path, "lock")),
|
Flock: flock.New(filepath.Join(path, "lock")),
|
||||||
config: NewDefaultConfig(),
|
config: newDefaultConfig(),
|
||||||
path: path,
|
path: path,
|
||||||
curr: curr,
|
curr: curr,
|
||||||
keydir: keydir,
|
keydir: keydir,
|
||||||
@@ -369,7 +382,7 @@ func Open(path string, options ...option) (*Bitcask, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !locked {
|
if !locked {
|
||||||
return nil, fmt.Errorf("error: database locked %s", path)
|
return nil, ErrDatabaseLocked
|
||||||
}
|
}
|
||||||
|
|
||||||
return bitcask, nil
|
return bitcask, nil
|
||||||
|
|||||||
111
bitcask_test.go
111
bitcask_test.go
@@ -40,12 +40,36 @@ func TestAll(t *testing.T) {
|
|||||||
assert.Equal([]byte("bar"), val)
|
assert.Equal([]byte("bar"), val)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("Has", func(t *testing.T) {
|
||||||
|
assert.True(db.Has("foo"))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Fold", func(t *testing.T) {
|
||||||
|
var (
|
||||||
|
keys []string
|
||||||
|
values [][]byte
|
||||||
|
)
|
||||||
|
|
||||||
|
err := db.Fold(func(key string) error {
|
||||||
|
value, err := db.Get(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
keys = append(keys, key)
|
||||||
|
values = append(values, value)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal([]string{"foo"}, keys)
|
||||||
|
assert.Equal([][]byte{[]byte("bar")}, values)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("Delete", func(t *testing.T) {
|
t.Run("Delete", func(t *testing.T) {
|
||||||
err := db.Delete("foo")
|
err := db.Delete("foo")
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
_, err = db.Get("foo")
|
_, err = db.Get("foo")
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
assert.Equal("error: key not found foo", err.Error())
|
assert.Equal(ErrKeyNotFound, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Sync", func(t *testing.T) {
|
t.Run("Sync", func(t *testing.T) {
|
||||||
@@ -92,7 +116,7 @@ func TestDeletedKeys(t *testing.T) {
|
|||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
_, err = db.Get("foo")
|
_, err = db.Get("foo")
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
assert.Equal("error: key not found foo", err.Error())
|
assert.Equal(ErrKeyNotFound, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Sync", func(t *testing.T) {
|
t.Run("Sync", func(t *testing.T) {
|
||||||
@@ -120,7 +144,7 @@ func TestDeletedKeys(t *testing.T) {
|
|||||||
t.Run("Get", func(t *testing.T) {
|
t.Run("Get", func(t *testing.T) {
|
||||||
_, err = db.Get("foo")
|
_, err = db.Get("foo")
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
assert.Equal("error: key not found foo", err.Error())
|
assert.Equal(ErrKeyNotFound, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Close", func(t *testing.T) {
|
t.Run("Close", func(t *testing.T) {
|
||||||
@@ -148,7 +172,7 @@ func TestMaxKeySize(t *testing.T) {
|
|||||||
value := []byte("foobar")
|
value := []byte("foobar")
|
||||||
err = db.Put(key, value)
|
err = db.Put(key, value)
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
assert.Equal("error: key too large 17 > 16", err.Error())
|
assert.Equal(ErrKeyTooLarge, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,11 +194,11 @@ func TestMaxValueSize(t *testing.T) {
|
|||||||
value := []byte(strings.Repeat(" ", 17))
|
value := []byte(strings.Repeat(" ", 17))
|
||||||
err = db.Put(key, value)
|
err = db.Put(key, value)
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
assert.Equal("error: value too large 17 > 16", err.Error())
|
assert.Equal(ErrValueTooLarge, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMerge(t *testing.T) {
|
func TestOpenMerge(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
testdir, err := ioutil.TempDir("", "bitcask")
|
testdir, err := ioutil.TempDir("", "bitcask")
|
||||||
@@ -187,7 +211,7 @@ func TestMerge(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
t.Run("Open", func(t *testing.T) {
|
t.Run("Open", func(t *testing.T) {
|
||||||
db, err = Open(testdir, WithMaxDatafileSize(1024))
|
db, err = Open(testdir, WithMaxDatafileSize(32))
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -245,6 +269,77 @@ func TestMerge(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMergeOpen(t *testing.T) {
|
||||||
|
var (
|
||||||
|
db *Bitcask
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
testdir, err := ioutil.TempDir("", "bitcask")
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
t.Run("Setup", func(t *testing.T) {
|
||||||
|
t.Run("Open", func(t *testing.T) {
|
||||||
|
db, err = Open(testdir, WithMaxDatafileSize(32))
|
||||||
|
assert.NoError(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Put", func(t *testing.T) {
|
||||||
|
for i := 0; i < 1024; i++ {
|
||||||
|
err = db.Put(string(i), []byte(strings.Repeat(" ", 1024)))
|
||||||
|
assert.NoError(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Get", func(t *testing.T) {
|
||||||
|
for i := 0; i < 32; i++ {
|
||||||
|
err = db.Put(string(i), []byte(strings.Repeat(" ", 1024)))
|
||||||
|
assert.NoError(err)
|
||||||
|
val, err := db.Get(string(i))
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal([]byte(strings.Repeat(" ", 1024)), val)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Sync", func(t *testing.T) {
|
||||||
|
err = db.Sync()
|
||||||
|
assert.NoError(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Close", func(t *testing.T) {
|
||||||
|
err = db.Close()
|
||||||
|
assert.NoError(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Merge", func(t *testing.T) {
|
||||||
|
t.Run("Merge", func(t *testing.T) {
|
||||||
|
err = Merge(testdir, true)
|
||||||
|
assert.NoError(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Open", func(t *testing.T) {
|
||||||
|
db, err = Open(testdir)
|
||||||
|
assert.NoError(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Get", func(t *testing.T) {
|
||||||
|
for i := 0; i < 32; i++ {
|
||||||
|
val, err := db.Get(string(i))
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal([]byte(strings.Repeat(" ", 1024)), val)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Close", func(t *testing.T) {
|
||||||
|
err = db.Close()
|
||||||
|
assert.NoError(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestConcurrent(t *testing.T) {
|
func TestConcurrent(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
db *Bitcask
|
db *Bitcask
|
||||||
@@ -387,7 +482,7 @@ func TestLocking(t *testing.T) {
|
|||||||
|
|
||||||
_, err = Open(testdir)
|
_, err = Open(testdir)
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
assert.Equal(fmt.Sprintf("error: database locked %s", testdir), err.Error())
|
assert.Equal(ErrDatabaseLocked, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
type benchmarkTestCase struct {
|
type benchmarkTestCase struct {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ type config struct {
|
|||||||
MaxValueSize int
|
MaxValueSize int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultConfig() *config {
|
func newDefaultConfig() *config {
|
||||||
return &config{
|
return &config{
|
||||||
MaxDatafileSize: DefaultMaxDatafileSize,
|
MaxDatafileSize: DefaultMaxDatafileSize,
|
||||||
MaxKeySize: DefaultMaxKeySize,
|
MaxKeySize: DefaultMaxKeySize,
|
||||||
|
|||||||
Reference in New Issue
Block a user