Add pushover notifications, this should be a super basic MVP
This commit is contained in:
parent
ed13a5994f
commit
d9917ab8b0
505 changed files with 195741 additions and 9 deletions
4
go.mod
4
go.mod
|
@ -5,5 +5,7 @@ go 1.16
|
|||
require (
|
||||
github.com/dgraph-io/badger v1.6.2
|
||||
github.com/dgraph-io/ristretto v0.0.4-0.20210122082011-bb5d392ed82d // indirect
|
||||
github.com/google/uuid v1.2.0 // indirect
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/gregdel/pushover v1.1.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
)
|
||||
|
|
4
go.sum
4
go.sum
|
@ -31,6 +31,8 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
|
|||
github.com/google/go-dap v0.2.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ=
|
||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gregdel/pushover v1.1.0 h1:dwHyvrcpZCOS9V1fAnKPaGRRI5OC55cVaKhMybqNsKQ=
|
||||
github.com/gregdel/pushover v1.1.0/go.mod h1:EcaO66Nn1StkpEm1iKtBTV3d2A16SoMsVER1PthX7to=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
|
@ -51,6 +53,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
|
|
68
main.go
68
main.go
|
@ -3,15 +3,19 @@ package main
|
|||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.0xdad.com/tblyler/meditime/config"
|
||||
"git.0xdad.com/tblyler/meditime/db"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gregdel/pushover"
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
func errLog(messages ...interface{}) {
|
||||
|
@ -25,7 +29,9 @@ func log(messages ...interface{}) {
|
|||
func help() {
|
||||
}
|
||||
|
||||
func run(b *db.Badger) error {
|
||||
func run(ctx context.Context, b *db.Badger, pushoverClient *pushover.Pushover) error {
|
||||
cron := cron.New()
|
||||
|
||||
users, err := b.ListUsers()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -38,13 +44,49 @@ func run(b *db.Badger) error {
|
|||
}
|
||||
|
||||
for _, medication := range medications {
|
||||
// FIXME do the stuff
|
||||
fmt.Println(user)
|
||||
fmt.Println(medication)
|
||||
_, err = cron.AddFunc(medication.IntervalCrontab, func() {
|
||||
for _, device := range medication.IntervalPushoverDevices {
|
||||
deviceToken, ok := user.PushoverDeviceTokens[device]
|
||||
if !ok {
|
||||
errLog(fmt.Sprintf(
|
||||
"invalid device name %s for id medication %s for id user %s",
|
||||
device,
|
||||
medication.ID.String(),
|
||||
user.ID.String(),
|
||||
))
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := pushoverClient.SendMessage(
|
||||
&pushover.Message{
|
||||
Message: fmt.Sprintf("take %d dose(s) of %s", medication.IntervalQuantity, medication.Name),
|
||||
Priority: pushover.PriorityEmergency,
|
||||
Retry: time.Minute * 5,
|
||||
Expire: time.Hour * 24,
|
||||
},
|
||||
pushover.NewRecipient(deviceToken),
|
||||
)
|
||||
if err != nil {
|
||||
errLog(fmt.Sprintf(
|
||||
"failed to send message to id user's (%s) device (%s): %v",
|
||||
user.ID.String(),
|
||||
device,
|
||||
err,
|
||||
))
|
||||
}
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add medication ID %s to cron: %w", medication.ID.String(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
cron.Start()
|
||||
<-ctx.Done()
|
||||
<-cron.Stop().Done()
|
||||
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
@ -55,6 +97,9 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
defer cancel()
|
||||
|
||||
err := func() error {
|
||||
inputScanner := bufio.NewScanner(os.Stdin)
|
||||
config := config.Env{}
|
||||
|
@ -64,6 +109,11 @@ func main() {
|
|||
return err
|
||||
}
|
||||
|
||||
pushoverAPIToken, err := config.PushoverAPIToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := db.NewBadger(badgerPath)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -71,9 +121,11 @@ func main() {
|
|||
|
||||
defer b.Close()
|
||||
|
||||
pushoverClient := pushover.New(pushoverAPIToken)
|
||||
|
||||
switch os.Args[1] {
|
||||
case "run":
|
||||
return run(b)
|
||||
return run(ctx, b, pushoverClient)
|
||||
|
||||
case "user":
|
||||
if lenArgs < 3 {
|
||||
|
@ -193,12 +245,12 @@ func main() {
|
|||
return fmt.Errorf("failed to get interval quantity from STDIN prompt: %w", inputScanner.Err())
|
||||
}
|
||||
|
||||
fmt.Print("interval pushover device token id: ")
|
||||
fmt.Print("interval pushover device token name: ")
|
||||
inputScanner.Scan()
|
||||
|
||||
intervalPushoverDevice := string(bytes.TrimSpace(inputScanner.Bytes()))
|
||||
if _, ok := user.PushoverDeviceTokens[intervalPushoverDevice]; !ok {
|
||||
return fmt.Errorf("the '%s' pushover device token doesn't exist for user %s", intervalPushoverDevice, user.Name)
|
||||
return fmt.Errorf("the '%s' pushover device token name doesn't exist for user %s", intervalPushoverDevice, user.Name)
|
||||
}
|
||||
|
||||
medication := &db.Medication{
|
||||
|
|
1
vendor/github.com/AndreasBriese/bbloom/.travis.yml
generated
vendored
Normal file
1
vendor/github.com/AndreasBriese/bbloom/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
language: go
|
35
vendor/github.com/AndreasBriese/bbloom/LICENSE
generated
vendored
Normal file
35
vendor/github.com/AndreasBriese/bbloom/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
bbloom.go
|
||||
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2014 Andreas Briese, eduToolbox@Bri-C GmbH, Sarstedt
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
siphash.go
|
||||
|
||||
// https://github.com/dchest/siphash
|
||||
//
|
||||
// Written in 2012 by Dmitry Chestnykh.
|
||||
//
|
||||
// To the extent possible under law, the author have dedicated all copyright
|
||||
// and related and neighboring rights to this software to the public domain
|
||||
// worldwide. This software is distributed without any warranty.
|
||||
// http://creativecommons.org/publicdomain/zero/1.0/
|
||||
//
|
||||
// Package siphash implements SipHash-2-4, a fast short-input PRF
|
||||
// created by Jean-Philippe Aumasson and Daniel J. Bernstein.
|
131
vendor/github.com/AndreasBriese/bbloom/README.md
generated
vendored
Normal file
131
vendor/github.com/AndreasBriese/bbloom/README.md
generated
vendored
Normal file
|
@ -0,0 +1,131 @@
|
|||
## bbloom: a bitset Bloom filter for go/golang
|
||||
===
|
||||
|
||||
[](http://travis-ci.org/AndreasBriese/bbloom)
|
||||
|
||||
package implements a fast bloom filter with real 'bitset' and JSONMarshal/JSONUnmarshal to store/reload the Bloom filter.
|
||||
|
||||
NOTE: the package uses unsafe.Pointer to set and read the bits from the bitset. If you're uncomfortable with using the unsafe package, please consider using my bloom filter package at github.com/AndreasBriese/bloom
|
||||
|
||||
===
|
||||
|
||||
changelog 11/2015: new thread safe methods AddTS(), HasTS(), AddIfNotHasTS() following a suggestion from Srdjan Marinovic (github @a-little-srdjan), who used this to code a bloomfilter cache.
|
||||
|
||||
This bloom filter was developed to strengthen a website-log database and was tested and optimized for this log-entry mask: "2014/%02i/%02i %02i:%02i:%02i /info.html".
|
||||
Nonetheless bbloom should work with any other form of entries.
|
||||
|
||||
~~Hash function is a modified Berkeley DB sdbm hash (to optimize for smaller strings). sdbm http://www.cse.yorku.ca/~oz/hash.html~~
|
||||
|
||||
Found sipHash (SipHash-2-4, a fast short-input PRF created by Jean-Philippe Aumasson and Daniel J. Bernstein.) to be about as fast. sipHash had been ported by Dimtry Chestnyk to Go (github.com/dchest/siphash )
|
||||
|
||||
Minimum hashset size is: 512 ([4]uint64; will be set automatically).
|
||||
|
||||
###install
|
||||
|
||||
```sh
|
||||
go get github.com/AndreasBriese/bbloom
|
||||
```
|
||||
|
||||
###test
|
||||
+ change to folder ../bbloom
|
||||
+ create wordlist in file "words.txt" (you might use `python permut.py`)
|
||||
+ run 'go test -bench=.' within the folder
|
||||
|
||||
```go
|
||||
go test -bench=.
|
||||
```
|
||||
|
||||
~~If you've installed the GOCONVEY TDD-framework http://goconvey.co/ you can run the tests automatically.~~
|
||||
|
||||
using go's testing framework now (have in mind that the op timing is related to 65536 operations of Add, Has, AddIfNotHas respectively)
|
||||
|
||||
### usage
|
||||
|
||||
after installation add
|
||||
|
||||
```go
|
||||
import (
|
||||
...
|
||||
"github.com/AndreasBriese/bbloom"
|
||||
...
|
||||
)
|
||||
```
|
||||
|
||||
at your header. In the program use
|
||||
|
||||
```go
|
||||
// create a bloom filter for 65536 items and 1 % wrong-positive ratio
|
||||
bf := bbloom.New(float64(1<<16), float64(0.01))
|
||||
|
||||
// or
|
||||
// create a bloom filter with 650000 for 65536 items and 7 locs per hash explicitly
|
||||
// bf = bbloom.New(float64(650000), float64(7))
|
||||
// or
|
||||
bf = bbloom.New(650000.0, 7.0)
|
||||
|
||||
// add one item
|
||||
bf.Add([]byte("butter"))
|
||||
|
||||
// Number of elements added is exposed now
|
||||
// Note: ElemNum will not be included in JSON export (for compatability to older version)
|
||||
nOfElementsInFilter := bf.ElemNum
|
||||
|
||||
// check if item is in the filter
|
||||
isIn := bf.Has([]byte("butter")) // should be true
|
||||
isNotIn := bf.Has([]byte("Butter")) // should be false
|
||||
|
||||
// 'add only if item is new' to the bloomfilter
|
||||
added := bf.AddIfNotHas([]byte("butter")) // should be false because 'butter' is already in the set
|
||||
added = bf.AddIfNotHas([]byte("buTTer")) // should be true because 'buTTer' is new
|
||||
|
||||
// thread safe versions for concurrent use: AddTS, HasTS, AddIfNotHasTS
|
||||
// add one item
|
||||
bf.AddTS([]byte("peanutbutter"))
|
||||
// check if item is in the filter
|
||||
isIn = bf.HasTS([]byte("peanutbutter")) // should be true
|
||||
isNotIn = bf.HasTS([]byte("peanutButter")) // should be false
|
||||
// 'add only if item is new' to the bloomfilter
|
||||
added = bf.AddIfNotHasTS([]byte("butter")) // should be false because 'peanutbutter' is already in the set
|
||||
added = bf.AddIfNotHasTS([]byte("peanutbuTTer")) // should be true because 'penutbuTTer' is new
|
||||
|
||||
// convert to JSON ([]byte)
|
||||
Json := bf.JSONMarshal()
|
||||
|
||||
// bloomfilters Mutex is exposed for external un-/locking
|
||||
// i.e. mutex lock while doing JSON conversion
|
||||
bf.Mtx.Lock()
|
||||
Json = bf.JSONMarshal()
|
||||
bf.Mtx.Unlock()
|
||||
|
||||
// restore a bloom filter from storage
|
||||
bfNew := bbloom.JSONUnmarshal(Json)
|
||||
|
||||
isInNew := bfNew.Has([]byte("butter")) // should be true
|
||||
isNotInNew := bfNew.Has([]byte("Butter")) // should be false
|
||||
|
||||
```
|
||||
|
||||
to work with the bloom filter.
|
||||
|
||||
### why 'fast'?
|
||||
|
||||
It's about 3 times faster than William Fitzgeralds bitset bloom filter https://github.com/willf/bloom . And it is about so fast as my []bool set variant for Boom filters (see https://github.com/AndreasBriese/bloom ) but having a 8times smaller memory footprint:
|
||||
|
||||
|
||||
Bloom filter (filter size 524288, 7 hashlocs)
|
||||
github.com/AndreasBriese/bbloom 'Add' 65536 items (10 repetitions): 6595800 ns (100 ns/op)
|
||||
github.com/AndreasBriese/bbloom 'Has' 65536 items (10 repetitions): 5986600 ns (91 ns/op)
|
||||
github.com/AndreasBriese/bloom 'Add' 65536 items (10 repetitions): 6304684 ns (96 ns/op)
|
||||
github.com/AndreasBriese/bloom 'Has' 65536 items (10 repetitions): 6568663 ns (100 ns/op)
|
||||
|
||||
github.com/willf/bloom 'Add' 65536 items (10 repetitions): 24367224 ns (371 ns/op)
|
||||
github.com/willf/bloom 'Test' 65536 items (10 repetitions): 21881142 ns (333 ns/op)
|
||||
github.com/dataence/bloom/standard 'Add' 65536 items (10 repetitions): 23041644 ns (351 ns/op)
|
||||
github.com/dataence/bloom/standard 'Check' 65536 items (10 repetitions): 19153133 ns (292 ns/op)
|
||||
github.com/cabello/bloom 'Add' 65536 items (10 repetitions): 131921507 ns (2012 ns/op)
|
||||
github.com/cabello/bloom 'Contains' 65536 items (10 repetitions): 131108962 ns (2000 ns/op)
|
||||
|
||||
(on MBPro15 OSX10.8.5 i7 4Core 2.4Ghz)
|
||||
|
||||
|
||||
With 32bit bloom filters (bloom32) using modified sdbm, bloom32 does hashing with only 2 bit shifts, one xor and one substraction per byte. smdb is about as fast as fnv64a but gives less collisions with the dataset (see mask above). bloom.New(float64(10 * 1<<16),float64(7)) populated with 1<<16 random items from the dataset (see above) and tested against the rest results in less than 0.05% collisions.
|
284
vendor/github.com/AndreasBriese/bbloom/bbloom.go
generated
vendored
Normal file
284
vendor/github.com/AndreasBriese/bbloom/bbloom.go
generated
vendored
Normal file
|
@ -0,0 +1,284 @@
|
|||
// The MIT License (MIT)
|
||||
// Copyright (c) 2014 Andreas Briese, eduToolbox@Bri-C GmbH, Sarstedt
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// 2019/08/25 code revision to reduce unsafe use
|
||||
// Parts are adopted from the fork at ipfs/bbloom after performance rev by
|
||||
// Steve Allen (https://github.com/Stebalien)
|
||||
// (see https://github.com/ipfs/bbloom/blob/master/bbloom.go)
|
||||
// -> func Has
|
||||
// -> func set
|
||||
// -> func add
|
||||
|
||||
package bbloom
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"math"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// helper
|
||||
// not needed anymore by Set
|
||||
// var mask = []uint8{1, 2, 4, 8, 16, 32, 64, 128}
|
||||
|
||||
func getSize(ui64 uint64) (size uint64, exponent uint64) {
|
||||
if ui64 < uint64(512) {
|
||||
ui64 = uint64(512)
|
||||
}
|
||||
size = uint64(1)
|
||||
for size < ui64 {
|
||||
size <<= 1
|
||||
exponent++
|
||||
}
|
||||
return size, exponent
|
||||
}
|
||||
|
||||
func calcSizeByWrongPositives(numEntries, wrongs float64) (uint64, uint64) {
|
||||
size := -1 * numEntries * math.Log(wrongs) / math.Pow(float64(0.69314718056), 2)
|
||||
locs := math.Ceil(float64(0.69314718056) * size / numEntries)
|
||||
return uint64(size), uint64(locs)
|
||||
}
|
||||
|
||||
// New
|
||||
// returns a new bloomfilter
|
||||
func New(params ...float64) (bloomfilter Bloom) {
|
||||
var entries, locs uint64
|
||||
if len(params) == 2 {
|
||||
if params[1] < 1 {
|
||||
entries, locs = calcSizeByWrongPositives(params[0], params[1])
|
||||
} else {
|
||||
entries, locs = uint64(params[0]), uint64(params[1])
|
||||
}
|
||||
} else {
|
||||
log.Fatal("usage: New(float64(number_of_entries), float64(number_of_hashlocations)) i.e. New(float64(1000), float64(3)) or New(float64(number_of_entries), float64(number_of_hashlocations)) i.e. New(float64(1000), float64(0.03))")
|
||||
}
|
||||
size, exponent := getSize(uint64(entries))
|
||||
bloomfilter = Bloom{
|
||||
Mtx: &sync.Mutex{},
|
||||
sizeExp: exponent,
|
||||
size: size - 1,
|
||||
setLocs: locs,
|
||||
shift: 64 - exponent,
|
||||
}
|
||||
bloomfilter.Size(size)
|
||||
return bloomfilter
|
||||
}
|
||||
|
||||
// NewWithBoolset
|
||||
// takes a []byte slice and number of locs per entry
|
||||
// returns the bloomfilter with a bitset populated according to the input []byte
|
||||
func NewWithBoolset(bs *[]byte, locs uint64) (bloomfilter Bloom) {
|
||||
bloomfilter = New(float64(len(*bs)<<3), float64(locs))
|
||||
for i, b := range *bs {
|
||||
*(*uint8)(unsafe.Pointer(uintptr(unsafe.Pointer(&bloomfilter.bitset[0])) + uintptr(i))) = b
|
||||
}
|
||||
return bloomfilter
|
||||
}
|
||||
|
||||
// bloomJSONImExport
|
||||
// Im/Export structure used by JSONMarshal / JSONUnmarshal
|
||||
type bloomJSONImExport struct {
|
||||
FilterSet []byte
|
||||
SetLocs uint64
|
||||
}
|
||||
|
||||
// JSONUnmarshal
|
||||
// takes JSON-Object (type bloomJSONImExport) as []bytes
|
||||
// returns Bloom object
|
||||
func JSONUnmarshal(dbData []byte) Bloom {
|
||||
bloomImEx := bloomJSONImExport{}
|
||||
json.Unmarshal(dbData, &bloomImEx)
|
||||
buf := bytes.NewBuffer(bloomImEx.FilterSet)
|
||||
bs := buf.Bytes()
|
||||
bf := NewWithBoolset(&bs, bloomImEx.SetLocs)
|
||||
return bf
|
||||
}
|
||||
|
||||
//
|
||||
// Bloom filter
|
||||
type Bloom struct {
|
||||
Mtx *sync.Mutex
|
||||
ElemNum uint64
|
||||
bitset []uint64
|
||||
sizeExp uint64
|
||||
size uint64
|
||||
setLocs uint64
|
||||
shift uint64
|
||||
}
|
||||
|
||||
// <--- http://www.cse.yorku.ca/~oz/hash.html
|
||||
// modified Berkeley DB Hash (32bit)
|
||||
// hash is casted to l, h = 16bit fragments
|
||||
// func (bl Bloom) absdbm(b *[]byte) (l, h uint64) {
|
||||
// hash := uint64(len(*b))
|
||||
// for _, c := range *b {
|
||||
// hash = uint64(c) + (hash << 6) + (hash << bl.sizeExp) - hash
|
||||
// }
|
||||
// h = hash >> bl.shift
|
||||
// l = hash << bl.shift >> bl.shift
|
||||
// return l, h
|
||||
// }
|
||||
|
||||
// Update: found sipHash of Jean-Philippe Aumasson & Daniel J. Bernstein to be even faster than absdbm()
|
||||
// https://131002.net/siphash/
|
||||
// siphash was implemented for Go by Dmitry Chestnykh https://github.com/dchest/siphash
|
||||
|
||||
// Add
|
||||
// set the bit(s) for entry; Adds an entry to the Bloom filter
|
||||
func (bl *Bloom) Add(entry []byte) {
|
||||
l, h := bl.sipHash(entry)
|
||||
for i := uint64(0); i < bl.setLocs; i++ {
|
||||
bl.set((h + i*l) & bl.size)
|
||||
bl.ElemNum++
|
||||
}
|
||||
}
|
||||
|
||||
// AddTS
|
||||
// Thread safe: Mutex.Lock the bloomfilter for the time of processing the entry
|
||||
func (bl *Bloom) AddTS(entry []byte) {
|
||||
bl.Mtx.Lock()
|
||||
defer bl.Mtx.Unlock()
|
||||
bl.Add(entry)
|
||||
}
|
||||
|
||||
// Has
|
||||
// check if bit(s) for entry is/are set
|
||||
// returns true if the entry was added to the Bloom Filter
|
||||
func (bl Bloom) Has(entry []byte) bool {
|
||||
l, h := bl.sipHash(entry)
|
||||
res := true
|
||||
for i := uint64(0); i < bl.setLocs; i++ {
|
||||
res = res && bl.isSet((h+i*l)&bl.size)
|
||||
// https://github.com/ipfs/bbloom/commit/84e8303a9bfb37b2658b85982921d15bbb0fecff
|
||||
// // Branching here (early escape) is not worth it
|
||||
// // This is my conclusion from benchmarks
|
||||
// // (prevents loop unrolling)
|
||||
// switch bl.IsSet((h + i*l) & bl.size) {
|
||||
// case false:
|
||||
// return false
|
||||
// }
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// HasTS
|
||||
// Thread safe: Mutex.Lock the bloomfilter for the time of processing the entry
|
||||
func (bl *Bloom) HasTS(entry []byte) bool {
|
||||
bl.Mtx.Lock()
|
||||
defer bl.Mtx.Unlock()
|
||||
return bl.Has(entry)
|
||||
}
|
||||
|
||||
// AddIfNotHas
|
||||
// Only Add entry if it's not present in the bloomfilter
|
||||
// returns true if entry was added
|
||||
// returns false if entry was allready registered in the bloomfilter
|
||||
func (bl Bloom) AddIfNotHas(entry []byte) (added bool) {
|
||||
if bl.Has(entry) {
|
||||
return added
|
||||
}
|
||||
bl.Add(entry)
|
||||
return true
|
||||
}
|
||||
|
||||
// AddIfNotHasTS
|
||||
// Tread safe: Only Add entry if it's not present in the bloomfilter
|
||||
// returns true if entry was added
|
||||
// returns false if entry was allready registered in the bloomfilter
|
||||
func (bl *Bloom) AddIfNotHasTS(entry []byte) (added bool) {
|
||||
bl.Mtx.Lock()
|
||||
defer bl.Mtx.Unlock()
|
||||
return bl.AddIfNotHas(entry)
|
||||
}
|
||||
|
||||
// Size
|
||||
// make Bloom filter with as bitset of size sz
|
||||
func (bl *Bloom) Size(sz uint64) {
|
||||
bl.bitset = make([]uint64, sz>>6)
|
||||
}
|
||||
|
||||
// Clear
|
||||
// resets the Bloom filter
|
||||
func (bl *Bloom) Clear() {
|
||||
bs := bl.bitset
|
||||
for i := range bs {
|
||||
bs[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Set
|
||||
// set the bit[idx] of bitsit
|
||||
func (bl *Bloom) set(idx uint64) {
|
||||
// ommit unsafe
|
||||
// *(*uint8)(unsafe.Pointer(uintptr(unsafe.Pointer(&bl.bitset[idx>>6])) + uintptr((idx%64)>>3))) |= mask[idx%8]
|
||||
bl.bitset[idx>>6] |= 1 << (idx % 64)
|
||||
}
|
||||
|
||||
// IsSet
|
||||
// check if bit[idx] of bitset is set
|
||||
// returns true/false
|
||||
func (bl *Bloom) isSet(idx uint64) bool {
|
||||
// ommit unsafe
|
||||
// return (((*(*uint8)(unsafe.Pointer(uintptr(unsafe.Pointer(&bl.bitset[idx>>6])) + uintptr((idx%64)>>3)))) >> (idx % 8)) & 1) == 1
|
||||
return bl.bitset[idx>>6]&(1<<(idx%64)) != 0
|
||||
}
|
||||
|
||||
// JSONMarshal
|
||||
// returns JSON-object (type bloomJSONImExport) as []byte
|
||||
func (bl Bloom) JSONMarshal() []byte {
|
||||
bloomImEx := bloomJSONImExport{}
|
||||
bloomImEx.SetLocs = uint64(bl.setLocs)
|
||||
bloomImEx.FilterSet = make([]byte, len(bl.bitset)<<3)
|
||||
for i := range bloomImEx.FilterSet {
|
||||
bloomImEx.FilterSet[i] = *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&bl.bitset[0])) + uintptr(i)))
|
||||
}
|
||||
data, err := json.Marshal(bloomImEx)
|
||||
if err != nil {
|
||||
log.Fatal("json.Marshal failed: ", err)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// // alternative hashFn
|
||||
// func (bl Bloom) fnv64a(b *[]byte) (l, h uint64) {
|
||||
// h64 := fnv.New64a()
|
||||
// h64.Write(*b)
|
||||
// hash := h64.Sum64()
|
||||
// h = hash >> 32
|
||||
// l = hash << 32 >> 32
|
||||
// return l, h
|
||||
// }
|
||||
//
|
||||
// // <-- http://partow.net/programming/hashfunctions/index.html
|
||||
// // citation: An algorithm proposed by Donald E. Knuth in The Art Of Computer Programming Volume 3,
|
||||
// // under the topic of sorting and search chapter 6.4.
|
||||
// // modified to fit with boolset-length
|
||||
// func (bl Bloom) DEKHash(b *[]byte) (l, h uint64) {
|
||||
// hash := uint64(len(*b))
|
||||
// for _, c := range *b {
|
||||
// hash = ((hash << 5) ^ (hash >> bl.shift)) ^ uint64(c)
|
||||
// }
|
||||
// h = hash >> bl.shift
|
||||
// l = hash << bl.sizeExp >> bl.sizeExp
|
||||
// return l, h
|
||||
// }
|
225
vendor/github.com/AndreasBriese/bbloom/sipHash.go
generated
vendored
Normal file
225
vendor/github.com/AndreasBriese/bbloom/sipHash.go
generated
vendored
Normal file
|
@ -0,0 +1,225 @@
|
|||
// Written in 2012 by Dmitry Chestnykh.
|
||||
//
|
||||
// To the extent possible under law, the author have dedicated all copyright
|
||||
// and related and neighboring rights to this software to the public domain
|
||||
// worldwide. This software is distributed without any warranty.
|
||||
// http://creativecommons.org/publicdomain/zero/1.0/
|
||||
//
|
||||
// Package siphash implements SipHash-2-4, a fast short-input PRF
|
||||
// created by Jean-Philippe Aumasson and Daniel J. Bernstein.
|
||||
|
||||
package bbloom
|
||||
|
||||
// Hash returns the 64-bit SipHash-2-4 of the given byte slice with two 64-bit
|
||||
// parts of 128-bit key: k0 and k1.
|
||||
func (bl Bloom) sipHash(p []byte) (l, h uint64) {
|
||||
// Initialization.
|
||||
v0 := uint64(8317987320269560794) // k0 ^ 0x736f6d6570736575
|
||||
v1 := uint64(7237128889637516672) // k1 ^ 0x646f72616e646f6d
|
||||
v2 := uint64(7816392314733513934) // k0 ^ 0x6c7967656e657261
|
||||
v3 := uint64(8387220255325274014) // k1 ^ 0x7465646279746573
|
||||
t := uint64(len(p)) << 56
|
||||
|
||||
// Compression.
|
||||
for len(p) >= 8 {
|
||||
|
||||
m := uint64(p[0]) | uint64(p[1])<<8 | uint64(p[2])<<16 | uint64(p[3])<<24 |
|
||||
uint64(p[4])<<32 | uint64(p[5])<<40 | uint64(p[6])<<48 | uint64(p[7])<<56
|
||||
|
||||
v3 ^= m
|
||||
|
||||
// Round 1.
|
||||
v0 += v1
|
||||
v1 = v1<<13 | v1>>51
|
||||
v1 ^= v0
|
||||
v0 = v0<<32 | v0>>32
|
||||
|
||||
v2 += v3
|
||||
v3 = v3<<16 | v3>>48
|
||||
v3 ^= v2
|
||||
|
||||
v0 += v3
|
||||
v3 = v3<<21 | v3>>43
|
||||
v3 ^= v0
|
||||
|
||||
v2 += v1
|
||||
v1 = v1<<17 | v1>>47
|
||||
v1 ^= v2
|
||||
v2 = v2<<32 | v2>>32
|
||||
|
||||
// Round 2.
|
||||
v0 += v1
|
||||
v1 = v1<<13 | v1>>51
|
||||
v1 ^= v0
|
||||
v0 = v0<<32 | v0>>32
|
||||
|
||||
v2 += v3
|
||||
v3 = v3<<16 | v3>>48
|
||||
v3 ^= v2
|
||||
|
||||
v0 += v3
|
||||
v3 = v3<<21 | v3>>43
|
||||
v3 ^= v0
|
||||
|
||||
v2 += v1
|
||||
v1 = v1<<17 | v1>>47
|
||||
v1 ^= v2
|
||||
v2 = v2<<32 | v2>>32
|
||||
|
||||
v0 ^= m
|
||||
p = p[8:]
|
||||
}
|
||||
|
||||
// Compress last block.
|
||||
switch len(p) {
|
||||
case 7:
|
||||
t |= uint64(p[6]) << 48
|
||||
fallthrough
|
||||
case 6:
|
||||
t |= uint64(p[5]) << 40
|
||||
fallthrough
|
||||
case 5:
|
||||
t |= uint64(p[4]) << 32
|
||||
fallthrough
|
||||
case 4:
|
||||
t |= uint64(p[3]) << 24
|
||||
fallthrough
|
||||
case 3:
|
||||
t |= uint64(p[2]) << 16
|
||||
fallthrough
|
||||
case 2:
|
||||
t |= uint64(p[1]) << 8
|
||||
fallthrough
|
||||
case 1:
|
||||
t |= uint64(p[0])
|
||||
}
|
||||
|
||||
v3 ^= t
|
||||
|
||||
// Round 1.
|
||||
v0 += v1
|
||||
v1 = v1<<13 | v1>>51
|
||||
v1 ^= v0
|
||||
v0 = v0<<32 | v0>>32
|
||||
|
||||
v2 += v3
|
||||
v3 = v3<<16 | v3>>48
|
||||
v3 ^= v2
|
||||
|
||||
v0 += v3
|
||||
v3 = v3<<21 | v3>>43
|
||||
v3 ^= v0
|
||||
|
||||
v2 += v1
|
||||
v1 = v1<<17 | v1>>47
|
||||
v1 ^= v2
|
||||
v2 = v2<<32 | v2>>32
|
||||
|
||||
// Round 2.
|
||||
v0 += v1
|
||||
v1 = v1<<13 | v1>>51
|
||||
v1 ^= v0
|
||||
v0 = v0<<32 | v0>>32
|
||||
|
||||
v2 += v3
|
||||
v3 = v3<<16 | v3>>48
|
||||
v3 ^= v2
|
||||
|
||||
v0 += v3
|
||||
v3 = v3<<21 | v3>>43
|
||||
v3 ^= v0
|
||||
|
||||
v2 += v1
|
||||
v1 = v1<<17 | v1>>47
|
||||
v1 ^= v2
|
||||
v2 = v2<<32 | v2>>32
|
||||
|
||||
v0 ^= t
|
||||
|
||||
// Finalization.
|
||||
v2 ^= 0xff
|
||||
|
||||
// Round 1.
|
||||
v0 += v1
|
||||
v1 = v1<<13 | v1>>51
|
||||
v1 ^= v0
|
||||
v0 = v0<<32 | v0>>32
|
||||
|
||||
v2 += v3
|
||||
v3 = v3<<16 | v3>>48
|
||||
v3 ^= v2
|
||||
|
||||
v0 += v3
|
||||
v3 = v3<<21 | v3>>43
|
||||
v3 ^= v0
|
||||
|
||||
v2 += v1
|
||||
v1 = v1<<17 | v1>>47
|
||||
v1 ^= v2
|
||||
v2 = v2<<32 | v2>>32
|
||||
|
||||
// Round 2.
|
||||
v0 += v1
|
||||
v1 = v1<<13 | v1>>51
|
||||
v1 ^= v0
|
||||
v0 = v0<<32 | v0>>32
|
||||
|
||||
v2 += v3
|
||||
v3 = v3<<16 | v3>>48
|
||||
v3 ^= v2
|
||||
|
||||
v0 += v3
|
||||
v3 = v3<<21 | v3>>43
|
||||
v3 ^= v0
|
||||
|
||||
v2 += v1
|
||||
v1 = v1<<17 | v1>>47
|
||||
v1 ^= v2
|
||||
v2 = v2<<32 | v2>>32
|
||||
|
||||
// Round 3.
|
||||
v0 += v1
|
||||
v1 = v1<<13 | v1>>51
|
||||
v1 ^= v0
|
||||
v0 = v0<<32 | v0>>32
|
||||
|
||||
v2 += v3
|
||||
v3 = v3<<16 | v3>>48
|
||||
v3 ^= v2
|
||||
|
||||
v0 += v3
|
||||
v3 = v3<<21 | v3>>43
|
||||
v3 ^= v0
|
||||
|
||||
v2 += v1
|
||||
v1 = v1<<17 | v1>>47
|
||||
v1 ^= v2
|
||||
v2 = v2<<32 | v2>>32
|
||||
|
||||
// Round 4.
|
||||
v0 += v1
|
||||
v1 = v1<<13 | v1>>51
|
||||
v1 ^= v0
|
||||
v0 = v0<<32 | v0>>32
|
||||
|
||||
v2 += v3
|
||||
v3 = v3<<16 | v3>>48
|
||||
v3 ^= v2
|
||||
|
||||
v0 += v3
|
||||
v3 = v3<<21 | v3>>43
|
||||
v3 ^= v0
|
||||
|
||||
v2 += v1
|
||||
v1 = v1<<17 | v1>>47
|
||||
v1 ^= v2
|
||||
v2 = v2<<32 | v2>>32
|
||||
|
||||
// return v0 ^ v1 ^ v2 ^ v3
|
||||
|
||||
hash := v0 ^ v1 ^ v2 ^ v3
|
||||
h = hash >> bl.shift
|
||||
l = hash << bl.shift >> bl.shift
|
||||
return l, h
|
||||
|
||||
}
|
140
vendor/github.com/AndreasBriese/bbloom/words.txt
generated
vendored
Normal file
140
vendor/github.com/AndreasBriese/bbloom/words.txt
generated
vendored
Normal file
|
@ -0,0 +1,140 @@
|
|||
2014/01/01 00:00:00 /info.html
|
||||
2014/01/01 00:00:00 /info.html
|
||||
2014/01/01 00:00:01 /info.html
|
||||
2014/01/01 00:00:02 /info.html
|
||||
2014/01/01 00:00:03 /info.html
|
||||
2014/01/01 00:00:04 /info.html
|
||||
2014/01/01 00:00:05 /info.html
|
||||
2014/01/01 00:00:06 /info.html
|
||||
2014/01/01 00:00:07 /info.html
|
||||
2014/01/01 00:00:08 /info.html
|
||||
2014/01/01 00:00:09 /info.html
|
||||
2014/01/01 00:00:10 /info.html
|
||||
2014/01/01 00:00:11 /info.html
|
||||
2014/01/01 00:00:12 /info.html
|
||||
2014/01/01 00:00:13 /info.html
|
||||
2014/01/01 00:00:14 /info.html
|
||||
2014/01/01 00:00:15 /info.html
|
||||
2014/01/01 00:00:16 /info.html
|
||||
2014/01/01 00:00:17 /info.html
|
||||
2014/01/01 00:00:18 /info.html
|
||||
2014/01/01 00:00:19 /info.html
|
||||
2014/01/01 00:00:20 /info.html
|
||||
2014/01/01 00:00:21 /info.html
|
||||
2014/01/01 00:00:22 /info.html
|
||||
2014/01/01 00:00:23 /info.html
|
||||
2014/01/01 00:00:24 /info.html
|
||||
2014/01/01 00:00:25 /info.html
|
||||
2014/01/01 00:00:26 /info.html
|
||||
2014/01/01 00:00:27 /info.html
|
||||
2014/01/01 00:00:28 /info.html
|
||||
2014/01/01 00:00:29 /info.html
|
||||
2014/01/01 00:00:30 /info.html
|
||||
2014/01/01 00:00:31 /info.html
|
||||
2014/01/01 00:00:32 /info.html
|
||||
2014/01/01 00:00:33 /info.html
|
||||
2014/01/01 00:00:34 /info.html
|
||||
2014/01/01 00:00:35 /info.html
|
||||
2014/01/01 00:00:36 /info.html
|
||||
2014/01/01 00:00:37 /info.html
|
||||
2014/01/01 00:00:38 /info.html
|
||||
2014/01/01 00:00:39 /info.html
|
||||
2014/01/01 00:00:40 /info.html
|
||||
2014/01/01 00:00:41 /info.html
|
||||
2014/01/01 00:00:42 /info.html
|
||||
2014/01/01 00:00:43 /info.html
|
||||
2014/01/01 00:00:44 /info.html
|
||||
2014/01/01 00:00:45 /info.html
|
||||
2014/01/01 00:00:46 /info.html
|
||||
2014/01/01 00:00:47 /info.html
|
||||
2014/01/01 00:00:48 /info.html
|
||||
2014/01/01 00:00:49 /info.html
|
||||
2014/01/01 00:00:50 /info.html
|
||||
2014/01/01 00:00:51 /info.html
|
||||
2014/01/01 00:00:52 /info.html
|
||||
2014/01/01 00:00:53 /info.html
|
||||
2014/01/01 00:00:54 /info.html
|
||||
2014/01/01 00:00:55 /info.html
|
||||
2014/01/01 00:00:56 /info.html
|
||||
2014/01/01 00:00:57 /info.html
|
||||
2014/01/01 00:00:58 /info.html
|
||||
2014/01/01 00:00:59 /info.html
|
||||
2014/01/01 00:01:00 /info.html
|
||||
2014/01/01 00:01:01 /info.html
|
||||
2014/01/01 00:01:02 /info.html
|
||||
2014/01/01 00:01:03 /info.html
|
||||
2014/01/01 00:01:04 /info.html
|
||||
2014/01/01 00:01:05 /info.html
|
||||
2014/01/01 00:01:06 /info.html
|
||||
2014/01/01 00:01:07 /info.html
|
||||
2014/01/01 00:01:08 /info.html
|
||||
2014/01/01 00:01:09 /info.html
|
||||
2014/01/01 00:01:10 /info.html
|
||||
2014/01/01 00:01:11 /info.html
|
||||
2014/01/01 00:01:12 /info.html
|
||||
2014/01/01 00:01:13 /info.html
|
||||
2014/01/01 00:01:14 /info.html
|
||||
2014/01/01 00:01:15 /info.html
|
||||
2014/01/01 00:01:16 /info.html
|
||||
2014/01/01 00:01:17 /info.html
|
||||
2014/01/01 00:01:18 /info.html
|
||||
2014/01/01 00:01:19 /info.html
|
||||
2014/01/01 00:01:20 /info.html
|
||||
2014/01/01 00:01:21 /info.html
|
||||
2014/01/01 00:01:22 /info.html
|
||||
2014/01/01 00:01:23 /info.html
|
||||
2014/01/01 00:01:24 /info.html
|
||||
2014/01/01 00:01:25 /info.html
|
||||
2014/01/01 00:01:26 /info.html
|
||||
2014/01/01 00:01:27 /info.html
|
||||
2014/01/01 00:01:28 /info.html
|
||||
2014/01/01 00:01:29 /info.html
|
||||
2014/01/01 00:01:30 /info.html
|
||||
2014/01/01 00:01:31 /info.html
|
||||
2014/01/01 00:01:32 /info.html
|
||||
2014/01/01 00:01:33 /info.html
|
||||
2014/01/01 00:01:34 /info.html
|
||||
2014/01/01 00:01:35 /info.html
|
||||
2014/01/01 00:01:36 /info.html
|
||||
2014/01/01 00:01:37 /info.html
|
||||
2014/01/01 00:01:38 /info.html
|
||||
2014/01/01 00:01:39 /info.html
|
||||
2014/01/01 00:01:40 /info.html
|
||||
2014/01/01 00:01:41 /info.html
|
||||
2014/01/01 00:01:42 /info.html
|
||||
2014/01/01 00:01:43 /info.html
|
||||
2014/01/01 00:01:44 /info.html
|
||||
2014/01/01 00:01:45 /info.html
|
||||
2014/01/01 00:01:46 /info.html
|
||||
2014/01/01 00:01:47 /info.html
|
||||
2014/01/01 00:01:48 /info.html
|
||||
2014/01/01 00:01:49 /info.html
|
||||
2014/01/01 00:01:50 /info.html
|
||||
2014/01/01 00:01:51 /info.html
|
||||
2014/01/01 00:01:52 /info.html
|
||||
2014/01/01 00:01:53 /info.html
|
||||
2014/01/01 00:01:54 /info.html
|
||||
2014/01/01 00:01:55 /info.html
|
||||
2014/01/01 00:01:56 /info.html
|
||||
2014/01/01 00:01:57 /info.html
|
||||
2014/01/01 00:01:58 /info.html
|
||||
2014/01/01 00:01:59 /info.html
|
||||
2014/01/01 00:02:00 /info.html
|
||||
2014/01/01 00:02:01 /info.html
|
||||
2014/01/01 00:02:02 /info.html
|
||||
2014/01/01 00:02:03 /info.html
|
||||
2014/01/01 00:02:04 /info.html
|
||||
2014/01/01 00:02:05 /info.html
|
||||
2014/01/01 00:02:06 /info.html
|
||||
2014/01/01 00:02:07 /info.html
|
||||
2014/01/01 00:02:08 /info.html
|
||||
2014/01/01 00:02:09 /info.html
|
||||
2014/01/01 00:02:10 /info.html
|
||||
2014/01/01 00:02:11 /info.html
|
||||
2014/01/01 00:02:12 /info.html
|
||||
2014/01/01 00:02:13 /info.html
|
||||
2014/01/01 00:02:14 /info.html
|
||||
2014/01/01 00:02:15 /info.html
|
||||
2014/01/01 00:02:16 /info.html
|
||||
2014/01/01 00:02:17 /info.html
|
||||
2014/01/01 00:02:18 /info.html
|
22
vendor/github.com/cespare/xxhash/LICENSE.txt
generated
vendored
Normal file
22
vendor/github.com/cespare/xxhash/LICENSE.txt
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
Copyright (c) 2016 Caleb Spare
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
50
vendor/github.com/cespare/xxhash/README.md
generated
vendored
Normal file
50
vendor/github.com/cespare/xxhash/README.md
generated
vendored
Normal file
|
@ -0,0 +1,50 @@
|
|||
# xxhash
|
||||
|
||||
[](https://godoc.org/github.com/cespare/xxhash)
|
||||
|
||||
xxhash is a Go implementation of the 64-bit
|
||||
[xxHash](http://cyan4973.github.io/xxHash/) algorithm, XXH64. This is a
|
||||
high-quality hashing algorithm that is much faster than anything in the Go
|
||||
standard library.
|
||||
|
||||
The API is very small, taking its cue from the other hashing packages in the
|
||||
standard library:
|
||||
|
||||
$ go doc github.com/cespare/xxhash !
|
||||
package xxhash // import "github.com/cespare/xxhash"
|
||||
|
||||
Package xxhash implements the 64-bit variant of xxHash (XXH64) as described
|
||||
at http://cyan4973.github.io/xxHash/.
|
||||
|
||||
func New() hash.Hash64
|
||||
func Sum64(b []byte) uint64
|
||||
func Sum64String(s string) uint64
|
||||
|
||||
This implementation provides a fast pure-Go implementation and an even faster
|
||||
assembly implementation for amd64.
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Here are some quick benchmarks comparing the pure-Go and assembly
|
||||
implementations of Sum64 against another popular Go XXH64 implementation,
|
||||
[github.com/OneOfOne/xxhash](https://github.com/OneOfOne/xxhash):
|
||||
|
||||
| input size | OneOfOne | cespare (purego) | cespare |
|
||||
| --- | --- | --- | --- |
|
||||
| 5 B | 416 MB/s | 720 MB/s | 872 MB/s |
|
||||
| 100 B | 3980 MB/s | 5013 MB/s | 5252 MB/s |
|
||||
| 4 KB | 12727 MB/s | 12999 MB/s | 13026 MB/s |
|
||||
| 10 MB | 9879 MB/s | 10775 MB/s | 10913 MB/s |
|
||||
|
||||
These numbers were generated with:
|
||||
|
||||
```
|
||||
$ go test -benchtime 10s -bench '/OneOfOne,'
|
||||
$ go test -tags purego -benchtime 10s -bench '/xxhash,'
|
||||
$ go test -benchtime 10s -bench '/xxhash,'
|
||||
```
|
||||
|
||||
## Projects using this package
|
||||
|
||||
- [InfluxDB](https://github.com/influxdata/influxdb)
|
||||
- [Prometheus](https://github.com/prometheus/prometheus)
|
6
vendor/github.com/cespare/xxhash/go.mod
generated
vendored
Normal file
6
vendor/github.com/cespare/xxhash/go.mod
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
module github.com/cespare/xxhash
|
||||
|
||||
require (
|
||||
github.com/OneOfOne/xxhash v1.2.2
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72
|
||||
)
|
4
vendor/github.com/cespare/xxhash/go.sum
generated
vendored
Normal file
4
vendor/github.com/cespare/xxhash/go.sum
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
14
vendor/github.com/cespare/xxhash/rotate.go
generated
vendored
Normal file
14
vendor/github.com/cespare/xxhash/rotate.go
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
// +build !go1.9
|
||||
|
||||
package xxhash
|
||||
|
||||
// TODO(caleb): After Go 1.10 comes out, remove this fallback code.
|
||||
|
||||
func rol1(x uint64) uint64 { return (x << 1) | (x >> (64 - 1)) }
|
||||
func rol7(x uint64) uint64 { return (x << 7) | (x >> (64 - 7)) }
|
||||
func rol11(x uint64) uint64 { return (x << 11) | (x >> (64 - 11)) }
|
||||
func rol12(x uint64) uint64 { return (x << 12) | (x >> (64 - 12)) }
|
||||
func rol18(x uint64) uint64 { return (x << 18) | (x >> (64 - 18)) }
|
||||
func rol23(x uint64) uint64 { return (x << 23) | (x >> (64 - 23)) }
|
||||
func rol27(x uint64) uint64 { return (x << 27) | (x >> (64 - 27)) }
|
||||
func rol31(x uint64) uint64 { return (x << 31) | (x >> (64 - 31)) }
|
14
vendor/github.com/cespare/xxhash/rotate19.go
generated
vendored
Normal file
14
vendor/github.com/cespare/xxhash/rotate19.go
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
// +build go1.9
|
||||
|
||||
package xxhash
|
||||
|
||||
import "math/bits"
|
||||
|
||||
func rol1(x uint64) uint64 { return bits.RotateLeft64(x, 1) }
|
||||
func rol7(x uint64) uint64 { return bits.RotateLeft64(x, 7) }
|
||||
func rol11(x uint64) uint64 { return bits.RotateLeft64(x, 11) }
|
||||
func rol12(x uint64) uint64 { return bits.RotateLeft64(x, 12) }
|
||||
func rol18(x uint64) uint64 { return bits.RotateLeft64(x, 18) }
|
||||
func rol23(x uint64) uint64 { return bits.RotateLeft64(x, 23) }
|
||||
func rol27(x uint64) uint64 { return bits.RotateLeft64(x, 27) }
|
||||
func rol31(x uint64) uint64 { return bits.RotateLeft64(x, 31) }
|
168
vendor/github.com/cespare/xxhash/xxhash.go
generated
vendored
Normal file
168
vendor/github.com/cespare/xxhash/xxhash.go
generated
vendored
Normal file
|
@ -0,0 +1,168 @@
|
|||
// Package xxhash implements the 64-bit variant of xxHash (XXH64) as described
|
||||
// at http://cyan4973.github.io/xxHash/.
|
||||
package xxhash
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"hash"
|
||||
)
|
||||
|
||||
const (
|
||||
prime1 uint64 = 11400714785074694791
|
||||
prime2 uint64 = 14029467366897019727
|
||||
prime3 uint64 = 1609587929392839161
|
||||
prime4 uint64 = 9650029242287828579
|
||||
prime5 uint64 = 2870177450012600261
|
||||
)
|
||||
|
||||
// NOTE(caleb): I'm using both consts and vars of the primes. Using consts where
|
||||
// possible in the Go code is worth a small (but measurable) performance boost
|
||||
// by avoiding some MOVQs. Vars are needed for the asm and also are useful for
|
||||
// convenience in the Go code in a few places where we need to intentionally
|
||||
// avoid constant arithmetic (e.g., v1 := prime1 + prime2 fails because the
|
||||
// result overflows a uint64).
|
||||
var (
|
||||
prime1v = prime1
|
||||
prime2v = prime2
|
||||
prime3v = prime3
|
||||
prime4v = prime4
|
||||
prime5v = prime5
|
||||
)
|
||||
|
||||
type xxh struct {
|
||||
v1 uint64
|
||||
v2 uint64
|
||||
v3 uint64
|
||||
v4 uint64
|
||||
total int
|
||||
mem [32]byte
|
||||
n int // how much of mem is used
|
||||
}
|
||||
|
||||
// New creates a new hash.Hash64 that implements the 64-bit xxHash algorithm.
|
||||
func New() hash.Hash64 {
|
||||
var x xxh
|
||||
x.Reset()
|
||||
return &x
|
||||
}
|
||||
|
||||
func (x *xxh) Reset() {
|
||||
x.n = 0
|
||||
x.total = 0
|
||||
x.v1 = prime1v + prime2
|
||||
x.v2 = prime2
|
||||
x.v3 = 0
|
||||
x.v4 = -prime1v
|
||||
}
|
||||
|
||||
func (x *xxh) Size() int { return 8 }
|
||||
func (x *xxh) BlockSize() int { return 32 }
|
||||
|
||||
// Write adds more data to x. It always returns len(b), nil.
|
||||
func (x *xxh) Write(b []byte) (n int, err error) {
|
||||
n = len(b)
|
||||
x.total += len(b)
|
||||
|
||||
if x.n+len(b) < 32 {
|
||||
// This new data doesn't even fill the current block.
|
||||
copy(x.mem[x.n:], b)
|
||||
x.n += len(b)
|
||||
return
|
||||
}
|
||||
|
||||
if x.n > 0 {
|
||||
// Finish off the partial block.
|
||||
copy(x.mem[x.n:], b)
|
||||
x.v1 = round(x.v1, u64(x.mem[0:8]))
|
||||
x.v2 = round(x.v2, u64(x.mem[8:16]))
|
||||
x.v3 = round(x.v3, u64(x.mem[16:24]))
|
||||
x.v4 = round(x.v4, u64(x.mem[24:32]))
|
||||
b = b[32-x.n:]
|
||||
x.n = 0
|
||||
}
|
||||
|
||||
if len(b) >= 32 {
|
||||
// One or more full blocks left.
|
||||
b = writeBlocks(x, b)
|
||||
}
|
||||
|
||||
// Store any remaining partial block.
|
||||
copy(x.mem[:], b)
|
||||
x.n = len(b)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (x *xxh) Sum(b []byte) []byte {
|
||||
s := x.Sum64()
|
||||
return append(
|
||||
b,
|
||||
byte(s>>56),
|
||||
byte(s>>48),
|
||||
byte(s>>40),
|
||||
byte(s>>32),
|
||||
byte(s>>24),
|
||||
byte(s>>16),
|
||||
byte(s>>8),
|
||||
byte(s),
|
||||
)
|
||||
}
|
||||
|
||||
func (x *xxh) Sum64() uint64 {
|
||||
var h uint64
|
||||
|
||||
if x.total >= 32 {
|
||||
v1, v2, v3, v4 := x.v1, x.v2, x.v3, x.v4
|
||||
h = rol1(v1) + rol7(v2) + rol12(v3) + rol18(v4)
|
||||
h = mergeRound(h, v1)
|
||||
h = mergeRound(h, v2)
|
||||
h = mergeRound(h, v3)
|
||||
h = mergeRound(h, v4)
|
||||
} else {
|
||||
h = x.v3 + prime5
|
||||
}
|
||||
|
||||
h += uint64(x.total)
|
||||
|
||||
i, end := 0, x.n
|
||||
for ; i+8 <= end; i += 8 {
|
||||
k1 := round(0, u64(x.mem[i:i+8]))
|
||||
h ^= k1
|
||||
h = rol27(h)*prime1 + prime4
|
||||
}
|
||||
if i+4 <= end {
|
||||
h ^= uint64(u32(x.mem[i:i+4])) * prime1
|
||||
h = rol23(h)*prime2 + prime3
|
||||
i += 4
|
||||
}
|
||||
for i < end {
|
||||
h ^= uint64(x.mem[i]) * prime5
|
||||
h = rol11(h) * prime1
|
||||
i++
|
||||
}
|
||||
|
||||
h ^= h >> 33
|
||||
h *= prime2
|
||||
h ^= h >> 29
|
||||
h *= prime3
|
||||
h ^= h >> 32
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func u64(b []byte) uint64 { return binary.LittleEndian.Uint64(b) }
|
||||
func u32(b []byte) uint32 { return binary.LittleEndian.Uint32(b) }
|
||||
|
||||
func round(acc, input uint64) uint64 {
|
||||
acc += input * prime2
|
||||
acc = rol31(acc)
|
||||
acc *= prime1
|
||||
return acc
|
||||
}
|
||||
|
||||
func mergeRound(acc, val uint64) uint64 {
|
||||
val = round(0, val)
|
||||
acc ^= val
|
||||
acc = acc*prime1 + prime4
|
||||
return acc
|
||||
}
|
12
vendor/github.com/cespare/xxhash/xxhash_amd64.go
generated
vendored
Normal file
12
vendor/github.com/cespare/xxhash/xxhash_amd64.go
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
// +build !appengine
|
||||
// +build gc
|
||||
// +build !purego
|
||||
|
||||
package xxhash
|
||||
|
||||
// Sum64 computes the 64-bit xxHash digest of b.
|
||||
//
|
||||
//go:noescape
|
||||
func Sum64(b []byte) uint64
|
||||
|
||||
func writeBlocks(x *xxh, b []byte) []byte
|
233
vendor/github.com/cespare/xxhash/xxhash_amd64.s
generated
vendored
Normal file
233
vendor/github.com/cespare/xxhash/xxhash_amd64.s
generated
vendored
Normal file
|
@ -0,0 +1,233 @@
|
|||
// +build !appengine
|
||||
// +build gc
|
||||
// +build !purego
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// Register allocation:
|
||||
// AX h
|
||||
// CX pointer to advance through b
|
||||
// DX n
|
||||
// BX loop end
|
||||
// R8 v1, k1
|
||||
// R9 v2
|
||||
// R10 v3
|
||||
// R11 v4
|
||||
// R12 tmp
|
||||
// R13 prime1v
|
||||
// R14 prime2v
|
||||
// R15 prime4v
|
||||
|
||||
// round reads from and advances the buffer pointer in CX.
|
||||
// It assumes that R13 has prime1v and R14 has prime2v.
|
||||
#define round(r) \
|
||||
MOVQ (CX), R12 \
|
||||
ADDQ $8, CX \
|
||||
IMULQ R14, R12 \
|
||||
ADDQ R12, r \
|
||||
ROLQ $31, r \
|
||||
IMULQ R13, r
|
||||
|
||||
// mergeRound applies a merge round on the two registers acc and val.
|
||||
// It assumes that R13 has prime1v, R14 has prime2v, and R15 has prime4v.
|
||||
#define mergeRound(acc, val) \
|
||||
IMULQ R14, val \
|
||||
ROLQ $31, val \
|
||||
IMULQ R13, val \
|
||||
XORQ val, acc \
|
||||
IMULQ R13, acc \
|
||||
ADDQ R15, acc
|
||||
|
||||
// func Sum64(b []byte) uint64
|
||||
TEXT ·Sum64(SB), NOSPLIT, $0-32
|
||||
// Load fixed primes.
|
||||
MOVQ ·prime1v(SB), R13
|
||||
MOVQ ·prime2v(SB), R14
|
||||
MOVQ ·prime4v(SB), R15
|
||||
|
||||
// Load slice.
|
||||
MOVQ b_base+0(FP), CX
|
||||
MOVQ b_len+8(FP), DX
|
||||
LEAQ (CX)(DX*1), BX
|
||||
|
||||
// The first loop limit will be len(b)-32.
|
||||
SUBQ $32, BX
|
||||
|
||||
// Check whether we have at least one block.
|
||||
CMPQ DX, $32
|
||||
JLT noBlocks
|
||||
|
||||
// Set up initial state (v1, v2, v3, v4).
|
||||
MOVQ R13, R8
|
||||
ADDQ R14, R8
|
||||
MOVQ R14, R9
|
||||
XORQ R10, R10
|
||||
XORQ R11, R11
|
||||
SUBQ R13, R11
|
||||
|
||||
// Loop until CX > BX.
|
||||
blockLoop:
|
||||
round(R8)
|
||||
round(R9)
|
||||
round(R10)
|
||||
round(R11)
|
||||
|
||||
CMPQ CX, BX
|
||||
JLE blockLoop
|
||||
|
||||
MOVQ R8, AX
|
||||
ROLQ $1, AX
|
||||
MOVQ R9, R12
|
||||
ROLQ $7, R12
|
||||
ADDQ R12, AX
|
||||
MOVQ R10, R12
|
||||
ROLQ $12, R12
|
||||
ADDQ R12, AX
|
||||
MOVQ R11, R12
|
||||
ROLQ $18, R12
|
||||
ADDQ R12, AX
|
||||
|
||||
mergeRound(AX, R8)
|
||||
mergeRound(AX, R9)
|
||||
mergeRound(AX, R10)
|
||||
mergeRound(AX, R11)
|
||||
|
||||
JMP afterBlocks
|
||||
|
||||
noBlocks:
|
||||
MOVQ ·prime5v(SB), AX
|
||||
|
||||
afterBlocks:
|
||||
ADDQ DX, AX
|
||||
|
||||
// Right now BX has len(b)-32, and we want to loop until CX > len(b)-8.
|
||||
ADDQ $24, BX
|
||||
|
||||
CMPQ CX, BX
|
||||
JG fourByte
|
||||
|
||||
wordLoop:
|
||||
// Calculate k1.
|
||||
MOVQ (CX), R8
|
||||
ADDQ $8, CX
|
||||
IMULQ R14, R8
|
||||
ROLQ $31, R8
|
||||
IMULQ R13, R8
|
||||
|
||||
XORQ R8, AX
|
||||
ROLQ $27, AX
|
||||
IMULQ R13, AX
|
||||
ADDQ R15, AX
|
||||
|
||||
CMPQ CX, BX
|
||||
JLE wordLoop
|
||||
|
||||
fourByte:
|
||||
ADDQ $4, BX
|
||||
CMPQ CX, BX
|
||||
JG singles
|
||||
|
||||
MOVL (CX), R8
|
||||
ADDQ $4, CX
|
||||
IMULQ R13, R8
|
||||
XORQ R8, AX
|
||||
|
||||
ROLQ $23, AX
|
||||
IMULQ R14, AX
|
||||
ADDQ ·prime3v(SB), AX
|
||||
|
||||
singles:
|
||||
ADDQ $4, BX
|
||||
CMPQ CX, BX
|
||||
JGE finalize
|
||||
|
||||
singlesLoop:
|
||||
MOVBQZX (CX), R12
|
||||
ADDQ $1, CX
|
||||
IMULQ ·prime5v(SB), R12
|
||||
XORQ R12, AX
|
||||
|
||||
ROLQ $11, AX
|
||||
IMULQ R13, AX
|
||||
|
||||
CMPQ CX, BX
|
||||
JL singlesLoop
|
||||
|
||||
finalize:
|
||||
MOVQ AX, R12
|
||||
SHRQ $33, R12
|
||||
XORQ R12, AX
|
||||
IMULQ R14, AX
|
||||
MOVQ AX, R12
|
||||
SHRQ $29, R12
|
||||
XORQ R12, AX
|
||||
IMULQ ·prime3v(SB), AX
|
||||
MOVQ AX, R12
|
||||
SHRQ $32, R12
|
||||
XORQ R12, AX
|
||||
|
||||
MOVQ AX, ret+24(FP)
|
||||
RET
|
||||
|
||||
// writeBlocks uses the same registers as above except that it uses AX to store
|
||||
// the x pointer.
|
||||
|
||||
// func writeBlocks(x *xxh, b []byte) []byte
|
||||
TEXT ·writeBlocks(SB), NOSPLIT, $0-56
|
||||
// Load fixed primes needed for round.
|
||||
MOVQ ·prime1v(SB), R13
|
||||
MOVQ ·prime2v(SB), R14
|
||||
|
||||
// Load slice.
|
||||
MOVQ b_base+8(FP), CX
|
||||
MOVQ CX, ret_base+32(FP) // initialize return base pointer; see NOTE below
|
||||
MOVQ b_len+16(FP), DX
|
||||
LEAQ (CX)(DX*1), BX
|
||||
SUBQ $32, BX
|
||||
|
||||
// Load vN from x.
|
||||
MOVQ x+0(FP), AX
|
||||
MOVQ 0(AX), R8 // v1
|
||||
MOVQ 8(AX), R9 // v2
|
||||
MOVQ 16(AX), R10 // v3
|
||||
MOVQ 24(AX), R11 // v4
|
||||
|
||||
// We don't need to check the loop condition here; this function is
|
||||
// always called with at least one block of data to process.
|
||||
blockLoop:
|
||||
round(R8)
|
||||
round(R9)
|
||||
round(R10)
|
||||
round(R11)
|
||||
|
||||
CMPQ CX, BX
|
||||
JLE blockLoop
|
||||
|
||||
// Copy vN back to x.
|
||||
MOVQ R8, 0(AX)
|
||||
MOVQ R9, 8(AX)
|
||||
MOVQ R10, 16(AX)
|
||||
MOVQ R11, 24(AX)
|
||||
|
||||
// Construct return slice.
|
||||
// NOTE: It's important that we don't construct a slice that has a base
|
||||
// pointer off the end of the original slice, as in Go 1.7+ this will
|
||||
// cause runtime crashes. (See discussion in, for example,
|
||||
// https://github.com/golang/go/issues/16772.)
|
||||
// Therefore, we calculate the length/cap first, and if they're zero, we
|
||||
// keep the old base. This is what the compiler does as well if you
|
||||
// write code like
|
||||
// b = b[len(b):]
|
||||
|
||||
// New length is 32 - (CX - BX) -> BX+32 - CX.
|
||||
ADDQ $32, BX
|
||||
SUBQ CX, BX
|
||||
JZ afterSetBase
|
||||
|
||||
MOVQ CX, ret_base+32(FP)
|
||||
|
||||
afterSetBase:
|
||||
MOVQ BX, ret_len+40(FP)
|
||||
MOVQ BX, ret_cap+48(FP) // set cap == len
|
||||
|
||||
RET
|
75
vendor/github.com/cespare/xxhash/xxhash_other.go
generated
vendored
Normal file
75
vendor/github.com/cespare/xxhash/xxhash_other.go
generated
vendored
Normal file
|
@ -0,0 +1,75 @@
|
|||
// +build !amd64 appengine !gc purego
|
||||
|
||||
package xxhash
|
||||
|
||||
// Sum64 computes the 64-bit xxHash digest of b.
|
||||
func Sum64(b []byte) uint64 {
|
||||
// A simpler version would be
|
||||
// x := New()
|
||||
// x.Write(b)
|
||||
// return x.Sum64()
|
||||
// but this is faster, particularly for small inputs.
|
||||
|
||||
n := len(b)
|
||||
var h uint64
|
||||
|
||||
if n >= 32 {
|
||||
v1 := prime1v + prime2
|
||||
v2 := prime2
|
||||
v3 := uint64(0)
|
||||
v4 := -prime1v
|
||||
for len(b) >= 32 {
|
||||
v1 = round(v1, u64(b[0:8:len(b)]))
|
||||
v2 = round(v2, u64(b[8:16:len(b)]))
|
||||
v3 = round(v3, u64(b[16:24:len(b)]))
|
||||
v4 = round(v4, u64(b[24:32:len(b)]))
|
||||
b = b[32:len(b):len(b)]
|
||||
}
|
||||
h = rol1(v1) + rol7(v2) + rol12(v3) + rol18(v4)
|
||||
h = mergeRound(h, v1)
|
||||
h = mergeRound(h, v2)
|
||||
h = mergeRound(h, v3)
|
||||
h = mergeRound(h, v4)
|
||||
} else {
|
||||
h = prime5
|
||||
}
|
||||
|
||||
h += uint64(n)
|
||||
|
||||
i, end := 0, len(b)
|
||||
for ; i+8 <= end; i += 8 {
|
||||
k1 := round(0, u64(b[i:i+8:len(b)]))
|
||||
h ^= k1
|
||||
h = rol27(h)*prime1 + prime4
|
||||
}
|
||||
if i+4 <= end {
|
||||
h ^= uint64(u32(b[i:i+4:len(b)])) * prime1
|
||||
h = rol23(h)*prime2 + prime3
|
||||
i += 4
|
||||
}
|
||||
for ; i < end; i++ {
|
||||
h ^= uint64(b[i]) * prime5
|
||||
h = rol11(h) * prime1
|
||||
}
|
||||
|
||||
h ^= h >> 33
|
||||
h *= prime2
|
||||
h ^= h >> 29
|
||||
h *= prime3
|
||||
h ^= h >> 32
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func writeBlocks(x *xxh, b []byte) []byte {
|
||||
v1, v2, v3, v4 := x.v1, x.v2, x.v3, x.v4
|
||||
for len(b) >= 32 {
|
||||
v1 = round(v1, u64(b[0:8:len(b)]))
|
||||
v2 = round(v2, u64(b[8:16:len(b)]))
|
||||
v3 = round(v3, u64(b[16:24:len(b)]))
|
||||
v4 = round(v4, u64(b[24:32:len(b)]))
|
||||
b = b[32:len(b):len(b)]
|
||||
}
|
||||
x.v1, x.v2, x.v3, x.v4 = v1, v2, v3, v4
|
||||
return b
|
||||
}
|
10
vendor/github.com/cespare/xxhash/xxhash_safe.go
generated
vendored
Normal file
10
vendor/github.com/cespare/xxhash/xxhash_safe.go
generated
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
// +build appengine
|
||||
|
||||
// This file contains the safe implementations of otherwise unsafe-using code.
|
||||
|
||||
package xxhash
|
||||
|
||||
// Sum64String computes the 64-bit xxHash digest of s.
|
||||
func Sum64String(s string) uint64 {
|
||||
return Sum64([]byte(s))
|
||||
}
|
30
vendor/github.com/cespare/xxhash/xxhash_unsafe.go
generated
vendored
Normal file
30
vendor/github.com/cespare/xxhash/xxhash_unsafe.go
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
// +build !appengine
|
||||
|
||||
// This file encapsulates usage of unsafe.
|
||||
// xxhash_safe.go contains the safe implementations.
|
||||
|
||||
package xxhash
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Sum64String computes the 64-bit xxHash digest of s.
|
||||
// It may be faster than Sum64([]byte(s)) by avoiding a copy.
|
||||
//
|
||||
// TODO(caleb): Consider removing this if an optimization is ever added to make
|
||||
// it unnecessary: https://golang.org/issue/2205.
|
||||
//
|
||||
// TODO(caleb): We still have a function call; we could instead write Go/asm
|
||||
// copies of Sum64 for strings to squeeze out a bit more speed.
|
||||
func Sum64String(s string) uint64 {
|
||||
// See https://groups.google.com/d/msg/golang-nuts/dcjzJy-bSpw/tcZYBzQqAQAJ
|
||||
// for some discussion about this unsafe conversion.
|
||||
var b []byte
|
||||
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||
bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
|
||||
bh.Len = len(s)
|
||||
bh.Cap = len(s)
|
||||
return Sum64(b)
|
||||
}
|
18
vendor/github.com/dgraph-io/badger/.deepsource.toml
generated
vendored
Normal file
18
vendor/github.com/dgraph-io/badger/.deepsource.toml
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
version = 1
|
||||
|
||||
test_patterns = [
|
||||
'integration/testgc/**',
|
||||
'**/*_test.go'
|
||||
]
|
||||
|
||||
exclude_patterns = [
|
||||
|
||||
]
|
||||
|
||||
[[analyzers]]
|
||||
name = 'go'
|
||||
enabled = true
|
||||
|
||||
|
||||
[analyzers.meta]
|
||||
import_path = 'github.com/dgraph-io/badger'
|
2
vendor/github.com/dgraph-io/badger/.gitignore
generated
vendored
Normal file
2
vendor/github.com/dgraph-io/badger/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
p/
|
||||
badger-test*/
|
27
vendor/github.com/dgraph-io/badger/.golangci.yml
generated
vendored
Normal file
27
vendor/github.com/dgraph-io/badger/.golangci.yml
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
run:
|
||||
tests: false
|
||||
|
||||
linters-settings:
|
||||
lll:
|
||||
line-length: 100
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- errcheck
|
||||
- ineffassign
|
||||
- gas
|
||||
- gofmt
|
||||
- golint
|
||||
- gosimple
|
||||
- govet
|
||||
- lll
|
||||
- varcheck
|
||||
- unused
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
- linters:
|
||||
- gosec
|
||||
text: "G404: "
|
||||
|
45
vendor/github.com/dgraph-io/badger/.travis.yml
generated
vendored
Normal file
45
vendor/github.com/dgraph-io/badger/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- "1.12"
|
||||
- "1.13"
|
||||
- tip
|
||||
os:
|
||||
- osx
|
||||
env:
|
||||
jobs:
|
||||
- GOARCH=386
|
||||
- GOARCH=amd64
|
||||
global:
|
||||
- secure: CRkV2+/jlO0gXzzS50XGxfMS117FNwiVjxNY/LeWq06RKD+dDCPxTJl3JCNe3l0cYEPAglV2uMMYukDiTqJ7e+HI4nh4N4mv6lwx39N8dAvJe1x5ITS2T4qk4kTjuQb1Q1vw/ZOxoQqmvNKj2uRmBdJ/HHmysbRJ1OzCWML3OXdUwJf0AYlJzTjpMfkOKr7sTtE4rwyyQtd4tKH1fGdurgI9ZuFd9qvYxK2qcJhsQ6CNqMXt+7FkVkN1rIPmofjjBTNryzUr4COFXuWH95aDAif19DeBW4lbNgo1+FpDsrgmqtuhl6NAuptI8q/imow2KXBYJ8JPXsxW8DVFj0IIp0RCd3GjaEnwBEbxAyiIHLfW7AudyTS/dJOvZffPqXnuJ8xj3OPIdNe4xY0hWl8Ju2HhKfLOAHq7VadHZWd3IHLil70EiL4/JLD1rNbMImUZisFaA8pyrcIvYYebjOnk4TscwKFLedClRSX1XsMjWWd0oykQtrdkHM2IxknnBpaLu7mFnfE07f6dkG0nlpyu4SCLey7hr5FdcEmljA0nIxTSYDg6035fQkBEAbe7hlESOekkVNT9IZPwG+lmt3vU4ofi6NqNbJecOuSB+h36IiZ9s4YQtxYNnLgW14zjuFGGyT5smc3IjBT7qngDjKIgyrSVoRkY/8udy9qbUgvBeW8=
|
||||
|
||||
|
||||
jobs:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
exclude:
|
||||
# Exclude builds for 386 architecture on 1.12 and tip
|
||||
# Since we don't want it to run for 32 bit
|
||||
- go: "1.12"
|
||||
env: GOARCH=386
|
||||
- go: tip
|
||||
env: GOARCH=386
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
slack:
|
||||
secure: X7uBLWYbuUhf8QFE16CoS5z7WvFR8EN9j6cEectMW6mKZ3vwXGwVXRIPsgUq/606DsQdCCx34MR8MRWYGlu6TBolbSe9y0EP0i46yipPz22YtuT7umcVUbGEyx8MZKgG0v1u/zA0O4aCsOBpGAA3gxz8h3JlEHDt+hv6U8xRsSllVLzLSNb5lwxDtcfEDxVVqP47GMEgjLPM28Pyt5qwjk7o5a4YSVzkfdxBXxd3gWzFUWzJ5E3cTacli50dK4GVfiLcQY2aQYoYO7AAvDnvP+TPfjDkBlUEE4MUz5CDIN51Xb+WW33sX7g+r3Bj7V5IRcF973RiYkpEh+3eoiPnyWyxhDZBYilty3b+Hysp6d4Ov/3I3ll7Bcny5+cYjakjkMH3l9w3gs6Y82GlpSLSJshKWS8vPRsxFe0Pstj6QSJXTd9EBaFr+l1ScXjJv/Sya9j8N9FfTuOTESWuaL1auX4Y7zEEVHlA8SCNOO8K0eTfxGZnC/YcIHsR8rePEAcFxfOYQppkyLF/XvAtnb/LMUuu0g4y2qNdme6Oelvyar1tFEMRtbl4mRCdu/krXBFtkrsfUaVY6WTPdvXAGotsFJ0wuA53zGVhlcd3+xAlSlR3c1QX95HIMeivJKb5L4nTjP+xnrmQNtnVk+tG4LSH2ltuwcZSSczModtcBmRefrk=
|
||||
|
||||
script: >-
|
||||
if [ $TRAVIS_OS_NAME = "linux" ] && [ $go_32 ]; then
|
||||
uname -a
|
||||
GOOS=linux GOARCH=arm go test -v ./...
|
||||
# Another round of tests after turning off mmap.
|
||||
GOOS=linux GOARCH=arm go test -v -vlog_mmap=false github.com/dgraph-io/badger
|
||||
else
|
||||
go test -v ./...
|
||||
# Another round of tests after turning off mmap.
|
||||
go test -v -vlog_mmap=false github.com/dgraph-io/badger
|
||||
# Cross-compile for Plan 9
|
||||
GOOS=plan9 go build ./...
|
||||
fi
|
270
vendor/github.com/dgraph-io/badger/CHANGELOG.md
generated
vendored
Normal file
270
vendor/github.com/dgraph-io/badger/CHANGELOG.md
generated
vendored
Normal file
|
@ -0,0 +1,270 @@
|
|||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Serialization Versioning](VERSIONING.md).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.6.2] - 2020-09-10
|
||||
|
||||
### Fixed
|
||||
- Fix Sequence generates duplicate values (#1281)
|
||||
- Ensure `bitValuePointer` flag is cleared for LSM entry values written to LSM (#1313)
|
||||
- Confirm `badgerMove` entry required before rewrite (#1302)
|
||||
- Drop move keys when its key prefix is dropped (#1331)
|
||||
- Compaction: Expired keys and delete markers are never purged (#1354)
|
||||
- Restore: Account for value size as well (#1358)
|
||||
- GC: Consider size of value while rewriting (#1357)
|
||||
- Rework DB.DropPrefix (#1381)
|
||||
- Update head while replaying value log (#1372)
|
||||
- Remove vlog file if bootstrap, syncDir or mmap fails (#1434)
|
||||
- Levels: Compaction incorrectly drops some delete markers (#1422)
|
||||
- Fix(replay) - Update head for LSM entries also (#1456)
|
||||
- Fix(Backup/Restore): Keep all versions (#1462)
|
||||
- Fix build on Plan 9 (#1451)
|
||||
|
||||
## [1.6.1] - 2020-03-26
|
||||
|
||||
### New APIs
|
||||
- Badger.DB
|
||||
- NewWriteBatchAt (#948)
|
||||
- Badger.Options
|
||||
- WithEventLogging (#1035)
|
||||
- WithVerifyValueChecksum (#1052)
|
||||
- WithBypassLockGuard (#1243)
|
||||
|
||||
### Features
|
||||
- Support checksum verification for values read from vlog (#1052)
|
||||
- Add EventLogging option (#1035)
|
||||
- Support WriteBatch API in managed mode (#948)
|
||||
- Add support for watching nil prefix in Subscribe API (#1246)
|
||||
|
||||
### Fixed
|
||||
- Initialize vlog before starting compactions in db.Open (#1226)
|
||||
- Fix int overflow for 32bit (#1216)
|
||||
- Remove the 'this entry should've caught' log from value.go (#1170)
|
||||
- Fix merge iterator duplicates issue (#1157)
|
||||
- Fix segmentation fault in vlog.Read (header.Decode) (#1150)
|
||||
- Fix VerifyValueChecksum checks (#1138)
|
||||
- Fix windows dataloss issue (#1134)
|
||||
- Fix request increment ref bug (#1121)
|
||||
- Limit manifest's change set size (#1119)
|
||||
- Fix deadlock in discard stats (#1070)
|
||||
- Acquire lock before unmapping vlog files (#1050)
|
||||
- Set move key's expiresAt for keys with TTL (#1006)
|
||||
- Fix deadlock when flushing discard stats. (#976)
|
||||
- Fix table.Smallest/Biggest and iterator Prefix bug (#997)
|
||||
- Fix boundaries on GC batch size (#987)
|
||||
- Lock log file before munmap (#949)
|
||||
- VlogSize to store correct directory name to expvar.Map (#956)
|
||||
- Fix transaction too big issue in restore (#957)
|
||||
- Fix race condition in updateDiscardStats (#973)
|
||||
- Cast results of len to uint32 to fix compilation in i386 arch. (#961)
|
||||
- Drop discard stats if we can't unmarshal it (#936)
|
||||
- Open all vlog files in RDWR mode (#923)
|
||||
- Fix race condition in flushDiscardStats function (#921)
|
||||
- Ensure rewrite in vlog is within transactional limits (#911)
|
||||
- Fix prefix bug in key iterator and allow all versions (#950)
|
||||
- Fix discard stats moved by GC bug (#929)
|
||||
|
||||
### Performance
|
||||
- Use fastRand instead of locked-rand in skiplist (#1173)
|
||||
- Fix checkOverlap in compaction (#1166)
|
||||
- Optimize createTable in stream_writer.go (#1132)
|
||||
- Add capacity to slice creation when capacity is known (#1103)
|
||||
- Introduce fast merge iterator (#1080)
|
||||
- Introduce StreamDone in Stream Writer (#1061)
|
||||
- Flush vlog buffer if it grows beyond threshold (#1067)
|
||||
- Binary search based table picker (#983)
|
||||
- Making the stream writer APIs goroutine-safe (#959)
|
||||
- Replace FarmHash with AESHash for Oracle conflicts (#952)
|
||||
- Change file picking strategy in compaction (#894)
|
||||
- Use trie for prefix matching (#851)
|
||||
- Fix busy-wait loop in Watermark (#920)
|
||||
|
||||
|
||||
## [1.6.0] - 2019-07-01
|
||||
|
||||
This is a release including almost 200 commits, so expect many changes - some of them
|
||||
not backward compatible.
|
||||
|
||||
Regarding backward compatibility in Badger versions, you might be interested on reading
|
||||
[VERSIONING.md](VERSIONING.md).
|
||||
|
||||
_Note_: The hashes in parentheses correspond to the commits that impacted the given feature.
|
||||
|
||||
### New APIs
|
||||
|
||||
- badger.DB
|
||||
- DropPrefix (291295e)
|
||||
- Flatten (7e41bba)
|
||||
- KeySplits (4751ef1)
|
||||
- MaxBatchCount (b65e2a3)
|
||||
- MaxBatchSize (b65e2a3)
|
||||
- PrintKeyValueHistogram (fd59907)
|
||||
- Subscribe (26128a7)
|
||||
- Sync (851e462)
|
||||
|
||||
- badger.DefaultOptions() and badger.LSMOnlyOptions() (91ce687)
|
||||
- badger.Options.WithX methods
|
||||
|
||||
- badger.Entry (e9447c9)
|
||||
- NewEntry
|
||||
- WithMeta
|
||||
- WithDiscard
|
||||
- WithTTL
|
||||
|
||||
- badger.Item
|
||||
- KeySize (fd59907)
|
||||
- ValueSize (5242a99)
|
||||
|
||||
- badger.IteratorOptions
|
||||
- PickTable (7d46029, 49a49e3)
|
||||
- Prefix (7d46029)
|
||||
|
||||
- badger.Logger (fbb2778)
|
||||
|
||||
- badger.Options
|
||||
- CompactL0OnClose (7e41bba)
|
||||
- Logger (3f66663)
|
||||
- LogRotatesToFlush (2237832)
|
||||
|
||||
- badger.Stream (14cbd89, 3258067)
|
||||
- badger.StreamWriter (7116e16)
|
||||
- badger.TableInfo.KeyCount (fd59907)
|
||||
- badger.TableManifest (2017987)
|
||||
- badger.Tx.NewKeyIterator (49a49e3)
|
||||
- badger.WriteBatch (6daccf9, 7e78e80)
|
||||
|
||||
### Modified APIs
|
||||
|
||||
#### Breaking changes:
|
||||
|
||||
- badger.DefaultOptions and badger.LSMOnlyOptions are now functions rather than variables (91ce687)
|
||||
- badger.Item.Value now receives a function that returns an error (439fd46)
|
||||
- badger.Txn.Commit doesn't receive any params now (6daccf9)
|
||||
- badger.DB.Tables now receives a boolean (76b5341)
|
||||
|
||||
#### Not breaking changes:
|
||||
|
||||
- badger.LSMOptions changed values (799c33f)
|
||||
- badger.DB.NewIterator now allows multiple iterators per RO txn (41d9656)
|
||||
- badger.Options.TableLoadingMode's new default is options.MemoryMap (6b97bac)
|
||||
|
||||
### Removed APIs
|
||||
|
||||
- badger.ManagedDB (d22c0e8)
|
||||
- badger.Options.DoNotCompact (7e41bba)
|
||||
- badger.Txn.SetWithX (e9447c9)
|
||||
|
||||
### Tools:
|
||||
|
||||
- badger bank disect (13db058)
|
||||
- badger bank test (13db058) --mmap (03870e3)
|
||||
- badger fill (7e41bba)
|
||||
- badger flatten (7e41bba)
|
||||
- badger info --histogram (fd59907) --history --lookup --show-keys --show-meta --with-prefix (09e9b63) --show-internal (fb2eed9)
|
||||
- badger benchmark read (239041e)
|
||||
- badger benchmark write (6d3b67d)
|
||||
|
||||
## [1.5.5] - 2019-06-20
|
||||
|
||||
* Introduce support for Go Modules
|
||||
|
||||
## [1.5.3] - 2018-07-11
|
||||
Bug Fixes:
|
||||
* Fix a panic caused due to item.vptr not copying over vs.Value, when looking
|
||||
for a move key.
|
||||
|
||||
## [1.5.2] - 2018-06-19
|
||||
Bug Fixes:
|
||||
* Fix the way move key gets generated.
|
||||
* If a transaction has unclosed, or multiple iterators running simultaneously,
|
||||
throw a panic. Every iterator must be properly closed. At any point in time,
|
||||
only one iterator per transaction can be running. This is to avoid bugs in a
|
||||
transaction data structure which is thread unsafe.
|
||||
|
||||
* *Warning: This change might cause panics in user code. Fix is to properly
|
||||
close your iterators, and only have one running at a time per transaction.*
|
||||
|
||||
## [1.5.1] - 2018-06-04
|
||||
Bug Fixes:
|
||||
* Fix for infinite yieldItemValue recursion. #503
|
||||
* Fix recursive addition of `badgerMove` prefix. https://github.com/dgraph-io/badger/commit/2e3a32f0ccac3066fb4206b28deb39c210c5266f
|
||||
* Use file size based window size for sampling, instead of fixing it to 10MB. #501
|
||||
|
||||
Cleanup:
|
||||
* Clarify comments and documentation.
|
||||
* Move badger tool one directory level up.
|
||||
|
||||
## [1.5.0] - 2018-05-08
|
||||
* Introduce `NumVersionsToKeep` option. This option is used to discard many
|
||||
versions of the same key, which saves space.
|
||||
* Add a new `SetWithDiscard` method, which would indicate that all the older
|
||||
versions of the key are now invalid. Those versions would be discarded during
|
||||
compactions.
|
||||
* Value log GC moves are now bound to another keyspace to ensure latest versions
|
||||
of data are always at the top in LSM tree.
|
||||
* Introduce `ValueLogMaxEntries` to restrict the number of key-value pairs per
|
||||
value log file. This helps bound the time it takes to garbage collect one
|
||||
file.
|
||||
|
||||
## [1.4.0] - 2018-05-04
|
||||
* Make mmap-ing of value log optional.
|
||||
* Run GC multiple times, based on recorded discard statistics.
|
||||
* Add MergeOperator.
|
||||
* Force compact L0 on clsoe (#439).
|
||||
* Add truncate option to warn about data loss (#452).
|
||||
* Discard key versions during compaction (#464).
|
||||
* Introduce new `LSMOnlyOptions`, to make Badger act like a typical LSM based DB.
|
||||
|
||||
Bug fix:
|
||||
* (Temporary) Check max version across all tables in Get (removed in next
|
||||
release).
|
||||
* Update commit and read ts while loading from backup.
|
||||
* Ensure all transaction entries are part of the same value log file.
|
||||
* On commit, run unlock callbacks before doing writes (#413).
|
||||
* Wait for goroutines to finish before closing iterators (#421).
|
||||
|
||||
## [1.3.0] - 2017-12-12
|
||||
* Add `DB.NextSequence()` method to generate monotonically increasing integer
|
||||
sequences.
|
||||
* Add `DB.Size()` method to return the size of LSM and value log files.
|
||||
* Tweaked mmap code to make Windows 32-bit builds work.
|
||||
* Tweaked build tags on some files to make iOS builds work.
|
||||
* Fix `DB.PurgeOlderVersions()` to not violate some constraints.
|
||||
|
||||
## [1.2.0] - 2017-11-30
|
||||
* Expose a `Txn.SetEntry()` method to allow setting the key-value pair
|
||||
and all the metadata at the same time.
|
||||
|
||||
## [1.1.1] - 2017-11-28
|
||||
* Fix bug where txn.Get was returing key deleted in same transaction.
|
||||
* Fix race condition while decrementing reference in oracle.
|
||||
* Update doneCommit in the callback for CommitAsync.
|
||||
* Iterator see writes of current txn.
|
||||
|
||||
## [1.1.0] - 2017-11-13
|
||||
* Create Badger directory if it does not exist when `badger.Open` is called.
|
||||
* Added `Item.ValueCopy()` to avoid deadlocks in long-running iterations
|
||||
* Fixed 64-bit alignment issues to make Badger run on Arm v7
|
||||
|
||||
## [1.0.1] - 2017-11-06
|
||||
* Fix an uint16 overflow when resizing key slice
|
||||
|
||||
[Unreleased]: https://github.com/dgraph-io/badger/compare/v1.6.2...HEAD
|
||||
[1.6.2]: https://github.com/dgraph-io/badger/compare/v1.6.1...v1.6.2
|
||||
[1.6.1]: https://github.com/dgraph-io/badger/compare/v1.6.0...v1.6.1
|
||||
[1.6.0]: https://github.com/dgraph-io/badger/compare/v1.5.5...v1.6.0
|
||||
[1.5.5]: https://github.com/dgraph-io/badger/compare/v1.5.3...v1.5.5
|
||||
[1.5.3]: https://github.com/dgraph-io/badger/compare/v1.5.2...v1.5.3
|
||||
[1.5.2]: https://github.com/dgraph-io/badger/compare/v1.5.1...v1.5.2
|
||||
[1.5.1]: https://github.com/dgraph-io/badger/compare/v1.5.0...v1.5.1
|
||||
[1.5.0]: https://github.com/dgraph-io/badger/compare/v1.4.0...v1.5.0
|
||||
[1.4.0]: https://github.com/dgraph-io/badger/compare/v1.3.0...v1.4.0
|
||||
[1.3.0]: https://github.com/dgraph-io/badger/compare/v1.2.0...v1.3.0
|
||||
[1.2.0]: https://github.com/dgraph-io/badger/compare/v1.1.1...v1.2.0
|
||||
[1.1.1]: https://github.com/dgraph-io/badger/compare/v1.1.0...v1.1.1
|
||||
[1.1.0]: https://github.com/dgraph-io/badger/compare/v1.0.1...v1.1.0
|
||||
[1.0.1]: https://github.com/dgraph-io/badger/compare/v1.0.0...v1.0.1
|
5
vendor/github.com/dgraph-io/badger/CODE_OF_CONDUCT.md
generated
vendored
Normal file
5
vendor/github.com/dgraph-io/badger/CODE_OF_CONDUCT.md
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Code of Conduct
|
||||
|
||||
Our Code of Conduct can be found here:
|
||||
|
||||
https://dgraph.io/conduct
|
176
vendor/github.com/dgraph-io/badger/LICENSE
generated
vendored
Normal file
176
vendor/github.com/dgraph-io/badger/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,176 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
898
vendor/github.com/dgraph-io/badger/README.md
generated
vendored
Normal file
898
vendor/github.com/dgraph-io/badger/README.md
generated
vendored
Normal file
|
@ -0,0 +1,898 @@
|
|||
# BadgerDB [](https://godoc.org/github.com/dgraph-io/badger) [](https://goreportcard.com/report/github.com/dgraph-io/badger) [](https://sourcegraph.com/github.com/dgraph-io/badger?badge) [/statusIcon.svg)](https://teamcity.dgraph.io/viewLog.html?buildTypeId=Badger_UnitTests&buildId=lastFinished&guest=1)  [](https://coveralls.io/github/dgraph-io/badger?branch=master)
|
||||
|
||||

|
||||
|
||||
BadgerDB is an embeddable, persistent and fast key-value (KV) database written
|
||||
in pure Go. It is the underlying database for [Dgraph](https://dgraph.io), a
|
||||
fast, distributed graph database. It's meant to be a performant alternative to
|
||||
non-Go-based key-value stores like RocksDB.
|
||||
|
||||
## Project Status [Jun 26, 2019]
|
||||
|
||||
Badger is stable and is being used to serve data sets worth hundreds of
|
||||
terabytes. Badger supports concurrent ACID transactions with serializable
|
||||
snapshot isolation (SSI) guarantees. A Jepsen-style bank test runs nightly for
|
||||
8h, with `--race` flag and ensures the maintenance of transactional guarantees.
|
||||
Badger has also been tested to work with filesystem level anomalies, to ensure
|
||||
persistence and consistency.
|
||||
|
||||
Badger v1.0 was released in Nov 2017, and the latest version that is data-compatible
|
||||
with v1.0 is v1.6.0.
|
||||
|
||||
Badger v2.0, a new release coming up very soon will use a new storage format which won't
|
||||
be compatible with all of the v1.x. The [Changelog] is kept fairly up-to-date.
|
||||
|
||||
For more details on our version naming schema please read [Choosing a version](#choosing-a-version).
|
||||
|
||||
[Changelog]:https://github.com/dgraph-io/badger/blob/master/CHANGELOG.md
|
||||
|
||||
## Table of Contents
|
||||
* [Getting Started](#getting-started)
|
||||
+ [Installing](#installing)
|
||||
- [Choosing a version](#choosing-a-version)
|
||||
+ [Opening a database](#opening-a-database)
|
||||
+ [Transactions](#transactions)
|
||||
- [Read-only transactions](#read-only-transactions)
|
||||
- [Read-write transactions](#read-write-transactions)
|
||||
- [Managing transactions manually](#managing-transactions-manually)
|
||||
+ [Using key/value pairs](#using-keyvalue-pairs)
|
||||
+ [Monotonically increasing integers](#monotonically-increasing-integers)
|
||||
* [Merge Operations](#merge-operations)
|
||||
+ [Setting Time To Live(TTL) and User Metadata on Keys](#setting-time-to-livettl-and-user-metadata-on-keys)
|
||||
+ [Iterating over keys](#iterating-over-keys)
|
||||
- [Prefix scans](#prefix-scans)
|
||||
- [Key-only iteration](#key-only-iteration)
|
||||
+ [Stream](#stream)
|
||||
+ [Garbage Collection](#garbage-collection)
|
||||
+ [Database backup](#database-backup)
|
||||
+ [Memory usage](#memory-usage)
|
||||
+ [Statistics](#statistics)
|
||||
* [Resources](#resources)
|
||||
+ [Blog Posts](#blog-posts)
|
||||
* [Contact](#contact)
|
||||
* [Design](#design)
|
||||
+ [Comparisons](#comparisons)
|
||||
+ [Benchmarks](#benchmarks)
|
||||
* [Other Projects Using Badger](#other-projects-using-badger)
|
||||
* [Frequently Asked Questions](#frequently-asked-questions)
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Installing
|
||||
To start using Badger, install Go 1.11 or above and run `go get`:
|
||||
|
||||
```sh
|
||||
$ go get github.com/dgraph-io/badger/...
|
||||
```
|
||||
|
||||
This will retrieve the library and install the `badger` command line
|
||||
utility into your `$GOBIN` path.
|
||||
|
||||
#### Choosing a version
|
||||
|
||||
BadgerDB is a pretty special package from the point of view that the most important change we can
|
||||
make to it is not on its API but rather on how data is stored on disk.
|
||||
|
||||
This is why we follow a version naming schema that differs from Semantic Versioning.
|
||||
|
||||
- New major versions are released when the data format on disk changes in an incompatible way.
|
||||
- New minor versions are released whenever the API changes but data compatibility is maintained.
|
||||
Note that the changes on the API could be backward-incompatible - unlike Semantic Versioning.
|
||||
- New patch versions are released when there's no changes to the data format nor the API.
|
||||
|
||||
Following these rules:
|
||||
|
||||
- v1.5.0 and v1.6.0 can be used on top of the same files without any concerns, as their major
|
||||
version is the same, therefore the data format on disk is compatible.
|
||||
- v1.6.0 and v2.0.0 are data incompatible as their major version implies, so files created with
|
||||
v1.6.0 will need to be converted into the new format before they can be used by v2.0.0.
|
||||
|
||||
For a longer explanation on the reasons behind using a new versioning naming schema, you can read
|
||||
[VERSIONING.md](VERSIONING.md).
|
||||
|
||||
### Opening a database
|
||||
The top-level object in Badger is a `DB`. It represents multiple files on disk
|
||||
in specific directories, which contain the data for a single database.
|
||||
|
||||
To open your database, use the `badger.Open()` function, with the appropriate
|
||||
options. The `Dir` and `ValueDir` options are mandatory and must be
|
||||
specified by the client. They can be set to the same value to simplify things.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
badger "github.com/dgraph-io/badger"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Open the Badger database located in the /tmp/badger directory.
|
||||
// It will be created if it doesn't exist.
|
||||
db, err := badger.Open(badger.DefaultOptions("/tmp/badger"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
// Your code here…
|
||||
}
|
||||
```
|
||||
|
||||
Please note that Badger obtains a lock on the directories so multiple processes
|
||||
cannot open the same database at the same time.
|
||||
|
||||
### Transactions
|
||||
|
||||
#### Read-only transactions
|
||||
To start a read-only transaction, you can use the `DB.View()` method:
|
||||
|
||||
```go
|
||||
err := db.View(func(txn *badger.Txn) error {
|
||||
// Your code here…
|
||||
return nil
|
||||
})
|
||||
```
|
||||
|
||||
You cannot perform any writes or deletes within this transaction. Badger
|
||||
ensures that you get a consistent view of the database within this closure. Any
|
||||
writes that happen elsewhere after the transaction has started, will not be
|
||||
seen by calls made within the closure.
|
||||
|
||||
#### Read-write transactions
|
||||
To start a read-write transaction, you can use the `DB.Update()` method:
|
||||
|
||||
```go
|
||||
err := db.Update(func(txn *badger.Txn) error {
|
||||
// Your code here…
|
||||
return nil
|
||||
})
|
||||
```
|
||||
|
||||
All database operations are allowed inside a read-write transaction.
|
||||
|
||||
Always check the returned error value. If you return an error
|
||||
within your closure it will be passed through.
|
||||
|
||||
An `ErrConflict` error will be reported in case of a conflict. Depending on the state
|
||||
of your application, you have the option to retry the operation if you receive
|
||||
this error.
|
||||
|
||||
An `ErrTxnTooBig` will be reported in case the number of pending writes/deletes in
|
||||
the transaction exceeds a certain limit. In that case, it is best to commit the
|
||||
transaction and start a new transaction immediately. Here is an example (we are
|
||||
not checking for errors in some places for simplicity):
|
||||
|
||||
```go
|
||||
updates := make(map[string]string)
|
||||
txn := db.NewTransaction(true)
|
||||
for k,v := range updates {
|
||||
if err := txn.Set([]byte(k),[]byte(v)); err == badger.ErrTxnTooBig {
|
||||
_ = txn.Commit()
|
||||
txn = db.NewTransaction(true)
|
||||
_ = txn.Set([]byte(k),[]byte(v))
|
||||
}
|
||||
}
|
||||
_ = txn.Commit()
|
||||
```
|
||||
|
||||
#### Managing transactions manually
|
||||
The `DB.View()` and `DB.Update()` methods are wrappers around the
|
||||
`DB.NewTransaction()` and `Txn.Commit()` methods (or `Txn.Discard()` in case of
|
||||
read-only transactions). These helper methods will start the transaction,
|
||||
execute a function, and then safely discard your transaction if an error is
|
||||
returned. This is the recommended way to use Badger transactions.
|
||||
|
||||
However, sometimes you may want to manually create and commit your
|
||||
transactions. You can use the `DB.NewTransaction()` function directly, which
|
||||
takes in a boolean argument to specify whether a read-write transaction is
|
||||
required. For read-write transactions, it is necessary to call `Txn.Commit()`
|
||||
to ensure the transaction is committed. For read-only transactions, calling
|
||||
`Txn.Discard()` is sufficient. `Txn.Commit()` also calls `Txn.Discard()`
|
||||
internally to cleanup the transaction, so just calling `Txn.Commit()` is
|
||||
sufficient for read-write transaction. However, if your code doesn’t call
|
||||
`Txn.Commit()` for some reason (for e.g it returns prematurely with an error),
|
||||
then please make sure you call `Txn.Discard()` in a `defer` block. Refer to the
|
||||
code below.
|
||||
|
||||
```go
|
||||
// Start a writable transaction.
|
||||
txn := db.NewTransaction(true)
|
||||
defer txn.Discard()
|
||||
|
||||
// Use the transaction...
|
||||
err := txn.Set([]byte("answer"), []byte("42"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Commit the transaction and check for error.
|
||||
if err := txn.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
The first argument to `DB.NewTransaction()` is a boolean stating if the transaction
|
||||
should be writable.
|
||||
|
||||
Badger allows an optional callback to the `Txn.Commit()` method. Normally, the
|
||||
callback can be set to `nil`, and the method will return after all the writes
|
||||
have succeeded. However, if this callback is provided, the `Txn.Commit()`
|
||||
method returns as soon as it has checked for any conflicts. The actual writing
|
||||
to the disk happens asynchronously, and the callback is invoked once the
|
||||
writing has finished, or an error has occurred. This can improve the throughput
|
||||
of the application in some cases. But it also means that a transaction is not
|
||||
durable until the callback has been invoked with a `nil` error value.
|
||||
|
||||
### Using key/value pairs
|
||||
To save a key/value pair, use the `Txn.Set()` method:
|
||||
|
||||
```go
|
||||
err := db.Update(func(txn *badger.Txn) error {
|
||||
err := txn.Set([]byte("answer"), []byte("42"))
|
||||
return err
|
||||
})
|
||||
```
|
||||
|
||||
Key/Value pair can also be saved by first creating `Entry`, then setting this
|
||||
`Entry` using `Txn.SetEntry()`. `Entry` also exposes methods to set properties
|
||||
on it.
|
||||
|
||||
```go
|
||||
err := db.Update(func(txn *badger.Txn) error {
|
||||
e := badger.NewEntry([]byte("answer"), []byte("42"))
|
||||
err := txn.SetEntry(e)
|
||||
return err
|
||||
})
|
||||
```
|
||||
|
||||
This will set the value of the `"answer"` key to `"42"`. To retrieve this
|
||||
value, we can use the `Txn.Get()` method:
|
||||
|
||||
```go
|
||||
err := db.View(func(txn *badger.Txn) error {
|
||||
item, err := txn.Get([]byte("answer"))
|
||||
handle(err)
|
||||
|
||||
var valNot, valCopy []byte
|
||||
err := item.Value(func(val []byte) error {
|
||||
// This func with val would only be called if item.Value encounters no error.
|
||||
|
||||
// Accessing val here is valid.
|
||||
fmt.Printf("The answer is: %s\n", val)
|
||||
|
||||
// Copying or parsing val is valid.
|
||||
valCopy = append([]byte{}, val...)
|
||||
|
||||
// Assigning val slice to another variable is NOT OK.
|
||||
valNot = val // Do not do this.
|
||||
return nil
|
||||
})
|
||||
handle(err)
|
||||
|
||||
// DO NOT access val here. It is the most common cause of bugs.
|
||||
fmt.Printf("NEVER do this. %s\n", valNot)
|
||||
|
||||
// You must copy it to use it outside item.Value(...).
|
||||
fmt.Printf("The answer is: %s\n", valCopy)
|
||||
|
||||
// Alternatively, you could also use item.ValueCopy().
|
||||
valCopy, err = item.ValueCopy(nil)
|
||||
handle(err)
|
||||
fmt.Printf("The answer is: %s\n", valCopy)
|
||||
|
||||
return nil
|
||||
})
|
||||
```
|
||||
|
||||
`Txn.Get()` returns `ErrKeyNotFound` if the value is not found.
|
||||
|
||||
Please note that values returned from `Get()` are only valid while the
|
||||
transaction is open. If you need to use a value outside of the transaction
|
||||
then you must use `copy()` to copy it to another byte slice.
|
||||
|
||||
Use the `Txn.Delete()` method to delete a key.
|
||||
|
||||
### Monotonically increasing integers
|
||||
|
||||
To get unique monotonically increasing integers with strong durability, you can
|
||||
use the `DB.GetSequence` method. This method returns a `Sequence` object, which
|
||||
is thread-safe and can be used concurrently via various goroutines.
|
||||
|
||||
Badger would lease a range of integers to hand out from memory, with the
|
||||
bandwidth provided to `DB.GetSequence`. The frequency at which disk writes are
|
||||
done is determined by this lease bandwidth and the frequency of `Next`
|
||||
invocations. Setting a bandwidth too low would do more disk writes, setting it
|
||||
too high would result in wasted integers if Badger is closed or crashes.
|
||||
To avoid wasted integers, call `Release` before closing Badger.
|
||||
|
||||
```go
|
||||
seq, err := db.GetSequence(key, 1000)
|
||||
defer seq.Release()
|
||||
for {
|
||||
num, err := seq.Next()
|
||||
}
|
||||
```
|
||||
|
||||
### Merge Operations
|
||||
Badger provides support for ordered merge operations. You can define a func
|
||||
of type `MergeFunc` which takes in an existing value, and a value to be
|
||||
_merged_ with it. It returns a new value which is the result of the _merge_
|
||||
operation. All values are specified in byte arrays. For e.g., here is a merge
|
||||
function (`add`) which appends a `[]byte` value to an existing `[]byte` value.
|
||||
|
||||
```Go
|
||||
// Merge function to append one byte slice to another
|
||||
func add(originalValue, newValue []byte) []byte {
|
||||
return append(originalValue, newValue...)
|
||||
}
|
||||
```
|
||||
|
||||
This function can then be passed to the `DB.GetMergeOperator()` method, along
|
||||
with a key, and a duration value. The duration specifies how often the merge
|
||||
function is run on values that have been added using the `MergeOperator.Add()`
|
||||
method.
|
||||
|
||||
`MergeOperator.Get()` method can be used to retrieve the cumulative value of the key
|
||||
associated with the merge operation.
|
||||
|
||||
```Go
|
||||
key := []byte("merge")
|
||||
|
||||
m := db.GetMergeOperator(key, add, 200*time.Millisecond)
|
||||
defer m.Stop()
|
||||
|
||||
m.Add([]byte("A"))
|
||||
m.Add([]byte("B"))
|
||||
m.Add([]byte("C"))
|
||||
|
||||
res, _ := m.Get() // res should have value ABC encoded
|
||||
```
|
||||
|
||||
Example: Merge operator which increments a counter
|
||||
|
||||
```Go
|
||||
func uint64ToBytes(i uint64) []byte {
|
||||
var buf [8]byte
|
||||
binary.BigEndian.PutUint64(buf[:], i)
|
||||
return buf[:]
|
||||
}
|
||||
|
||||
func bytesToUint64(b []byte) uint64 {
|
||||
return binary.BigEndian.Uint64(b)
|
||||
}
|
||||
|
||||
// Merge function to add two uint64 numbers
|
||||
func add(existing, new []byte) []byte {
|
||||
return uint64ToBytes(bytesToUint64(existing) + bytesToUint64(new))
|
||||
}
|
||||
```
|
||||
It can be used as
|
||||
```Go
|
||||
key := []byte("merge")
|
||||
|
||||
m := db.GetMergeOperator(key, add, 200*time.Millisecond)
|
||||
defer m.Stop()
|
||||
|
||||
m.Add(uint64ToBytes(1))
|
||||
m.Add(uint64ToBytes(2))
|
||||
m.Add(uint64ToBytes(3))
|
||||
|
||||
res, _ := m.Get() // res should have value 6 encoded
|
||||
```
|
||||
|
||||
### Setting Time To Live(TTL) and User Metadata on Keys
|
||||
Badger allows setting an optional Time to Live (TTL) value on keys. Once the TTL has
|
||||
elapsed, the key will no longer be retrievable and will be eligible for garbage
|
||||
collection. A TTL can be set as a `time.Duration` value using the `Entry.WithTTL()`
|
||||
and `Txn.SetEntry()` API methods.
|
||||
|
||||
```go
|
||||
err := db.Update(func(txn *badger.Txn) error {
|
||||
e := badger.NewEntry([]byte("answer"), []byte("42")).WithTTL(time.Hour)
|
||||
err := txn.SetEntry(e)
|
||||
return err
|
||||
})
|
||||
```
|
||||
|
||||
An optional user metadata value can be set on each key. A user metadata value
|
||||
is represented by a single byte. It can be used to set certain bits along
|
||||
with the key to aid in interpreting or decoding the key-value pair. User
|
||||
metadata can be set using `Entry.WithMeta()` and `Txn.SetEntry()` API methods.
|
||||
|
||||
```go
|
||||
err := db.Update(func(txn *badger.Txn) error {
|
||||
e := badger.NewEntry([]byte("answer"), []byte("42")).WithMeta(byte(1))
|
||||
err := txn.SetEntry(e)
|
||||
return err
|
||||
})
|
||||
```
|
||||
|
||||
`Entry` APIs can be used to add the user metadata and TTL for same key. This `Entry`
|
||||
then can be set using `Txn.SetEntry()`.
|
||||
|
||||
```go
|
||||
err := db.Update(func(txn *badger.Txn) error {
|
||||
e := badger.NewEntry([]byte("answer"), []byte("42")).WithMeta(byte(1)).WithTTL(time.Hour)
|
||||
err := txn.SetEntry(e)
|
||||
return err
|
||||
})
|
||||
```
|
||||
|
||||
### Iterating over keys
|
||||
To iterate over keys, we can use an `Iterator`, which can be obtained using the
|
||||
`Txn.NewIterator()` method. Iteration happens in byte-wise lexicographical sorting
|
||||
order.
|
||||
|
||||
|
||||
```go
|
||||
err := db.View(func(txn *badger.Txn) error {
|
||||
opts := badger.DefaultIteratorOptions
|
||||
opts.PrefetchSize = 10
|
||||
it := txn.NewIterator(opts)
|
||||
defer it.Close()
|
||||
for it.Rewind(); it.Valid(); it.Next() {
|
||||
item := it.Item()
|
||||
k := item.Key()
|
||||
err := item.Value(func(v []byte) error {
|
||||
fmt.Printf("key=%s, value=%s\n", k, v)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
```
|
||||
|
||||
The iterator allows you to move to a specific point in the list of keys and move
|
||||
forward or backward through the keys one at a time.
|
||||
|
||||
By default, Badger prefetches the values of the next 100 items. You can adjust
|
||||
that with the `IteratorOptions.PrefetchSize` field. However, setting it to
|
||||
a value higher than `GOMAXPROCS` (which we recommend to be 128 or higher)
|
||||
shouldn’t give any additional benefits. You can also turn off the fetching of
|
||||
values altogether. See section below on key-only iteration.
|
||||
|
||||
#### Prefix scans
|
||||
To iterate over a key prefix, you can combine `Seek()` and `ValidForPrefix()`:
|
||||
|
||||
```go
|
||||
db.View(func(txn *badger.Txn) error {
|
||||
it := txn.NewIterator(badger.DefaultIteratorOptions)
|
||||
defer it.Close()
|
||||
prefix := []byte("1234")
|
||||
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
|
||||
item := it.Item()
|
||||
k := item.Key()
|
||||
err := item.Value(func(v []byte) error {
|
||||
fmt.Printf("key=%s, value=%s\n", k, v)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
```
|
||||
|
||||
#### Key-only iteration
|
||||
Badger supports a unique mode of iteration called _key-only_ iteration. It is
|
||||
several order of magnitudes faster than regular iteration, because it involves
|
||||
access to the LSM-tree only, which is usually resident entirely in RAM. To
|
||||
enable key-only iteration, you need to set the `IteratorOptions.PrefetchValues`
|
||||
field to `false`. This can also be used to do sparse reads for selected keys
|
||||
during an iteration, by calling `item.Value()` only when required.
|
||||
|
||||
```go
|
||||
err := db.View(func(txn *badger.Txn) error {
|
||||
opts := badger.DefaultIteratorOptions
|
||||
opts.PrefetchValues = false
|
||||
it := txn.NewIterator(opts)
|
||||
defer it.Close()
|
||||
for it.Rewind(); it.Valid(); it.Next() {
|
||||
item := it.Item()
|
||||
k := item.Key()
|
||||
fmt.Printf("key=%s\n", k)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
```
|
||||
|
||||
### Stream
|
||||
Badger provides a Stream framework, which concurrently iterates over all or a
|
||||
portion of the DB, converting data into custom key-values, and streams it out
|
||||
serially to be sent over network, written to disk, or even written back to
|
||||
Badger. This is a lot faster way to iterate over Badger than using a single
|
||||
Iterator. Stream supports Badger in both managed and normal mode.
|
||||
|
||||
Stream uses the natural boundaries created by SSTables within the LSM tree, to
|
||||
quickly generate key ranges. Each goroutine then picks a range and runs an
|
||||
iterator to iterate over it. Each iterator iterates over all versions of values
|
||||
and is created from the same transaction, thus working over a snapshot of the
|
||||
DB. Every time a new key is encountered, it calls `ChooseKey(item)`, followed
|
||||
by `KeyToList(key, itr)`. This allows a user to select or reject that key, and
|
||||
if selected, convert the value versions into custom key-values. The goroutine
|
||||
batches up 4MB worth of key-values, before sending it over to a channel.
|
||||
Another goroutine further batches up data from this channel using *smart
|
||||
batching* algorithm and calls `Send` serially.
|
||||
|
||||
This framework is designed for high throughput key-value iteration, spreading
|
||||
the work of iteration across many goroutines. `DB.Backup` uses this framework to
|
||||
provide full and incremental backups quickly. Dgraph is a heavy user of this
|
||||
framework. In fact, this framework was developed and used within Dgraph, before
|
||||
getting ported over to Badger.
|
||||
|
||||
```go
|
||||
stream := db.NewStream()
|
||||
// db.NewStreamAt(readTs) for managed mode.
|
||||
|
||||
// -- Optional settings
|
||||
stream.NumGo = 16 // Set number of goroutines to use for iteration.
|
||||
stream.Prefix = []byte("some-prefix") // Leave nil for iteration over the whole DB.
|
||||
stream.LogPrefix = "Badger.Streaming" // For identifying stream logs. Outputs to Logger.
|
||||
|
||||
// ChooseKey is called concurrently for every key. If left nil, assumes true by default.
|
||||
stream.ChooseKey = func(item *badger.Item) bool {
|
||||
return bytes.HasSuffix(item.Key(), []byte("er"))
|
||||
}
|
||||
|
||||
// KeyToList is called concurrently for chosen keys. This can be used to convert
|
||||
// Badger data into custom key-values. If nil, uses stream.ToList, a default
|
||||
// implementation, which picks all valid key-values.
|
||||
stream.KeyToList = nil
|
||||
|
||||
// -- End of optional settings.
|
||||
|
||||
// Send is called serially, while Stream.Orchestrate is running.
|
||||
stream.Send = func(list *pb.KVList) error {
|
||||
return proto.MarshalText(w, list) // Write to w.
|
||||
}
|
||||
|
||||
// Run the stream
|
||||
if err := stream.Orchestrate(context.Background()); err != nil {
|
||||
return err
|
||||
}
|
||||
// Done.
|
||||
```
|
||||
|
||||
### Garbage Collection
|
||||
Badger values need to be garbage collected, because of two reasons:
|
||||
|
||||
* Badger keeps values separately from the LSM tree. This means that the compaction operations
|
||||
that clean up the LSM tree do not touch the values at all. Values need to be cleaned up
|
||||
separately.
|
||||
|
||||
* Concurrent read/write transactions could leave behind multiple values for a single key, because they
|
||||
are stored with different versions. These could accumulate, and take up unneeded space beyond the
|
||||
time these older versions are needed.
|
||||
|
||||
Badger relies on the client to perform garbage collection at a time of their choosing. It provides
|
||||
the following method, which can be invoked at an appropriate time:
|
||||
|
||||
* `DB.RunValueLogGC()`: This method is designed to do garbage collection while
|
||||
Badger is online. Along with randomly picking a file, it uses statistics generated by the
|
||||
LSM-tree compactions to pick files that are likely to lead to maximum space
|
||||
reclamation. It is recommended to be called during periods of low activity in
|
||||
your system, or periodically. One call would only result in removal of at max
|
||||
one log file. As an optimization, you could also immediately re-run it whenever
|
||||
it returns nil error (indicating a successful value log GC), as shown below.
|
||||
|
||||
```go
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
again:
|
||||
err := db.RunValueLogGC(0.7)
|
||||
if err == nil {
|
||||
goto again
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* `DB.PurgeOlderVersions()`: This method is **DEPRECATED** since v1.5.0. Now, Badger's LSM tree automatically discards older/invalid versions of keys.
|
||||
|
||||
**Note: The RunValueLogGC method would not garbage collect the latest value log.**
|
||||
|
||||
### Database backup
|
||||
There are two public API methods `DB.Backup()` and `DB.Load()` which can be
|
||||
used to do online backups and restores. Badger v0.9 provides a CLI tool
|
||||
`badger`, which can do offline backup/restore. Make sure you have `$GOPATH/bin`
|
||||
in your PATH to use this tool.
|
||||
|
||||
The command below will create a version-agnostic backup of the database, to a
|
||||
file `badger.bak` in the current working directory
|
||||
|
||||
```
|
||||
badger backup --dir <path/to/badgerdb>
|
||||
```
|
||||
|
||||
To restore `badger.bak` in the current working directory to a new database:
|
||||
|
||||
```
|
||||
badger restore --dir <path/to/badgerdb>
|
||||
```
|
||||
|
||||
See `badger --help` for more details.
|
||||
|
||||
If you have a Badger database that was created using v0.8 (or below), you can
|
||||
use the `badger_backup` tool provided in v0.8.1, and then restore it using the
|
||||
command above to upgrade your database to work with the latest version.
|
||||
|
||||
```
|
||||
badger_backup --dir <path/to/badgerdb> --backup-file badger.bak
|
||||
```
|
||||
|
||||
We recommend all users to use the `Backup` and `Restore` APIs and tools. However,
|
||||
Badger is also rsync-friendly because all files are immutable, barring the
|
||||
latest value log which is append-only. So, rsync can be used as rudimentary way
|
||||
to perform a backup. In the following script, we repeat rsync to ensure that the
|
||||
LSM tree remains consistent with the MANIFEST file while doing a full backup.
|
||||
|
||||
```
|
||||
#!/bin/bash
|
||||
set -o history
|
||||
set -o histexpand
|
||||
# Makes a complete copy of a Badger database directory.
|
||||
# Repeat rsync if the MANIFEST and SSTables are updated.
|
||||
rsync -avz --delete db/ dst
|
||||
while !! | grep -q "(MANIFEST\|\.sst)$"; do :; done
|
||||
```
|
||||
|
||||
### Memory usage
|
||||
Badger's memory usage can be managed by tweaking several options available in
|
||||
the `Options` struct that is passed in when opening the database using
|
||||
`DB.Open`.
|
||||
|
||||
- `Options.ValueLogLoadingMode` can be set to `options.FileIO` (instead of the
|
||||
default `options.MemoryMap`) to avoid memory-mapping log files. This can be
|
||||
useful in environments with low RAM.
|
||||
- Number of memtables (`Options.NumMemtables`)
|
||||
- If you modify `Options.NumMemtables`, also adjust `Options.NumLevelZeroTables` and
|
||||
`Options.NumLevelZeroTablesStall` accordingly.
|
||||
- Number of concurrent compactions (`Options.NumCompactors`)
|
||||
- Mode in which LSM tree is loaded (`Options.TableLoadingMode`)
|
||||
- Size of table (`Options.MaxTableSize`)
|
||||
- Size of value log file (`Options.ValueLogFileSize`)
|
||||
|
||||
If you want to decrease the memory usage of Badger instance, tweak these
|
||||
options (ideally one at a time) until you achieve the desired
|
||||
memory usage.
|
||||
|
||||
### Statistics
|
||||
Badger records metrics using the [expvar] package, which is included in the Go
|
||||
standard library. All the metrics are documented in [y/metrics.go][metrics]
|
||||
file.
|
||||
|
||||
`expvar` package adds a handler in to the default HTTP server (which has to be
|
||||
started explicitly), and serves up the metrics at the `/debug/vars` endpoint.
|
||||
These metrics can then be collected by a system like [Prometheus], to get
|
||||
better visibility into what Badger is doing.
|
||||
|
||||
[expvar]: https://golang.org/pkg/expvar/
|
||||
[metrics]: https://github.com/dgraph-io/badger/blob/master/y/metrics.go
|
||||
[Prometheus]: https://prometheus.io/
|
||||
|
||||
## Resources
|
||||
|
||||
### Blog Posts
|
||||
1. [Introducing Badger: A fast key-value store written natively in
|
||||
Go](https://open.dgraph.io/post/badger/)
|
||||
2. [Make Badger crash resilient with ALICE](https://blog.dgraph.io/post/alice/)
|
||||
3. [Badger vs LMDB vs BoltDB: Benchmarking key-value databases in Go](https://blog.dgraph.io/post/badger-lmdb-boltdb/)
|
||||
4. [Concurrent ACID Transactions in Badger](https://blog.dgraph.io/post/badger-txn/)
|
||||
|
||||
## Design
|
||||
Badger was written with these design goals in mind:
|
||||
|
||||
- Write a key-value database in pure Go.
|
||||
- Use latest research to build the fastest KV database for data sets spanning terabytes.
|
||||
- Optimize for SSDs.
|
||||
|
||||
Badger’s design is based on a paper titled _[WiscKey: Separating Keys from
|
||||
Values in SSD-conscious Storage][wisckey]_.
|
||||
|
||||
[wisckey]: https://www.usenix.org/system/files/conference/fast16/fast16-papers-lu.pdf
|
||||
|
||||
### Comparisons
|
||||
| Feature | Badger | RocksDB | BoltDB |
|
||||
| ------- | ------ | ------- | ------ |
|
||||
| Design | LSM tree with value log | LSM tree only | B+ tree |
|
||||
| High Read throughput | Yes | No | Yes |
|
||||
| High Write throughput | Yes | Yes | No |
|
||||
| Designed for SSDs | Yes (with latest research <sup>1</sup>) | Not specifically <sup>2</sup> | No |
|
||||
| Embeddable | Yes | Yes | Yes |
|
||||
| Sorted KV access | Yes | Yes | Yes |
|
||||
| Pure Go (no Cgo) | Yes | No | Yes |
|
||||
| Transactions | Yes, ACID, concurrent with SSI<sup>3</sup> | Yes (but non-ACID) | Yes, ACID |
|
||||
| Snapshots | Yes | Yes | Yes |
|
||||
| TTL support | Yes | Yes | No |
|
||||
| 3D access (key-value-version) | Yes<sup>4</sup> | No | No |
|
||||
|
||||
<sup>1</sup> The [WISCKEY paper][wisckey] (on which Badger is based) saw big
|
||||
wins with separating values from keys, significantly reducing the write
|
||||
amplification compared to a typical LSM tree.
|
||||
|
||||
<sup>2</sup> RocksDB is an SSD optimized version of LevelDB, which was designed specifically for rotating disks.
|
||||
As such RocksDB's design isn't aimed at SSDs.
|
||||
|
||||
<sup>3</sup> SSI: Serializable Snapshot Isolation. For more details, see the blog post [Concurrent ACID Transactions in Badger](https://blog.dgraph.io/post/badger-txn/)
|
||||
|
||||
<sup>4</sup> Badger provides direct access to value versions via its Iterator API.
|
||||
Users can also specify how many versions to keep per key via Options.
|
||||
|
||||
### Benchmarks
|
||||
We have run comprehensive benchmarks against RocksDB, Bolt and LMDB. The
|
||||
benchmarking code, and the detailed logs for the benchmarks can be found in the
|
||||
[badger-bench] repo. More explanation, including graphs can be found the blog posts (linked
|
||||
above).
|
||||
|
||||
[badger-bench]: https://github.com/dgraph-io/badger-bench
|
||||
|
||||
## Other Projects Using Badger
|
||||
Below is a list of known projects that use Badger:
|
||||
|
||||
* [0-stor](https://github.com/zero-os/0-stor) - Single device object store.
|
||||
* [Dgraph](https://github.com/dgraph-io/dgraph) - Distributed graph database.
|
||||
* [Jaeger](https://github.com/jaegertracing/jaeger) - Distributed tracing platform.
|
||||
* [TalariaDB](https://github.com/grab/talaria) - Distributed, low latency time-series database.
|
||||
* [Dispatch Protocol](https://github.com/dispatchlabs/disgo) - Blockchain protocol for distributed application data analytics.
|
||||
* [Sandglass](https://github.com/celrenheit/sandglass) - distributed, horizontally scalable, persistent, time sorted message queue.
|
||||
* [Usenet Express](https://usenetexpress.com/) - Serving over 300TB of data with Badger.
|
||||
* [go-ipfs](https://github.com/ipfs/go-ipfs) - Go client for the InterPlanetary File System (IPFS), a new hypermedia distribution protocol.
|
||||
* [gorush](https://github.com/appleboy/gorush) - A push notification server written in Go.
|
||||
* [emitter](https://github.com/emitter-io/emitter) - Scalable, low latency, distributed pub/sub broker with message storage, uses MQTT, gossip and badger.
|
||||
* [GarageMQ](https://github.com/valinurovam/garagemq) - AMQP server written in Go.
|
||||
* [RedixDB](https://alash3al.github.io/redix/) - A real-time persistent key-value store with the same redis protocol.
|
||||
* [BBVA](https://github.com/BBVA/raft-badger) - Raft backend implementation using BadgerDB for Hashicorp raft.
|
||||
* [Riot](https://github.com/go-ego/riot) - An open-source, distributed search engine.
|
||||
* [Fantom](https://github.com/Fantom-foundation/go-lachesis) - aBFT Consensus platform for distributed applications.
|
||||
* [decred](https://github.com/decred/dcrdata) - An open, progressive, and self-funding cryptocurrency with a system of community-based governance integrated into its blockchain.
|
||||
* [OpenNetSys](https://github.com/opennetsys/c3-go) - Create useful dApps in any software language.
|
||||
* [HoneyTrap](https://github.com/honeytrap/honeytrap) - An extensible and opensource system for running, monitoring and managing honeypots.
|
||||
* [Insolar](https://github.com/insolar/insolar) - Enterprise-ready blockchain platform.
|
||||
* [IoTeX](https://github.com/iotexproject/iotex-core) - The next generation of the decentralized network for IoT powered by scalability- and privacy-centric blockchains.
|
||||
* [go-sessions](https://github.com/kataras/go-sessions) - The sessions manager for Go net/http and fasthttp.
|
||||
* [Babble](https://github.com/mosaicnetworks/babble) - BFT Consensus platform for distributed applications.
|
||||
* [Tormenta](https://github.com/jpincas/tormenta) - Embedded object-persistence layer / simple JSON database for Go projects.
|
||||
* [BadgerHold](https://github.com/timshannon/badgerhold) - An embeddable NoSQL store for querying Go types built on Badger
|
||||
* [Goblero](https://github.com/didil/goblero) - Pure Go embedded persistent job queue backed by BadgerDB
|
||||
* [Surfline](https://www.surfline.com) - Serving global wave and weather forecast data with Badger.
|
||||
* [Cete](https://github.com/mosuka/cete) - Simple and highly available distributed key-value store built on Badger. Makes it easy bringing up a cluster of Badger with Raft consensus algorithm by hashicorp/raft.
|
||||
* [Volument](https://volument.com/) - A new take on website analytics backed by Badger.
|
||||
* [Sloop](https://github.com/salesforce/sloop) - Kubernetes History Visualization.
|
||||
* [KVdb](https://kvdb.io/) - Hosted key-value store and serverless platform built on top of Badger.
|
||||
* [Dkron](https://dkron.io/) - Distributed, fault tolerant job scheduling system.
|
||||
|
||||
If you are using Badger in a project please send a pull request to add it to the list.
|
||||
|
||||
## Frequently Asked Questions
|
||||
### My writes are getting stuck. Why?
|
||||
|
||||
**Update: With the new `Value(func(v []byte))` API, this deadlock can no longer
|
||||
happen.**
|
||||
|
||||
The following is true for users on Badger v1.x.
|
||||
|
||||
This can happen if a long running iteration with `Prefetch` is set to false, but
|
||||
a `Item::Value` call is made internally in the loop. That causes Badger to
|
||||
acquire read locks over the value log files to avoid value log GC removing the
|
||||
file from underneath. As a side effect, this also blocks a new value log GC
|
||||
file from being created, when the value log file boundary is hit.
|
||||
|
||||
Please see Github issues [#293](https://github.com/dgraph-io/badger/issues/293)
|
||||
and [#315](https://github.com/dgraph-io/badger/issues/315).
|
||||
|
||||
There are multiple workarounds during iteration:
|
||||
|
||||
1. Use `Item::ValueCopy` instead of `Item::Value` when retrieving value.
|
||||
1. Set `Prefetch` to true. Badger would then copy over the value and release the
|
||||
file lock immediately.
|
||||
1. When `Prefetch` is false, don't call `Item::Value` and do a pure key-only
|
||||
iteration. This might be useful if you just want to delete a lot of keys.
|
||||
1. Do the writes in a separate transaction after the reads.
|
||||
|
||||
### My writes are really slow. Why?
|
||||
|
||||
Are you creating a new transaction for every single key update, and waiting for
|
||||
it to `Commit` fully before creating a new one? This will lead to very low
|
||||
throughput.
|
||||
|
||||
We have created `WriteBatch` API which provides a way to batch up
|
||||
many updates into a single transaction and `Commit` that transaction using
|
||||
callbacks to avoid blocking. This amortizes the cost of a transaction really
|
||||
well, and provides the most efficient way to do bulk writes.
|
||||
|
||||
```go
|
||||
wb := db.NewWriteBatch()
|
||||
defer wb.Cancel()
|
||||
|
||||
for i := 0; i < N; i++ {
|
||||
err := wb.Set(key(i), value(i), 0) // Will create txns as needed.
|
||||
handle(err)
|
||||
}
|
||||
handle(wb.Flush()) // Wait for all txns to finish.
|
||||
```
|
||||
|
||||
Note that `WriteBatch` API does not allow any reads. For read-modify-write
|
||||
workloads, you should be using the `Transaction` API.
|
||||
|
||||
### I don't see any disk writes. Why?
|
||||
|
||||
If you're using Badger with `SyncWrites=false`, then your writes might not be written to value log
|
||||
and won't get synced to disk immediately. Writes to LSM tree are done inmemory first, before they
|
||||
get compacted to disk. The compaction would only happen once `MaxTableSize` has been reached. So, if
|
||||
you're doing a few writes and then checking, you might not see anything on disk. Once you `Close`
|
||||
the database, you'll see these writes on disk.
|
||||
|
||||
### Reverse iteration doesn't give me the right results.
|
||||
|
||||
Just like forward iteration goes to the first key which is equal or greater than the SEEK key, reverse iteration goes to the first key which is equal or lesser than the SEEK key. Therefore, SEEK key would not be part of the results. You can typically add a `0xff` byte as a suffix to the SEEK key to include it in the results. See the following issues: [#436](https://github.com/dgraph-io/badger/issues/436) and [#347](https://github.com/dgraph-io/badger/issues/347).
|
||||
|
||||
### Which instances should I use for Badger?
|
||||
|
||||
We recommend using instances which provide local SSD storage, without any limit
|
||||
on the maximum IOPS. In AWS, these are storage optimized instances like i3. They
|
||||
provide local SSDs which clock 100K IOPS over 4KB blocks easily.
|
||||
|
||||
### I'm getting a closed channel error. Why?
|
||||
|
||||
```
|
||||
panic: close of closed channel
|
||||
panic: send on closed channel
|
||||
```
|
||||
|
||||
If you're seeing panics like above, this would be because you're operating on a closed DB. This can happen, if you call `Close()` before sending a write, or multiple times. You should ensure that you only call `Close()` once, and all your read/write operations finish before closing.
|
||||
|
||||
### Are there any Go specific settings that I should use?
|
||||
|
||||
We *highly* recommend setting a high number for `GOMAXPROCS`, which allows Go to
|
||||
observe the full IOPS throughput provided by modern SSDs. In Dgraph, we have set
|
||||
it to 128. For more details, [see this
|
||||
thread](https://groups.google.com/d/topic/golang-nuts/jPb_h3TvlKE/discussion).
|
||||
|
||||
### Are there any Linux specific settings that I should use?
|
||||
|
||||
We recommend setting `max file descriptors` to a high number depending upon the expected size of
|
||||
your data. On Linux and Mac, you can check the file descriptor limit with `ulimit -n -H` for the
|
||||
hard limit and `ulimit -n -S` for the soft limit. A soft limit of `65535` is a good lower bound.
|
||||
You can adjust the limit as needed.
|
||||
|
||||
### I see "manifest has unsupported version: X (we support Y)" error.
|
||||
|
||||
This error means you have a badger directory which was created by an older version of badger and
|
||||
you're trying to open in a newer version of badger. The underlying data format can change across
|
||||
badger versions and users will have to migrate their data directory.
|
||||
Badger data can be migrated from version X of badger to version Y of badger by following the steps
|
||||
listed below.
|
||||
Assume you were on badger v1.6.0 and you wish to migrate to v2.0.0 version.
|
||||
1. Install badger version v1.6.0
|
||||
- `cd $GOPATH/src/github.com/dgraph-io/badger`
|
||||
- `git checkout v1.6.0`
|
||||
- `cd badger && go install`
|
||||
|
||||
This should install the old badger binary in your $GOBIN.
|
||||
2. Create Backup
|
||||
- `badger backup --dir path/to/badger/directory -f badger.backup`
|
||||
3. Install badger version v2.0.0
|
||||
- `cd $GOPATH/src/github.com/dgraph-io/badger`
|
||||
- `git checkout v2.0.0`
|
||||
- `cd badger && go install`
|
||||
|
||||
This should install new badger binary in your $GOBIN
|
||||
4. Install badger version v2.0.0
|
||||
- `badger restore --dir path/to/new/badger/directory -f badger.backup`
|
||||
|
||||
This will create a new directory on `path/to/new/badger/directory` and add badger data in
|
||||
newer format to it.
|
||||
|
||||
NOTE - The above steps shouldn't cause any data loss but please ensure the new data is valid before
|
||||
deleting the old badger directory.
|
||||
## Contact
|
||||
- Please use [discuss.dgraph.io](https://discuss.dgraph.io) for questions, feature requests and discussions.
|
||||
- Please use [Github issue tracker](https://github.com/dgraph-io/badger/issues) for filing bugs or feature requests.
|
||||
- Join [](http://slack.dgraph.io).
|
||||
- Follow us on Twitter [@dgraphlabs](https://twitter.com/dgraphlabs).
|
||||
|
47
vendor/github.com/dgraph-io/badger/VERSIONING.md
generated
vendored
Normal file
47
vendor/github.com/dgraph-io/badger/VERSIONING.md
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
# Serialization Versioning: Semantic Versioning for databases
|
||||
|
||||
Semantic Versioning, commonly known as SemVer, is a great idea that has been very widely adopted as
|
||||
a way to decide how to name software versions. The whole concept is very well summarized on
|
||||
semver.org with the following lines:
|
||||
|
||||
> Given a version number MAJOR.MINOR.PATCH, increment the:
|
||||
>
|
||||
> 1. MAJOR version when you make incompatible API changes,
|
||||
> 2. MINOR version when you add functionality in a backwards-compatible manner, and
|
||||
> 3. PATCH version when you make backwards-compatible bug fixes.
|
||||
>
|
||||
> Additional labels for pre-release and build metadata are available as extensions to the
|
||||
> MAJOR.MINOR.PATCH format.
|
||||
|
||||
Unfortunately, API changes are not the most important changes for libraries that serialize data for
|
||||
later consumption. For these libraries, such as BadgerDB, changes to the API are much easier to
|
||||
handle than change to the data format used to store data on disk.
|
||||
|
||||
## Serialization Version specification
|
||||
|
||||
Serialization Versioning, like Semantic Versioning, uses 3 numbers and also calls them
|
||||
MAJOR.MINOR.PATCH, but the semantics of the numbers are slightly modified:
|
||||
|
||||
Given a version number MAJOR.MINOR.PATCH, increment the:
|
||||
|
||||
- MAJOR version when you make changes that require a transformation of the dataset before it can be
|
||||
used again.
|
||||
- MINOR version when old datasets are still readable but the API might have changed in
|
||||
backwards-compatible or incompatible ways.
|
||||
- PATCH version when you make backwards-compatible bug fixes.
|
||||
|
||||
Additional labels for pre-release and build metadata are available as extensions to the
|
||||
MAJOR.MINOR.PATCH format.
|
||||
|
||||
Following this naming strategy, migration from v1.x to v2.x requires a migration strategy for your
|
||||
existing dataset, and as such has to be carefully planned. Migrations in between different minor
|
||||
versions (e.g. v1.5.x and v1.6.x) might break your build, as the API *might* have changed, but once
|
||||
your code compiles there's no need for any data migration. Lastly, changes in between two different
|
||||
patch versions should never break your build or dataset.
|
||||
|
||||
For more background on our decision to adopt Serialization Versioning, read the blog post
|
||||
[Semantic Versioning, Go Modules, and Databases][blog] and the original proposal on
|
||||
[this comment on Dgraph's Discuss forum][discuss].
|
||||
|
||||
[blog]: https://blog.dgraph.io/post/serialization-versioning/
|
||||
[discuss]: https://discuss.dgraph.io/t/go-modules-on-badger-and-dgraph/4662/7
|
49
vendor/github.com/dgraph-io/badger/appveyor.yml
generated
vendored
Normal file
49
vendor/github.com/dgraph-io/badger/appveyor.yml
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
# version format
|
||||
version: "{build}"
|
||||
|
||||
# Operating system (build VM template)
|
||||
os: Windows Server 2012 R2
|
||||
|
||||
# Platform.
|
||||
platform: x64
|
||||
|
||||
clone_folder: c:\gopath\src\github.com\dgraph-io\badger
|
||||
|
||||
# Environment variables
|
||||
environment:
|
||||
GOVERSION: 1.12
|
||||
GOPATH: c:\gopath
|
||||
GO111MODULE: on
|
||||
|
||||
# scripts that run after cloning repository
|
||||
install:
|
||||
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
|
||||
- go version
|
||||
- go env
|
||||
- python --version
|
||||
|
||||
# To run your custom scripts instead of automatic MSBuild
|
||||
build_script:
|
||||
# We need to disable firewall - https://github.com/appveyor/ci/issues/1579#issuecomment-309830648
|
||||
- ps: Disable-NetFirewallRule -DisplayName 'File and Printer Sharing (SMB-Out)'
|
||||
- cd c:\gopath\src\github.com\dgraph-io\badger
|
||||
- git branch
|
||||
- go get -t ./...
|
||||
|
||||
# To run your custom scripts instead of automatic tests
|
||||
test_script:
|
||||
# Unit tests
|
||||
- ps: Add-AppveyorTest "Unit Tests" -Outcome Running
|
||||
- go test -v github.com/dgraph-io/badger/...
|
||||
- go test -v -vlog_mmap=false github.com/dgraph-io/badger/...
|
||||
- ps: Update-AppveyorTest "Unit Tests" -Outcome Passed
|
||||
|
||||
notifications:
|
||||
- provider: Email
|
||||
to:
|
||||
- pawan@dgraph.io
|
||||
on_build_failure: true
|
||||
on_build_status_changed: true
|
||||
# to disable deployment
|
||||
deploy: off
|
||||
|
264
vendor/github.com/dgraph-io/badger/backup.go
generated
vendored
Normal file
264
vendor/github.com/dgraph-io/badger/backup.go
generated
vendored
Normal file
|
@ -0,0 +1,264 @@
|
|||
/*
|
||||
* Copyright 2017 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 (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/dgraph-io/badger/pb"
|
||||
"github.com/dgraph-io/badger/y"
|
||||
"github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
// flushThreshold determines when a buffer will be flushed. When performing a
|
||||
// backup/restore, the entries will be batched up until the total size of batch
|
||||
// is more than flushThreshold or entry size (without the value size) is more
|
||||
// than the maxBatchSize.
|
||||
const flushThreshold = 100 << 20
|
||||
|
||||
// Backup is a wrapper function over Stream.Backup to generate full and incremental backups of the
|
||||
// DB. For more control over how many goroutines are used to generate the backup, or if you wish to
|
||||
// backup only a certain range of keys, use Stream.Backup directly.
|
||||
func (db *DB) Backup(w io.Writer, since uint64) (uint64, error) {
|
||||
stream := db.NewStream()
|
||||
stream.LogPrefix = "DB.Backup"
|
||||
return stream.Backup(w, since)
|
||||
}
|
||||
|
||||
// Backup dumps a protobuf-encoded list of all entries in the database into the
|
||||
// given writer, that are newer than the specified version. It returns a
|
||||
// timestamp indicating when the entries were dumped which can be passed into a
|
||||
// later invocation to generate an incremental dump, of entries that have been
|
||||
// added/modified since the last invocation of Stream.Backup().
|
||||
//
|
||||
// This can be used to backup the data in a database at a given point in time.
|
||||
func (stream *Stream) Backup(w io.Writer, since uint64) (uint64, error) {
|
||||
stream.KeyToList = func(key []byte, itr *Iterator) (*pb.KVList, error) {
|
||||
list := &pb.KVList{}
|
||||
for ; itr.Valid(); itr.Next() {
|
||||
item := itr.Item()
|
||||
if !bytes.Equal(item.Key(), key) {
|
||||
return list, nil
|
||||
}
|
||||
if item.Version() < since {
|
||||
// Ignore versions less than given timestamp, or skip older
|
||||
// versions of the given key.
|
||||
return list, nil
|
||||
}
|
||||
|
||||
var valCopy []byte
|
||||
if !item.IsDeletedOrExpired() {
|
||||
// No need to copy value, if item is deleted or expired.
|
||||
var err error
|
||||
valCopy, err = item.ValueCopy(nil)
|
||||
if err != nil {
|
||||
stream.db.opt.Errorf("Key [%x, %d]. Error while fetching value [%v]\n",
|
||||
item.Key(), item.Version(), err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// clear txn bits
|
||||
meta := item.meta &^ (bitTxn | bitFinTxn)
|
||||
kv := &pb.KV{
|
||||
Key: item.KeyCopy(nil),
|
||||
Value: valCopy,
|
||||
UserMeta: []byte{item.UserMeta()},
|
||||
Version: item.Version(),
|
||||
ExpiresAt: item.ExpiresAt(),
|
||||
Meta: []byte{meta},
|
||||
}
|
||||
list.Kv = append(list.Kv, kv)
|
||||
|
||||
switch {
|
||||
case item.DiscardEarlierVersions():
|
||||
// If we need to discard earlier versions of this item, add a delete
|
||||
// marker just below the current version.
|
||||
list.Kv = append(list.Kv, &pb.KV{
|
||||
Key: item.KeyCopy(nil),
|
||||
Version: item.Version() - 1,
|
||||
Meta: []byte{bitDelete},
|
||||
})
|
||||
return list, nil
|
||||
|
||||
case item.IsDeletedOrExpired():
|
||||
return list, nil
|
||||
}
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
var maxVersion uint64
|
||||
stream.Send = func(list *pb.KVList) error {
|
||||
for _, kv := range list.Kv {
|
||||
if maxVersion < kv.Version {
|
||||
maxVersion = kv.Version
|
||||
}
|
||||
}
|
||||
return writeTo(list, w)
|
||||
}
|
||||
|
||||
if err := stream.Orchestrate(context.Background()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return maxVersion, nil
|
||||
}
|
||||
|
||||
func writeTo(list *pb.KVList, w io.Writer) error {
|
||||
if err := binary.Write(w, binary.LittleEndian, uint64(proto.Size(list))); err != nil {
|
||||
return err
|
||||
}
|
||||
buf, err := proto.Marshal(list)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
// KVLoader is used to write KVList objects in to badger. It can be used to restore a backup.
|
||||
type KVLoader struct {
|
||||
db *DB
|
||||
throttle *y.Throttle
|
||||
entries []*Entry
|
||||
entriesSize int64
|
||||
totalSize int64
|
||||
}
|
||||
|
||||
// NewKVLoader returns a new instance of KVLoader.
|
||||
func (db *DB) NewKVLoader(maxPendingWrites int) *KVLoader {
|
||||
return &KVLoader{
|
||||
db: db,
|
||||
throttle: y.NewThrottle(maxPendingWrites),
|
||||
entries: make([]*Entry, 0, db.opt.maxBatchCount),
|
||||
}
|
||||
}
|
||||
|
||||
// Set writes the key-value pair to the database.
|
||||
func (l *KVLoader) Set(kv *pb.KV) error {
|
||||
var userMeta, meta byte
|
||||
if len(kv.UserMeta) > 0 {
|
||||
userMeta = kv.UserMeta[0]
|
||||
}
|
||||
if len(kv.Meta) > 0 {
|
||||
meta = kv.Meta[0]
|
||||
}
|
||||
e := &Entry{
|
||||
Key: y.KeyWithTs(kv.Key, kv.Version),
|
||||
Value: kv.Value,
|
||||
UserMeta: userMeta,
|
||||
ExpiresAt: kv.ExpiresAt,
|
||||
meta: meta,
|
||||
}
|
||||
estimatedSize := int64(e.estimateSize(l.db.opt.ValueThreshold))
|
||||
// Flush entries if inserting the next entry would overflow the transactional limits.
|
||||
if int64(len(l.entries))+1 >= l.db.opt.maxBatchCount ||
|
||||
l.entriesSize+estimatedSize >= l.db.opt.maxBatchSize ||
|
||||
l.totalSize >= flushThreshold {
|
||||
if err := l.send(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
l.entries = append(l.entries, e)
|
||||
l.entriesSize += estimatedSize
|
||||
l.totalSize += estimatedSize + int64(len(e.Value))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *KVLoader) send() error {
|
||||
if err := l.throttle.Do(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := l.db.batchSetAsync(l.entries, func(err error) {
|
||||
l.throttle.Done(err)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.entries = make([]*Entry, 0, l.db.opt.maxBatchCount)
|
||||
l.entriesSize = 0
|
||||
l.totalSize = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// Finish is meant to be called after all the key-value pairs have been loaded.
|
||||
func (l *KVLoader) Finish() error {
|
||||
if len(l.entries) > 0 {
|
||||
if err := l.send(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return l.throttle.Finish()
|
||||
}
|
||||
|
||||
// Load reads a protobuf-encoded list of all entries from a reader and writes
|
||||
// them to the database. This can be used to restore the database from a backup
|
||||
// made by calling DB.Backup(). If more complex logic is needed to restore a badger
|
||||
// backup, the KVLoader interface should be used instead.
|
||||
//
|
||||
// DB.Load() should be called on a database that is not running any other
|
||||
// concurrent transactions while it is running.
|
||||
func (db *DB) Load(r io.Reader, maxPendingWrites int) error {
|
||||
br := bufio.NewReaderSize(r, 16<<10)
|
||||
unmarshalBuf := make([]byte, 1<<10)
|
||||
|
||||
ldr := db.NewKVLoader(maxPendingWrites)
|
||||
for {
|
||||
var sz uint64
|
||||
err := binary.Read(br, binary.LittleEndian, &sz)
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cap(unmarshalBuf) < int(sz) {
|
||||
unmarshalBuf = make([]byte, sz)
|
||||
}
|
||||
|
||||
if _, err = io.ReadFull(br, unmarshalBuf[:sz]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
list := &pb.KVList{}
|
||||
if err := proto.Unmarshal(unmarshalBuf[:sz], list); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, kv := range list.Kv {
|
||||
if err := ldr.Set(kv); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update nextTxnTs, memtable stores this
|
||||
// timestamp in badger head when flushed.
|
||||
if kv.Version >= db.orc.nextTxnTs {
|
||||
db.orc.nextTxnTs = kv.Version + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := ldr.Finish(); err != nil {
|
||||
return err
|
||||
}
|
||||
db.orc.txnMark.Done(db.orc.nextTxnTs - 1)
|
||||
return nil
|
||||
}
|
171
vendor/github.com/dgraph-io/badger/batch.go
generated
vendored
Normal file
171
vendor/github.com/dgraph-io/badger/batch.go
generated
vendored
Normal file
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* 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"
|
||||
|
||||
"github.com/dgraph-io/badger/y"
|
||||
)
|
||||
|
||||
// WriteBatch holds the necessary info to perform batched writes.
|
||||
type WriteBatch struct {
|
||||
sync.Mutex
|
||||
txn *Txn
|
||||
db *DB
|
||||
throttle *y.Throttle
|
||||
err error
|
||||
commitTs uint64
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if db.opt.managedTxns {
|
||||
panic("cannot use NewWriteBatch in managed mode. Use NewWriteBatchAt instead")
|
||||
}
|
||||
return db.newWriteBatch()
|
||||
}
|
||||
|
||||
func (db *DB) newWriteBatch() *WriteBatch {
|
||||
return &WriteBatch{
|
||||
db: db,
|
||||
txn: db.newTransaction(true, true),
|
||||
throttle: y.NewThrottle(16),
|
||||
}
|
||||
}
|
||||
|
||||
// SetMaxPendingTxns sets a limit on maximum number of pending transactions while writing batches.
|
||||
// This function should be called before using WriteBatch. Default value of MaxPendingTxns is
|
||||
// 16 to minimise memory usage.
|
||||
func (wb *WriteBatch) SetMaxPendingTxns(max int) {
|
||||
wb.throttle = y.NewThrottle(max)
|
||||
}
|
||||
|
||||
// Cancel function must be called if there's a chance that Flush might not get
|
||||
// called. If neither Flush or Cancel is called, the transaction oracle would
|
||||
// never get a chance to clear out the row commit timestamp map, thus causing an
|
||||
// unbounded memory consumption. Typically, you can call Cancel as a defer
|
||||
// statement right after NewWriteBatch is called.
|
||||
//
|
||||
// Note that any committed writes would still go through despite calling Cancel.
|
||||
func (wb *WriteBatch) Cancel() {
|
||||
if err := wb.throttle.Finish(); err != nil {
|
||||
wb.db.opt.Errorf("WatchBatch.Cancel error while finishing: %v", err)
|
||||
}
|
||||
wb.txn.Discard()
|
||||
}
|
||||
|
||||
func (wb *WriteBatch) callback(err error) {
|
||||
// sync.WaitGroup is thread-safe, so it doesn't need to be run inside wb.Lock.
|
||||
defer wb.throttle.Done(err)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
wb.Lock()
|
||||
defer wb.Unlock()
|
||||
if wb.err != nil {
|
||||
return
|
||||
}
|
||||
wb.err = err
|
||||
}
|
||||
|
||||
// SetEntry is the equivalent of Txn.SetEntry.
|
||||
func (wb *WriteBatch) SetEntry(e *Entry) error {
|
||||
wb.Lock()
|
||||
defer wb.Unlock()
|
||||
|
||||
if err := wb.txn.SetEntry(e); 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.SetEntry(e); err != nil {
|
||||
wb.err = err
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set is equivalent of Txn.Set().
|
||||
func (wb *WriteBatch) Set(k, v []byte) error {
|
||||
e := &Entry{Key: k, Value: v}
|
||||
return wb.SetEntry(e)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
if err := wb.throttle.Do(); err != nil {
|
||||
return err
|
||||
}
|
||||
wb.txn.CommitWith(wb.callback)
|
||||
wb.txn = wb.db.newTransaction(true, true)
|
||||
wb.txn.readTs = 0 // We're not reading anything.
|
||||
wb.txn.commitTs = wb.commitTs
|
||||
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()
|
||||
|
||||
if err := wb.throttle.Finish(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
213
vendor/github.com/dgraph-io/badger/compaction.go
generated
vendored
Normal file
213
vendor/github.com/dgraph-io/badger/compaction.go
generated
vendored
Normal file
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
* Copyright 2017 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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/net/trace"
|
||||
|
||||
"github.com/dgraph-io/badger/table"
|
||||
"github.com/dgraph-io/badger/y"
|
||||
)
|
||||
|
||||
type keyRange struct {
|
||||
left []byte
|
||||
right []byte
|
||||
inf bool
|
||||
}
|
||||
|
||||
var infRange = keyRange{inf: true}
|
||||
|
||||
func (r keyRange) String() string {
|
||||
return fmt.Sprintf("[left=%x, right=%x, inf=%v]", r.left, r.right, r.inf)
|
||||
}
|
||||
|
||||
func (r keyRange) equals(dst keyRange) bool {
|
||||
return bytes.Equal(r.left, dst.left) &&
|
||||
bytes.Equal(r.right, dst.right) &&
|
||||
r.inf == dst.inf
|
||||
}
|
||||
|
||||
func (r keyRange) overlapsWith(dst keyRange) bool {
|
||||
if r.inf || dst.inf {
|
||||
return true
|
||||
}
|
||||
|
||||
// If my left is greater than dst right, we have no overlap.
|
||||
if y.CompareKeys(r.left, dst.right) > 0 {
|
||||
return false
|
||||
}
|
||||
// If my right is less than dst left, we have no overlap.
|
||||
if y.CompareKeys(r.right, dst.left) < 0 {
|
||||
return false
|
||||
}
|
||||
// We have overlap.
|
||||
return true
|
||||
}
|
||||
|
||||
func getKeyRange(tables ...*table.Table) keyRange {
|
||||
if len(tables) == 0 {
|
||||
return keyRange{}
|
||||
}
|
||||
smallest := tables[0].Smallest()
|
||||
biggest := tables[0].Biggest()
|
||||
for i := 1; i < len(tables); i++ {
|
||||
if y.CompareKeys(tables[i].Smallest(), smallest) < 0 {
|
||||
smallest = tables[i].Smallest()
|
||||
}
|
||||
if y.CompareKeys(tables[i].Biggest(), biggest) > 0 {
|
||||
biggest = tables[i].Biggest()
|
||||
}
|
||||
}
|
||||
|
||||
// We pick all the versions of the smallest and the biggest key. Note that version zero would
|
||||
// be the rightmost key, considering versions are default sorted in descending order.
|
||||
return keyRange{
|
||||
left: y.KeyWithTs(y.ParseKey(smallest), math.MaxUint64),
|
||||
right: y.KeyWithTs(y.ParseKey(biggest), 0),
|
||||
}
|
||||
}
|
||||
|
||||
type levelCompactStatus struct {
|
||||
ranges []keyRange
|
||||
delSize int64
|
||||
}
|
||||
|
||||
func (lcs *levelCompactStatus) debug() string {
|
||||
var b bytes.Buffer
|
||||
for _, r := range lcs.ranges {
|
||||
b.WriteString(r.String())
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (lcs *levelCompactStatus) overlapsWith(dst keyRange) bool {
|
||||
for _, r := range lcs.ranges {
|
||||
if r.overlapsWith(dst) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (lcs *levelCompactStatus) remove(dst keyRange) bool {
|
||||
final := lcs.ranges[:0]
|
||||
var found bool
|
||||
for _, r := range lcs.ranges {
|
||||
if !r.equals(dst) {
|
||||
final = append(final, r)
|
||||
} else {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
lcs.ranges = final
|
||||
return found
|
||||
}
|
||||
|
||||
type compactStatus struct {
|
||||
sync.RWMutex
|
||||
levels []*levelCompactStatus
|
||||
}
|
||||
|
||||
func (cs *compactStatus) toLog(tr trace.Trace) {
|
||||
cs.RLock()
|
||||
defer cs.RUnlock()
|
||||
|
||||
tr.LazyPrintf("Compaction status:")
|
||||
for i, l := range cs.levels {
|
||||
if l.debug() == "" {
|
||||
continue
|
||||
}
|
||||
tr.LazyPrintf("[%d] %s", i, l.debug())
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *compactStatus) overlapsWith(level int, this keyRange) bool {
|
||||
cs.RLock()
|
||||
defer cs.RUnlock()
|
||||
|
||||
thisLevel := cs.levels[level]
|
||||
return thisLevel.overlapsWith(this)
|
||||
}
|
||||
|
||||
func (cs *compactStatus) delSize(l int) int64 {
|
||||
cs.RLock()
|
||||
defer cs.RUnlock()
|
||||
return cs.levels[l].delSize
|
||||
}
|
||||
|
||||
type thisAndNextLevelRLocked struct{}
|
||||
|
||||
// compareAndAdd will check whether we can run this compactDef. That it doesn't overlap with any
|
||||
// other running compaction. If it can be run, it would store this run in the compactStatus state.
|
||||
func (cs *compactStatus) compareAndAdd(_ thisAndNextLevelRLocked, cd compactDef) bool {
|
||||
cs.Lock()
|
||||
defer cs.Unlock()
|
||||
|
||||
level := cd.thisLevel.level
|
||||
|
||||
y.AssertTruef(level < len(cs.levels)-1, "Got level %d. Max levels: %d", level, len(cs.levels))
|
||||
thisLevel := cs.levels[level]
|
||||
nextLevel := cs.levels[level+1]
|
||||
|
||||
if thisLevel.overlapsWith(cd.thisRange) {
|
||||
return false
|
||||
}
|
||||
if nextLevel.overlapsWith(cd.nextRange) {
|
||||
return false
|
||||
}
|
||||
// Check whether this level really needs compaction or not. Otherwise, we'll end up
|
||||
// running parallel compactions for the same level.
|
||||
// Update: We should not be checking size here. Compaction priority already did the size checks.
|
||||
// Here we should just be executing the wish of others.
|
||||
|
||||
thisLevel.ranges = append(thisLevel.ranges, cd.thisRange)
|
||||
nextLevel.ranges = append(nextLevel.ranges, cd.nextRange)
|
||||
thisLevel.delSize += cd.thisSize
|
||||
return true
|
||||
}
|
||||
|
||||
func (cs *compactStatus) delete(cd compactDef) {
|
||||
cs.Lock()
|
||||
defer cs.Unlock()
|
||||
|
||||
level := cd.thisLevel.level
|
||||
y.AssertTruef(level < len(cs.levels)-1, "Got level %d. Max levels: %d", level, len(cs.levels))
|
||||
|
||||
thisLevel := cs.levels[level]
|
||||
nextLevel := cs.levels[level+1]
|
||||
|
||||
thisLevel.delSize -= cd.thisSize
|
||||
found := thisLevel.remove(cd.thisRange)
|
||||
found = nextLevel.remove(cd.nextRange) && found
|
||||
|
||||
if !found {
|
||||
this := cd.thisRange
|
||||
next := cd.nextRange
|
||||
fmt.Printf("Looking for: [%q, %q, %v] in this level.\n", this.left, this.right, this.inf)
|
||||
fmt.Printf("This Level:\n%s\n", thisLevel.debug())
|
||||
fmt.Println()
|
||||
fmt.Printf("Looking for: [%q, %q, %v] in next level.\n", next.left, next.right, next.inf)
|
||||
fmt.Printf("Next Level:\n%s\n", nextLevel.debug())
|
||||
log.Fatal("keyRange not found")
|
||||
}
|
||||
}
|
1560
vendor/github.com/dgraph-io/badger/db.go
generated
vendored
Normal file
1560
vendor/github.com/dgraph-io/badger/db.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
150
vendor/github.com/dgraph-io/badger/dir_plan9.go
generated
vendored
Normal file
150
vendor/github.com/dgraph-io/badger/dir_plan9.go
generated
vendored
Normal file
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Copyright 2020 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"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// directoryLockGuard holds a lock on a directory and a pid file inside. The pid file isn't part
|
||||
// of the locking mechanism, it's just advisory.
|
||||
type directoryLockGuard struct {
|
||||
// File handle on the directory, which we've locked.
|
||||
f *os.File
|
||||
// The absolute path to our pid file.
|
||||
path string
|
||||
}
|
||||
|
||||
// acquireDirectoryLock gets a lock on the directory.
|
||||
// It will also write our pid to dirPath/pidFileName for convenience.
|
||||
// readOnly is not supported on Plan 9.
|
||||
func acquireDirectoryLock(dirPath string, pidFileName string, readOnly bool) (
|
||||
*directoryLockGuard, error) {
|
||||
if readOnly {
|
||||
return nil, ErrPlan9NotSupported
|
||||
}
|
||||
|
||||
// Convert to absolute path so that Release still works even if we do an unbalanced
|
||||
// chdir in the meantime.
|
||||
absPidFilePath, err := filepath.Abs(filepath.Join(dirPath, pidFileName))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot get absolute path for pid lock file")
|
||||
}
|
||||
|
||||
// If the file was unpacked or created by some other program, it might not
|
||||
// have the ModeExclusive bit set. Set it before we call OpenFile, so that we
|
||||
// can be confident that a successful OpenFile implies exclusive use.
|
||||
//
|
||||
// OpenFile fails if the file ModeExclusive bit set *and* the file is already open.
|
||||
// So, if the file is closed when the DB crashed, we're fine. When the process
|
||||
// that was managing the DB crashes, the OS will close the file for us.
|
||||
//
|
||||
// This bit of code is copied from Go's lockedfile internal package:
|
||||
// https://github.com/golang/go/blob/go1.15rc1/src/cmd/go/internal/lockedfile/lockedfile_plan9.go#L58
|
||||
if fi, err := os.Stat(absPidFilePath); err == nil {
|
||||
if fi.Mode()&os.ModeExclusive == 0 {
|
||||
if err := os.Chmod(absPidFilePath, fi.Mode()|os.ModeExclusive); err != nil {
|
||||
return nil, errors.Wrapf(err, "could not set exclusive mode bit")
|
||||
}
|
||||
}
|
||||
} else if !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
f, err := os.OpenFile(absPidFilePath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666|os.ModeExclusive)
|
||||
if err != nil {
|
||||
if isLocked(err) {
|
||||
return nil, errors.Wrapf(err,
|
||||
"Cannot open pid lock file %q. Another process is using this Badger database",
|
||||
absPidFilePath)
|
||||
}
|
||||
return nil, errors.Wrapf(err, "Cannot open pid lock file %q", absPidFilePath)
|
||||
}
|
||||
|
||||
if _, err = fmt.Fprintf(f, "%d\n", os.Getpid()); err != nil {
|
||||
f.Close()
|
||||
return nil, errors.Wrapf(err, "could not write pid")
|
||||
}
|
||||
return &directoryLockGuard{f, absPidFilePath}, nil
|
||||
}
|
||||
|
||||
// Release deletes the pid file and releases our lock on the directory.
|
||||
func (guard *directoryLockGuard) release() error {
|
||||
// It's important that we remove the pid file first.
|
||||
err := os.Remove(guard.path)
|
||||
|
||||
if closeErr := guard.f.Close(); err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
guard.path = ""
|
||||
guard.f = nil
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// openDir opens a directory for syncing.
|
||||
func openDir(path string) (*os.File, error) { return os.Open(path) }
|
||||
|
||||
// When you create or delete a file, you have to ensure the directory entry for the file is synced
|
||||
// in order to guarantee the file is visible (if the system crashes). (See the man page for fsync,
|
||||
// or see https://github.com/coreos/etcd/issues/6368 for an example.)
|
||||
func syncDir(dir string) error {
|
||||
f, err := openDir(dir)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "While opening directory: %s.", dir)
|
||||
}
|
||||
|
||||
err = f.Sync()
|
||||
closeErr := f.Close()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "While syncing directory: %s.", dir)
|
||||
}
|
||||
return errors.Wrapf(closeErr, "While closing directory: %s.", dir)
|
||||
}
|
||||
|
||||
// Opening an exclusive-use file returns an error.
|
||||
// The expected error strings are:
|
||||
//
|
||||
// - "open/create -- file is locked" (cwfs, kfs)
|
||||
// - "exclusive lock" (fossil)
|
||||
// - "exclusive use file already open" (ramfs)
|
||||
//
|
||||
// See https://github.com/golang/go/blob/go1.15rc1/src/cmd/go/internal/lockedfile/lockedfile_plan9.go#L16
|
||||
var lockedErrStrings = [...]string{
|
||||
"file is locked",
|
||||
"exclusive lock",
|
||||
"exclusive use file already open",
|
||||
}
|
||||
|
||||
// Even though plan9 doesn't support the Lock/RLock/Unlock functions to
|
||||
// manipulate already-open files, IsLocked is still meaningful: os.OpenFile
|
||||
// itself may return errors that indicate that a file with the ModeExclusive bit
|
||||
// set is already open.
|
||||
func isLocked(err error) bool {
|
||||
s := err.Error()
|
||||
|
||||
for _, frag := range lockedErrStrings {
|
||||
if strings.Contains(s, frag) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
118
vendor/github.com/dgraph-io/badger/dir_unix.go
generated
vendored
Normal file
118
vendor/github.com/dgraph-io/badger/dir_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,118 @@
|
|||
// +build !windows,!plan9
|
||||
|
||||
/*
|
||||
* Copyright 2017 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"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/dgraph-io/badger/y"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// directoryLockGuard holds a lock on a directory and a pid file inside. The pid file isn't part
|
||||
// of the locking mechanism, it's just advisory.
|
||||
type directoryLockGuard struct {
|
||||
// File handle on the directory, which we've flocked.
|
||||
f *os.File
|
||||
// The absolute path to our pid file.
|
||||
path string
|
||||
// Was this a shared lock for a read-only database?
|
||||
readOnly bool
|
||||
}
|
||||
|
||||
// acquireDirectoryLock gets a lock on the directory (using flock). If
|
||||
// this is not read-only, it will also write our pid to
|
||||
// dirPath/pidFileName for convenience.
|
||||
func acquireDirectoryLock(dirPath string, pidFileName string, readOnly bool) (
|
||||
*directoryLockGuard, error) {
|
||||
// Convert to absolute path so that Release still works even if we do an unbalanced
|
||||
// chdir in the meantime.
|
||||
absPidFilePath, err := filepath.Abs(filepath.Join(dirPath, pidFileName))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot get absolute path for pid lock file")
|
||||
}
|
||||
f, err := os.Open(dirPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot open directory %q", dirPath)
|
||||
}
|
||||
opts := unix.LOCK_EX | unix.LOCK_NB
|
||||
if readOnly {
|
||||
opts = unix.LOCK_SH | unix.LOCK_NB
|
||||
}
|
||||
|
||||
err = unix.Flock(int(f.Fd()), opts)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return nil, errors.Wrapf(err,
|
||||
"Cannot acquire directory lock on %q. Another process is using this Badger database.",
|
||||
dirPath)
|
||||
}
|
||||
|
||||
if !readOnly {
|
||||
// Yes, we happily overwrite a pre-existing pid file. We're the
|
||||
// only read-write badger process using this directory.
|
||||
err = ioutil.WriteFile(absPidFilePath, []byte(fmt.Sprintf("%d\n", os.Getpid())), 0666)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return nil, errors.Wrapf(err,
|
||||
"Cannot write pid file %q", absPidFilePath)
|
||||
}
|
||||
}
|
||||
return &directoryLockGuard{f, absPidFilePath, readOnly}, nil
|
||||
}
|
||||
|
||||
// Release deletes the pid file and releases our lock on the directory.
|
||||
func (guard *directoryLockGuard) release() error {
|
||||
var err error
|
||||
if !guard.readOnly {
|
||||
// It's important that we remove the pid file first.
|
||||
err = os.Remove(guard.path)
|
||||
}
|
||||
|
||||
if closeErr := guard.f.Close(); err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
guard.path = ""
|
||||
guard.f = nil
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// openDir opens a directory for syncing.
|
||||
func openDir(path string) (*os.File, error) { return os.Open(path) }
|
||||
|
||||
// When you create or delete a file, you have to ensure the directory entry for the file is synced
|
||||
// in order to guarantee the file is visible (if the system crashes). (See the man page for fsync,
|
||||
// or see https://github.com/coreos/etcd/issues/6368 for an example.)
|
||||
func syncDir(dir string) error {
|
||||
f, err := openDir(dir)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "While opening directory: %s.", dir)
|
||||
}
|
||||
err = y.FileSync(f)
|
||||
closeErr := f.Close()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "While syncing directory: %s.", dir)
|
||||
}
|
||||
return errors.Wrapf(closeErr, "While closing directory: %s.", dir)
|
||||
}
|
110
vendor/github.com/dgraph-io/badger/dir_windows.go
generated
vendored
Normal file
110
vendor/github.com/dgraph-io/badger/dir_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,110 @@
|
|||
// +build windows
|
||||
|
||||
/*
|
||||
* Copyright 2017 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
|
||||
|
||||
// OpenDir opens a directory in windows with write access for syncing.
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// FILE_ATTRIBUTE_TEMPORARY - A file that is being used for temporary storage.
|
||||
// FILE_FLAG_DELETE_ON_CLOSE - The file is to be deleted immediately after all of its handles are
|
||||
// closed, which includes the specified handle and any other open or duplicated handles.
|
||||
// See: https://docs.microsoft.com/en-us/windows/desktop/FileIO/file-attribute-constants
|
||||
// NOTE: Added here to avoid importing golang.org/x/sys/windows
|
||||
const (
|
||||
FILE_ATTRIBUTE_TEMPORARY = 0x00000100
|
||||
FILE_FLAG_DELETE_ON_CLOSE = 0x04000000
|
||||
)
|
||||
|
||||
func openDir(path string) (*os.File, error) {
|
||||
fd, err := openDirWin(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return os.NewFile(uintptr(fd), path), nil
|
||||
}
|
||||
|
||||
func openDirWin(path string) (fd syscall.Handle, err error) {
|
||||
if len(path) == 0 {
|
||||
return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND
|
||||
}
|
||||
pathp, err := syscall.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return syscall.InvalidHandle, err
|
||||
}
|
||||
access := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE)
|
||||
sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE)
|
||||
createmode := uint32(syscall.OPEN_EXISTING)
|
||||
fl := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS)
|
||||
return syscall.CreateFile(pathp, access, sharemode, nil, createmode, fl, 0)
|
||||
}
|
||||
|
||||
// DirectoryLockGuard holds a lock on the directory.
|
||||
type directoryLockGuard struct {
|
||||
h syscall.Handle
|
||||
path string
|
||||
}
|
||||
|
||||
// AcquireDirectoryLock acquires exclusive access to a directory.
|
||||
func acquireDirectoryLock(dirPath string, pidFileName string, readOnly bool) (*directoryLockGuard, error) {
|
||||
if readOnly {
|
||||
return nil, ErrWindowsNotSupported
|
||||
}
|
||||
|
||||
// Convert to absolute path so that Release still works even if we do an unbalanced
|
||||
// chdir in the meantime.
|
||||
absLockFilePath, err := filepath.Abs(filepath.Join(dirPath, pidFileName))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Cannot get absolute path for pid lock file")
|
||||
}
|
||||
|
||||
// This call creates a file handler in memory that only one process can use at a time. When
|
||||
// that process ends, the file is deleted by the system.
|
||||
// FILE_ATTRIBUTE_TEMPORARY is used to tell Windows to try to create the handle in memory.
|
||||
// FILE_FLAG_DELETE_ON_CLOSE is not specified in syscall_windows.go but tells Windows to delete
|
||||
// the file when all processes holding the handler are closed.
|
||||
// XXX: this works but it's a bit klunky. i'd prefer to use LockFileEx but it needs unsafe pkg.
|
||||
h, err := syscall.CreateFile(
|
||||
syscall.StringToUTF16Ptr(absLockFilePath), 0, 0, nil,
|
||||
syscall.OPEN_ALWAYS,
|
||||
uint32(FILE_ATTRIBUTE_TEMPORARY|FILE_FLAG_DELETE_ON_CLOSE),
|
||||
0)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err,
|
||||
"Cannot create lock file %q. Another process is using this Badger database",
|
||||
absLockFilePath)
|
||||
}
|
||||
|
||||
return &directoryLockGuard{h: h, path: absLockFilePath}, nil
|
||||
}
|
||||
|
||||
// Release removes the directory lock.
|
||||
func (g *directoryLockGuard) release() error {
|
||||
g.path = ""
|
||||
return syscall.CloseHandle(g.h)
|
||||
}
|
||||
|
||||
// Windows doesn't support syncing directories to the file system. See
|
||||
// https://github.com/dgraph-io/badger/issues/699#issuecomment-504133587 for more details.
|
||||
func syncDir(dir string) error { return nil }
|
28
vendor/github.com/dgraph-io/badger/doc.go
generated
vendored
Normal file
28
vendor/github.com/dgraph-io/badger/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
Package badger implements an embeddable, simple and fast key-value database,
|
||||
written in pure Go. It is designed to be highly performant for both reads and
|
||||
writes simultaneously. Badger uses Multi-Version Concurrency Control (MVCC), and
|
||||
supports transactions. It runs transactions concurrently, with serializable
|
||||
snapshot isolation guarantees.
|
||||
|
||||
Badger uses an LSM tree along with a value log to separate keys from values,
|
||||
hence reducing both write amplification and the size of the LSM tree. This
|
||||
allows LSM tree to be served entirely from RAM, while the values are served
|
||||
from SSD.
|
||||
|
||||
|
||||
Usage
|
||||
|
||||
Badger has the following main types: DB, Txn, Item and Iterator. DB contains
|
||||
keys that are associated with values. It must be opened with the appropriate
|
||||
options before it can be accessed.
|
||||
|
||||
All operations happen inside a Txn. Txn represents a transaction, which can
|
||||
be read-only or read-write. Read-only transactions can read values for a
|
||||
given key (which are returned inside an Item), or iterate over a set of
|
||||
key-value pairs using an Iterator (which are returned as Item type values as
|
||||
well). Read-write transactions can also update and delete keys from the DB.
|
||||
|
||||
See the examples for more usage details.
|
||||
*/
|
||||
package badger
|
120
vendor/github.com/dgraph-io/badger/errors.go
generated
vendored
Normal file
120
vendor/github.com/dgraph-io/badger/errors.go
generated
vendored
Normal file
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Copyright 2017 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 (
|
||||
"math"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// ValueThresholdLimit is the maximum permissible value of opt.ValueThreshold.
|
||||
ValueThresholdLimit = math.MaxUint16 - 16 + 1
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrValueLogSize is returned when opt.ValueLogFileSize option is not within the valid
|
||||
// range.
|
||||
ErrValueLogSize = errors.New("Invalid ValueLogFileSize, must be between 1MB and 2GB")
|
||||
|
||||
// ErrValueThreshold is returned when ValueThreshold is set to a value close to or greater than
|
||||
// uint16.
|
||||
ErrValueThreshold = errors.Errorf(
|
||||
"Invalid ValueThreshold, must be less than %d", ValueThresholdLimit)
|
||||
|
||||
// ErrKeyNotFound is returned when key isn't found on a txn.Get.
|
||||
ErrKeyNotFound = errors.New("Key not found")
|
||||
|
||||
// ErrTxnTooBig is returned if too many writes are fit into a single transaction.
|
||||
ErrTxnTooBig = errors.New("Txn is too big to fit into one request")
|
||||
|
||||
// ErrConflict is returned when a transaction conflicts with another transaction. This can
|
||||
// happen if the read rows had been updated concurrently by another transaction.
|
||||
ErrConflict = errors.New("Transaction Conflict. Please retry")
|
||||
|
||||
// ErrReadOnlyTxn is returned if an update function is called on a read-only transaction.
|
||||
ErrReadOnlyTxn = errors.New("No sets or deletes are allowed in a read-only transaction")
|
||||
|
||||
// ErrDiscardedTxn is returned if a previously discarded transaction is re-used.
|
||||
ErrDiscardedTxn = errors.New("This transaction has been discarded. Create a new one")
|
||||
|
||||
// ErrEmptyKey is returned if an empty key is passed on an update function.
|
||||
ErrEmptyKey = errors.New("Key cannot be empty")
|
||||
|
||||
// ErrInvalidKey is returned if the key has a special !badger! prefix,
|
||||
// reserved for internal usage.
|
||||
ErrInvalidKey = errors.New("Key is using a reserved !badger! prefix")
|
||||
|
||||
// ErrRetry is returned when a log file containing the value is not found.
|
||||
// This usually indicates that it may have been garbage collected, and the
|
||||
// operation needs to be retried.
|
||||
ErrRetry = errors.New("Unable to find log file. Please retry")
|
||||
|
||||
// ErrThresholdZero is returned if threshold is set to zero, and value log GC is called.
|
||||
// In such a case, GC can't be run.
|
||||
ErrThresholdZero = errors.New(
|
||||
"Value log GC can't run because threshold is set to zero")
|
||||
|
||||
// ErrNoRewrite is returned if a call for value log GC doesn't result in a log file rewrite.
|
||||
ErrNoRewrite = errors.New(
|
||||
"Value log GC attempt didn't result in any cleanup")
|
||||
|
||||
// ErrRejected is returned if a value log GC is called either while another GC is running, or
|
||||
// after DB::Close has been called.
|
||||
ErrRejected = errors.New("Value log GC request rejected")
|
||||
|
||||
// ErrInvalidRequest is returned if the user request is invalid.
|
||||
ErrInvalidRequest = errors.New("Invalid request")
|
||||
|
||||
// ErrManagedTxn is returned if the user tries to use an API which isn't
|
||||
// allowed due to external management of transactions, when using ManagedDB.
|
||||
ErrManagedTxn = errors.New(
|
||||
"Invalid API request. Not allowed to perform this action using ManagedDB")
|
||||
|
||||
// ErrInvalidDump if a data dump made previously cannot be loaded into the database.
|
||||
ErrInvalidDump = errors.New("Data dump cannot be read")
|
||||
|
||||
// ErrZeroBandwidth is returned if the user passes in zero bandwidth for sequence.
|
||||
ErrZeroBandwidth = errors.New("Bandwidth must be greater than zero")
|
||||
|
||||
// ErrInvalidLoadingMode is returned when opt.ValueLogLoadingMode option is not
|
||||
// within the valid range
|
||||
ErrInvalidLoadingMode = errors.New("Invalid ValueLogLoadingMode, must be FileIO or MemoryMap")
|
||||
|
||||
// ErrReplayNeeded is returned when opt.ReadOnly is set but the
|
||||
// database requires a value log replay.
|
||||
ErrReplayNeeded = errors.New("Database was not properly closed, cannot open read-only")
|
||||
|
||||
// ErrWindowsNotSupported is returned when opt.ReadOnly is used on Windows
|
||||
ErrWindowsNotSupported = errors.New("Read-only mode is not supported on Windows")
|
||||
|
||||
// ErrPlan9NotSupported is returned when opt.ReadOnly is used on Plan 9
|
||||
ErrPlan9NotSupported = errors.New("Read-only mode is not supported on Plan 9")
|
||||
|
||||
// ErrTruncateNeeded is returned when the value log gets corrupt, and requires truncation of
|
||||
// corrupt data to allow Badger to run properly.
|
||||
ErrTruncateNeeded = errors.New(
|
||||
"Value log truncate required to run DB. This might result in data loss")
|
||||
|
||||
// ErrBlockedWrites is returned if the user called DropAll. During the process of dropping all
|
||||
// data from Badger, we stop accepting new writes, by returning this error.
|
||||
ErrBlockedWrites = errors.New("Writes are blocked, possibly due to DropAll or Close")
|
||||
|
||||
// ErrNilCallback is returned when subscriber's callback is nil.
|
||||
ErrNilCallback = errors.New("Callback cannot be nil")
|
||||
)
|
18
vendor/github.com/dgraph-io/badger/go.mod
generated
vendored
Normal file
18
vendor/github.com/dgraph-io/badger/go.mod
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
module github.com/dgraph-io/badger
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96
|
||||
github.com/dgraph-io/ristretto v0.0.2
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/golang/protobuf v1.3.1
|
||||
github.com/kr/pretty v0.2.0 // indirect
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/spf13/cobra v0.0.5
|
||||
github.com/stretchr/testify v1.4.0
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
)
|
74
vendor/github.com/dgraph-io/badger/go.sum
generated
vendored
Normal file
74
vendor/github.com/dgraph-io/badger/go.sum
generated
vendored
Normal file
|
@ -0,0 +1,74 @@
|
|||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgraph-io/ristretto v0.0.2 h1:a5WaUrDa0qm0YrAAS1tUykT5El3kt62KNZZeMxQn3po=
|
||||
github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
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/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
169
vendor/github.com/dgraph-io/badger/histogram.go
generated
vendored
Normal file
169
vendor/github.com/dgraph-io/badger/histogram.go
generated
vendored
Normal file
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* Copyright 2019 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"
|
||||
"math"
|
||||
)
|
||||
|
||||
// PrintHistogram builds and displays the key-value size histogram.
|
||||
// When keyPrefix is set, only the keys that have prefix "keyPrefix" are
|
||||
// considered for creating the histogram
|
||||
func (db *DB) PrintHistogram(keyPrefix []byte) {
|
||||
if db == nil {
|
||||
fmt.Println("\nCannot build histogram: DB is nil.")
|
||||
return
|
||||
}
|
||||
histogram := db.buildHistogram(keyPrefix)
|
||||
fmt.Printf("Histogram of key sizes (in bytes)\n")
|
||||
histogram.keySizeHistogram.printHistogram()
|
||||
fmt.Printf("Histogram of value sizes (in bytes)\n")
|
||||
histogram.valueSizeHistogram.printHistogram()
|
||||
}
|
||||
|
||||
// histogramData stores information about a histogram
|
||||
type histogramData struct {
|
||||
bins []int64
|
||||
countPerBin []int64
|
||||
totalCount int64
|
||||
min int64
|
||||
max int64
|
||||
sum int64
|
||||
}
|
||||
|
||||
// sizeHistogram contains keySize histogram and valueSize histogram
|
||||
type sizeHistogram struct {
|
||||
keySizeHistogram, valueSizeHistogram histogramData
|
||||
}
|
||||
|
||||
// newSizeHistogram returns a new instance of keyValueSizeHistogram with
|
||||
// properly initialized fields.
|
||||
func newSizeHistogram() *sizeHistogram {
|
||||
// TODO(ibrahim): find appropriate bin size.
|
||||
keyBins := createHistogramBins(1, 16)
|
||||
valueBins := createHistogramBins(1, 30)
|
||||
return &sizeHistogram{
|
||||
keySizeHistogram: histogramData{
|
||||
bins: keyBins,
|
||||
countPerBin: make([]int64, len(keyBins)+1),
|
||||
max: math.MinInt64,
|
||||
min: math.MaxInt64,
|
||||
sum: 0,
|
||||
},
|
||||
valueSizeHistogram: histogramData{
|
||||
bins: valueBins,
|
||||
countPerBin: make([]int64, len(valueBins)+1),
|
||||
max: math.MinInt64,
|
||||
min: math.MaxInt64,
|
||||
sum: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// createHistogramBins creates bins for an histogram. The bin sizes are powers
|
||||
// of two of the form [2^min_exponent, ..., 2^max_exponent].
|
||||
func createHistogramBins(minExponent, maxExponent uint32) []int64 {
|
||||
var bins []int64
|
||||
for i := minExponent; i <= maxExponent; i++ {
|
||||
bins = append(bins, int64(1)<<i)
|
||||
}
|
||||
return bins
|
||||
}
|
||||
|
||||
// Update the min and max fields if value is less than or greater than the
|
||||
// current min/max value.
|
||||
func (histogram *histogramData) Update(value int64) {
|
||||
if value > histogram.max {
|
||||
histogram.max = value
|
||||
}
|
||||
if value < histogram.min {
|
||||
histogram.min = value
|
||||
}
|
||||
|
||||
histogram.sum += value
|
||||
histogram.totalCount++
|
||||
|
||||
for index := 0; index <= len(histogram.bins); index++ {
|
||||
// Allocate value in the last buckets if we reached the end of the Bounds array.
|
||||
if index == len(histogram.bins) {
|
||||
histogram.countPerBin[index]++
|
||||
break
|
||||
}
|
||||
|
||||
// Check if the value should be added to the "index" bin
|
||||
if value < int64(histogram.bins[index]) {
|
||||
histogram.countPerBin[index]++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// buildHistogram builds the key-value size histogram.
|
||||
// When keyPrefix is set, only the keys that have prefix "keyPrefix" are
|
||||
// considered for creating the histogram
|
||||
func (db *DB) buildHistogram(keyPrefix []byte) *sizeHistogram {
|
||||
txn := db.NewTransaction(false)
|
||||
defer txn.Discard()
|
||||
|
||||
itr := txn.NewIterator(DefaultIteratorOptions)
|
||||
defer itr.Close()
|
||||
|
||||
badgerHistogram := newSizeHistogram()
|
||||
|
||||
// Collect key and value sizes.
|
||||
for itr.Seek(keyPrefix); itr.ValidForPrefix(keyPrefix); itr.Next() {
|
||||
item := itr.Item()
|
||||
badgerHistogram.keySizeHistogram.Update(item.KeySize())
|
||||
badgerHistogram.valueSizeHistogram.Update(item.ValueSize())
|
||||
}
|
||||
return badgerHistogram
|
||||
}
|
||||
|
||||
// printHistogram prints the histogram data in a human-readable format.
|
||||
func (histogram histogramData) printHistogram() {
|
||||
fmt.Printf("Total count: %d\n", histogram.totalCount)
|
||||
fmt.Printf("Min value: %d\n", histogram.min)
|
||||
fmt.Printf("Max value: %d\n", histogram.max)
|
||||
fmt.Printf("Mean: %.2f\n", float64(histogram.sum)/float64(histogram.totalCount))
|
||||
fmt.Printf("%24s %9s\n", "Range", "Count")
|
||||
|
||||
numBins := len(histogram.bins)
|
||||
for index, count := range histogram.countPerBin {
|
||||
if count == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// The last bin represents the bin that contains the range from
|
||||
// the last bin up to infinity so it's processed differently than the
|
||||
// other bins.
|
||||
if index == len(histogram.countPerBin)-1 {
|
||||
lowerBound := int(histogram.bins[numBins-1])
|
||||
fmt.Printf("[%10d, %10s) %9d\n", lowerBound, "infinity", count)
|
||||
continue
|
||||
}
|
||||
|
||||
upperBound := int(histogram.bins[index])
|
||||
lowerBound := 0
|
||||
if index > 0 {
|
||||
lowerBound = int(histogram.bins[index-1])
|
||||
}
|
||||
|
||||
fmt.Printf("[%10d, %10d) %9d\n", lowerBound, upperBound, count)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
736
vendor/github.com/dgraph-io/badger/iterator.go
generated
vendored
Normal file
736
vendor/github.com/dgraph-io/badger/iterator.go
generated
vendored
Normal file
|
@ -0,0 +1,736 @@
|
|||
/*
|
||||
* Copyright 2017 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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/dgraph-io/badger/options"
|
||||
"github.com/dgraph-io/badger/table"
|
||||
|
||||
"github.com/dgraph-io/badger/y"
|
||||
)
|
||||
|
||||
type prefetchStatus uint8
|
||||
|
||||
const (
|
||||
prefetched prefetchStatus = iota + 1
|
||||
)
|
||||
|
||||
// Item is returned during iteration. Both the Key() and Value() output is only valid until
|
||||
// iterator.Next() is called.
|
||||
type Item struct {
|
||||
status prefetchStatus
|
||||
err error
|
||||
wg sync.WaitGroup
|
||||
db *DB
|
||||
key []byte
|
||||
vptr []byte
|
||||
meta byte // We need to store meta to know about bitValuePointer.
|
||||
userMeta byte
|
||||
expiresAt uint64
|
||||
val []byte
|
||||
slice *y.Slice // Used only during prefetching.
|
||||
next *Item
|
||||
version uint64
|
||||
txn *Txn
|
||||
}
|
||||
|
||||
// String returns a string representation of Item
|
||||
func (item *Item) String() string {
|
||||
return fmt.Sprintf("key=%q, version=%d, meta=%x", item.Key(), item.Version(), item.meta)
|
||||
}
|
||||
|
||||
// Key returns the key.
|
||||
//
|
||||
// Key is only valid as long as item is valid, or transaction is valid. If you need to use it
|
||||
// outside its validity, please use KeyCopy.
|
||||
func (item *Item) Key() []byte {
|
||||
return item.key
|
||||
}
|
||||
|
||||
// KeyCopy returns a copy of the key of the item, writing it to dst slice.
|
||||
// If nil is passed, or capacity of dst isn't sufficient, a new slice would be allocated and
|
||||
// returned.
|
||||
func (item *Item) KeyCopy(dst []byte) []byte {
|
||||
return y.SafeCopy(dst, item.key)
|
||||
}
|
||||
|
||||
// Version returns the commit timestamp of the item.
|
||||
func (item *Item) Version() uint64 {
|
||||
return item.version
|
||||
}
|
||||
|
||||
// Value retrieves the value of the item from the value log.
|
||||
//
|
||||
// This method must be called within a transaction. Calling it outside a
|
||||
// transaction is considered undefined behavior. If an iterator is being used,
|
||||
// then Item.Value() is defined in the current iteration only, because items are
|
||||
// reused.
|
||||
//
|
||||
// If you need to use a value outside a transaction, please use Item.ValueCopy
|
||||
// instead, or copy it yourself. Value might change once discard or commit is called.
|
||||
// Use ValueCopy if you want to do a Set after Get.
|
||||
func (item *Item) Value(fn func(val []byte) error) error {
|
||||
item.wg.Wait()
|
||||
if item.status == prefetched {
|
||||
if item.err == nil && fn != nil {
|
||||
if err := fn(item.val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return item.err
|
||||
}
|
||||
buf, cb, err := item.yieldItemValue()
|
||||
defer runCallback(cb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fn != nil {
|
||||
return fn(buf)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValueCopy returns a copy of the value of the item from the value log, writing it to dst slice.
|
||||
// If nil is passed, or capacity of dst isn't sufficient, a new slice would be allocated and
|
||||
// returned. Tip: It might make sense to reuse the returned slice as dst argument for the next call.
|
||||
//
|
||||
// This function is useful in long running iterate/update transactions to avoid a write deadlock.
|
||||
// See Github issue: https://github.com/dgraph-io/badger/issues/315
|
||||
func (item *Item) ValueCopy(dst []byte) ([]byte, error) {
|
||||
item.wg.Wait()
|
||||
if item.status == prefetched {
|
||||
return y.SafeCopy(dst, item.val), item.err
|
||||
}
|
||||
buf, cb, err := item.yieldItemValue()
|
||||
defer runCallback(cb)
|
||||
return y.SafeCopy(dst, buf), err
|
||||
}
|
||||
|
||||
func (item *Item) hasValue() bool {
|
||||
if item.meta == 0 && item.vptr == nil {
|
||||
// key not found
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsDeletedOrExpired returns true if item contains deleted or expired value.
|
||||
func (item *Item) IsDeletedOrExpired() bool {
|
||||
return isDeletedOrExpired(item.meta, item.expiresAt)
|
||||
}
|
||||
|
||||
// DiscardEarlierVersions returns whether the item was created with the
|
||||
// option to discard earlier versions of a key when multiple are available.
|
||||
func (item *Item) DiscardEarlierVersions() bool {
|
||||
return item.meta&bitDiscardEarlierVersions > 0
|
||||
}
|
||||
|
||||
func (item *Item) yieldItemValue() ([]byte, func(), error) {
|
||||
key := item.Key() // No need to copy.
|
||||
for {
|
||||
if !item.hasValue() {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
if item.slice == nil {
|
||||
item.slice = new(y.Slice)
|
||||
}
|
||||
|
||||
if (item.meta & bitValuePointer) == 0 {
|
||||
val := item.slice.Resize(len(item.vptr))
|
||||
copy(val, item.vptr)
|
||||
return val, nil, nil
|
||||
}
|
||||
|
||||
var vp valuePointer
|
||||
vp.Decode(item.vptr)
|
||||
result, cb, err := item.db.vlog.Read(vp, item.slice)
|
||||
if err != ErrRetry {
|
||||
return result, cb, err
|
||||
}
|
||||
if bytes.HasPrefix(key, badgerMove) {
|
||||
// err == ErrRetry
|
||||
// Error is retry even after checking the move keyspace. So, let's
|
||||
// just assume that value is not present.
|
||||
return nil, cb, nil
|
||||
}
|
||||
|
||||
// The value pointer is pointing to a deleted value log. Look for the
|
||||
// move key and read that instead.
|
||||
runCallback(cb)
|
||||
// Do not put badgerMove on the left in append. It seems to cause some sort of manipulation.
|
||||
keyTs := y.KeyWithTs(item.Key(), item.Version())
|
||||
key = make([]byte, len(badgerMove)+len(keyTs))
|
||||
n := copy(key, badgerMove)
|
||||
copy(key[n:], keyTs)
|
||||
// Note that we can't set item.key to move key, because that would
|
||||
// change the key user sees before and after this call. Also, this move
|
||||
// logic is internal logic and should not impact the external behavior
|
||||
// of the retrieval.
|
||||
vs, err := item.db.get(key)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if vs.Version != item.Version() {
|
||||
return nil, nil, nil
|
||||
}
|
||||
// Bug fix: Always copy the vs.Value into vptr here. Otherwise, when item is reused this
|
||||
// slice gets overwritten.
|
||||
item.vptr = y.SafeCopy(item.vptr, vs.Value)
|
||||
item.meta &^= bitValuePointer // Clear the value pointer bit.
|
||||
if vs.Meta&bitValuePointer > 0 {
|
||||
item.meta |= bitValuePointer // This meta would only be about value pointer.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runCallback(cb func()) {
|
||||
if cb != nil {
|
||||
cb()
|
||||
}
|
||||
}
|
||||
|
||||
func (item *Item) prefetchValue() {
|
||||
val, cb, err := item.yieldItemValue()
|
||||
defer runCallback(cb)
|
||||
|
||||
item.err = err
|
||||
item.status = prefetched
|
||||
if val == nil {
|
||||
return
|
||||
}
|
||||
if item.db.opt.ValueLogLoadingMode == options.MemoryMap {
|
||||
buf := item.slice.Resize(len(val))
|
||||
copy(buf, val)
|
||||
item.val = buf
|
||||
} else {
|
||||
item.val = val
|
||||
}
|
||||
}
|
||||
|
||||
// EstimatedSize returns the approximate size of the key-value pair.
|
||||
//
|
||||
// This can be called while iterating through a store to quickly estimate the
|
||||
// size of a range of key-value pairs (without fetching the corresponding
|
||||
// values).
|
||||
func (item *Item) EstimatedSize() int64 {
|
||||
if !item.hasValue() {
|
||||
return 0
|
||||
}
|
||||
if (item.meta & bitValuePointer) == 0 {
|
||||
return int64(len(item.key) + len(item.vptr))
|
||||
}
|
||||
var vp valuePointer
|
||||
vp.Decode(item.vptr)
|
||||
return int64(vp.Len) // includes key length.
|
||||
}
|
||||
|
||||
// KeySize returns the size of the key.
|
||||
// Exact size of the key is key + 8 bytes of timestamp
|
||||
func (item *Item) KeySize() int64 {
|
||||
return int64(len(item.key))
|
||||
}
|
||||
|
||||
// ValueSize returns the exact size of the value.
|
||||
//
|
||||
// This can be called to quickly estimate the size of a value without fetching
|
||||
// it.
|
||||
func (item *Item) ValueSize() int64 {
|
||||
if !item.hasValue() {
|
||||
return 0
|
||||
}
|
||||
if (item.meta & bitValuePointer) == 0 {
|
||||
return int64(len(item.vptr))
|
||||
}
|
||||
var vp valuePointer
|
||||
vp.Decode(item.vptr)
|
||||
|
||||
klen := int64(len(item.key) + 8) // 8 bytes for timestamp.
|
||||
return int64(vp.Len) - klen - headerBufSize - crc32.Size
|
||||
}
|
||||
|
||||
// UserMeta returns the userMeta set by the user. Typically, this byte, optionally set by the user
|
||||
// is used to interpret the value.
|
||||
func (item *Item) UserMeta() byte {
|
||||
return item.userMeta
|
||||
}
|
||||
|
||||
// ExpiresAt returns a Unix time value indicating when the item will be
|
||||
// considered expired. 0 indicates that the item will never expire.
|
||||
func (item *Item) ExpiresAt() uint64 {
|
||||
return item.expiresAt
|
||||
}
|
||||
|
||||
// TODO: Switch this to use linked list container in Go.
|
||||
type list struct {
|
||||
head *Item
|
||||
tail *Item
|
||||
}
|
||||
|
||||
func (l *list) push(i *Item) {
|
||||
i.next = nil
|
||||
if l.tail == nil {
|
||||
l.head = i
|
||||
l.tail = i
|
||||
return
|
||||
}
|
||||
l.tail.next = i
|
||||
l.tail = i
|
||||
}
|
||||
|
||||
func (l *list) pop() *Item {
|
||||
if l.head == nil {
|
||||
return nil
|
||||
}
|
||||
i := l.head
|
||||
if l.head == l.tail {
|
||||
l.tail = nil
|
||||
l.head = nil
|
||||
} else {
|
||||
l.head = i.next
|
||||
}
|
||||
i.next = nil
|
||||
return i
|
||||
}
|
||||
|
||||
// IteratorOptions is used to set options when iterating over Badger key-value
|
||||
// stores.
|
||||
//
|
||||
// This package provides DefaultIteratorOptions which contains options that
|
||||
// should work for most applications. Consider using that as a starting point
|
||||
// before customizing it for your own needs.
|
||||
type IteratorOptions struct {
|
||||
// Indicates whether we should prefetch values during iteration and store them.
|
||||
PrefetchValues bool
|
||||
// How many KV pairs to prefetch while iterating. Valid only if PrefetchValues is true.
|
||||
PrefetchSize int
|
||||
Reverse bool // Direction of iteration. False is forward, true is backward.
|
||||
AllVersions bool // Fetch all valid versions of the same key.
|
||||
|
||||
// The following option is used to narrow down the SSTables that iterator picks up. If
|
||||
// Prefix is specified, only tables which could have this prefix are picked based on their range
|
||||
// of keys.
|
||||
Prefix []byte // Only iterate over this given prefix.
|
||||
prefixIsKey bool // If set, use the prefix for bloom filter lookup.
|
||||
|
||||
InternalAccess bool // Used to allow internal access to badger keys.
|
||||
}
|
||||
|
||||
func (opt *IteratorOptions) compareToPrefix(key []byte) int {
|
||||
// We should compare key without timestamp. For example key - a[TS] might be > "aa" prefix.
|
||||
key = y.ParseKey(key)
|
||||
if len(key) > len(opt.Prefix) {
|
||||
key = key[:len(opt.Prefix)]
|
||||
}
|
||||
return bytes.Compare(key, opt.Prefix)
|
||||
}
|
||||
|
||||
func (opt *IteratorOptions) pickTable(t table.TableInterface) bool {
|
||||
if len(opt.Prefix) == 0 {
|
||||
return true
|
||||
}
|
||||
if opt.compareToPrefix(t.Smallest()) > 0 {
|
||||
return false
|
||||
}
|
||||
if opt.compareToPrefix(t.Biggest()) < 0 {
|
||||
return false
|
||||
}
|
||||
// Bloom filter lookup would only work if opt.Prefix does NOT have the read
|
||||
// timestamp as part of the key.
|
||||
if opt.prefixIsKey && t.DoesNotHave(opt.Prefix) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// pickTables picks the necessary table for the iterator. This function also assumes
|
||||
// that the tables are sorted in the right order.
|
||||
func (opt *IteratorOptions) pickTables(all []*table.Table) []*table.Table {
|
||||
if len(opt.Prefix) == 0 {
|
||||
out := make([]*table.Table, len(all))
|
||||
copy(out, all)
|
||||
return out
|
||||
}
|
||||
sIdx := sort.Search(len(all), func(i int) bool {
|
||||
return opt.compareToPrefix(all[i].Biggest()) >= 0
|
||||
})
|
||||
if sIdx == len(all) {
|
||||
// Not found.
|
||||
return []*table.Table{}
|
||||
}
|
||||
|
||||
filtered := all[sIdx:]
|
||||
if !opt.prefixIsKey {
|
||||
eIdx := sort.Search(len(filtered), func(i int) bool {
|
||||
return opt.compareToPrefix(filtered[i].Smallest()) > 0
|
||||
})
|
||||
out := make([]*table.Table, len(filtered[:eIdx]))
|
||||
copy(out, filtered[:eIdx])
|
||||
return out
|
||||
}
|
||||
|
||||
var out []*table.Table
|
||||
for _, t := range filtered {
|
||||
// When we encounter the first table whose smallest key is higher than
|
||||
// opt.Prefix, we can stop.
|
||||
if opt.compareToPrefix(t.Smallest()) > 0 {
|
||||
return out
|
||||
}
|
||||
// opt.Prefix is actually the key. So, we can run bloom filter checks
|
||||
// as well.
|
||||
if t.DoesNotHave(opt.Prefix) {
|
||||
continue
|
||||
}
|
||||
out = append(out, t)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// DefaultIteratorOptions contains default options when iterating over Badger key-value stores.
|
||||
var DefaultIteratorOptions = IteratorOptions{
|
||||
PrefetchValues: true,
|
||||
PrefetchSize: 100,
|
||||
Reverse: false,
|
||||
AllVersions: false,
|
||||
}
|
||||
|
||||
// Iterator helps iterating over the KV pairs in a lexicographically sorted order.
|
||||
type Iterator struct {
|
||||
iitr y.Iterator
|
||||
txn *Txn
|
||||
readTs uint64
|
||||
|
||||
opt IteratorOptions
|
||||
item *Item
|
||||
data list
|
||||
waste list
|
||||
|
||||
lastKey []byte // Used to skip over multiple versions of the same key.
|
||||
|
||||
closed bool
|
||||
}
|
||||
|
||||
// NewIterator returns a new iterator. Depending upon the options, either only keys, or both
|
||||
// key-value pairs would be fetched. The keys are returned in lexicographically sorted order.
|
||||
// Using prefetch is recommended if you're doing a long running iteration, for performance.
|
||||
//
|
||||
// Multiple Iterators:
|
||||
// For a read-only txn, multiple iterators can be running simultaneously. However, for a read-write
|
||||
// txn, only one can be running at one time to avoid race conditions, because Txn is thread-unsafe.
|
||||
func (txn *Txn) NewIterator(opt IteratorOptions) *Iterator {
|
||||
if txn.discarded {
|
||||
panic("Transaction has already been discarded")
|
||||
}
|
||||
// Do not change the order of the next if. We must track the number of running iterators.
|
||||
if atomic.AddInt32(&txn.numIterators, 1) > 1 && txn.update {
|
||||
atomic.AddInt32(&txn.numIterators, -1)
|
||||
panic("Only one iterator can be active at one time, for a RW txn.")
|
||||
}
|
||||
|
||||
// TODO: If Prefix is set, only pick those memtables which have keys with
|
||||
// the prefix.
|
||||
tables, decr := txn.db.getMemTables()
|
||||
defer decr()
|
||||
txn.db.vlog.incrIteratorCount()
|
||||
var iters []y.Iterator
|
||||
if itr := txn.newPendingWritesIterator(opt.Reverse); itr != nil {
|
||||
iters = append(iters, itr)
|
||||
}
|
||||
for i := 0; i < len(tables); i++ {
|
||||
iters = append(iters, tables[i].NewUniIterator(opt.Reverse))
|
||||
}
|
||||
iters = txn.db.lc.appendIterators(iters, &opt) // This will increment references.
|
||||
|
||||
res := &Iterator{
|
||||
txn: txn,
|
||||
iitr: table.NewMergeIterator(iters, opt.Reverse),
|
||||
opt: opt,
|
||||
readTs: txn.readTs,
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// NewKeyIterator is just like NewIterator, but allows the user to iterate over all versions of a
|
||||
// single key. Internally, it sets the Prefix option in provided opt, and uses that prefix to
|
||||
// additionally run bloom filter lookups before picking tables from the LSM tree.
|
||||
func (txn *Txn) NewKeyIterator(key []byte, opt IteratorOptions) *Iterator {
|
||||
if len(opt.Prefix) > 0 {
|
||||
panic("opt.Prefix should be nil for NewKeyIterator.")
|
||||
}
|
||||
opt.Prefix = key // This key must be without the timestamp.
|
||||
opt.prefixIsKey = true
|
||||
opt.AllVersions = true
|
||||
return txn.NewIterator(opt)
|
||||
}
|
||||
|
||||
func (it *Iterator) newItem() *Item {
|
||||
item := it.waste.pop()
|
||||
if item == nil {
|
||||
item = &Item{slice: new(y.Slice), db: it.txn.db, txn: it.txn}
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
// Item returns pointer to the current key-value pair.
|
||||
// This item is only valid until it.Next() gets called.
|
||||
func (it *Iterator) Item() *Item {
|
||||
tx := it.txn
|
||||
tx.addReadKey(it.item.Key())
|
||||
return it.item
|
||||
}
|
||||
|
||||
// Valid returns false when iteration is done.
|
||||
func (it *Iterator) Valid() bool {
|
||||
if it.item == nil {
|
||||
return false
|
||||
}
|
||||
if it.opt.prefixIsKey {
|
||||
return bytes.Equal(it.item.key, it.opt.Prefix)
|
||||
}
|
||||
return bytes.HasPrefix(it.item.key, it.opt.Prefix)
|
||||
}
|
||||
|
||||
// ValidForPrefix returns false when iteration is done
|
||||
// or when the current key is not prefixed by the specified prefix.
|
||||
func (it *Iterator) ValidForPrefix(prefix []byte) bool {
|
||||
return it.Valid() && bytes.HasPrefix(it.item.key, prefix)
|
||||
}
|
||||
|
||||
// Close would close the iterator. It is important to call this when you're done with iteration.
|
||||
func (it *Iterator) Close() {
|
||||
if it.closed {
|
||||
return
|
||||
}
|
||||
it.closed = true
|
||||
|
||||
it.iitr.Close()
|
||||
// It is important to wait for the fill goroutines to finish. Otherwise, we might leave zombie
|
||||
// goroutines behind, which are waiting to acquire file read locks after DB has been closed.
|
||||
waitFor := func(l list) {
|
||||
item := l.pop()
|
||||
for item != nil {
|
||||
item.wg.Wait()
|
||||
item = l.pop()
|
||||
}
|
||||
}
|
||||
waitFor(it.waste)
|
||||
waitFor(it.data)
|
||||
|
||||
// TODO: We could handle this error.
|
||||
_ = it.txn.db.vlog.decrIteratorCount()
|
||||
atomic.AddInt32(&it.txn.numIterators, -1)
|
||||
}
|
||||
|
||||
// Next would advance the iterator by one. Always check it.Valid() after a Next()
|
||||
// to ensure you have access to a valid it.Item().
|
||||
func (it *Iterator) Next() {
|
||||
// Reuse current item
|
||||
it.item.wg.Wait() // Just cleaner to wait before pushing to avoid doing ref counting.
|
||||
it.waste.push(it.item)
|
||||
|
||||
// Set next item to current
|
||||
it.item = it.data.pop()
|
||||
|
||||
for it.iitr.Valid() {
|
||||
if it.parseItem() {
|
||||
// parseItem calls one extra next.
|
||||
// This is used to deal with the complexity of reverse iteration.
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isDeletedOrExpired(meta byte, expiresAt uint64) bool {
|
||||
if meta&bitDelete > 0 {
|
||||
return true
|
||||
}
|
||||
if expiresAt == 0 {
|
||||
return false
|
||||
}
|
||||
return expiresAt <= uint64(time.Now().Unix())
|
||||
}
|
||||
|
||||
// parseItem is a complex function because it needs to handle both forward and reverse iteration
|
||||
// implementation. We store keys such that their versions are sorted in descending order. This makes
|
||||
// forward iteration efficient, but revese iteration complicated. This tradeoff is better because
|
||||
// forward iteration is more common than reverse.
|
||||
//
|
||||
// This function advances the iterator.
|
||||
func (it *Iterator) parseItem() bool {
|
||||
mi := it.iitr
|
||||
key := mi.Key()
|
||||
|
||||
setItem := func(item *Item) {
|
||||
if it.item == nil {
|
||||
it.item = item
|
||||
} else {
|
||||
it.data.push(item)
|
||||
}
|
||||
}
|
||||
|
||||
// Skip badger keys.
|
||||
if !it.opt.InternalAccess && bytes.HasPrefix(key, badgerPrefix) {
|
||||
mi.Next()
|
||||
return false
|
||||
}
|
||||
|
||||
// Skip any versions which are beyond the readTs.
|
||||
version := y.ParseTs(key)
|
||||
if version > it.readTs {
|
||||
mi.Next()
|
||||
return false
|
||||
}
|
||||
|
||||
if it.opt.AllVersions {
|
||||
// Return deleted or expired values also, otherwise user can't figure out
|
||||
// whether the key was deleted.
|
||||
item := it.newItem()
|
||||
it.fill(item)
|
||||
setItem(item)
|
||||
mi.Next()
|
||||
return true
|
||||
}
|
||||
|
||||
// If iterating in forward direction, then just checking the last key against current key would
|
||||
// be sufficient.
|
||||
if !it.opt.Reverse {
|
||||
if y.SameKey(it.lastKey, key) {
|
||||
mi.Next()
|
||||
return false
|
||||
}
|
||||
// Only track in forward direction.
|
||||
// We should update lastKey as soon as we find a different key in our snapshot.
|
||||
// Consider keys: a 5, b 7 (del), b 5. When iterating, lastKey = a.
|
||||
// Then we see b 7, which is deleted. If we don't store lastKey = b, we'll then return b 5,
|
||||
// which is wrong. Therefore, update lastKey here.
|
||||
it.lastKey = y.SafeCopy(it.lastKey, mi.Key())
|
||||
}
|
||||
|
||||
FILL:
|
||||
// If deleted, advance and return.
|
||||
vs := mi.Value()
|
||||
if isDeletedOrExpired(vs.Meta, vs.ExpiresAt) {
|
||||
mi.Next()
|
||||
return false
|
||||
}
|
||||
|
||||
item := it.newItem()
|
||||
it.fill(item)
|
||||
// fill item based on current cursor position. All Next calls have returned, so reaching here
|
||||
// means no Next was called.
|
||||
|
||||
mi.Next() // Advance but no fill item yet.
|
||||
if !it.opt.Reverse || !mi.Valid() { // Forward direction, or invalid.
|
||||
setItem(item)
|
||||
return true
|
||||
}
|
||||
|
||||
// Reverse direction.
|
||||
nextTs := y.ParseTs(mi.Key())
|
||||
mik := y.ParseKey(mi.Key())
|
||||
if nextTs <= it.readTs && bytes.Equal(mik, item.key) {
|
||||
// This is a valid potential candidate.
|
||||
goto FILL
|
||||
}
|
||||
// Ignore the next candidate. Return the current one.
|
||||
setItem(item)
|
||||
return true
|
||||
}
|
||||
|
||||
func (it *Iterator) fill(item *Item) {
|
||||
vs := it.iitr.Value()
|
||||
item.meta = vs.Meta
|
||||
item.userMeta = vs.UserMeta
|
||||
item.expiresAt = vs.ExpiresAt
|
||||
|
||||
item.version = y.ParseTs(it.iitr.Key())
|
||||
item.key = y.SafeCopy(item.key, y.ParseKey(it.iitr.Key()))
|
||||
|
||||
item.vptr = y.SafeCopy(item.vptr, vs.Value)
|
||||
item.val = nil
|
||||
if it.opt.PrefetchValues {
|
||||
item.wg.Add(1)
|
||||
go func() {
|
||||
// FIXME we are not handling errors here.
|
||||
item.prefetchValue()
|
||||
item.wg.Done()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (it *Iterator) prefetch() {
|
||||
prefetchSize := 2
|
||||
if it.opt.PrefetchValues && it.opt.PrefetchSize > 1 {
|
||||
prefetchSize = it.opt.PrefetchSize
|
||||
}
|
||||
|
||||
i := it.iitr
|
||||
var count int
|
||||
it.item = nil
|
||||
for i.Valid() {
|
||||
if !it.parseItem() {
|
||||
continue
|
||||
}
|
||||
count++
|
||||
if count == prefetchSize {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Seek would seek to the provided key if present. If absent, it would seek to the next
|
||||
// smallest key greater than the provided key if iterating in the forward direction.
|
||||
// Behavior would be reversed if iterating backwards.
|
||||
func (it *Iterator) Seek(key []byte) {
|
||||
for i := it.data.pop(); i != nil; i = it.data.pop() {
|
||||
i.wg.Wait()
|
||||
it.waste.push(i)
|
||||
}
|
||||
|
||||
it.lastKey = it.lastKey[:0]
|
||||
if len(key) == 0 {
|
||||
key = it.opt.Prefix
|
||||
}
|
||||
if len(key) == 0 {
|
||||
it.iitr.Rewind()
|
||||
it.prefetch()
|
||||
return
|
||||
}
|
||||
|
||||
if !it.opt.Reverse {
|
||||
key = y.KeyWithTs(key, it.txn.readTs)
|
||||
} else {
|
||||
key = y.KeyWithTs(key, 0)
|
||||
}
|
||||
it.iitr.Seek(key)
|
||||
it.prefetch()
|
||||
}
|
||||
|
||||
// Rewind would rewind the iterator cursor all the way to zero-th position, which would be the
|
||||
// smallest key if iterating forward, and largest if iterating backward. It does not keep track of
|
||||
// whether the cursor started with a Seek().
|
||||
func (it *Iterator) Rewind() {
|
||||
it.Seek(nil)
|
||||
}
|
326
vendor/github.com/dgraph-io/badger/level_handler.go
generated
vendored
Normal file
326
vendor/github.com/dgraph-io/badger/level_handler.go
generated
vendored
Normal file
|
@ -0,0 +1,326 @@
|
|||
/*
|
||||
* Copyright 2017 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"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/dgraph-io/badger/table"
|
||||
"github.com/dgraph-io/badger/y"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type levelHandler struct {
|
||||
// Guards tables, totalSize.
|
||||
sync.RWMutex
|
||||
|
||||
// For level >= 1, tables are sorted by key ranges, which do not overlap.
|
||||
// For level 0, tables are sorted by time.
|
||||
// For level 0, newest table are at the back. Compact the oldest one first, which is at the front.
|
||||
tables []*table.Table
|
||||
totalSize int64
|
||||
|
||||
// The following are initialized once and const.
|
||||
level int
|
||||
strLevel string
|
||||
maxTotalSize int64
|
||||
db *DB
|
||||
}
|
||||
|
||||
func (s *levelHandler) getTotalSize() int64 {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
return s.totalSize
|
||||
}
|
||||
|
||||
// initTables replaces s.tables with given tables. This is done during loading.
|
||||
func (s *levelHandler) initTables(tables []*table.Table) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
s.tables = tables
|
||||
s.totalSize = 0
|
||||
for _, t := range tables {
|
||||
s.totalSize += t.Size()
|
||||
}
|
||||
|
||||
if s.level == 0 {
|
||||
// Key range will overlap. Just sort by fileID in ascending order
|
||||
// because newer tables are at the end of level 0.
|
||||
sort.Slice(s.tables, func(i, j int) bool {
|
||||
return s.tables[i].ID() < s.tables[j].ID()
|
||||
})
|
||||
} else {
|
||||
// Sort tables by keys.
|
||||
sort.Slice(s.tables, func(i, j int) bool {
|
||||
return y.CompareKeys(s.tables[i].Smallest(), s.tables[j].Smallest()) < 0
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// deleteTables remove tables idx0, ..., idx1-1.
|
||||
func (s *levelHandler) deleteTables(toDel []*table.Table) error {
|
||||
s.Lock() // s.Unlock() below
|
||||
|
||||
toDelMap := make(map[uint64]struct{})
|
||||
for _, t := range toDel {
|
||||
toDelMap[t.ID()] = struct{}{}
|
||||
}
|
||||
|
||||
// Make a copy as iterators might be keeping a slice of tables.
|
||||
var newTables []*table.Table
|
||||
for _, t := range s.tables {
|
||||
_, found := toDelMap[t.ID()]
|
||||
if !found {
|
||||
newTables = append(newTables, t)
|
||||
continue
|
||||
}
|
||||
s.totalSize -= t.Size()
|
||||
}
|
||||
s.tables = newTables
|
||||
|
||||
s.Unlock() // Unlock s _before_ we DecrRef our tables, which can be slow.
|
||||
|
||||
return decrRefs(toDel)
|
||||
}
|
||||
|
||||
// replaceTables will replace tables[left:right] with newTables. Note this EXCLUDES tables[right].
|
||||
// You must call decr() to delete the old tables _after_ writing the update to the manifest.
|
||||
func (s *levelHandler) replaceTables(toDel, toAdd []*table.Table) error {
|
||||
// Need to re-search the range of tables in this level to be replaced as other goroutines might
|
||||
// be changing it as well. (They can't touch our tables, but if they add/remove other tables,
|
||||
// the indices get shifted around.)
|
||||
s.Lock() // We s.Unlock() below.
|
||||
|
||||
toDelMap := make(map[uint64]struct{})
|
||||
for _, t := range toDel {
|
||||
toDelMap[t.ID()] = struct{}{}
|
||||
}
|
||||
var newTables []*table.Table
|
||||
for _, t := range s.tables {
|
||||
_, found := toDelMap[t.ID()]
|
||||
if !found {
|
||||
newTables = append(newTables, t)
|
||||
continue
|
||||
}
|
||||
s.totalSize -= t.Size()
|
||||
}
|
||||
|
||||
// Increase totalSize first.
|
||||
for _, t := range toAdd {
|
||||
s.totalSize += t.Size()
|
||||
t.IncrRef()
|
||||
newTables = append(newTables, t)
|
||||
}
|
||||
|
||||
// Assign tables.
|
||||
s.tables = newTables
|
||||
sort.Slice(s.tables, func(i, j int) bool {
|
||||
return y.CompareKeys(s.tables[i].Smallest(), s.tables[j].Smallest()) < 0
|
||||
})
|
||||
s.Unlock() // s.Unlock before we DecrRef tables -- that can be slow.
|
||||
return decrRefs(toDel)
|
||||
}
|
||||
|
||||
// addTable adds toAdd table to levelHandler. Normally when we add tables to levelHandler, we sort
|
||||
// tables based on table.Smallest. This is required for correctness of the system. But in case of
|
||||
// stream writer this can be avoided. We can just add tables to levelHandler's table list
|
||||
// and after all addTable calls, we can sort table list(check sortTable method).
|
||||
// NOTE: levelHandler.sortTables() should be called after call addTable calls are done.
|
||||
func (s *levelHandler) addTable(t *table.Table) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
s.totalSize += t.Size() // Increase totalSize first.
|
||||
t.IncrRef()
|
||||
s.tables = append(s.tables, t)
|
||||
}
|
||||
|
||||
// sortTables sorts tables of levelHandler based on table.Smallest.
|
||||
// Normally it should be called after all addTable calls.
|
||||
func (s *levelHandler) sortTables() {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
sort.Slice(s.tables, func(i, j int) bool {
|
||||
return y.CompareKeys(s.tables[i].Smallest(), s.tables[j].Smallest()) < 0
|
||||
})
|
||||
}
|
||||
|
||||
func decrRefs(tables []*table.Table) error {
|
||||
for _, table := range tables {
|
||||
if err := table.DecrRef(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newLevelHandler(db *DB, level int) *levelHandler {
|
||||
return &levelHandler{
|
||||
level: level,
|
||||
strLevel: fmt.Sprintf("l%d", level),
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// tryAddLevel0Table returns true if ok and no stalling.
|
||||
func (s *levelHandler) tryAddLevel0Table(t *table.Table) bool {
|
||||
y.AssertTrue(s.level == 0)
|
||||
// Need lock as we may be deleting the first table during a level 0 compaction.
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
if len(s.tables) >= s.db.opt.NumLevelZeroTablesStall {
|
||||
return false
|
||||
}
|
||||
|
||||
s.tables = append(s.tables, t)
|
||||
t.IncrRef()
|
||||
s.totalSize += t.Size()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *levelHandler) numTables() int {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
return len(s.tables)
|
||||
}
|
||||
|
||||
func (s *levelHandler) close() error {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
var err error
|
||||
for _, t := range s.tables {
|
||||
if closeErr := t.Close(); closeErr != nil && err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
}
|
||||
return errors.Wrap(err, "levelHandler.close")
|
||||
}
|
||||
|
||||
// getTableForKey acquires a read-lock to access s.tables. It returns a list of tableHandlers.
|
||||
func (s *levelHandler) getTableForKey(key []byte) ([]*table.Table, func() error) {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
if s.level == 0 {
|
||||
// For level 0, we need to check every table. Remember to make a copy as s.tables may change
|
||||
// once we exit this function, and we don't want to lock s.tables while seeking in tables.
|
||||
// CAUTION: Reverse the tables.
|
||||
out := make([]*table.Table, 0, len(s.tables))
|
||||
for i := len(s.tables) - 1; i >= 0; i-- {
|
||||
out = append(out, s.tables[i])
|
||||
s.tables[i].IncrRef()
|
||||
}
|
||||
return out, func() error {
|
||||
for _, t := range out {
|
||||
if err := t.DecrRef(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// For level >= 1, we can do a binary search as key range does not overlap.
|
||||
idx := sort.Search(len(s.tables), func(i int) bool {
|
||||
return y.CompareKeys(s.tables[i].Biggest(), key) >= 0
|
||||
})
|
||||
if idx >= len(s.tables) {
|
||||
// Given key is strictly > than every element we have.
|
||||
return nil, func() error { return nil }
|
||||
}
|
||||
tbl := s.tables[idx]
|
||||
tbl.IncrRef()
|
||||
return []*table.Table{tbl}, tbl.DecrRef
|
||||
}
|
||||
|
||||
// get returns value for a given key or the key after that. If not found, return nil.
|
||||
func (s *levelHandler) get(key []byte) (y.ValueStruct, error) {
|
||||
tables, decr := s.getTableForKey(key)
|
||||
keyNoTs := y.ParseKey(key)
|
||||
|
||||
var maxVs y.ValueStruct
|
||||
for _, th := range tables {
|
||||
if th.DoesNotHave(keyNoTs) {
|
||||
y.NumLSMBloomHits.Add(s.strLevel, 1)
|
||||
continue
|
||||
}
|
||||
|
||||
it := th.NewIterator(false)
|
||||
defer it.Close()
|
||||
|
||||
y.NumLSMGets.Add(s.strLevel, 1)
|
||||
it.Seek(key)
|
||||
if !it.Valid() {
|
||||
continue
|
||||
}
|
||||
if y.SameKey(key, it.Key()) {
|
||||
if version := y.ParseTs(it.Key()); maxVs.Version < version {
|
||||
maxVs = it.Value()
|
||||
maxVs.Version = version
|
||||
}
|
||||
}
|
||||
}
|
||||
return maxVs, decr()
|
||||
}
|
||||
|
||||
// appendIterators appends iterators to an array of iterators, for merging.
|
||||
// Note: This obtains references for the table handlers. Remember to close these iterators.
|
||||
func (s *levelHandler) appendIterators(iters []y.Iterator, opt *IteratorOptions) []y.Iterator {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
if s.level == 0 {
|
||||
// Remember to add in reverse order!
|
||||
// The newer table at the end of s.tables should be added first as it takes precedence.
|
||||
// Level 0 tables are not in key sorted order, so we need to consider them one by one.
|
||||
var out []*table.Table
|
||||
for _, t := range s.tables {
|
||||
if opt.pickTable(t) {
|
||||
out = append(out, t)
|
||||
}
|
||||
}
|
||||
return appendIteratorsReversed(iters, out, opt.Reverse)
|
||||
}
|
||||
|
||||
tables := opt.pickTables(s.tables)
|
||||
if len(tables) == 0 {
|
||||
return iters
|
||||
}
|
||||
return append(iters, table.NewConcatIterator(tables, opt.Reverse))
|
||||
}
|
||||
|
||||
type levelHandlerRLocked struct{}
|
||||
|
||||
// overlappingTables returns the tables that intersect with key range. Returns a half-interval.
|
||||
// This function should already have acquired a read lock, and this is so important the caller must
|
||||
// pass an empty parameter declaring such.
|
||||
func (s *levelHandler) overlappingTables(_ levelHandlerRLocked, kr keyRange) (int, int) {
|
||||
if len(kr.left) == 0 || len(kr.right) == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
left := sort.Search(len(s.tables), func(i int) bool {
|
||||
return y.CompareKeys(kr.left, s.tables[i].Biggest()) <= 0
|
||||
})
|
||||
right := sort.Search(len(s.tables), func(i int) bool {
|
||||
return y.CompareKeys(kr.right, s.tables[i].Smallest()) < 0
|
||||
})
|
||||
return left, right
|
||||
}
|
1092
vendor/github.com/dgraph-io/badger/levels.go
generated
vendored
Normal file
1092
vendor/github.com/dgraph-io/badger/levels.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
85
vendor/github.com/dgraph-io/badger/logger.go
generated
vendored
Normal file
85
vendor/github.com/dgraph-io/badger/logger.go
generated
vendored
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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 (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Logger is implemented by any logging system that is used for standard logs.
|
||||
type Logger interface {
|
||||
Errorf(string, ...interface{})
|
||||
Warningf(string, ...interface{})
|
||||
Infof(string, ...interface{})
|
||||
Debugf(string, ...interface{})
|
||||
}
|
||||
|
||||
// Errorf logs an ERROR log message to the logger specified in opts or to the
|
||||
// global logger if no logger is specified in opts.
|
||||
func (opt *Options) Errorf(format string, v ...interface{}) {
|
||||
if opt.Logger == nil {
|
||||
return
|
||||
}
|
||||
opt.Logger.Errorf(format, v...)
|
||||
}
|
||||
|
||||
// Infof logs an INFO message to the logger specified in opts.
|
||||
func (opt *Options) Infof(format string, v ...interface{}) {
|
||||
if opt.Logger == nil {
|
||||
return
|
||||
}
|
||||
opt.Logger.Infof(format, v...)
|
||||
}
|
||||
|
||||
// Warningf logs a WARNING message to the logger specified in opts.
|
||||
func (opt *Options) Warningf(format string, v ...interface{}) {
|
||||
if opt.Logger == nil {
|
||||
return
|
||||
}
|
||||
opt.Logger.Warningf(format, v...)
|
||||
}
|
||||
|
||||
// Debugf logs a DEBUG message to the logger specified in opts.
|
||||
func (opt *Options) Debugf(format string, v ...interface{}) {
|
||||
if opt.Logger == nil {
|
||||
return
|
||||
}
|
||||
opt.Logger.Debugf(format, v...)
|
||||
}
|
||||
|
||||
type defaultLog struct {
|
||||
*log.Logger
|
||||
}
|
||||
|
||||
var defaultLogger = &defaultLog{Logger: log.New(os.Stderr, "badger ", log.LstdFlags)}
|
||||
|
||||
func (l *defaultLog) Errorf(f string, v ...interface{}) {
|
||||
l.Printf("ERROR: "+f, v...)
|
||||
}
|
||||
|
||||
func (l *defaultLog) Warningf(f string, v ...interface{}) {
|
||||
l.Printf("WARNING: "+f, v...)
|
||||
}
|
||||
|
||||
func (l *defaultLog) Infof(f string, v ...interface{}) {
|
||||
l.Printf("INFO: "+f, v...)
|
||||
}
|
||||
|
||||
func (l *defaultLog) Debugf(f string, v ...interface{}) {
|
||||
l.Printf("DEBUG: "+f, v...)
|
||||
}
|
81
vendor/github.com/dgraph-io/badger/managed_db.go
generated
vendored
Normal file
81
vendor/github.com/dgraph-io/badger/managed_db.go
generated
vendored
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright 2017 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
|
||||
|
||||
// OpenManaged returns a new DB, which allows more control over setting
|
||||
// transaction timestamps, aka managed mode.
|
||||
//
|
||||
// This is only useful for databases built on top of Badger (like Dgraph), and
|
||||
// can be ignored by most users.
|
||||
func OpenManaged(opts Options) (*DB, error) {
|
||||
opts.managedTxns = true
|
||||
return Open(opts)
|
||||
}
|
||||
|
||||
// NewTransactionAt follows the same logic as DB.NewTransaction(), but uses the
|
||||
// provided read timestamp.
|
||||
//
|
||||
// This is only useful for databases built on top of Badger (like Dgraph), and
|
||||
// can be ignored by most users.
|
||||
func (db *DB) NewTransactionAt(readTs uint64, update bool) *Txn {
|
||||
if !db.opt.managedTxns {
|
||||
panic("Cannot use NewTransactionAt with managedDB=false. Use NewTransaction instead.")
|
||||
}
|
||||
txn := db.newTransaction(update, true)
|
||||
txn.readTs = readTs
|
||||
return txn
|
||||
}
|
||||
|
||||
// NewWriteBatchAt is similar to NewWriteBatch but it allows user to set the commit timestamp.
|
||||
// NewWriteBatchAt is supposed to be used only in the managed mode.
|
||||
func (db *DB) NewWriteBatchAt(commitTs uint64) *WriteBatch {
|
||||
if !db.opt.managedTxns {
|
||||
panic("cannot use NewWriteBatchAt with managedDB=false. Use NewWriteBatch instead")
|
||||
}
|
||||
|
||||
wb := db.newWriteBatch()
|
||||
wb.commitTs = commitTs
|
||||
wb.txn.commitTs = commitTs
|
||||
return wb
|
||||
}
|
||||
|
||||
// CommitAt commits the transaction, following the same logic as Commit(), but
|
||||
// at the given commit timestamp. This will panic if not used with managed transactions.
|
||||
//
|
||||
// This is only useful for databases built on top of Badger (like Dgraph), and
|
||||
// can be ignored by most users.
|
||||
func (txn *Txn) CommitAt(commitTs uint64, callback func(error)) error {
|
||||
if !txn.db.opt.managedTxns {
|
||||
panic("Cannot use CommitAt with managedDB=false. Use Commit instead.")
|
||||
}
|
||||
txn.commitTs = commitTs
|
||||
if callback == nil {
|
||||
return txn.Commit()
|
||||
}
|
||||
txn.CommitWith(callback)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDiscardTs sets a timestamp at or below which, any invalid or deleted
|
||||
// versions can be discarded from the LSM tree, and thence from the value log to
|
||||
// reclaim disk space. Can only be used with managed transactions.
|
||||
func (db *DB) SetDiscardTs(ts uint64) {
|
||||
if !db.opt.managedTxns {
|
||||
panic("Cannot use SetDiscardTs with managedDB=false.")
|
||||
}
|
||||
db.orc.setDiscardTs(ts)
|
||||
}
|
456
vendor/github.com/dgraph-io/badger/manifest.go
generated
vendored
Normal file
456
vendor/github.com/dgraph-io/badger/manifest.go
generated
vendored
Normal file
|
@ -0,0 +1,456 @@
|
|||
/*
|
||||
* Copyright 2017 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 (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/dgraph-io/badger/pb"
|
||||
"github.com/dgraph-io/badger/y"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Manifest represents the contents of the MANIFEST file in a Badger store.
|
||||
//
|
||||
// The MANIFEST file describes the startup state of the db -- all LSM files and what level they're
|
||||
// at.
|
||||
//
|
||||
// It consists of a sequence of ManifestChangeSet objects. Each of these is treated atomically,
|
||||
// and contains a sequence of ManifestChange's (file creations/deletions) which we use to
|
||||
// reconstruct the manifest at startup.
|
||||
type Manifest struct {
|
||||
Levels []levelManifest
|
||||
Tables map[uint64]TableManifest
|
||||
|
||||
// Contains total number of creation and deletion changes in the manifest -- used to compute
|
||||
// whether it'd be useful to rewrite the manifest.
|
||||
Creations int
|
||||
Deletions int
|
||||
}
|
||||
|
||||
func createManifest() Manifest {
|
||||
levels := make([]levelManifest, 0)
|
||||
return Manifest{
|
||||
Levels: levels,
|
||||
Tables: make(map[uint64]TableManifest),
|
||||
}
|
||||
}
|
||||
|
||||
// levelManifest contains information about LSM tree levels
|
||||
// in the MANIFEST file.
|
||||
type levelManifest struct {
|
||||
Tables map[uint64]struct{} // Set of table id's
|
||||
}
|
||||
|
||||
// TableManifest contains information about a specific level
|
||||
// in the LSM tree.
|
||||
type TableManifest struct {
|
||||
Level uint8
|
||||
Checksum []byte
|
||||
}
|
||||
|
||||
// manifestFile holds the file pointer (and other info) about the manifest file, which is a log
|
||||
// file we append to.
|
||||
type manifestFile struct {
|
||||
fp *os.File
|
||||
directory string
|
||||
// We make this configurable so that unit tests can hit rewrite() code quickly
|
||||
deletionsRewriteThreshold int
|
||||
|
||||
// Guards appends, which includes access to the manifest field.
|
||||
appendLock sync.Mutex
|
||||
|
||||
// Used to track the current state of the manifest, used when rewriting.
|
||||
manifest Manifest
|
||||
}
|
||||
|
||||
const (
|
||||
// ManifestFilename is the filename for the manifest file.
|
||||
ManifestFilename = "MANIFEST"
|
||||
manifestRewriteFilename = "MANIFEST-REWRITE"
|
||||
manifestDeletionsRewriteThreshold = 10000
|
||||
manifestDeletionsRatio = 10
|
||||
)
|
||||
|
||||
// asChanges returns a sequence of changes that could be used to recreate the Manifest in its
|
||||
// present state.
|
||||
func (m *Manifest) asChanges() []*pb.ManifestChange {
|
||||
changes := make([]*pb.ManifestChange, 0, len(m.Tables))
|
||||
for id, tm := range m.Tables {
|
||||
changes = append(changes, newCreateChange(id, int(tm.Level), tm.Checksum))
|
||||
}
|
||||
return changes
|
||||
}
|
||||
|
||||
func (m *Manifest) clone() Manifest {
|
||||
changeSet := pb.ManifestChangeSet{Changes: m.asChanges()}
|
||||
ret := createManifest()
|
||||
y.Check(applyChangeSet(&ret, &changeSet))
|
||||
return ret
|
||||
}
|
||||
|
||||
// openOrCreateManifestFile opens a Badger manifest file if it exists, or creates on if
|
||||
// one doesn’t.
|
||||
func openOrCreateManifestFile(dir string, readOnly bool) (
|
||||
ret *manifestFile, result Manifest, err error) {
|
||||
return helpOpenOrCreateManifestFile(dir, readOnly, manifestDeletionsRewriteThreshold)
|
||||
}
|
||||
|
||||
func helpOpenOrCreateManifestFile(dir string, readOnly bool, deletionsThreshold int) (
|
||||
*manifestFile, Manifest, error) {
|
||||
|
||||
path := filepath.Join(dir, ManifestFilename)
|
||||
var flags uint32
|
||||
if readOnly {
|
||||
flags |= y.ReadOnly
|
||||
}
|
||||
fp, err := y.OpenExistingFile(path, flags) // We explicitly sync in addChanges, outside the lock.
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, Manifest{}, err
|
||||
}
|
||||
if readOnly {
|
||||
return nil, Manifest{}, fmt.Errorf("no manifest found, required for read-only db")
|
||||
}
|
||||
m := createManifest()
|
||||
fp, netCreations, err := helpRewrite(dir, &m)
|
||||
if err != nil {
|
||||
return nil, Manifest{}, err
|
||||
}
|
||||
y.AssertTrue(netCreations == 0)
|
||||
mf := &manifestFile{
|
||||
fp: fp,
|
||||
directory: dir,
|
||||
manifest: m.clone(),
|
||||
deletionsRewriteThreshold: deletionsThreshold,
|
||||
}
|
||||
return mf, m, nil
|
||||
}
|
||||
|
||||
manifest, truncOffset, err := ReplayManifestFile(fp)
|
||||
if err != nil {
|
||||
_ = fp.Close()
|
||||
return nil, Manifest{}, err
|
||||
}
|
||||
|
||||
if !readOnly {
|
||||
// Truncate file so we don't have a half-written entry at the end.
|
||||
if err := fp.Truncate(truncOffset); err != nil {
|
||||
_ = fp.Close()
|
||||
return nil, Manifest{}, err
|
||||
}
|
||||
}
|
||||
if _, err = fp.Seek(0, io.SeekEnd); err != nil {
|
||||
_ = fp.Close()
|
||||
return nil, Manifest{}, err
|
||||
}
|
||||
|
||||
mf := &manifestFile{
|
||||
fp: fp,
|
||||
directory: dir,
|
||||
manifest: manifest.clone(),
|
||||
deletionsRewriteThreshold: deletionsThreshold,
|
||||
}
|
||||
return mf, manifest, nil
|
||||
}
|
||||
|
||||
func (mf *manifestFile) close() error {
|
||||
return mf.fp.Close()
|
||||
}
|
||||
|
||||
// addChanges writes a batch of changes, atomically, to the file. By "atomically" that means when
|
||||
// we replay the MANIFEST file, we'll either replay all the changes or none of them. (The truth of
|
||||
// this depends on the filesystem -- some might append garbage data if a system crash happens at
|
||||
// the wrong time.)
|
||||
func (mf *manifestFile) addChanges(changesParam []*pb.ManifestChange) error {
|
||||
changes := pb.ManifestChangeSet{Changes: changesParam}
|
||||
buf, err := proto.Marshal(&changes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Maybe we could use O_APPEND instead (on certain file systems)
|
||||
mf.appendLock.Lock()
|
||||
if err := applyChangeSet(&mf.manifest, &changes); err != nil {
|
||||
mf.appendLock.Unlock()
|
||||
return err
|
||||
}
|
||||
// Rewrite manifest if it'd shrink by 1/10 and it's big enough to care
|
||||
if mf.manifest.Deletions > mf.deletionsRewriteThreshold &&
|
||||
mf.manifest.Deletions > manifestDeletionsRatio*(mf.manifest.Creations-mf.manifest.Deletions) {
|
||||
if err := mf.rewrite(); err != nil {
|
||||
mf.appendLock.Unlock()
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
var lenCrcBuf [8]byte
|
||||
binary.BigEndian.PutUint32(lenCrcBuf[0:4], uint32(len(buf)))
|
||||
binary.BigEndian.PutUint32(lenCrcBuf[4:8], crc32.Checksum(buf, y.CastagnoliCrcTable))
|
||||
buf = append(lenCrcBuf[:], buf...)
|
||||
if _, err := mf.fp.Write(buf); err != nil {
|
||||
mf.appendLock.Unlock()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
mf.appendLock.Unlock()
|
||||
return y.FileSync(mf.fp)
|
||||
}
|
||||
|
||||
// Has to be 4 bytes. The value can never change, ever, anyway.
|
||||
var magicText = [4]byte{'B', 'd', 'g', 'r'}
|
||||
|
||||
// The magic version number.
|
||||
const magicVersion = 4
|
||||
|
||||
func helpRewrite(dir string, m *Manifest) (*os.File, int, error) {
|
||||
rewritePath := filepath.Join(dir, manifestRewriteFilename)
|
||||
// We explicitly sync.
|
||||
fp, err := y.OpenTruncFile(rewritePath, false)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
buf := make([]byte, 8)
|
||||
copy(buf[0:4], magicText[:])
|
||||
binary.BigEndian.PutUint32(buf[4:8], magicVersion)
|
||||
|
||||
netCreations := len(m.Tables)
|
||||
changes := m.asChanges()
|
||||
set := pb.ManifestChangeSet{Changes: changes}
|
||||
|
||||
changeBuf, err := proto.Marshal(&set)
|
||||
if err != nil {
|
||||
fp.Close()
|
||||
return nil, 0, err
|
||||
}
|
||||
var lenCrcBuf [8]byte
|
||||
binary.BigEndian.PutUint32(lenCrcBuf[0:4], uint32(len(changeBuf)))
|
||||
binary.BigEndian.PutUint32(lenCrcBuf[4:8], crc32.Checksum(changeBuf, y.CastagnoliCrcTable))
|
||||
buf = append(buf, lenCrcBuf[:]...)
|
||||
buf = append(buf, changeBuf...)
|
||||
if _, err := fp.Write(buf); err != nil {
|
||||
fp.Close()
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := y.FileSync(fp); err != nil {
|
||||
fp.Close()
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// In Windows the files should be closed before doing a Rename.
|
||||
if err = fp.Close(); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
manifestPath := filepath.Join(dir, ManifestFilename)
|
||||
if err := os.Rename(rewritePath, manifestPath); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
fp, err = y.OpenExistingFile(manifestPath, 0)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if _, err := fp.Seek(0, io.SeekEnd); err != nil {
|
||||
fp.Close()
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := syncDir(dir); err != nil {
|
||||
fp.Close()
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return fp, netCreations, nil
|
||||
}
|
||||
|
||||
// Must be called while appendLock is held.
|
||||
func (mf *manifestFile) rewrite() error {
|
||||
// In Windows the files should be closed before doing a Rename.
|
||||
if err := mf.fp.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
fp, netCreations, err := helpRewrite(mf.directory, &mf.manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mf.fp = fp
|
||||
mf.manifest.Creations = netCreations
|
||||
mf.manifest.Deletions = 0
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type countingReader struct {
|
||||
wrapped *bufio.Reader
|
||||
count int64
|
||||
}
|
||||
|
||||
func (r *countingReader) Read(p []byte) (n int, err error) {
|
||||
n, err = r.wrapped.Read(p)
|
||||
r.count += int64(n)
|
||||
return
|
||||
}
|
||||
|
||||
func (r *countingReader) ReadByte() (b byte, err error) {
|
||||
b, err = r.wrapped.ReadByte()
|
||||
if err == nil {
|
||||
r.count++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
errBadMagic = errors.New("manifest has bad magic")
|
||||
errBadChecksum = errors.New("manifest has checksum mismatch")
|
||||
)
|
||||
|
||||
// ReplayManifestFile reads the manifest file and constructs two manifest objects. (We need one
|
||||
// immutable copy and one mutable copy of the manifest. Easiest way is to construct two of them.)
|
||||
// Also, returns the last offset after a completely read manifest entry -- the file must be
|
||||
// truncated at that point before further appends are made (if there is a partial entry after
|
||||
// that). In normal conditions, truncOffset is the file size.
|
||||
func ReplayManifestFile(fp *os.File) (Manifest, int64, error) {
|
||||
r := countingReader{wrapped: bufio.NewReader(fp)}
|
||||
|
||||
var magicBuf [8]byte
|
||||
if _, err := io.ReadFull(&r, magicBuf[:]); err != nil {
|
||||
return Manifest{}, 0, errBadMagic
|
||||
}
|
||||
if !bytes.Equal(magicBuf[0:4], magicText[:]) {
|
||||
return Manifest{}, 0, errBadMagic
|
||||
}
|
||||
version := binary.BigEndian.Uint32(magicBuf[4:8])
|
||||
if version != magicVersion {
|
||||
return Manifest{}, 0,
|
||||
//nolint:lll
|
||||
fmt.Errorf("manifest has unsupported version: %d (we support %d).\n"+
|
||||
"Please see https://github.com/dgraph-io/badger/blob/master/README.md#i-see-manifest-has-unsupported-version-x-we-support-y-error"+
|
||||
" on how to fix this.",
|
||||
version, magicVersion)
|
||||
}
|
||||
|
||||
stat, err := fp.Stat()
|
||||
if err != nil {
|
||||
return Manifest{}, 0, err
|
||||
}
|
||||
|
||||
build := createManifest()
|
||||
var offset int64
|
||||
for {
|
||||
offset = r.count
|
||||
var lenCrcBuf [8]byte
|
||||
_, err := io.ReadFull(&r, lenCrcBuf[:])
|
||||
if err != nil {
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
break
|
||||
}
|
||||
return Manifest{}, 0, err
|
||||
}
|
||||
length := binary.BigEndian.Uint32(lenCrcBuf[0:4])
|
||||
// Sanity check to ensure we don't over-allocate memory.
|
||||
if length > uint32(stat.Size()) {
|
||||
return Manifest{}, 0, errors.Errorf(
|
||||
"Buffer length: %d greater than file size: %d. Manifest file might be corrupted",
|
||||
length, stat.Size())
|
||||
}
|
||||
var buf = make([]byte, length)
|
||||
if _, err := io.ReadFull(&r, buf); err != nil {
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
break
|
||||
}
|
||||
return Manifest{}, 0, err
|
||||
}
|
||||
if crc32.Checksum(buf, y.CastagnoliCrcTable) != binary.BigEndian.Uint32(lenCrcBuf[4:8]) {
|
||||
return Manifest{}, 0, errBadChecksum
|
||||
}
|
||||
|
||||
var changeSet pb.ManifestChangeSet
|
||||
if err := proto.Unmarshal(buf, &changeSet); err != nil {
|
||||
return Manifest{}, 0, err
|
||||
}
|
||||
|
||||
if err := applyChangeSet(&build, &changeSet); err != nil {
|
||||
return Manifest{}, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return build, offset, nil
|
||||
}
|
||||
|
||||
func applyManifestChange(build *Manifest, tc *pb.ManifestChange) error {
|
||||
switch tc.Op {
|
||||
case pb.ManifestChange_CREATE:
|
||||
if _, ok := build.Tables[tc.Id]; ok {
|
||||
return fmt.Errorf("MANIFEST invalid, table %d exists", tc.Id)
|
||||
}
|
||||
build.Tables[tc.Id] = TableManifest{
|
||||
Level: uint8(tc.Level),
|
||||
Checksum: append([]byte{}, tc.Checksum...),
|
||||
}
|
||||
for len(build.Levels) <= int(tc.Level) {
|
||||
build.Levels = append(build.Levels, levelManifest{make(map[uint64]struct{})})
|
||||
}
|
||||
build.Levels[tc.Level].Tables[tc.Id] = struct{}{}
|
||||
build.Creations++
|
||||
case pb.ManifestChange_DELETE:
|
||||
tm, ok := build.Tables[tc.Id]
|
||||
if !ok {
|
||||
return fmt.Errorf("MANIFEST removes non-existing table %d", tc.Id)
|
||||
}
|
||||
delete(build.Levels[tm.Level].Tables, tc.Id)
|
||||
delete(build.Tables, tc.Id)
|
||||
build.Deletions++
|
||||
default:
|
||||
return fmt.Errorf("MANIFEST file has invalid manifestChange op")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// This is not a "recoverable" error -- opening the KV store fails because the MANIFEST file is
|
||||
// just plain broken.
|
||||
func applyChangeSet(build *Manifest, changeSet *pb.ManifestChangeSet) error {
|
||||
for _, change := range changeSet.Changes {
|
||||
if err := applyManifestChange(build, change); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newCreateChange(id uint64, level int, checksum []byte) *pb.ManifestChange {
|
||||
return &pb.ManifestChange{
|
||||
Id: id,
|
||||
Op: pb.ManifestChange_CREATE,
|
||||
Level: uint32(level),
|
||||
Checksum: checksum,
|
||||
}
|
||||
}
|
||||
|
||||
func newDeleteChange(id uint64) *pb.ManifestChange {
|
||||
return &pb.ManifestChange{
|
||||
Id: id,
|
||||
Op: pb.ManifestChange_DELETE,
|
||||
}
|
||||
}
|
177
vendor/github.com/dgraph-io/badger/merge.go
generated
vendored
Normal file
177
vendor/github.com/dgraph-io/badger/merge.go
generated
vendored
Normal file
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* Copyright 2017 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"
|
||||
"time"
|
||||
|
||||
"github.com/dgraph-io/badger/y"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// MergeOperator represents a Badger merge operator.
|
||||
type MergeOperator struct {
|
||||
sync.RWMutex
|
||||
f MergeFunc
|
||||
db *DB
|
||||
key []byte
|
||||
closer *y.Closer
|
||||
}
|
||||
|
||||
// MergeFunc accepts two byte slices, one representing an existing value, and
|
||||
// another representing a new value that needs to be ‘merged’ into it. MergeFunc
|
||||
// contains the logic to perform the ‘merge’ and return an updated value.
|
||||
// MergeFunc could perform operations like integer addition, list appends etc.
|
||||
// Note that the ordering of the operands is maintained.
|
||||
type MergeFunc func(existingVal, newVal []byte) []byte
|
||||
|
||||
// GetMergeOperator creates a new MergeOperator for a given key and returns a
|
||||
// pointer to it. It also fires off a goroutine that performs a compaction using
|
||||
// the merge function that runs periodically, as specified by dur.
|
||||
func (db *DB) GetMergeOperator(key []byte,
|
||||
f MergeFunc, dur time.Duration) *MergeOperator {
|
||||
op := &MergeOperator{
|
||||
f: f,
|
||||
db: db,
|
||||
key: key,
|
||||
closer: y.NewCloser(1),
|
||||
}
|
||||
|
||||
go op.runCompactions(dur)
|
||||
return op
|
||||
}
|
||||
|
||||
var errNoMerge = errors.New("No need for merge")
|
||||
|
||||
func (op *MergeOperator) iterateAndMerge() (newVal []byte, latest uint64, err error) {
|
||||
txn := op.db.NewTransaction(false)
|
||||
defer txn.Discard()
|
||||
opt := DefaultIteratorOptions
|
||||
opt.AllVersions = true
|
||||
it := txn.NewKeyIterator(op.key, opt)
|
||||
defer it.Close()
|
||||
|
||||
var numVersions int
|
||||
for it.Rewind(); it.Valid(); it.Next() {
|
||||
item := it.Item()
|
||||
numVersions++
|
||||
if numVersions == 1 {
|
||||
// This should be the newVal, considering this is the latest version.
|
||||
newVal, err = item.ValueCopy(newVal)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
latest = item.Version()
|
||||
} else {
|
||||
if err := item.Value(func(oldVal []byte) error {
|
||||
// The merge should always be on the newVal considering it has the merge result of
|
||||
// the latest version. The value read should be the oldVal.
|
||||
newVal = op.f(oldVal, newVal)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
if item.DiscardEarlierVersions() {
|
||||
break
|
||||
}
|
||||
}
|
||||
if numVersions == 0 {
|
||||
return nil, latest, ErrKeyNotFound
|
||||
} else if numVersions == 1 {
|
||||
return newVal, latest, errNoMerge
|
||||
}
|
||||
return newVal, latest, nil
|
||||
}
|
||||
|
||||
func (op *MergeOperator) compact() error {
|
||||
op.Lock()
|
||||
defer op.Unlock()
|
||||
val, version, err := op.iterateAndMerge()
|
||||
if err == ErrKeyNotFound || err == errNoMerge {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
entries := []*Entry{
|
||||
{
|
||||
Key: y.KeyWithTs(op.key, version),
|
||||
Value: val,
|
||||
meta: bitDiscardEarlierVersions,
|
||||
},
|
||||
}
|
||||
// Write value back to the DB. It is important that we do not set the bitMergeEntry bit
|
||||
// here. When compaction happens, all the older merged entries will be removed.
|
||||
return op.db.batchSetAsync(entries, func(err error) {
|
||||
if err != nil {
|
||||
op.db.opt.Errorf("failed to insert the result of merge compaction: %s", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (op *MergeOperator) runCompactions(dur time.Duration) {
|
||||
ticker := time.NewTicker(dur)
|
||||
defer op.closer.Done()
|
||||
var stop bool
|
||||
for {
|
||||
select {
|
||||
case <-op.closer.HasBeenClosed():
|
||||
stop = true
|
||||
case <-ticker.C: // wait for tick
|
||||
}
|
||||
if err := op.compact(); err != nil {
|
||||
op.db.opt.Errorf("failure while running merge operation: %s", err)
|
||||
}
|
||||
if stop {
|
||||
ticker.Stop()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add records a value in Badger which will eventually be merged by a background
|
||||
// routine into the values that were recorded by previous invocations to Add().
|
||||
func (op *MergeOperator) Add(val []byte) error {
|
||||
return op.db.Update(func(txn *Txn) error {
|
||||
return txn.SetEntry(NewEntry(op.key, val).withMergeBit())
|
||||
})
|
||||
}
|
||||
|
||||
// Get returns the latest value for the merge operator, which is derived by
|
||||
// applying the merge function to all the values added so far.
|
||||
//
|
||||
// If Add has not been called even once, Get will return ErrKeyNotFound.
|
||||
func (op *MergeOperator) Get() ([]byte, error) {
|
||||
op.RLock()
|
||||
defer op.RUnlock()
|
||||
var existing []byte
|
||||
err := op.db.View(func(txn *Txn) (err error) {
|
||||
existing, _, err = op.iterateAndMerge()
|
||||
return err
|
||||
})
|
||||
if err == errNoMerge {
|
||||
return existing, nil
|
||||
}
|
||||
return existing, err
|
||||
}
|
||||
|
||||
// Stop waits for any pending merge to complete and then stops the background
|
||||
// goroutine.
|
||||
func (op *MergeOperator) Stop() {
|
||||
op.closer.SignalAndWait()
|
||||
}
|
420
vendor/github.com/dgraph-io/badger/options.go
generated
vendored
Normal file
420
vendor/github.com/dgraph-io/badger/options.go
generated
vendored
Normal file
|
@ -0,0 +1,420 @@
|
|||
/*
|
||||
* Copyright 2017 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 (
|
||||
"github.com/dgraph-io/badger/options"
|
||||
)
|
||||
|
||||
// Note: If you add a new option X make sure you also add a WithX method on Options.
|
||||
|
||||
// Options are params for creating DB object.
|
||||
//
|
||||
// This package provides DefaultOptions which contains options that should
|
||||
// work for most applications. Consider using that as a starting point before
|
||||
// customizing it for your own needs.
|
||||
//
|
||||
// Each option X is documented on the WithX method.
|
||||
type Options struct {
|
||||
// Required options.
|
||||
|
||||
Dir string
|
||||
ValueDir string
|
||||
|
||||
// Usually modified options.
|
||||
|
||||
SyncWrites bool
|
||||
TableLoadingMode options.FileLoadingMode
|
||||
ValueLogLoadingMode options.FileLoadingMode
|
||||
NumVersionsToKeep int
|
||||
ReadOnly bool
|
||||
Truncate bool
|
||||
Logger Logger
|
||||
EventLogging bool
|
||||
|
||||
// Fine tuning options.
|
||||
|
||||
MaxTableSize int64
|
||||
LevelSizeMultiplier int
|
||||
MaxLevels int
|
||||
ValueThreshold int
|
||||
NumMemtables int
|
||||
|
||||
NumLevelZeroTables int
|
||||
NumLevelZeroTablesStall int
|
||||
|
||||
LevelOneSize int64
|
||||
ValueLogFileSize int64
|
||||
ValueLogMaxEntries uint32
|
||||
|
||||
NumCompactors int
|
||||
CompactL0OnClose bool
|
||||
LogRotatesToFlush int32
|
||||
// When set, checksum will be validated for each entry read from the value log file.
|
||||
VerifyValueChecksum bool
|
||||
|
||||
// BypassLockGaurd will bypass the lock guard on badger. Bypassing lock
|
||||
// guard can cause data corruption if multiple badger instances are using
|
||||
// the same directory. Use this options with caution.
|
||||
BypassLockGuard bool
|
||||
|
||||
// Transaction start and commit timestamps are managed by end-user.
|
||||
// This is only useful for databases built on top of Badger (like Dgraph).
|
||||
// Not recommended for most users.
|
||||
managedTxns bool
|
||||
|
||||
// 4. Flags for testing purposes
|
||||
// ------------------------------
|
||||
maxBatchCount int64 // max entries in batch
|
||||
maxBatchSize int64 // max batch size in bytes
|
||||
|
||||
}
|
||||
|
||||
// DefaultOptions sets a list of recommended options for good performance.
|
||||
// Feel free to modify these to suit your needs with the WithX methods.
|
||||
func DefaultOptions(path string) Options {
|
||||
return Options{
|
||||
Dir: path,
|
||||
ValueDir: path,
|
||||
LevelOneSize: 256 << 20,
|
||||
LevelSizeMultiplier: 10,
|
||||
TableLoadingMode: options.MemoryMap,
|
||||
ValueLogLoadingMode: options.MemoryMap,
|
||||
// table.MemoryMap to mmap() the tables.
|
||||
// table.Nothing to not preload the tables.
|
||||
MaxLevels: 7,
|
||||
MaxTableSize: 64 << 20,
|
||||
NumCompactors: 2, // Compactions can be expensive. Only run 2.
|
||||
NumLevelZeroTables: 5,
|
||||
NumLevelZeroTablesStall: 10,
|
||||
NumMemtables: 5,
|
||||
SyncWrites: true,
|
||||
NumVersionsToKeep: 1,
|
||||
CompactL0OnClose: true,
|
||||
VerifyValueChecksum: false,
|
||||
// Nothing to read/write value log using standard File I/O
|
||||
// MemoryMap to mmap() the value log files
|
||||
// (2^30 - 1)*2 when mmapping < 2^31 - 1, max int32.
|
||||
// -1 so 2*ValueLogFileSize won't overflow on 32-bit systems.
|
||||
ValueLogFileSize: 1<<30 - 1,
|
||||
|
||||
ValueLogMaxEntries: 1000000,
|
||||
ValueThreshold: 32,
|
||||
Truncate: false,
|
||||
Logger: defaultLogger,
|
||||
EventLogging: true,
|
||||
LogRotatesToFlush: 2,
|
||||
}
|
||||
}
|
||||
|
||||
// LSMOnlyOptions follows from DefaultOptions, but sets a higher ValueThreshold
|
||||
// so values would be colocated with the LSM tree, with value log largely acting
|
||||
// as a write-ahead log only. These options would reduce the disk usage of value
|
||||
// log, and make Badger act more like a typical LSM tree.
|
||||
func LSMOnlyOptions(path string) Options {
|
||||
// Max value length which fits in uint16.
|
||||
// Let's not set any other options, because they can cause issues with the
|
||||
// size of key-value a user can pass to Badger. For e.g., if we set
|
||||
// ValueLogFileSize to 64MB, a user can't pass a value more than that.
|
||||
// Setting it to ValueLogMaxEntries to 1000, can generate too many files.
|
||||
// These options are better configured on a usage basis, than broadly here.
|
||||
// The ValueThreshold is the most important setting a user needs to do to
|
||||
// achieve a heavier usage of LSM tree.
|
||||
// NOTE: If a user does not want to set 64KB as the ValueThreshold because
|
||||
// of performance reasons, 1KB would be a good option too, allowing
|
||||
// values smaller than 1KB to be colocated with the keys in the LSM tree.
|
||||
return DefaultOptions(path).WithValueThreshold(65500)
|
||||
}
|
||||
|
||||
// WithDir returns a new Options value with Dir set to the given value.
|
||||
//
|
||||
// Dir is the path of the directory where key data will be stored in.
|
||||
// If it doesn't exist, Badger will try to create it for you.
|
||||
// This is set automatically to be the path given to `DefaultOptions`.
|
||||
func (opt Options) WithDir(val string) Options {
|
||||
opt.Dir = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithValueDir returns a new Options value with ValueDir set to the given value.
|
||||
//
|
||||
// ValueDir is the path of the directory where value data will be stored in.
|
||||
// If it doesn't exist, Badger will try to create it for you.
|
||||
// This is set automatically to be the path given to `DefaultOptions`.
|
||||
func (opt Options) WithValueDir(val string) Options {
|
||||
opt.ValueDir = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithSyncWrites returns a new Options value with SyncWrites set to the given value.
|
||||
//
|
||||
// When SyncWrites is true all writes are synced to disk. Setting this to false would achieve better
|
||||
// performance, but may cause data loss in case of crash.
|
||||
//
|
||||
// The default value of SyncWrites is true.
|
||||
func (opt Options) WithSyncWrites(val bool) Options {
|
||||
opt.SyncWrites = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithTableLoadingMode returns a new Options value with TableLoadingMode set to the given value.
|
||||
//
|
||||
// TableLoadingMode indicates which file loading mode should be used for the LSM tree data files.
|
||||
//
|
||||
// The default value of TableLoadingMode is options.MemoryMap.
|
||||
func (opt Options) WithTableLoadingMode(val options.FileLoadingMode) Options {
|
||||
opt.TableLoadingMode = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithValueLogLoadingMode returns a new Options value with ValueLogLoadingMode set to the given
|
||||
// value.
|
||||
//
|
||||
// ValueLogLoadingMode indicates which file loading mode should be used for the value log data
|
||||
// files.
|
||||
//
|
||||
// The default value of ValueLogLoadingMode is options.MemoryMap.
|
||||
func (opt Options) WithValueLogLoadingMode(val options.FileLoadingMode) Options {
|
||||
opt.ValueLogLoadingMode = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithNumVersionsToKeep returns a new Options value with NumVersionsToKeep set to the given value.
|
||||
//
|
||||
// NumVersionsToKeep sets how many versions to keep per key at most.
|
||||
//
|
||||
// The default value of NumVersionsToKeep is 1.
|
||||
func (opt Options) WithNumVersionsToKeep(val int) Options {
|
||||
opt.NumVersionsToKeep = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithReadOnly returns a new Options value with ReadOnly set to the given value.
|
||||
//
|
||||
// When ReadOnly is true the DB will be opened on read-only mode.
|
||||
// Multiple processes can open the same Badger DB.
|
||||
// Note: if the DB being opened had crashed before and has vlog data to be replayed,
|
||||
// ReadOnly will cause Open to fail with an appropriate message.
|
||||
//
|
||||
// The default value of ReadOnly is false.
|
||||
func (opt Options) WithReadOnly(val bool) Options {
|
||||
opt.ReadOnly = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithTruncate returns a new Options value with Truncate set to the given value.
|
||||
//
|
||||
// Truncate indicates whether value log files should be truncated to delete corrupt data, if any.
|
||||
// This option is ignored when ReadOnly is true.
|
||||
//
|
||||
// The default value of Truncate is false.
|
||||
func (opt Options) WithTruncate(val bool) Options {
|
||||
opt.Truncate = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithLogger returns a new Options value with Logger set to the given value.
|
||||
//
|
||||
// Logger provides a way to configure what logger each value of badger.DB uses.
|
||||
//
|
||||
// The default value of Logger writes to stderr using the log package from the Go standard library.
|
||||
func (opt Options) WithLogger(val Logger) Options {
|
||||
opt.Logger = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithEventLogging returns a new Options value with EventLogging set to the given value.
|
||||
//
|
||||
// EventLogging provides a way to enable or disable trace.EventLog logging.
|
||||
//
|
||||
// The default value of EventLogging is true.
|
||||
func (opt Options) WithEventLogging(enabled bool) Options {
|
||||
opt.EventLogging = enabled
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithMaxTableSize returns a new Options value with MaxTableSize set to the given value.
|
||||
//
|
||||
// MaxTableSize sets the maximum size in bytes for each LSM table or file.
|
||||
//
|
||||
// The default value of MaxTableSize is 64MB.
|
||||
func (opt Options) WithMaxTableSize(val int64) Options {
|
||||
opt.MaxTableSize = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithLevelSizeMultiplier returns a new Options value with LevelSizeMultiplier set to the given
|
||||
// value.
|
||||
//
|
||||
// LevelSizeMultiplier sets the ratio between the maximum sizes of contiguous levels in the LSM.
|
||||
// Once a level grows to be larger than this ratio allowed, the compaction process will be
|
||||
// triggered.
|
||||
//
|
||||
// The default value of LevelSizeMultiplier is 10.
|
||||
func (opt Options) WithLevelSizeMultiplier(val int) Options {
|
||||
opt.LevelSizeMultiplier = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithMaxLevels returns a new Options value with MaxLevels set to the given value.
|
||||
//
|
||||
// Maximum number of levels of compaction allowed in the LSM.
|
||||
//
|
||||
// The default value of MaxLevels is 7.
|
||||
func (opt Options) WithMaxLevels(val int) Options {
|
||||
opt.MaxLevels = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithValueThreshold returns a new Options value with ValueThreshold set to the given value.
|
||||
//
|
||||
// ValueThreshold sets the threshold used to decide whether a value is stored directly in the LSM
|
||||
// tree or separatedly in the log value files.
|
||||
//
|
||||
// The default value of ValueThreshold is 32, but LSMOnlyOptions sets it to 65500.
|
||||
func (opt Options) WithValueThreshold(val int) Options {
|
||||
opt.ValueThreshold = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithNumMemtables returns a new Options value with NumMemtables set to the given value.
|
||||
//
|
||||
// NumMemtables sets the maximum number of tables to keep in memory before stalling.
|
||||
//
|
||||
// The default value of NumMemtables is 5.
|
||||
func (opt Options) WithNumMemtables(val int) Options {
|
||||
opt.NumMemtables = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithNumLevelZeroTables returns a new Options value with NumLevelZeroTables set to the given
|
||||
// value.
|
||||
//
|
||||
// NumLevelZeroTables sets the maximum number of Level 0 tables before compaction starts.
|
||||
//
|
||||
// The default value of NumLevelZeroTables is 5.
|
||||
func (opt Options) WithNumLevelZeroTables(val int) Options {
|
||||
opt.NumLevelZeroTables = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithNumLevelZeroTablesStall returns a new Options value with NumLevelZeroTablesStall set to the
|
||||
// given value.
|
||||
//
|
||||
// NumLevelZeroTablesStall sets the number of Level 0 tables that once reached causes the DB to
|
||||
// stall until compaction succeeds.
|
||||
//
|
||||
// The default value of NumLevelZeroTablesStall is 10.
|
||||
func (opt Options) WithNumLevelZeroTablesStall(val int) Options {
|
||||
opt.NumLevelZeroTablesStall = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithLevelOneSize returns a new Options value with LevelOneSize set to the given value.
|
||||
//
|
||||
// LevelOneSize sets the maximum total size for Level 1.
|
||||
//
|
||||
// The default value of LevelOneSize is 20MB.
|
||||
func (opt Options) WithLevelOneSize(val int64) Options {
|
||||
opt.LevelOneSize = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithValueLogFileSize returns a new Options value with ValueLogFileSize set to the given value.
|
||||
//
|
||||
// ValueLogFileSize sets the maximum size of a single value log file.
|
||||
//
|
||||
// The default value of ValueLogFileSize is 1GB.
|
||||
func (opt Options) WithValueLogFileSize(val int64) Options {
|
||||
opt.ValueLogFileSize = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithValueLogMaxEntries returns a new Options value with ValueLogMaxEntries set to the given
|
||||
// value.
|
||||
//
|
||||
// ValueLogMaxEntries sets the maximum number of entries a value log file can hold approximately.
|
||||
// A actual size limit of a value log file is the minimum of ValueLogFileSize and
|
||||
// ValueLogMaxEntries.
|
||||
//
|
||||
// The default value of ValueLogMaxEntries is one million (1000000).
|
||||
func (opt Options) WithValueLogMaxEntries(val uint32) Options {
|
||||
opt.ValueLogMaxEntries = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithNumCompactors returns a new Options value with NumCompactors set to the given value.
|
||||
//
|
||||
// NumCompactors sets the number of compaction workers to run concurrently.
|
||||
// Setting this to zero stops compactions, which could eventually cause writes to block forever.
|
||||
//
|
||||
// The default value of NumCompactors is 2.
|
||||
func (opt Options) WithNumCompactors(val int) Options {
|
||||
opt.NumCompactors = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithCompactL0OnClose returns a new Options value with CompactL0OnClose set to the given value.
|
||||
//
|
||||
// CompactL0OnClose determines whether Level 0 should be compacted before closing the DB.
|
||||
// This ensures that both reads and writes are efficient when the DB is opened later.
|
||||
//
|
||||
// The default value of CompactL0OnClose is true.
|
||||
func (opt Options) WithCompactL0OnClose(val bool) Options {
|
||||
opt.CompactL0OnClose = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithLogRotatesToFlush returns a new Options value with LogRotatesToFlush set to the given value.
|
||||
//
|
||||
// LogRotatesToFlush sets the number of value log file rotates after which the Memtables are
|
||||
// flushed to disk. This is useful in write loads with fewer keys and larger values. This work load
|
||||
// would fill up the value logs quickly, while not filling up the Memtables. Thus, on a crash
|
||||
// and restart, the value log head could cause the replay of a good number of value log files
|
||||
// which can slow things on start.
|
||||
//
|
||||
// The default value of LogRotatesToFlush is 2.
|
||||
func (opt Options) WithLogRotatesToFlush(val int32) Options {
|
||||
opt.LogRotatesToFlush = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithVerifyValueChecksum returns a new Options value with VerifyValueChecksum set to
|
||||
// the given value.
|
||||
//
|
||||
// When VerifyValueChecksum is set to true, checksum will be verified for every entry read
|
||||
// from the value log. If the value is stored in SST (value size less than value threshold) then the
|
||||
// checksum validation will not be done.
|
||||
//
|
||||
// The default value of VerifyValueChecksum is False.
|
||||
func (opt Options) WithVerifyValueChecksum(val bool) Options {
|
||||
opt.VerifyValueChecksum = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithBypassLockGuard returns a new Options value with BypassLockGuard
|
||||
// set to the given value.
|
||||
//
|
||||
// When BypassLockGuard option is set, badger will not acquire a lock on the
|
||||
// directory. This could lead to data corruption if multiple badger instances
|
||||
// write to the same data directory. Use this option with caution.
|
||||
//
|
||||
// The default value of BypassLockGuard is false.
|
||||
func (opt Options) WithBypassLockGuard(b bool) Options {
|
||||
opt.BypassLockGuard = b
|
||||
return opt
|
||||
}
|
30
vendor/github.com/dgraph-io/badger/options/options.go
generated
vendored
Normal file
30
vendor/github.com/dgraph-io/badger/options/options.go
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright 2017 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 options
|
||||
|
||||
// FileLoadingMode specifies how data in LSM table files and value log files should
|
||||
// be loaded.
|
||||
type FileLoadingMode int
|
||||
|
||||
const (
|
||||
// FileIO indicates that files must be loaded using standard I/O
|
||||
FileIO FileLoadingMode = iota
|
||||
// LoadToRAM indicates that file must be loaded into RAM
|
||||
LoadToRAM
|
||||
// MemoryMap indicates that that the file must be memory-mapped
|
||||
MemoryMap
|
||||
)
|
7
vendor/github.com/dgraph-io/badger/pb/gen.sh
generated
vendored
Normal file
7
vendor/github.com/dgraph-io/badger/pb/gen.sh
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
|
||||
# You might need to go get -v github.com/gogo/protobuf/...
|
||||
|
||||
protos=${GOPATH-$HOME/go}/src/github.com/dgraph-io/badger/pb
|
||||
pushd $protos > /dev/null
|
||||
protoc --gofast_out=plugins=grpc:. -I=. pb.proto
|
1359
vendor/github.com/dgraph-io/badger/pb/pb.pb.go
generated
vendored
Normal file
1359
vendor/github.com/dgraph-io/badger/pb/pb.pb.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
54
vendor/github.com/dgraph-io/badger/pb/pb.proto
generated
vendored
Normal file
54
vendor/github.com/dgraph-io/badger/pb/pb.proto
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (C) 2017 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.
|
||||
*/
|
||||
|
||||
// Use protos/gen.sh to generate .pb.go files.
|
||||
syntax = "proto3";
|
||||
|
||||
package pb;
|
||||
|
||||
message KV {
|
||||
bytes key = 1;
|
||||
bytes value = 2;
|
||||
bytes user_meta = 3;
|
||||
uint64 version = 4;
|
||||
uint64 expires_at = 5;
|
||||
bytes meta = 6;
|
||||
|
||||
// Stream id is used to identify which stream the KV came from.
|
||||
uint32 stream_id = 10;
|
||||
// Stream done is used to indicate end of stream.
|
||||
bool stream_done = 11;
|
||||
}
|
||||
|
||||
message KVList {
|
||||
repeated KV kv = 1;
|
||||
}
|
||||
|
||||
message ManifestChangeSet {
|
||||
// A set of changes that are applied atomically.
|
||||
repeated ManifestChange changes = 1;
|
||||
}
|
||||
|
||||
message ManifestChange {
|
||||
uint64 Id = 1;
|
||||
enum Operation {
|
||||
CREATE = 0;
|
||||
DELETE = 1;
|
||||
}
|
||||
Operation Op = 2;
|
||||
uint32 Level = 3; // Only used for CREATE
|
||||
bytes Checksum = 4; // Only used for CREATE
|
||||
}
|
164
vendor/github.com/dgraph-io/badger/publisher.go
generated
vendored
Normal file
164
vendor/github.com/dgraph-io/badger/publisher.go
generated
vendored
Normal file
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* Copyright 2019 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"
|
||||
|
||||
"github.com/dgraph-io/badger/pb"
|
||||
"github.com/dgraph-io/badger/trie"
|
||||
"github.com/dgraph-io/badger/y"
|
||||
)
|
||||
|
||||
type subscriber struct {
|
||||
prefixes [][]byte
|
||||
sendCh chan<- *pb.KVList
|
||||
subCloser *y.Closer
|
||||
}
|
||||
|
||||
type publisher struct {
|
||||
sync.Mutex
|
||||
pubCh chan requests
|
||||
subscribers map[uint64]subscriber
|
||||
nextID uint64
|
||||
indexer *trie.Trie
|
||||
}
|
||||
|
||||
func newPublisher() *publisher {
|
||||
return &publisher{
|
||||
pubCh: make(chan requests, 1000),
|
||||
subscribers: make(map[uint64]subscriber),
|
||||
nextID: 0,
|
||||
indexer: trie.NewTrie(),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *publisher) listenForUpdates(c *y.Closer) {
|
||||
defer func() {
|
||||
p.cleanSubscribers()
|
||||
c.Done()
|
||||
}()
|
||||
slurp := func(batch requests) {
|
||||
for {
|
||||
select {
|
||||
case reqs := <-p.pubCh:
|
||||
batch = append(batch, reqs...)
|
||||
default:
|
||||
p.publishUpdates(batch)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-c.HasBeenClosed():
|
||||
return
|
||||
case reqs := <-p.pubCh:
|
||||
slurp(reqs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *publisher) publishUpdates(reqs requests) {
|
||||
p.Lock()
|
||||
defer func() {
|
||||
p.Unlock()
|
||||
// Release all the request.
|
||||
reqs.DecrRef()
|
||||
}()
|
||||
batchedUpdates := make(map[uint64]*pb.KVList)
|
||||
for _, req := range reqs {
|
||||
for _, e := range req.Entries {
|
||||
ids := p.indexer.Get(e.Key)
|
||||
if len(ids) > 0 {
|
||||
k := y.SafeCopy(nil, e.Key)
|
||||
kv := &pb.KV{
|
||||
Key: y.ParseKey(k),
|
||||
Value: y.SafeCopy(nil, e.Value),
|
||||
Meta: []byte{e.UserMeta},
|
||||
ExpiresAt: e.ExpiresAt,
|
||||
Version: y.ParseTs(k),
|
||||
}
|
||||
for id := range ids {
|
||||
if _, ok := batchedUpdates[id]; !ok {
|
||||
batchedUpdates[id] = &pb.KVList{}
|
||||
}
|
||||
batchedUpdates[id].Kv = append(batchedUpdates[id].Kv, kv)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for id, kvs := range batchedUpdates {
|
||||
p.subscribers[id].sendCh <- kvs
|
||||
}
|
||||
}
|
||||
|
||||
func (p *publisher) newSubscriber(c *y.Closer, prefixes ...[]byte) (<-chan *pb.KVList, uint64) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
ch := make(chan *pb.KVList, 1000)
|
||||
id := p.nextID
|
||||
// Increment next ID.
|
||||
p.nextID++
|
||||
p.subscribers[id] = subscriber{
|
||||
prefixes: prefixes,
|
||||
sendCh: ch,
|
||||
subCloser: c,
|
||||
}
|
||||
for _, prefix := range prefixes {
|
||||
p.indexer.Add(prefix, id)
|
||||
}
|
||||
return ch, id
|
||||
}
|
||||
|
||||
// cleanSubscribers stops all the subscribers. Ideally, It should be called while closing DB.
|
||||
func (p *publisher) cleanSubscribers() {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
for id, s := range p.subscribers {
|
||||
for _, prefix := range s.prefixes {
|
||||
p.indexer.Delete(prefix, id)
|
||||
}
|
||||
delete(p.subscribers, id)
|
||||
s.subCloser.SignalAndWait()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *publisher) deleteSubscriber(id uint64) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
if s, ok := p.subscribers[id]; ok {
|
||||
for _, prefix := range s.prefixes {
|
||||
p.indexer.Delete(prefix, id)
|
||||
}
|
||||
}
|
||||
delete(p.subscribers, id)
|
||||
}
|
||||
|
||||
func (p *publisher) sendUpdates(reqs requests) {
|
||||
if p.noOfSubscribers() != 0 {
|
||||
reqs.IncrRef()
|
||||
p.pubCh <- reqs
|
||||
}
|
||||
}
|
||||
|
||||
func (p *publisher) noOfSubscribers() int {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
return len(p.subscribers)
|
||||
}
|
113
vendor/github.com/dgraph-io/badger/skl/README.md
generated
vendored
Normal file
113
vendor/github.com/dgraph-io/badger/skl/README.md
generated
vendored
Normal file
|
@ -0,0 +1,113 @@
|
|||
This is much better than `skiplist` and `slist`.
|
||||
|
||||
```
|
||||
BenchmarkReadWrite/frac_0-8 3000000 537 ns/op
|
||||
BenchmarkReadWrite/frac_1-8 3000000 503 ns/op
|
||||
BenchmarkReadWrite/frac_2-8 3000000 492 ns/op
|
||||
BenchmarkReadWrite/frac_3-8 3000000 475 ns/op
|
||||
BenchmarkReadWrite/frac_4-8 3000000 440 ns/op
|
||||
BenchmarkReadWrite/frac_5-8 5000000 442 ns/op
|
||||
BenchmarkReadWrite/frac_6-8 5000000 380 ns/op
|
||||
BenchmarkReadWrite/frac_7-8 5000000 338 ns/op
|
||||
BenchmarkReadWrite/frac_8-8 5000000 294 ns/op
|
||||
BenchmarkReadWrite/frac_9-8 10000000 268 ns/op
|
||||
BenchmarkReadWrite/frac_10-8 100000000 26.3 ns/op
|
||||
```
|
||||
|
||||
And even better than a simple map with read-write lock:
|
||||
|
||||
```
|
||||
BenchmarkReadWriteMap/frac_0-8 2000000 774 ns/op
|
||||
BenchmarkReadWriteMap/frac_1-8 2000000 647 ns/op
|
||||
BenchmarkReadWriteMap/frac_2-8 3000000 605 ns/op
|
||||
BenchmarkReadWriteMap/frac_3-8 3000000 603 ns/op
|
||||
BenchmarkReadWriteMap/frac_4-8 3000000 556 ns/op
|
||||
BenchmarkReadWriteMap/frac_5-8 3000000 472 ns/op
|
||||
BenchmarkReadWriteMap/frac_6-8 3000000 476 ns/op
|
||||
BenchmarkReadWriteMap/frac_7-8 3000000 457 ns/op
|
||||
BenchmarkReadWriteMap/frac_8-8 5000000 444 ns/op
|
||||
BenchmarkReadWriteMap/frac_9-8 5000000 361 ns/op
|
||||
BenchmarkReadWriteMap/frac_10-8 10000000 212 ns/op
|
||||
```
|
||||
|
||||
# Node Pooling
|
||||
|
||||
Command used
|
||||
|
||||
```
|
||||
rm -Rf tmp && /usr/bin/time -l ./populate -keys_mil 10
|
||||
```
|
||||
|
||||
For pprof results, we run without using /usr/bin/time. There are four runs below.
|
||||
|
||||
Results seem to vary quite a bit between runs.
|
||||
|
||||
## Before node pooling
|
||||
|
||||
```
|
||||
1311.53MB of 1338.69MB total (97.97%)
|
||||
Dropped 30 nodes (cum <= 6.69MB)
|
||||
Showing top 10 nodes out of 37 (cum >= 12.50MB)
|
||||
flat flat% sum% cum cum%
|
||||
523.04MB 39.07% 39.07% 523.04MB 39.07% github.com/dgraph-io/badger/skl.(*Skiplist).Put
|
||||
184.51MB 13.78% 52.85% 184.51MB 13.78% runtime.stringtoslicebyte
|
||||
166.01MB 12.40% 65.25% 689.04MB 51.47% github.com/dgraph-io/badger/mem.(*Table).Put
|
||||
165MB 12.33% 77.58% 165MB 12.33% runtime.convT2E
|
||||
116.92MB 8.73% 86.31% 116.92MB 8.73% bytes.makeSlice
|
||||
62.50MB 4.67% 90.98% 62.50MB 4.67% main.newValue
|
||||
34.50MB 2.58% 93.56% 34.50MB 2.58% github.com/dgraph-io/badger/table.(*BlockIterator).parseKV
|
||||
25.50MB 1.90% 95.46% 100.06MB 7.47% github.com/dgraph-io/badger/y.(*MergeIterator).Next
|
||||
21.06MB 1.57% 97.04% 21.06MB 1.57% github.com/dgraph-io/badger/table.(*Table).read
|
||||
12.50MB 0.93% 97.97% 12.50MB 0.93% github.com/dgraph-io/badger/table.header.Encode
|
||||
|
||||
128.31 real 329.37 user 17.11 sys
|
||||
3355660288 maximum resident set size
|
||||
0 average shared memory size
|
||||
0 average unshared data size
|
||||
0 average unshared stack size
|
||||
2203080 page reclaims
|
||||
764 page faults
|
||||
0 swaps
|
||||
275 block input operations
|
||||
76 block output operations
|
||||
0 messages sent
|
||||
0 messages received
|
||||
0 signals received
|
||||
49173 voluntary context switches
|
||||
599922 involuntary context switches
|
||||
```
|
||||
|
||||
## After node pooling
|
||||
|
||||
```
|
||||
1963.13MB of 2026.09MB total (96.89%)
|
||||
Dropped 29 nodes (cum <= 10.13MB)
|
||||
Showing top 10 nodes out of 41 (cum >= 185.62MB)
|
||||
flat flat% sum% cum cum%
|
||||
658.05MB 32.48% 32.48% 658.05MB 32.48% github.com/dgraph-io/badger/skl.glob..func1
|
||||
297.51MB 14.68% 47.16% 297.51MB 14.68% runtime.convT2E
|
||||
257.51MB 12.71% 59.87% 257.51MB 12.71% runtime.stringtoslicebyte
|
||||
249.01MB 12.29% 72.16% 1007.06MB 49.70% github.com/dgraph-io/badger/mem.(*Table).Put
|
||||
142.43MB 7.03% 79.19% 142.43MB 7.03% bytes.makeSlice
|
||||
100MB 4.94% 84.13% 758.05MB 37.41% github.com/dgraph-io/badger/skl.newNode
|
||||
99.50MB 4.91% 89.04% 99.50MB 4.91% main.newValue
|
||||
75MB 3.70% 92.74% 75MB 3.70% github.com/dgraph-io/badger/table.(*BlockIterator).parseKV
|
||||
44.62MB 2.20% 94.94% 44.62MB 2.20% github.com/dgraph-io/badger/table.(*Table).read
|
||||
39.50MB 1.95% 96.89% 185.62MB 9.16% github.com/dgraph-io/badger/y.(*MergeIterator).Next
|
||||
|
||||
135.58 real 374.29 user 17.65 sys
|
||||
3740614656 maximum resident set size
|
||||
0 average shared memory size
|
||||
0 average unshared data size
|
||||
0 average unshared stack size
|
||||
2276566 page reclaims
|
||||
770 page faults
|
||||
0 swaps
|
||||
128 block input operations
|
||||
90 block output operations
|
||||
0 messages sent
|
||||
0 messages received
|
||||
0 signals received
|
||||
46434 voluntary context switches
|
||||
597049 involuntary context switches
|
||||
```
|
136
vendor/github.com/dgraph-io/badger/skl/arena.go
generated
vendored
Normal file
136
vendor/github.com/dgraph-io/badger/skl/arena.go
generated
vendored
Normal file
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright 2017 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 skl
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
"github.com/dgraph-io/badger/y"
|
||||
)
|
||||
|
||||
const (
|
||||
offsetSize = int(unsafe.Sizeof(uint32(0)))
|
||||
|
||||
// Always align nodes on 64-bit boundaries, even on 32-bit architectures,
|
||||
// so that the node.value field is 64-bit aligned. This is necessary because
|
||||
// node.getValueOffset uses atomic.LoadUint64, which expects its input
|
||||
// pointer to be 64-bit aligned.
|
||||
nodeAlign = int(unsafe.Sizeof(uint64(0))) - 1
|
||||
)
|
||||
|
||||
// Arena should be lock-free.
|
||||
type Arena struct {
|
||||
n uint32
|
||||
buf []byte
|
||||
}
|
||||
|
||||
// newArena returns a new arena.
|
||||
func newArena(n int64) *Arena {
|
||||
// Don't store data at position 0 in order to reserve offset=0 as a kind
|
||||
// of nil pointer.
|
||||
out := &Arena{
|
||||
n: 1,
|
||||
buf: make([]byte, n),
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *Arena) size() int64 {
|
||||
return int64(atomic.LoadUint32(&s.n))
|
||||
}
|
||||
|
||||
func (s *Arena) reset() {
|
||||
atomic.StoreUint32(&s.n, 0)
|
||||
}
|
||||
|
||||
// putNode allocates a node in the arena. The node is aligned on a pointer-sized
|
||||
// boundary. The arena offset of the node is returned.
|
||||
func (s *Arena) putNode(height int) uint32 {
|
||||
// Compute the amount of the tower that will never be used, since the height
|
||||
// is less than maxHeight.
|
||||
unusedSize := (maxHeight - height) * offsetSize
|
||||
|
||||
// Pad the allocation with enough bytes to ensure pointer alignment.
|
||||
l := uint32(MaxNodeSize - unusedSize + nodeAlign)
|
||||
n := atomic.AddUint32(&s.n, l)
|
||||
y.AssertTruef(int(n) <= len(s.buf),
|
||||
"Arena too small, toWrite:%d newTotal:%d limit:%d",
|
||||
l, n, len(s.buf))
|
||||
|
||||
// Return the aligned offset.
|
||||
m := (n - l + uint32(nodeAlign)) & ^uint32(nodeAlign)
|
||||
return m
|
||||
}
|
||||
|
||||
// Put will *copy* val into arena. To make better use of this, reuse your input
|
||||
// val buffer. Returns an offset into buf. User is responsible for remembering
|
||||
// size of val. We could also store this size inside arena but the encoding and
|
||||
// decoding will incur some overhead.
|
||||
func (s *Arena) putVal(v y.ValueStruct) uint32 {
|
||||
l := uint32(v.EncodedSize())
|
||||
n := atomic.AddUint32(&s.n, l)
|
||||
y.AssertTruef(int(n) <= len(s.buf),
|
||||
"Arena too small, toWrite:%d newTotal:%d limit:%d",
|
||||
l, n, len(s.buf))
|
||||
m := n - l
|
||||
v.Encode(s.buf[m:])
|
||||
return m
|
||||
}
|
||||
|
||||
func (s *Arena) putKey(key []byte) uint32 {
|
||||
l := uint32(len(key))
|
||||
n := atomic.AddUint32(&s.n, l)
|
||||
y.AssertTruef(int(n) <= len(s.buf),
|
||||
"Arena too small, toWrite:%d newTotal:%d limit:%d",
|
||||
l, n, len(s.buf))
|
||||
m := n - l
|
||||
y.AssertTrue(len(key) == copy(s.buf[m:n], key))
|
||||
return m
|
||||
}
|
||||
|
||||
// getNode returns a pointer to the node located at offset. If the offset is
|
||||
// zero, then the nil node pointer is returned.
|
||||
func (s *Arena) getNode(offset uint32) *node {
|
||||
if offset == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (*node)(unsafe.Pointer(&s.buf[offset]))
|
||||
}
|
||||
|
||||
// getKey returns byte slice at offset.
|
||||
func (s *Arena) getKey(offset uint32, size uint16) []byte {
|
||||
return s.buf[offset : offset+uint32(size)]
|
||||
}
|
||||
|
||||
// getVal returns byte slice at offset. The given size should be just the value
|
||||
// size and should NOT include the meta bytes.
|
||||
func (s *Arena) getVal(offset uint32, size uint16) (ret y.ValueStruct) {
|
||||
ret.Decode(s.buf[offset : offset+uint32(size)])
|
||||
return
|
||||
}
|
||||
|
||||
// getNodeOffset returns the offset of node in the arena. If the node pointer is
|
||||
// nil, then the zero offset is returned.
|
||||
func (s *Arena) getNodeOffset(nd *node) uint32 {
|
||||
if nd == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return uint32(uintptr(unsafe.Pointer(nd)) - uintptr(unsafe.Pointer(&s.buf[0])))
|
||||
}
|
517
vendor/github.com/dgraph-io/badger/skl/skl.go
generated
vendored
Normal file
517
vendor/github.com/dgraph-io/badger/skl/skl.go
generated
vendored
Normal file
|
@ -0,0 +1,517 @@
|
|||
/*
|
||||
* Copyright 2017 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
Adapted from RocksDB inline skiplist.
|
||||
|
||||
Key differences:
|
||||
- No optimization for sequential inserts (no "prev").
|
||||
- No custom comparator.
|
||||
- Support overwrites. This requires care when we see the same key when inserting.
|
||||
For RocksDB or LevelDB, overwrites are implemented as a newer sequence number in the key, so
|
||||
there is no need for values. We don't intend to support versioning. In-place updates of values
|
||||
would be more efficient.
|
||||
- We discard all non-concurrent code.
|
||||
- We do not support Splices. This simplifies the code a lot.
|
||||
- No AllocateNode or other pointer arithmetic.
|
||||
- We combine the findLessThan, findGreaterOrEqual, etc into one function.
|
||||
*/
|
||||
|
||||
package skl
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
"github.com/dgraph-io/badger/y"
|
||||
"github.com/dgraph-io/ristretto/z"
|
||||
)
|
||||
|
||||
const (
|
||||
maxHeight = 20
|
||||
heightIncrease = math.MaxUint32 / 3
|
||||
)
|
||||
|
||||
// MaxNodeSize is the memory footprint of a node of maximum height.
|
||||
const MaxNodeSize = int(unsafe.Sizeof(node{}))
|
||||
|
||||
type node struct {
|
||||
// Multiple parts of the value are encoded as a single uint64 so that it
|
||||
// can be atomically loaded and stored:
|
||||
// value offset: uint32 (bits 0-31)
|
||||
// value size : uint16 (bits 32-47)
|
||||
value uint64
|
||||
|
||||
// A byte slice is 24 bytes. We are trying to save space here.
|
||||
keyOffset uint32 // Immutable. No need to lock to access key.
|
||||
keySize uint16 // Immutable. No need to lock to access key.
|
||||
|
||||
// Height of the tower.
|
||||
height uint16
|
||||
|
||||
// Most nodes do not need to use the full height of the tower, since the
|
||||
// probability of each successive level decreases exponentially. Because
|
||||
// these elements are never accessed, they do not need to be allocated.
|
||||
// Therefore, when a node is allocated in the arena, its memory footprint
|
||||
// is deliberately truncated to not include unneeded tower elements.
|
||||
//
|
||||
// All accesses to elements should use CAS operations, with no need to lock.
|
||||
tower [maxHeight]uint32
|
||||
}
|
||||
|
||||
// Skiplist maps keys to values (in memory)
|
||||
type Skiplist struct {
|
||||
height int32 // Current height. 1 <= height <= kMaxHeight. CAS.
|
||||
head *node
|
||||
ref int32
|
||||
arena *Arena
|
||||
}
|
||||
|
||||
// IncrRef increases the refcount
|
||||
func (s *Skiplist) IncrRef() {
|
||||
atomic.AddInt32(&s.ref, 1)
|
||||
}
|
||||
|
||||
// DecrRef decrements the refcount, deallocating the Skiplist when done using it
|
||||
func (s *Skiplist) DecrRef() {
|
||||
newRef := atomic.AddInt32(&s.ref, -1)
|
||||
if newRef > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
s.arena.reset()
|
||||
// Indicate we are closed. Good for testing. Also, lets GC reclaim memory. Race condition
|
||||
// here would suggest we are accessing skiplist when we are supposed to have no reference!
|
||||
s.arena = nil
|
||||
// Since the head references the arena's buf, as long as the head is kept around
|
||||
// GC can't release the buf.
|
||||
s.head = nil
|
||||
}
|
||||
|
||||
func newNode(arena *Arena, key []byte, v y.ValueStruct, height int) *node {
|
||||
// The base level is already allocated in the node struct.
|
||||
offset := arena.putNode(height)
|
||||
node := arena.getNode(offset)
|
||||
node.keyOffset = arena.putKey(key)
|
||||
node.keySize = uint16(len(key))
|
||||
node.height = uint16(height)
|
||||
node.value = encodeValue(arena.putVal(v), v.EncodedSize())
|
||||
return node
|
||||
}
|
||||
|
||||
func encodeValue(valOffset uint32, valSize uint16) uint64 {
|
||||
return uint64(valSize)<<32 | uint64(valOffset)
|
||||
}
|
||||
|
||||
func decodeValue(value uint64) (valOffset uint32, valSize uint16) {
|
||||
valOffset = uint32(value)
|
||||
valSize = uint16(value >> 32)
|
||||
return
|
||||
}
|
||||
|
||||
// NewSkiplist makes a new empty skiplist, with a given arena size
|
||||
func NewSkiplist(arenaSize int64) *Skiplist {
|
||||
arena := newArena(arenaSize)
|
||||
head := newNode(arena, nil, y.ValueStruct{}, maxHeight)
|
||||
return &Skiplist{
|
||||
height: 1,
|
||||
head: head,
|
||||
arena: arena,
|
||||
ref: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *node) getValueOffset() (uint32, uint16) {
|
||||
value := atomic.LoadUint64(&s.value)
|
||||
return decodeValue(value)
|
||||
}
|
||||
|
||||
func (s *node) key(arena *Arena) []byte {
|
||||
return arena.getKey(s.keyOffset, s.keySize)
|
||||
}
|
||||
|
||||
func (s *node) setValue(arena *Arena, v y.ValueStruct) {
|
||||
valOffset := arena.putVal(v)
|
||||
value := encodeValue(valOffset, v.EncodedSize())
|
||||
atomic.StoreUint64(&s.value, value)
|
||||
}
|
||||
|
||||
func (s *node) getNextOffset(h int) uint32 {
|
||||
return atomic.LoadUint32(&s.tower[h])
|
||||
}
|
||||
|
||||
func (s *node) casNextOffset(h int, old, val uint32) bool {
|
||||
return atomic.CompareAndSwapUint32(&s.tower[h], old, val)
|
||||
}
|
||||
|
||||
// Returns true if key is strictly > n.key.
|
||||
// If n is nil, this is an "end" marker and we return false.
|
||||
//func (s *Skiplist) keyIsAfterNode(key []byte, n *node) bool {
|
||||
// y.AssertTrue(n != s.head)
|
||||
// return n != nil && y.CompareKeys(key, n.key) > 0
|
||||
//}
|
||||
|
||||
func (s *Skiplist) randomHeight() int {
|
||||
h := 1
|
||||
for h < maxHeight && z.FastRand() <= heightIncrease {
|
||||
h++
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (s *Skiplist) getNext(nd *node, height int) *node {
|
||||
return s.arena.getNode(nd.getNextOffset(height))
|
||||
}
|
||||
|
||||
// findNear finds the node near to key.
|
||||
// If less=true, it finds rightmost node such that node.key < key (if allowEqual=false) or
|
||||
// node.key <= key (if allowEqual=true).
|
||||
// If less=false, it finds leftmost node such that node.key > key (if allowEqual=false) or
|
||||
// node.key >= key (if allowEqual=true).
|
||||
// Returns the node found. The bool returned is true if the node has key equal to given key.
|
||||
func (s *Skiplist) findNear(key []byte, less bool, allowEqual bool) (*node, bool) {
|
||||
x := s.head
|
||||
level := int(s.getHeight() - 1)
|
||||
for {
|
||||
// Assume x.key < key.
|
||||
next := s.getNext(x, level)
|
||||
if next == nil {
|
||||
// x.key < key < END OF LIST
|
||||
if level > 0 {
|
||||
// Can descend further to iterate closer to the end.
|
||||
level--
|
||||
continue
|
||||
}
|
||||
// Level=0. Cannot descend further. Let's return something that makes sense.
|
||||
if !less {
|
||||
return nil, false
|
||||
}
|
||||
// Try to return x. Make sure it is not a head node.
|
||||
if x == s.head {
|
||||
return nil, false
|
||||
}
|
||||
return x, false
|
||||
}
|
||||
|
||||
nextKey := next.key(s.arena)
|
||||
cmp := y.CompareKeys(key, nextKey)
|
||||
if cmp > 0 {
|
||||
// x.key < next.key < key. We can continue to move right.
|
||||
x = next
|
||||
continue
|
||||
}
|
||||
if cmp == 0 {
|
||||
// x.key < key == next.key.
|
||||
if allowEqual {
|
||||
return next, true
|
||||
}
|
||||
if !less {
|
||||
// We want >, so go to base level to grab the next bigger note.
|
||||
return s.getNext(next, 0), false
|
||||
}
|
||||
// We want <. If not base level, we should go closer in the next level.
|
||||
if level > 0 {
|
||||
level--
|
||||
continue
|
||||
}
|
||||
// On base level. Return x.
|
||||
if x == s.head {
|
||||
return nil, false
|
||||
}
|
||||
return x, false
|
||||
}
|
||||
// cmp < 0. In other words, x.key < key < next.
|
||||
if level > 0 {
|
||||
level--
|
||||
continue
|
||||
}
|
||||
// At base level. Need to return something.
|
||||
if !less {
|
||||
return next, false
|
||||
}
|
||||
// Try to return x. Make sure it is not a head node.
|
||||
if x == s.head {
|
||||
return nil, false
|
||||
}
|
||||
return x, false
|
||||
}
|
||||
}
|
||||
|
||||
// findSpliceForLevel returns (outBefore, outAfter) with outBefore.key <= key <= outAfter.key.
|
||||
// The input "before" tells us where to start looking.
|
||||
// If we found a node with the same key, then we return outBefore = outAfter.
|
||||
// Otherwise, outBefore.key < key < outAfter.key.
|
||||
func (s *Skiplist) findSpliceForLevel(key []byte, before *node, level int) (*node, *node) {
|
||||
for {
|
||||
// Assume before.key < key.
|
||||
next := s.getNext(before, level)
|
||||
if next == nil {
|
||||
return before, next
|
||||
}
|
||||
nextKey := next.key(s.arena)
|
||||
cmp := y.CompareKeys(key, nextKey)
|
||||
if cmp == 0 {
|
||||
// Equality case.
|
||||
return next, next
|
||||
}
|
||||
if cmp < 0 {
|
||||
// before.key < key < next.key. We are done for this level.
|
||||
return before, next
|
||||
}
|
||||
before = next // Keep moving right on this level.
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Skiplist) getHeight() int32 {
|
||||
return atomic.LoadInt32(&s.height)
|
||||
}
|
||||
|
||||
// Put inserts the key-value pair.
|
||||
func (s *Skiplist) Put(key []byte, v y.ValueStruct) {
|
||||
// Since we allow overwrite, we may not need to create a new node. We might not even need to
|
||||
// increase the height. Let's defer these actions.
|
||||
|
||||
listHeight := s.getHeight()
|
||||
var prev [maxHeight + 1]*node
|
||||
var next [maxHeight + 1]*node
|
||||
prev[listHeight] = s.head
|
||||
next[listHeight] = nil
|
||||
for i := int(listHeight) - 1; i >= 0; i-- {
|
||||
// Use higher level to speed up for current level.
|
||||
prev[i], next[i] = s.findSpliceForLevel(key, prev[i+1], i)
|
||||
if prev[i] == next[i] {
|
||||
prev[i].setValue(s.arena, v)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// We do need to create a new node.
|
||||
height := s.randomHeight()
|
||||
x := newNode(s.arena, key, v, height)
|
||||
|
||||
// Try to increase s.height via CAS.
|
||||
listHeight = s.getHeight()
|
||||
for height > int(listHeight) {
|
||||
if atomic.CompareAndSwapInt32(&s.height, listHeight, int32(height)) {
|
||||
// Successfully increased skiplist.height.
|
||||
break
|
||||
}
|
||||
listHeight = s.getHeight()
|
||||
}
|
||||
|
||||
// We always insert from the base level and up. After you add a node in base level, we cannot
|
||||
// create a node in the level above because it would have discovered the node in the base level.
|
||||
for i := 0; i < height; i++ {
|
||||
for {
|
||||
if prev[i] == nil {
|
||||
y.AssertTrue(i > 1) // This cannot happen in base level.
|
||||
// We haven't computed prev, next for this level because height exceeds old listHeight.
|
||||
// For these levels, we expect the lists to be sparse, so we can just search from head.
|
||||
prev[i], next[i] = s.findSpliceForLevel(key, s.head, i)
|
||||
// Someone adds the exact same key before we are able to do so. This can only happen on
|
||||
// the base level. But we know we are not on the base level.
|
||||
y.AssertTrue(prev[i] != next[i])
|
||||
}
|
||||
nextOffset := s.arena.getNodeOffset(next[i])
|
||||
x.tower[i] = nextOffset
|
||||
if prev[i].casNextOffset(i, nextOffset, s.arena.getNodeOffset(x)) {
|
||||
// Managed to insert x between prev[i] and next[i]. Go to the next level.
|
||||
break
|
||||
}
|
||||
// CAS failed. We need to recompute prev and next.
|
||||
// It is unlikely to be helpful to try to use a different level as we redo the search,
|
||||
// because it is unlikely that lots of nodes are inserted between prev[i] and next[i].
|
||||
prev[i], next[i] = s.findSpliceForLevel(key, prev[i], i)
|
||||
if prev[i] == next[i] {
|
||||
y.AssertTruef(i == 0, "Equality can happen only on base level: %d", i)
|
||||
prev[i].setValue(s.arena, v)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Empty returns if the Skiplist is empty.
|
||||
func (s *Skiplist) Empty() bool {
|
||||
return s.findLast() == nil
|
||||
}
|
||||
|
||||
// findLast returns the last element. If head (empty list), we return nil. All the find functions
|
||||
// will NEVER return the head nodes.
|
||||
func (s *Skiplist) findLast() *node {
|
||||
n := s.head
|
||||
level := int(s.getHeight()) - 1
|
||||
for {
|
||||
next := s.getNext(n, level)
|
||||
if next != nil {
|
||||
n = next
|
||||
continue
|
||||
}
|
||||
if level == 0 {
|
||||
if n == s.head {
|
||||
return nil
|
||||
}
|
||||
return n
|
||||
}
|
||||
level--
|
||||
}
|
||||
}
|
||||
|
||||
// Get gets the value associated with the key. It returns a valid value if it finds equal or earlier
|
||||
// version of the same key.
|
||||
func (s *Skiplist) Get(key []byte) y.ValueStruct {
|
||||
n, _ := s.findNear(key, false, true) // findGreaterOrEqual.
|
||||
if n == nil {
|
||||
return y.ValueStruct{}
|
||||
}
|
||||
|
||||
nextKey := s.arena.getKey(n.keyOffset, n.keySize)
|
||||
if !y.SameKey(key, nextKey) {
|
||||
return y.ValueStruct{}
|
||||
}
|
||||
|
||||
valOffset, valSize := n.getValueOffset()
|
||||
vs := s.arena.getVal(valOffset, valSize)
|
||||
vs.Version = y.ParseTs(nextKey)
|
||||
return vs
|
||||
}
|
||||
|
||||
// NewIterator returns a skiplist iterator. You have to Close() the iterator.
|
||||
func (s *Skiplist) NewIterator() *Iterator {
|
||||
s.IncrRef()
|
||||
return &Iterator{list: s}
|
||||
}
|
||||
|
||||
// MemSize returns the size of the Skiplist in terms of how much memory is used within its internal
|
||||
// arena.
|
||||
func (s *Skiplist) MemSize() int64 { return s.arena.size() }
|
||||
|
||||
// Iterator is an iterator over skiplist object. For new objects, you just
|
||||
// need to initialize Iterator.list.
|
||||
type Iterator struct {
|
||||
list *Skiplist
|
||||
n *node
|
||||
}
|
||||
|
||||
// Close frees the resources held by the iterator
|
||||
func (s *Iterator) Close() error {
|
||||
s.list.DecrRef()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Valid returns true iff the iterator is positioned at a valid node.
|
||||
func (s *Iterator) Valid() bool { return s.n != nil }
|
||||
|
||||
// Key returns the key at the current position.
|
||||
func (s *Iterator) Key() []byte {
|
||||
return s.list.arena.getKey(s.n.keyOffset, s.n.keySize)
|
||||
}
|
||||
|
||||
// Value returns value.
|
||||
func (s *Iterator) Value() y.ValueStruct {
|
||||
valOffset, valSize := s.n.getValueOffset()
|
||||
return s.list.arena.getVal(valOffset, valSize)
|
||||
}
|
||||
|
||||
// Next advances to the next position.
|
||||
func (s *Iterator) Next() {
|
||||
y.AssertTrue(s.Valid())
|
||||
s.n = s.list.getNext(s.n, 0)
|
||||
}
|
||||
|
||||
// Prev advances to the previous position.
|
||||
func (s *Iterator) Prev() {
|
||||
y.AssertTrue(s.Valid())
|
||||
s.n, _ = s.list.findNear(s.Key(), true, false) // find <. No equality allowed.
|
||||
}
|
||||
|
||||
// Seek advances to the first entry with a key >= target.
|
||||
func (s *Iterator) Seek(target []byte) {
|
||||
s.n, _ = s.list.findNear(target, false, true) // find >=.
|
||||
}
|
||||
|
||||
// SeekForPrev finds an entry with key <= target.
|
||||
func (s *Iterator) SeekForPrev(target []byte) {
|
||||
s.n, _ = s.list.findNear(target, true, true) // find <=.
|
||||
}
|
||||
|
||||
// SeekToFirst seeks position at the first entry in list.
|
||||
// Final state of iterator is Valid() iff list is not empty.
|
||||
func (s *Iterator) SeekToFirst() {
|
||||
s.n = s.list.getNext(s.list.head, 0)
|
||||
}
|
||||
|
||||
// SeekToLast seeks position at the last entry in list.
|
||||
// Final state of iterator is Valid() iff list is not empty.
|
||||
func (s *Iterator) SeekToLast() {
|
||||
s.n = s.list.findLast()
|
||||
}
|
||||
|
||||
// UniIterator is a unidirectional memtable iterator. It is a thin wrapper around
|
||||
// Iterator. We like to keep Iterator as before, because it is more powerful and
|
||||
// we might support bidirectional iterators in the future.
|
||||
type UniIterator struct {
|
||||
iter *Iterator
|
||||
reversed bool
|
||||
}
|
||||
|
||||
// NewUniIterator returns a UniIterator.
|
||||
func (s *Skiplist) NewUniIterator(reversed bool) *UniIterator {
|
||||
return &UniIterator{
|
||||
iter: s.NewIterator(),
|
||||
reversed: reversed,
|
||||
}
|
||||
}
|
||||
|
||||
// Next implements y.Interface
|
||||
func (s *UniIterator) Next() {
|
||||
if !s.reversed {
|
||||
s.iter.Next()
|
||||
} else {
|
||||
s.iter.Prev()
|
||||
}
|
||||
}
|
||||
|
||||
// Rewind implements y.Interface
|
||||
func (s *UniIterator) Rewind() {
|
||||
if !s.reversed {
|
||||
s.iter.SeekToFirst()
|
||||
} else {
|
||||
s.iter.SeekToLast()
|
||||