From 723c7c6244bd4101f665dceea961d8e6eacc7504 Mon Sep 17 00:00:00 2001 From: Ian Norden Date: Fri, 7 Jun 2019 08:42:10 -0500 Subject: [PATCH] fix backfill operations and dependency issue; hopefully travis will work now --- Gopkg.lock | 8 +- Gopkg.toml | 4 +- cmd/streamSubscribe.go | 79 +- cmd/syncPublishScreenAndServe.go | 1 + pkg/ipfs/api.go | 9 +- pkg/ipfs/converter_test.go | 11 +- pkg/ipfs/fetcher.go | 7 +- pkg/ipfs/{helpers => }/mocks/api.go | 0 pkg/ipfs/{helpers => }/mocks/converter.go | 0 pkg/ipfs/{helpers => }/mocks/publisher.go | 0 pkg/ipfs/{helpers => }/mocks/repository.go | 0 pkg/ipfs/{helpers => }/mocks/screener.go | 0 pkg/ipfs/{helpers => }/mocks/streamer.go | 0 pkg/ipfs/{helpers => mocks}/test_data.go | 6 +- pkg/ipfs/publisher_test.go | 11 +- pkg/ipfs/repository_test.go | 7 +- pkg/ipfs/resolver.go | 1 - pkg/ipfs/retreiver.go | 91 +- pkg/ipfs/screener.go | 7 +- pkg/ipfs/service.go | 34 +- pkg/ipfs/service_test.go | 15 +- pkg/ipfs/streamer_test.go | 7 +- pkg/ipfs/types.go | 2 +- vendor/github.com/dgraph-io/badger/.gitignore | 1 + .../github.com/dgraph-io/badger/.golangci.yml | 20 + .../github.com/dgraph-io/badger/.travis.yml | 25 + vendor/github.com/dgraph-io/badger/README.md | 7 +- vendor/github.com/dgraph-io/badger/backup.go | 145 +- .../dgraph-io/badger/backup_test.go | 519 ----- .../dgraph-io/badger/badger/.gitignore | 1 - .../dgraph-io/badger/badger/cmd/backup.go | 72 - .../dgraph-io/badger/badger/cmd/bank.go | 451 ----- .../dgraph-io/badger/badger/cmd/fill.go | 93 - .../dgraph-io/badger/badger/cmd/flatten.go | 56 - .../dgraph-io/badger/badger/cmd/info.go | 294 --- .../dgraph-io/badger/badger/cmd/restore.go | 81 - .../dgraph-io/badger/badger/cmd/root.go | 65 - .../dgraph-io/badger/badger/main.go | 42 - vendor/github.com/dgraph-io/badger/batch.go | 41 +- .../github.com/dgraph-io/badger/batch_test.go | 69 - .../github.com/dgraph-io/badger/compaction.go | 12 +- .../dgraph-io/badger/contrib/cover.sh | 23 - vendor/github.com/dgraph-io/badger/db.go | 371 +++- .../github.com/dgraph-io/badger/db2_test.go | 325 ---- vendor/github.com/dgraph-io/badger/db_test.go | 1708 ----------------- .../github.com/dgraph-io/badger/dir_unix.go | 4 +- .../dgraph-io/badger/dir_windows.go | 2 +- vendor/github.com/dgraph-io/badger/errors.go | 5 +- .../github.com/dgraph-io/badger/histogram.go | 169 ++ .../badger/images/benchmarks-rocksdb.png | Bin 66938 -> 0 bytes .../dgraph-io/badger/images/diggy-shadow.png | Bin 33101 -> 0 bytes .../badger/integration/testgc/.gitignore | 1 - .../badger/integration/testgc/main.go | 218 --- .../github.com/dgraph-io/badger/iterator.go | 14 +- .../dgraph-io/badger/iterator_test.go | 244 --- .../dgraph-io/badger/level_handler.go | 67 +- vendor/github.com/dgraph-io/badger/levels.go | 182 +- vendor/github.com/dgraph-io/badger/logger.go | 19 +- .../dgraph-io/badger/logger_test.go | 67 - .../dgraph-io/badger/managed_db_test.go | 353 ---- .../github.com/dgraph-io/badger/manifest.go | 6 +- .../dgraph-io/badger/manifest_test.go | 244 --- vendor/github.com/dgraph-io/badger/merge.go | 14 +- vendor/github.com/dgraph-io/badger/options.go | 35 +- .../github.com/dgraph-io/badger/package.json | 72 - vendor/github.com/dgraph-io/badger/pb/gen.sh | 2 +- .../github.com/dgraph-io/badger/pb/pb.pb.go | 139 +- .../github.com/dgraph-io/badger/pb/pb.proto | 3 + .../github.com/dgraph-io/badger/publisher.go | 151 ++ .../github.com/dgraph-io/badger/skl/arena.go | 2 +- vendor/github.com/dgraph-io/badger/skl/skl.go | 2 +- .../dgraph-io/badger/skl/skl_test.go | 475 ----- vendor/github.com/dgraph-io/badger/stream.go | 60 +- .../dgraph-io/badger/stream_test.go | 169 -- .../dgraph-io/badger/stream_writer.go | 311 +++ vendor/github.com/dgraph-io/badger/structs.go | 5 +- .../dgraph-io/badger/table/builder.go | 8 +- .../dgraph-io/badger/table/iterator.go | 4 +- .../dgraph-io/badger/table/table.go | 16 +- .../dgraph-io/badger/table/table_test.go | 729 ------- vendor/github.com/dgraph-io/badger/test.sh | 9 +- vendor/github.com/dgraph-io/badger/txn.go | 20 +- .../github.com/dgraph-io/badger/txn_test.go | 845 -------- vendor/github.com/dgraph-io/badger/util.go | 6 +- vendor/github.com/dgraph-io/badger/value.go | 132 +- .../github.com/dgraph-io/badger/value_test.go | 880 --------- vendor/github.com/dgraph-io/badger/y/error.go | 2 +- .../dgraph-io/badger/y/file_dsync.go | 2 +- .../github.com/dgraph-io/badger/y/iterator.go | 2 +- .../dgraph-io/badger/y/iterator_test.go | 234 --- .../dgraph-io/badger/y/mmap_unix.go | 2 +- .../dgraph-io/badger/y/watermark.go | 10 +- vendor/github.com/dgraph-io/badger/y/y.go | 48 +- 93 files changed, 1832 insertions(+), 8873 deletions(-) rename pkg/ipfs/{helpers => }/mocks/api.go (100%) rename pkg/ipfs/{helpers => }/mocks/converter.go (100%) rename pkg/ipfs/{helpers => }/mocks/publisher.go (100%) rename pkg/ipfs/{helpers => }/mocks/repository.go (100%) rename pkg/ipfs/{helpers => }/mocks/screener.go (100%) rename pkg/ipfs/{helpers => }/mocks/streamer.go (100%) rename pkg/ipfs/{helpers => mocks}/test_data.go (99%) create mode 100644 vendor/github.com/dgraph-io/badger/.gitignore create mode 100644 vendor/github.com/dgraph-io/badger/.golangci.yml create mode 100644 vendor/github.com/dgraph-io/badger/.travis.yml delete mode 100644 vendor/github.com/dgraph-io/badger/backup_test.go delete mode 100644 vendor/github.com/dgraph-io/badger/badger/.gitignore delete mode 100644 vendor/github.com/dgraph-io/badger/badger/cmd/backup.go delete mode 100644 vendor/github.com/dgraph-io/badger/badger/cmd/bank.go delete mode 100644 vendor/github.com/dgraph-io/badger/badger/cmd/fill.go delete mode 100644 vendor/github.com/dgraph-io/badger/badger/cmd/flatten.go delete mode 100644 vendor/github.com/dgraph-io/badger/badger/cmd/info.go delete mode 100644 vendor/github.com/dgraph-io/badger/badger/cmd/restore.go delete mode 100644 vendor/github.com/dgraph-io/badger/badger/cmd/root.go delete mode 100644 vendor/github.com/dgraph-io/badger/badger/main.go delete mode 100644 vendor/github.com/dgraph-io/badger/batch_test.go delete mode 100644 vendor/github.com/dgraph-io/badger/contrib/cover.sh delete mode 100644 vendor/github.com/dgraph-io/badger/db2_test.go delete mode 100644 vendor/github.com/dgraph-io/badger/db_test.go create mode 100644 vendor/github.com/dgraph-io/badger/histogram.go delete mode 100644 vendor/github.com/dgraph-io/badger/images/benchmarks-rocksdb.png delete mode 100644 vendor/github.com/dgraph-io/badger/images/diggy-shadow.png delete mode 100644 vendor/github.com/dgraph-io/badger/integration/testgc/.gitignore delete mode 100644 vendor/github.com/dgraph-io/badger/integration/testgc/main.go delete mode 100644 vendor/github.com/dgraph-io/badger/iterator_test.go delete mode 100644 vendor/github.com/dgraph-io/badger/logger_test.go delete mode 100644 vendor/github.com/dgraph-io/badger/managed_db_test.go delete mode 100644 vendor/github.com/dgraph-io/badger/manifest_test.go delete mode 100644 vendor/github.com/dgraph-io/badger/package.json mode change 100644 => 100755 vendor/github.com/dgraph-io/badger/pb/gen.sh create mode 100644 vendor/github.com/dgraph-io/badger/publisher.go delete mode 100644 vendor/github.com/dgraph-io/badger/skl/skl_test.go delete mode 100644 vendor/github.com/dgraph-io/badger/stream_test.go create mode 100644 vendor/github.com/dgraph-io/badger/stream_writer.go delete mode 100644 vendor/github.com/dgraph-io/badger/table/table_test.go mode change 100644 => 100755 vendor/github.com/dgraph-io/badger/test.sh delete mode 100644 vendor/github.com/dgraph-io/badger/txn_test.go delete mode 100644 vendor/github.com/dgraph-io/badger/value_test.go delete mode 100644 vendor/github.com/dgraph-io/badger/y/iterator_test.go diff --git a/Gopkg.lock b/Gopkg.lock index 8bbf00c8..036fef7d 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -129,19 +129,18 @@ version = "v1.7.1" [[projects]] - digest = "1:5f5090f05382959db941fa45acbeb7f4c5241aa8ac0f8f4393dec696e5953f53" + digest = "1:98436a9785f711ffee66f06265296dd36afd6e3dc532410c98a9051b5fd4c2f1" name = "github.com/dgraph-io/badger" packages = [ ".", "options", - "protos", + "pb", "skl", "table", "y", ] pruneopts = "UT" - revision = "99233d725dbdd26d156c61b2f42ae1671b794656" - version = "gx/v1.5.4" + revision = "0ce1d2e26af1ba8b8a72ea864145a3e1e3b382cd" [[projects]] branch = "master" @@ -2289,6 +2288,7 @@ "github.com/ethereum/go-ethereum/accounts/abi/bind", "github.com/ethereum/go-ethereum/common", "github.com/ethereum/go-ethereum/common/hexutil", + "github.com/ethereum/go-ethereum/core", "github.com/ethereum/go-ethereum/core/rawdb", "github.com/ethereum/go-ethereum/core/state", "github.com/ethereum/go-ethereum/core/types", diff --git a/Gopkg.toml b/Gopkg.toml index bbb400a0..db32d945 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -63,8 +63,8 @@ version = "0.4.20" [[override]] - name = "github.com/ipfs/go-ds-badger" - version = "0.0.3" + name = "github.com/dgraph-io/badger" + revision = "0ce1d2e26af1ba8b8a72ea864145a3e1e3b382cd" [prune] go-tests = true diff --git a/cmd/streamSubscribe.go b/cmd/streamSubscribe.go index d2ac134b..3b66c8de 100644 --- a/cmd/streamSubscribe.go +++ b/cmd/streamSubscribe.go @@ -17,7 +17,9 @@ package cmd import ( "bytes" + "fmt" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" @@ -48,6 +50,7 @@ func init() { } func streamSubscribe() { + log.SetLevel(log.DebugLevel) // Prep the subscription config/filters to be sent to the server subscriptionConfig() @@ -61,27 +64,26 @@ func streamSubscribe() { // Subscribe to the seed node service with the given config/filter parameters sub, err := str.Stream(payloadChan, subConfig) if err != nil { - println(err.Error()) log.Fatal(err) } - + log.Info("awaiting payloads") // Receive response payloads and print out the results for { select { case payload := <-payloadChan: - if payload.Err != nil { - log.Error(payload.Err) + if payload.ErrMsg != "" { + log.Error(payload.ErrMsg) + continue } for _, headerRlp := range payload.HeadersRlp { var header types.Header err = rlp.Decode(bytes.NewBuffer(headerRlp), &header) if err != nil { - println(err.Error()) log.Error(err) + continue } - println("Header") - println(header.Hash().Hex()) - println(header.Number.Int64()) + fmt.Printf("Header number %d, hash %s\n", header.Number.Int64(), header.Hash().Hex()) + fmt.Printf("header: %v\n", header) } for _, trxRlp := range payload.TransactionsRlp { var trx types.Transaction @@ -89,11 +91,11 @@ func streamSubscribe() { stream := rlp.NewStream(buff, 0) err := trx.DecodeRLP(stream) if err != nil { - println(err.Error()) log.Error(err) + continue } - println("Trx") - println(trx.Hash().Hex()) + fmt.Printf("Transaction with hash %s\n", trx.Hash().Hex()) + fmt.Printf("trx: %v", trx) } for _, rctRlp := range payload.ReceiptsRlp { var rct types.Receipt @@ -101,52 +103,59 @@ func streamSubscribe() { stream := rlp.NewStream(buff, 0) err = rct.DecodeRLP(stream) if err != nil { - println(err.Error()) log.Error(err) + continue } - println("Rct") + fmt.Printf("Receipt with block hash %s, trx hash %s\n", rct.BlockHash.Hex(), rct.TxHash.Hex()) + fmt.Printf("rct: %v", rct) for _, l := range rct.Logs { - println("log") - println(l.BlockHash.Hex()) - println(l.TxHash.Hex()) - println(l.Address.Hex()) + if len(l.Topics) < 1 { + log.Error(fmt.Sprintf("log only has %d topics", len(l.Topics))) + continue + } + fmt.Printf("Log for block hash %s, trx hash %s, address %s, and with topic0 %s\n", + l.BlockHash.Hex(), l.TxHash.Hex(), l.Address.Hex(), l.Topics[0].Hex()) + fmt.Printf("log: %v\n", l) } } + // This assumes leafs only for key, stateRlp := range payload.StateNodesRlp { var acct state.Account err = rlp.Decode(bytes.NewBuffer(stateRlp), &acct) if err != nil { - println(err.Error()) log.Error(err) + continue } - println("State") - print("key: ") - println(key.Hex()) - print("root: ") - println(acct.Root.Hex()) - print("balance: ") - println(acct.Balance.Int64()) + fmt.Printf("Account for key %s, and root %s, with balance %d\n", + key.Hex(), acct.Root.Hex(), acct.Balance.Int64()) + fmt.Printf("state account: %v\n", acct) } for stateKey, mappedRlp := range payload.StorageNodesRlp { - println("Storage") - print("state key: ") - println(stateKey.Hex()) + fmt.Printf("Storage for state key %s ", stateKey.Hex()) for storageKey, storageRlp := range mappedRlp { - println("Storage") - print("key: ") - println(storageKey.Hex()) + fmt.Printf("with storage key %s\n", storageKey.Hex()) var i []interface{} err := rlp.DecodeBytes(storageRlp, i) if err != nil { - println(err.Error()) log.Error(err) + continue + } + // if a leaf node + if len(i) == 2 { + keyBytes, ok := i[0].([]byte) + if !ok { + continue + } + valueBytes, ok := i[0].([]byte) + if !ok { + continue + } + fmt.Printf("Storage leaf key: %s, and value hash: %s\n", + common.BytesToHash(keyBytes).Hex(), common.BytesToHash(valueBytes).Hex()) } - print("bytes: ") - println(storageRlp) } } case err = <-sub.Err(): - println(err.Error()) log.Fatal(err) } } diff --git a/cmd/syncPublishScreenAndServe.go b/cmd/syncPublishScreenAndServe.go index b80f31f2..6512e8fa 100644 --- a/cmd/syncPublishScreenAndServe.go +++ b/cmd/syncPublishScreenAndServe.go @@ -47,6 +47,7 @@ func init() { } func syncPublishScreenAndServe() { + log.SetLevel(log.DebugLevel) blockChain, ethClient, rpcClient := getBlockChainAndClients() db := utils.LoadPostgres(databaseConfig, blockChain.Node()) diff --git a/pkg/ipfs/api.go b/pkg/ipfs/api.go index aecb0763..f5169e5c 100644 --- a/pkg/ipfs/api.go +++ b/pkg/ipfs/api.go @@ -64,8 +64,13 @@ func (api *PublicSeedNodeAPI) Stream(ctx context.Context, streamFilters config.S for { select { case packet := <-payloadChannel: - if err := notifier.Notify(rpcSub.ID, packet); err != nil { - log.Error("Failed to send state diff packet", "err", err) + if notifyErr := notifier.Notify(rpcSub.ID, packet); notifyErr != nil { + log.Error("Failed to send state diff packet", "err", notifyErr) + unSubErr := api.snp.Unsubscribe(rpcSub.ID) + if unSubErr != nil { + log.Error("Failed to unsubscribe from the state diff service", unSubErr) + } + return } case <-rpcSub.Err(): err := api.snp.Unsubscribe(rpcSub.ID) diff --git a/pkg/ipfs/converter_test.go b/pkg/ipfs/converter_test.go index 88b8c403..545dea67 100644 --- a/pkg/ipfs/converter_test.go +++ b/pkg/ipfs/converter_test.go @@ -20,19 +20,18 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/vulcanize/vulcanizedb/pkg/ipfs/helpers" - "github.com/vulcanize/vulcanizedb/pkg/ipfs/helpers/mocks" + "github.com/vulcanize/vulcanizedb/pkg/ipfs/mocks" ) var _ = Describe("Converter", func() { Describe("Convert", func() { It("Converts StatediffPayloads into IPLDPayloads", func() { mockConverter := mocks.PayloadConverter{} - mockConverter.ReturnIPLDPayload = &helpers.MockIPLDPayload - ipldPayload, err := mockConverter.Convert(helpers.MockStatediffPayload) + mockConverter.ReturnIPLDPayload = &mocks.MockIPLDPayload + ipldPayload, err := mockConverter.Convert(mocks.MockStatediffPayload) Expect(err).ToNot(HaveOccurred()) - Expect(ipldPayload).To(Equal(&helpers.MockIPLDPayload)) - Expect(mockConverter.PassedStatediffPayload).To(Equal(helpers.MockStatediffPayload)) + Expect(ipldPayload).To(Equal(&mocks.MockIPLDPayload)) + Expect(mockConverter.PassedStatediffPayload).To(Equal(mocks.MockStatediffPayload)) }) }) }) diff --git a/pkg/ipfs/fetcher.go b/pkg/ipfs/fetcher.go index 4319efca..1d50d1be 100644 --- a/pkg/ipfs/fetcher.go +++ b/pkg/ipfs/fetcher.go @@ -20,7 +20,6 @@ import ( "context" "github.com/ethereum/go-ethereum/common" - "github.com/ipfs/go-block-format" "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" @@ -50,6 +49,7 @@ func NewIPLDFetcher(ipfsPath string) (*EthIPLDFetcher, error) { // FetchCIDs is the exported method for fetching and returning all the cids passed in a CidWrapper func (f *EthIPLDFetcher) FetchCIDs(cids CidWrapper) (*IpldWrapper, error) { + log.Debug("fetching iplds") blocks := &IpldWrapper{ Headers: make([]blocks.Block, 0), Transactions: make([]blocks.Block, 0), @@ -85,6 +85,7 @@ func (f *EthIPLDFetcher) FetchCIDs(cids CidWrapper) (*IpldWrapper, error) { // fetchHeaders fetches headers // It uses the f.fetchBatch method func (f *EthIPLDFetcher) fetchHeaders(cids CidWrapper, blocks *IpldWrapper) error { + log.Debug("fetching header iplds") headerCids := make([]cid.Cid, 0, len(cids.Headers)) for _, c := range cids.Headers { dc, err := cid.Decode(c) @@ -103,6 +104,7 @@ func (f *EthIPLDFetcher) fetchHeaders(cids CidWrapper, blocks *IpldWrapper) erro // fetchTrxs fetches transactions // It uses the f.fetchBatch method func (f *EthIPLDFetcher) fetchTrxs(cids CidWrapper, blocks *IpldWrapper) error { + log.Debug("fetching transaction iplds") trxCids := make([]cid.Cid, 0, len(cids.Transactions)) for _, c := range cids.Transactions { dc, err := cid.Decode(c) @@ -121,6 +123,7 @@ func (f *EthIPLDFetcher) fetchTrxs(cids CidWrapper, blocks *IpldWrapper) error { // fetchRcts fetches receipts // It uses the f.fetchBatch method func (f *EthIPLDFetcher) fetchRcts(cids CidWrapper, blocks *IpldWrapper) error { + log.Debug("fetching receipt iplds") rctCids := make([]cid.Cid, 0, len(cids.Receipts)) for _, c := range cids.Receipts { dc, err := cid.Decode(c) @@ -140,6 +143,7 @@ func (f *EthIPLDFetcher) fetchRcts(cids CidWrapper, blocks *IpldWrapper) error { // It uses the single f.fetch method instead of the batch fetch, because it // needs to maintain the data's relation to state keys func (f *EthIPLDFetcher) fetchState(cids CidWrapper, blocks *IpldWrapper) error { + log.Debug("fetching state iplds") for _, stateNode := range cids.StateNodes { if stateNode.CID == "" || stateNode.Key == "" { continue @@ -161,6 +165,7 @@ func (f *EthIPLDFetcher) fetchState(cids CidWrapper, blocks *IpldWrapper) error // It uses the single f.fetch method instead of the batch fetch, because it // needs to maintain the data's relation to state and storage keys func (f *EthIPLDFetcher) fetchStorage(cids CidWrapper, blocks *IpldWrapper) error { + log.Debug("fetching storage iplds") for _, storageNode := range cids.StorageNodes { if storageNode.CID == "" || storageNode.Key == "" || storageNode.StateKey == "" { continue diff --git a/pkg/ipfs/helpers/mocks/api.go b/pkg/ipfs/mocks/api.go similarity index 100% rename from pkg/ipfs/helpers/mocks/api.go rename to pkg/ipfs/mocks/api.go diff --git a/pkg/ipfs/helpers/mocks/converter.go b/pkg/ipfs/mocks/converter.go similarity index 100% rename from pkg/ipfs/helpers/mocks/converter.go rename to pkg/ipfs/mocks/converter.go diff --git a/pkg/ipfs/helpers/mocks/publisher.go b/pkg/ipfs/mocks/publisher.go similarity index 100% rename from pkg/ipfs/helpers/mocks/publisher.go rename to pkg/ipfs/mocks/publisher.go diff --git a/pkg/ipfs/helpers/mocks/repository.go b/pkg/ipfs/mocks/repository.go similarity index 100% rename from pkg/ipfs/helpers/mocks/repository.go rename to pkg/ipfs/mocks/repository.go diff --git a/pkg/ipfs/helpers/mocks/screener.go b/pkg/ipfs/mocks/screener.go similarity index 100% rename from pkg/ipfs/helpers/mocks/screener.go rename to pkg/ipfs/mocks/screener.go diff --git a/pkg/ipfs/helpers/mocks/streamer.go b/pkg/ipfs/mocks/streamer.go similarity index 100% rename from pkg/ipfs/helpers/mocks/streamer.go rename to pkg/ipfs/mocks/streamer.go diff --git a/pkg/ipfs/helpers/test_data.go b/pkg/ipfs/mocks/test_data.go similarity index 99% rename from pkg/ipfs/helpers/test_data.go rename to pkg/ipfs/mocks/test_data.go index 33daefd0..564098e7 100644 --- a/pkg/ipfs/helpers/test_data.go +++ b/pkg/ipfs/mocks/test_data.go @@ -14,20 +14,20 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -package helpers +package mocks import ( "errors" "math/big" "math/rand" + "github.com/vulcanize/vulcanizedb/pkg/ipfs" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/statediff" - - "github.com/vulcanize/vulcanizedb/pkg/ipfs" ) // Test variables diff --git a/pkg/ipfs/publisher_test.go b/pkg/ipfs/publisher_test.go index 2e14a814..e3279d06 100644 --- a/pkg/ipfs/publisher_test.go +++ b/pkg/ipfs/publisher_test.go @@ -20,19 +20,18 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/vulcanize/vulcanizedb/pkg/ipfs/helpers" - "github.com/vulcanize/vulcanizedb/pkg/ipfs/helpers/mocks" + "github.com/vulcanize/vulcanizedb/pkg/ipfs/mocks" ) var _ = Describe("Publisher", func() { Describe("Publish", func() { It("Publishes IPLDPayload to IPFS", func() { mockPublisher := mocks.IPLDPublisher{} - mockPublisher.ReturnCIDPayload = &helpers.MockCIDPayload - cidPayload, err := mockPublisher.Publish(&helpers.MockIPLDPayload) + mockPublisher.ReturnCIDPayload = &mocks.MockCIDPayload + cidPayload, err := mockPublisher.Publish(&mocks.MockIPLDPayload) Expect(err).ToNot(HaveOccurred()) - Expect(cidPayload).To(Equal(&helpers.MockCIDPayload)) - Expect(mockPublisher.PassedIPLDPayload).To(Equal(&helpers.MockIPLDPayload)) + Expect(cidPayload).To(Equal(&mocks.MockCIDPayload)) + Expect(mockPublisher.PassedIPLDPayload).To(Equal(&mocks.MockIPLDPayload)) }) }) }) diff --git a/pkg/ipfs/repository_test.go b/pkg/ipfs/repository_test.go index 0f127224..dcffd0fe 100644 --- a/pkg/ipfs/repository_test.go +++ b/pkg/ipfs/repository_test.go @@ -20,17 +20,16 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/vulcanize/vulcanizedb/pkg/ipfs/helpers" - "github.com/vulcanize/vulcanizedb/pkg/ipfs/helpers/mocks" + "github.com/vulcanize/vulcanizedb/pkg/ipfs/mocks" ) var _ = Describe("Repository", func() { Describe("Index", func() { It("Indexes CIDs against their metadata", func() { mockRepo := mocks.CIDRepository{} - err := mockRepo.Index(&helpers.MockCIDPayload) + err := mockRepo.Index(&mocks.MockCIDPayload) Expect(err).ToNot(HaveOccurred()) - Expect(mockRepo.PassedCIDPayload).To(Equal(&helpers.MockCIDPayload)) + Expect(mockRepo.PassedCIDPayload).To(Equal(&mocks.MockCIDPayload)) }) }) }) diff --git a/pkg/ipfs/resolver.go b/pkg/ipfs/resolver.go index 1cf2baf2..90fe92fd 100644 --- a/pkg/ipfs/resolver.go +++ b/pkg/ipfs/resolver.go @@ -18,7 +18,6 @@ package ipfs import ( "github.com/ethereum/go-ethereum/common" - "github.com/ipfs/go-block-format" ) diff --git a/pkg/ipfs/retreiver.go b/pkg/ipfs/retreiver.go index cb113b0f..6bf8e583 100644 --- a/pkg/ipfs/retreiver.go +++ b/pkg/ipfs/retreiver.go @@ -19,6 +19,8 @@ package ipfs import ( "github.com/jmoiron/sqlx" "github.com/lib/pq" + log "github.com/sirupsen/logrus" + "github.com/vulcanize/vulcanizedb/pkg/config" "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" ) @@ -49,6 +51,7 @@ func (ecr *EthCIDRetriever) GetLastBlockNumber() (int64, error) { // RetrieveCIDs is used to retrieve all of the CIDs which conform to the passed StreamFilters func (ecr *EthCIDRetriever) RetrieveCIDs(streamFilters config.Subscription) ([]CidWrapper, error) { + log.Debug("retrieving cids") var endingBlock int64 var err error if streamFilters.EndingBlock <= 0 || streamFilters.EndingBlock <= streamFilters.StartingBlock { @@ -63,70 +66,68 @@ func (ecr *EthCIDRetriever) RetrieveCIDs(streamFilters config.Subscription) ([]C return nil, err } for i := streamFilters.StartingBlock; i <= endingBlock; i++ { - cw := &CidWrapper{ - BlockNumber: i, - Headers: make([]string, 0), - Transactions: make([]string, 0), - Receipts: make([]string, 0), - StateNodes: make([]StateNodeCID, 0), - StorageNodes: make([]StorageNodeCID, 0), - } + cw := CidWrapper{} if !streamFilters.HeaderFilter.Off { - err = ecr.retrieveHeaderCIDs(tx, streamFilters, cw, i) + cw.Headers, err = ecr.retrieveHeaderCIDs(tx, streamFilters, i) if err != nil { tx.Rollback() + log.Error("header cid retrieval error") return nil, err } } var trxIds []int64 if !streamFilters.TrxFilter.Off { - trxIds, err = ecr.retrieveTrxCIDs(tx, streamFilters, cw, i) + cw.Transactions, trxIds, err = ecr.retrieveTrxCIDs(tx, streamFilters, i) if err != nil { tx.Rollback() + log.Error("transaction cid retrieval error") return nil, err } } if !streamFilters.ReceiptFilter.Off { - err = ecr.retrieveRctCIDs(tx, streamFilters, cw, i, trxIds) + cw.Receipts, err = ecr.retrieveRctCIDs(tx, streamFilters, i, trxIds) if err != nil { tx.Rollback() + log.Error("receipt cid retrieval error") return nil, err } } if !streamFilters.StateFilter.Off { - err = ecr.retrieveStateCIDs(tx, streamFilters, cw, i) + cw.StateNodes, err = ecr.retrieveStateCIDs(tx, streamFilters, i) if err != nil { tx.Rollback() + log.Error("state cid retrieval error") return nil, err } } if !streamFilters.StorageFilter.Off { - err = ecr.retrieveStorageCIDs(tx, streamFilters, cw, i) + cw.StorageNodes, err = ecr.retrieveStorageCIDs(tx, streamFilters, i) if err != nil { tx.Rollback() + log.Error("storage cid retrieval error") return nil, err } } - cids = append(cids, *cw) + cids = append(cids, cw) } - return cids, err + return cids, tx.Commit() } -func (ecr *EthCIDRetriever) retrieveHeaderCIDs(tx *sqlx.Tx, streamFilters config.Subscription, cids *CidWrapper, blockNumber int64) error { - var pgStr string - if streamFilters.HeaderFilter.FinalOnly { - pgStr = `SELECT cid FROM header_cids - WHERE block_number = $1 - AND final IS TRUE` - } else { - pgStr = `SELECT cid FROM header_cids +func (ecr *EthCIDRetriever) retrieveHeaderCIDs(tx *sqlx.Tx, streamFilters config.Subscription, blockNumber int64) ([]string, error) { + log.Debug("retrieving header cids") + headers := make([]string, 0) + pgStr := `SELECT cid FROM header_cids WHERE block_number = $1` + if streamFilters.HeaderFilter.FinalOnly { + pgStr += ` AND final IS TRUE` } - return tx.Select(cids.Headers, pgStr, blockNumber) + err := tx.Select(&headers, pgStr, blockNumber) + return headers, err } -func (ecr *EthCIDRetriever) retrieveTrxCIDs(tx *sqlx.Tx, streamFilters config.Subscription, cids *CidWrapper, blockNumber int64) ([]int64, error) { +func (ecr *EthCIDRetriever) retrieveTrxCIDs(tx *sqlx.Tx, streamFilters config.Subscription, blockNumber int64) ([]string, []int64, error) { + log.Debug("retrieving transaction cids") args := make([]interface{}, 0, 3) type result struct { ID int64 `db:"id"` @@ -144,19 +145,21 @@ func (ecr *EthCIDRetriever) retrieveTrxCIDs(tx *sqlx.Tx, streamFilters config.Su pgStr += ` AND transaction_cids.src = ANY($3::VARCHAR(66)[])` args = append(args, pq.Array(streamFilters.TrxFilter.Src)) } - err := tx.Select(results, pgStr, args...) + err := tx.Select(&results, pgStr, args...) if err != nil { - return nil, err + return nil, nil, err } - ids := make([]int64, 0) + ids := make([]int64, 0, len(results)) + cids := make([]string, 0, len(results)) for _, res := range results { - cids.Transactions = append(cids.Transactions, res.Cid) + cids = append(cids, res.Cid) ids = append(ids, res.ID) } - return ids, nil + return cids, ids, nil } -func (ecr *EthCIDRetriever) retrieveRctCIDs(tx *sqlx.Tx, streamFilters config.Subscription, cids *CidWrapper, blockNumber int64, trxIds []int64) error { +func (ecr *EthCIDRetriever) retrieveRctCIDs(tx *sqlx.Tx, streamFilters config.Subscription, blockNumber int64, trxIds []int64) ([]string, error) { + log.Debug("retrieving receipt cids") args := make([]interface{}, 0, 2) pgStr := `SELECT receipt_cids.cid FROM receipt_cids, transaction_cids, header_cids WHERE receipt_cids.tx_id = transaction_cids.id @@ -173,10 +176,13 @@ func (ecr *EthCIDRetriever) retrieveRctCIDs(tx *sqlx.Tx, streamFilters config.Su } else { pgStr += `)` } - return tx.Select(cids.Receipts, pgStr, args...) + receiptCids := make([]string, 0) + err := tx.Select(&receiptCids, pgStr, args...) + return receiptCids, err } -func (ecr *EthCIDRetriever) retrieveStateCIDs(tx *sqlx.Tx, streamFilters config.Subscription, cids *CidWrapper, blockNumber int64) error { +func (ecr *EthCIDRetriever) retrieveStateCIDs(tx *sqlx.Tx, streamFilters config.Subscription, blockNumber int64) ([]StateNodeCID, error) { + log.Debug("retrieving state cids") args := make([]interface{}, 0, 2) pgStr := `SELECT state_cids.cid, state_cids.state_key FROM state_cids INNER JOIN header_cids ON (state_cids.header_id = header_cids.id) WHERE header_cids.block_number = $1` @@ -190,10 +196,16 @@ func (ecr *EthCIDRetriever) retrieveStateCIDs(tx *sqlx.Tx, streamFilters config. pgStr += ` AND state_cids.state_key = ANY($2::VARCHAR(66)[])` args = append(args, pq.Array(keys)) } - return tx.Select(cids.StateNodes, pgStr, args...) + if !streamFilters.StorageFilter.IntermediateNodes { + pgStr += ` AND state_cids.leaf = TRUE` + } + stateNodeCIDs := make([]StateNodeCID, 0) + err := tx.Select(&stateNodeCIDs, pgStr, args...) + return stateNodeCIDs, err } -func (ecr *EthCIDRetriever) retrieveStorageCIDs(tx *sqlx.Tx, streamFilters config.Subscription, cids *CidWrapper, blockNumber int64) error { +func (ecr *EthCIDRetriever) retrieveStorageCIDs(tx *sqlx.Tx, streamFilters config.Subscription, blockNumber int64) ([]StorageNodeCID, error) { + log.Debug("retrieving storage cids") args := make([]interface{}, 0, 3) pgStr := `SELECT storage_cids.cid, state_cids.state_key, storage_cids.storage_key FROM storage_cids, state_cids, header_cids WHERE storage_cids.state_id = state_cids.id @@ -213,7 +225,10 @@ func (ecr *EthCIDRetriever) retrieveStorageCIDs(tx *sqlx.Tx, streamFilters confi pgStr += ` AND storage_cids.storage_key = ANY($3::VARCHAR(66)[])` args = append(args, pq.Array(streamFilters.StorageFilter.StorageKeys)) } - return tx.Select(cids.StorageNodes, pgStr, args...) + if !streamFilters.StorageFilter.IntermediateNodes { + pgStr += ` AND storage_cids.leaf = TRUE` + } + storageNodeCIDs := make([]StorageNodeCID, 0) + err := tx.Select(&storageNodeCIDs, pgStr, args...) + return storageNodeCIDs, err } - -// ADD IF LEAF ONLY!! diff --git a/pkg/ipfs/screener.go b/pkg/ipfs/screener.go index 5468870c..8305a84d 100644 --- a/pkg/ipfs/screener.go +++ b/pkg/ipfs/screener.go @@ -19,12 +19,11 @@ package ipfs import ( "bytes" - "github.com/vulcanize/vulcanizedb/pkg/config" - - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" + + "github.com/vulcanize/vulcanizedb/pkg/config" ) // ResponseScreener is the inteface used to screen eth data and package appropriate data into a response payload diff --git a/pkg/ipfs/service.go b/pkg/ipfs/service.go index a89907b8..54e9da67 100644 --- a/pkg/ipfs/service.go +++ b/pkg/ipfs/service.go @@ -121,7 +121,8 @@ func (sap *Service) APIs() []rpc.API { } // SyncAndPublish is the backend processing loop which streams data from geth, converts it to iplds, publishes them to ipfs, and indexes their cids -// It then forwards the data to the Serve() loop which filters and sends relevant data to client subscriptions +// This continues on no matter if or how many subscribers there are, it then forwards the data to the ScreenAndServe() loop +// which filters and sends relevant data to client subscriptions, if there are any func (sap *Service) SyncAndPublish(wg *sync.WaitGroup, forwardPayloadChan chan<- IPLDPayload, forwardQuitchan chan<- bool) error { sub, err := sap.Streamer.Stream(sap.PayloadChan) if err != nil { @@ -173,7 +174,8 @@ func (sap *Service) SyncAndPublish(wg *sync.WaitGroup, forwardPayloadChan chan<- return nil } -// ScreenAndServe is the processing loop used to screen data streamed from the state diffing eth node and send the appropriate data to a requesting client subscription +// ScreenAndServe is the loop used to screen data streamed from the state diffing eth node +// and send the appropriate portions of it to a requesting client subscription, according to their subscription configuration func (sap *Service) ScreenAndServe(wg *sync.WaitGroup, receivePayloadChan <-chan IPLDPayload, receiveQuitchan <-chan bool) { wg.Add(1) go func() { @@ -206,32 +208,32 @@ func (sap *Service) processResponse(payload IPLDPayload) error { // Subscribe is used by the API to subscribe to the service loop func (sap *Service) Subscribe(id rpc.ID, sub chan<- ResponsePayload, quitChan chan<- bool, streamFilters *config.Subscription) { - log.Info("Subscribing to the statediff service") + log.Info("Subscribing to the seed node service") sap.Lock() sap.Subscriptions[id] = Subscription{ PayloadChan: sub, QuitChan: quitChan, StreamFilters: streamFilters, } - sap.Unlock() // If the subscription requests a backfill, use the Postgres index to lookup and retrieve historical data // Otherwise we only filter new data as it is streamed in from the state diffing geth node - if streamFilters.BackFill { + if streamFilters.BackFill || streamFilters.BackFillOnly { + log.Debug("back-filling data for id", id) // Retrieve cached CIDs relevant to this subscriber - cids, err := sap.Retriever.RetrieveCIDs(*streamFilters) + cidWrappers, err := sap.Retriever.RetrieveCIDs(*streamFilters) if err != nil { log.Error(err) sap.serve(id, ResponsePayload{ - Err: err, + ErrMsg: "CID retrieval error: " + err.Error(), }) return } - for _, cid := range cids { - blocksWrapper, err := sap.Fetcher.FetchCIDs(cid) + for _, cidWrapper := range cidWrappers { + blocksWrapper, err := sap.Fetcher.FetchCIDs(cidWrapper) if err != nil { log.Error(err) sap.serve(id, ResponsePayload{ - Err: err, + ErrMsg: "IPLD fetching error: " + err.Error(), }) return } @@ -239,18 +241,22 @@ func (sap *Service) Subscribe(id rpc.ID, sub chan<- ResponsePayload, quitChan ch if err != nil { log.Error(err) sap.serve(id, ResponsePayload{ - Err: err, + ErrMsg: "IPLD resolving error: " + err.Error(), }) return } sap.serve(id, *backFillIplds) } + if streamFilters.BackFillOnly { + delete(sap.Subscriptions, id) + } } + sap.Unlock() } // Unsubscribe is used to unsubscribe to the StateDiffingService loop func (sap *Service) Unsubscribe(id rpc.ID) error { - log.Info("Unsubscribing from the statediff service") + log.Info("Unsubscribing from the seed node service") sap.Lock() _, ok := sap.Subscriptions[id] if !ok { @@ -263,7 +269,7 @@ func (sap *Service) Unsubscribe(id rpc.ID) error { // Start is used to begin the service func (sap *Service) Start(*p2p.Server) error { - log.Info("Starting statediff service") + log.Info("Starting seed node service") wg := new(sync.WaitGroup) payloadChan := make(chan IPLDPayload, payloadChanBufferSize) quitChan := make(chan bool, 1) @@ -276,7 +282,7 @@ func (sap *Service) Start(*p2p.Server) error { // Stop is used to close down the service func (sap *Service) Stop() error { - log.Info("Stopping statediff service") + log.Info("Stopping seed node service") close(sap.QuitChan) return nil } diff --git a/pkg/ipfs/service_test.go b/pkg/ipfs/service_test.go index d5f95071..fbe0963f 100644 --- a/pkg/ipfs/service_test.go +++ b/pkg/ipfs/service_test.go @@ -26,8 +26,7 @@ import ( . "github.com/onsi/gomega" "github.com/vulcanize/vulcanizedb/pkg/ipfs" - "github.com/vulcanize/vulcanizedb/pkg/ipfs/helpers" - "github.com/vulcanize/vulcanizedb/pkg/ipfs/helpers/mocks" + "github.com/vulcanize/vulcanizedb/pkg/ipfs/mocks" ) var _ = Describe("Service", func() { @@ -41,18 +40,18 @@ var _ = Describe("Service", func() { ReturnErr: nil, } mockPublisher := &mocks.IPLDPublisher{ - ReturnCIDPayload: &helpers.MockCIDPayload, + ReturnCIDPayload: &mocks.MockCIDPayload, ReturnErr: nil, } mockStreamer := &mocks.StateDiffStreamer{ ReturnSub: &rpc.ClientSubscription{}, StreamPayloads: []statediff.Payload{ - helpers.MockStatediffPayload, + mocks.MockStatediffPayload, }, ReturnErr: nil, } mockConverter := &mocks.PayloadConverter{ - ReturnIPLDPayload: &helpers.MockIPLDPayload, + ReturnIPLDPayload: &mocks.MockIPLDPayload, ReturnErr: nil, } processor := &ipfs.Service{ @@ -68,9 +67,9 @@ var _ = Describe("Service", func() { time.Sleep(2 * time.Second) quitChan <- true wg.Wait() - Expect(mockConverter.PassedStatediffPayload).To(Equal(helpers.MockStatediffPayload)) - Expect(mockCidRepo.PassedCIDPayload).To(Equal(&helpers.MockCIDPayload)) - Expect(mockPublisher.PassedIPLDPayload).To(Equal(&helpers.MockIPLDPayload)) + Expect(mockConverter.PassedStatediffPayload).To(Equal(mocks.MockStatediffPayload)) + Expect(mockCidRepo.PassedCIDPayload).To(Equal(&mocks.MockCIDPayload)) + Expect(mockPublisher.PassedIPLDPayload).To(Equal(&mocks.MockIPLDPayload)) Expect(mockStreamer.PassedPayloadChan).To(Equal(payloadChan)) }) }) diff --git a/pkg/ipfs/streamer_test.go b/pkg/ipfs/streamer_test.go index acbc506e..70387142 100644 --- a/pkg/ipfs/streamer_test.go +++ b/pkg/ipfs/streamer_test.go @@ -22,8 +22,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/vulcanize/vulcanizedb/pkg/ipfs/helpers" - "github.com/vulcanize/vulcanizedb/pkg/ipfs/helpers/mocks" + "github.com/vulcanize/vulcanizedb/pkg/ipfs/mocks" ) var _ = Describe("Streamer", func() { @@ -32,7 +31,7 @@ var _ = Describe("Streamer", func() { mockStreamer := mocks.StateDiffStreamer{} mockStreamer.ReturnSub = &rpc.ClientSubscription{} mockStreamer.StreamPayloads = []statediff.Payload{ - helpers.MockStatediffPayload, + mocks.MockStatediffPayload, } payloadChan := make(chan statediff.Payload, 1) sub, err := mockStreamer.Stream(payloadChan) @@ -40,7 +39,7 @@ var _ = Describe("Streamer", func() { Expect(sub).To(Equal(&rpc.ClientSubscription{})) Expect(mockStreamer.PassedPayloadChan).To(Equal(payloadChan)) streamedPayload := <-payloadChan - Expect(streamedPayload).To(Equal(helpers.MockStatediffPayload)) + Expect(streamedPayload).To(Equal(mocks.MockStatediffPayload)) }) }) }) diff --git a/pkg/ipfs/types.go b/pkg/ipfs/types.go index bdf23113..0a33cf51 100644 --- a/pkg/ipfs/types.go +++ b/pkg/ipfs/types.go @@ -43,7 +43,7 @@ type ResponsePayload struct { ReceiptsRlp [][]byte `json:"receiptsRlp"` StateNodesRlp map[common.Hash][]byte `json:"stateNodesRlp"` StorageNodesRlp map[common.Hash]map[common.Hash][]byte `json:"storageNodesRlp"` - Err error `json:"error"` + ErrMsg string `json:"errMsg"` encoded []byte err error diff --git a/vendor/github.com/dgraph-io/badger/.gitignore b/vendor/github.com/dgraph-io/badger/.gitignore new file mode 100644 index 00000000..11b9bcb1 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/.gitignore @@ -0,0 +1 @@ +p/ diff --git a/vendor/github.com/dgraph-io/badger/.golangci.yml b/vendor/github.com/dgraph-io/badger/.golangci.yml new file mode 100644 index 00000000..22f245f2 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/.golangci.yml @@ -0,0 +1,20 @@ +run: + tests: false + +linters-settings: + lll: + line-length: 100 + +linters: + disable-all: true + enable: + - errcheck + - ineffassign + - gas + - gofmt + - golint + - gosimple + - govet + - lll + - varcheck + - unused diff --git a/vendor/github.com/dgraph-io/badger/.travis.yml b/vendor/github.com/dgraph-io/badger/.travis.yml new file mode 100644 index 00000000..43bf4cdc --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/.travis.yml @@ -0,0 +1,25 @@ +language: go + +go: + - "1.9" + - "1.10" + - "1.11" + +matrix: + include: + - os: osx +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= + +env: + global: + - secure: CRkV2+/jlO0gXzzS50XGxfMS117FNwiVjxNY/LeWq06RKD+dDCPxTJl3JCNe3l0cYEPAglV2uMMYukDiTqJ7e+HI4nh4N4mv6lwx39N8dAvJe1x5ITS2T4qk4kTjuQb1Q1vw/ZOxoQqmvNKj2uRmBdJ/HHmysbRJ1OzCWML3OXdUwJf0AYlJzTjpMfkOKr7sTtE4rwyyQtd4tKH1fGdurgI9ZuFd9qvYxK2qcJhsQ6CNqMXt+7FkVkN1rIPmofjjBTNryzUr4COFXuWH95aDAif19DeBW4lbNgo1+FpDsrgmqtuhl6NAuptI8q/imow2KXBYJ8JPXsxW8DVFj0IIp0RCd3GjaEnwBEbxAyiIHLfW7AudyTS/dJOvZffPqXnuJ8xj3OPIdNe4xY0hWl8Ju2HhKfLOAHq7VadHZWd3IHLil70EiL4/JLD1rNbMImUZisFaA8pyrcIvYYebjOnk4TscwKFLedClRSX1XsMjWWd0oykQtrdkHM2IxknnBpaLu7mFnfE07f6dkG0nlpyu4SCLey7hr5FdcEmljA0nIxTSYDg6035fQkBEAbe7hlESOekkVNT9IZPwG+lmt3vU4ofi6NqNbJecOuSB+h36IiZ9s4YQtxYNnLgW14zjuFGGyT5smc3IjBT7qngDjKIgyrSVoRkY/8udy9qbUgvBeW8= + +before_script: +- go get github.com/mattn/goveralls +script: +- bash contrib/cover.sh $HOME/build coverage.out || travis_terminate 1 +- goveralls -service=travis-ci -coverprofile=coverage.out || true +- goveralls -coverprofile=coverage.out -service=travis-ci diff --git a/vendor/github.com/dgraph-io/badger/README.md b/vendor/github.com/dgraph-io/badger/README.md index 4133210f..a25d8e1f 100644 --- a/vendor/github.com/dgraph-io/badger/README.md +++ b/vendor/github.com/dgraph-io/badger/README.md @@ -58,7 +58,7 @@ To start using Badger, install Go 1.8 or above and run `go get`: $ go get github.com/dgraph-io/badger/... ``` -This will retrieve the library and install the `badger_info` command line +This will retrieve the library and install the `badger` command line utility into your `$GOBIN` path. @@ -144,7 +144,7 @@ txn := db.NewTransaction(true) for k,v := range updates { if err := txn.Set([]byte(k),[]byte(v)); err == ErrTxnTooBig { _ = txn.Commit() - txn = db.NewTransaction(..) + txn = db.NewTransaction(true) _ = txn.Set([]byte(k),[]byte(v)) } } @@ -673,6 +673,9 @@ Below is a list of known projects that use Badger: * [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. If you are using Badger in a project please send a pull request to add it to the list. diff --git a/vendor/github.com/dgraph-io/badger/backup.go b/vendor/github.com/dgraph-io/badger/backup.go index 170d4c37..0bc3b328 100644 --- a/vendor/github.com/dgraph-io/badger/backup.go +++ b/vendor/github.com/dgraph-io/badger/backup.go @@ -22,10 +22,9 @@ import ( "context" "encoding/binary" "io" - "sync" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/pb" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" + "github.com/dgraph-io/badger/pb" + "github.com/dgraph-io/badger/y" ) // Backup is a wrapper function over Stream.Backup to generate full and incremental backups of the @@ -106,11 +105,8 @@ func (stream *Stream) Backup(w io.Writer, since uint64) (uint64, error) { if maxVersion < kv.Version { maxVersion = kv.Version } - if err := writeTo(kv, w); err != nil { - return err - } } - return nil + return writeTo(list, w) } if err := stream.Orchestrate(context.Background()); err != nil { @@ -119,11 +115,11 @@ func (stream *Stream) Backup(w io.Writer, since uint64) (uint64, error) { return maxVersion, nil } -func writeTo(entry *pb.KV, w io.Writer) error { - if err := binary.Write(w, binary.LittleEndian, uint64(entry.Size())); err != nil { +func writeTo(list *pb.KVList, w io.Writer) error { + if err := binary.Write(w, binary.LittleEndian, uint64(list.Size())); err != nil { return err } - buf, err := entry.Marshal() + buf, err := list.Marshal() if err != nil { return err } @@ -131,38 +127,73 @@ func writeTo(entry *pb.KV, w io.Writer) error { return err } +type loader struct { + db *DB + throttle *y.Throttle + entries []*Entry +} + +func (db *DB) newLoader(maxPendingWrites int) *loader { + return &loader{ + db: db, + throttle: y.NewThrottle(maxPendingWrites), + } +} + +func (l *loader) 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] + } + + l.entries = append(l.entries, &Entry{ + Key: y.KeyWithTs(kv.Key, kv.Version), + Value: kv.Value, + UserMeta: userMeta, + ExpiresAt: kv.ExpiresAt, + meta: meta, + }) + if len(l.entries) >= 1000 { + return l.send() + } + return nil +} + +func (l *loader) send() error { + if err := l.throttle.Do(); err != nil { + return err + } + l.db.batchSetAsync(l.entries, func(err error) { + l.throttle.Done(err) + }) + + l.entries = make([]*Entry, 0, 1000) + return nil +} + +func (l *loader) 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(). // // 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) error { +func (db *DB) Load(r io.Reader, maxPendingWrites int) error { br := bufio.NewReaderSize(r, 16<<10) unmarshalBuf := make([]byte, 1<<10) - var entries []*Entry - var wg sync.WaitGroup - errChan := make(chan error, 1) - - // func to check for pending error before sending off a batch for writing - batchSetAsyncIfNoErr := func(entries []*Entry) error { - select { - case err := <-errChan: - return err - default: - wg.Add(1) - return db.batchSetAsync(entries, func(err error) { - defer wg.Done() - if err != nil { - select { - case errChan <- err: - default: - } - } - }) - } - } + ldr := db.newLoader(maxPendingWrites) for { var sz uint64 err := binary.Read(br, binary.LittleEndian, &sz) @@ -176,51 +207,31 @@ func (db *DB) Load(r io.Reader) error { unmarshalBuf = make([]byte, sz) } - e := &pb.KV{} if _, err = io.ReadFull(br, unmarshalBuf[:sz]); err != nil { return err } - if err = e.Unmarshal(unmarshalBuf[:sz]); err != nil { + + list := &pb.KVList{} + if err := list.Unmarshal(unmarshalBuf[:sz]); err != nil { return err } - var userMeta byte - if len(e.UserMeta) > 0 { - userMeta = e.UserMeta[0] - } - entries = append(entries, &Entry{ - Key: y.KeyWithTs(e.Key, e.Version), - Value: e.Value, - UserMeta: userMeta, - ExpiresAt: e.ExpiresAt, - meta: e.Meta[0], - }) - // Update nextTxnTs, memtable stores this timestamp in badger head - // when flushed. - if e.Version >= db.orc.nextTxnTs { - db.orc.nextTxnTs = e.Version + 1 - } - if len(entries) == 1000 { - if err := batchSetAsyncIfNoErr(entries); err != nil { + for _, kv := range list.Kv { + if err := ldr.set(kv); err != nil { return err } - entries = make([]*Entry, 0, 1000) + + // Update nextTxnTs, memtable stores this + // timestamp in badger head when flushed. + if kv.Version >= db.orc.nextTxnTs { + db.orc.nextTxnTs = kv.Version + 1 + } } } - if len(entries) > 0 { - if err := batchSetAsyncIfNoErr(entries); err != nil { - return err - } - } - wg.Wait() - - select { - case err := <-errChan: + if err := ldr.finish(); err != nil { return err - default: - // Mark all versions done up until nextTxnTs. - db.orc.txnMark.Done(db.orc.nextTxnTs - 1) - return nil } + db.orc.txnMark.Done(db.orc.nextTxnTs - 1) + return nil } diff --git a/vendor/github.com/dgraph-io/badger/backup_test.go b/vendor/github.com/dgraph-io/badger/backup_test.go deleted file mode 100644 index 3e8a1150..00000000 --- a/vendor/github.com/dgraph-io/badger/backup_test.go +++ /dev/null @@ -1,519 +0,0 @@ -/* - * 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" - "io/ioutil" - "math/rand" - "os" - "path/filepath" - "reflect" - "strconv" - "testing" - "time" - - "github.com/stretchr/testify/require" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/pb" -) - -func TestBackupRestore1(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - db, err := Open(getTestOptions(dir)) - require.NoError(t, err) - - // Write some stuff - entries := []struct { - key []byte - val []byte - userMeta byte - version uint64 - }{ - {key: []byte("answer1"), val: []byte("42"), version: 1}, - {key: []byte("answer2"), val: []byte("43"), userMeta: 1, version: 2}, - } - - err = db.Update(func(txn *Txn) error { - e := entries[0] - err := txn.SetWithMeta(e.key, e.val, e.userMeta) - if err != nil { - return err - } - return nil - }) - require.NoError(t, err) - - err = db.Update(func(txn *Txn) error { - e := entries[1] - err := txn.SetWithMeta(e.key, e.val, e.userMeta) - if err != nil { - return err - } - return nil - }) - require.NoError(t, err) - - // Use different directory. - dir, err = ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - bak, err := ioutil.TempFile(dir, "badgerbak") - require.NoError(t, err) - ts, err := db.Backup(bak, 0) - t.Logf("New ts: %d\n", ts) - require.NoError(t, err) - require.NoError(t, bak.Close()) - require.NoError(t, db.Close()) - - db, err = Open(getTestOptions(dir)) - require.NoError(t, err) - defer db.Close() - bak, err = os.Open(bak.Name()) - require.NoError(t, err) - defer bak.Close() - - require.NoError(t, db.Load(bak)) - - err = db.View(func(txn *Txn) error { - opts := DefaultIteratorOptions - opts.AllVersions = true - it := txn.NewIterator(opts) - defer it.Close() - var count int - for it.Rewind(); it.Valid(); it.Next() { - item := it.Item() - val, err := item.ValueCopy(nil) - if err != nil { - return err - } - require.Equal(t, entries[count].key, item.Key()) - require.Equal(t, entries[count].val, val) - require.Equal(t, entries[count].version, item.Version()) - require.Equal(t, entries[count].userMeta, item.UserMeta()) - count++ - } - require.Equal(t, count, 2) - return nil - }) - require.NoError(t, err) -} - -func TestBackupRestore2(t *testing.T) { - tmpdir, err := ioutil.TempDir("", "badger-test") - if err != nil { - t.Fatal(err) - } - defer func() { - os.RemoveAll(tmpdir) - }() - - s1Path := filepath.Join(tmpdir, "test1") - s2Path := filepath.Join(tmpdir, "test2") - s3Path := filepath.Join(tmpdir, "test3") - - opts := DefaultOptions - opts.Dir = s1Path - opts.ValueDir = s1Path - db1, err := Open(opts) - if err != nil { - t.Fatal(err) - } - key1 := []byte("key1") - key2 := []byte("key2") - rawValue := []byte("NotLongValue") - N := byte(251) - err = db1.Update(func(tx *Txn) error { - if err := tx.Set(key1, rawValue); err != nil { - return err - } - return tx.Set(key2, rawValue) - }) - if err != nil { - t.Fatal(err) - } - for i := byte(1); i < N; i++ { - err = db1.Update(func(tx *Txn) error { - if err := tx.Set(append(key1, i), rawValue); err != nil { - return err - } - return tx.Set(append(key2, i), rawValue) - }) - if err != nil { - t.Fatal(err) - } - } - var backup bytes.Buffer - _, err = db1.Backup(&backup, 0) - if err != nil { - t.Fatal(err) - } - fmt.Println("backup1 length:", backup.Len()) - - opts = DefaultOptions - opts.Dir = s2Path - opts.ValueDir = s2Path - db2, err := Open(opts) - if err != nil { - t.Fatal(err) - } - err = db2.Load(&backup) - if err != nil { - t.Fatal(err) - } - - for i := byte(1); i < N; i++ { - err = db2.View(func(tx *Txn) error { - k := append(key1, i) - item, err := tx.Get(k) - if err != nil { - if err == ErrKeyNotFound { - return fmt.Errorf("Key %q has been not found, but was set\n", k) - } - return err - } - v, err := item.ValueCopy(nil) - if err != nil { - return err - } - if !reflect.DeepEqual(v, rawValue) { - return fmt.Errorf("Values not match, got %v, expected %v", v, rawValue) - } - return nil - }) - if err != nil { - t.Fatal(err) - } - } - - for i := byte(1); i < N; i++ { - err = db2.Update(func(tx *Txn) error { - if err := tx.Set(append(key1, i), rawValue); err != nil { - return err - } - return tx.Set(append(key2, i), rawValue) - }) - if err != nil { - t.Fatal(err) - } - } - - backup.Reset() - _, err = db2.Backup(&backup, 0) - if err != nil { - t.Fatal(err) - } - fmt.Println("backup2 length:", backup.Len()) - opts = DefaultOptions - opts.Dir = s3Path - opts.ValueDir = s3Path - db3, err := Open(opts) - if err != nil { - t.Fatal(err) - } - - err = db3.Load(&backup) - if err != nil { - t.Fatal(err) - } - - for i := byte(1); i < N; i++ { - err = db3.View(func(tx *Txn) error { - k := append(key1, i) - item, err := tx.Get(k) - if err != nil { - if err == ErrKeyNotFound { - return fmt.Errorf("Key %q has been not found, but was set\n", k) - } - return err - } - v, err := item.ValueCopy(nil) - if err != nil { - return err - } - if !reflect.DeepEqual(v, rawValue) { - return fmt.Errorf("Values not match, got %v, expected %v", v, rawValue) - } - return nil - }) - if err != nil { - t.Fatal(err) - } - } - -} - -var randSrc = rand.NewSource(time.Now().UnixNano()) - -func createEntries(n int) []*pb.KV { - entries := make([]*pb.KV, n) - for i := 0; i < n; i++ { - entries[i] = &pb.KV{ - Key: []byte(fmt.Sprint("key", i)), - Value: []byte{1}, - UserMeta: []byte{0}, - Meta: []byte{0}, - } - } - return entries -} - -func populateEntries(db *DB, entries []*pb.KV) error { - return db.Update(func(txn *Txn) error { - var err error - for i, e := range entries { - if err = txn.Set(e.Key, e.Value); err != nil { - return err - } - entries[i].Version = 1 - } - return nil - }) -} - -func TestBackup(t *testing.T) { - var bb bytes.Buffer - - tmpdir, err := ioutil.TempDir("", "badger-test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpdir) - - opts := DefaultOptions - opts.Dir = filepath.Join(tmpdir, "backup0") - opts.ValueDir = opts.Dir - db1, err := Open(opts) - if err != nil { - t.Fatal(err) - } - - N := 1000 - entries := createEntries(N) - require.NoError(t, populateEntries(db1, entries)) - - _, err = db1.Backup(&bb, 0) - require.NoError(t, err) - - err = db1.View(func(txn *Txn) error { - opts := DefaultIteratorOptions - it := txn.NewIterator(opts) - defer it.Close() - var count int - for it.Rewind(); it.Valid(); it.Next() { - item := it.Item() - idx, err := strconv.Atoi(string(item.Key())[3:]) - if err != nil { - return err - } - if idx > N || !bytes.Equal(entries[idx].Key, item.Key()) { - return fmt.Errorf("%s: %s", string(item.Key()), ErrKeyNotFound) - } - count++ - } - if N != count { - return fmt.Errorf("wrong number of items: %d expected, %d actual", N, count) - } - return nil - }) - require.NoError(t, err) -} - -func TestBackupRestore3(t *testing.T) { - var bb bytes.Buffer - - tmpdir, err := ioutil.TempDir("", "badger-test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpdir) - - opts := DefaultOptions - N := 1000 - entries := createEntries(N) - - // backup - { - opts.Dir = filepath.Join(tmpdir, "backup1") - opts.ValueDir = opts.Dir - db1, err := Open(opts) - if err != nil { - t.Fatal(err) - } - - require.NoError(t, populateEntries(db1, entries)) - - _, err = db1.Backup(&bb, 0) - require.NoError(t, err) - require.NoError(t, db1.Close()) - } - require.True(t, len(entries) == N) - require.True(t, bb.Len() > 0) - - // restore - opts.Dir = filepath.Join(tmpdir, "restore1") - opts.ValueDir = opts.Dir - db2, err := Open(opts) - if err != nil { - t.Fatal(err) - } - require.NoError(t, db2.Load(&bb)) - - // verify - err = db2.View(func(txn *Txn) error { - opts := DefaultIteratorOptions - it := txn.NewIterator(opts) - defer it.Close() - var count int - for it.Rewind(); it.Valid(); it.Next() { - item := it.Item() - idx, err := strconv.Atoi(string(item.Key())[3:]) - if err != nil { - return err - } - if idx > N || !bytes.Equal(entries[idx].Key, item.Key()) { - return fmt.Errorf("%s: %s", string(item.Key()), ErrKeyNotFound) - } - count++ - } - if N != count { - return fmt.Errorf("wrong number of items: %d expected, %d actual", N, count) - } - return nil - }) - require.NoError(t, err) -} - -func TestBackupLoadIncremental(t *testing.T) { - tmpdir, err := ioutil.TempDir("", "badger-test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpdir) - - opts := DefaultOptions - N := 100 - entries := createEntries(N) - updates := make(map[int]byte) - var bb bytes.Buffer - - // backup - { - opts.Dir = filepath.Join(tmpdir, "backup2") - opts.ValueDir = opts.Dir - db1, err := Open(opts) - if err != nil { - t.Fatal(err) - } - - require.NoError(t, populateEntries(db1, entries)) - since, err := db1.Backup(&bb, 0) - require.NoError(t, err) - - ints := rand.New(randSrc).Perm(N) - - // pick 10 items to mark as deleted. - err = db1.Update(func(txn *Txn) error { - for _, i := range ints[:10] { - if err := txn.Delete(entries[i].Key); err != nil { - return err - } - updates[i] = bitDelete - } - return nil - }) - require.NoError(t, err) - since, err = db1.Backup(&bb, since) - require.NoError(t, err) - - // pick 5 items to mark as expired. - err = db1.Update(func(txn *Txn) error { - for _, i := range (ints)[10:15] { - if err := txn.SetWithTTL( - entries[i].Key, entries[i].Value, -time.Hour); err != nil { - return err - } - updates[i] = bitDelete // expired - } - return nil - }) - require.NoError(t, err) - since, err = db1.Backup(&bb, since) - require.NoError(t, err) - - // pick 5 items to mark as discard. - err = db1.Update(func(txn *Txn) error { - for _, i := range ints[15:20] { - if err := txn.SetWithDiscard(entries[i].Key, entries[i].Value, 0); err != nil { - return err - } - updates[i] = bitDiscardEarlierVersions - } - return nil - }) - require.NoError(t, err) - _, err = db1.Backup(&bb, since) - require.NoError(t, err) - require.NoError(t, db1.Close()) - } - require.True(t, len(entries) == N) - require.True(t, bb.Len() > 0) - - // restore - opts.Dir = filepath.Join(tmpdir, "restore2") - opts.ValueDir = opts.Dir - db2, err := Open(opts) - if err != nil { - t.Fatal(err) - } - require.NoError(t, db2.Load(&bb)) - - // verify - actual := make(map[int]byte) - err = db2.View(func(txn *Txn) error { - opts := DefaultIteratorOptions - opts.AllVersions = true - it := txn.NewIterator(opts) - defer it.Close() - var count int - for it.Rewind(); it.Valid(); it.Next() { - item := it.Item() - idx, err := strconv.Atoi(string(item.Key())[3:]) - if err != nil { - return err - } - if item.IsDeletedOrExpired() { - _, ok := updates[idx] - if !ok { - return fmt.Errorf("%s: not expected to be updated but it is", - string(item.Key())) - } - actual[idx] = item.meta - count++ - continue - } - } - if len(updates) != count { - return fmt.Errorf("mismatched updated items: %d expected, %d actual", - len(updates), count) - } - return nil - }) - require.NoError(t, err, "%v %v", updates, actual) -} diff --git a/vendor/github.com/dgraph-io/badger/badger/.gitignore b/vendor/github.com/dgraph-io/badger/badger/.gitignore deleted file mode 100644 index a8e6bd9e..00000000 --- a/vendor/github.com/dgraph-io/badger/badger/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/badger diff --git a/vendor/github.com/dgraph-io/badger/badger/cmd/backup.go b/vendor/github.com/dgraph-io/badger/badger/cmd/backup.go deleted file mode 100644 index 1b47bd4f..00000000 --- a/vendor/github.com/dgraph-io/badger/badger/cmd/backup.go +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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 cmd - -import ( - "os" - - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger" - "gx/ipfs/QmXj63M2w2Pq7mnBpcrs7Va8prmfhvfMUNqVhJ9TgjiMbT/cobra" -) - -var backupFile string -var truncate bool - -// backupCmd represents the backup command -var backupCmd = &cobra.Command{ - Use: "backup", - Short: "Backup Badger database.", - Long: `Backup Badger database to a file in a version-agnostic manner. - -Iterates over each key-value pair, encodes it along with its metadata and -version in protocol buffers and writes them to a file. This file can later be -used by the restore command to create an identical copy of the -database.`, - RunE: doBackup, -} - -func init() { - RootCmd.AddCommand(backupCmd) - backupCmd.Flags().StringVarP(&backupFile, "backup-file", "f", - "badger.bak", "File to backup to") - backupCmd.Flags().BoolVarP(&truncate, "truncate", "t", - false, "Allow value log truncation if required.") -} - -func doBackup(cmd *cobra.Command, args []string) error { - // Open DB - opts := badger.DefaultOptions - opts.Dir = sstDir - opts.ValueDir = vlogDir - opts.Truncate = truncate - db, err := badger.Open(opts) - if err != nil { - return err - } - defer db.Close() - - // Create File - f, err := os.Create(backupFile) - if err != nil { - return err - } - defer f.Close() - - // Run Backup - _, err = db.Backup(f, 0) - return err -} diff --git a/vendor/github.com/dgraph-io/badger/badger/cmd/bank.go b/vendor/github.com/dgraph-io/badger/badger/cmd/bank.go deleted file mode 100644 index 90039eac..00000000 --- a/vendor/github.com/dgraph-io/badger/badger/cmd/bank.go +++ /dev/null @@ -1,451 +0,0 @@ -/* - * 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 cmd - -import ( - "bytes" - "errors" - "fmt" - "log" - "math" - "math/rand" - "strconv" - "sync" - "sync/atomic" - "time" - - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/options" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" - "gx/ipfs/QmXj63M2w2Pq7mnBpcrs7Va8prmfhvfMUNqVhJ9TgjiMbT/cobra" -) - -var testCmd = &cobra.Command{ - Use: "bank", - Short: "Run bank test on Badger.", - Long: ` -This command runs bank test on Badger, inspired by Jepsen. It creates many -accounts and moves money among them transactionally. It also reads the sum total -of all the accounts, to ensure that the total never changes. -`, -} - -var bankTest = &cobra.Command{ - Use: "test", - Short: "Execute bank test on Badger.", - RunE: runTest, -} - -var bankDisect = &cobra.Command{ - Use: "disect", - Short: "Disect the bank output.", - Long: ` -Disect the bank output BadgerDB to find the first transaction which causes -failure of the total invariant. -`, - RunE: runDisect, -} - -var numGoroutines, numAccounts, numPrevious int -var duration string -var stopAll int32 -var mmap bool - -const keyPrefix = "account:" - -const initialBal uint64 = 100 - -func init() { - RootCmd.AddCommand(testCmd) - testCmd.AddCommand(bankTest) - testCmd.AddCommand(bankDisect) - - testCmd.Flags().IntVarP( - &numAccounts, "accounts", "a", 10000, "Number of accounts in the bank.") - bankTest.Flags().IntVarP( - &numGoroutines, "conc", "c", 16, "Number of concurrent transactions to run.") - bankTest.Flags().StringVarP(&duration, "duration", "d", "3m", "How long to run the test.") - bankTest.Flags().BoolVarP(&mmap, "mmap", "m", false, "If true, mmap LSM tree. Default is RAM.") - bankDisect.Flags().IntVarP(&numPrevious, "previous", "p", 12, - "Starting from the violation txn, how many previous versions to retrieve.") -} - -func key(account int) []byte { - return []byte(fmt.Sprintf("%s%s", keyPrefix, strconv.Itoa(account))) -} - -func toAccount(key []byte) int { - i, err := strconv.Atoi(string(key[len(keyPrefix):])) - y.Check(err) - return i -} - -func toUint64(val []byte) uint64 { - u, err := strconv.ParseUint(string(val), 10, 64) - y.Check(err) - return uint64(u) -} - -func toSlice(bal uint64) []byte { - return []byte(strconv.FormatUint(bal, 10)) -} - -func getBalance(txn *badger.Txn, account int) (uint64, error) { - item, err := txn.Get(key(account)) - if err != nil { - return 0, err - } - - var bal uint64 - err = item.Value(func(v []byte) error { - bal = toUint64(v) - return nil - }) - return bal, err -} - -func putBalance(txn *badger.Txn, account int, bal uint64) error { - return txn.Set(key(account), toSlice(bal)) -} - -func min(a, b uint64) uint64 { - if a < b { - return a - } - return b -} - -var errAbandoned = errors.New("Transaction abandonded due to insufficient balance") - -func moveMoney(db *badger.DB, from, to int) error { - return db.Update(func(txn *badger.Txn) error { - balf, err := getBalance(txn, from) - if err != nil { - return err - } - balt, err := getBalance(txn, to) - if err != nil { - return err - } - - floor := min(balf, balt) - if floor < 5 { - return errAbandoned - } - // Move the money. - balf -= 5 - balt += 5 - - if err = putBalance(txn, from, balf); err != nil { - return err - } - return putBalance(txn, to, balt) - }) -} - -type account struct { - Id int - Bal uint64 -} - -func diff(a, b []account) string { - var buf bytes.Buffer - y.AssertTruef(len(a) == len(b), "len(a)=%d. len(b)=%d\n", len(a), len(b)) - for i := range a { - ai := a[i] - bi := b[i] - if ai.Id != bi.Id || ai.Bal != bi.Bal { - buf.WriteString(fmt.Sprintf("Index: %d. Account [%+v] -> [%+v]\n", i, ai, bi)) - } - } - return buf.String() -} - -var errFailure = errors.New("Found an balance mismatch. Test failed.") - -// seekTotal retrives the total of all accounts by seeking for each account key. -func seekTotal(txn *badger.Txn) ([]account, error) { - expected := uint64(numAccounts) * uint64(initialBal) - var accounts []account - - var total uint64 - for i := 0; i < numAccounts; i++ { - item, err := txn.Get(key(i)) - if err != nil { - log.Printf("Error for account: %d. err=%v. key=%q\n", i, err, key(i)) - return accounts, err - } - val, err := item.ValueCopy(nil) - if err != nil { - return accounts, err - } - acc := account{ - Id: i, - Bal: toUint64(val), - } - accounts = append(accounts, acc) - total += acc.Bal - } - if total != expected { - log.Printf("Balance did NOT match up. Expected: %d. Received: %d", - expected, total) - atomic.AddInt32(&stopAll, 1) - return accounts, errFailure - } - return accounts, nil -} - -// Range is [lowTs, highTs). -func findFirstInvalidTxn(db *badger.DB, lowTs, highTs uint64) uint64 { - checkAt := func(ts uint64) error { - txn := db.NewTransactionAt(ts, false) - _, err := seekTotal(txn) - txn.Discard() - return err - } - - if highTs-lowTs < 1 { - log.Printf("Checking at lowTs: %d\n", lowTs) - err := checkAt(lowTs) - if err == errFailure { - fmt.Printf("Violation at ts: %d\n", lowTs) - return lowTs - } else if err != nil { - log.Printf("Error at lowTs: %d. Err=%v\n", lowTs, err) - return 0 - } - fmt.Printf("No violation found at ts: %d\n", lowTs) - return 0 - } - - midTs := (lowTs + highTs) / 2 - log.Println() - log.Printf("Checking. low=%d. high=%d. mid=%d\n", lowTs, highTs, midTs) - err := checkAt(midTs) - if err == badger.ErrKeyNotFound || err == nil { - // If no failure, move to higher ts. - return findFirstInvalidTxn(db, midTs+1, highTs) - } - // Found an error. - return findFirstInvalidTxn(db, lowTs, midTs) -} - -func compareTwo(db *badger.DB, before, after uint64) { - fmt.Printf("Comparing @ts=%d with @ts=%d\n", before, after) - txn := db.NewTransactionAt(before, false) - prev, err := seekTotal(txn) - if err == errFailure { - // pass - } else { - y.Check(err) - } - txn.Discard() - - txn = db.NewTransactionAt(after, false) - now, err := seekTotal(txn) - if err == errFailure { - // pass - } else { - y.Check(err) - } - txn.Discard() - - fmt.Println(diff(prev, now)) -} - -func runDisect(cmd *cobra.Command, args []string) error { - opts := badger.DefaultOptions - opts.Dir = sstDir - opts.ValueDir = vlogDir - opts.ReadOnly = true - - // The total did not match up. So, let's disect the DB to find the - // transction which caused the total mismatch. - db, err := badger.OpenManaged(opts) - if err != nil { - return err - } - fmt.Println("opened db") - - var min, max uint64 = math.MaxUint64, 0 - { - txn := db.NewTransactionAt(uint64(math.MaxUint32), false) - iopt := badger.DefaultIteratorOptions - iopt.AllVersions = true - itr := txn.NewIterator(iopt) - for itr.Rewind(); itr.Valid(); itr.Next() { - item := itr.Item() - if min > item.Version() { - min = item.Version() - } - if max < item.Version() { - max = item.Version() - } - } - itr.Close() - txn.Discard() - } - - log.Printf("min=%d. max=%d\n", min, max) - ts := findFirstInvalidTxn(db, min, max) - fmt.Println() - if ts == 0 { - fmt.Println("Nothing found. Exiting.") - return nil - } - - for i := 0; i < numPrevious; i++ { - compareTwo(db, ts-1-uint64(i), ts-uint64(i)) - } - return nil -} - -func runTest(cmd *cobra.Command, args []string) error { - rand.Seed(time.Now().UnixNano()) - - // Open DB - opts := badger.DefaultOptions - opts.Dir = sstDir - opts.ValueDir = vlogDir - opts.MaxTableSize = 4 << 20 // Force more compactions. - opts.NumLevelZeroTables = 2 - opts.NumMemtables = 2 - // Do not GC any versions, because we need them for the disect. - opts.NumVersionsToKeep = int(math.MaxInt32) - opts.ValueThreshold = 1 // Make all values go to value log. - if mmap { - opts.TableLoadingMode = options.MemoryMap - } - log.Printf("Opening DB with options: %+v\n", opts) - - db, err := badger.Open(opts) - if err != nil { - return err - } - defer db.Close() - - wb := db.NewWriteBatch() - for i := 0; i < numAccounts; i++ { - y.Check(wb.Set(key(i), toSlice(initialBal), 0)) - } - log.Println("Waiting for writes to be done...") - y.Check(wb.Flush()) - - log.Println("Bank initialization OK. Commencing test.") - log.Printf("Running with %d accounts, and %d goroutines.\n", numAccounts, numGoroutines) - log.Printf("Using keyPrefix: %s\n", keyPrefix) - - dur, err := time.ParseDuration(duration) - y.Check(err) - - // startTs := time.Now() - endTs := time.Now().Add(dur) - var total, errors, reads uint64 - - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - ticker := time.NewTicker(time.Second) - defer ticker.Stop() - - for range ticker.C { - if atomic.LoadInt32(&stopAll) > 0 { - // Do not proceed. - return - } - // log.Printf("[%6s] Total: %d. Errors: %d Reads: %d.\n", - // time.Since(startTs).Round(time.Second).String(), - // atomic.LoadUint64(&total), - // atomic.LoadUint64(&errors), - // atomic.LoadUint64(&reads)) - if time.Now().After(endTs) { - return - } - } - }() - - // RW goroutines. - for i := 0; i < numGoroutines; i++ { - wg.Add(1) - go func() { - defer wg.Done() - - ticker := time.NewTicker(10 * time.Microsecond) - defer ticker.Stop() - - for range ticker.C { - if atomic.LoadInt32(&stopAll) > 0 { - // Do not proceed. - return - } - if time.Now().After(endTs) { - return - } - from := rand.Intn(numAccounts) - to := rand.Intn(numAccounts) - if from == to { - continue - } - err := moveMoney(db, from, to) - atomic.AddUint64(&total, 1) - if err == nil { - log.Printf("Moved $5. %d -> %d\n", from, to) - } else { - atomic.AddUint64(&errors, 1) - } - } - }() - } - - // RO goroutine. - wg.Add(1) - go func() { - defer wg.Done() - - ticker := time.NewTicker(10 * time.Microsecond) - defer ticker.Stop() - - for range ticker.C { - if atomic.LoadInt32(&stopAll) > 0 { - // Do not proceed. - return - } - if time.Now().After(endTs) { - return - } - - y.Check(db.View(func(txn *badger.Txn) error { - _, err := seekTotal(txn) - if err != nil { - log.Printf("Error while calculating total: %v", err) - } else { - atomic.AddUint64(&reads, 1) - } - return nil - })) - } - }() - wg.Wait() - - if atomic.LoadInt32(&stopAll) == 0 { - log.Println("Test OK") - return nil - } - log.Println("Test FAILED") - return fmt.Errorf("Test FAILED") -} diff --git a/vendor/github.com/dgraph-io/badger/badger/cmd/fill.go b/vendor/github.com/dgraph-io/badger/badger/cmd/fill.go deleted file mode 100644 index 6b103bb2..00000000 --- a/vendor/github.com/dgraph-io/badger/badger/cmd/fill.go +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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 cmd - -import ( - "crypto/rand" - "time" - - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/options" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" - "gx/ipfs/QmXj63M2w2Pq7mnBpcrs7Va8prmfhvfMUNqVhJ9TgjiMbT/cobra" -) - -var fillCmd = &cobra.Command{ - Use: "fill", - Short: "Fill Badger with random data.", - Long: ` -This command would fill Badger with random data. Useful for testing and performance analysis. -`, - RunE: fill, -} - -var keySz, valSz int -var numKeys float64 -var force bool - -const mil float64 = 1e6 - -func init() { - RootCmd.AddCommand(fillCmd) - fillCmd.Flags().IntVarP(&keySz, "key-size", "k", 32, "Size of key") - fillCmd.Flags().IntVarP(&valSz, "val-size", "v", 128, "Size of value") - fillCmd.Flags().Float64VarP(&numKeys, "keys-mil", "m", 10.0, - "Number of keys to add in millions") - fillCmd.Flags().BoolVarP(&force, "force-compact", "f", true, "Force compact level 0 on close.") -} - -func fill(cmd *cobra.Command, args []string) error { - opts := badger.DefaultOptions - opts.Dir = sstDir - opts.ValueDir = vlogDir - opts.Truncate = truncate - opts.SyncWrites = false - opts.CompactL0OnClose = force - opts.TableLoadingMode = options.FileIO - opts.ValueLogLoadingMode = options.FileIO - - db, err := badger.Open(opts) - if err != nil { - return err - } - defer func() { - start := time.Now() - err := db.Close() - opts.Infof("DB.Close. Error: %v. Time taken: %s", err, time.Since(start)) - }() - - start := time.Now() - batch := db.NewWriteBatch() - num := int64(numKeys * mil) - for i := int64(1); i <= num; i++ { - k := make([]byte, keySz) - v := make([]byte, valSz) - y.Check2(rand.Read(k)) - y.Check2(rand.Read(v)) - if err := batch.Set(k, v, 0); err != nil { - return err - } - if i%1e5 == 0 { - opts.Infof("Written keys: %d\n", i) - } - } - if err := batch.Flush(); err != nil { - return err - } - opts.Infof("%d keys written. Time taken: %s\n", num, time.Since(start)) - return nil -} diff --git a/vendor/github.com/dgraph-io/badger/badger/cmd/flatten.go b/vendor/github.com/dgraph-io/badger/badger/cmd/flatten.go deleted file mode 100644 index 4b67c982..00000000 --- a/vendor/github.com/dgraph-io/badger/badger/cmd/flatten.go +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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 cmd - -import ( - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger" - "gx/ipfs/QmXj63M2w2Pq7mnBpcrs7Va8prmfhvfMUNqVhJ9TgjiMbT/cobra" -) - -var flattenCmd = &cobra.Command{ - Use: "flatten", - Short: "Flatten the LSM tree.", - Long: ` -This command would compact all the LSM tables into one level. -`, - RunE: flatten, -} - -var numWorkers int - -func init() { - RootCmd.AddCommand(flattenCmd) - flattenCmd.Flags().IntVarP(&numWorkers, "num-workers", "w", 1, - "Number of concurrent compactors to run. More compactors would use more"+ - " server resources to potentially achieve faster compactions.") -} - -func flatten(cmd *cobra.Command, args []string) error { - opts := badger.DefaultOptions - opts.Dir = sstDir - opts.ValueDir = vlogDir - opts.Truncate = truncate - opts.NumCompactors = 0 - - db, err := badger.Open(opts) - if err != nil { - return err - } - defer db.Close() - - return db.Flatten(numWorkers) -} diff --git a/vendor/github.com/dgraph-io/badger/badger/cmd/info.go b/vendor/github.com/dgraph-io/badger/badger/cmd/info.go deleted file mode 100644 index ca8c28b5..00000000 --- a/vendor/github.com/dgraph-io/badger/badger/cmd/info.go +++ /dev/null @@ -1,294 +0,0 @@ -/* - * 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 cmd - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "sort" - "strings" - "time" - - humanize "gx/ipfs/QmQMxG9D52TirZd9eLA37nxiNspnMRkKbyPWrVAa1gvtSy/go-humanize" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/options" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/table" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" - "gx/ipfs/QmXj63M2w2Pq7mnBpcrs7Va8prmfhvfMUNqVhJ9TgjiMbT/cobra" -) - -var infoCmd = &cobra.Command{ - Use: "info", - Short: "Health info about Badger database.", - Long: ` -This command prints information about the badger key-value store. It reads MANIFEST and prints its -info. It also prints info about missing/extra files, and general information about the value log -files (which are not referenced by the manifest). Use this tool to report any issues about Badger -to the Dgraph team. -`, - Run: func(cmd *cobra.Command, args []string) { - err := printInfo(sstDir, vlogDir) - if err != nil { - fmt.Println("Error:", err.Error()) - os.Exit(1) - } - if !showTables { - return - } - err = tableInfo(sstDir, vlogDir) - if err != nil { - fmt.Println("Error:", err.Error()) - os.Exit(1) - } - }, -} - -var showTables bool - -func init() { - RootCmd.AddCommand(infoCmd) - infoCmd.Flags().BoolVarP(&showTables, "show-tables", "s", false, - "If set to true, show tables as well.") -} - -func hbytes(sz int64) string { - return humanize.Bytes(uint64(sz)) -} - -func dur(src, dst time.Time) string { - return humanize.RelTime(dst, src, "earlier", "later") -} - -func tableInfo(dir, valueDir string) error { - // Open DB - opts := badger.DefaultOptions - opts.TableLoadingMode = options.MemoryMap - opts.Dir = sstDir - opts.ValueDir = vlogDir - opts.ReadOnly = true - - db, err := badger.Open(opts) - if err != nil { - return err - } - defer db.Close() - - tables := db.Tables() - for _, t := range tables { - lk, lv := y.ParseKey(t.Left), y.ParseTs(t.Left) - rk, rv := y.ParseKey(t.Right), y.ParseTs(t.Right) - fmt.Printf("SSTable [L%d, %03d] [%20X, v%-10d -> %20X, v%-10d]\n", - t.Level, t.ID, lk, lv, rk, rv) - } - return nil -} - -func printInfo(dir, valueDir string) error { - if dir == "" { - return fmt.Errorf("--dir not supplied") - } - if valueDir == "" { - valueDir = dir - } - fp, err := os.Open(filepath.Join(dir, badger.ManifestFilename)) - if err != nil { - return err - } - defer func() { - if fp != nil { - fp.Close() - } - }() - manifest, truncOffset, err := badger.ReplayManifestFile(fp) - if err != nil { - return err - } - fp.Close() - fp = nil - - fileinfos, err := ioutil.ReadDir(dir) - if err != nil { - return err - } - fileinfoByName := make(map[string]os.FileInfo) - fileinfoMarked := make(map[string]bool) - for _, info := range fileinfos { - fileinfoByName[info.Name()] = info - fileinfoMarked[info.Name()] = false - } - - fmt.Println() - var baseTime time.Time - // fmt.Print("\n[Manifest]\n") - manifestTruncated := false - manifestInfo, ok := fileinfoByName[badger.ManifestFilename] - if ok { - fileinfoMarked[badger.ManifestFilename] = true - truncatedString := "" - if truncOffset != manifestInfo.Size() { - truncatedString = fmt.Sprintf(" [TRUNCATED to %d]", truncOffset) - manifestTruncated = true - } - - baseTime = manifestInfo.ModTime() - fmt.Printf("[%25s] %-12s %6s MA%s\n", manifestInfo.ModTime().Format(time.RFC3339), - manifestInfo.Name(), hbytes(manifestInfo.Size()), truncatedString) - } else { - fmt.Printf("%s [MISSING]\n", manifestInfo.Name()) - } - - numMissing := 0 - numEmpty := 0 - - levelSizes := make([]int64, len(manifest.Levels)) - for level, lm := range manifest.Levels { - // fmt.Printf("\n[Level %d]\n", level) - // We create a sorted list of table ID's so that output is in consistent order. - tableIDs := make([]uint64, 0, len(lm.Tables)) - for id := range lm.Tables { - tableIDs = append(tableIDs, id) - } - sort.Slice(tableIDs, func(i, j int) bool { - return tableIDs[i] < tableIDs[j] - }) - for _, tableID := range tableIDs { - tableFile := table.IDToFilename(tableID) - tm, ok1 := manifest.Tables[tableID] - file, ok2 := fileinfoByName[tableFile] - if ok1 && ok2 { - fileinfoMarked[tableFile] = true - emptyString := "" - fileSize := file.Size() - if fileSize == 0 { - emptyString = " [EMPTY]" - numEmpty++ - } - levelSizes[level] += fileSize - // (Put level on every line to make easier to process with sed/perl.) - fmt.Printf("[%25s] %-12s %6s L%d %x%s\n", dur(baseTime, file.ModTime()), - tableFile, hbytes(fileSize), level, tm.Checksum, emptyString) - } else { - fmt.Printf("%s [MISSING]\n", tableFile) - numMissing++ - } - } - } - - valueDirFileinfos := fileinfos - if valueDir != dir { - valueDirFileinfos, err = ioutil.ReadDir(valueDir) - if err != nil { - return err - } - } - - // If valueDir is different from dir, holds extra files in the value dir. - valueDirExtras := []os.FileInfo{} - - valueLogSize := int64(0) - // fmt.Print("\n[Value Log]\n") - for _, file := range valueDirFileinfos { - if !strings.HasSuffix(file.Name(), ".vlog") { - if valueDir != dir { - valueDirExtras = append(valueDirExtras, file) - } - continue - } - - fileSize := file.Size() - emptyString := "" - if fileSize == 0 { - emptyString = " [EMPTY]" - numEmpty++ - } - valueLogSize += fileSize - fmt.Printf("[%25s] %-12s %6s VL%s\n", dur(baseTime, file.ModTime()), file.Name(), - hbytes(fileSize), emptyString) - - fileinfoMarked[file.Name()] = true - } - - numExtra := 0 - for _, file := range fileinfos { - if fileinfoMarked[file.Name()] { - continue - } - if numExtra == 0 { - fmt.Print("\n[EXTRA]\n") - } - fmt.Printf("[%s] %-12s %6s\n", file.ModTime().Format(time.RFC3339), - file.Name(), hbytes(file.Size())) - numExtra++ - } - - numValueDirExtra := 0 - for _, file := range valueDirExtras { - if numValueDirExtra == 0 { - fmt.Print("\n[ValueDir EXTRA]\n") - } - fmt.Printf("[%s] %-12s %6s\n", file.ModTime().Format(time.RFC3339), - file.Name(), hbytes(file.Size())) - numValueDirExtra++ - } - - fmt.Print("\n[Summary]\n") - totalIndexSize := int64(0) - for i, sz := range levelSizes { - fmt.Printf("Level %d size: %12s\n", i, hbytes(sz)) - totalIndexSize += sz - } - - fmt.Printf("Total index size: %8s\n", hbytes(totalIndexSize)) - fmt.Printf("Value log size: %10s\n", hbytes(valueLogSize)) - fmt.Println() - totalExtra := numExtra + numValueDirExtra - if totalExtra == 0 && numMissing == 0 && numEmpty == 0 && !manifestTruncated { - fmt.Println("Abnormalities: None.") - } else { - fmt.Println("Abnormalities:") - } - fmt.Printf("%d extra %s.\n", totalExtra, pluralFiles(totalExtra)) - fmt.Printf("%d missing %s.\n", numMissing, pluralFiles(numMissing)) - fmt.Printf("%d empty %s.\n", numEmpty, pluralFiles(numEmpty)) - fmt.Printf("%d truncated %s.\n", boolToNum(manifestTruncated), - pluralManifest(manifestTruncated)) - - return nil -} - -func boolToNum(x bool) int { - if x { - return 1 - } - return 0 -} - -func pluralManifest(manifestTruncated bool) string { - if manifestTruncated { - return "manifest" - } - return "manifests" -} - -func pluralFiles(count int) string { - if count == 1 { - return "file" - } - return "files" -} diff --git a/vendor/github.com/dgraph-io/badger/badger/cmd/restore.go b/vendor/github.com/dgraph-io/badger/badger/cmd/restore.go deleted file mode 100644 index fc54e1ce..00000000 --- a/vendor/github.com/dgraph-io/badger/badger/cmd/restore.go +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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 cmd - -import ( - "errors" - "os" - "path" - - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger" - "gx/ipfs/QmXj63M2w2Pq7mnBpcrs7Va8prmfhvfMUNqVhJ9TgjiMbT/cobra" -) - -var restoreFile string - -// restoreCmd represents the restore command -var restoreCmd = &cobra.Command{ - Use: "restore", - Short: "Restore Badger database.", - Long: `Restore Badger database from a file. - -It reads a file generated using the backup command (or by calling the -DB.Backup() API method) and writes each key-value pair found in the file to -the Badger database. - -Restore creates a new database, and currently does not work on an already -existing database.`, - RunE: doRestore, -} - -func init() { - RootCmd.AddCommand(restoreCmd) - restoreCmd.Flags().StringVarP(&restoreFile, "backup-file", "f", - "badger.bak", "File to restore from") -} - -func doRestore(cmd *cobra.Command, args []string) error { - // Check if the DB already exists - manifestFile := path.Join(sstDir, badger.ManifestFilename) - if _, err := os.Stat(manifestFile); err == nil { // No error. File already exists. - return errors.New("Cannot restore to an already existing database") - } else if os.IsNotExist(err) { - // pass - } else { // Return an error if anything other than the error above - return err - } - - // Open DB - opts := badger.DefaultOptions - opts.Dir = sstDir - opts.ValueDir = vlogDir - db, err := badger.Open(opts) - if err != nil { - return err - } - defer db.Close() - - // Open File - f, err := os.Open(restoreFile) - if err != nil { - return err - } - defer f.Close() - - // Run restore - return db.Load(f) -} diff --git a/vendor/github.com/dgraph-io/badger/badger/cmd/root.go b/vendor/github.com/dgraph-io/badger/badger/cmd/root.go deleted file mode 100644 index 1506ce53..00000000 --- a/vendor/github.com/dgraph-io/badger/badger/cmd/root.go +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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 cmd - -import ( - "errors" - "fmt" - "os" - "strings" - - "gx/ipfs/QmXj63M2w2Pq7mnBpcrs7Va8prmfhvfMUNqVhJ9TgjiMbT/cobra" -) - -var sstDir, vlogDir string - -// RootCmd represents the base command when called without any subcommands -var RootCmd = &cobra.Command{ - Use: "badger", - Short: "Tools to manage Badger database.", - PersistentPreRunE: validateRootCmdArgs, -} - -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - if err := RootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } -} - -func init() { - RootCmd.PersistentFlags().StringVar(&sstDir, "dir", "", - "Directory where the LSM tree files are located. (required)") - - RootCmd.PersistentFlags().StringVar(&vlogDir, "vlog-dir", "", - "Directory where the value log files are located, if different from --dir") -} - -func validateRootCmdArgs(cmd *cobra.Command, args []string) error { - if strings.HasPrefix(cmd.Use, "help ") { // No need to validate if it is help - return nil - } - if sstDir == "" { - return errors.New("--dir not specified") - } - if vlogDir == "" { - vlogDir = sstDir - } - return nil -} diff --git a/vendor/github.com/dgraph-io/badger/badger/main.go b/vendor/github.com/dgraph-io/badger/badger/main.go deleted file mode 100644 index e5864d8f..00000000 --- a/vendor/github.com/dgraph-io/badger/badger/main.go +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 main - -import ( - "fmt" - "net/http" - _ "net/http/pprof" - "runtime" - - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/badger/cmd" -) - -func main() { - go func() { - for i := 8080; i < 9080; i++ { - fmt.Printf("Listening for /debug HTTP requests at port: %d\n", i) - if err := http.ListenAndServe(fmt.Sprintf("localhost:%d", i), nil); err != nil { - fmt.Println("Port busy. Trying another one...") - continue - - } - } - }() - runtime.SetBlockProfileRate(100) - runtime.GOMAXPROCS(128) - cmd.Execute() -} diff --git a/vendor/github.com/dgraph-io/badger/batch.go b/vendor/github.com/dgraph-io/badger/batch.go index 2c26d4b0..bfbc239b 100644 --- a/vendor/github.com/dgraph-io/badger/batch.go +++ b/vendor/github.com/dgraph-io/badger/batch.go @@ -19,15 +19,17 @@ package badger import ( "sync" "time" + + "github.com/dgraph-io/badger/y" ) // WriteBatch holds the necessary info to perform batched writes. type WriteBatch struct { sync.Mutex - txn *Txn - db *DB - wg sync.WaitGroup - err error + txn *Txn + db *DB + throttle *y.Throttle + err error } // NewWriteBatch creates a new WriteBatch. This provides a way to conveniently do a lot of writes, @@ -36,7 +38,18 @@ type WriteBatch struct { // creating and committing transactions. Due to the nature of SSI guaratees provided by Badger, // blind writes can never encounter transaction conflicts (ErrConflict). func (db *DB) NewWriteBatch() *WriteBatch { - return &WriteBatch{db: db, txn: db.newTransaction(true, true)} + 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 @@ -47,13 +60,15 @@ func (db *DB) NewWriteBatch() *WriteBatch { // // Note that any committed writes would still go through despite calling Cancel. func (wb *WriteBatch) Cancel() { - wb.wg.Wait() + 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.wg.Done() + defer wb.throttle.Done(err) if err == nil { return } @@ -123,9 +138,9 @@ func (wb *WriteBatch) commit() error { if wb.err != nil { return wb.err } - // Get a new txn before we commit this one. So, the new txn doesn't need - // to wait for this one to commit. - wb.wg.Add(1) + 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. @@ -140,8 +155,10 @@ func (wb *WriteBatch) Flush() error { wb.txn.Discard() wb.Unlock() - wb.wg.Wait() - // Safe to access error without any synchronization here. + if err := wb.throttle.Finish(); err != nil { + return err + } + return wb.err } diff --git a/vendor/github.com/dgraph-io/badger/batch_test.go b/vendor/github.com/dgraph-io/badger/batch_test.go deleted file mode 100644 index 041fe92a..00000000 --- a/vendor/github.com/dgraph-io/badger/batch_test.go +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2018 Dgraph Labs, Inc. and Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package badger - -import ( - "fmt" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -func TestWriteBatch(t *testing.T) { - key := func(i int) []byte { - return []byte(fmt.Sprintf("%10d", i)) - } - val := func(i int) []byte { - return []byte(fmt.Sprintf("%128d", i)) - } - - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - wb := db.NewWriteBatch() - defer wb.Cancel() - - N, M := 50000, 1000 - start := time.Now() - - for i := 0; i < N; i++ { - require.NoError(t, wb.Set(key(i), val(i), 0)) - } - for i := 0; i < M; i++ { - require.NoError(t, wb.Delete(key(i))) - } - require.NoError(t, wb.Flush()) - t.Logf("Time taken for %d writes (w/ test options): %s\n", N+M, time.Since(start)) - - err := db.View(func(txn *Txn) error { - itr := txn.NewIterator(DefaultIteratorOptions) - defer itr.Close() - - i := M - for itr.Rewind(); itr.Valid(); itr.Next() { - item := itr.Item() - require.Equal(t, string(key(i)), string(item.Key())) - valcopy, err := item.ValueCopy(nil) - require.NoError(t, err) - require.Equal(t, val(i), valcopy) - i++ - } - require.Equal(t, N, i) - return nil - }) - require.NoError(t, err) - }) -} diff --git a/vendor/github.com/dgraph-io/badger/compaction.go b/vendor/github.com/dgraph-io/badger/compaction.go index 7fd1b174..931d5666 100644 --- a/vendor/github.com/dgraph-io/badger/compaction.go +++ b/vendor/github.com/dgraph-io/badger/compaction.go @@ -23,10 +23,10 @@ import ( "math" "sync" - "gx/ipfs/QmRvYNctevGUW52urgmoFZscT6buMKqhHezLUS64WepGWn/go-net/trace" + "golang.org/x/net/trace" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/table" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" + "github.com/dgraph-io/badger/table" + "github.com/dgraph-io/badger/y" ) type keyRange struct { @@ -65,7 +65,9 @@ func (r keyRange) overlapsWith(dst keyRange) bool { } func getKeyRange(tables []*table.Table) keyRange { - y.AssertTrue(len(tables) > 0) + if len(tables) == 0 { + return keyRange{} + } smallest := tables[0].Smallest() biggest := tables[0].Biggest() for i := 1; i < len(tables); i++ { @@ -129,7 +131,7 @@ func (cs *compactStatus) toLog(tr trace.Trace) { tr.LazyPrintf("Compaction status:") for i, l := range cs.levels { - if len(l.debug()) == 0 { + if l.debug() == "" { continue } tr.LazyPrintf("[%d] %s", i, l.debug()) diff --git a/vendor/github.com/dgraph-io/badger/contrib/cover.sh b/vendor/github.com/dgraph-io/badger/contrib/cover.sh deleted file mode 100644 index 5e2c179a..00000000 --- a/vendor/github.com/dgraph-io/badger/contrib/cover.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -SRC="$( cd -P "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/.." -TMP=$(mktemp /tmp/badger-coverage-XXXXX.txt) - -BUILD=$1 -OUT=$2 - -set -e - -pushd $SRC &> /dev/null - -# create coverage output -echo 'mode: atomic' > $OUT -for PKG in $(go list ./...|grep -v -E 'vendor'); do - go test -covermode=atomic -coverprofile=$TMP $PKG - tail -n +2 $TMP >> $OUT -done - -# Another round of tests after turning off mmap -go test -v -vlog_mmap=false github.com/dgraph-io/badger - -popd &> /dev/null diff --git a/vendor/github.com/dgraph-io/badger/db.go b/vendor/github.com/dgraph-io/badger/db.go index 8c374bdc..24e7b1a4 100644 --- a/vendor/github.com/dgraph-io/badger/db.go +++ b/vendor/github.com/dgraph-io/badger/db.go @@ -18,8 +18,11 @@ package badger import ( "bytes" + "context" "encoding/binary" + "encoding/hex" "expvar" + "io" "math" "os" "path/filepath" @@ -29,20 +32,22 @@ import ( "sync/atomic" "time" - humanize "gx/ipfs/QmQMxG9D52TirZd9eLA37nxiNspnMRkKbyPWrVAa1gvtSy/go-humanize" - "gx/ipfs/QmRvYNctevGUW52urgmoFZscT6buMKqhHezLUS64WepGWn/go-net/trace" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/options" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/skl" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/table" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" - "gx/ipfs/QmVmDhyTTUcQXFD1rRQ64fGLMSAoaQvNH3hwuaCFAPq2hy/errors" + "github.com/dgraph-io/badger/options" + "github.com/dgraph-io/badger/pb" + "github.com/dgraph-io/badger/skl" + "github.com/dgraph-io/badger/table" + "github.com/dgraph-io/badger/y" + humanize "github.com/dustin/go-humanize" + "github.com/pkg/errors" + "golang.org/x/net/trace" ) var ( - badgerPrefix = []byte("!badger!") // Prefix for internal keys used by badger. - head = []byte("!badger!head") // For storing value offset for replay. - txnKey = []byte("!badger!txn") // For indicating end of entries in txn. - badgerMove = []byte("!badger!move") // For key-value pairs which got moved during GC. + badgerPrefix = []byte("!badger!") // Prefix for internal keys used by badger. + head = []byte("!badger!head") // For storing value offset for replay. + txnKey = []byte("!badger!txn") // For indicating end of entries in txn. + badgerMove = []byte("!badger!move") // For key-value pairs which got moved during GC. + lfDiscardStatsKey = []byte("!badger!discard") // For storing lfDiscardStats ) type closers struct { @@ -51,8 +56,11 @@ type closers struct { memtable *y.Closer writes *y.Closer valueGC *y.Closer + pub *y.Closer } +type callback func(kv *pb.KVList) + // DB provides the various functions required to interact with Badger. // DB is thread-safe. type DB struct { @@ -73,10 +81,18 @@ type DB struct { vhead valuePointer // less than or equal to a pointer to the last vlog value put into mt writeCh chan *request flushChan chan flushTask // For flushing memtables. + closeOnce sync.Once // For closing DB only once. + + // Number of log rotates since the last memtable flush. We will access this field via atomic + // functions. Since we are not going to use any 64bit atomic functions, there is no need for + // 64 bit alignment of this struct(see #311). + logRotates int32 blockWrites int32 orc *oracle + + pub *publisher } const ( @@ -125,9 +141,10 @@ func (db *DB) replayFunction() func(Entry, valuePointer) error { } v := y.ValueStruct{ - Value: nv, - Meta: meta, - UserMeta: e.UserMeta, + Value: nv, + Meta: meta, + UserMeta: e.UserMeta, + ExpiresAt: e.ExpiresAt, } if e.meta&bitFinTxn > 0 { @@ -182,6 +199,8 @@ func Open(opt Options) (db *DB, err error) { if opt.ReadOnly { // Can't truncate if the DB is read only. opt.Truncate = false + // Do not perform compaction in read only mode. + opt.CompactL0OnClose = false } for _, path := range []string{opt.Dir, opt.ValueDir} { @@ -256,6 +275,7 @@ func Open(opt Options) (db *DB, err error) { dirLockGuard: dirLockGuard, valueDirGuard: valueDirLockGuard, orc: newOracle(opt), + pub: newPublisher(), } // Calculate initial size. @@ -300,7 +320,10 @@ func Open(opt Options) (db *DB, err error) { // Let's advance nextTxnTs to one more than whatever we observed via // replaying the logs. db.orc.txnMark.Done(db.orc.nextTxnTs) - db.orc.nextTxnTs++ + // In normal mode, we must update readMark so older versions of keys can be removed during + // compaction when run in offline mode via the flatten tool. + db.orc.readMark.Done(db.orc.nextTxnTs) + db.orc.incrementNextTs() db.writeCh = make(chan *request, kvWriteChCapacity) db.closers.writes = y.NewCloser(1) @@ -309,16 +332,26 @@ func Open(opt Options) (db *DB, err error) { db.closers.valueGC = y.NewCloser(1) go db.vlog.waitOnGC(db.closers.valueGC) + db.closers.pub = y.NewCloser(1) + go db.pub.listenForUpdates(db.closers.pub) + valueDirLockGuard = nil dirLockGuard = nil manifestFile = nil return db, nil } -// Close closes a DB. It's crucial to call it to ensure all the pending updates -// make their way to disk. Calling DB.Close() multiple times is not safe and would -// cause panic. -func (db *DB) Close() (err error) { +// Close closes a DB. It's crucial to call it to ensure all the pending updates make their way to +// disk. Calling DB.Close() multiple times would still only close the DB once. +func (db *DB) Close() error { + var err error + db.closeOnce.Do(func() { + err = db.close() + }) + return err +} + +func (db *DB) close() (err error) { db.elog.Printf("Closing database") atomic.StoreInt32(&db.blockWrites, 1) @@ -328,8 +361,10 @@ func (db *DB) Close() (err error) { // Stop writes next. db.closers.writes.SignalAndWait() + db.closers.pub.SignalAndWait() + // Now close the value log. - if vlogErr := db.vlog.Close(); err == nil { + if vlogErr := db.vlog.Close(); vlogErr != nil { err = errors.Wrap(vlogErr, "DB.Close") } @@ -346,7 +381,7 @@ func (db *DB) Close() (err error) { defer db.Unlock() y.AssertTrue(db.mt != nil) select { - case db.flushChan <- flushTask{db.mt, db.vhead}: + case db.flushChan <- flushTask{mt: db.mt, vptr: db.vhead}: db.imm = append(db.imm, db.mt) // Flusher will attempt to remove this from s.imm. db.mt = nil // Will segfault if we try writing! db.elog.Printf("pushed to flush chan\n") @@ -369,10 +404,15 @@ func (db *DB) Close() (err error) { // Force Compact L0 // We don't need to care about cstatus since no parallel compaction is running. if db.opt.CompactL0OnClose { - if err := db.lc.doCompact(compactionPriority{level: 0, score: 1.73}); err != nil { - db.opt.Warningf("While forcing compaction on level 0: %v", err) - } else { + err := db.lc.doCompact(compactionPriority{level: 0, score: 1.73}) + switch err { + case errFillTables: + // This error only means that there might be enough tables to do a compaction. So, we + // should not report it to the end user to avoid confusing them. + case nil: db.opt.Infof("Force compaction on level 0 done") + default: + db.opt.Warningf("While forcing compaction on level 0: %v", err) } } @@ -416,6 +456,12 @@ const ( lockFile = "LOCK" ) +// Sync syncs database content to disk. This function provides +// more control to user to sync data whenever required. +func (db *DB) Sync() error { + return db.vlog.sync(math.MaxUint32) +} + // 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.) @@ -585,6 +631,8 @@ func (db *DB) writeRequests(reqs []*request) error { return err } + db.elog.Printf("Sending updates to subscribers") + db.pub.sendUpdates(reqs) db.elog.Printf("Writing to memtable") var count int for _, b := range reqs { @@ -593,7 +641,7 @@ func (db *DB) writeRequests(reqs []*request) error { } count += len(b.Entries) var i uint64 - for err := db.ensureRoomForWrite(); err == errNoRoom; err = db.ensureRoomForWrite() { + for err = db.ensureRoomForWrite(); err == errNoRoom; err = db.ensureRoomForWrite() { i++ if i%100 == 0 { db.elog.Printf("Making room for writes") @@ -637,6 +685,8 @@ func (db *DB) sendToWriteCh(entries []*Entry) (*request, error) { req.Entries = entries req.Wg = sync.WaitGroup{} req.Wg.Add(1) + req.IncrRef() // for db write + req.IncrRef() // for publisher updates db.writeCh <- req // Handled in doWrites. y.NumPuts.Add(int64(len(entries))) @@ -741,21 +791,36 @@ func (db *DB) ensureRoomForWrite() error { var err error db.Lock() defer db.Unlock() - if db.mt.MemSize() < db.opt.MaxTableSize { + + // Here we determine if we need to force flush memtable. Given we rotated log file, it would + // make sense to force flush a memtable, so the updated value head would have a chance to be + // pushed to L0. Otherwise, it would not go to L0, until the memtable has been fully filled, + // which can take a lot longer if the write load has fewer keys and larger values. This force + // flush, thus avoids the need to read through a lot of log files on a crash and restart. + // Above approach is quite simple with small drawback. We are calling ensureRoomForWrite before + // inserting every entry in Memtable. We will get latest db.head after all entries for a request + // are inserted in Memtable. If we have done >= db.logRotates rotations, then while inserting + // first entry in Memtable, below condition will be true and we will endup flushing old value of + // db.head. Hence we are limiting no of value log files to be read to db.logRotates only. + forceFlush := atomic.LoadInt32(&db.logRotates) >= db.opt.LogRotatesToFlush + + if !forceFlush && db.mt.MemSize() < db.opt.MaxTableSize { return nil } y.AssertTrue(db.mt != nil) // A nil mt indicates that DB is being closed. select { - case db.flushChan <- flushTask{db.mt, db.vhead}: - db.elog.Printf("Flushing value log to disk if async mode.") + case db.flushChan <- flushTask{mt: db.mt, vptr: db.vhead}: + // After every memtable flush, let's reset the counter. + atomic.StoreInt32(&db.logRotates, 0) + // Ensure value log is synced to disk so this memtable's contents wouldn't be lost. - err = db.vlog.sync() + err = db.vlog.sync(db.vhead.Fid) if err != nil { return err } - db.elog.Printf("Flushing memtable, mt.size=%d size of flushChan: %d\n", + db.opt.Debugf("Flushing memtable, mt.size=%d size of flushChan: %d\n", db.mt.MemSize(), len(db.flushChan)) // We manage to push this task. Let's modify imm. db.imm = append(db.imm, db.mt) @@ -773,12 +838,15 @@ func arenaSize(opt Options) int64 { } // WriteLevel0Table flushes memtable. -func writeLevel0Table(s *skl.Skiplist, f *os.File) error { - iter := s.NewIterator() +func writeLevel0Table(ft flushTask, f io.Writer) error { + iter := ft.mt.NewIterator() defer iter.Close() b := table.NewTableBuilder() defer b.Close() for iter.SeekToFirst(); iter.Valid(); iter.Next() { + if len(ft.dropPrefix) > 0 && bytes.HasPrefix(iter.Key(), ft.dropPrefix) { + continue + } if err := b.Add(iter.Key(), iter.Value()); err != nil { return err } @@ -788,25 +856,34 @@ func writeLevel0Table(s *skl.Skiplist, f *os.File) error { } type flushTask struct { - mt *skl.Skiplist - vptr valuePointer + mt *skl.Skiplist + vptr valuePointer + dropPrefix []byte } // handleFlushTask must be run serially. func (db *DB) handleFlushTask(ft flushTask) error { - if !ft.mt.Empty() { - // Store badger head even if vptr is zero, need it for readTs - db.opt.Infof("Storing value log head: %+v\n", ft.vptr) - db.elog.Printf("Storing offset: %+v\n", ft.vptr) - offset := make([]byte, vptrSize) - ft.vptr.Encode(offset) - - // Pick the max commit ts, so in case of crash, our read ts would be higher than all the - // commits. - headTs := y.KeyWithTs(head, db.orc.nextTs()) - ft.mt.Put(headTs, y.ValueStruct{Value: offset}) + // There can be a scnerio, when empty memtable is flushed. For example, memtable is empty and + // after writing request to value log, rotation count exceeds db.LogRotatesToFlush. + if ft.mt.Empty() { + return nil } + // Store badger head even if vptr is zero, need it for readTs + db.opt.Debugf("Storing value log head: %+v\n", ft.vptr) + db.elog.Printf("Storing offset: %+v\n", ft.vptr) + offset := make([]byte, vptrSize) + ft.vptr.Encode(offset) + + // Pick the max commit ts, so in case of crash, our read ts would be higher than all the + // commits. + headTs := y.KeyWithTs(head, db.orc.nextTs()) + ft.mt.Put(headTs, y.ValueStruct{Value: offset}) + + // Also store lfDiscardStats before flushing memtables + discardStatsKey := y.KeyWithTs(lfDiscardStatsKey, 1) + ft.mt.Put(discardStatsKey, y.ValueStruct{Value: db.vlog.encodedDiscardStats()}) + fileID := db.lc.reserveFileID() fd, err := y.CreateSyncedFile(table.NewFilename(fileID, db.opt.Dir), true) if err != nil { @@ -817,7 +894,7 @@ func (db *DB) handleFlushTask(ft flushTask) error { dirSyncCh := make(chan error) go func() { dirSyncCh <- syncDir(db.opt.Dir) }() - err = writeLevel0Table(ft.mt, fd) + err = writeLevel0Table(ft, fd) dirSyncErr := <-dirSyncCh if err != nil { @@ -837,22 +914,7 @@ func (db *DB) handleFlushTask(ft flushTask) error { // We own a ref on tbl. err = db.lc.addLevel0Table(tbl) // This will incrRef (if we don't error, sure) tbl.DecrRef() // Releases our ref. - if err != nil { - return err - } - - // Update s.imm. Need a lock. - db.Lock() - defer db.Unlock() - // This is a single-threaded operation. ft.mt corresponds to the head of - // db.imm list. Once we flush it, we advance db.imm. The next ft.mt - // which would arrive here would match db.imm[0], because we acquire a - // lock over DB when pushing to flushChan. - // TODO: This logic is dirty AF. Any change and this could easily break. - y.AssertTrue(ft.mt == db.imm[0]) - db.imm = db.imm[1:] - ft.mt.DecrRef() // Return memory. - return nil + return err } // flushMemtable must keep running until we send it an empty flushTask. If there @@ -868,6 +930,18 @@ func (db *DB) flushMemtable(lc *y.Closer) error { for { err := db.handleFlushTask(ft) if err == nil { + // Update s.imm. Need a lock. + db.Lock() + // This is a single-threaded operation. ft.mt corresponds to the head of + // db.imm list. Once we flush it, we advance db.imm. The next ft.mt + // which would arrive here would match db.imm[0], because we acquire a + // lock over DB when pushing to flushChan. + // TODO: This logic is dirty AF. Any change and this could easily break. + y.AssertTrue(ft.mt == db.imm[0]) + db.imm = db.imm[1:] + ft.mt.DecrRef() // Return memory. + db.Unlock() + break } // Encountered error. Retry indefinitely. @@ -994,7 +1068,7 @@ func (db *DB) RunValueLogGC(discardRatio float64) error { // Size returns the size of lsm and value log files in bytes. It can be used to decide how often to // call RunValueLogGC. -func (db *DB) Size() (lsm int64, vlog int64) { +func (db *DB) Size() (lsm, vlog int64) { if y.LSMSize.Get(db.opt.Dir) == nil { lsm, vlog = 0, 0 return @@ -1104,16 +1178,18 @@ func (db *DB) GetSequence(key []byte, bandwidth uint64) (*Sequence, error) { return seq, err } -// Tables gets the TableInfo objects from the level controller. -func (db *DB) Tables() []TableInfo { - return db.lc.getTableInfo() +// Tables gets the TableInfo objects from the level controller. If withKeysCount +// is true, TableInfo objects also contain counts of keys for the tables. +func (db *DB) Tables(withKeysCount bool) []TableInfo { + return db.lc.getTableInfo(withKeysCount) } // KeySplits can be used to get rough key ranges to divide up iteration over // the DB. func (db *DB) KeySplits(prefix []byte) []string { var splits []string - for _, ti := range db.Tables() { + // We just want table ranges here and not keys count. + for _, ti := range db.Tables(false) { // We don't use ti.Left, because that has a tendency to store !badger // keys. if bytes.HasPrefix(ti.Right, prefix) { @@ -1230,6 +1306,32 @@ func (db *DB) Flatten(workers int) error { } } +func (db *DB) prepareToDrop() func() { + if db.opt.ReadOnly { + panic("Attempting to drop data in read-only mode.") + } + // Stop accepting new writes. + atomic.StoreInt32(&db.blockWrites, 1) + + // Make all pending writes finish. The following will also close writeCh. + db.closers.writes.SignalAndWait() + db.opt.Infof("Writes flushed. Stopping compactions now...") + + // Stop all compactions. + db.stopCompactions() + return func() { + db.opt.Infof("Resuming writes") + db.startCompactions() + + db.writeCh = make(chan *request, kvWriteChCapacity) + db.closers.writes = y.NewCloser(1) + go db.doWrites(db.closers.writes) + + // Resume writes. + atomic.StoreInt32(&db.blockWrites, 0) + } +} + // DropAll would drop all the data stored in Badger. It does this in the following way. // - Stop accepting new writes. // - Pause memtable flushes and compactions. @@ -1242,31 +1344,20 @@ func (db *DB) Flatten(workers int) error { // any reads while DropAll is going on, otherwise they may result in panics. Ideally, both reads and // writes are paused before running DropAll, and resumed after it is finished. func (db *DB) DropAll() error { - if db.opt.ReadOnly { - panic("Attempting to drop data in read-only mode.") + f, err := db.dropAll() + if err != nil { + return err } + if f == nil { + panic("both error and returned function cannot be nil in DropAll") + } + f() + return nil +} + +func (db *DB) dropAll() (func(), error) { db.opt.Infof("DropAll called. Blocking writes...") - // Stop accepting new writes. - atomic.StoreInt32(&db.blockWrites, 1) - - // Make all pending writes finish. The following will also close writeCh. - db.closers.writes.SignalAndWait() - db.opt.Infof("Writes flushed. Stopping compactions now...") - - // Stop all compactions. - db.stopCompactions() - defer func() { - db.opt.Infof("Resuming writes") - db.startCompactions() - - db.writeCh = make(chan *request, kvWriteChCapacity) - db.closers.writes = y.NewCloser(1) - go db.doWrites(db.closers.writes) - - // Resume writes. - atomic.StoreInt32(&db.blockWrites, 0) - }() - db.opt.Infof("Compactions stopped. Dropping all SSTables...") + f := db.prepareToDrop() // Block all foreign interactions with memory tables. db.Lock() @@ -1274,23 +1365,115 @@ func (db *DB) DropAll() error { // Remove inmemory tables. Calling DecrRef for safety. Not sure if they're absolutely needed. db.mt.DecrRef() - db.mt = skl.NewSkiplist(arenaSize(db.opt)) // Set it up for future writes. for _, mt := range db.imm { mt.DecrRef() } db.imm = db.imm[:0] + db.mt = skl.NewSkiplist(arenaSize(db.opt)) // Set it up for future writes. - num, err := db.lc.deleteLSMTree() + num, err := db.lc.dropTree() if err != nil { - return err + return nil, err } db.opt.Infof("Deleted %d SSTables. Now deleting value logs...\n", num) num, err = db.vlog.dropAll() if err != nil { - return err + return nil, err } db.vhead = valuePointer{} // Zero it out. + db.lc.nextFileID = 1 db.opt.Infof("Deleted %d value log files. DropAll done.\n", num) + return f, nil +} + +// DropPrefix would drop all the keys with the provided prefix. It does this in the following way: +// - Stop accepting new writes. +// - Stop memtable flushes and compactions. +// - Flush out all memtables, skipping over keys with the given prefix, Kp. +// - Write out the value log header to memtables when flushing, so we don't accidentally bring Kp +// back after a restart. +// - Compact L0->L1, skipping over Kp. +// - Compact rest of the levels, Li->Li, picking tables which have Kp. +// - Resume memtable flushes, compactions and writes. +func (db *DB) DropPrefix(prefix []byte) error { + db.opt.Infof("DropPrefix called on %s. Blocking writes...", hex.Dump(prefix)) + f := db.prepareToDrop() + defer f() + + // Block all foreign interactions with memory tables. + db.Lock() + defer db.Unlock() + + db.imm = append(db.imm, db.mt) + for _, memtable := range db.imm { + if memtable.Empty() { + memtable.DecrRef() + continue + } + task := flushTask{ + mt: memtable, + // Ensure that the head of value log gets persisted to disk. + vptr: db.vhead, + dropPrefix: prefix, + } + db.opt.Debugf("Flushing memtable") + if err := db.handleFlushTask(task); err != nil { + db.opt.Errorf("While trying to flush memtable: %v", err) + return err + } + memtable.DecrRef() + } + db.imm = db.imm[:0] + db.mt = skl.NewSkiplist(arenaSize(db.opt)) + + // Drop prefixes from the levels. + if err := db.lc.dropPrefix(prefix); err != nil { + return err + } + db.opt.Infof("DropPrefix done") return nil } + +// Subscribe can be used watch key changes for the given key prefix. +func (db *DB) Subscribe(ctx context.Context, cb callback, prefix []byte, prefixes ...[]byte) error { + if cb == nil { + return ErrNilCallback + } + prefixes = append(prefixes, prefix) + c := y.NewCloser(1) + recvCh, id := db.pub.newSubscriber(c, prefixes...) + slurp := func(batch *pb.KVList) { + defer func() { + if len(batch.GetKv()) > 0 { + cb(batch) + } + }() + for { + select { + case kvs := <-recvCh: + batch.Kv = append(batch.Kv, kvs.Kv...) + default: + return + } + } + } + for { + select { + case <-c.HasBeenClosed(): + slurp(new(pb.KVList)) + // Drain if any pending updates. + c.Done() + // No need to delete here. Closer will be called only while + // closing DB. Subscriber will be deleted by cleanSubscribers. + return nil + case <-ctx.Done(): + c.Done() + db.pub.deleteSubscriber(id) + // Delete the subscriber to avoid further updates. + return ctx.Err() + case batch := <-recvCh: + slurp(batch) + } + } +} diff --git a/vendor/github.com/dgraph-io/badger/db2_test.go b/vendor/github.com/dgraph-io/badger/db2_test.go deleted file mode 100644 index 03a7b265..00000000 --- a/vendor/github.com/dgraph-io/badger/db2_test.go +++ /dev/null @@ -1,325 +0,0 @@ -/* - * 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 ( - "flag" - "fmt" - "io/ioutil" - "log" - "math/rand" - "os" - "path" - "regexp" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestTruncateVlogWithClose(t *testing.T) { - key := func(i int) []byte { - return []byte(fmt.Sprintf("%d%10d", i, i)) - } - data := func(l int) []byte { - m := make([]byte, l) - _, err := rand.Read(m) - require.NoError(t, err) - return m - } - - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - - opt := getTestOptions(dir) - opt.SyncWrites = true - opt.Truncate = true - opt.ValueThreshold = 1 // Force all reads from value log. - - db, err := Open(opt) - require.NoError(t, err) - - err = db.Update(func(txn *Txn) error { - return txn.Set(key(0), data(4055)) - }) - require.NoError(t, err) - - // Close the DB. - require.NoError(t, db.Close()) - require.NoError(t, os.Truncate(path.Join(dir, "000000.vlog"), 4096)) - - // Reopen and write some new data. - db, err = Open(opt) - require.NoError(t, err) - for i := 0; i < 32; i++ { - err := db.Update(func(txn *Txn) error { - return txn.Set(key(i), data(10)) - }) - require.NoError(t, err) - } - // Read it back to ensure that we can read it now. - for i := 0; i < 32; i++ { - err := db.View(func(txn *Txn) error { - item, err := txn.Get(key(i)) - require.NoError(t, err) - val := getItemValue(t, item) - require.Equal(t, 10, len(val)) - return nil - }) - require.NoError(t, err) - } - require.NoError(t, db.Close()) - - // Reopen and read the data again. - db, err = Open(opt) - require.NoError(t, err) - for i := 0; i < 32; i++ { - err := db.View(func(txn *Txn) error { - item, err := txn.Get(key(i)) - require.NoError(t, err) - val := getItemValue(t, item) - require.Equal(t, 10, len(val)) - return nil - }) - require.NoError(t, err) - } - require.NoError(t, db.Close()) -} - -var manual = flag.Bool("manual", false, "Set when manually running some tests.") - -// The following 3 TruncateVlogNoClose tests should be run one after another. -// None of these close the DB, simulating a crash. They should be run with a -// script, which truncates the value log to 4096, lining up with the end of the -// first entry in the txn. At <4096, it would cause the entry to be truncated -// immediately, at >4096, same thing. -func TestTruncateVlogNoClose(t *testing.T) { - if !*manual { - t.Skip("Skipping test meant to be run manually.") - return - } - fmt.Println("running") - dir := "p" - opts := getTestOptions(dir) - opts.SyncWrites = true - opts.Truncate = true - - kv, err := Open(opts) - require.NoError(t, err) - key := func(i int) string { - return fmt.Sprintf("%d%10d", i, i) - } - data := fmt.Sprintf("%4055d", 1) - err = kv.Update(func(txn *Txn) error { - return txn.Set([]byte(key(0)), []byte(data)) - }) - require.NoError(t, err) -} -func TestTruncateVlogNoClose2(t *testing.T) { - if !*manual { - t.Skip("Skipping test meant to be run manually.") - return - } - dir := "p" - opts := getTestOptions(dir) - opts.SyncWrites = true - opts.Truncate = true - - kv, err := Open(opts) - require.NoError(t, err) - key := func(i int) string { - return fmt.Sprintf("%d%10d", i, i) - } - data := fmt.Sprintf("%10d", 1) - for i := 32; i < 64; i++ { - err := kv.Update(func(txn *Txn) error { - return txn.Set([]byte(key(i)), []byte(data)) - }) - require.NoError(t, err) - } - for i := 32; i < 64; i++ { - require.NoError(t, kv.View(func(txn *Txn) error { - item, err := txn.Get([]byte(key(i))) - require.NoError(t, err) - val := getItemValue(t, item) - require.NotNil(t, val) - require.True(t, len(val) > 0) - return nil - })) - } -} -func TestTruncateVlogNoClose3(t *testing.T) { - if !*manual { - t.Skip("Skipping test meant to be run manually.") - return - } - fmt.Print("Running") - dir := "p" - opts := getTestOptions(dir) - opts.SyncWrites = true - opts.Truncate = true - - kv, err := Open(opts) - require.NoError(t, err) - key := func(i int) string { - return fmt.Sprintf("%d%10d", i, i) - } - for i := 32; i < 64; i++ { - require.NoError(t, kv.View(func(txn *Txn) error { - item, err := txn.Get([]byte(key(i))) - require.NoError(t, err) - val := getItemValue(t, item) - require.NotNil(t, val) - require.True(t, len(val) > 0) - return nil - })) - } -} - -func TestBigKeyValuePairs(t *testing.T) { - // This test takes too much memory. So, run separately. - if !*manual { - t.Skip("Skipping test meant to be run manually.") - return - } - opts := DefaultOptions - opts.MaxTableSize = 1 << 20 - opts.ValueLogMaxEntries = 64 - runBadgerTest(t, &opts, func(t *testing.T, db *DB) { - bigK := make([]byte, 65001) - bigV := make([]byte, db.opt.ValueLogFileSize+1) - small := make([]byte, 65000) - - txn := db.NewTransaction(true) - require.Regexp(t, regexp.MustCompile("Key.*exceeded"), txn.Set(bigK, small)) - require.Regexp(t, regexp.MustCompile("Value.*exceeded"), txn.Set(small, bigV)) - - require.NoError(t, txn.Set(small, small)) - require.Regexp(t, regexp.MustCompile("Key.*exceeded"), txn.Set(bigK, bigV)) - - require.NoError(t, db.View(func(txn *Txn) error { - _, err := txn.Get(small) - require.Equal(t, ErrKeyNotFound, err) - return nil - })) - - // Now run a longer test, which involves value log GC. - data := fmt.Sprintf("%100d", 1) - key := func(i int) string { - return fmt.Sprintf("%65000d", i) - } - - saveByKey := func(key string, value []byte) error { - return db.Update(func(txn *Txn) error { - return txn.Set([]byte(key), value) - }) - } - - getByKey := func(key string) error { - return db.View(func(txn *Txn) error { - item, err := txn.Get([]byte(key)) - if err != nil { - return err - } - return item.Value(func(val []byte) error { - if len(val) == 0 { - log.Fatalf("key not found %q", len(key)) - } - return nil - }) - }) - } - - for i := 0; i < 32; i++ { - if i < 30 { - require.NoError(t, saveByKey(key(i), []byte(data))) - } else { - require.NoError(t, saveByKey(key(i), []byte(fmt.Sprintf("%100d", i)))) - } - } - - for j := 0; j < 5; j++ { - for i := 0; i < 32; i++ { - if i < 30 { - require.NoError(t, saveByKey(key(i), []byte(data))) - } else { - require.NoError(t, saveByKey(key(i), []byte(fmt.Sprintf("%100d", i)))) - } - } - } - - for i := 0; i < 32; i++ { - require.NoError(t, getByKey(key(i))) - } - - var loops int - var err error - for err == nil { - err = db.RunValueLogGC(0.5) - require.NotRegexp(t, regexp.MustCompile("truncate"), err) - loops++ - } - t.Logf("Ran value log GC %d times. Last error: %v\n", loops, err) - }) -} - -// The following test checks for issue #585. -func TestPushValueLogLimit(t *testing.T) { - // This test takes too much memory. So, run separately. - if !*manual { - t.Skip("Skipping test meant to be run manually.") - return - } - opt := DefaultOptions - opt.ValueLogMaxEntries = 64 - opt.ValueLogFileSize = 2 << 30 - runBadgerTest(t, &opt, func(t *testing.T, db *DB) { - data := []byte(fmt.Sprintf("%30d", 1)) - key := func(i int) string { - return fmt.Sprintf("%100d", i) - } - - for i := 0; i < 32; i++ { - if i == 4 { - v := make([]byte, 2<<30) - err := db.Update(func(txn *Txn) error { - return txn.Set([]byte(key(i)), v) - }) - require.NoError(t, err) - } else { - err := db.Update(func(txn *Txn) error { - return txn.Set([]byte(key(i)), data) - }) - require.NoError(t, err) - } - } - - for i := 0; i < 32; i++ { - err := db.View(func(txn *Txn) error { - item, err := txn.Get([]byte(key(i))) - require.NoError(t, err, "Getting key: %s", key(i)) - err = item.Value(func(v []byte) error { - _ = v - return nil - }) - require.NoError(t, err, "Getting value: %s", key(i)) - return nil - }) - require.NoError(t, err) - } - }) -} diff --git a/vendor/github.com/dgraph-io/badger/db_test.go b/vendor/github.com/dgraph-io/badger/db_test.go deleted file mode 100644 index 4d7c05bf..00000000 --- a/vendor/github.com/dgraph-io/badger/db_test.go +++ /dev/null @@ -1,1708 +0,0 @@ -/* - * 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" - "encoding/binary" - "flag" - "fmt" - "io/ioutil" - "log" - "math" - "math/rand" - "net/http" - "os" - "path/filepath" - "runtime" - "sort" - "sync" - "testing" - "time" - - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/options" - - "github.com/stretchr/testify/require" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" -) - -var mmap = flag.Bool("vlog_mmap", true, "Specify if value log must be memory-mapped") - -func getTestOptions(dir string) Options { - opt := DefaultOptions - opt.MaxTableSize = 1 << 15 // Force more compaction. - opt.LevelOneSize = 4 << 15 // Force more compaction. - opt.Dir = dir - opt.ValueDir = dir - opt.SyncWrites = false - if !*mmap { - opt.ValueLogLoadingMode = options.FileIO - } - return opt -} - -func getItemValue(t *testing.T, item *Item) (val []byte) { - t.Helper() - var v []byte - size := item.ValueSize() - err := item.Value(func(val []byte) error { - if val == nil { - v = nil - } else { - v = append([]byte{}, val...) - } - return nil - }) - if err != nil { - t.Error(err) - } - if int64(len(v)) != size { - t.Errorf("incorrect size: expected %d, got %d", len(v), size) - } - if v == nil { - return nil - } - another, err := item.ValueCopy(nil) - require.NoError(t, err) - require.Equal(t, v, another) - return v -} - -func txnSet(t *testing.T, kv *DB, key []byte, val []byte, meta byte) { - txn := kv.NewTransaction(true) - require.NoError(t, txn.SetWithMeta(key, val, meta)) - require.NoError(t, txn.Commit()) -} - -func txnDelete(t *testing.T, kv *DB, key []byte) { - txn := kv.NewTransaction(true) - require.NoError(t, txn.Delete(key)) - require.NoError(t, txn.Commit()) -} - -// Opens a badger db and runs a a test on it. -func runBadgerTest(t *testing.T, opts *Options, test func(t *testing.T, db *DB)) { - dir, err := ioutil.TempDir(".", "badger-test") - require.NoError(t, err) - defer os.RemoveAll(dir) - if opts == nil { - opts = new(Options) - *opts = getTestOptions(dir) - } else { - opts.Dir = dir - opts.ValueDir = dir - } - db, err := Open(*opts) - require.NoError(t, err) - defer db.Close() - test(t, db) -} - -func TestWrite(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - for i := 0; i < 100; i++ { - txnSet(t, db, []byte(fmt.Sprintf("key%d", i)), []byte(fmt.Sprintf("val%d", i)), 0x00) - } - }) -} - -func TestUpdateAndView(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - err := db.Update(func(txn *Txn) error { - for i := 0; i < 10; i++ { - err := txn.Set([]byte(fmt.Sprintf("key%d", i)), []byte(fmt.Sprintf("val%d", i))) - if err != nil { - return err - } - } - return nil - }) - require.NoError(t, err) - - err = db.View(func(txn *Txn) error { - for i := 0; i < 10; i++ { - item, err := txn.Get([]byte(fmt.Sprintf("key%d", i))) - if err != nil { - return err - } - - expected := []byte(fmt.Sprintf("val%d", i)) - if err := item.Value(func(val []byte) error { - require.Equal(t, expected, val, - "Invalid value for key %q. expected: %q, actual: %q", - item.Key(), expected, val) - return nil - }); err != nil { - return err - } - } - return nil - }) - require.NoError(t, err) - }) -} - -func TestConcurrentWrite(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - // Not a benchmark. Just a simple test for concurrent writes. - n := 20 - m := 500 - var wg sync.WaitGroup - for i := 0; i < n; i++ { - wg.Add(1) - go func(i int) { - defer wg.Done() - for j := 0; j < m; j++ { - txnSet(t, db, []byte(fmt.Sprintf("k%05d_%08d", i, j)), - []byte(fmt.Sprintf("v%05d_%08d", i, j)), byte(j%127)) - } - }(i) - } - wg.Wait() - - t.Log("Starting iteration") - - opt := IteratorOptions{} - opt.Reverse = false - opt.PrefetchSize = 10 - opt.PrefetchValues = true - - txn := db.NewTransaction(true) - it := txn.NewIterator(opt) - defer it.Close() - var i, j int - for it.Rewind(); it.Valid(); it.Next() { - item := it.Item() - k := item.Key() - if k == nil { - break // end of iteration. - } - - require.EqualValues(t, fmt.Sprintf("k%05d_%08d", i, j), string(k)) - v := getItemValue(t, item) - require.EqualValues(t, fmt.Sprintf("v%05d_%08d", i, j), string(v)) - require.Equal(t, item.UserMeta(), byte(j%127)) - j++ - if j == m { - i++ - j = 0 - } - } - require.EqualValues(t, n, i) - require.EqualValues(t, 0, j) - }) -} - -func TestGet(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - txnSet(t, db, []byte("key1"), []byte("val1"), 0x08) - - txn := db.NewTransaction(false) - item, err := txn.Get([]byte("key1")) - require.NoError(t, err) - require.EqualValues(t, "val1", getItemValue(t, item)) - require.Equal(t, byte(0x08), item.UserMeta()) - txn.Discard() - - txnSet(t, db, []byte("key1"), []byte("val2"), 0x09) - - txn = db.NewTransaction(false) - item, err = txn.Get([]byte("key1")) - require.NoError(t, err) - require.EqualValues(t, "val2", getItemValue(t, item)) - require.Equal(t, byte(0x09), item.UserMeta()) - txn.Discard() - - txnDelete(t, db, []byte("key1")) - - txn = db.NewTransaction(false) - _, err = txn.Get([]byte("key1")) - require.Equal(t, ErrKeyNotFound, err) - txn.Discard() - - txnSet(t, db, []byte("key1"), []byte("val3"), 0x01) - - txn = db.NewTransaction(false) - item, err = txn.Get([]byte("key1")) - require.NoError(t, err) - require.EqualValues(t, "val3", getItemValue(t, item)) - require.Equal(t, byte(0x01), item.UserMeta()) - - longVal := make([]byte, 1000) - txnSet(t, db, []byte("key1"), longVal, 0x00) - - txn = db.NewTransaction(false) - item, err = txn.Get([]byte("key1")) - require.NoError(t, err) - require.EqualValues(t, longVal, getItemValue(t, item)) - txn.Discard() - }) -} - -func TestGetAfterDelete(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - // populate with one entry - key := []byte("key") - txnSet(t, db, key, []byte("val1"), 0x00) - require.NoError(t, db.Update(func(txn *Txn) error { - err := txn.Delete(key) - require.NoError(t, err) - - _, err = txn.Get(key) - require.Equal(t, ErrKeyNotFound, err) - return nil - })) - }) -} - -func TestTxnTooBig(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - data := func(i int) []byte { - return []byte(fmt.Sprintf("%b", i)) - } - // n := 500000 - n := 1000 - txn := db.NewTransaction(true) - for i := 0; i < n; { - if err := txn.Set(data(i), data(i)); err != nil { - require.NoError(t, txn.Commit()) - txn = db.NewTransaction(true) - } else { - i++ - } - } - require.NoError(t, txn.Commit()) - - txn = db.NewTransaction(true) - for i := 0; i < n; { - if err := txn.Delete(data(i)); err != nil { - require.NoError(t, txn.Commit()) - txn = db.NewTransaction(true) - } else { - i++ - } - } - require.NoError(t, txn.Commit()) - }) -} - -func TestForceCompactL0(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - - opts := getTestOptions(dir) - opts.ValueLogFileSize = 15 << 20 - opts.managedTxns = true - db, err := Open(opts) - require.NoError(t, err) - - data := func(i int) []byte { - return []byte(fmt.Sprintf("%b", i)) - } - n := 80 - m := 45 // Increasing would cause ErrTxnTooBig - sz := 32 << 10 - v := make([]byte, sz) - for i := 0; i < n; i += 2 { - version := uint64(i) - txn := db.NewTransactionAt(version, true) - for j := 0; j < m; j++ { - require.NoError(t, txn.Set(data(j), v)) - } - require.NoError(t, txn.CommitAt(version+1, nil)) - } - db.Close() - - opts.managedTxns = true - db, err = Open(opts) - require.NoError(t, err) - require.Equal(t, len(db.lc.levels[0].tables), 0) - require.NoError(t, db.Close()) -} - -// Put a lot of data to move some data to disk. -// WARNING: This test might take a while but it should pass! -func TestGetMore(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - - data := func(i int) []byte { - return []byte(fmt.Sprintf("%b", i)) - } - // n := 500000 - n := 10000 - m := 45 // Increasing would cause ErrTxnTooBig - for i := 0; i < n; i += m { - txn := db.NewTransaction(true) - for j := i; j < i+m && j < n; j++ { - require.NoError(t, txn.Set(data(j), data(j))) - } - require.NoError(t, txn.Commit()) - } - require.NoError(t, db.validate()) - - for i := 0; i < n; i++ { - txn := db.NewTransaction(false) - item, err := txn.Get(data(i)) - if err != nil { - t.Error(err) - } - require.EqualValues(t, string(data(i)), string(getItemValue(t, item))) - txn.Discard() - } - - // Overwrite - for i := 0; i < n; i += m { - txn := db.NewTransaction(true) - for j := i; j < i+m && j < n; j++ { - require.NoError(t, txn.Set(data(j), - // Use a long value that will certainly exceed value threshold. - []byte(fmt.Sprintf("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz%9d", j)))) - } - require.NoError(t, txn.Commit()) - } - require.NoError(t, db.validate()) - - for i := 0; i < n; i++ { - expectedValue := fmt.Sprintf("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz%9d", i) - k := data(i) - txn := db.NewTransaction(false) - item, err := txn.Get(k) - if err != nil { - t.Error(err) - } - got := string(getItemValue(t, item)) - if expectedValue != got { - - vs, err := db.get(y.KeyWithTs(k, math.MaxUint64)) - require.NoError(t, err) - fmt.Printf("wanted=%q Item: %s\n", k, item) - fmt.Printf("on re-run, got version: %+v\n", vs) - - txn := db.NewTransaction(false) - itr := txn.NewIterator(DefaultIteratorOptions) - for itr.Seek(k); itr.Valid(); itr.Next() { - item := itr.Item() - fmt.Printf("item=%s\n", item) - if !bytes.Equal(item.Key(), k) { - break - } - } - itr.Close() - txn.Discard() - } - require.EqualValues(t, expectedValue, string(getItemValue(t, item)), "wanted=%q Item: %s\n", k, item) - txn.Discard() - } - - // "Delete" key. - for i := 0; i < n; i += m { - if (i % 10000) == 0 { - fmt.Printf("Deleting i=%d\n", i) - } - txn := db.NewTransaction(true) - for j := i; j < i+m && j < n; j++ { - require.NoError(t, txn.Delete(data(j))) - } - require.NoError(t, txn.Commit()) - } - db.validate() - for i := 0; i < n; i++ { - if (i % 10000) == 0 { - // Display some progress. Right now, it's not very fast with no caching. - fmt.Printf("Testing i=%d\n", i) - } - k := data(i) - txn := db.NewTransaction(false) - _, err := txn.Get([]byte(k)) - require.Equal(t, ErrKeyNotFound, err, "should not have found k: %q", k) - txn.Discard() - } - }) -} - -// Put a lot of data to move some data to disk. -// WARNING: This test might take a while but it should pass! -func TestExistsMore(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - // n := 500000 - n := 10000 - m := 45 - for i := 0; i < n; i += m { - if (i % 1000) == 0 { - t.Logf("Putting i=%d\n", i) - } - txn := db.NewTransaction(true) - for j := i; j < i+m && j < n; j++ { - require.NoError(t, txn.Set([]byte(fmt.Sprintf("%09d", j)), - []byte(fmt.Sprintf("%09d", j)))) - } - require.NoError(t, txn.Commit()) - } - db.validate() - - for i := 0; i < n; i++ { - if (i % 1000) == 0 { - fmt.Printf("Testing i=%d\n", i) - } - k := fmt.Sprintf("%09d", i) - require.NoError(t, db.View(func(txn *Txn) error { - _, err := txn.Get([]byte(k)) - require.NoError(t, err) - return nil - })) - } - require.NoError(t, db.View(func(txn *Txn) error { - _, err := txn.Get([]byte("non-exists")) - require.Error(t, err) - return nil - })) - - // "Delete" key. - for i := 0; i < n; i += m { - if (i % 1000) == 0 { - fmt.Printf("Deleting i=%d\n", i) - } - txn := db.NewTransaction(true) - for j := i; j < i+m && j < n; j++ { - require.NoError(t, txn.Delete([]byte(fmt.Sprintf("%09d", j)))) - } - require.NoError(t, txn.Commit()) - } - db.validate() - for i := 0; i < n; i++ { - if (i % 10000) == 0 { - // Display some progress. Right now, it's not very fast with no caching. - fmt.Printf("Testing i=%d\n", i) - } - k := fmt.Sprintf("%09d", i) - - require.NoError(t, db.View(func(txn *Txn) error { - _, err := txn.Get([]byte(k)) - require.Error(t, err) - return nil - })) - } - fmt.Println("Done and closing") - }) -} - -func TestIterate2Basic(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - - bkey := func(i int) []byte { - return []byte(fmt.Sprintf("%09d", i)) - } - bval := func(i int) []byte { - return []byte(fmt.Sprintf("%025d", i)) - } - - // n := 500000 - n := 10000 - for i := 0; i < n; i++ { - if (i % 1000) == 0 { - t.Logf("Put i=%d\n", i) - } - txnSet(t, db, bkey(i), bval(i), byte(i%127)) - } - - opt := IteratorOptions{} - opt.PrefetchValues = true - opt.PrefetchSize = 10 - - txn := db.NewTransaction(false) - it := txn.NewIterator(opt) - { - var count int - rewind := true - t.Log("Starting first basic iteration") - for it.Rewind(); it.Valid(); it.Next() { - item := it.Item() - key := item.Key() - if rewind && count == 5000 { - // Rewind would skip /head/ key, and it.Next() would skip 0. - count = 1 - it.Rewind() - t.Log("Rewinding from 5000 to zero.") - rewind = false - continue - } - require.EqualValues(t, bkey(count), string(key)) - val := getItemValue(t, item) - require.EqualValues(t, bval(count), string(val)) - require.Equal(t, byte(count%127), item.UserMeta()) - count++ - } - require.EqualValues(t, n, count) - } - - { - t.Log("Starting second basic iteration") - idx := 5030 - for it.Seek(bkey(idx)); it.Valid(); it.Next() { - item := it.Item() - require.EqualValues(t, bkey(idx), string(item.Key())) - require.EqualValues(t, bval(idx), string(getItemValue(t, item))) - idx++ - } - } - it.Close() - }) -} - -func TestLoad(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - n := 10000 - { - kv, err := Open(getTestOptions(dir)) - require.NoError(t, err) - for i := 0; i < n; i++ { - if (i % 10000) == 0 { - fmt.Printf("Putting i=%d\n", i) - } - k := []byte(fmt.Sprintf("%09d", i)) - txnSet(t, kv, k, k, 0x00) - } - kv.Close() - } - - kv, err := Open(getTestOptions(dir)) - require.NoError(t, err) - require.Equal(t, uint64(10001), kv.orc.readTs()) - - for i := 0; i < n; i++ { - if (i % 10000) == 0 { - fmt.Printf("Testing i=%d\n", i) - } - k := fmt.Sprintf("%09d", i) - require.NoError(t, kv.View(func(txn *Txn) error { - item, err := txn.Get([]byte(k)) - require.NoError(t, err) - require.EqualValues(t, k, string(getItemValue(t, item))) - return nil - })) - } - kv.Close() - summary := kv.lc.getSummary() - - // Check that files are garbage collected. - idMap := getIDMap(dir) - for fileID := range idMap { - // Check that name is in summary.filenames. - require.True(t, summary.fileIDs[fileID], "%d", fileID) - } - require.EqualValues(t, len(idMap), len(summary.fileIDs)) - - var fileIDs []uint64 - for k := range summary.fileIDs { // Map to array. - fileIDs = append(fileIDs, k) - } - sort.Slice(fileIDs, func(i, j int) bool { return fileIDs[i] < fileIDs[j] }) - fmt.Printf("FileIDs: %v\n", fileIDs) -} - -func TestIterateDeleted(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - txnSet(t, db, []byte("Key1"), []byte("Value1"), 0x00) - txnSet(t, db, []byte("Key2"), []byte("Value2"), 0x00) - - iterOpt := DefaultIteratorOptions - iterOpt.PrefetchValues = false - txn := db.NewTransaction(false) - idxIt := txn.NewIterator(iterOpt) - defer idxIt.Close() - - count := 0 - txn2 := db.NewTransaction(true) - prefix := []byte("Key") - for idxIt.Seek(prefix); idxIt.ValidForPrefix(prefix); idxIt.Next() { - key := idxIt.Item().Key() - count++ - newKey := make([]byte, len(key)) - copy(newKey, key) - require.NoError(t, txn2.Delete(newKey)) - } - require.Equal(t, 2, count) - require.NoError(t, txn2.Commit()) - - for _, prefetch := range [...]bool{true, false} { - t.Run(fmt.Sprintf("Prefetch=%t", prefetch), func(t *testing.T) { - txn := db.NewTransaction(false) - iterOpt = DefaultIteratorOptions - iterOpt.PrefetchValues = prefetch - idxIt = txn.NewIterator(iterOpt) - - var estSize int64 - var idxKeys []string - for idxIt.Seek(prefix); idxIt.Valid(); idxIt.Next() { - item := idxIt.Item() - key := item.Key() - estSize += item.EstimatedSize() - if !bytes.HasPrefix(key, prefix) { - break - } - idxKeys = append(idxKeys, string(key)) - t.Logf("%+v\n", idxIt.Item()) - } - require.Equal(t, 0, len(idxKeys)) - require.Equal(t, int64(0), estSize) - }) - } - }) -} - -func TestIterateParallel(t *testing.T) { - key := func(account int) []byte { - var b [4]byte - binary.BigEndian.PutUint32(b[:], uint32(account)) - return append([]byte("account-"), b[:]...) - } - - N := 100000 - iterate := func(txn *Txn, wg *sync.WaitGroup) { - defer wg.Done() - itr := txn.NewIterator(DefaultIteratorOptions) - defer itr.Close() - - var count int - for itr.Rewind(); itr.Valid(); itr.Next() { - count++ - item := itr.Item() - require.Equal(t, "account-", string(item.Key()[0:8])) - err := item.Value(func(val []byte) error { - require.Equal(t, "1000", string(val)) - return nil - }) - require.NoError(t, err) - } - require.Equal(t, N, count) - itr.Close() // Double close. - } - - opt := DefaultOptions - runBadgerTest(t, &opt, func(t *testing.T, db *DB) { - var wg sync.WaitGroup - var txns []*Txn - for i := 0; i < N; i++ { - wg.Add(1) - txn := db.NewTransaction(true) - require.NoError(t, txn.Set(key(i), []byte("1000"))) - txns = append(txns, txn) - } - for _, txn := range txns { - txn.CommitWith(func(err error) { - y.Check(err) - wg.Done() - }) - } - - wg.Wait() - - // Check that a RW txn can't run multiple iterators. - txn := db.NewTransaction(true) - itr := txn.NewIterator(DefaultIteratorOptions) - require.Panics(t, func() { - txn.NewIterator(DefaultIteratorOptions) - }) - require.Panics(t, txn.Discard) - itr.Close() - txn.Discard() - - // Run multiple iterators for a RO txn. - txn = db.NewTransaction(false) - defer txn.Discard() - wg.Add(3) - go iterate(txn, &wg) - go iterate(txn, &wg) - go iterate(txn, &wg) - wg.Wait() - }) -} - -func TestDeleteWithoutSyncWrite(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - opt := DefaultOptions - opt.Dir = dir - opt.ValueDir = dir - kv, err := Open(opt) - if err != nil { - t.Error(err) - t.Fail() - } - - key := []byte("k1") - // Set a value with size > value threshold so that its written to value log. - txnSet(t, kv, key, []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789FOOBARZOGZOG"), 0x00) - txnDelete(t, kv, key) - kv.Close() - - // Reopen KV - kv, err = Open(opt) - if err != nil { - t.Error(err) - t.Fail() - } - defer kv.Close() - - require.NoError(t, kv.View(func(txn *Txn) error { - _, err := txn.Get(key) - require.Equal(t, ErrKeyNotFound, err) - return nil - })) -} - -func TestPidFile(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - // Reopen database - _, err := Open(getTestOptions(db.opt.Dir)) - require.Error(t, err) - require.Contains(t, err.Error(), "Another process is using this Badger database") - }) -} - -func TestInvalidKey(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - err := db.Update(func(txn *Txn) error { - err := txn.Set([]byte("!badger!head"), nil) - require.Equal(t, ErrInvalidKey, err) - - err = txn.Set([]byte("!badger!"), nil) - require.Equal(t, ErrInvalidKey, err) - - err = txn.Set([]byte("!badger"), []byte("BadgerDB")) - require.NoError(t, err) - return err - }) - require.NoError(t, err) - - require.NoError(t, db.View(func(txn *Txn) error { - item, err := txn.Get([]byte("!badger")) - if err != nil { - return err - } - require.NoError(t, item.Value(func(val []byte) error { - require.Equal(t, []byte("BadgerDB"), val) - return nil - })) - return nil - })) - }) -} - -func TestIteratorPrefetchSize(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - - bkey := func(i int) []byte { - return []byte(fmt.Sprintf("%09d", i)) - } - bval := func(i int) []byte { - return []byte(fmt.Sprintf("%025d", i)) - } - - n := 100 - for i := 0; i < n; i++ { - // if (i % 10) == 0 { - // t.Logf("Put i=%d\n", i) - // } - txnSet(t, db, bkey(i), bval(i), byte(i%127)) - } - - getIteratorCount := func(prefetchSize int) int { - opt := IteratorOptions{} - opt.PrefetchValues = true - opt.PrefetchSize = prefetchSize - - var count int - txn := db.NewTransaction(false) - it := txn.NewIterator(opt) - { - t.Log("Starting first basic iteration") - for it.Rewind(); it.Valid(); it.Next() { - count++ - } - require.EqualValues(t, n, count) - } - return count - } - - var sizes = []int{-10, 0, 1, 10} - for _, size := range sizes { - c := getIteratorCount(size) - require.Equal(t, 100, c) - } - }) -} - -func TestSetIfAbsentAsync(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - kv, _ := Open(getTestOptions(dir)) - - bkey := func(i int) []byte { - return []byte(fmt.Sprintf("%09d", i)) - } - - f := func(err error) {} - - n := 1000 - for i := 0; i < n; i++ { - // if (i % 10) == 0 { - // t.Logf("Put i=%d\n", i) - // } - txn := kv.NewTransaction(true) - _, err = txn.Get(bkey(i)) - require.Equal(t, ErrKeyNotFound, err) - require.NoError(t, txn.SetWithMeta(bkey(i), nil, byte(i%127))) - txn.CommitWith(f) - } - - require.NoError(t, kv.Close()) - kv, err = Open(getTestOptions(dir)) - require.NoError(t, err) - - opt := DefaultIteratorOptions - txn := kv.NewTransaction(false) - var count int - it := txn.NewIterator(opt) - { - t.Log("Starting first basic iteration") - for it.Rewind(); it.Valid(); it.Next() { - count++ - } - require.EqualValues(t, n, count) - } - require.Equal(t, n, count) - require.NoError(t, kv.Close()) -} - -func TestGetSetRace(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - - data := make([]byte, 4096) - _, err := rand.Read(data) - require.NoError(t, err) - - var ( - numOp = 100 - wg sync.WaitGroup - keyCh = make(chan string) - ) - - // writer - wg.Add(1) - go func() { - defer func() { - wg.Done() - close(keyCh) - }() - - for i := 0; i < numOp; i++ { - key := fmt.Sprintf("%d", i) - txnSet(t, db, []byte(key), data, 0x00) - keyCh <- key - } - }() - - // reader - wg.Add(1) - go func() { - defer wg.Done() - - for key := range keyCh { - require.NoError(t, db.View(func(txn *Txn) error { - item, err := txn.Get([]byte(key)) - require.NoError(t, err) - err = item.Value(nil) - require.NoError(t, err) - return nil - })) - } - }() - - wg.Wait() - }) -} - -func TestDiscardVersionsBelow(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - // Write 4 versions of the same key - for i := 0; i < 4; i++ { - err := db.Update(func(txn *Txn) error { - return txn.Set([]byte("answer"), []byte(fmt.Sprintf("%d", i))) - }) - require.NoError(t, err) - } - - opts := DefaultIteratorOptions - opts.AllVersions = true - opts.PrefetchValues = false - - // Verify that there are 4 versions, and record 3rd version (2nd from top in iteration) - db.View(func(txn *Txn) error { - it := txn.NewIterator(opts) - defer it.Close() - var count int - for it.Rewind(); it.Valid(); it.Next() { - count++ - item := it.Item() - require.Equal(t, []byte("answer"), item.Key()) - if item.DiscardEarlierVersions() { - break - } - } - require.Equal(t, 4, count) - return nil - }) - - // Set new version and discard older ones. - err := db.Update(func(txn *Txn) error { - return txn.SetWithDiscard([]byte("answer"), []byte("5"), 0) - }) - require.NoError(t, err) - - // Verify that there are only 2 versions left, and versions - // below ts have been deleted. - db.View(func(txn *Txn) error { - it := txn.NewIterator(opts) - defer it.Close() - var count int - for it.Rewind(); it.Valid(); it.Next() { - count++ - item := it.Item() - require.Equal(t, []byte("answer"), item.Key()) - if item.DiscardEarlierVersions() { - break - } - } - require.Equal(t, 1, count) - return nil - }) - }) -} - -func TestExpiry(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - // Write two keys, one with a TTL - err := db.Update(func(txn *Txn) error { - return txn.Set([]byte("answer1"), []byte("42")) - }) - require.NoError(t, err) - - err = db.Update(func(txn *Txn) error { - return txn.SetWithTTL([]byte("answer2"), []byte("43"), 1*time.Second) - }) - require.NoError(t, err) - - time.Sleep(2 * time.Second) - - // Verify that only unexpired key is found during iteration - err = db.View(func(txn *Txn) error { - _, err := txn.Get([]byte("answer1")) - require.NoError(t, err) - - _, err = txn.Get([]byte("answer2")) - require.Equal(t, ErrKeyNotFound, err) - return nil - }) - require.NoError(t, err) - - // Verify that only one key is found during iteration - opts := DefaultIteratorOptions - opts.PrefetchValues = false - err = db.View(func(txn *Txn) error { - it := txn.NewIterator(opts) - defer it.Close() - var count int - for it.Rewind(); it.Valid(); it.Next() { - count++ - item := it.Item() - require.Equal(t, []byte("answer1"), item.Key()) - } - require.Equal(t, 1, count) - return nil - }) - require.NoError(t, err) - }) -} - -func randBytes(n int) []byte { - recv := make([]byte, n) - in, err := rand.Read(recv) - if err != nil { - log.Fatal(err) - } - return recv[:in] -} - -var benchmarkData = []struct { - key, value []byte -}{ - {randBytes(100), nil}, - {randBytes(1000), []byte("foo")}, - {[]byte("foo"), randBytes(1000)}, - {[]byte(""), randBytes(1000)}, - {nil, randBytes(1000000)}, - {randBytes(100000), nil}, - {randBytes(1000000), nil}, -} - -func TestLargeKeys(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - - opts := new(Options) - *opts = DefaultOptions - opts.ValueLogFileSize = 1024 * 1024 * 1024 - opts.Dir = dir - opts.ValueDir = dir - - db, err := Open(*opts) - if err != nil { - t.Fatal(err) - } - for i := 0; i < 1000; i++ { - tx := db.NewTransaction(true) - for _, kv := range benchmarkData { - k := make([]byte, len(kv.key)) - copy(k, kv.key) - - v := make([]byte, len(kv.value)) - copy(v, kv.value) - if err := tx.Set(k, v); err != nil { - // Skip over this record. - } - } - if err := tx.Commit(); err != nil { - t.Fatalf("#%d: batchSet err: %v", i, err) - } - } -} - -func TestCreateDirs(t *testing.T) { - dir, err := ioutil.TempDir("", "parent") - require.NoError(t, err) - defer os.RemoveAll(dir) - - opts := DefaultOptions - dir = filepath.Join(dir, "badger") - opts.Dir = dir - opts.ValueDir = dir - db, err := Open(opts) - require.NoError(t, err) - db.Close() - _, err = os.Stat(dir) - require.NoError(t, err) -} - -func TestGetSetDeadlock(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - fmt.Println(dir) - require.NoError(t, err) - defer os.RemoveAll(dir) - - opt := DefaultOptions - opt.Dir = dir - opt.ValueDir = dir - opt.ValueLogFileSize = 1 << 20 - db, err := Open(opt) - require.NoError(t, err) - defer db.Close() - - val := make([]byte, 1<<19) - key := []byte("key1") - require.NoError(t, db.Update(func(txn *Txn) error { - rand.Read(val) - require.NoError(t, txn.Set(key, val)) - return nil - })) - - timeout, done := time.After(10*time.Second), make(chan bool) - - go func() { - db.Update(func(txn *Txn) error { - item, err := txn.Get(key) - require.NoError(t, err) - err = item.Value(nil) // This take a RLock on file - require.NoError(t, err) - - rand.Read(val) - require.NoError(t, txn.Set(key, val)) - require.NoError(t, txn.Set([]byte("key2"), val)) - return nil - }) - done <- true - }() - - select { - case <-timeout: - t.Fatal("db.Update did not finish within 10s, assuming deadlock.") - case <-done: - t.Log("db.Update finished.") - } -} - -func TestWriteDeadlock(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - fmt.Println(dir) - require.NoError(t, err) - defer os.RemoveAll(dir) - - opt := DefaultOptions - opt.Dir = dir - opt.ValueDir = dir - opt.ValueLogFileSize = 10 << 20 - db, err := Open(opt) - require.NoError(t, err) - - print := func(count *int) { - *count++ - if *count%100 == 0 { - fmt.Printf("%05d\r", *count) - } - } - - var count int - val := make([]byte, 10000) - require.NoError(t, db.Update(func(txn *Txn) error { - for i := 0; i < 1500; i++ { - key := fmt.Sprintf("%d", i) - rand.Read(val) - require.NoError(t, txn.Set([]byte(key), val)) - print(&count) - } - return nil - })) - - count = 0 - fmt.Println("\nWrites done. Iteration and updates starting...") - err = db.Update(func(txn *Txn) error { - opt := DefaultIteratorOptions - opt.PrefetchValues = false - it := txn.NewIterator(opt) - defer it.Close() - for it.Rewind(); it.Valid(); it.Next() { - item := it.Item() - - // Using Value() would cause deadlock. - // item.Value() - out, err := item.ValueCopy(nil) - require.NoError(t, err) - require.Equal(t, len(val), len(out)) - - key := y.Copy(item.Key()) - rand.Read(val) - require.NoError(t, txn.Set(key, val)) - print(&count) - } - return nil - }) - require.NoError(t, err) -} - -func TestSequence(t *testing.T) { - key0 := []byte("seq0") - key1 := []byte("seq1") - - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - seq0, err := db.GetSequence(key0, 10) - require.NoError(t, err) - seq1, err := db.GetSequence(key1, 100) - require.NoError(t, err) - - for i := uint64(0); i < uint64(105); i++ { - num, err := seq0.Next() - require.NoError(t, err) - require.Equal(t, i, num) - - num, err = seq1.Next() - require.NoError(t, err) - require.Equal(t, i, num) - } - err = db.View(func(txn *Txn) error { - item, err := txn.Get(key0) - if err != nil { - return err - } - var num0 uint64 - if err := item.Value(func(val []byte) error { - num0 = binary.BigEndian.Uint64(val) - return nil - }); err != nil { - return err - } - require.Equal(t, uint64(110), num0) - - item, err = txn.Get(key1) - if err != nil { - return err - } - var num1 uint64 - if err := item.Value(func(val []byte) error { - num1 = binary.BigEndian.Uint64(val) - return nil - }); err != nil { - return err - } - require.Equal(t, uint64(200), num1) - return nil - }) - require.NoError(t, err) - }) -} - -func TestSequence_Release(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - // get sequence, use once and release - key := []byte("key") - seq, err := db.GetSequence(key, 1000) - require.NoError(t, err) - num, err := seq.Next() - require.NoError(t, err) - require.Equal(t, uint64(0), num) - require.NoError(t, seq.Release()) - - // we used up 0 and 1 should be stored now - err = db.View(func(txn *Txn) error { - item, err := txn.Get(key) - if err != nil { - return err - } - val, err := item.ValueCopy(nil) - if err != nil { - return err - } - require.Equal(t, num+1, binary.BigEndian.Uint64(val)) - return nil - }) - require.NoError(t, err) - - // using it again will lease 1+1000 - num, err = seq.Next() - require.NoError(t, err) - require.Equal(t, uint64(1), num) - err = db.View(func(txn *Txn) error { - item, err := txn.Get(key) - if err != nil { - return err - } - val, err := item.ValueCopy(nil) - if err != nil { - return err - } - require.Equal(t, uint64(1001), binary.BigEndian.Uint64(val)) - return nil - }) - require.NoError(t, err) - }) -} - -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)) -} - -func TestMergeOperatorGetBeforeAdd(t *testing.T) { - key := []byte("merge") - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - m := db.GetMergeOperator(key, add, 200*time.Millisecond) - defer m.Stop() - - _, err := m.Get() - require.Equal(t, ErrKeyNotFound, err) - }) -} - -func TestMergeOperatorBeforeAdd(t *testing.T) { - key := []byte("merge") - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - m := db.GetMergeOperator(key, add, 200*time.Millisecond) - defer m.Stop() - time.Sleep(time.Second) - }) -} - -func TestMergeOperatorAddAndGet(t *testing.T) { - key := []byte("merge") - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - m := db.GetMergeOperator(key, add, 200*time.Millisecond) - defer m.Stop() - - err := m.Add(uint64ToBytes(1)) - require.NoError(t, err) - m.Add(uint64ToBytes(2)) - require.NoError(t, err) - m.Add(uint64ToBytes(3)) - require.NoError(t, err) - - res, err := m.Get() - require.NoError(t, err) - require.Equal(t, uint64(6), bytesToUint64(res)) - }) -} - -func TestMergeOperatorCompactBeforeGet(t *testing.T) { - key := []byte("merge") - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - m := db.GetMergeOperator(key, add, 200*time.Millisecond) - defer m.Stop() - - err := m.Add(uint64ToBytes(1)) - require.NoError(t, err) - m.Add(uint64ToBytes(2)) - require.NoError(t, err) - m.Add(uint64ToBytes(3)) - require.NoError(t, err) - - time.Sleep(250 * time.Millisecond) // wait for merge to happen - - res, err := m.Get() - require.NoError(t, err) - require.Equal(t, uint64(6), bytesToUint64(res)) - }) -} - -func TestMergeOperatorGetAfterStop(t *testing.T) { - key := []byte("merge") - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - m := db.GetMergeOperator(key, add, 1*time.Second) - - err := m.Add(uint64ToBytes(1)) - require.NoError(t, err) - m.Add(uint64ToBytes(2)) - require.NoError(t, err) - m.Add(uint64ToBytes(3)) - require.NoError(t, err) - - m.Stop() - res, err := m.Get() - require.NoError(t, err) - require.Equal(t, uint64(6), bytesToUint64(res)) - }) -} - -func TestReadOnly(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - opts := getTestOptions(dir) - - // Create the DB - db, err := Open(opts) - require.NoError(t, err) - for i := 0; i < 10000; i++ { - txnSet(t, db, []byte(fmt.Sprintf("key%d", i)), []byte(fmt.Sprintf("value%d", i)), 0x00) - } - - // Attempt a read-only open while it's open read-write. - opts.ReadOnly = true - _, err = Open(opts) - require.Error(t, err) - if err == ErrWindowsNotSupported { - return - } - require.Contains(t, err.Error(), "Another process is using this Badger database") - db.Close() - - // Open one read-only - opts.ReadOnly = true - kv1, err := Open(opts) - require.NoError(t, err) - defer kv1.Close() - - // Open another read-only - kv2, err := Open(opts) - require.NoError(t, err) - defer kv2.Close() - - // Attempt a read-write open while it's open for read-only - opts.ReadOnly = false - _, err = Open(opts) - require.Error(t, err) - require.Contains(t, err.Error(), "Another process is using this Badger database") - - // Get a thing from the DB - txn1 := kv1.NewTransaction(true) - v1, err := txn1.Get([]byte("key1")) - require.NoError(t, err) - b1, err := v1.ValueCopy(nil) - require.NoError(t, err) - require.Equal(t, b1, []byte("value1")) - err = txn1.Commit() - require.NoError(t, err) - - // Get a thing from the DB via the other connection - txn2 := kv2.NewTransaction(true) - v2, err := txn2.Get([]byte("key2000")) - require.NoError(t, err) - b2, err := v2.ValueCopy(nil) - require.NoError(t, err) - require.Equal(t, b2, []byte("value2000")) - err = txn2.Commit() - require.NoError(t, err) - - // Attempt to set a value on a read-only connection - txn := kv1.NewTransaction(true) - err = txn.SetWithMeta([]byte("key"), []byte("value"), 0x00) - require.Error(t, err) - require.Contains(t, err.Error(), "No sets or deletes are allowed in a read-only transaction") - err = txn.Commit() - require.NoError(t, err) -} - -func TestLSMOnly(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - - opts := LSMOnlyOptions - opts.Dir = dir - opts.ValueDir = dir - - dopts := DefaultOptions - require.NotEqual(t, dopts.ValueThreshold, opts.ValueThreshold) - - dopts.ValueThreshold = 1 << 16 - _, err = Open(dopts) - require.Equal(t, ErrValueThreshold, err) - - opts.ValueLogMaxEntries = 100 - db, err := Open(opts) - require.NoError(t, err) - if err != nil { - t.Fatal(err) - } - - value := make([]byte, 128) - _, err = rand.Read(value) - for i := 0; i < 500; i++ { - require.NoError(t, err) - txnSet(t, db, []byte(fmt.Sprintf("key%d", i)), value, 0x00) - } - require.NoError(t, db.Close()) // Close to force compactions, so Value log GC would run. - - db, err = Open(opts) - require.NoError(t, err) - if err != nil { - t.Fatal(err) - } - defer db.Close() - require.NoError(t, db.RunValueLogGC(0.2)) -} - -// This test function is doing some intricate sorcery. -func TestMinReadTs(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - for i := 0; i < 10; i++ { - require.NoError(t, db.Update(func(txn *Txn) error { - return txn.Set([]byte("x"), []byte("y")) - })) - } - time.Sleep(time.Millisecond) - - readTxn0 := db.NewTransaction(false) - require.Equal(t, uint64(10), readTxn0.readTs) - - min := db.orc.readMark.DoneUntil() - require.Equal(t, uint64(9), min) - - readTxn := db.NewTransaction(false) - for i := 0; i < 10; i++ { - require.NoError(t, db.Update(func(txn *Txn) error { - return txn.Set([]byte("x"), []byte("y")) - })) - } - require.Equal(t, uint64(20), db.orc.readTs()) - - time.Sleep(time.Millisecond) - require.Equal(t, min, db.orc.readMark.DoneUntil()) - - readTxn0.Discard() - readTxn.Discard() - time.Sleep(time.Millisecond) - require.Equal(t, uint64(19), db.orc.readMark.DoneUntil()) - db.orc.readMark.Done(uint64(20)) // Because we called readTs. - - for i := 0; i < 10; i++ { - db.View(func(txn *Txn) error { - return nil - }) - } - time.Sleep(time.Millisecond) - require.Equal(t, uint64(20), db.orc.readMark.DoneUntil()) - }) -} - -func TestGoroutineLeak(t *testing.T) { - before := runtime.NumGoroutine() - t.Logf("Num go: %d", before) - for i := 0; i < 12; i++ { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - err := db.Update(func(txn *Txn) error { - return txn.Set([]byte("key"), []byte("value")) - }) - require.NoError(t, err) - }) - } - require.Equal(t, before, runtime.NumGoroutine()) -} - -func ExampleOpen() { - dir, err := ioutil.TempDir("", "badger") - if err != nil { - log.Fatal(err) - } - defer os.RemoveAll(dir) - opts := DefaultOptions - opts.Dir = dir - opts.ValueDir = dir - db, err := Open(opts) - if err != nil { - log.Fatal(err) - } - defer db.Close() - - err = db.View(func(txn *Txn) error { - _, err := txn.Get([]byte("key")) - // We expect ErrKeyNotFound - fmt.Println(err) - return nil - }) - - if err != nil { - log.Fatal(err) - } - - txn := db.NewTransaction(true) // Read-write txn - err = txn.Set([]byte("key"), []byte("value")) - if err != nil { - log.Fatal(err) - } - err = txn.Commit() - if err != nil { - log.Fatal(err) - } - - err = db.View(func(txn *Txn) error { - item, err := txn.Get([]byte("key")) - if err != nil { - return err - } - val, err := item.ValueCopy(nil) - if err != nil { - return err - } - fmt.Printf("%s\n", string(val)) - return nil - }) - - if err != nil { - log.Fatal(err) - } - - // Output: - // Key not found - // value -} - -func ExampleTxn_NewIterator() { - dir, err := ioutil.TempDir("", "badger") - if err != nil { - log.Fatal(err) - } - defer os.RemoveAll(dir) - - opts := DefaultOptions - opts.Dir = dir - opts.ValueDir = dir - - db, err := Open(opts) - if err != nil { - log.Fatal(err) - } - defer db.Close() - - bkey := func(i int) []byte { - return []byte(fmt.Sprintf("%09d", i)) - } - bval := func(i int) []byte { - return []byte(fmt.Sprintf("%025d", i)) - } - - txn := db.NewTransaction(true) - - // Fill in 1000 items - n := 1000 - for i := 0; i < n; i++ { - err := txn.Set(bkey(i), bval(i)) - if err != nil { - log.Fatal(err) - } - } - - err = txn.Commit() - if err != nil { - log.Fatal(err) - } - - opt := DefaultIteratorOptions - opt.PrefetchSize = 10 - - // Iterate over 1000 items - var count int - err = db.View(func(txn *Txn) error { - it := txn.NewIterator(opt) - defer it.Close() - for it.Rewind(); it.Valid(); it.Next() { - count++ - } - return nil - }) - if err != nil { - log.Fatal(err) - } - fmt.Printf("Counted %d elements", count) - // Output: - // Counted 1000 elements -} - -func TestMain(m *testing.M) { - // call flag.Parse() here if TestMain uses flags - go func() { - if err := http.ListenAndServe("localhost:8080", nil); err != nil { - log.Fatalf("Unable to open http port at 8080") - } - }() - os.Exit(m.Run()) -} diff --git a/vendor/github.com/dgraph-io/badger/dir_unix.go b/vendor/github.com/dgraph-io/badger/dir_unix.go index 4f809b66..a5e0fa33 100644 --- a/vendor/github.com/dgraph-io/badger/dir_unix.go +++ b/vendor/github.com/dgraph-io/badger/dir_unix.go @@ -24,8 +24,8 @@ import ( "os" "path/filepath" - "gx/ipfs/QmVGjyM9i2msKvLXwh9VosCTgP4mL91kC7hDmqnwTTx6Hu/sys/unix" - "gx/ipfs/QmVmDhyTTUcQXFD1rRQ64fGLMSAoaQvNH3hwuaCFAPq2hy/errors" + "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 diff --git a/vendor/github.com/dgraph-io/badger/dir_windows.go b/vendor/github.com/dgraph-io/badger/dir_windows.go index 248bd27e..28ccb7aa 100644 --- a/vendor/github.com/dgraph-io/badger/dir_windows.go +++ b/vendor/github.com/dgraph-io/badger/dir_windows.go @@ -24,7 +24,7 @@ import ( "path/filepath" "syscall" - "gx/ipfs/QmVmDhyTTUcQXFD1rRQ64fGLMSAoaQvNH3hwuaCFAPq2hy/errors" + "github.com/pkg/errors" ) // FILE_ATTRIBUTE_TEMPORARY - A file that is being used for temporary storage. diff --git a/vendor/github.com/dgraph-io/badger/errors.go b/vendor/github.com/dgraph-io/badger/errors.go index 00836109..cad66cb1 100644 --- a/vendor/github.com/dgraph-io/badger/errors.go +++ b/vendor/github.com/dgraph-io/badger/errors.go @@ -17,7 +17,7 @@ package badger import ( - "gx/ipfs/QmVmDhyTTUcQXFD1rRQ64fGLMSAoaQvNH3hwuaCFAPq2hy/errors" + "github.com/pkg/errors" ) var ( @@ -102,4 +102,7 @@ var ( // 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") ) diff --git a/vendor/github.com/dgraph-io/badger/histogram.go b/vendor/github.com/dgraph-io/badger/histogram.go new file mode 100644 index 00000000..d8c94bb7 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/histogram.go @@ -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)< 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() +} diff --git a/vendor/github.com/dgraph-io/badger/images/benchmarks-rocksdb.png b/vendor/github.com/dgraph-io/badger/images/benchmarks-rocksdb.png deleted file mode 100644 index 27081e8122b857c6f0dade69c5e837c74229ba10..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66938 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 0 @@ -246,6 +246,12 @@ func (item *Item) EstimatedSize() int64 { 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 diff --git a/vendor/github.com/dgraph-io/badger/iterator_test.go b/vendor/github.com/dgraph-io/badger/iterator_test.go deleted file mode 100644 index 9404b075..00000000 --- a/vendor/github.com/dgraph-io/badger/iterator_test.go +++ /dev/null @@ -1,244 +0,0 @@ -/* - * 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 ( - "bytes" - "fmt" - "io/ioutil" - "math/rand" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/stretchr/testify/require" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/options" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" -) - -type tableMock struct { - left, right []byte -} - -func (tm *tableMock) Smallest() []byte { return tm.left } -func (tm *tableMock) Biggest() []byte { return tm.right } -func (tm *tableMock) DoesNotHave(key []byte) bool { return false } - -func TestPickTables(t *testing.T) { - opt := DefaultIteratorOptions - - within := func(prefix, left, right string) { - opt.Prefix = []byte(prefix) - tm := &tableMock{left: []byte(left), right: []byte(right)} - require.True(t, opt.pickTable(tm)) - } - outside := func(prefix, left, right string) { - opt.Prefix = []byte(prefix) - tm := &tableMock{left: []byte(left), right: []byte(right)} - require.False(t, opt.pickTable(tm)) - } - within("abc", "ab", "ad") - within("abc", "abc", "ad") - within("abc", "abb123", "ad") - within("abc", "abc123", "abd234") - within("abc", "abc123", "abc456") - - outside("abd", "abe", "ad") - outside("abd", "ac", "ad") - outside("abd", "b", "e") - outside("abd", "a", "ab") - outside("abd", "ab", "abc") - outside("abd", "ab", "abc123") -} - -func TestIteratePrefix(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - bkey := func(i int) []byte { - return []byte(fmt.Sprintf("%04d", i)) - } - val := []byte("OK") - n := 10000 - - batch := db.NewWriteBatch() - for i := 0; i < n; i++ { - if (i % 1000) == 0 { - t.Logf("Put i=%d\n", i) - } - require.NoError(t, batch.Set(bkey(i), val, 0)) - } - require.NoError(t, batch.Flush()) - - countKeys := func(prefix string) int { - t.Logf("Testing with prefix: %s", prefix) - var count int - opt := DefaultIteratorOptions - opt.Prefix = []byte(prefix) - err := db.View(func(txn *Txn) error { - itr := txn.NewIterator(opt) - defer itr.Close() - for itr.Rewind(); itr.Valid(); itr.Next() { - item := itr.Item() - err := item.Value(func(v []byte) error { - require.Equal(t, val, v) - return nil - }) - require.NoError(t, err) - require.True(t, bytes.HasPrefix(item.Key(), opt.Prefix)) - count++ - } - return nil - }) - require.NoError(t, err) - return count - } - - countOneKey := func(key []byte) int { - var count int - err := db.View(func(txn *Txn) error { - itr := txn.NewKeyIterator(key, DefaultIteratorOptions) - defer itr.Close() - for itr.Rewind(); itr.Valid(); itr.Next() { - item := itr.Item() - err := item.Value(func(v []byte) error { - require.Equal(t, val, v) - return nil - }) - require.NoError(t, err) - require.Equal(t, key, item.Key()) - count++ - } - return nil - }) - require.NoError(t, err) - return count - } - - for i := 0; i <= 9; i++ { - require.Equal(t, 1, countKeys(fmt.Sprintf("%d%d%d%d", i, i, i, i))) - require.Equal(t, 10, countKeys(fmt.Sprintf("%d%d%d", i, i, i))) - require.Equal(t, 100, countKeys(fmt.Sprintf("%d%d", i, i))) - require.Equal(t, 1000, countKeys(fmt.Sprintf("%d", i))) - } - require.Equal(t, 10000, countKeys("")) - - t.Logf("Testing each key with key iterator") - for i := 0; i < n; i++ { - require.Equal(t, 1, countOneKey(bkey(i))) - } - }) -} - -// go test -v -run=XXX -bench=BenchmarkIterate -benchtime=3s -// Benchmark with opt.Prefix set === -// goos: linux -// goarch: amd64 -// pkg: github.com/dgraph-io/badger -// BenchmarkIteratePrefixSingleKey/Key_lookups-4 10000 365539 ns/op -// --- BENCH: BenchmarkIteratePrefixSingleKey/Key_lookups-4 -// iterator_test.go:147: Inner b.N: 1 -// iterator_test.go:147: Inner b.N: 100 -// iterator_test.go:147: Inner b.N: 10000 -// --- BENCH: BenchmarkIteratePrefixSingleKey -// iterator_test.go:143: LSM files: 79 -// iterator_test.go:145: Outer b.N: 1 -// PASS -// ok github.com/dgraph-io/badger 41.586s -// -// Benchmark with NO opt.Prefix set === -// goos: linux -// goarch: amd64 -// pkg: github.com/dgraph-io/badger -// BenchmarkIteratePrefixSingleKey/Key_lookups-4 10000 460924 ns/op -// --- BENCH: BenchmarkIteratePrefixSingleKey/Key_lookups-4 -// iterator_test.go:147: Inner b.N: 1 -// iterator_test.go:147: Inner b.N: 100 -// iterator_test.go:147: Inner b.N: 10000 -// --- BENCH: BenchmarkIteratePrefixSingleKey -// iterator_test.go:143: LSM files: 83 -// iterator_test.go:145: Outer b.N: 1 -// PASS -// ok github.com/dgraph-io/badger 41.836s -// -// Only my laptop there's a 20% improvement in latency with ~80 files. -func BenchmarkIteratePrefixSingleKey(b *testing.B) { - dir, err := ioutil.TempDir(".", "badger-test") - y.Check(err) - defer os.RemoveAll(dir) - opts := getTestOptions(dir) - opts.TableLoadingMode = options.LoadToRAM - db, err := Open(opts) - y.Check(err) - defer db.Close() - - N := 100000 // Should generate around 80 SSTables. - val := []byte("OK") - bkey := func(i int) []byte { - return []byte(fmt.Sprintf("%06d", i)) - } - - batch := db.NewWriteBatch() - for i := 0; i < N; i++ { - y.Check(batch.Set(bkey(i), val, 0)) - } - y.Check(batch.Flush()) - var lsmFiles int - err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if strings.HasSuffix(path, ".sst") { - lsmFiles++ - } - if err != nil { - return err - } - return nil - }) - y.Check(err) - b.Logf("LSM files: %d", lsmFiles) - b.Logf("Key splits: %v", db.KeySplits(nil)) - b.Logf("Key splits with prefix: %v", db.KeySplits([]byte("09"))) - - b.Logf("Outer b.N: %d", b.N) - b.Run("Key lookups", func(b *testing.B) { - b.Logf("Inner b.N: %d", b.N) - for i := 0; i < b.N; i++ { - key := bkey(rand.Intn(N)) - err := db.View(func(txn *Txn) error { - opt := DefaultIteratorOptions - // NOTE: Comment opt.Prefix out here to compare the performance - // difference between providing Prefix as an option, v/s not. I - // see a 20% improvement when there are ~80 SSTables. - opt.Prefix = key - opt.AllVersions = true - - itr := txn.NewIterator(opt) - defer itr.Close() - - var count int - for itr.Seek(key); itr.ValidForPrefix(key); itr.Next() { - count++ - } - if count != 1 { - b.Fatalf("Count must be one key: %s. Found: %d", key, count) - } - return nil - }) - if err != nil { - b.Fatalf("Error while View: %v", err) - } - } - }) -} diff --git a/vendor/github.com/dgraph-io/badger/level_handler.go b/vendor/github.com/dgraph-io/badger/level_handler.go index a33124d8..147967fb 100644 --- a/vendor/github.com/dgraph-io/badger/level_handler.go +++ b/vendor/github.com/dgraph-io/badger/level_handler.go @@ -21,9 +21,9 @@ import ( "sort" "sync" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/table" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" - "gx/ipfs/QmVmDhyTTUcQXFD1rRQ64fGLMSAoaQvNH3hwuaCFAPq2hy/errors" + "github.com/dgraph-io/badger/table" + "github.com/dgraph-io/badger/y" + "github.com/pkg/errors" ) type levelHandler struct { @@ -102,48 +102,40 @@ func (s *levelHandler) deleteTables(toDel []*table.Table) error { // 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(newTables []*table.Table) error { +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.) - if len(newTables) == 0 { - return nil - } - 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 _, tbl := range newTables { - s.totalSize += tbl.Size() - tbl.IncrRef() + for _, t := range toAdd { + s.totalSize += t.Size() + t.IncrRef() + newTables = append(newTables, t) } - kr := keyRange{ - left: newTables[0].Smallest(), - right: newTables[len(newTables)-1].Biggest(), - } - left, right := s.overlappingTables(levelHandlerRLocked{}, kr) - - toDecr := make([]*table.Table, right-left) - // Update totalSize and reference counts. - for i := left; i < right; i++ { - tbl := s.tables[i] - s.totalSize -= tbl.Size() - toDecr[i-left] = tbl - } - - // To be safe, just make a copy. TODO: Be more careful and avoid copying. - numDeleted := right - left - numAdded := len(newTables) - tables := make([]*table.Table, len(s.tables)-numDeleted+numAdded) - y.AssertTrue(left == copy(tables, s.tables[:left])) - t := tables[left:] - y.AssertTrue(numAdded == copy(t, newTables)) - t = t[numAdded:] - y.AssertTrue(len(s.tables[right:]) == copy(t, s.tables[right:])) - s.tables = tables + // 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(toDecr) + return decrRefs(toDel) } func decrRefs(tables []*table.Table) error { @@ -294,6 +286,9 @@ type levelHandlerRLocked struct{} // 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 }) diff --git a/vendor/github.com/dgraph-io/badger/levels.go b/vendor/github.com/dgraph-io/badger/levels.go index 8b8ad3c7..df90164c 100644 --- a/vendor/github.com/dgraph-io/badger/levels.go +++ b/vendor/github.com/dgraph-io/badger/levels.go @@ -17,6 +17,7 @@ package badger import ( + "bytes" "fmt" "math" "math/rand" @@ -27,12 +28,12 @@ import ( "sync/atomic" "time" - "gx/ipfs/QmRvYNctevGUW52urgmoFZscT6buMKqhHezLUS64WepGWn/go-net/trace" + "golang.org/x/net/trace" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/pb" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/table" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" - "gx/ipfs/QmVmDhyTTUcQXFD1rRQ64fGLMSAoaQvNH3hwuaCFAPq2hy/errors" + "github.com/dgraph-io/badger/pb" + "github.com/dgraph-io/badger/table" + "github.com/dgraph-io/badger/y" + "github.com/pkg/errors" ) type levelsController struct { @@ -215,10 +216,10 @@ func (s *levelsController) cleanupLevels() error { return firstErr } -// This function picks all tables from all levels, creates a manifest changeset, +// dropTree picks all tables from all levels, creates a manifest changeset, // applies it, and then decrements the refs of these tables, which would result // in their deletion. -func (s *levelsController) deleteLSMTree() (int, error) { +func (s *levelsController) dropTree() (int, error) { // First pick all tables, so we can create a manifest changelog. var all []*table.Table for _, l := range s.levels { @@ -255,6 +256,72 @@ func (s *levelsController) deleteLSMTree() (int, error) { return len(all), nil } +// dropPrefix runs a L0->L1 compaction, and then runs same level compaction on the rest of the +// levels. For L0->L1 compaction, it runs compactions normally, but skips over all the keys with the +// provided prefix. For Li->Li compactions, it picks up the tables which would have the prefix. The +// tables who only have keys with this prefix are quickly dropped. The ones which have other keys +// are run through MergeIterator and compacted to create new tables. All the mechanisms of +// compactions apply, i.e. level sizes and MANIFEST are updated as in the normal flow. +func (s *levelsController) dropPrefix(prefix []byte) error { + opt := s.kv.opt + for _, l := range s.levels { + l.RLock() + if l.level == 0 { + size := len(l.tables) + l.RUnlock() + + if size > 0 { + cp := compactionPriority{ + level: 0, + score: 1.74, + // A unique number greater than 1.0 does two things. Helps identify this + // function in logs, and forces a compaction. + dropPrefix: prefix, + } + if err := s.doCompact(cp); err != nil { + opt.Warningf("While compacting level 0: %v", err) + return nil + } + } + continue + } + + var tables []*table.Table + for _, table := range l.tables { + var absent bool + switch { + case bytes.HasPrefix(table.Smallest(), prefix): + case bytes.HasPrefix(table.Biggest(), prefix): + case bytes.Compare(prefix, table.Smallest()) > 0 && + bytes.Compare(prefix, table.Biggest()) < 0: + default: + absent = true + } + if !absent { + tables = append(tables, table) + } + } + l.RUnlock() + if len(tables) == 0 { + continue + } + + cd := compactDef{ + elog: trace.New(fmt.Sprintf("Badger.L%d", l.level), "Compact"), + thisLevel: l, + nextLevel: l, + top: []*table.Table{}, + bot: tables, + dropPrefix: prefix, + } + if err := s.runCompactDef(l.level, cd); err != nil { + opt.Warningf("While running compact def: %+v. Error: %v", cd, err) + return err + } + } + return nil +} + func (s *levelsController) startCompact(lc *y.Closer) { n := s.kv.opt.NumCompactors lc.AddRunning(n - 1) @@ -311,8 +378,9 @@ func (l *levelHandler) isCompactable(delSize int64) bool { } type compactionPriority struct { - level int - score float64 + level int + score float64 + dropPrefix []byte } // pickCompactLevel determines which level to compact. @@ -350,7 +418,7 @@ func (s *levelsController) pickCompactLevels() (prios []compactionPriority) { // compactBuildTables merge topTables and botTables to form a list of new tables. func (s *levelsController) compactBuildTables( - l int, cd compactDef) ([]*table.Table, func() error, error) { + lev int, cd compactDef) ([]*table.Table, func() error, error) { topTables := cd.top botTables := cd.bot @@ -358,7 +426,7 @@ func (s *levelsController) compactBuildTables( { kr := getKeyRange(cd.top) for i, lh := range s.levels { - if i <= l { // Skip upper levels. + if i <= lev { // Skip upper levels. continue } lh.RLock() @@ -369,7 +437,6 @@ func (s *levelsController) compactBuildTables( break } } - cd.elog.LazyPrintf("Key range overlaps with lower levels: %v", hasOverlap) } // Try to collect stats so that we can inform value log about GC. That would help us find which @@ -385,15 +452,26 @@ func (s *levelsController) compactBuildTables( // Create iterators across all the tables involved first. var iters []y.Iterator - if l == 0 { + if lev == 0 { iters = appendIteratorsReversed(iters, topTables, false) - } else { + } else if len(topTables) > 0 { y.AssertTrue(len(topTables) == 1) iters = []y.Iterator{topTables[0].NewIterator(false)} } // Next level has level>=1 and we can use ConcatIterator as key ranges do not overlap. - iters = append(iters, table.NewConcatIterator(botTables, false)) + var valid []*table.Table + for _, table := range botTables { + if len(cd.dropPrefix) > 0 && + bytes.HasPrefix(table.Smallest(), cd.dropPrefix) && + bytes.HasPrefix(table.Biggest(), cd.dropPrefix) { + // All the keys in this table have the dropPrefix. So, this table does not need to be + // in the iterator and can be dropped immediately. + continue + } + valid = append(valid, table) + } + iters = append(iters, table.NewConcatIterator(valid, false)) it := y.NewMergeIterator(iters, false) defer it.Close() // Important to close the iterator to do ref counting. @@ -417,6 +495,13 @@ func (s *levelsController) compactBuildTables( builder := table.NewTableBuilder() var numKeys, numSkips uint64 for ; it.Valid(); it.Next() { + // See if we need to skip the prefix. + if len(cd.dropPrefix) > 0 && bytes.HasPrefix(it.Key(), cd.dropPrefix) { + numSkips++ + updateStats(it.Value()) + continue + } + // See if we need to skip this key. if len(skipKey) > 0 { if y.SameKey(it.Key(), skipKey) { @@ -441,7 +526,8 @@ func (s *levelsController) compactBuildTables( vs := it.Value() version := y.ParseTs(it.Key()) - if version <= discardTs { + // Do not discard entries inserted by merge operator. These entries will be discarded once they're merged + if version <= discardTs && vs.Meta&bitMergeEntry == 0 { // Keep track of the number of versions encountered for this key. Only consider the // versions which are below the minReadTs, otherwise, we might end up discarding the // only valid version for a running transaction. @@ -474,8 +560,8 @@ func (s *levelsController) compactBuildTables( } // It was true that it.Valid() at least once in the loop above, which means we // called Add() at least once, and builder is not Empty(). - cd.elog.LazyPrintf("Added %d keys. Skipped %d keys.", numKeys, numSkips) - cd.elog.LazyPrintf("LOG Compact. Iteration took: %v\n", time.Since(timeStart)) + s.kv.opt.Debugf("LOG Compact. Added %d keys. Skipped %d keys. Iteration took: %v", + numKeys, numSkips, time.Since(timeStart)) if !builder.Empty() { numBuilds++ fileID := s.reserveFileID() @@ -533,8 +619,8 @@ func (s *levelsController) compactBuildTables( sort.Slice(newTables, func(i, j int) bool { return y.CompareKeys(newTables[i].Biggest(), newTables[j].Biggest()) < 0 }) - s.kv.vlog.updateGCStats(discardStats) - cd.elog.LazyPrintf("Discard stats: %v", discardStats) + s.kv.vlog.updateDiscardStats(discardStats) + s.kv.opt.Debugf("Discard stats: %v", discardStats) return newTables, func() error { return decrRefs(newTables) }, nil } @@ -566,6 +652,8 @@ type compactDef struct { nextRange keyRange thisSize int64 + + dropPrefix []byte } func (cd *compactDef) lockLevels() { @@ -689,7 +777,7 @@ func (s *levelsController) runCompactDef(l int, cd compactDef) (err error) { // See comment earlier in this function about the ordering of these ops, and the order in which // we access levels when reading. - if err := nextLevel.replaceTables(newTables); err != nil { + if err := nextLevel.replaceTables(cd.bot, newTables); err != nil { return err } if err := thisLevel.deleteTables(cd.top); err != nil { @@ -699,8 +787,9 @@ func (s *levelsController) runCompactDef(l int, cd compactDef) (err error) { // Note: For level 0, while doCompact is running, it is possible that new tables are added. // However, the tables are added only to the end, so it is ok to just delete the first table. - cd.elog.LazyPrintf("LOG Compact %d->%d, del %d tables, add %d tables, took %v\n", - l, l+1, len(cd.top)+len(cd.bot), len(newTables), time.Since(timeStart)) + s.kv.opt.Infof("LOG Compact %d->%d, del %d tables, add %d tables, took %v\n", + thisLevel.level, nextLevel.level, len(cd.top)+len(cd.bot), + len(newTables), time.Since(timeStart)) return nil } @@ -712,41 +801,40 @@ func (s *levelsController) doCompact(p compactionPriority) error { y.AssertTrue(l+1 < s.kv.opt.MaxLevels) // Sanity check. cd := compactDef{ - elog: trace.New(fmt.Sprintf("Badger.L%d", l), "Compact"), - thisLevel: s.levels[l], - nextLevel: s.levels[l+1], + elog: trace.New(fmt.Sprintf("Badger.L%d", l), "Compact"), + thisLevel: s.levels[l], + nextLevel: s.levels[l+1], + dropPrefix: p.dropPrefix, } cd.elog.SetMaxEvents(100) defer cd.elog.Finish() - cd.elog.LazyPrintf("Got compaction priority: %+v", p) + s.kv.opt.Infof("Got compaction priority: %+v", p) // While picking tables to be compacted, both levels' tables are expected to // remain unchanged. if l == 0 { if !s.fillTablesL0(&cd) { - cd.elog.LazyPrintf("fillTables failed for level: %d\n", l) return errFillTables } } else { if !s.fillTables(&cd) { - cd.elog.LazyPrintf("fillTables failed for level: %d\n", l) return errFillTables } } defer s.cstatus.delete(cd) // Remove the ranges from compaction status. - cd.elog.LazyPrintf("Running for level: %d\n", cd.thisLevel.level) + s.kv.opt.Infof("Running for level: %d\n", cd.thisLevel.level) s.cstatus.toLog(cd.elog) if err := s.runCompactDef(l, cd); err != nil { // This compaction couldn't be done successfully. - cd.elog.LazyPrintf("\tLOG Compact FAILED with error: %+v: %+v", err, cd) + s.kv.opt.Warningf("LOG Compact FAILED with error: %+v: %+v", err, cd) return err } s.cstatus.toLog(cd.elog) - cd.elog.LazyPrintf("Compaction for level: %d DONE", cd.thisLevel.level) + s.kv.opt.Infof("Compaction for level: %d DONE", cd.thisLevel.level) return nil } @@ -858,23 +946,35 @@ func (s *levelsController) appendIterators( // TableInfo represents the information about a table. type TableInfo struct { - ID uint64 - Level int - Left []byte - Right []byte + ID uint64 + Level int + Left []byte + Right []byte + KeyCount uint64 // Number of keys in the table } -func (s *levelsController) getTableInfo() (result []TableInfo) { +func (s *levelsController) getTableInfo(withKeysCount bool) (result []TableInfo) { for _, l := range s.levels { + l.RLock() for _, t := range l.tables { + var count uint64 + if withKeysCount { + it := t.NewIterator(false) + for it.Rewind(); it.Valid(); it.Next() { + count++ + } + } + info := TableInfo{ - ID: t.ID(), - Level: l.level, - Left: t.Smallest(), - Right: t.Biggest(), + ID: t.ID(), + Level: l.level, + Left: t.Smallest(), + Right: t.Biggest(), + KeyCount: count, } result = append(result, info) } + l.RUnlock() } sort.Slice(result, func(i, j int) bool { if result[i].Level != result[j].Level { diff --git a/vendor/github.com/dgraph-io/badger/logger.go b/vendor/github.com/dgraph-io/badger/logger.go index c8e64c21..3a9b8a33 100644 --- a/vendor/github.com/dgraph-io/badger/logger.go +++ b/vendor/github.com/dgraph-io/badger/logger.go @@ -24,8 +24,9 @@ import ( // Logger is implemented by any logging system that is used for standard logs. type Logger interface { Errorf(string, ...interface{}) - Infof(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 @@ -53,6 +54,14 @@ func (opt *Options) Warningf(format string, v ...interface{}) { 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 } @@ -63,10 +72,14 @@ 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) Warningf(f string, v ...interface{}) { - l.Printf("WARNING: "+f, v...) +func (l *defaultLog) Debugf(f string, v ...interface{}) { + l.Printf("DEBUG: "+f, v...) } diff --git a/vendor/github.com/dgraph-io/badger/logger_test.go b/vendor/github.com/dgraph-io/badger/logger_test.go deleted file mode 100644 index 321c379a..00000000 --- a/vendor/github.com/dgraph-io/badger/logger_test.go +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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" - "testing" - - "github.com/stretchr/testify/require" -) - -type mockLogger struct { - output string -} - -func (l *mockLogger) Errorf(f string, v ...interface{}) { - l.output = fmt.Sprintf("ERROR: "+f, v...) -} - -func (l *mockLogger) Infof(f string, v ...interface{}) { - l.output = fmt.Sprintf("INFO: "+f, v...) -} - -func (l *mockLogger) Warningf(f string, v ...interface{}) { - l.output = fmt.Sprintf("WARNING: "+f, v...) -} - -// Test that the DB-specific log is used instead of the global log. -func TestDbLog(t *testing.T) { - l := &mockLogger{} - opt := Options{Logger: l} - - opt.Errorf("test") - require.Equal(t, "ERROR: test", l.output) - opt.Infof("test") - require.Equal(t, "INFO: test", l.output) - opt.Warningf("test") - require.Equal(t, "WARNING: test", l.output) -} - -// Test that the global logger is used when no logger is specified in Options. -func TestNoDbLog(t *testing.T) { - l := &mockLogger{} - opt := Options{} - opt.Logger = l - - opt.Errorf("test") - require.Equal(t, "ERROR: test", l.output) - opt.Infof("test") - require.Equal(t, "INFO: test", l.output) - opt.Warningf("test") - require.Equal(t, "WARNING: test", l.output) -} diff --git a/vendor/github.com/dgraph-io/badger/managed_db_test.go b/vendor/github.com/dgraph-io/badger/managed_db_test.go deleted file mode 100644 index dbb84c82..00000000 --- a/vendor/github.com/dgraph-io/badger/managed_db_test.go +++ /dev/null @@ -1,353 +0,0 @@ -package badger - -import ( - "io/ioutil" - "math" - "math/rand" - "os" - "runtime" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/stretchr/testify/require" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" -) - -func val(large bool) []byte { - var buf []byte - if large { - buf = make([]byte, 8192) - } else { - buf = make([]byte, 16) - } - rand.Read(buf) - return buf -} - -func numKeys(db *DB) int { - var count int - err := db.View(func(txn *Txn) error { - itr := txn.NewIterator(DefaultIteratorOptions) - defer itr.Close() - - for itr.Rewind(); itr.Valid(); itr.Next() { - count++ - } - return nil - }) - y.Check(err) - return count -} - -func numKeysManaged(db *DB, readTs uint64) int { - txn := db.NewTransactionAt(readTs, false) - defer txn.Discard() - - itr := txn.NewIterator(DefaultIteratorOptions) - defer itr.Close() - - var count int - for itr.Rewind(); itr.Valid(); itr.Next() { - count++ - } - return count -} - -func TestDropAllManaged(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - opts := getTestOptions(dir) - opts.managedTxns = true - opts.ValueLogFileSize = 5 << 20 - db, err := Open(opts) - require.NoError(t, err) - - N := uint64(10000) - populate := func(db *DB, start uint64) { - var wg sync.WaitGroup - for i := start; i < start+N; i++ { - wg.Add(1) - txn := db.NewTransactionAt(math.MaxUint64, true) - require.NoError(t, txn.Set([]byte(key("key", int(i))), val(true))) - require.NoError(t, txn.CommitAt(uint64(i), func(err error) { - require.NoError(t, err) - wg.Done() - })) - } - wg.Wait() - } - - populate(db, N) - require.Equal(t, int(N), numKeysManaged(db, math.MaxUint64)) - - require.NoError(t, db.DropAll()) - require.NoError(t, db.DropAll()) // Just call it twice, for fun. - require.Equal(t, 0, numKeysManaged(db, math.MaxUint64)) - - // Check that we can still write to mdb, and using lower timestamps. - populate(db, 1) - require.Equal(t, int(N), numKeysManaged(db, math.MaxUint64)) - db.Close() - - // Ensure that value log is correctly replayed, that we are preserving badgerHead. - opts.managedTxns = true - db2, err := Open(opts) - require.NoError(t, err) - require.Equal(t, int(N), numKeysManaged(db2, math.MaxUint64)) - db2.Close() -} - -func TestDropAll(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - opts := getTestOptions(dir) - opts.ValueLogFileSize = 5 << 20 - db, err := Open(opts) - require.NoError(t, err) - - N := uint64(10000) - populate := func(db *DB) { - writer := db.NewWriteBatch() - for i := uint64(0); i < N; i++ { - require.NoError(t, writer.Set([]byte(key("key", int(i))), val(true), 0)) - } - require.NoError(t, writer.Flush()) - } - - populate(db) - require.Equal(t, int(N), numKeys(db)) - - require.NoError(t, db.DropAll()) - require.Equal(t, 0, numKeys(db)) - - // Check that we can still write to mdb, and using lower timestamps. - populate(db) - require.Equal(t, int(N), numKeys(db)) - db.Close() - - // Ensure that value log is correctly replayed. - db2, err := Open(opts) - require.NoError(t, err) - require.Equal(t, int(N), numKeys(db2)) - db2.Close() -} - -func TestDropAllTwice(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - opts := getTestOptions(dir) - opts.ValueLogFileSize = 5 << 20 - db, err := Open(opts) - require.NoError(t, err) - - N := uint64(10000) - populate := func(db *DB) { - writer := db.NewWriteBatch() - for i := uint64(0); i < N; i++ { - require.NoError(t, writer.Set([]byte(key("key", int(i))), val(true), 0)) - } - require.NoError(t, writer.Flush()) - } - - populate(db) - require.Equal(t, int(N), numKeys(db)) - - require.NoError(t, db.DropAll()) - require.Equal(t, 0, numKeys(db)) - - // Call DropAll again. - require.NoError(t, db.DropAll()) -} - -func TestDropAllWithPendingTxn(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - opts := getTestOptions(dir) - opts.ValueLogFileSize = 5 << 20 - db, err := Open(opts) - require.NoError(t, err) - - N := uint64(10000) - populate := func(db *DB) { - writer := db.NewWriteBatch() - for i := uint64(0); i < N; i++ { - require.NoError(t, writer.Set([]byte(key("key", int(i))), val(true), 0)) - } - require.NoError(t, writer.Flush()) - } - - populate(db) - require.Equal(t, int(N), numKeys(db)) - - txn := db.NewTransaction(true) - - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - itr := txn.NewIterator(DefaultIteratorOptions) - defer itr.Close() - - var keys []string - for { - var count int - for itr.Rewind(); itr.Valid(); itr.Next() { - count++ - item := itr.Item() - keys = append(keys, string(item.KeyCopy(nil))) - _, err := item.ValueCopy(nil) - if err != nil { - t.Logf("Got error during value copy: %v", err) - return - } - } - t.Logf("Got number of keys: %d\n", count) - for _, key := range keys { - item, err := txn.Get([]byte(key)) - if err != nil { - t.Logf("Got error during key lookup: %v", err) - return - } - if _, err := item.ValueCopy(nil); err != nil { - t.Logf("Got error during second value copy: %v", err) - return - } - } - } - }() - // Do not cancel txn. - - go func() { - time.Sleep(2 * time.Second) - require.NoError(t, db.DropAll()) - }() - wg.Wait() -} - -func TestDropReadOnly(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - opts := getTestOptions(dir) - opts.ValueLogFileSize = 5 << 20 - db, err := Open(opts) - require.NoError(t, err) - N := uint64(1000) - populate := func(db *DB) { - writer := db.NewWriteBatch() - for i := uint64(0); i < N; i++ { - require.NoError(t, writer.Set([]byte(key("key", int(i))), val(true), 0)) - } - require.NoError(t, writer.Flush()) - } - - populate(db) - require.Equal(t, int(N), numKeys(db)) - require.NoError(t, db.Close()) - - opts.ReadOnly = true - db2, err := Open(opts) - // acquireDirectoryLock returns ErrWindowsNotSupported on Windows. It can be ignored safely. - if runtime.GOOS == "windows" { - require.Equal(t, err, ErrWindowsNotSupported) - } else { - require.NoError(t, err) - } - require.Panics(t, func() { db2.DropAll() }) -} - -func TestWriteAfterClose(t *testing.T) { - dir, err := ioutil.TempDir(".", "badger-test") - require.NoError(t, err) - defer os.RemoveAll(dir) - opts := getTestOptions(dir) - opts.ValueLogFileSize = 5 << 20 - db, err := Open(opts) - require.NoError(t, err) - N := uint64(1000) - populate := func(db *DB) { - writer := db.NewWriteBatch() - for i := uint64(0); i < N; i++ { - require.NoError(t, writer.Set([]byte(key("key", int(i))), val(true), 0)) - } - require.NoError(t, writer.Flush()) - } - - populate(db) - require.Equal(t, int(N), numKeys(db)) - require.NoError(t, db.Close()) - err = db.Update(func(txn *Txn) error { - return txn.Set([]byte("a"), []byte("b")) - }) - require.Equal(t, ErrBlockedWrites, err) -} - -func TestDropAllRace(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - opts := getTestOptions(dir) - opts.managedTxns = true - db, err := Open(opts) - require.NoError(t, err) - - N := 10000 - // Start a goroutine to keep trying to write to DB while DropAll happens. - closer := y.NewCloser(1) - go func() { - defer closer.Done() - ticker := time.NewTicker(time.Millisecond) - defer ticker.Stop() - - i := N + 1 // Writes would happen above N. - var errors int32 - for { - select { - case <-ticker.C: - i++ - txn := db.NewTransactionAt(math.MaxUint64, true) - require.NoError(t, txn.Set([]byte(key("key", i)), val(false))) - if err := txn.CommitAt(uint64(i), func(err error) { - if err != nil { - atomic.AddInt32(&errors, 1) - } - }); err != nil { - atomic.AddInt32(&errors, 1) - } - case <-closer.HasBeenClosed(): - // The following causes a data race. - // t.Logf("i: %d. Number of (expected) write errors: %d.\n", i, errors) - return - } - } - }() - - var wg sync.WaitGroup - for i := 1; i <= N; i++ { - wg.Add(1) - txn := db.NewTransactionAt(math.MaxUint64, true) - require.NoError(t, txn.Set([]byte(key("key", i)), val(false))) - require.NoError(t, txn.CommitAt(uint64(i), func(err error) { - require.NoError(t, err) - wg.Done() - })) - } - wg.Wait() - - before := numKeysManaged(db, math.MaxUint64) - require.True(t, before > N) - - require.NoError(t, db.DropAll()) - closer.SignalAndWait() - - after := numKeysManaged(db, math.MaxUint64) - t.Logf("Before: %d. After dropall: %d\n", before, after) - require.True(t, after < before) - db.Close() -} diff --git a/vendor/github.com/dgraph-io/badger/manifest.go b/vendor/github.com/dgraph-io/badger/manifest.go index 06a5b96b..34ce1217 100644 --- a/vendor/github.com/dgraph-io/badger/manifest.go +++ b/vendor/github.com/dgraph-io/badger/manifest.go @@ -27,9 +27,9 @@ import ( "path/filepath" "sync" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/pb" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" - "gx/ipfs/QmVmDhyTTUcQXFD1rRQ64fGLMSAoaQvNH3hwuaCFAPq2hy/errors" + "github.com/dgraph-io/badger/pb" + "github.com/dgraph-io/badger/y" + "github.com/pkg/errors" ) // Manifest represents the contents of the MANIFEST file in a Badger store. diff --git a/vendor/github.com/dgraph-io/badger/manifest_test.go b/vendor/github.com/dgraph-io/badger/manifest_test.go deleted file mode 100644 index 7bf55a68..00000000 --- a/vendor/github.com/dgraph-io/badger/manifest_test.go +++ /dev/null @@ -1,244 +0,0 @@ -/* - * 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" - "math/rand" - "os" - "path/filepath" - "sort" - "testing" - - "gx/ipfs/QmRvYNctevGUW52urgmoFZscT6buMKqhHezLUS64WepGWn/go-net/trace" - - "github.com/stretchr/testify/require" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/options" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/pb" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/table" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" -) - -func TestManifestBasic(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - - opt := getTestOptions(dir) - { - kv, err := Open(opt) - require.NoError(t, err) - n := 5000 - for i := 0; i < n; i++ { - if (i % 10000) == 0 { - fmt.Printf("Putting i=%d\n", i) - } - k := []byte(fmt.Sprintf("%16x", rand.Int63())) - txnSet(t, kv, k, k, 0x00) - } - txnSet(t, kv, []byte("testkey"), []byte("testval"), 0x05) - kv.validate() - require.NoError(t, kv.Close()) - } - - kv, err := Open(opt) - require.NoError(t, err) - - require.NoError(t, kv.View(func(txn *Txn) error { - item, err := txn.Get([]byte("testkey")) - require.NoError(t, err) - require.EqualValues(t, "testval", string(getItemValue(t, item))) - require.EqualValues(t, byte(0x05), item.UserMeta()) - return nil - })) - require.NoError(t, kv.Close()) -} - -func helpTestManifestFileCorruption(t *testing.T, off int64, errorContent string) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - - opt := getTestOptions(dir) - { - kv, err := Open(opt) - require.NoError(t, err) - require.NoError(t, kv.Close()) - } - fp, err := os.OpenFile(filepath.Join(dir, ManifestFilename), os.O_RDWR, 0) - require.NoError(t, err) - // Mess with magic value or version to force error - _, err = fp.WriteAt([]byte{'X'}, off) - require.NoError(t, err) - require.NoError(t, fp.Close()) - kv, err := Open(opt) - defer func() { - if kv != nil { - kv.Close() - } - }() - require.Error(t, err) - require.Contains(t, err.Error(), errorContent) -} - -func TestManifestMagic(t *testing.T) { - helpTestManifestFileCorruption(t, 3, "bad magic") -} - -func TestManifestVersion(t *testing.T) { - helpTestManifestFileCorruption(t, 4, "unsupported version") -} - -func key(prefix string, i int) string { - return prefix + fmt.Sprintf("%04d", i) -} - -func buildTestTable(t *testing.T, prefix string, n int) *os.File { - y.AssertTrue(n <= 10000) - keyValues := make([][]string, n) - for i := 0; i < n; i++ { - k := key(prefix, i) - v := fmt.Sprintf("%d", i) - keyValues[i] = []string{k, v} - } - return buildTable(t, keyValues) -} - -// TODO - Move these to somewhere where table package can also use it. -// keyValues is n by 2 where n is number of pairs. -func buildTable(t *testing.T, keyValues [][]string) *os.File { - b := table.NewTableBuilder() - defer b.Close() - // TODO: Add test for file garbage collection here. No files should be left after the tests here. - - filename := fmt.Sprintf("%s%s%d.sst", os.TempDir(), string(os.PathSeparator), rand.Int63()) - f, err := y.OpenSyncedFile(filename, true) - if t != nil { - require.NoError(t, err) - } else { - y.Check(err) - } - - sort.Slice(keyValues, func(i, j int) bool { - return keyValues[i][0] < keyValues[j][0] - }) - for _, kv := range keyValues { - y.AssertTrue(len(kv) == 2) - err := b.Add(y.KeyWithTs([]byte(kv[0]), 10), y.ValueStruct{ - Value: []byte(kv[1]), - Meta: 'A', - UserMeta: 0, - }) - if t != nil { - require.NoError(t, err) - } else { - y.Check(err) - } - } - f.Write(b.Finish()) - f.Close() - f, _ = y.OpenSyncedFile(filename, true) - return f -} - -func TestOverlappingKeyRangeError(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - opt := DefaultOptions - opt.Dir = dir - opt.ValueDir = dir - kv, err := Open(opt) - require.NoError(t, err) - - lh0 := newLevelHandler(kv, 0) - lh1 := newLevelHandler(kv, 1) - f := buildTestTable(t, "k", 2) - t1, err := table.OpenTable(f, options.MemoryMap, nil) - require.NoError(t, err) - defer t1.DecrRef() - - done := lh0.tryAddLevel0Table(t1) - require.Equal(t, true, done) - - cd := compactDef{ - thisLevel: lh0, - nextLevel: lh1, - elog: trace.New("Badger", "Compact"), - } - - manifest := createManifest() - lc, err := newLevelsController(kv, &manifest) - require.NoError(t, err) - done = lc.fillTablesL0(&cd) - require.Equal(t, true, done) - lc.runCompactDef(0, cd) - - f = buildTestTable(t, "l", 2) - t2, err := table.OpenTable(f, options.MemoryMap, nil) - require.NoError(t, err) - defer t2.DecrRef() - done = lh0.tryAddLevel0Table(t2) - require.Equal(t, true, done) - - cd = compactDef{ - thisLevel: lh0, - nextLevel: lh1, - elog: trace.New("Badger", "Compact"), - } - lc.fillTablesL0(&cd) - lc.runCompactDef(0, cd) -} - -func TestManifestRewrite(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - deletionsThreshold := 10 - mf, m, err := helpOpenOrCreateManifestFile(dir, false, deletionsThreshold) - defer func() { - if mf != nil { - mf.close() - } - }() - require.NoError(t, err) - require.Equal(t, 0, m.Creations) - require.Equal(t, 0, m.Deletions) - - err = mf.addChanges([]*pb.ManifestChange{ - newCreateChange(0, 0, nil), - }) - require.NoError(t, err) - - for i := uint64(0); i < uint64(deletionsThreshold*3); i++ { - ch := []*pb.ManifestChange{ - newCreateChange(i+1, 0, nil), - newDeleteChange(i), - } - err := mf.addChanges(ch) - require.NoError(t, err) - } - err = mf.close() - require.NoError(t, err) - mf = nil - mf, m, err = helpOpenOrCreateManifestFile(dir, false, deletionsThreshold) - require.NoError(t, err) - require.Equal(t, map[uint64]TableManifest{ - uint64(deletionsThreshold * 3): {Level: 0, Checksum: []byte{}}, - }, m.Tables) -} diff --git a/vendor/github.com/dgraph-io/badger/merge.go b/vendor/github.com/dgraph-io/badger/merge.go index af35e1f4..7bca447a 100644 --- a/vendor/github.com/dgraph-io/badger/merge.go +++ b/vendor/github.com/dgraph-io/badger/merge.go @@ -20,8 +20,8 @@ import ( "sync" "time" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" - "gx/ipfs/QmVmDhyTTUcQXFD1rRQ64fGLMSAoaQvNH3hwuaCFAPq2hy/errors" + "github.com/dgraph-io/badger/y" + "github.com/pkg/errors" ) // MergeOperator represents a Badger merge operator. @@ -63,11 +63,11 @@ var errNoMerge = errors.New("No need for merge") func (op *MergeOperator) iterateAndMerge(txn *Txn) (val []byte, err error) { opt := DefaultIteratorOptions opt.AllVersions = true - it := txn.NewIterator(opt) + it := txn.NewKeyIterator(op.key, opt) defer it.Close() var numVersions int - for it.Rewind(); it.ValidForPrefix(op.key); it.Next() { + for it.Rewind(); it.Valid(); it.Next() { item := it.Item() numVersions++ if numVersions == 1 { @@ -107,8 +107,8 @@ func (op *MergeOperator) compact() error { if err != nil { return err } - - // Write value back to db + // 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 txn.SetWithDiscard(op.key, val, 0) }) @@ -144,7 +144,7 @@ func (op *MergeOperator) runCompactions(dur time.Duration) { // 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.Set(op.key, val) + return txn.setMergeEntry(op.key, val) }) } diff --git a/vendor/github.com/dgraph-io/badger/options.go b/vendor/github.com/dgraph-io/badger/options.go index cdfddbe7..560b65b2 100644 --- a/vendor/github.com/dgraph-io/badger/options.go +++ b/vendor/github.com/dgraph-io/badger/options.go @@ -17,7 +17,7 @@ package badger import ( - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/options" + "github.com/dgraph-io/badger/options" ) // NOTE: Keep the comments in the following to 75 chars width, so they @@ -53,6 +53,18 @@ type Options struct { // How many versions to keep per key. NumVersionsToKeep int + // Open the DB as read-only. With this set, 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. + ReadOnly bool + + // Truncate value log to delete corrupt data, if any. Would not truncate if ReadOnly is set. + Truncate bool + + // DB-specific logger which will override the global logger. + Logger Logger + // 3. Flags that user might want to review // ---------------------------------------- // The following affect all levels of LSM tree. @@ -89,6 +101,13 @@ type Options struct { // efficient when the DB is opened later. CompactL0OnClose bool + // After this many number of value log file rotates, there would be a force flushing of memtable + // 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. + LogRotatesToFlush int32 + // 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. @@ -99,17 +118,6 @@ type Options struct { maxBatchCount int64 // max entries in batch maxBatchSize int64 // max batch size in bytes - // Open the DB as read-only. With this set, 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. - ReadOnly bool - - // Truncate value log to delete corrupt data, if any. Would not truncate if ReadOnly is set. - Truncate bool - - // DB-specific logger which will override the global logger. - Logger Logger } // DefaultOptions sets a list of recommended options for good performance. @@ -117,7 +125,7 @@ type Options struct { var DefaultOptions = Options{ LevelOneSize: 256 << 20, LevelSizeMultiplier: 10, - TableLoadingMode: options.LoadToRAM, + TableLoadingMode: options.MemoryMap, ValueLogLoadingMode: options.MemoryMap, // table.MemoryMap to mmap() the tables. // table.Nothing to not preload the tables. @@ -140,6 +148,7 @@ var DefaultOptions = Options{ ValueThreshold: 32, Truncate: false, Logger: defaultLogger, + LogRotatesToFlush: 2, } // LSMOnlyOptions follows from DefaultOptions, but sets a higher ValueThreshold diff --git a/vendor/github.com/dgraph-io/badger/package.json b/vendor/github.com/dgraph-io/badger/package.json deleted file mode 100644 index 86883fbb..00000000 --- a/vendor/github.com/dgraph-io/badger/package.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "author": "dgraph-io", - "bugs": { - "url": "https://github.com/dgraph-io/badger" - }, - "gx": { - "dvcsimport": "github.com/dgraph-io/badger" - }, - "gxDependencies": [ - { - "author": "whyrusleeping", - "hash": "QmVmDhyTTUcQXFD1rRQ64fGLMSAoaQvNH3hwuaCFAPq2hy", - "name": "errors", - "version": "0.0.1" - }, - { - "author": "magik6k", - "hash": "Qmbvv2urkn5Wtwws4yzjE85qRjB293EodchZofJsrTRuvN", - "name": "go-lz4", - "version": "1.0.0" - }, - { - "author": "kubuxu", - "hash": "QmWaLViWQF8jgyoLLqqcSrnp6dJpHESiJfzor1vrfDyTZf", - "name": "bbloom", - "version": "0.1.2" - }, - { - "author": "kubuxu", - "hash": "QmVGjyM9i2msKvLXwh9VosCTgP4mL91kC7hDmqnwTTx6Hu", - "name": "sys", - "version": "0.2.0" - }, - { - "author": "whyrusleeping", - "hash": "QmRvYNctevGUW52urgmoFZscT6buMKqhHezLUS64WepGWn", - "name": "go-net", - "version": "0.2.0" - }, - { - "author": "magik6k", - "hash": "QmRFFHk2jw9tgjxv12bCuuTnSbVXxEvYQkuNCLMEv9eUwP", - "name": "go-farm", - "version": "1.0.0" - }, - { - "author": "magik6k", - "hash": "QmQMxG9D52TirZd9eLA37nxiNspnMRkKbyPWrVAa1gvtSy", - "name": "go-humanize", - "version": "1.0.1" - }, - { - "author": "GoGo", - "hash": "QmddjPSGZb3ieihSseFeCfVRpZzcqczPNsD2DvarSwnjJB", - "name": "gogo-protobuf", - "version": "1.2.1" - }, - { - "author": "magik6k", - "hash": "QmXj63M2w2Pq7mnBpcrs7Va8prmfhvfMUNqVhJ9TgjiMbT", - "name": "cobra", - "version": "0.0.1" - } - ], - "gxVersion": "0.10.0", - "language": "go", - "license": "Apache 2.0", - "name": "badger", - "releaseCmd": "git commit -a -m \"gx publish $VERSION\"", - "version": "2.11.4" -} - diff --git a/vendor/github.com/dgraph-io/badger/pb/gen.sh b/vendor/github.com/dgraph-io/badger/pb/gen.sh old mode 100644 new mode 100755 index bb446f26..49b44ff4 --- a/vendor/github.com/dgraph-io/badger/pb/gen.sh +++ b/vendor/github.com/dgraph-io/badger/pb/gen.sh @@ -4,4 +4,4 @@ protos=${GOPATH-$HOME/go}/src/github.com/dgraph-io/badger/pb pushd $protos > /dev/null -protoc --gogofaster_out=. -I=. pb.proto +protoc --gofast_out=plugins=grpc:. -I=. pb.proto diff --git a/vendor/github.com/dgraph-io/badger/pb/pb.pb.go b/vendor/github.com/dgraph-io/badger/pb/pb.pb.go index 147c581c..f9a2c6ee 100644 --- a/vendor/github.com/dgraph-io/badger/pb/pb.pb.go +++ b/vendor/github.com/dgraph-io/badger/pb/pb.pb.go @@ -5,7 +5,7 @@ package pb import ( fmt "fmt" - proto "gx/ipfs/QmddjPSGZb3ieihSseFeCfVRpZzcqczPNsD2DvarSwnjJB/gogo-protobuf/proto" + proto "github.com/golang/protobuf/proto" io "io" math "math" ) @@ -19,7 +19,7 @@ var _ = math.Inf // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. -const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type ManifestChange_Operation int32 @@ -53,6 +53,11 @@ type KV struct { Version uint64 `protobuf:"varint,4,opt,name=version,proto3" json:"version,omitempty"` ExpiresAt uint64 `protobuf:"varint,5,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` Meta []byte `protobuf:"bytes,6,opt,name=meta,proto3" json:"meta,omitempty"` + // Stream id is used to identify which stream the KV came from. + StreamId uint32 `protobuf:"varint,10,opt,name=stream_id,json=streamId,proto3" json:"stream_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *KV) Reset() { *m = KV{} } @@ -130,8 +135,18 @@ func (m *KV) GetMeta() []byte { return nil } +func (m *KV) GetStreamId() uint32 { + if m != nil { + return m.StreamId + } + return 0 +} + type KVList struct { - Kv []*KV `protobuf:"bytes,1,rep,name=kv,proto3" json:"kv,omitempty"` + Kv []*KV `protobuf:"bytes,1,rep,name=kv,proto3" json:"kv,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *KVList) Reset() { *m = KVList{} } @@ -176,7 +191,10 @@ func (m *KVList) GetKv() []*KV { type ManifestChangeSet struct { // A set of changes that are applied atomically. - Changes []*ManifestChange `protobuf:"bytes,1,rep,name=changes,proto3" json:"changes,omitempty"` + Changes []*ManifestChange `protobuf:"bytes,1,rep,name=changes,proto3" json:"changes,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *ManifestChangeSet) Reset() { *m = ManifestChangeSet{} } @@ -220,10 +238,13 @@ func (m *ManifestChangeSet) GetChanges() []*ManifestChange { } type ManifestChange struct { - Id uint64 `protobuf:"varint,1,opt,name=Id,proto3" json:"Id,omitempty"` - Op ManifestChange_Operation `protobuf:"varint,2,opt,name=Op,proto3,enum=pb.ManifestChange_Operation" json:"Op,omitempty"` - Level uint32 `protobuf:"varint,3,opt,name=Level,proto3" json:"Level,omitempty"` - Checksum []byte `protobuf:"bytes,4,opt,name=Checksum,proto3" json:"Checksum,omitempty"` + Id uint64 `protobuf:"varint,1,opt,name=Id,proto3" json:"Id,omitempty"` + Op ManifestChange_Operation `protobuf:"varint,2,opt,name=Op,proto3,enum=pb.ManifestChange_Operation" json:"Op,omitempty"` + Level uint32 `protobuf:"varint,3,opt,name=Level,proto3" json:"Level,omitempty"` + Checksum []byte `protobuf:"bytes,4,opt,name=Checksum,proto3" json:"Checksum,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *ManifestChange) Reset() { *m = ManifestChange{} } @@ -298,29 +319,30 @@ func init() { func init() { proto.RegisterFile("pb.proto", fileDescriptor_f80abaa17e25ccc8) } var fileDescriptor_f80abaa17e25ccc8 = []byte{ - // 349 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x91, 0x4b, 0x4e, 0xc2, 0x40, - 0x18, 0xc7, 0x3b, 0x43, 0x29, 0xf0, 0x89, 0x04, 0x27, 0xc6, 0x4c, 0x7c, 0x34, 0x4d, 0xdd, 0xb0, - 0x20, 0x5d, 0xe0, 0x09, 0x10, 0xbb, 0x20, 0x40, 0x48, 0x46, 0xc2, 0x96, 0xb4, 0x30, 0x4a, 0x53, - 0x68, 0x27, 0xed, 0xd0, 0xe8, 0x2d, 0x5c, 0x79, 0x03, 0xef, 0xe2, 0x92, 0xa5, 0x4b, 0x03, 0x17, - 0x31, 0x1d, 0x1e, 0x09, 0x71, 0xf7, 0x7f, 0xcc, 0xf7, 0x5f, 0xfc, 0x06, 0xca, 0xc2, 0x77, 0x44, - 0x12, 0xcb, 0x98, 0x60, 0xe1, 0xdb, 0x9f, 0x08, 0x70, 0x6f, 0x4c, 0xea, 0x50, 0x08, 0xf9, 0x3b, - 0x45, 0x16, 0x6a, 0x54, 0x59, 0x2e, 0xc9, 0x25, 0x14, 0x33, 0x6f, 0xb1, 0xe2, 0x14, 0xab, 0x6c, - 0x67, 0xc8, 0x0d, 0x54, 0x56, 0x29, 0x4f, 0x26, 0x4b, 0x2e, 0x3d, 0x5a, 0x50, 0x4d, 0x39, 0x0f, - 0x06, 0x5c, 0x7a, 0x84, 0x42, 0x29, 0xe3, 0x49, 0x1a, 0xc4, 0x11, 0xd5, 0x2d, 0xd4, 0xd0, 0xd9, - 0xc1, 0x92, 0x3b, 0x00, 0xfe, 0x26, 0x82, 0x84, 0xa7, 0x13, 0x4f, 0xd2, 0xa2, 0x2a, 0x2b, 0xfb, - 0xa4, 0x2d, 0x09, 0x01, 0x5d, 0x0d, 0x1a, 0x6a, 0x50, 0x69, 0xdb, 0x02, 0xa3, 0x37, 0xee, 0x07, - 0xa9, 0x24, 0x57, 0x80, 0xc3, 0x8c, 0x22, 0xab, 0xd0, 0x38, 0x6b, 0x19, 0x8e, 0xf0, 0x9d, 0xde, - 0x98, 0xe1, 0x30, 0xb3, 0xdb, 0x70, 0x31, 0xf0, 0xa2, 0xe0, 0x85, 0xa7, 0xb2, 0x33, 0xf7, 0xa2, - 0x57, 0xfe, 0xcc, 0x25, 0x69, 0x42, 0x69, 0xaa, 0x4c, 0xba, 0xbf, 0x20, 0xf9, 0xc5, 0xe9, 0x3b, - 0x76, 0x78, 0x62, 0x7f, 0x21, 0xa8, 0x9d, 0x76, 0xa4, 0x06, 0xb8, 0x3b, 0x53, 0x20, 0x74, 0x86, - 0xbb, 0x33, 0xd2, 0x04, 0x3c, 0x14, 0x0a, 0x42, 0xad, 0x75, 0xfb, 0x7f, 0xcb, 0x19, 0x0a, 0x9e, - 0x78, 0x32, 0x88, 0x23, 0x86, 0x87, 0x22, 0xa7, 0xd6, 0xe7, 0x19, 0x5f, 0x28, 0x36, 0xe7, 0x6c, - 0x67, 0xc8, 0x35, 0x94, 0x3b, 0x73, 0x3e, 0x0d, 0xd3, 0xd5, 0x52, 0x91, 0xa9, 0xb2, 0xa3, 0xb7, - 0xef, 0xa1, 0x72, 0x9c, 0x20, 0x00, 0x46, 0x87, 0xb9, 0xed, 0x91, 0x5b, 0xd7, 0x72, 0xfd, 0xe4, - 0xf6, 0xdd, 0x91, 0x5b, 0x47, 0x8f, 0xf4, 0x7b, 0x63, 0xa2, 0xf5, 0xc6, 0x44, 0xbf, 0x1b, 0x13, - 0x7d, 0x6c, 0x4d, 0x6d, 0xbd, 0x35, 0xb5, 0x9f, 0xad, 0xa9, 0xf9, 0x86, 0xfa, 0xca, 0x87, 0xbf, - 0x00, 0x00, 0x00, 0xff, 0xff, 0xc9, 0xf9, 0xca, 0x14, 0xd6, 0x01, 0x00, 0x00, + // 365 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x91, 0x4f, 0x8a, 0xdb, 0x30, + 0x14, 0xc6, 0x47, 0x8a, 0xc7, 0xe3, 0xbc, 0xce, 0x04, 0x57, 0x94, 0x22, 0xfa, 0xc7, 0x18, 0x77, + 0xe3, 0xc5, 0xe0, 0xc5, 0xf4, 0x04, 0x69, 0xea, 0x45, 0x48, 0x42, 0x40, 0x0d, 0xd9, 0x06, 0x39, + 0x7e, 0x6d, 0x8c, 0x13, 0x5b, 0x58, 0x8a, 0x69, 0x6f, 0xd2, 0x0b, 0xf4, 0x04, 0xbd, 0x44, 0x97, + 0x3d, 0x42, 0x49, 0x2f, 0x52, 0xac, 0xfc, 0x81, 0xd0, 0xdd, 0xfb, 0xbe, 0xef, 0xbd, 0x4f, 0xf0, + 0x13, 0x78, 0x2a, 0x4b, 0x54, 0x53, 0x9b, 0x9a, 0x51, 0x95, 0x45, 0x3f, 0x09, 0xd0, 0xc9, 0x92, + 0xf9, 0xd0, 0x2b, 0xf1, 0x1b, 0x27, 0x21, 0x89, 0xef, 0x45, 0x37, 0xb2, 0x17, 0x70, 0xdb, 0xca, + 0xed, 0x1e, 0x39, 0xb5, 0xde, 0x51, 0xb0, 0xd7, 0xd0, 0xdf, 0x6b, 0x6c, 0x56, 0x3b, 0x34, 0x92, + 0xf7, 0x6c, 0xe2, 0x75, 0xc6, 0x0c, 0x8d, 0x64, 0x1c, 0xee, 0x5a, 0x6c, 0x74, 0x51, 0x57, 0xdc, + 0x09, 0x49, 0xec, 0x88, 0xb3, 0x64, 0x6f, 0x01, 0xf0, 0xab, 0x2a, 0x1a, 0xd4, 0x2b, 0x69, 0xf8, + 0xad, 0x0d, 0xfb, 0x27, 0x67, 0x68, 0x18, 0x03, 0xc7, 0x16, 0xba, 0xb6, 0xd0, 0xce, 0xdd, 0x4b, + 0xda, 0x34, 0x28, 0x77, 0xab, 0x22, 0xe7, 0x10, 0x92, 0xf8, 0x41, 0x78, 0x47, 0x63, 0x9c, 0x47, + 0x21, 0xb8, 0x93, 0xe5, 0xb4, 0xd0, 0x86, 0xbd, 0x04, 0x5a, 0xb6, 0x9c, 0x84, 0xbd, 0xf8, 0xd9, + 0x93, 0x9b, 0xa8, 0x2c, 0x99, 0x2c, 0x05, 0x2d, 0xdb, 0x68, 0x08, 0xcf, 0x67, 0xb2, 0x2a, 0x3e, + 0xa3, 0x36, 0xa3, 0x8d, 0xac, 0xbe, 0xe0, 0x27, 0x34, 0xec, 0x11, 0xee, 0xd6, 0x56, 0xe8, 0xd3, + 0x05, 0xeb, 0x2e, 0xae, 0xf7, 0xc4, 0x79, 0x25, 0xfa, 0x41, 0x60, 0x70, 0x9d, 0xb1, 0x01, 0xd0, + 0x71, 0x6e, 0x29, 0x39, 0x82, 0x8e, 0x73, 0xf6, 0x08, 0x74, 0xae, 0x2c, 0xa1, 0xc1, 0xd3, 0x9b, + 0xff, 0xbb, 0x92, 0xb9, 0xc2, 0x46, 0x9a, 0xa2, 0xae, 0x04, 0x9d, 0xab, 0x0e, 0xe9, 0x14, 0x5b, + 0xdc, 0x5a, 0x70, 0x0f, 0xe2, 0x28, 0xd8, 0x2b, 0xf0, 0x46, 0x1b, 0x5c, 0x97, 0x7a, 0xbf, 0xb3, + 0xd8, 0xee, 0xc5, 0x45, 0x47, 0xef, 0xa0, 0x7f, 0xa9, 0x60, 0x00, 0xee, 0x48, 0xa4, 0xc3, 0x45, + 0xea, 0xdf, 0x74, 0xf3, 0xc7, 0x74, 0x9a, 0x2e, 0x52, 0x9f, 0x7c, 0xf0, 0x7f, 0x1d, 0x02, 0xf2, + 0xfb, 0x10, 0x90, 0x3f, 0x87, 0x80, 0x7c, 0xff, 0x1b, 0xdc, 0x64, 0xae, 0xfd, 0xdf, 0xf7, 0xff, + 0x02, 0x00, 0x00, 0xff, 0xff, 0xeb, 0x28, 0x5d, 0xcf, 0xeb, 0x01, 0x00, 0x00, } func (m *KV) Marshal() (dAtA []byte, err error) { @@ -372,6 +394,14 @@ func (m *KV) MarshalTo(dAtA []byte) (int, error) { i = encodeVarintPb(dAtA, i, uint64(len(m.Meta))) i += copy(dAtA[i:], m.Meta) } + if m.StreamId != 0 { + dAtA[i] = 0x50 + i++ + i = encodeVarintPb(dAtA, i, uint64(m.StreamId)) + } + if m.XXX_unrecognized != nil { + i += copy(dAtA[i:], m.XXX_unrecognized) + } return i, nil } @@ -402,6 +432,9 @@ func (m *KVList) MarshalTo(dAtA []byte) (int, error) { i += n } } + if m.XXX_unrecognized != nil { + i += copy(dAtA[i:], m.XXX_unrecognized) + } return i, nil } @@ -432,6 +465,9 @@ func (m *ManifestChangeSet) MarshalTo(dAtA []byte) (int, error) { i += n } } + if m.XXX_unrecognized != nil { + i += copy(dAtA[i:], m.XXX_unrecognized) + } return i, nil } @@ -471,6 +507,9 @@ func (m *ManifestChange) MarshalTo(dAtA []byte) (int, error) { i = encodeVarintPb(dAtA, i, uint64(len(m.Checksum))) i += copy(dAtA[i:], m.Checksum) } + if m.XXX_unrecognized != nil { + i += copy(dAtA[i:], m.XXX_unrecognized) + } return i, nil } @@ -511,6 +550,12 @@ func (m *KV) Size() (n int) { if l > 0 { n += 1 + l + sovPb(uint64(l)) } + if m.StreamId != 0 { + n += 1 + sovPb(uint64(m.StreamId)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } return n } @@ -526,6 +571,9 @@ func (m *KVList) Size() (n int) { n += 1 + l + sovPb(uint64(l)) } } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } return n } @@ -541,6 +589,9 @@ func (m *ManifestChangeSet) Size() (n int) { n += 1 + l + sovPb(uint64(l)) } } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } return n } @@ -563,6 +614,9 @@ func (m *ManifestChange) Size() (n int) { if l > 0 { n += 1 + l + sovPb(uint64(l)) } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } return n } @@ -782,6 +836,25 @@ func (m *KV) Unmarshal(dAtA []byte) error { m.Meta = []byte{} } iNdEx = postIndex + case 10: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field StreamId", wireType) + } + m.StreamId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPb + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.StreamId |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipPb(dAtA[iNdEx:]) @@ -797,6 +870,7 @@ func (m *KV) Unmarshal(dAtA []byte) error { if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) iNdEx += skippy } } @@ -884,6 +958,7 @@ func (m *KVList) Unmarshal(dAtA []byte) error { if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) iNdEx += skippy } } @@ -971,6 +1046,7 @@ func (m *ManifestChangeSet) Unmarshal(dAtA []byte) error { if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) iNdEx += skippy } } @@ -1115,6 +1191,7 @@ func (m *ManifestChange) Unmarshal(dAtA []byte) error { if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) iNdEx += skippy } } diff --git a/vendor/github.com/dgraph-io/badger/pb/pb.proto b/vendor/github.com/dgraph-io/badger/pb/pb.proto index b790cf69..c6e7f413 100644 --- a/vendor/github.com/dgraph-io/badger/pb/pb.proto +++ b/vendor/github.com/dgraph-io/badger/pb/pb.proto @@ -26,6 +26,9 @@ message KV { 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; } message KVList { diff --git a/vendor/github.com/dgraph-io/badger/publisher.go b/vendor/github.com/dgraph-io/badger/publisher.go new file mode 100644 index 00000000..60f4fc90 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/publisher.go @@ -0,0 +1,151 @@ +/* + * 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 ( + "bytes" + "sync" + + "github.com/dgraph-io/badger/pb" + "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 +} + +func newPublisher() *publisher { + return &publisher{ + pubCh: make(chan requests, 1000), + subscribers: make(map[uint64]subscriber), + nextID: 0, + } +} + +func (p *publisher) listenForUpdates(c *y.Closer) { + defer func() { + p.cleanSubscribers() + c.Done() + }() + slurp := func(batch []*request) { + 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) { + kvs := &pb.KVList{} + p.Lock() + defer func() { + p.Unlock() + // Release all the request. + reqs.DecrRef() + }() + for _, s := range p.subscribers { + for _, prefix := range s.prefixes { + for _, req := range reqs { + for _, e := range req.Entries { + // TODO: Use trie to find subscribers. + if bytes.HasPrefix(e.Key, prefix) { + 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), + } + kvs.Kv = append(kvs.Kv, kv) + } + } + } + } + if len(kvs.GetKv()) > 0 { + s.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, + } + 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 { + delete(p.subscribers, id) + s.subCloser.SignalAndWait() + } +} + +func (p *publisher) deleteSubscriber(id uint64) { + p.Lock() + defer p.Unlock() + if _, ok := p.subscribers[id]; !ok { + return + } + delete(p.subscribers, id) +} + +func (p *publisher) sendUpdates(reqs []*request) { + // TODO: Prefix check before pushing into pubCh. + if p.noOfSubscribers() != 0 { + p.pubCh <- reqs + } +} + +func (p *publisher) noOfSubscribers() int { + p.Lock() + defer p.Unlock() + return len(p.subscribers) +} diff --git a/vendor/github.com/dgraph-io/badger/skl/arena.go b/vendor/github.com/dgraph-io/badger/skl/arena.go index 2decb75c..def55071 100644 --- a/vendor/github.com/dgraph-io/badger/skl/arena.go +++ b/vendor/github.com/dgraph-io/badger/skl/arena.go @@ -20,7 +20,7 @@ import ( "sync/atomic" "unsafe" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" + "github.com/dgraph-io/badger/y" ) const ( diff --git a/vendor/github.com/dgraph-io/badger/skl/skl.go b/vendor/github.com/dgraph-io/badger/skl/skl.go index 81a0506e..b465b09e 100644 --- a/vendor/github.com/dgraph-io/badger/skl/skl.go +++ b/vendor/github.com/dgraph-io/badger/skl/skl.go @@ -38,7 +38,7 @@ import ( "sync/atomic" "unsafe" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" + "github.com/dgraph-io/badger/y" ) const ( diff --git a/vendor/github.com/dgraph-io/badger/skl/skl_test.go b/vendor/github.com/dgraph-io/badger/skl/skl_test.go deleted file mode 100644 index cc695fbf..00000000 --- a/vendor/github.com/dgraph-io/badger/skl/skl_test.go +++ /dev/null @@ -1,475 +0,0 @@ -/* - * 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 ( - "encoding/binary" - "fmt" - "math/rand" - "strconv" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" -) - -const arenaSize = 1 << 20 - -func newValue(v int) []byte { - return []byte(fmt.Sprintf("%05d", v)) -} - -// length iterates over skiplist to give exact size. -func length(s *Skiplist) int { - x := s.getNext(s.head, 0) - count := 0 - for x != nil { - count++ - x = s.getNext(x, 0) - } - return count -} - -func TestEmpty(t *testing.T) { - key := []byte("aaa") - l := NewSkiplist(arenaSize) - - v := l.Get(key) - require.True(t, v.Value == nil) // Cannot use require.Nil for unsafe.Pointer nil. - - for _, less := range []bool{true, false} { - for _, allowEqual := range []bool{true, false} { - n, found := l.findNear(key, less, allowEqual) - require.Nil(t, n) - require.False(t, found) - } - } - - it := l.NewIterator() - require.False(t, it.Valid()) - - it.SeekToFirst() - require.False(t, it.Valid()) - - it.SeekToLast() - require.False(t, it.Valid()) - - it.Seek(key) - require.False(t, it.Valid()) - - l.DecrRef() - require.True(t, l.valid()) // Check the reference counting. - - it.Close() - require.False(t, l.valid()) // Check the reference counting. -} - -// TestBasic tests single-threaded inserts and updates and gets. -func TestBasic(t *testing.T) { - l := NewSkiplist(arenaSize) - val1 := newValue(42) - val2 := newValue(52) - val3 := newValue(62) - val4 := newValue(72) - - // Try inserting values. - // Somehow require.Nil doesn't work when checking for unsafe.Pointer(nil). - l.Put(y.KeyWithTs([]byte("key1"), 0), y.ValueStruct{Value: val1, Meta: 55, UserMeta: 0}) - l.Put(y.KeyWithTs([]byte("key2"), 2), y.ValueStruct{Value: val2, Meta: 56, UserMeta: 0}) - l.Put(y.KeyWithTs([]byte("key3"), 0), y.ValueStruct{Value: val3, Meta: 57, UserMeta: 0}) - - v := l.Get(y.KeyWithTs([]byte("key"), 0)) - require.True(t, v.Value == nil) - - v = l.Get(y.KeyWithTs([]byte("key1"), 0)) - require.True(t, v.Value != nil) - require.EqualValues(t, "00042", string(v.Value)) - require.EqualValues(t, 55, v.Meta) - - v = l.Get(y.KeyWithTs([]byte("key2"), 0)) - require.True(t, v.Value == nil) - - v = l.Get(y.KeyWithTs([]byte("key3"), 0)) - require.True(t, v.Value != nil) - require.EqualValues(t, "00062", string(v.Value)) - require.EqualValues(t, 57, v.Meta) - - l.Put(y.KeyWithTs([]byte("key3"), 1), y.ValueStruct{Value: val4, Meta: 12, UserMeta: 0}) - v = l.Get(y.KeyWithTs([]byte("key3"), 1)) - require.True(t, v.Value != nil) - require.EqualValues(t, "00072", string(v.Value)) - require.EqualValues(t, 12, v.Meta) -} - -// TestConcurrentBasic tests concurrent writes followed by concurrent reads. -func TestConcurrentBasic(t *testing.T) { - const n = 1000 - l := NewSkiplist(arenaSize) - var wg sync.WaitGroup - key := func(i int) []byte { - return y.KeyWithTs([]byte(fmt.Sprintf("%05d", i)), 0) - } - for i := 0; i < n; i++ { - wg.Add(1) - go func(i int) { - defer wg.Done() - l.Put(key(i), - y.ValueStruct{Value: newValue(i), Meta: 0, UserMeta: 0}) - }(i) - } - wg.Wait() - // Check values. Concurrent reads. - for i := 0; i < n; i++ { - wg.Add(1) - go func(i int) { - defer wg.Done() - v := l.Get(key(i)) - require.True(t, v.Value != nil) - require.EqualValues(t, newValue(i), v.Value) - }(i) - } - wg.Wait() - require.EqualValues(t, n, length(l)) -} - -// TestOneKey will read while writing to one single key. -func TestOneKey(t *testing.T) { - const n = 100 - key := y.KeyWithTs([]byte("thekey"), 0) - l := NewSkiplist(arenaSize) - defer l.DecrRef() - - var wg sync.WaitGroup - for i := 0; i < n; i++ { - wg.Add(1) - go func(i int) { - defer wg.Done() - l.Put(key, y.ValueStruct{Value: newValue(i), Meta: 0, UserMeta: 0}) - }(i) - } - // We expect that at least some write made it such that some read returns a value. - var sawValue int32 - for i := 0; i < n; i++ { - wg.Add(1) - go func() { - defer wg.Done() - p := l.Get(key) - if p.Value == nil { - return - } - atomic.AddInt32(&sawValue, 1) - v, err := strconv.Atoi(string(p.Value)) - require.NoError(t, err) - require.True(t, 0 <= v && v < n, fmt.Sprintf("invalid value %d", v)) - }() - } - wg.Wait() - require.True(t, sawValue > 0) - require.EqualValues(t, 1, length(l)) -} - -func TestFindNear(t *testing.T) { - l := NewSkiplist(arenaSize) - defer l.DecrRef() - for i := 0; i < 1000; i++ { - key := fmt.Sprintf("%05d", i*10+5) - l.Put(y.KeyWithTs([]byte(key), 0), y.ValueStruct{Value: newValue(i), Meta: 0, UserMeta: 0}) - } - - n, eq := l.findNear(y.KeyWithTs([]byte("00001"), 0), false, false) - require.NotNil(t, n) - require.EqualValues(t, y.KeyWithTs([]byte("00005"), 0), string(n.key(l.arena))) - require.False(t, eq) - n, eq = l.findNear(y.KeyWithTs([]byte("00001"), 0), false, true) - require.NotNil(t, n) - require.EqualValues(t, y.KeyWithTs([]byte("00005"), 0), string(n.key(l.arena))) - require.False(t, eq) - n, eq = l.findNear(y.KeyWithTs([]byte("00001"), 0), true, false) - require.Nil(t, n) - require.False(t, eq) - n, eq = l.findNear(y.KeyWithTs([]byte("00001"), 0), true, true) - require.Nil(t, n) - require.False(t, eq) - - n, eq = l.findNear(y.KeyWithTs([]byte("00005"), 0), false, false) - require.NotNil(t, n) - require.EqualValues(t, y.KeyWithTs([]byte("00015"), 0), string(n.key(l.arena))) - require.False(t, eq) - n, eq = l.findNear(y.KeyWithTs([]byte("00005"), 0), false, true) - require.NotNil(t, n) - require.EqualValues(t, y.KeyWithTs([]byte("00005"), 0), string(n.key(l.arena))) - require.True(t, eq) - n, eq = l.findNear(y.KeyWithTs([]byte("00005"), 0), true, false) - require.Nil(t, n) - require.False(t, eq) - n, eq = l.findNear(y.KeyWithTs([]byte("00005"), 0), true, true) - require.NotNil(t, n) - require.EqualValues(t, y.KeyWithTs([]byte("00005"), 0), string(n.key(l.arena))) - require.True(t, eq) - - n, eq = l.findNear(y.KeyWithTs([]byte("05555"), 0), false, false) - require.NotNil(t, n) - require.EqualValues(t, y.KeyWithTs([]byte("05565"), 0), string(n.key(l.arena))) - require.False(t, eq) - n, eq = l.findNear(y.KeyWithTs([]byte("05555"), 0), false, true) - require.NotNil(t, n) - require.EqualValues(t, y.KeyWithTs([]byte("05555"), 0), string(n.key(l.arena))) - require.True(t, eq) - n, eq = l.findNear(y.KeyWithTs([]byte("05555"), 0), true, false) - require.NotNil(t, n) - require.EqualValues(t, y.KeyWithTs([]byte("05545"), 0), string(n.key(l.arena))) - require.False(t, eq) - n, eq = l.findNear(y.KeyWithTs([]byte("05555"), 0), true, true) - require.NotNil(t, n) - require.EqualValues(t, y.KeyWithTs([]byte("05555"), 0), string(n.key(l.arena))) - require.True(t, eq) - - n, eq = l.findNear(y.KeyWithTs([]byte("05558"), 0), false, false) - require.NotNil(t, n) - require.EqualValues(t, y.KeyWithTs([]byte("05565"), 0), string(n.key(l.arena))) - require.False(t, eq) - n, eq = l.findNear(y.KeyWithTs([]byte("05558"), 0), false, true) - require.NotNil(t, n) - require.EqualValues(t, y.KeyWithTs([]byte("05565"), 0), string(n.key(l.arena))) - require.False(t, eq) - n, eq = l.findNear(y.KeyWithTs([]byte("05558"), 0), true, false) - require.NotNil(t, n) - require.EqualValues(t, y.KeyWithTs([]byte("05555"), 0), string(n.key(l.arena))) - require.False(t, eq) - n, eq = l.findNear(y.KeyWithTs([]byte("05558"), 0), true, true) - require.NotNil(t, n) - require.EqualValues(t, y.KeyWithTs([]byte("05555"), 0), string(n.key(l.arena))) - require.False(t, eq) - - n, eq = l.findNear(y.KeyWithTs([]byte("09995"), 0), false, false) - require.Nil(t, n) - require.False(t, eq) - n, eq = l.findNear(y.KeyWithTs([]byte("09995"), 0), false, true) - require.NotNil(t, n) - require.EqualValues(t, y.KeyWithTs([]byte("09995"), 0), string(n.key(l.arena))) - require.True(t, eq) - n, eq = l.findNear(y.KeyWithTs([]byte("09995"), 0), true, false) - require.NotNil(t, n) - require.EqualValues(t, y.KeyWithTs([]byte("09985"), 0), string(n.key(l.arena))) - require.False(t, eq) - n, eq = l.findNear(y.KeyWithTs([]byte("09995"), 0), true, true) - require.NotNil(t, n) - require.EqualValues(t, y.KeyWithTs([]byte("09995"), 0), string(n.key(l.arena))) - require.True(t, eq) - - n, eq = l.findNear(y.KeyWithTs([]byte("59995"), 0), false, false) - require.Nil(t, n) - require.False(t, eq) - n, eq = l.findNear(y.KeyWithTs([]byte("59995"), 0), false, true) - require.Nil(t, n) - require.False(t, eq) - n, eq = l.findNear(y.KeyWithTs([]byte("59995"), 0), true, false) - require.NotNil(t, n) - require.EqualValues(t, y.KeyWithTs([]byte("09995"), 0), string(n.key(l.arena))) - require.False(t, eq) - n, eq = l.findNear(y.KeyWithTs([]byte("59995"), 0), true, true) - require.NotNil(t, n) - require.EqualValues(t, y.KeyWithTs([]byte("09995"), 0), string(n.key(l.arena))) - require.False(t, eq) -} - -// TestIteratorNext tests a basic iteration over all nodes from the beginning. -func TestIteratorNext(t *testing.T) { - const n = 100 - l := NewSkiplist(arenaSize) - defer l.DecrRef() - it := l.NewIterator() - defer it.Close() - require.False(t, it.Valid()) - it.SeekToFirst() - require.False(t, it.Valid()) - for i := n - 1; i >= 0; i-- { - l.Put(y.KeyWithTs([]byte(fmt.Sprintf("%05d", i)), 0), - y.ValueStruct{Value: newValue(i), Meta: 0, UserMeta: 0}) - } - it.SeekToFirst() - for i := 0; i < n; i++ { - require.True(t, it.Valid()) - v := it.Value() - require.EqualValues(t, newValue(i), v.Value) - it.Next() - } - require.False(t, it.Valid()) -} - -// TestIteratorPrev tests a basic iteration over all nodes from the end. -func TestIteratorPrev(t *testing.T) { - const n = 100 - l := NewSkiplist(arenaSize) - defer l.DecrRef() - it := l.NewIterator() - defer it.Close() - require.False(t, it.Valid()) - it.SeekToFirst() - require.False(t, it.Valid()) - for i := 0; i < n; i++ { - l.Put(y.KeyWithTs([]byte(fmt.Sprintf("%05d", i)), 0), - y.ValueStruct{Value: newValue(i), Meta: 0, UserMeta: 0}) - } - it.SeekToLast() - for i := n - 1; i >= 0; i-- { - require.True(t, it.Valid()) - v := it.Value() - require.EqualValues(t, newValue(i), v.Value) - it.Prev() - } - require.False(t, it.Valid()) -} - -// TestIteratorSeek tests Seek and SeekForPrev. -func TestIteratorSeek(t *testing.T) { - const n = 100 - l := NewSkiplist(arenaSize) - defer l.DecrRef() - - it := l.NewIterator() - defer it.Close() - - require.False(t, it.Valid()) - it.SeekToFirst() - require.False(t, it.Valid()) - // 1000, 1010, 1020, ..., 1990. - for i := n - 1; i >= 0; i-- { - v := i*10 + 1000 - l.Put(y.KeyWithTs([]byte(fmt.Sprintf("%05d", i*10+1000)), 0), - y.ValueStruct{Value: newValue(v), Meta: 0, UserMeta: 0}) - } - it.SeekToFirst() - require.True(t, it.Valid()) - v := it.Value() - require.EqualValues(t, "01000", v.Value) - - it.Seek(y.KeyWithTs([]byte("01000"), 0)) - require.True(t, it.Valid()) - v = it.Value() - require.EqualValues(t, "01000", v.Value) - - it.Seek(y.KeyWithTs([]byte("01005"), 0)) - require.True(t, it.Valid()) - v = it.Value() - require.EqualValues(t, "01010", v.Value) - - it.Seek(y.KeyWithTs([]byte("01010"), 0)) - require.True(t, it.Valid()) - v = it.Value() - require.EqualValues(t, "01010", v.Value) - - it.Seek(y.KeyWithTs([]byte("99999"), 0)) - require.False(t, it.Valid()) - - // Try SeekForPrev. - it.SeekForPrev(y.KeyWithTs([]byte("00"), 0)) - require.False(t, it.Valid()) - - it.SeekForPrev(y.KeyWithTs([]byte("01000"), 0)) - require.True(t, it.Valid()) - v = it.Value() - require.EqualValues(t, "01000", v.Value) - - it.SeekForPrev(y.KeyWithTs([]byte("01005"), 0)) - require.True(t, it.Valid()) - v = it.Value() - require.EqualValues(t, "01000", v.Value) - - it.SeekForPrev(y.KeyWithTs([]byte("01010"), 0)) - require.True(t, it.Valid()) - v = it.Value() - require.EqualValues(t, "01010", v.Value) - - it.SeekForPrev(y.KeyWithTs([]byte("99999"), 0)) - require.True(t, it.Valid()) - v = it.Value() - require.EqualValues(t, "01990", v.Value) -} - -func randomKey(rng *rand.Rand) []byte { - b := make([]byte, 8) - key := rng.Uint32() - key2 := rng.Uint32() - binary.LittleEndian.PutUint32(b, key) - binary.LittleEndian.PutUint32(b[4:], key2) - return y.KeyWithTs(b, 0) -} - -// Standard test. Some fraction is read. Some fraction is write. Writes have -// to go through mutex lock. -func BenchmarkReadWrite(b *testing.B) { - value := newValue(123) - for i := 0; i <= 10; i++ { - readFrac := float32(i) / 10.0 - b.Run(fmt.Sprintf("frac_%d", i), func(b *testing.B) { - l := NewSkiplist(int64((b.N + 1) * MaxNodeSize)) - defer l.DecrRef() - b.ResetTimer() - var count int - b.RunParallel(func(pb *testing.PB) { - rng := rand.New(rand.NewSource(time.Now().UnixNano())) - for pb.Next() { - if rng.Float32() < readFrac { - v := l.Get(randomKey(rng)) - if v.Value != nil { - count++ - } - } else { - l.Put(randomKey(rng), y.ValueStruct{Value: value, Meta: 0, UserMeta: 0}) - } - } - }) - }) - } -} - -// Standard test. Some fraction is read. Some fraction is write. Writes have -// to go through mutex lock. -func BenchmarkReadWriteMap(b *testing.B) { - value := newValue(123) - for i := 0; i <= 10; i++ { - readFrac := float32(i) / 10.0 - b.Run(fmt.Sprintf("frac_%d", i), func(b *testing.B) { - m := make(map[string][]byte) - var mutex sync.RWMutex - b.ResetTimer() - var count int - b.RunParallel(func(pb *testing.PB) { - rng := rand.New(rand.NewSource(time.Now().UnixNano())) - for pb.Next() { - if rand.Float32() < readFrac { - mutex.RLock() - _, ok := m[string(randomKey(rng))] - mutex.RUnlock() - if ok { - count++ - } - } else { - mutex.Lock() - m[string(randomKey(rng))] = value - mutex.Unlock() - } - } - }) - }) - } -} diff --git a/vendor/github.com/dgraph-io/badger/stream.go b/vendor/github.com/dgraph-io/badger/stream.go index f40e5a2e..f0841a6a 100644 --- a/vendor/github.com/dgraph-io/badger/stream.go +++ b/vendor/github.com/dgraph-io/badger/stream.go @@ -19,12 +19,14 @@ package badger import ( "bytes" "context" + "math" "sync" + "sync/atomic" "time" - humanize "gx/ipfs/QmQMxG9D52TirZd9eLA37nxiNspnMRkKbyPWrVAa1gvtSy/go-humanize" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/pb" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" + "github.com/dgraph-io/badger/pb" + "github.com/dgraph-io/badger/y" + humanize "github.com/dustin/go-humanize" ) const pageSize = 4 << 20 // 4MB @@ -66,10 +68,11 @@ type Stream struct { // single goroutine, i.e. logic within Send method can expect single threaded execution. Send func(*pb.KVList) error - readTs uint64 - db *DB - rangeCh chan keyRange - kvChan chan *pb.KVList + readTs uint64 + db *DB + rangeCh chan keyRange + kvChan chan *pb.KVList + nextStreamId uint32 } // ToList is a default implementation of KeyToList. It picks up all valid versions of the key, @@ -113,6 +116,23 @@ func (st *Stream) ToList(key []byte, itr *Iterator) (*pb.KVList, error) { // end byte slices are owned by keyRange struct. func (st *Stream) produceRanges(ctx context.Context) { splits := st.db.KeySplits(st.Prefix) + + // We don't need to create more key ranges than NumGo goroutines. This way, we will have limited + // number of "streams" coming out, which then helps limit the memory used by SSWriter. + { + pickEvery := int(math.Floor(float64(len(splits)) / float64(st.NumGo))) + if pickEvery < 1 { + pickEvery = 1 + } + filtered := splits[:0] + for i, split := range splits { + if (i+1)%pickEvery == 0 { + filtered = append(filtered, split) + } + } + splits = filtered + } + start := y.SafeCopy(nil, st.Prefix) for _, key := range splits { st.rangeCh <- keyRange{left: start, right: y.SafeCopy(nil, []byte(key))} @@ -143,6 +163,9 @@ func (st *Stream) produceKVs(ctx context.Context) error { itr := txn.NewIterator(iterOpts) defer itr.Close() + // This unique stream id is used to identify all the keys from this iteration. + streamId := atomic.AddUint32(&st.nextStreamId, 1) + outList := new(pb.KVList) var prevKey []byte for itr.Seek(kr.left); itr.Valid(); { @@ -174,13 +197,28 @@ func (st *Stream) produceKVs(ctx context.Context) error { outList.Kv = append(outList.Kv, list.Kv...) size += list.Size() if size >= pageSize { - st.kvChan <- outList + for _, kv := range outList.Kv { + kv.StreamId = streamId + } + select { + case st.kvChan <- outList: + case <-ctx.Done(): + return ctx.Err() + } outList = new(pb.KVList) size = 0 } } if len(outList.Kv) > 0 { - st.kvChan <- outList + for _, kv := range outList.Kv { + kv.StreamId = streamId + } + // TODO: Think of a way to indicate that a stream is over. + select { + case st.kvChan <- outList: + case <-ctx.Done(): + return ctx.Err() + } } return nil } @@ -278,8 +316,8 @@ func (st *Stream) Orchestrate(ctx context.Context) error { // kvChan should only have a small capacity to ensure that we don't buffer up too much data if // sending is slow. Page size is set to 4MB, which is used to lazily cap the size of each - // KVList. To get around 64MB buffer, we can set the channel size to 16. - st.kvChan = make(chan *pb.KVList, 16) + // KVList. To get 128MB buffer, we can set the channel size to 32. + st.kvChan = make(chan *pb.KVList, 32) if st.KeyToList == nil { st.KeyToList = st.ToList diff --git a/vendor/github.com/dgraph-io/badger/stream_test.go b/vendor/github.com/dgraph-io/badger/stream_test.go deleted file mode 100644 index cdb1ec56..00000000 --- a/vendor/github.com/dgraph-io/badger/stream_test.go +++ /dev/null @@ -1,169 +0,0 @@ -/* - * 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 ( - "context" - "fmt" - "io/ioutil" - "math" - "os" - "strconv" - "strings" - "testing" - - "github.com/stretchr/testify/require" - bpb "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/pb" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" -) - -func openManaged(dir string) (*DB, error) { - opt := DefaultOptions - opt.Dir = dir - opt.ValueDir = dir - - return OpenManaged(opt) -} - -func keyWithPrefix(prefix string, k int) []byte { - return []byte(fmt.Sprintf("%s-%d", prefix, k)) -} - -func keyToInt(k []byte) (string, int) { - splits := strings.Split(string(k), "-") - key, err := strconv.Atoi(splits[1]) - y.Check(err) - return splits[0], key -} - -func value(k int) []byte { - return []byte(fmt.Sprintf("%08d", k)) -} - -type collector struct { - kv []*bpb.KV -} - -func (c *collector) Send(list *bpb.KVList) error { - c.kv = append(c.kv, list.Kv...) - return nil -} - -var ctxb = context.Background() - -func TestStream(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - - db, err := openManaged(dir) - require.NoError(t, err) - - var count int - for _, prefix := range []string{"p0", "p1", "p2"} { - txn := db.NewTransactionAt(math.MaxUint64, true) - for i := 1; i <= 100; i++ { - require.NoError(t, txn.Set(keyWithPrefix(prefix, i), value(i))) - count++ - } - require.NoError(t, txn.CommitAt(5, nil)) - } - - stream := db.NewStreamAt(math.MaxUint64) - stream.LogPrefix = "Testing" - c := &collector{} - stream.Send = func(list *bpb.KVList) error { - return c.Send(list) - } - - // Test case 1. Retrieve everything. - err = stream.Orchestrate(ctxb) - require.NoError(t, err) - require.Equal(t, 300, len(c.kv), "Expected 300. Got: %d", len(c.kv)) - - m := make(map[string]int) - for _, kv := range c.kv { - prefix, ki := keyToInt(kv.Key) - expected := value(ki) - require.Equal(t, expected, kv.Value) - m[prefix]++ - } - require.Equal(t, 3, len(m)) - for pred, count := range m { - require.Equal(t, 100, count, "Count mismatch for pred: %s", pred) - } - - // Test case 2. Retrieve only 1 predicate. - stream.Prefix = []byte("p1") - c.kv = c.kv[:0] - err = stream.Orchestrate(ctxb) - require.NoError(t, err) - require.Equal(t, 100, len(c.kv), "Expected 100. Got: %d", len(c.kv)) - - m = make(map[string]int) - for _, kv := range c.kv { - prefix, ki := keyToInt(kv.Key) - expected := value(ki) - require.Equal(t, expected, kv.Value) - m[prefix]++ - } - require.Equal(t, 1, len(m)) - for pred, count := range m { - require.Equal(t, 100, count, "Count mismatch for pred: %s", pred) - } - - // Test case 3. Retrieve select keys within the predicate. - c.kv = c.kv[:0] - stream.ChooseKey = func(item *Item) bool { - _, k := keyToInt(item.Key()) - return k%2 == 0 - } - err = stream.Orchestrate(ctxb) - require.NoError(t, err) - require.Equal(t, 50, len(c.kv), "Expected 50. Got: %d", len(c.kv)) - - m = make(map[string]int) - for _, kv := range c.kv { - prefix, ki := keyToInt(kv.Key) - expected := value(ki) - require.Equal(t, expected, kv.Value) - m[prefix]++ - } - require.Equal(t, 1, len(m)) - for pred, count := range m { - require.Equal(t, 50, count, "Count mismatch for pred: %s", pred) - } - - // Test case 4. Retrieve select keys from all predicates. - c.kv = c.kv[:0] - stream.Prefix = []byte{} - err = stream.Orchestrate(ctxb) - require.NoError(t, err) - require.Equal(t, 150, len(c.kv), "Expected 150. Got: %d", len(c.kv)) - - m = make(map[string]int) - for _, kv := range c.kv { - prefix, ki := keyToInt(kv.Key) - expected := value(ki) - require.Equal(t, expected, kv.Value) - m[prefix]++ - } - require.Equal(t, 3, len(m)) - for pred, count := range m { - require.Equal(t, 50, count, "Count mismatch for pred: %s", pred) - } -} diff --git a/vendor/github.com/dgraph-io/badger/stream_writer.go b/vendor/github.com/dgraph-io/badger/stream_writer.go new file mode 100644 index 00000000..cdf8849c --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/stream_writer.go @@ -0,0 +1,311 @@ +/* + * 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 ( + "bytes" + "math" + + "github.com/dgraph-io/badger/pb" + "github.com/dgraph-io/badger/table" + "github.com/dgraph-io/badger/y" + humanize "github.com/dustin/go-humanize" + "github.com/pkg/errors" +) + +const headStreamId uint32 = math.MaxUint32 + +// StreamWriter is used to write data coming from multiple streams. The streams must not have any +// overlapping key ranges. Within each stream, the keys must be sorted. Badger Stream framework is +// capable of generating such an output. So, this StreamWriter can be used at the other end to build +// BadgerDB at a much faster pace by writing SSTables (and value logs) directly to LSM tree levels +// without causing any compactions at all. This is way faster than using batched writer or using +// transactions, but only applicable in situations where the keys are pre-sorted and the DB is being +// bootstrapped. Existing data would get deleted when using this writer. So, this is only useful +// when restoring from backup or replicating DB across servers. +// +// StreamWriter should not be called on in-use DB instances. It is designed only to bootstrap new +// DBs. +type StreamWriter struct { + db *DB + done func() + throttle *y.Throttle + head valuePointer + maxVersion uint64 + writers map[uint32]*sortedWriter +} + +// NewStreamWriter creates a StreamWriter. Right after creating StreamWriter, Prepare must be +// called. The memory usage of a StreamWriter is directly proportional to the number of streams +// possible. So, efforts must be made to keep the number of streams low. Stream framework would +// typically use 16 goroutines and hence create 16 streams. +func (db *DB) NewStreamWriter() *StreamWriter { + return &StreamWriter{ + db: db, + // throttle shouldn't make much difference. Memory consumption is based on the number of + // concurrent streams being processed. + throttle: y.NewThrottle(16), + writers: make(map[uint32]*sortedWriter), + } +} + +// Prepare should be called before writing any entry to StreamWriter. It deletes all data present in +// existing DB, stops compactions and any writes being done by other means. Be very careful when +// calling Prepare, because it could result in permanent data loss. Not calling Prepare would result +// in a corrupt Badger instance. +func (sw *StreamWriter) Prepare() error { + var err error + sw.done, err = sw.db.dropAll() + return err +} + +// Write writes KVList to DB. Each KV within the list contains the stream id which StreamWriter +// would use to demux the writes. +func (sw *StreamWriter) Write(kvs *pb.KVList) error { + var entries []*Entry + for _, kv := range kvs.Kv { + var meta, userMeta byte + if len(kv.Meta) > 0 { + meta = kv.Meta[0] + } + if len(kv.UserMeta) > 0 { + userMeta = kv.UserMeta[0] + } + if sw.maxVersion < kv.Version { + sw.maxVersion = kv.Version + } + e := &Entry{ + Key: y.KeyWithTs(kv.Key, kv.Version), + Value: kv.Value, + UserMeta: userMeta, + ExpiresAt: kv.ExpiresAt, + meta: meta, + } + // If the value can be colocated with the key in LSM tree, we can skip + // writing the value to value log. + e.skipVlog = sw.db.shouldWriteValueToLSM(*e) + entries = append(entries, e) + } + req := &request{ + Entries: entries, + } + y.AssertTrue(len(kvs.Kv) == len(req.Entries)) + if err := sw.db.vlog.write([]*request{req}); err != nil { + return err + } + + for i, kv := range kvs.Kv { + e := req.Entries[i] + vptr := req.Ptrs[i] + if !vptr.IsZero() { + y.AssertTrue(sw.head.Less(vptr)) + sw.head = vptr + } + + writer, ok := sw.writers[kv.StreamId] + if !ok { + writer = sw.newWriter(kv.StreamId) + sw.writers[kv.StreamId] = writer + } + + var vs y.ValueStruct + if e.skipVlog { + vs = y.ValueStruct{ + Value: e.Value, + Meta: e.meta, + UserMeta: e.UserMeta, + ExpiresAt: e.ExpiresAt, + } + } else { + vbuf := make([]byte, vptrSize) + vs = y.ValueStruct{ + Value: vptr.Encode(vbuf), + Meta: e.meta | bitValuePointer, + UserMeta: e.UserMeta, + ExpiresAt: e.ExpiresAt, + } + } + if err := writer.Add(e.Key, vs); err != nil { + return err + } + } + return nil +} + +// Flush is called once we are done writing all the entries. It syncs DB directories. It also +// updates Oracle with maxVersion found in all entries (if DB is not managed). +func (sw *StreamWriter) Flush() error { + defer sw.done() + for _, writer := range sw.writers { + if err := writer.Done(); err != nil { + return err + } + } + + // Encode and write the value log head into a new table. + data := make([]byte, vptrSize) + sw.head.Encode(data) + headWriter := sw.newWriter(headStreamId) + if err := headWriter.Add( + y.KeyWithTs(head, sw.maxVersion), + y.ValueStruct{Value: data}); err != nil { + return err + } + if err := headWriter.Done(); err != nil { + return err + } + + if !sw.db.opt.managedTxns { + sw.db.orc = newOracle(sw.db.opt) + sw.db.orc.nextTxnTs = sw.maxVersion + sw.db.orc.txnMark.Done(sw.maxVersion) + sw.db.orc.readMark.Done(sw.maxVersion) + sw.db.orc.incrementNextTs() + } + + // Wait for all files to be written. + if err := sw.throttle.Finish(); err != nil { + return err + } + + // Now sync the directories, so all the files are registered. + if sw.db.opt.ValueDir != sw.db.opt.Dir { + if err := syncDir(sw.db.opt.ValueDir); err != nil { + return err + } + } + return syncDir(sw.db.opt.Dir) +} + +type sortedWriter struct { + db *DB + throttle *y.Throttle + + builder *table.Builder + lastKey []byte + streamId uint32 +} + +func (sw *StreamWriter) newWriter(streamId uint32) *sortedWriter { + return &sortedWriter{ + db: sw.db, + streamId: streamId, + throttle: sw.throttle, + builder: table.NewTableBuilder(), + } +} + +// ErrUnsortedKey is returned when any out of order key arrives at sortedWriter during call to Add. +var ErrUnsortedKey = errors.New("Keys not in sorted order") + +// Add adds key and vs to sortedWriter. +func (w *sortedWriter) Add(key []byte, vs y.ValueStruct) error { + if bytes.Compare(key, w.lastKey) <= 0 { + return ErrUnsortedKey + } + sameKey := y.SameKey(key, w.lastKey) + w.lastKey = y.SafeCopy(w.lastKey, key) + + if err := w.builder.Add(key, vs); err != nil { + return err + } + // Same keys should go into the same SSTable. + if !sameKey && w.builder.ReachedCapacity(w.db.opt.MaxTableSize) { + return w.send() + } + return nil +} + +func (w *sortedWriter) send() error { + if err := w.throttle.Do(); err != nil { + return err + } + go func(builder *table.Builder) { + data := builder.Finish() + err := w.createTable(data) + w.throttle.Done(err) + }(w.builder) + w.builder = table.NewTableBuilder() + return nil +} + +// Done is called once we are done writing all keys and valueStructs +// to sortedWriter. It completes writing current SST to disk. +func (w *sortedWriter) Done() error { + if w.builder.Empty() { + return nil + } + return w.send() +} + +func (w *sortedWriter) createTable(data []byte) error { + if len(data) == 0 { + return nil + } + fileID := w.db.lc.reserveFileID() + fd, err := y.CreateSyncedFile(table.NewFilename(fileID, w.db.opt.Dir), true) + if err != nil { + return err + } + if _, err := fd.Write(data); err != nil { + return err + } + tbl, err := table.OpenTable(fd, w.db.opt.TableLoadingMode, nil) + if err != nil { + return err + } + lc := w.db.lc + + var lhandler *levelHandler + // We should start the levels from 1, because we need level 0 to set the !badger!head key. We + // cannot mix up this key with other keys from the DB, otherwise we would introduce a range + // overlap violation. + y.AssertTrue(len(lc.levels) > 1) + for _, l := range lc.levels[1:] { + ratio := float64(l.getTotalSize()) / float64(l.maxTotalSize) + if ratio < 1.0 { + lhandler = l + break + } + } + if lhandler == nil { + // If we're exceeding the size of the lowest level, shove it in the lowest level. Can't do + // better than that. + lhandler = lc.levels[len(lc.levels)-1] + } + if w.streamId == headStreamId { + // This is a special !badger!head key. We should store it at level 0, separate from all the + // other keys to avoid an overlap. + lhandler = lc.levels[0] + } + // Now that table can be opened successfully, let's add this to the MANIFEST. + change := &pb.ManifestChange{ + Id: tbl.ID(), + Op: pb.ManifestChange_CREATE, + Level: uint32(lhandler.level), + Checksum: tbl.Checksum, + } + if err := w.db.manifest.addChanges([]*pb.ManifestChange{change}); err != nil { + return err + } + if err := lhandler.replaceTables([]*table.Table{}, []*table.Table{tbl}); err != nil { + return err + } + w.db.opt.Infof("Table created: %d at level: %d for stream: %d. Size: %s\n", + fileID, lhandler.level, w.streamId, humanize.Bytes(uint64(tbl.Size()))) + return nil +} diff --git a/vendor/github.com/dgraph-io/badger/structs.go b/vendor/github.com/dgraph-io/badger/structs.go index eaeeb1d4..2161b7f3 100644 --- a/vendor/github.com/dgraph-io/badger/structs.go +++ b/vendor/github.com/dgraph-io/badger/structs.go @@ -6,7 +6,7 @@ import ( "fmt" "hash/crc32" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" + "github.com/dgraph-io/badger/y" ) type valuePointer struct { @@ -85,7 +85,8 @@ type Entry struct { meta byte // Fields maintained internally. - offset uint32 + offset uint32 + skipVlog bool } func (e *Entry) estimateSize(threshold int) int { diff --git a/vendor/github.com/dgraph-io/badger/table/builder.go b/vendor/github.com/dgraph-io/badger/table/builder.go index a19c6589..43e65622 100644 --- a/vendor/github.com/dgraph-io/badger/table/builder.go +++ b/vendor/github.com/dgraph-io/badger/table/builder.go @@ -22,8 +22,8 @@ import ( "io" "math" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" - "gx/ipfs/QmWaLViWQF8jgyoLLqqcSrnp6dJpHESiJfzor1vrfDyTZf/bbloom" + "github.com/AndreasBriese/bbloom" + "github.com/dgraph-io/badger/y" ) var ( @@ -201,7 +201,7 @@ func (b *Builder) blockIndex() []byte { // Finish finishes the table by appending the index. func (b *Builder) Finish() []byte { - bf, _ := bbloom.New(float64(b.keyCount), 0.01) + bf := bbloom.New(float64(b.keyCount), 0.01) var klen [2]byte key := make([]byte, 1024) for { @@ -224,7 +224,7 @@ func (b *Builder) Finish() []byte { b.buf.Write(index) // Write bloom filter. - bdata, _ := bf.JSONMarshal() + bdata := bf.JSONMarshal() n, err := b.buf.Write(bdata) y.Check(err) var buf [4]byte diff --git a/vendor/github.com/dgraph-io/badger/table/iterator.go b/vendor/github.com/dgraph-io/badger/table/iterator.go index 28feb99e..0eb5ed01 100644 --- a/vendor/github.com/dgraph-io/badger/table/iterator.go +++ b/vendor/github.com/dgraph-io/badger/table/iterator.go @@ -22,8 +22,8 @@ import ( "math" "sort" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" - "gx/ipfs/QmVmDhyTTUcQXFD1rRQ64fGLMSAoaQvNH3hwuaCFAPq2hy/errors" + "github.com/dgraph-io/badger/y" + "github.com/pkg/errors" ) type blockIterator struct { diff --git a/vendor/github.com/dgraph-io/badger/table/table.go b/vendor/github.com/dgraph-io/badger/table/table.go index f8119346..9650c08e 100644 --- a/vendor/github.com/dgraph-io/badger/table/table.go +++ b/vendor/github.com/dgraph-io/badger/table/table.go @@ -30,10 +30,10 @@ import ( "sync" "sync/atomic" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/options" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" - "gx/ipfs/QmVmDhyTTUcQXFD1rRQ64fGLMSAoaQvNH3hwuaCFAPq2hy/errors" - "gx/ipfs/QmWaLViWQF8jgyoLLqqcSrnp6dJpHESiJfzor1vrfDyTZf/bbloom" + "github.com/AndreasBriese/bbloom" + "github.com/dgraph-io/badger/options" + "github.com/dgraph-io/badger/y" + "github.com/pkg/errors" ) const fileSuffix = ".sst" @@ -59,7 +59,7 @@ type Table struct { tableSize int // Initialized in OpenTable, using fd.Stat(). blockIndex []keyOffset - ref int32 // For file garbage collection. Atomic. + ref int32 // For file garbage collection. Atomic. loadingMode options.FileLoadingMode mmap []byte // Memory mapped. @@ -197,7 +197,7 @@ func (t *Table) Close() error { return t.fd.Close() } -func (t *Table) read(off int, sz int) ([]byte, error) { +func (t *Table) read(off, sz int) ([]byte, error) { if len(t.mmap) > 0 { if len(t.mmap[off:]) < sz { return nil, y.ErrEOF @@ -212,7 +212,7 @@ func (t *Table) read(off int, sz int) ([]byte, error) { return res, err } -func (t *Table) readNoFail(off int, sz int) []byte { +func (t *Table) readNoFail(off, sz int) []byte { res, err := t.read(off, sz) y.Check(err) return res @@ -230,7 +230,7 @@ func (t *Table) readIndex() error { bloomLen := int(binary.BigEndian.Uint32(buf)) readPos -= bloomLen data := t.readNoFail(readPos, bloomLen) - t.bf = *bbloom.JSONUnmarshal(data) + t.bf = bbloom.JSONUnmarshal(data) readPos -= 4 buf = t.readNoFail(readPos, 4) diff --git a/vendor/github.com/dgraph-io/badger/table/table_test.go b/vendor/github.com/dgraph-io/badger/table/table_test.go deleted file mode 100644 index d16e4083..00000000 --- a/vendor/github.com/dgraph-io/badger/table/table_test.go +++ /dev/null @@ -1,729 +0,0 @@ -/* - * 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 table - -import ( - "fmt" - "math/rand" - "os" - "sort" - "testing" - - "github.com/stretchr/testify/require" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/options" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" -) - -func key(prefix string, i int) string { - return prefix + fmt.Sprintf("%04d", i) -} - -func buildTestTable(t *testing.T, prefix string, n int) *os.File { - y.AssertTrue(n <= 10000) - keyValues := make([][]string, n) - for i := 0; i < n; i++ { - k := key(prefix, i) - v := fmt.Sprintf("%d", i) - keyValues[i] = []string{k, v} - } - return buildTable(t, keyValues) -} - -// keyValues is n by 2 where n is number of pairs. -func buildTable(t *testing.T, keyValues [][]string) *os.File { - b := NewTableBuilder() - defer b.Close() - // TODO: Add test for file garbage collection here. No files should be left after the tests here. - - filename := fmt.Sprintf("%s%s%d.sst", os.TempDir(), string(os.PathSeparator), rand.Int63()) - f, err := y.OpenSyncedFile(filename, true) - if t != nil { - require.NoError(t, err) - } else { - y.Check(err) - } - - sort.Slice(keyValues, func(i, j int) bool { - return keyValues[i][0] < keyValues[j][0] - }) - for _, kv := range keyValues { - y.AssertTrue(len(kv) == 2) - err := b.Add(y.KeyWithTs([]byte(kv[0]), 0), y.ValueStruct{Value: []byte(kv[1]), Meta: 'A', UserMeta: 0}) - if t != nil { - require.NoError(t, err) - } else { - y.Check(err) - } - } - f.Write(b.Finish()) - f.Close() - f, _ = y.OpenSyncedFile(filename, true) - return f -} - -func TestTableIterator(t *testing.T) { - for _, n := range []int{99, 100, 101} { - t.Run(fmt.Sprintf("n=%d", n), func(t *testing.T) { - f := buildTestTable(t, "key", n) - table, err := OpenTable(f, options.MemoryMap, nil) - require.NoError(t, err) - defer table.DecrRef() - it := table.NewIterator(false) - defer it.Close() - count := 0 - for it.Rewind(); it.Valid(); it.Next() { - v := it.Value() - k := y.KeyWithTs([]byte(key("key", count)), 0) - require.EqualValues(t, k, it.Key()) - require.EqualValues(t, fmt.Sprintf("%d", count), string(v.Value)) - count++ - } - require.Equal(t, count, n) - }) - } -} - -func TestSeekToFirst(t *testing.T) { - for _, n := range []int{99, 100, 101, 199, 200, 250, 9999, 10000} { - t.Run(fmt.Sprintf("n=%d", n), func(t *testing.T) { - f := buildTestTable(t, "key", n) - table, err := OpenTable(f, options.MemoryMap, nil) - require.NoError(t, err) - defer table.DecrRef() - it := table.NewIterator(false) - defer it.Close() - it.seekToFirst() - require.True(t, it.Valid()) - v := it.Value() - require.EqualValues(t, "0", string(v.Value)) - require.EqualValues(t, 'A', v.Meta) - }) - } -} - -func TestSeekToLast(t *testing.T) { - for _, n := range []int{99, 100, 101, 199, 200, 250, 9999, 10000} { - t.Run(fmt.Sprintf("n=%d", n), func(t *testing.T) { - f := buildTestTable(t, "key", n) - table, err := OpenTable(f, options.MemoryMap, nil) - require.NoError(t, err) - defer table.DecrRef() - it := table.NewIterator(false) - defer it.Close() - it.seekToLast() - require.True(t, it.Valid()) - v := it.Value() - require.EqualValues(t, fmt.Sprintf("%d", n-1), string(v.Value)) - require.EqualValues(t, 'A', v.Meta) - it.prev() - require.True(t, it.Valid()) - v = it.Value() - require.EqualValues(t, fmt.Sprintf("%d", n-2), string(v.Value)) - require.EqualValues(t, 'A', v.Meta) - }) - } -} - -func TestSeek(t *testing.T) { - f := buildTestTable(t, "k", 10000) - table, err := OpenTable(f, options.MemoryMap, nil) - require.NoError(t, err) - defer table.DecrRef() - - it := table.NewIterator(false) - defer it.Close() - - var data = []struct { - in string - valid bool - out string - }{ - {"abc", true, "k0000"}, - {"k0100", true, "k0100"}, - {"k0100b", true, "k0101"}, // Test case where we jump to next block. - {"k1234", true, "k1234"}, - {"k1234b", true, "k1235"}, - {"k9999", true, "k9999"}, - {"z", false, ""}, - } - - for _, tt := range data { - it.seek(y.KeyWithTs([]byte(tt.in), 0)) - if !tt.valid { - require.False(t, it.Valid()) - continue - } - require.True(t, it.Valid()) - k := it.Key() - require.EqualValues(t, tt.out, string(y.ParseKey(k))) - } -} - -func TestSeekForPrev(t *testing.T) { - f := buildTestTable(t, "k", 10000) - table, err := OpenTable(f, options.MemoryMap, nil) - require.NoError(t, err) - defer table.DecrRef() - - it := table.NewIterator(false) - defer it.Close() - - var data = []struct { - in string - valid bool - out string - }{ - {"abc", false, ""}, - {"k0100", true, "k0100"}, - {"k0100b", true, "k0100"}, // Test case where we jump to next block. - {"k1234", true, "k1234"}, - {"k1234b", true, "k1234"}, - {"k9999", true, "k9999"}, - {"z", true, "k9999"}, - } - - for _, tt := range data { - it.seekForPrev(y.KeyWithTs([]byte(tt.in), 0)) - if !tt.valid { - require.False(t, it.Valid()) - continue - } - require.True(t, it.Valid()) - k := it.Key() - require.EqualValues(t, tt.out, string(y.ParseKey(k))) - } -} - -func TestIterateFromStart(t *testing.T) { - // Vary the number of elements added. - for _, n := range []int{99, 100, 101, 199, 200, 250, 9999, 10000} { - t.Run(fmt.Sprintf("n=%d", n), func(t *testing.T) { - f := buildTestTable(t, "key", n) - table, err := OpenTable(f, options.MemoryMap, nil) - require.NoError(t, err) - defer table.DecrRef() - ti := table.NewIterator(false) - defer ti.Close() - ti.reset() - ti.seekToFirst() - require.True(t, ti.Valid()) - // No need to do a Next. - // ti.Seek brings us to the first key >= "". Essentially a SeekToFirst. - var count int - for ; ti.Valid(); ti.next() { - v := ti.Value() - require.EqualValues(t, fmt.Sprintf("%d", count), string(v.Value)) - require.EqualValues(t, 'A', v.Meta) - count++ - } - require.EqualValues(t, n, count) - }) - } -} - -func TestIterateFromEnd(t *testing.T) { - // Vary the number of elements added. - for _, n := range []int{99, 100, 101, 199, 200, 250, 9999, 10000} { - t.Run(fmt.Sprintf("n=%d", n), func(t *testing.T) { - f := buildTestTable(t, "key", n) - table, err := OpenTable(f, options.FileIO, nil) - require.NoError(t, err) - defer table.DecrRef() - ti := table.NewIterator(false) - defer ti.Close() - ti.reset() - ti.seek(y.KeyWithTs([]byte("zzzzzz"), 0)) // Seek to end, an invalid element. - require.False(t, ti.Valid()) - for i := n - 1; i >= 0; i-- { - ti.prev() - require.True(t, ti.Valid()) - v := ti.Value() - require.EqualValues(t, fmt.Sprintf("%d", i), string(v.Value)) - require.EqualValues(t, 'A', v.Meta) - } - ti.prev() - require.False(t, ti.Valid()) - }) - } -} - -func TestTable(t *testing.T) { - f := buildTestTable(t, "key", 10000) - table, err := OpenTable(f, options.FileIO, nil) - require.NoError(t, err) - defer table.DecrRef() - ti := table.NewIterator(false) - defer ti.Close() - kid := 1010 - seek := y.KeyWithTs([]byte(key("key", kid)), 0) - for ti.seek(seek); ti.Valid(); ti.next() { - k := ti.Key() - require.EqualValues(t, string(y.ParseKey(k)), key("key", kid)) - kid++ - } - if kid != 10000 { - t.Errorf("Expected kid: 10000. Got: %v", kid) - } - - ti.seek(y.KeyWithTs([]byte(key("key", 99999)), 0)) - require.False(t, ti.Valid()) - - ti.seek(y.KeyWithTs([]byte(key("key", -1)), 0)) - require.True(t, ti.Valid()) - k := ti.Key() - require.EqualValues(t, string(y.ParseKey(k)), key("key", 0)) -} - -func TestIterateBackAndForth(t *testing.T) { - f := buildTestTable(t, "key", 10000) - table, err := OpenTable(f, options.MemoryMap, nil) - require.NoError(t, err) - defer table.DecrRef() - - seek := y.KeyWithTs([]byte(key("key", 1010)), 0) - it := table.NewIterator(false) - defer it.Close() - it.seek(seek) - require.True(t, it.Valid()) - k := it.Key() - require.EqualValues(t, seek, k) - - it.prev() - it.prev() - require.True(t, it.Valid()) - k = it.Key() - require.EqualValues(t, key("key", 1008), string(y.ParseKey(k))) - - it.next() - it.next() - require.True(t, it.Valid()) - k = it.Key() - require.EqualValues(t, key("key", 1010), y.ParseKey(k)) - - it.seek(y.KeyWithTs([]byte(key("key", 2000)), 0)) - require.True(t, it.Valid()) - k = it.Key() - require.EqualValues(t, key("key", 2000), y.ParseKey(k)) - - it.prev() - require.True(t, it.Valid()) - k = it.Key() - require.EqualValues(t, key("key", 1999), y.ParseKey(k)) - - it.seekToFirst() - k = it.Key() - require.EqualValues(t, key("key", 0), y.ParseKey(k)) -} - -func TestUniIterator(t *testing.T) { - f := buildTestTable(t, "key", 10000) - table, err := OpenTable(f, options.MemoryMap, nil) - require.NoError(t, err) - defer table.DecrRef() - { - it := table.NewIterator(false) - defer it.Close() - var count int - for it.Rewind(); it.Valid(); it.Next() { - v := it.Value() - require.EqualValues(t, fmt.Sprintf("%d", count), string(v.Value)) - require.EqualValues(t, 'A', v.Meta) - count++ - } - require.EqualValues(t, 10000, count) - } - { - it := table.NewIterator(true) - defer it.Close() - var count int - for it.Rewind(); it.Valid(); it.Next() { - v := it.Value() - require.EqualValues(t, fmt.Sprintf("%d", 10000-1-count), string(v.Value)) - require.EqualValues(t, 'A', v.Meta) - count++ - } - require.EqualValues(t, 10000, count) - } -} - -// Try having only one table. -func TestConcatIteratorOneTable(t *testing.T) { - f := buildTable(t, [][]string{ - {"k1", "a1"}, - {"k2", "a2"}, - }) - - tbl, err := OpenTable(f, options.MemoryMap, nil) - require.NoError(t, err) - defer tbl.DecrRef() - - it := NewConcatIterator([]*Table{tbl}, false) - defer it.Close() - - it.Rewind() - require.True(t, it.Valid()) - k := it.Key() - require.EqualValues(t, "k1", string(y.ParseKey(k))) - vs := it.Value() - require.EqualValues(t, "a1", string(vs.Value)) - require.EqualValues(t, 'A', vs.Meta) -} - -func TestConcatIterator(t *testing.T) { - f := buildTestTable(t, "keya", 10000) - f2 := buildTestTable(t, "keyb", 10000) - f3 := buildTestTable(t, "keyc", 10000) - tbl, err := OpenTable(f, options.MemoryMap, nil) - require.NoError(t, err) - defer tbl.DecrRef() - tbl2, err := OpenTable(f2, options.LoadToRAM, nil) - require.NoError(t, err) - defer tbl2.DecrRef() - tbl3, err := OpenTable(f3, options.LoadToRAM, nil) - require.NoError(t, err) - defer tbl3.DecrRef() - - { - it := NewConcatIterator([]*Table{tbl, tbl2, tbl3}, false) - defer it.Close() - it.Rewind() - require.True(t, it.Valid()) - var count int - for ; it.Valid(); it.Next() { - vs := it.Value() - require.EqualValues(t, fmt.Sprintf("%d", count%10000), string(vs.Value)) - require.EqualValues(t, 'A', vs.Meta) - count++ - } - require.EqualValues(t, 30000, count) - - it.Seek(y.KeyWithTs([]byte("a"), 0)) - require.EqualValues(t, "keya0000", string(y.ParseKey(it.Key()))) - vs := it.Value() - require.EqualValues(t, "0", string(vs.Value)) - - it.Seek(y.KeyWithTs([]byte("keyb"), 0)) - require.EqualValues(t, "keyb0000", string(y.ParseKey(it.Key()))) - vs = it.Value() - require.EqualValues(t, "0", string(vs.Value)) - - it.Seek(y.KeyWithTs([]byte("keyb9999b"), 0)) - require.EqualValues(t, "keyc0000", string(y.ParseKey(it.Key()))) - vs = it.Value() - require.EqualValues(t, "0", string(vs.Value)) - - it.Seek(y.KeyWithTs([]byte("keyd"), 0)) - require.False(t, it.Valid()) - } - { - it := NewConcatIterator([]*Table{tbl, tbl2, tbl3}, true) - defer it.Close() - it.Rewind() - require.True(t, it.Valid()) - var count int - for ; it.Valid(); it.Next() { - vs := it.Value() - require.EqualValues(t, fmt.Sprintf("%d", 10000-(count%10000)-1), string(vs.Value)) - require.EqualValues(t, 'A', vs.Meta) - count++ - } - require.EqualValues(t, 30000, count) - - it.Seek(y.KeyWithTs([]byte("a"), 0)) - require.False(t, it.Valid()) - - it.Seek(y.KeyWithTs([]byte("keyb"), 0)) - require.EqualValues(t, "keya9999", string(y.ParseKey(it.Key()))) - vs := it.Value() - require.EqualValues(t, "9999", string(vs.Value)) - - it.Seek(y.KeyWithTs([]byte("keyb9999b"), 0)) - require.EqualValues(t, "keyb9999", string(y.ParseKey(it.Key()))) - vs = it.Value() - require.EqualValues(t, "9999", string(vs.Value)) - - it.Seek(y.KeyWithTs([]byte("keyd"), 0)) - require.EqualValues(t, "keyc9999", string(y.ParseKey(it.Key()))) - vs = it.Value() - require.EqualValues(t, "9999", string(vs.Value)) - } -} - -func TestMergingIterator(t *testing.T) { - f1 := buildTable(t, [][]string{ - {"k1", "a1"}, - {"k2", "a2"}, - }) - f2 := buildTable(t, [][]string{ - {"k1", "b1"}, - {"k2", "b2"}, - }) - tbl1, err := OpenTable(f1, options.LoadToRAM, nil) - require.NoError(t, err) - defer tbl1.DecrRef() - tbl2, err := OpenTable(f2, options.LoadToRAM, nil) - require.NoError(t, err) - defer tbl2.DecrRef() - it1 := tbl1.NewIterator(false) - it2 := NewConcatIterator([]*Table{tbl2}, false) - it := y.NewMergeIterator([]y.Iterator{it1, it2}, false) - defer it.Close() - - it.Rewind() - require.True(t, it.Valid()) - k := it.Key() - require.EqualValues(t, "k1", string(y.ParseKey(k))) - vs := it.Value() - require.EqualValues(t, "a1", string(vs.Value)) - require.EqualValues(t, 'A', vs.Meta) - it.Next() - - require.True(t, it.Valid()) - k = it.Key() - require.EqualValues(t, "k2", string(y.ParseKey(k))) - vs = it.Value() - require.EqualValues(t, "a2", string(vs.Value)) - require.EqualValues(t, 'A', vs.Meta) - it.Next() - - require.False(t, it.Valid()) -} - -func TestMergingIteratorReversed(t *testing.T) { - f1 := buildTable(t, [][]string{ - {"k1", "a1"}, - {"k2", "a2"}, - }) - f2 := buildTable(t, [][]string{ - {"k1", "b1"}, - {"k2", "b2"}, - }) - tbl1, err := OpenTable(f1, options.LoadToRAM, nil) - require.NoError(t, err) - defer tbl1.DecrRef() - tbl2, err := OpenTable(f2, options.LoadToRAM, nil) - require.NoError(t, err) - defer tbl2.DecrRef() - it1 := tbl1.NewIterator(true) - it2 := NewConcatIterator([]*Table{tbl2}, true) - it := y.NewMergeIterator([]y.Iterator{it1, it2}, true) - defer it.Close() - - it.Rewind() - require.True(t, it.Valid()) - k := it.Key() - require.EqualValues(t, "k2", string(y.ParseKey(k))) - vs := it.Value() - require.EqualValues(t, "a2", string(vs.Value)) - require.EqualValues(t, 'A', vs.Meta) - it.Next() - - require.True(t, it.Valid()) - k = it.Key() - require.EqualValues(t, "k1", string(y.ParseKey(k))) - vs = it.Value() - require.EqualValues(t, "a1", string(vs.Value)) - require.EqualValues(t, 'A', vs.Meta) - it.Next() - - require.False(t, it.Valid()) -} - -// Take only the first iterator. -func TestMergingIteratorTakeOne(t *testing.T) { - f1 := buildTable(t, [][]string{ - {"k1", "a1"}, - {"k2", "a2"}, - }) - f2 := buildTable(t, [][]string{}) - - t1, err := OpenTable(f1, options.LoadToRAM, nil) - require.NoError(t, err) - defer t1.DecrRef() - t2, err := OpenTable(f2, options.LoadToRAM, nil) - require.NoError(t, err) - defer t2.DecrRef() - - it1 := NewConcatIterator([]*Table{t1}, false) - it2 := NewConcatIterator([]*Table{t2}, false) - it := y.NewMergeIterator([]y.Iterator{it1, it2}, false) - defer it.Close() - - it.Rewind() - require.True(t, it.Valid()) - k := it.Key() - require.EqualValues(t, "k1", string(y.ParseKey(k))) - vs := it.Value() - require.EqualValues(t, "a1", string(vs.Value)) - require.EqualValues(t, 'A', vs.Meta) - it.Next() - - require.True(t, it.Valid()) - k = it.Key() - require.EqualValues(t, "k2", string(y.ParseKey(k))) - vs = it.Value() - require.EqualValues(t, "a2", string(vs.Value)) - require.EqualValues(t, 'A', vs.Meta) - it.Next() - - require.False(t, it.Valid()) -} - -// Take only the second iterator. -func TestMergingIteratorTakeTwo(t *testing.T) { - f1 := buildTable(t, [][]string{}) - f2 := buildTable(t, [][]string{ - {"k1", "a1"}, - {"k2", "a2"}, - }) - - t1, err := OpenTable(f1, options.LoadToRAM, nil) - require.NoError(t, err) - defer t1.DecrRef() - t2, err := OpenTable(f2, options.LoadToRAM, nil) - require.NoError(t, err) - defer t2.DecrRef() - - it1 := NewConcatIterator([]*Table{t1}, false) - it2 := NewConcatIterator([]*Table{t2}, false) - it := y.NewMergeIterator([]y.Iterator{it1, it2}, false) - defer it.Close() - - it.Rewind() - require.True(t, it.Valid()) - k := it.Key() - require.EqualValues(t, "k1", string(y.ParseKey(k))) - vs := it.Value() - require.EqualValues(t, "a1", string(vs.Value)) - require.EqualValues(t, 'A', vs.Meta) - it.Next() - - require.True(t, it.Valid()) - k = it.Key() - require.EqualValues(t, "k2", string(y.ParseKey(k))) - vs = it.Value() - require.EqualValues(t, "a2", string(vs.Value)) - require.EqualValues(t, 'A', vs.Meta) - it.Next() - - require.False(t, it.Valid()) -} - -func BenchmarkRead(b *testing.B) { - n := 5 << 20 - builder := NewTableBuilder() - filename := fmt.Sprintf("%s%s%d.sst", os.TempDir(), string(os.PathSeparator), rand.Int63()) - f, err := y.OpenSyncedFile(filename, true) - y.Check(err) - for i := 0; i < n; i++ { - k := fmt.Sprintf("%016x", i) - v := fmt.Sprintf("%d", i) - y.Check(builder.Add([]byte(k), y.ValueStruct{Value: []byte(v), Meta: 123, UserMeta: 0})) - } - - f.Write(builder.Finish()) - tbl, err := OpenTable(f, options.MemoryMap, nil) - y.Check(err) - defer tbl.DecrRef() - - // y.Printf("Size of table: %d\n", tbl.Size()) - b.ResetTimer() - // Iterate b.N times over the entire table. - for i := 0; i < b.N; i++ { - func() { - it := tbl.NewIterator(false) - defer it.Close() - for it.seekToFirst(); it.Valid(); it.next() { - } - }() - } -} - -func BenchmarkReadAndBuild(b *testing.B) { - n := 5 << 20 - builder := NewTableBuilder() - filename := fmt.Sprintf("%s%s%d.sst", os.TempDir(), string(os.PathSeparator), rand.Int63()) - f, err := y.OpenSyncedFile(filename, true) - y.Check(err) - for i := 0; i < n; i++ { - k := fmt.Sprintf("%016x", i) - v := fmt.Sprintf("%d", i) - y.Check(builder.Add([]byte(k), y.ValueStruct{Value: []byte(v), Meta: 123, UserMeta: 0})) - } - - f.Write(builder.Finish()) - tbl, err := OpenTable(f, options.MemoryMap, nil) - y.Check(err) - defer tbl.DecrRef() - - // y.Printf("Size of table: %d\n", tbl.Size()) - b.ResetTimer() - // Iterate b.N times over the entire table. - for i := 0; i < b.N; i++ { - func() { - newBuilder := NewTableBuilder() - it := tbl.NewIterator(false) - defer it.Close() - for it.seekToFirst(); it.Valid(); it.next() { - vs := it.Value() - newBuilder.Add(it.Key(), vs) - } - newBuilder.Finish() - }() - } -} - -func BenchmarkReadMerged(b *testing.B) { - n := 5 << 20 - m := 5 // Number of tables. - y.AssertTrue((n % m) == 0) - tableSize := n / m - var tables []*Table - for i := 0; i < m; i++ { - filename := fmt.Sprintf("%s%s%d.sst", os.TempDir(), string(os.PathSeparator), rand.Int63()) - builder := NewTableBuilder() - f, err := y.OpenSyncedFile(filename, true) - y.Check(err) - for j := 0; j < tableSize; j++ { - id := j*m + i // Arrays are interleaved. - // id := i*tableSize+j (not interleaved) - k := fmt.Sprintf("%016x", id) - v := fmt.Sprintf("%d", id) - y.Check(builder.Add([]byte(k), y.ValueStruct{Value: []byte(v), Meta: 123, UserMeta: 0})) - } - f.Write(builder.Finish()) - tbl, err := OpenTable(f, options.MemoryMap, nil) - y.Check(err) - tables = append(tables, tbl) - defer tbl.DecrRef() - } - - b.ResetTimer() - // Iterate b.N times over the entire table. - for i := 0; i < b.N; i++ { - func() { - var iters []y.Iterator - for _, tbl := range tables { - iters = append(iters, tbl.NewIterator(false)) - } - it := y.NewMergeIterator(iters, false) - defer it.Close() - for it.Rewind(); it.Valid(); it.Next() { - } - }() - } -} diff --git a/vendor/github.com/dgraph-io/badger/test.sh b/vendor/github.com/dgraph-io/badger/test.sh old mode 100644 new mode 100755 index 2216ecbd..e2df230e --- a/vendor/github.com/dgraph-io/badger/test.sh +++ b/vendor/github.com/dgraph-io/badger/test.sh @@ -17,8 +17,15 @@ go test -v --manual=true -run='TestTruncateVlogNoClose$' . truncate --size=4096 p/000000.vlog go test -v --manual=true -run='TestTruncateVlogNoClose2$' . go test -v --manual=true -run='TestTruncateVlogNoClose3$' . -rm -R p +rm -R p || true # Then the normal tests. +echo +echo "==> Starting tests with value log mmapped..." +sleep 5 go test -v --vlog_mmap=true -race ./... + +echo +echo "==> Starting tests with value log not mmapped..." +sleep 5 go test -v --vlog_mmap=false -race ./... diff --git a/vendor/github.com/dgraph-io/badger/txn.go b/vendor/github.com/dgraph-io/badger/txn.go index 1164108a..f6faa926 100644 --- a/vendor/github.com/dgraph-io/badger/txn.go +++ b/vendor/github.com/dgraph-io/badger/txn.go @@ -27,9 +27,9 @@ import ( "sync/atomic" "time" - farm "gx/ipfs/QmRFFHk2jw9tgjxv12bCuuTnSbVXxEvYQkuNCLMEv9eUwP/go-farm" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" - "gx/ipfs/QmVmDhyTTUcQXFD1rRQ64fGLMSAoaQvNH3hwuaCFAPq2hy/errors" + "github.com/dgraph-io/badger/y" + farm "github.com/dgryski/go-farm" + "github.com/pkg/errors" ) type oracle struct { @@ -127,6 +127,12 @@ func (o *oracle) nextTs() uint64 { return o.nextTxnTs } +func (o *oracle) incrementNextTs() { + o.Lock() + defer o.Unlock() + o.nextTxnTs++ +} + // Any deleted or invalid versions at or below ts would be discarded during // compaction to reclaim disk space in LSM tree and thence value log. func (o *oracle) setDiscardTs(ts uint64) { @@ -353,7 +359,7 @@ func (txn *Txn) SetWithDiscard(key, val []byte, meta byte) error { // SetWithTTL adds a key-value pair to the database, along with a time-to-live // (TTL) setting. A key stored with a TTL would automatically expire after the -// time has elapsed , and be eligible for garbage collection. +// time has elapsed, and be eligible for garbage collection. // // The current transaction keeps a reference to the key and val byte slice // arguments. Users must not modify key and val until the end of the @@ -364,6 +370,12 @@ func (txn *Txn) SetWithTTL(key, val []byte, dur time.Duration) error { return txn.SetEntry(e) } +// setMergeEntry is similar to SetEntry but it sets the bitMergeEntry flag +func (txn *Txn) setMergeEntry(key, val []byte) error { + e := &Entry{Key: key, Value: val, meta: bitMergeEntry} + return txn.SetEntry(e) +} + func exceedsSize(prefix string, max int64, key []byte) error { return errors.Errorf("%s with size %d exceeded %d limit. %s:\n%s", prefix, len(key), max, prefix, hex.Dump(key[:1<<10])) diff --git a/vendor/github.com/dgraph-io/badger/txn_test.go b/vendor/github.com/dgraph-io/badger/txn_test.go deleted file mode 100644 index 557a5bdf..00000000 --- a/vendor/github.com/dgraph-io/badger/txn_test.go +++ /dev/null @@ -1,845 +0,0 @@ -/* - * 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" - "math/rand" - "os" - "strconv" - "sync" - "testing" - "time" - - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/options" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" - - "github.com/stretchr/testify/require" -) - -func TestTxnSimple(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - - txn := db.NewTransaction(true) - - for i := 0; i < 10; i++ { - k := []byte(fmt.Sprintf("key=%d", i)) - v := []byte(fmt.Sprintf("val=%d", i)) - txn.Set(k, v) - } - - item, err := txn.Get([]byte("key=8")) - require.NoError(t, err) - - require.NoError(t, item.Value(func(val []byte) error { - require.Equal(t, []byte("val=8"), val) - return nil - })) - - require.Panics(t, func() { txn.CommitAt(100, nil) }) - require.NoError(t, txn.Commit()) - }) -} - -func TestTxnReadAfterWrite(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - var wg sync.WaitGroup - N := 100 - wg.Add(N) - for i := 0; i < N; i++ { - go func(i int) { - defer wg.Done() - key := []byte(fmt.Sprintf("key%d", i)) - err := db.Update(func(tx *Txn) error { - return tx.Set(key, key) - }) - require.NoError(t, err) - err = db.View(func(tx *Txn) error { - item, err := tx.Get(key) - require.NoError(t, err) - val, err := item.ValueCopy(nil) - require.NoError(t, err) - require.Equal(t, val, key) - return nil - }) - require.NoError(t, err) - }(i) - } - wg.Wait() - }) -} - -func TestTxnCommitAsync(t *testing.T) { - key := func(i int) []byte { - return []byte(fmt.Sprintf("key=%d", i)) - } - - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - txn := db.NewTransaction(true) - for i := 0; i < 40; i++ { - err := txn.Set(key(i), []byte(strconv.Itoa(100))) - require.NoError(t, err) - } - require.NoError(t, txn.Commit()) - txn.Discard() - - closer := y.NewCloser(1) - go func() { - defer closer.Done() - for { - select { - case <-closer.HasBeenClosed(): - return - default: - } - // Keep checking balance variant - txn := db.NewTransaction(false) - totalBalance := 0 - for i := 0; i < 40; i++ { - item, err := txn.Get(key(i)) - require.NoError(t, err) - val, err := item.ValueCopy(nil) - require.NoError(t, err) - bal, err := strconv.Atoi(string(val)) - require.NoError(t, err) - totalBalance += bal - } - require.Equal(t, totalBalance, 4000) - txn.Discard() - } - }() - - var wg sync.WaitGroup - wg.Add(100) - for i := 0; i < 100; i++ { - go func() { - txn := db.NewTransaction(true) - delta := rand.Intn(100) - for i := 0; i < 20; i++ { - err := txn.Set(key(i), []byte(strconv.Itoa(100-delta))) - require.NoError(t, err) - } - for i := 20; i < 40; i++ { - err := txn.Set(key(i), []byte(strconv.Itoa(100+delta))) - require.NoError(t, err) - } - // We are only doing writes, so there won't be any conflicts. - txn.CommitWith(func(err error) {}) - txn.Discard() - wg.Done() - }() - } - wg.Wait() - closer.SignalAndWait() - time.Sleep(time.Millisecond * 10) // allow goroutine to complete. - }) -} - -func TestTxnVersions(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - k := []byte("key") - for i := 1; i < 10; i++ { - txn := db.NewTransaction(true) - - txn.Set(k, []byte(fmt.Sprintf("valversion=%d", i))) - require.NoError(t, txn.Commit()) - require.Equal(t, uint64(i), db.orc.readTs()) - } - - checkIterator := func(itr *Iterator, i int) { - defer itr.Close() - count := 0 - for itr.Rewind(); itr.Valid(); itr.Next() { - item := itr.Item() - require.Equal(t, k, item.Key()) - - val, err := item.ValueCopy(nil) - require.NoError(t, err) - exp := fmt.Sprintf("valversion=%d", i) - require.Equal(t, exp, string(val), "i=%d", i) - count++ - } - require.Equal(t, 1, count, "i=%d", i) // Should only loop once. - } - - checkAllVersions := func(itr *Iterator, i int) { - var version uint64 - if itr.opt.Reverse { - version = 1 - } else { - version = uint64(i) - } - - count := 0 - for itr.Rewind(); itr.Valid(); itr.Next() { - item := itr.Item() - require.Equal(t, k, item.Key()) - require.Equal(t, version, item.Version()) - - val, err := item.ValueCopy(nil) - require.NoError(t, err) - exp := fmt.Sprintf("valversion=%d", version) - require.Equal(t, exp, string(val), "v=%d", version) - count++ - - if itr.opt.Reverse { - version++ - } else { - version-- - } - } - require.Equal(t, i, count, "i=%d", i) // Should loop as many times as i. - } - - for i := 1; i < 10; i++ { - txn := db.NewTransaction(true) - txn.readTs = uint64(i) // Read version at i. - - item, err := txn.Get(k) - require.NoError(t, err) - - val, err := item.ValueCopy(nil) - require.NoError(t, err) - require.Equal(t, []byte(fmt.Sprintf("valversion=%d", i)), val, - "Expected versions to match up at i=%d", i) - - // Try retrieving the latest version forward and reverse. - itr := txn.NewIterator(DefaultIteratorOptions) - checkIterator(itr, i) - - opt := DefaultIteratorOptions - opt.Reverse = true - itr = txn.NewIterator(opt) - checkIterator(itr, i) - - // Now try retrieving all versions forward and reverse. - opt = DefaultIteratorOptions - opt.AllVersions = true - itr = txn.NewIterator(opt) - checkAllVersions(itr, i) - itr.Close() - - opt = DefaultIteratorOptions - opt.AllVersions = true - opt.Reverse = true - itr = txn.NewIterator(opt) - checkAllVersions(itr, i) - itr.Close() - - txn.Discard() - } - txn := db.NewTransaction(true) - defer txn.Discard() - item, err := txn.Get(k) - require.NoError(t, err) - - val, err := item.ValueCopy(nil) - require.NoError(t, err) - require.Equal(t, []byte("valversion=9"), val) - }) -} - -func TestTxnWriteSkew(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - // Accounts - ax := []byte("x") - ay := []byte("y") - - // Set balance to $100 in each account. - txn := db.NewTransaction(true) - defer txn.Discard() - val := []byte(strconv.Itoa(100)) - txn.Set(ax, val) - txn.Set(ay, val) - require.NoError(t, txn.Commit()) - require.Equal(t, uint64(1), db.orc.readTs()) - - getBal := func(txn *Txn, key []byte) (bal int) { - item, err := txn.Get(key) - require.NoError(t, err) - - val, err := item.ValueCopy(nil) - require.NoError(t, err) - bal, err = strconv.Atoi(string(val)) - require.NoError(t, err) - return bal - } - - // Start two transactions, each would read both accounts and deduct from one account. - txn1 := db.NewTransaction(true) - - sum := getBal(txn1, ax) - sum += getBal(txn1, ay) - require.Equal(t, 200, sum) - txn1.Set(ax, []byte("0")) // Deduct 100 from ax. - - // Let's read this back. - sum = getBal(txn1, ax) - require.Equal(t, 0, sum) - sum += getBal(txn1, ay) - require.Equal(t, 100, sum) - // Don't commit yet. - - txn2 := db.NewTransaction(true) - - sum = getBal(txn2, ax) - sum += getBal(txn2, ay) - require.Equal(t, 200, sum) - txn2.Set(ay, []byte("0")) // Deduct 100 from ay. - - // Let's read this back. - sum = getBal(txn2, ax) - require.Equal(t, 100, sum) - sum += getBal(txn2, ay) - require.Equal(t, 100, sum) - - // Commit both now. - require.NoError(t, txn1.Commit()) - require.Error(t, txn2.Commit()) // This should fail. - - require.Equal(t, uint64(2), db.orc.readTs()) - }) -} - -// a3, a2, b4 (del), b3, c2, c1 -// Read at ts=4 -> a3, c2 -// Read at ts=4(Uncommitted) -> a3, b4 -// Read at ts=3 -> a3, b3, c2 -// Read at ts=2 -> a2, c2 -// Read at ts=1 -> c1 -func TestTxnIterationEdgeCase(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - ka := []byte("a") - kb := []byte("b") - kc := []byte("c") - - // c1 - txn := db.NewTransaction(true) - txn.Set(kc, []byte("c1")) - require.NoError(t, txn.Commit()) - require.Equal(t, uint64(1), db.orc.readTs()) - - // a2, c2 - txn = db.NewTransaction(true) - txn.Set(ka, []byte("a2")) - txn.Set(kc, []byte("c2")) - require.NoError(t, txn.Commit()) - require.Equal(t, uint64(2), db.orc.readTs()) - - // b3 - txn = db.NewTransaction(true) - txn.Set(ka, []byte("a3")) - txn.Set(kb, []byte("b3")) - require.NoError(t, txn.Commit()) - require.Equal(t, uint64(3), db.orc.readTs()) - - // b4, c4(del) (Uncommitted) - txn4 := db.NewTransaction(true) - require.NoError(t, txn4.Set(kb, []byte("b4"))) - require.NoError(t, txn4.Delete(kc)) - require.Equal(t, uint64(3), db.orc.readTs()) - - // b4 (del) - txn = db.NewTransaction(true) - txn.Delete(kb) - require.NoError(t, txn.Commit()) - require.Equal(t, uint64(4), db.orc.readTs()) - - checkIterator := func(itr *Iterator, expected []string) { - defer itr.Close() - var i int - for itr.Rewind(); itr.Valid(); itr.Next() { - item := itr.Item() - val, err := item.ValueCopy(nil) - require.NoError(t, err) - require.Equal(t, expected[i], string(val), "readts=%d", itr.readTs) - i++ - } - require.Equal(t, len(expected), i) - } - - txn = db.NewTransaction(true) - defer txn.Discard() - itr := txn.NewIterator(DefaultIteratorOptions) - itr5 := txn4.NewIterator(DefaultIteratorOptions) - checkIterator(itr, []string{"a3", "c2"}) - checkIterator(itr5, []string{"a3", "b4"}) - - rev := DefaultIteratorOptions - rev.Reverse = true - itr = txn.NewIterator(rev) - itr5 = txn4.NewIterator(rev) - checkIterator(itr, []string{"c2", "a3"}) - checkIterator(itr5, []string{"b4", "a3"}) - - txn.readTs = 3 - itr = txn.NewIterator(DefaultIteratorOptions) - checkIterator(itr, []string{"a3", "b3", "c2"}) - itr = txn.NewIterator(rev) - checkIterator(itr, []string{"c2", "b3", "a3"}) - - txn.readTs = 2 - itr = txn.NewIterator(DefaultIteratorOptions) - checkIterator(itr, []string{"a2", "c2"}) - itr = txn.NewIterator(rev) - checkIterator(itr, []string{"c2", "a2"}) - - txn.readTs = 1 - itr = txn.NewIterator(DefaultIteratorOptions) - checkIterator(itr, []string{"c1"}) - itr = txn.NewIterator(rev) - checkIterator(itr, []string{"c1"}) - }) -} - -// a2, a3, b4 (del), b3, c2, c1 -// Read at ts=4 -> a3, c2 -// Read at ts=3 -> a3, b3, c2 -// Read at ts=2 -> a2, c2 -// Read at ts=1 -> c1 -func TestTxnIterationEdgeCase2(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - ka := []byte("a") - kb := []byte("aa") - kc := []byte("aaa") - - // c1 - txn := db.NewTransaction(true) - txn.Set(kc, []byte("c1")) - require.NoError(t, txn.Commit()) - require.Equal(t, uint64(1), db.orc.readTs()) - - // a2, c2 - txn = db.NewTransaction(true) - txn.Set(ka, []byte("a2")) - txn.Set(kc, []byte("c2")) - require.NoError(t, txn.Commit()) - require.Equal(t, uint64(2), db.orc.readTs()) - - // b3 - txn = db.NewTransaction(true) - txn.Set(ka, []byte("a3")) - txn.Set(kb, []byte("b3")) - require.NoError(t, txn.Commit()) - require.Equal(t, uint64(3), db.orc.readTs()) - - // b4 (del) - txn = db.NewTransaction(true) - txn.Delete(kb) - require.NoError(t, txn.Commit()) - require.Equal(t, uint64(4), db.orc.readTs()) - - checkIterator := func(itr *Iterator, expected []string) { - defer itr.Close() - var i int - for itr.Rewind(); itr.Valid(); itr.Next() { - item := itr.Item() - val, err := item.ValueCopy(nil) - require.NoError(t, err) - require.Equal(t, expected[i], string(val), "readts=%d", itr.readTs) - i++ - } - require.Equal(t, len(expected), i) - } - txn = db.NewTransaction(true) - defer txn.Discard() - rev := DefaultIteratorOptions - rev.Reverse = true - - itr := txn.NewIterator(DefaultIteratorOptions) - checkIterator(itr, []string{"a3", "c2"}) - itr = txn.NewIterator(rev) - checkIterator(itr, []string{"c2", "a3"}) - - txn.readTs = 5 - itr = txn.NewIterator(DefaultIteratorOptions) - itr.Seek(ka) - require.True(t, itr.Valid()) - require.Equal(t, itr.item.Key(), ka) - itr.Seek(kc) - require.True(t, itr.Valid()) - require.Equal(t, itr.item.Key(), kc) - itr.Close() - - itr = txn.NewIterator(rev) - itr.Seek(ka) - require.True(t, itr.Valid()) - require.Equal(t, itr.item.Key(), ka) - itr.Seek(kc) - require.True(t, itr.Valid()) - require.Equal(t, itr.item.Key(), kc) - itr.Close() - - txn.readTs = 3 - itr = txn.NewIterator(DefaultIteratorOptions) - checkIterator(itr, []string{"a3", "b3", "c2"}) - itr = txn.NewIterator(rev) - checkIterator(itr, []string{"c2", "b3", "a3"}) - - txn.readTs = 2 - itr = txn.NewIterator(DefaultIteratorOptions) - checkIterator(itr, []string{"a2", "c2"}) - itr = txn.NewIterator(rev) - checkIterator(itr, []string{"c2", "a2"}) - - txn.readTs = 1 - itr = txn.NewIterator(DefaultIteratorOptions) - checkIterator(itr, []string{"c1"}) - itr = txn.NewIterator(rev) - checkIterator(itr, []string{"c1"}) - }) -} - -func TestTxnIterationEdgeCase3(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - kb := []byte("abc") - kc := []byte("acd") - kd := []byte("ade") - - // c1 - txn := db.NewTransaction(true) - txn.Set(kc, []byte("c1")) - require.NoError(t, txn.Commit()) - require.Equal(t, uint64(1), db.orc.readTs()) - - // b2 - txn = db.NewTransaction(true) - txn.Set(kb, []byte("b2")) - require.NoError(t, txn.Commit()) - require.Equal(t, uint64(2), db.orc.readTs()) - - txn2 := db.NewTransaction(true) - require.NoError(t, txn2.Set(kd, []byte("d2"))) - require.NoError(t, txn2.Delete(kc)) - - txn = db.NewTransaction(true) - defer txn.Discard() - rev := DefaultIteratorOptions - rev.Reverse = true - - itr := txn.NewIterator(DefaultIteratorOptions) - itr.Seek([]byte("ab")) - require.True(t, itr.Valid()) - require.Equal(t, itr.item.Key(), kb) - itr.Seek([]byte("ac")) - require.True(t, itr.Valid()) - require.Equal(t, itr.item.Key(), kc) - itr.Seek(nil) - require.True(t, itr.Valid()) - require.Equal(t, itr.item.Key(), kb) - itr.Seek([]byte("ac")) - itr.Rewind() - itr.Seek(nil) - require.True(t, itr.Valid()) - require.Equal(t, itr.item.Key(), kb) - itr.Seek([]byte("ac")) - require.True(t, itr.Valid()) - require.Equal(t, itr.item.Key(), kc) - itr.Close() - - // Keys: "abc", "ade" - // Read pending writes. - itr = txn2.NewIterator(DefaultIteratorOptions) - itr.Seek([]byte("ab")) - require.True(t, itr.Valid()) - require.Equal(t, itr.item.Key(), kb) - itr.Seek([]byte("ac")) - require.True(t, itr.Valid()) - require.Equal(t, itr.item.Key(), kd) - itr.Seek(nil) - require.True(t, itr.Valid()) - require.Equal(t, itr.item.Key(), kb) - itr.Seek([]byte("ac")) - itr.Rewind() - itr.Seek(nil) - require.True(t, itr.Valid()) - require.Equal(t, itr.item.Key(), kb) - itr.Seek([]byte("ad")) - require.True(t, itr.Valid()) - require.Equal(t, itr.item.Key(), kd) - itr.Close() - - itr = txn.NewIterator(rev) - itr.Seek([]byte("ac")) - require.True(t, itr.Valid()) - require.Equal(t, itr.item.Key(), kb) - itr.Seek([]byte("ad")) - require.True(t, itr.Valid()) - require.Equal(t, itr.item.Key(), kc) - itr.Seek(nil) - require.True(t, itr.Valid()) - require.Equal(t, itr.item.Key(), kc) - itr.Seek([]byte("ac")) - itr.Rewind() - require.True(t, itr.Valid()) - require.Equal(t, itr.item.Key(), kc) - itr.Seek([]byte("ad")) - require.True(t, itr.Valid()) - require.Equal(t, itr.item.Key(), kc) - itr.Close() - - // Keys: "abc", "ade" - itr = txn2.NewIterator(rev) - itr.Seek([]byte("ad")) - require.True(t, itr.Valid()) - require.Equal(t, itr.item.Key(), kb) - itr.Seek([]byte("ae")) - require.True(t, itr.Valid()) - require.Equal(t, itr.item.Key(), kd) - itr.Seek(nil) - require.True(t, itr.Valid()) - require.Equal(t, itr.item.Key(), kd) - itr.Seek([]byte("ab")) - itr.Rewind() - require.True(t, itr.Valid()) - require.Equal(t, itr.item.Key(), kd) - itr.Seek([]byte("ac")) - require.True(t, itr.Valid()) - require.Equal(t, itr.item.Key(), kb) - itr.Close() - }) -} - -func TestIteratorAllVersionsWithDeleted(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - // Write two keys - err := db.Update(func(txn *Txn) error { - txn.Set([]byte("answer1"), []byte("42")) - txn.Set([]byte("answer2"), []byte("43")) - return nil - }) - require.NoError(t, err) - - // Delete the specific key version from underlying db directly - err = db.View(func(txn *Txn) error { - item, err := txn.Get([]byte("answer1")) - require.NoError(t, err) - err = txn.db.batchSet([]*Entry{ - { - Key: y.KeyWithTs(item.key, item.version), - meta: bitDelete, - }, - }) - require.NoError(t, err) - return err - }) - require.NoError(t, err) - - opts := DefaultIteratorOptions - opts.AllVersions = true - opts.PrefetchValues = false - - // Verify that deleted shows up when AllVersions is set. - err = db.View(func(txn *Txn) error { - it := txn.NewIterator(opts) - defer it.Close() - var count int - for it.Rewind(); it.Valid(); it.Next() { - count++ - item := it.Item() - if count == 1 { - require.Equal(t, []byte("answer1"), item.Key()) - require.True(t, item.meta&bitDelete > 0) - } else { - require.Equal(t, []byte("answer2"), item.Key()) - } - } - require.Equal(t, 2, count) - return nil - }) - require.NoError(t, err) - }) -} - -func TestIteratorAllVersionsWithDeleted2(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - // Set and delete alternatively - for i := 0; i < 4; i++ { - err := db.Update(func(txn *Txn) error { - if i%2 == 0 { - txn.Set([]byte("key"), []byte("value")) - return nil - } - txn.Delete([]byte("key")) - return nil - }) - require.NoError(t, err) - } - - opts := DefaultIteratorOptions - opts.AllVersions = true - opts.PrefetchValues = false - - // Verify that deleted shows up when AllVersions is set. - err := db.View(func(txn *Txn) error { - it := txn.NewIterator(opts) - defer it.Close() - var count int - for it.Rewind(); it.Valid(); it.Next() { - item := it.Item() - require.Equal(t, []byte("key"), item.Key()) - if count%2 != 0 { - val, err := item.ValueCopy(nil) - require.NoError(t, err) - require.Equal(t, val, []byte("value")) - } else { - require.True(t, item.meta&bitDelete > 0) - } - count++ - } - require.Equal(t, 4, count) - return nil - }) - require.NoError(t, err) - }) -} - -func TestManagedDB(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - - opt := getTestOptions(dir) - opt.managedTxns = true - db, err := Open(opt) - require.NoError(t, err) - defer db.Close() - - key := func(i int) []byte { - return []byte(fmt.Sprintf("key-%02d", i)) - } - - val := func(i int) []byte { - return []byte(fmt.Sprintf("val-%d", i)) - } - - require.Panics(t, func() { - db.Update(func(tx *Txn) error { return nil }) - }) - - err = db.View(func(tx *Txn) error { return nil }) - require.NoError(t, err) - - // Write data at t=3. - txn := db.NewTransactionAt(3, true) - for i := 0; i <= 3; i++ { - require.NoError(t, txn.Set(key(i), val(i))) - } - require.Panics(t, func() { txn.Commit() }) - require.NoError(t, txn.CommitAt(3, nil)) - - // Read data at t=2. - txn = db.NewTransactionAt(2, false) - for i := 0; i <= 3; i++ { - _, err := txn.Get(key(i)) - require.Equal(t, ErrKeyNotFound, err) - } - txn.Discard() - - // Read data at t=3. - txn = db.NewTransactionAt(3, false) - for i := 0; i <= 3; i++ { - item, err := txn.Get(key(i)) - require.NoError(t, err) - require.Equal(t, uint64(3), item.Version()) - v, err := item.ValueCopy(nil) - require.NoError(t, err) - require.Equal(t, val(i), v) - } - txn.Discard() - - // Write data at t=7. - txn = db.NewTransactionAt(6, true) - for i := 0; i <= 7; i++ { - _, err := txn.Get(key(i)) - if err == nil { - continue // Don't overwrite existing keys. - } - require.NoError(t, txn.Set(key(i), val(i))) - } - require.NoError(t, txn.CommitAt(7, nil)) - - // Read data at t=9. - txn = db.NewTransactionAt(9, false) - for i := 0; i <= 9; i++ { - item, err := txn.Get(key(i)) - if i <= 7 { - require.NoError(t, err) - } else { - require.Equal(t, ErrKeyNotFound, err) - } - - if i <= 3 { - require.Equal(t, uint64(3), item.Version()) - } else if i <= 7 { - require.Equal(t, uint64(7), item.Version()) - } - if i <= 7 { - v, err := item.ValueCopy(nil) - require.NoError(t, err) - require.Equal(t, val(i), v) - } - } - txn.Discard() -} - -func TestArmV7Issue311Fix(t *testing.T) { - dir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - - config := DefaultOptions - config.TableLoadingMode = options.MemoryMap - config.ValueLogFileSize = 16 << 20 - config.LevelOneSize = 8 << 20 - config.MaxTableSize = 2 << 20 - config.Dir = dir - config.ValueDir = dir - config.SyncWrites = false - - db, err := Open(config) - if err != nil { - t.Fatalf("cannot open db at location %s: %v", dir, err) - } - - err = db.View(func(txn *Txn) error { return nil }) - if err != nil { - t.Fatal(err) - } - - err = db.Update(func(txn *Txn) error { - return txn.Set([]byte{0x11}, []byte{0x22}) - }) - if err != nil { - t.Fatal(err) - } - - err = db.Update(func(txn *Txn) error { - return txn.Set([]byte{0x11}, []byte{0x22}) - }) - - if err != nil { - t.Fatal(err) - } - - if err = db.Close(); err != nil { - t.Fatal(err) - } -} diff --git a/vendor/github.com/dgraph-io/badger/util.go b/vendor/github.com/dgraph-io/badger/util.go index e74fc87f..02952a80 100644 --- a/vendor/github.com/dgraph-io/badger/util.go +++ b/vendor/github.com/dgraph-io/badger/util.go @@ -23,9 +23,9 @@ import ( "sync/atomic" "time" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/table" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" - "gx/ipfs/QmVmDhyTTUcQXFD1rRQ64fGLMSAoaQvNH3hwuaCFAPq2hy/errors" + "github.com/dgraph-io/badger/table" + "github.com/dgraph-io/badger/y" + "github.com/pkg/errors" ) // summary is produced when DB is closed. Currently it is used only for testing. diff --git a/vendor/github.com/dgraph-io/badger/value.go b/vendor/github.com/dgraph-io/badger/value.go index 3c7511b2..77cdc185 100644 --- a/vendor/github.com/dgraph-io/badger/value.go +++ b/vendor/github.com/dgraph-io/badger/value.go @@ -20,6 +20,7 @@ import ( "bufio" "bytes" "encoding/binary" + "encoding/json" "fmt" "hash/crc32" "io" @@ -34,10 +35,10 @@ import ( "sync/atomic" "time" - "gx/ipfs/QmRvYNctevGUW52urgmoFZscT6buMKqhHezLUS64WepGWn/go-net/trace" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/options" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" - "gx/ipfs/QmVmDhyTTUcQXFD1rRQ64fGLMSAoaQvNH3hwuaCFAPq2hy/errors" + "github.com/dgraph-io/badger/options" + "github.com/dgraph-io/badger/y" + "github.com/pkg/errors" + "golang.org/x/net/trace" ) // Values have their first byte being byteData or byteDelete. This helps us distinguish between @@ -46,7 +47,8 @@ const ( bitDelete byte = 1 << 0 // Set if the key has been deleted. bitValuePointer byte = 1 << 1 // Set if the value is NOT stored directly next to key. bitDiscardEarlierVersions byte = 1 << 2 // Set if earlier versions can be discarded. - + // Set if item shouldn't be discarded via compactions (used by merge operator) + bitMergeEntry byte = 1 << 3 // The MSB 2 bits are for transactions. bitTxn byte = 1 << 6 // Set if the entry is part of a txn. bitFinTxn byte = 1 << 7 // Set if the entry is to indicate end of txn in value log. @@ -555,6 +557,9 @@ func (vlog *valueLog) decrIteratorCount() error { } func (vlog *valueLog) deleteLogFile(lf *logFile) error { + if lf == nil { + return nil + } path := vlog.fpath(lf.fid) if err := lf.munmap(); err != nil { _ = lf.fd.Close() @@ -702,11 +707,15 @@ func errFile(err error, path string, msg string) error { } func (vlog *valueLog) replayLog(lf *logFile, offset uint32, replayFn logEntry) error { - // We should open the file in RW mode, so it can be truncated. var err error - lf.fd, err = os.OpenFile(lf.path, os.O_RDWR, 0) + mode := os.O_RDONLY + if vlog.opt.Truncate { + // We should open the file in RW mode, so it can be truncated. + mode = os.O_RDWR + } + lf.fd, err = os.OpenFile(lf.path, mode, 0) if err != nil { - return errFile(err, lf.path, "Open file in RW mode") + return errFile(err, lf.path, "Open file") } defer lf.fd.Close() @@ -745,7 +754,10 @@ func (vlog *valueLog) open(db *DB, ptr valuePointer, replayFn logEntry) error { vlog.db = db vlog.elog = trace.NewEventLog("Badger", "Valuelog") vlog.garbageCh = make(chan struct{}, 1) // Only allow one GC at a time. - vlog.lfDiscardStats = &lfDiscardStats{m: make(map[uint32]int64)} + + if err := vlog.populateDiscardStats(); err != nil { + return err + } if err := vlog.populateFilesMap(); err != nil { return err @@ -877,40 +889,68 @@ type request struct { Ptrs []valuePointer Wg sync.WaitGroup Err error + ref int32 +} + +func (req *request) IncrRef() { + atomic.AddInt32(&req.ref, 1) +} + +func (req *request) DecrRef() { + nRef := atomic.AddInt32(&req.ref, -1) + if nRef > 0 { + return + } + req.Entries = nil + requestPool.Put(req) } func (req *request) Wait() error { req.Wg.Wait() - req.Entries = nil err := req.Err - requestPool.Put(req) + req.DecrRef() // DecrRef after writing to DB. return err } -// sync is thread-unsafe and should not be called concurrently with write. -func (vlog *valueLog) sync() error { +type requests []*request + +func (reqs requests) DecrRef() { + for _, req := range reqs { + req.DecrRef() + } +} + +// sync function syncs content of latest value log file to disk. Syncing of value log directory is +// not required here as it happens every time a value log file rotation happens(check createVlogFile +// function). During rotation, previous value log file also gets synced to disk. It only syncs file +// if fid >= vlog.maxFid. In some cases such as replay(while openning db), it might be called with +// fid < vlog.maxFid. To sync irrespective of file id just call it with math.MaxUint32. +func (vlog *valueLog) sync(fid uint32) error { if vlog.opt.SyncWrites { return nil } vlog.filesLock.RLock() - if len(vlog.filesMap) == 0 { + maxFid := atomic.LoadUint32(&vlog.maxFid) + // During replay it is possible to get sync call with fid less than maxFid. + // Because older file has already been synced, we can return from here. + if fid < maxFid || len(vlog.filesMap) == 0 { vlog.filesLock.RUnlock() return nil } - maxFid := atomic.LoadUint32(&vlog.maxFid) curlf := vlog.filesMap[maxFid] + // Sometimes it is possible that vlog.maxFid has been increased but file creation + // with same id is still in progress and this function is called. In those cases + // entry for the file might not be present in vlog.filesMap. + if curlf == nil { + vlog.filesLock.RUnlock() + return nil + } curlf.lock.RLock() vlog.filesLock.RUnlock() - dirSyncCh := make(chan error) - go func() { dirSyncCh <- syncDir(vlog.opt.ValueDir) }() err := curlf.sync() curlf.lock.RUnlock() - dirSyncErr := <-dirSyncCh - if err != nil { - err = dirSyncErr - } return err } @@ -955,6 +995,7 @@ func (vlog *valueLog) write(reqs []*request) error { return err } curlf = newlf + atomic.AddInt32(&vlog.db.logRotates, 1) } return nil } @@ -962,8 +1003,13 @@ func (vlog *valueLog) write(reqs []*request) error { for i := range reqs { b := reqs[i] b.Ptrs = b.Ptrs[:0] + var written int for j := range b.Entries { e := b.Entries[j] + if e.skipVlog { + b.Ptrs = append(b.Ptrs, valuePointer{}) + continue + } var p valuePointer p.Fid = curlf.fid @@ -975,8 +1021,9 @@ func (vlog *valueLog) write(reqs []*request) error { } p.Len = uint32(plen) b.Ptrs = append(b.Ptrs, p) + written++ } - vlog.numEntriesWritten += uint32(len(b.Entries)) + vlog.numEntriesWritten += uint32(written) // We write to disk here so that all entries that are part of the same transaction are // written to the same vlog file. writeNow := @@ -1157,6 +1204,7 @@ func (vlog *valueLog) doRunGC(lf *logFile, discardRatio float64, tr trace.Trace) // Set up the sampling window sizes. sizeWindow := float64(fi.Size()) * 0.1 // 10% of the file as window. + sizeWindowM := sizeWindow / (1 << 20) // in MBs. countWindow := int(float64(vlog.opt.ValueLogMaxEntries) * 0.01) // 1% of num entries. tr.LazyPrintf("Size window: %5.2f. Count window: %d.", sizeWindow, countWindow) @@ -1185,7 +1233,7 @@ func (vlog *valueLog) doRunGC(lf *logFile, discardRatio float64, tr trace.Trace) tr.LazyPrintf("Stopping sampling after %d entries.", countWindow) return errStop } - if r.total > sizeWindow { + if r.total > sizeWindowM { tr.LazyPrintf("Stopping sampling after reaching window size.") return errStop } @@ -1250,7 +1298,7 @@ func (vlog *valueLog) doRunGC(lf *logFile, discardRatio float64, tr trace.Trace) // If we couldn't sample at least a 1000 KV pairs or at least 75% of the window size, // and what we can discard is below the threshold, we should skip the rewrite. - if (r.count < countWindow && r.total < sizeWindow*0.75) || r.discard < discardRatio*r.total { + if (r.count < countWindow && r.total < sizeWindowM*0.75) || r.discard < discardRatio*r.total { tr.LazyPrintf("Skipping GC on fid: %d", lf.fid) return ErrNoRewrite } @@ -1305,10 +1353,44 @@ func (vlog *valueLog) runGC(discardRatio float64, head valuePointer) error { } } -func (vlog *valueLog) updateGCStats(stats map[uint32]int64) { +func (vlog *valueLog) updateDiscardStats(stats map[uint32]int64) { vlog.lfDiscardStats.Lock() for fid, sz := range stats { vlog.lfDiscardStats.m[fid] += sz } vlog.lfDiscardStats.Unlock() } + +// encodedDiscardStats returns []byte representation of lfDiscardStats +// This will be called while storing stats in BadgerDB +func (vlog *valueLog) encodedDiscardStats() []byte { + vlog.lfDiscardStats.Lock() + defer vlog.lfDiscardStats.Unlock() + + encodedStats, _ := json.Marshal(vlog.lfDiscardStats.m) + return encodedStats +} + +// populateDiscardStats populates vlog.lfDiscardStats +// This function will be called while initializing valueLog +func (vlog *valueLog) populateDiscardStats() error { + discardStatsKey := y.KeyWithTs(lfDiscardStatsKey, math.MaxUint64) + vs, err := vlog.db.get(discardStatsKey) + if err != nil { + return err + } + + // check if value is Empty + if vs.Value == nil || len(vs.Value) == 0 { + vlog.lfDiscardStats = &lfDiscardStats{m: make(map[uint32]int64)} + return nil + } + + var statsMap map[uint32]int64 + if err := json.Unmarshal(vs.Value, &statsMap); err != nil { + return err + } + vlog.opt.Debugf("Value Log Discard stats: %v", statsMap) + vlog.lfDiscardStats = &lfDiscardStats{m: statsMap} + return nil +} diff --git a/vendor/github.com/dgraph-io/badger/value_test.go b/vendor/github.com/dgraph-io/badger/value_test.go deleted file mode 100644 index b32d583c..00000000 --- a/vendor/github.com/dgraph-io/badger/value_test.go +++ /dev/null @@ -1,880 +0,0 @@ -/* - * 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" - "math/rand" - "os" - "sync" - "testing" - - "github.com/stretchr/testify/require" - humanize "gx/ipfs/QmQMxG9D52TirZd9eLA37nxiNspnMRkKbyPWrVAa1gvtSy/go-humanize" - "gx/ipfs/QmRvYNctevGUW52urgmoFZscT6buMKqhHezLUS64WepGWn/go-net/trace" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/options" - "gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y" -) - -func TestValueBasic(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - y.Check(err) - defer os.RemoveAll(dir) - - kv, _ := Open(getTestOptions(dir)) - defer kv.Close() - log := &kv.vlog - - // Use value big enough that the value log writes them even if SyncWrites is false. - const val1 = "sampleval012345678901234567890123" - const val2 = "samplevalb012345678901234567890123" - require.True(t, len(val1) >= kv.opt.ValueThreshold) - - e := &Entry{ - Key: []byte("samplekey"), - Value: []byte(val1), - meta: bitValuePointer, - } - e2 := &Entry{ - Key: []byte("samplekeyb"), - Value: []byte(val2), - meta: bitValuePointer, - } - - b := new(request) - b.Entries = []*Entry{e, e2} - - log.write([]*request{b}) - require.Len(t, b.Ptrs, 2) - t.Logf("Pointer written: %+v %+v\n", b.Ptrs[0], b.Ptrs[1]) - - s := new(y.Slice) - buf1, cb1, err1 := log.readValueBytes(b.Ptrs[0], s) - buf2, cb2, err2 := log.readValueBytes(b.Ptrs[1], s) - require.NoError(t, err1) - require.NoError(t, err2) - defer runCallback(cb1) - defer runCallback(cb2) - - readEntries := []Entry{valueBytesToEntry(buf1), valueBytesToEntry(buf2)} - require.EqualValues(t, []Entry{ - { - Key: []byte("samplekey"), - Value: []byte(val1), - meta: bitValuePointer, - }, - { - Key: []byte("samplekeyb"), - Value: []byte(val2), - meta: bitValuePointer, - }, - }, readEntries) - -} - -func TestValueGCManaged(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - - N := 10000 - opt := getTestOptions(dir) - opt.ValueLogMaxEntries = uint32(N / 10) - opt.managedTxns = true - db, err := Open(opt) - require.NoError(t, err) - defer db.Close() - - var ts uint64 - newTs := func() uint64 { - ts++ - return ts - } - - sz := 64 << 10 - var wg sync.WaitGroup - for i := 0; i < N; i++ { - v := make([]byte, sz) - rand.Read(v[:rand.Intn(sz)]) - - wg.Add(1) - txn := db.NewTransactionAt(newTs(), true) - require.NoError(t, txn.Set([]byte(fmt.Sprintf("key%d", i)), v)) - require.NoError(t, txn.CommitAt(newTs(), func(err error) { - wg.Done() - require.NoError(t, err) - })) - } - - for i := 0; i < N; i++ { - wg.Add(1) - txn := db.NewTransactionAt(newTs(), true) - require.NoError(t, txn.Delete([]byte(fmt.Sprintf("key%d", i)))) - require.NoError(t, txn.CommitAt(newTs(), func(err error) { - wg.Done() - require.NoError(t, err) - })) - } - wg.Wait() - files, err := ioutil.ReadDir(dir) - require.NoError(t, err) - for _, fi := range files { - t.Logf("File: %s. Size: %s\n", fi.Name(), humanize.Bytes(uint64(fi.Size()))) - } - - for i := 0; i < 100; i++ { - // Try at max 100 times to GC even a single value log file. - if err := db.RunValueLogGC(0.0001); err == nil { - return // Done - } - } - require.Fail(t, "Unable to GC even a single value log file.") -} - -func TestValueGC(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - opt := getTestOptions(dir) - opt.ValueLogFileSize = 1 << 20 - - kv, _ := Open(opt) - defer kv.Close() - - sz := 32 << 10 - txn := kv.NewTransaction(true) - for i := 0; i < 100; i++ { - v := make([]byte, sz) - rand.Read(v[:rand.Intn(sz)]) - require.NoError(t, txn.Set([]byte(fmt.Sprintf("key%d", i)), v)) - if i%20 == 0 { - require.NoError(t, txn.Commit()) - txn = kv.NewTransaction(true) - } - } - require.NoError(t, txn.Commit()) - - for i := 0; i < 45; i++ { - txnDelete(t, kv, []byte(fmt.Sprintf("key%d", i))) - } - - kv.vlog.filesLock.RLock() - lf := kv.vlog.filesMap[kv.vlog.sortedFids()[0]] - kv.vlog.filesLock.RUnlock() - - // lf.iterate(0, func(e Entry) bool { - // e.print("lf") - // return true - // }) - - tr := trace.New("Test", "Test") - defer tr.Finish() - kv.vlog.rewrite(lf, tr) - for i := 45; i < 100; i++ { - key := []byte(fmt.Sprintf("key%d", i)) - - require.NoError(t, kv.View(func(txn *Txn) error { - item, err := txn.Get(key) - require.NoError(t, err) - val := getItemValue(t, item) - require.NotNil(t, val) - require.True(t, len(val) == sz, "Size found: %d", len(val)) - return nil - })) - } -} - -func TestValueGC2(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - opt := getTestOptions(dir) - opt.ValueLogFileSize = 1 << 20 - - kv, _ := Open(opt) - defer kv.Close() - - sz := 32 << 10 - txn := kv.NewTransaction(true) - for i := 0; i < 100; i++ { - v := make([]byte, sz) - rand.Read(v[:rand.Intn(sz)]) - require.NoError(t, txn.Set([]byte(fmt.Sprintf("key%d", i)), v)) - if i%20 == 0 { - require.NoError(t, txn.Commit()) - txn = kv.NewTransaction(true) - } - } - require.NoError(t, txn.Commit()) - - for i := 0; i < 5; i++ { - txnDelete(t, kv, []byte(fmt.Sprintf("key%d", i))) - } - - for i := 5; i < 10; i++ { - v := []byte(fmt.Sprintf("value%d", i)) - txnSet(t, kv, []byte(fmt.Sprintf("key%d", i)), v, 0) - } - - kv.vlog.filesLock.RLock() - lf := kv.vlog.filesMap[kv.vlog.sortedFids()[0]] - kv.vlog.filesLock.RUnlock() - - // lf.iterate(0, func(e Entry) bool { - // e.print("lf") - // return true - // }) - - tr := trace.New("Test", "Test") - defer tr.Finish() - kv.vlog.rewrite(lf, tr) - for i := 0; i < 5; i++ { - key := []byte(fmt.Sprintf("key%d", i)) - require.NoError(t, kv.View(func(txn *Txn) error { - _, err := txn.Get(key) - require.Equal(t, ErrKeyNotFound, err) - return nil - })) - } - for i := 5; i < 10; i++ { - key := []byte(fmt.Sprintf("key%d", i)) - require.NoError(t, kv.View(func(txn *Txn) error { - item, err := txn.Get(key) - require.NoError(t, err) - val := getItemValue(t, item) - require.NotNil(t, val) - require.Equal(t, string(val), fmt.Sprintf("value%d", i)) - return nil - })) - } - for i := 10; i < 100; i++ { - key := []byte(fmt.Sprintf("key%d", i)) - require.NoError(t, kv.View(func(txn *Txn) error { - item, err := txn.Get(key) - require.NoError(t, err) - val := getItemValue(t, item) - require.NotNil(t, val) - require.True(t, len(val) == sz, "Size found: %d", len(val)) - return nil - })) - } -} - -func TestValueGC3(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - opt := getTestOptions(dir) - opt.ValueLogFileSize = 1 << 20 - - kv, err := Open(opt) - require.NoError(t, err) - defer kv.Close() - - // We want to test whether an iterator can continue through a value log GC. - - valueSize := 32 << 10 - - var value3 []byte - txn := kv.NewTransaction(true) - for i := 0; i < 100; i++ { - v := make([]byte, valueSize) // 32K * 100 will take >=3'276'800 B. - if i == 3 { - value3 = v - } - rand.Read(v[:]) - // Keys key000, key001, key002, such that sorted order matches insertion order - require.NoError(t, txn.Set([]byte(fmt.Sprintf("key%03d", i)), v)) - if i%20 == 0 { - require.NoError(t, txn.Commit()) - txn = kv.NewTransaction(true) - } - } - require.NoError(t, txn.Commit()) - - // Start an iterator to keys in the first value log file - itOpt := IteratorOptions{ - PrefetchValues: false, - PrefetchSize: 0, - Reverse: false, - } - - txn = kv.NewTransaction(true) - it := txn.NewIterator(itOpt) - defer it.Close() - // Walk a few keys - it.Rewind() - require.True(t, it.Valid()) - item := it.Item() - require.Equal(t, []byte("key000"), item.Key()) - it.Next() - require.True(t, it.Valid()) - item = it.Item() - require.Equal(t, []byte("key001"), item.Key()) - it.Next() - require.True(t, it.Valid()) - item = it.Item() - require.Equal(t, []byte("key002"), item.Key()) - - // Like other tests, we pull out a logFile to rewrite it directly - - kv.vlog.filesLock.RLock() - logFile := kv.vlog.filesMap[kv.vlog.sortedFids()[0]] - kv.vlog.filesLock.RUnlock() - - tr := trace.New("Test", "Test") - defer tr.Finish() - kv.vlog.rewrite(logFile, tr) - it.Next() - require.True(t, it.Valid()) - item = it.Item() - require.Equal(t, []byte("key003"), item.Key()) - - v3, err := item.ValueCopy(nil) - require.NoError(t, err) - require.Equal(t, value3, v3) -} - -func TestValueGC4(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - opt := getTestOptions(dir) - opt.ValueLogFileSize = 1 << 20 - opt.Truncate = true - - kv, err := Open(opt) - require.NoError(t, err) - defer kv.Close() - - sz := 128 << 10 // 5 entries per value log file. - txn := kv.NewTransaction(true) - for i := 0; i < 24; i++ { - v := make([]byte, sz) - rand.Read(v[:rand.Intn(sz)]) - require.NoError(t, txn.Set([]byte(fmt.Sprintf("key%d", i)), v)) - if i%3 == 0 { - require.NoError(t, txn.Commit()) - txn = kv.NewTransaction(true) - } - } - require.NoError(t, txn.Commit()) - - for i := 0; i < 8; i++ { - txnDelete(t, kv, []byte(fmt.Sprintf("key%d", i))) - } - - for i := 8; i < 16; i++ { - v := []byte(fmt.Sprintf("value%d", i)) - txnSet(t, kv, []byte(fmt.Sprintf("key%d", i)), v, 0) - } - - kv.vlog.filesLock.RLock() - lf0 := kv.vlog.filesMap[kv.vlog.sortedFids()[0]] - lf1 := kv.vlog.filesMap[kv.vlog.sortedFids()[1]] - kv.vlog.filesLock.RUnlock() - - // lf.iterate(0, func(e Entry) bool { - // e.print("lf") - // return true - // }) - - tr := trace.New("Test", "Test") - defer tr.Finish() - kv.vlog.rewrite(lf0, tr) - kv.vlog.rewrite(lf1, tr) - - err = kv.vlog.Close() - require.NoError(t, err) - - err = kv.vlog.open(kv, valuePointer{Fid: 2}, kv.replayFunction()) - require.NoError(t, err) - - for i := 0; i < 8; i++ { - key := []byte(fmt.Sprintf("key%d", i)) - require.NoError(t, kv.View(func(txn *Txn) error { - _, err := txn.Get(key) - require.Equal(t, ErrKeyNotFound, err) - return nil - })) - } - for i := 8; i < 16; i++ { - key := []byte(fmt.Sprintf("key%d", i)) - require.NoError(t, kv.View(func(txn *Txn) error { - item, err := txn.Get(key) - require.NoError(t, err) - val := getItemValue(t, item) - require.NotNil(t, val) - require.Equal(t, string(val), fmt.Sprintf("value%d", i)) - return nil - })) - } -} - -func TestChecksums(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - - // Set up SST with K1=V1 - opts := getTestOptions(dir) - opts.Truncate = true - opts.ValueLogFileSize = 100 * 1024 * 1024 // 100Mb - kv, err := Open(opts) - require.NoError(t, err) - require.NoError(t, kv.Close()) - - var ( - k0 = []byte("k0") - k1 = []byte("k1") - k2 = []byte("k2") - k3 = []byte("k3") - v0 = []byte("value0-012345678901234567890123012345678901234567890123") - v1 = []byte("value1-012345678901234567890123012345678901234567890123") - v2 = []byte("value2-012345678901234567890123012345678901234567890123") - v3 = []byte("value3-012345678901234567890123012345678901234567890123") - ) - // Make sure the value log would actually store the item - require.True(t, len(v0) >= kv.opt.ValueThreshold) - - // Use a vlog with K0=V0 and a (corrupted) second transaction(k1,k2) - buf := createVlog(t, []*Entry{ - {Key: k0, Value: v0}, - {Key: k1, Value: v1}, - {Key: k2, Value: v2}, - }) - buf[len(buf)-1]++ // Corrupt last byte - require.NoError(t, ioutil.WriteFile(vlogFilePath(dir, 0), buf, 0777)) - - // K1 should exist, but K2 shouldn't. - kv, err = Open(opts) - require.NoError(t, err) - - require.NoError(t, kv.View(func(txn *Txn) error { - item, err := txn.Get(k0) - require.NoError(t, err) - require.Equal(t, getItemValue(t, item), v0) - - _, err = txn.Get(k1) - require.Equal(t, ErrKeyNotFound, err) - - _, err = txn.Get(k2) - require.Equal(t, ErrKeyNotFound, err) - return nil - })) - - // Write K3 at the end of the vlog. - txnSet(t, kv, k3, v3, 0) - require.NoError(t, kv.Close()) - - // The vlog should contain K0 and K3 (K1 and k2 was lost when Badger started up - // last due to checksum failure). - kv, err = Open(opts) - require.NoError(t, err) - - { - txn := kv.NewTransaction(false) - - iter := txn.NewIterator(DefaultIteratorOptions) - iter.Seek(k0) - require.True(t, iter.Valid()) - it := iter.Item() - require.Equal(t, it.Key(), k0) - require.Equal(t, getItemValue(t, it), v0) - iter.Next() - require.True(t, iter.Valid()) - it = iter.Item() - require.Equal(t, it.Key(), k3) - require.Equal(t, getItemValue(t, it), v3) - - iter.Close() - txn.Discard() - } - - require.NoError(t, kv.Close()) -} - -func TestPartialAppendToValueLog(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - - // Create skeleton files. - opts := getTestOptions(dir) - opts.Truncate = true - opts.ValueLogFileSize = 100 * 1024 * 1024 // 100Mb - kv, err := Open(opts) - require.NoError(t, err) - require.NoError(t, kv.Close()) - - var ( - k0 = []byte("k0") - k1 = []byte("k1") - k2 = []byte("k2") - k3 = []byte("k3") - v0 = []byte("value0-01234567890123456789012012345678901234567890123") - v1 = []byte("value1-01234567890123456789012012345678901234567890123") - v2 = []byte("value2-01234567890123456789012012345678901234567890123") - v3 = []byte("value3-01234567890123456789012012345678901234567890123") - ) - // Values need to be long enough to actually get written to value log. - require.True(t, len(v3) >= kv.opt.ValueThreshold) - - // Create truncated vlog to simulate a partial append. - // k0 - single transaction, k1 and k2 in another transaction - buf := createVlog(t, []*Entry{ - {Key: k0, Value: v0}, - {Key: k1, Value: v1}, - {Key: k2, Value: v2}, - }) - buf = buf[:len(buf)-6] - require.NoError(t, ioutil.WriteFile(vlogFilePath(dir, 0), buf, 0777)) - - // Badger should now start up - kv, err = Open(opts) - require.NoError(t, err) - - require.NoError(t, kv.View(func(txn *Txn) error { - item, err := txn.Get(k0) - require.NoError(t, err) - require.Equal(t, v0, getItemValue(t, item)) - - _, err = txn.Get(k1) - require.Equal(t, ErrKeyNotFound, err) - _, err = txn.Get(k2) - require.Equal(t, ErrKeyNotFound, err) - return nil - })) - - // When K3 is set, it should be persisted after a restart. - txnSet(t, kv, k3, v3, 0) - require.NoError(t, kv.Close()) - kv, err = Open(opts) - require.NoError(t, err) - checkKeys(t, kv, [][]byte{k3}) - - // Replay value log from beginning, badger head is past k2. - require.NoError(t, kv.vlog.Close()) - require.NoError(t, - kv.vlog.open(kv, valuePointer{Fid: 0}, kv.replayFunction())) - require.NoError(t, kv.Close()) -} - -func TestReadOnlyOpenWithPartialAppendToValueLog(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - - // Create skeleton files. - opts := getTestOptions(dir) - opts.ValueLogFileSize = 100 * 1024 * 1024 // 100Mb - kv, err := Open(opts) - require.NoError(t, err) - require.NoError(t, kv.Close()) - - var ( - k0 = []byte("k0") - k1 = []byte("k1") - k2 = []byte("k2") - v0 = []byte("value0-012345678901234567890123") - v1 = []byte("value1-012345678901234567890123") - v2 = []byte("value2-012345678901234567890123") - ) - - // Create truncated vlog to simulate a partial append. - // k0 - single transaction, k1 and k2 in another transaction - buf := createVlog(t, []*Entry{ - {Key: k0, Value: v0}, - {Key: k1, Value: v1}, - {Key: k2, Value: v2}, - }) - buf = buf[:len(buf)-6] - require.NoError(t, ioutil.WriteFile(vlogFilePath(dir, 0), buf, 0777)) - - opts.ReadOnly = true - // Badger should fail a read-only open with values to replay - kv, err = Open(opts) - require.Error(t, err) - require.Regexp(t, "Database was not properly closed, cannot open read-only|Read-only mode is not supported on Windows", err.Error()) -} - -func TestValueLogTrigger(t *testing.T) { - t.Skip("Difficult to trigger compaction, so skipping. Re-enable after fixing #226") - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - - opt := getTestOptions(dir) - opt.ValueLogFileSize = 1 << 20 - kv, err := Open(opt) - require.NoError(t, err) - - // Write a lot of data, so it creates some work for valug log GC. - sz := 32 << 10 - txn := kv.NewTransaction(true) - for i := 0; i < 100; i++ { - v := make([]byte, sz) - rand.Read(v[:rand.Intn(sz)]) - require.NoError(t, txn.Set([]byte(fmt.Sprintf("key%d", i)), v)) - if i%20 == 0 { - require.NoError(t, txn.Commit()) - txn = kv.NewTransaction(true) - } - } - require.NoError(t, txn.Commit()) - - for i := 0; i < 45; i++ { - txnDelete(t, kv, []byte(fmt.Sprintf("key%d", i))) - } - - require.NoError(t, kv.RunValueLogGC(0.5)) - - require.NoError(t, kv.Close()) - - err = kv.RunValueLogGC(0.5) - require.Equal(t, ErrRejected, err, "Error should be returned after closing DB.") -} - -func createVlog(t *testing.T, entries []*Entry) []byte { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - - opts := getTestOptions(dir) - opts.ValueLogFileSize = 100 * 1024 * 1024 // 100Mb - kv, err := Open(opts) - require.NoError(t, err) - txnSet(t, kv, entries[0].Key, entries[0].Value, entries[0].meta) - entries = entries[1:] - txn := kv.NewTransaction(true) - for _, entry := range entries { - require.NoError(t, txn.SetWithMeta(entry.Key, entry.Value, entry.meta)) - } - require.NoError(t, txn.Commit()) - require.NoError(t, kv.Close()) - - filename := vlogFilePath(dir, 0) - buf, err := ioutil.ReadFile(filename) - require.NoError(t, err) - return buf -} - -func TestPenultimateLogCorruption(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - opt := getTestOptions(dir) - opt.ValueLogLoadingMode = options.FileIO - // Each txn generates at least two entries. 3 txns will fit each file. - opt.ValueLogMaxEntries = 5 - - db0, err := Open(opt) - require.NoError(t, err) - - h := testHelper{db: db0, t: t} - h.writeRange(0, 7) - h.readRange(0, 7) - - for i := 2; i >= 0; i-- { - fpath := vlogFilePath(dir, uint32(i)) - fi, err := os.Stat(fpath) - require.NoError(t, err) - require.True(t, fi.Size() > 0, "Empty file at log=%d", i) - if i == 0 { - err := os.Truncate(fpath, fi.Size()-1) - require.NoError(t, err) - } - } - // Simulate a crash by not closing db0, but releasing the locks. - if db0.dirLockGuard != nil { - require.NoError(t, db0.dirLockGuard.release()) - } - if db0.valueDirGuard != nil { - require.NoError(t, db0.valueDirGuard.release()) - } - - opt.Truncate = true - db1, err := Open(opt) - require.NoError(t, err) - h.db = db1 - h.readRange(0, 1) // Only 2 should be gone, because it is at the end of logfile 0. - h.readRange(3, 7) - err = db1.View(func(txn *Txn) error { - _, err := txn.Get(h.key(2)) // Verify that 2 is gone. - require.Equal(t, ErrKeyNotFound, err) - return nil - }) - require.NoError(t, err) - require.NoError(t, db1.Close()) -} - -func checkKeys(t *testing.T, kv *DB, keys [][]byte) { - i := 0 - txn := kv.NewTransaction(false) - iter := txn.NewIterator(IteratorOptions{}) - for iter.Seek(keys[0]); iter.Valid(); iter.Next() { - require.Equal(t, iter.Item().Key(), keys[i]) - i++ - } - require.Equal(t, i, len(keys)) -} - -type testHelper struct { - db *DB - t *testing.T - val []byte -} - -func (th *testHelper) key(i int) []byte { - return []byte(fmt.Sprintf("%010d", i)) -} -func (th *testHelper) value() []byte { - if len(th.val) > 0 { - return th.val - } - th.val = make([]byte, 100) - y.Check2(rand.Read(th.val)) - return th.val -} - -// writeRange [from, to]. -func (th *testHelper) writeRange(from, to int) { - for i := from; i <= to; i++ { - err := th.db.Update(func(txn *Txn) error { - return txn.Set(th.key(i), th.value()) - }) - require.NoError(th.t, err) - } -} - -func (th *testHelper) readRange(from, to int) { - for i := from; i <= to; i++ { - err := th.db.View(func(txn *Txn) error { - item, err := txn.Get(th.key(i)) - if err != nil { - return err - } - return item.Value(func(val []byte) error { - require.Equal(th.t, val, th.value(), "key=%q", th.key(i)) - return nil - - }) - }) - require.NoError(th.t, err, "key=%q", th.key(i)) - } -} - -// Test Bug #578, which showed that if a value is moved during value log GC, an -// older version can end up at a higher level in the LSM tree than a newer -// version, causing the data to not be returned. -func TestBug578(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - y.Check(err) - defer os.RemoveAll(dir) - - opts := DefaultOptions - opts.Dir = dir - opts.ValueDir = dir - opts.ValueLogMaxEntries = 64 - opts.MaxTableSize = 1 << 13 - - db, err := Open(opts) - require.NoError(t, err) - - h := testHelper{db: db, t: t} - - // Let's run this whole thing a few times. - for j := 0; j < 10; j++ { - t.Logf("Cycle: %d\n", j) - h.writeRange(0, 32) - h.writeRange(0, 10) - h.writeRange(50, 72) - h.writeRange(40, 72) - h.writeRange(40, 72) - - // Run value log GC a few times. - for i := 0; i < 5; i++ { - db.RunValueLogGC(0.5) - } - h.readRange(0, 10) - } -} - -func BenchmarkReadWrite(b *testing.B) { - rwRatio := []float32{ - 0.1, 0.2, 0.5, 1.0, - } - valueSize := []int{ - 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, - } - - for _, vsz := range valueSize { - for _, rw := range rwRatio { - b.Run(fmt.Sprintf("%3.1f,%04d", rw, vsz), func(b *testing.B) { - dir, err := ioutil.TempDir("", "vlog-benchmark") - y.Check(err) - defer os.RemoveAll(dir) - - db, err := Open(getTestOptions(dir)) - y.Check(err) - - vl := &db.vlog - b.ResetTimer() - - for i := 0; i < b.N; i++ { - e := new(Entry) - e.Key = make([]byte, 16) - e.Value = make([]byte, vsz) - bl := new(request) - bl.Entries = []*Entry{e} - - var ptrs []valuePointer - - vl.write([]*request{bl}) - ptrs = append(ptrs, bl.Ptrs...) - - f := rand.Float32() - if f < rw { - vl.write([]*request{bl}) - - } else { - ln := len(ptrs) - if ln == 0 { - b.Fatalf("Zero length of ptrs") - } - idx := rand.Intn(ln) - s := new(y.Slice) - buf, cb, err := vl.readValueBytes(ptrs[idx], s) - if err != nil { - b.Fatalf("Benchmark Read: %v", err) - } - - e := valueBytesToEntry(buf) - if len(e.Key) != 16 { - b.Fatalf("Key is invalid") - } - if len(e.Value) != vsz { - b.Fatalf("Value is invalid") - } - cb() - } - } - }) - } - } -} diff --git a/vendor/github.com/dgraph-io/badger/y/error.go b/vendor/github.com/dgraph-io/badger/y/error.go index 4f341cab..59bb2835 100644 --- a/vendor/github.com/dgraph-io/badger/y/error.go +++ b/vendor/github.com/dgraph-io/badger/y/error.go @@ -32,7 +32,7 @@ import ( "fmt" "log" - "gx/ipfs/QmVmDhyTTUcQXFD1rRQ64fGLMSAoaQvNH3hwuaCFAPq2hy/errors" + "github.com/pkg/errors" ) var debugMode = true diff --git a/vendor/github.com/dgraph-io/badger/y/file_dsync.go b/vendor/github.com/dgraph-io/badger/y/file_dsync.go index 10b8c9ca..3f3445e2 100644 --- a/vendor/github.com/dgraph-io/badger/y/file_dsync.go +++ b/vendor/github.com/dgraph-io/badger/y/file_dsync.go @@ -18,7 +18,7 @@ package y -import "gx/ipfs/QmVGjyM9i2msKvLXwh9VosCTgP4mL91kC7hDmqnwTTx6Hu/sys/unix" +import "golang.org/x/sys/unix" func init() { datasyncFileFlag = unix.O_DSYNC diff --git a/vendor/github.com/dgraph-io/badger/y/iterator.go b/vendor/github.com/dgraph-io/badger/y/iterator.go index c6eb9f0b..719e8ec8 100644 --- a/vendor/github.com/dgraph-io/badger/y/iterator.go +++ b/vendor/github.com/dgraph-io/badger/y/iterator.go @@ -21,7 +21,7 @@ import ( "container/heap" "encoding/binary" - "gx/ipfs/QmVmDhyTTUcQXFD1rRQ64fGLMSAoaQvNH3hwuaCFAPq2hy/errors" + "github.com/pkg/errors" ) // ValueStruct represents the value info that can be associated with a key, but also the internal diff --git a/vendor/github.com/dgraph-io/badger/y/iterator_test.go b/vendor/github.com/dgraph-io/badger/y/iterator_test.go deleted file mode 100644 index cff88be0..00000000 --- a/vendor/github.com/dgraph-io/badger/y/iterator_test.go +++ /dev/null @@ -1,234 +0,0 @@ -/* - * 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 y - -import ( - "sort" - "testing" - - "github.com/stretchr/testify/require" -) - -type SimpleIterator struct { - keys [][]byte - vals [][]byte - idx int - reversed bool -} - -var ( - closeCount int -) - -func (s *SimpleIterator) Close() error { closeCount++; return nil } - -func (s *SimpleIterator) Next() { - if !s.reversed { - s.idx++ - } else { - s.idx-- - } -} - -func (s *SimpleIterator) Rewind() { - if !s.reversed { - s.idx = 0 - } else { - s.idx = len(s.keys) - 1 - } -} - -func (s *SimpleIterator) Seek(key []byte) { - key = KeyWithTs(key, 0) - if !s.reversed { - s.idx = sort.Search(len(s.keys), func(i int) bool { - return CompareKeys(s.keys[i], key) >= 0 - }) - } else { - n := len(s.keys) - s.idx = n - 1 - sort.Search(n, func(i int) bool { - return CompareKeys(s.keys[n-1-i], key) <= 0 - }) - } -} - -func (s *SimpleIterator) Key() []byte { return s.keys[s.idx] } -func (s *SimpleIterator) Value() ValueStruct { - return ValueStruct{ - Value: s.vals[s.idx], - UserMeta: 55, - Meta: 0, - } -} -func (s *SimpleIterator) Valid() bool { - return s.idx >= 0 && s.idx < len(s.keys) -} - -func newSimpleIterator(keys []string, vals []string, reversed bool) *SimpleIterator { - k := make([][]byte, len(keys)) - v := make([][]byte, len(vals)) - AssertTrue(len(keys) == len(vals)) - for i := 0; i < len(keys); i++ { - k[i] = KeyWithTs([]byte(keys[i]), 0) - v[i] = []byte(vals[i]) - } - return &SimpleIterator{ - keys: k, - vals: v, - idx: -1, - reversed: reversed, - } -} - -func getAll(it Iterator) ([]string, []string) { - var keys, vals []string - for ; it.Valid(); it.Next() { - k := it.Key() - keys = append(keys, string(ParseKey(k))) - v := it.Value() - vals = append(vals, string(v.Value)) - } - return keys, vals -} - -func closeAndCheck(t *testing.T, it Iterator, expected int) { - closeCount = 0 - it.Close() - require.EqualValues(t, expected, closeCount) -} - -func TestSimpleIterator(t *testing.T) { - keys := []string{"1", "2", "3"} - vals := []string{"v1", "v2", "v3"} - it := newSimpleIterator(keys, vals, false) - it.Rewind() - k, v := getAll(it) - require.EqualValues(t, keys, k) - require.EqualValues(t, vals, v) - - closeAndCheck(t, it, 1) -} - -func reversed(a []string) []string { - var out []string - for i := len(a) - 1; i >= 0; i-- { - out = append(out, a[i]) - } - return out -} - -func TestMergeSingle(t *testing.T) { - keys := []string{"1", "2", "3"} - vals := []string{"v1", "v2", "v3"} - it := newSimpleIterator(keys, vals, false) - mergeIt := NewMergeIterator([]Iterator{it}, false) - mergeIt.Rewind() - k, v := getAll(mergeIt) - require.EqualValues(t, keys, k) - require.EqualValues(t, vals, v) - closeAndCheck(t, mergeIt, 1) -} - -func TestMergeSingleReversed(t *testing.T) { - keys := []string{"1", "2", "3"} - vals := []string{"v1", "v2", "v3"} - it := newSimpleIterator(keys, vals, true) - mergeIt := NewMergeIterator([]Iterator{it}, true) - mergeIt.Rewind() - k, v := getAll(mergeIt) - require.EqualValues(t, reversed(keys), k) - require.EqualValues(t, reversed(vals), v) - closeAndCheck(t, mergeIt, 1) -} - -func TestMergeMore(t *testing.T) { - it := newSimpleIterator([]string{"1", "3", "7"}, []string{"a1", "a3", "a7"}, false) - it2 := newSimpleIterator([]string{"2", "3", "5"}, []string{"b2", "b3", "b5"}, false) - it3 := newSimpleIterator([]string{"1"}, []string{"c1"}, false) - it4 := newSimpleIterator([]string{"1", "7", "9"}, []string{"d1", "d7", "d9"}, false) - - mergeIt := NewMergeIterator([]Iterator{it, it2, it3, it4}, false) - expectedKeys := []string{"1", "2", "3", "5", "7", "9"} - expectedVals := []string{"a1", "b2", "a3", "b5", "a7", "d9"} - mergeIt.Rewind() - k, v := getAll(mergeIt) - require.EqualValues(t, expectedKeys, k) - require.EqualValues(t, expectedVals, v) - closeAndCheck(t, mergeIt, 4) -} - -// Ensure MergeIterator satisfies the Iterator interface -func TestMergeIteratorNested(t *testing.T) { - keys := []string{"1", "2", "3"} - vals := []string{"v1", "v2", "v3"} - it := newSimpleIterator(keys, vals, false) - mergeIt := NewMergeIterator([]Iterator{it}, false) - mergeIt2 := NewMergeIterator([]Iterator{mergeIt}, false) - mergeIt2.Rewind() - k, v := getAll(mergeIt2) - require.EqualValues(t, keys, k) - require.EqualValues(t, vals, v) - closeAndCheck(t, mergeIt2, 1) -} - -func TestMergeIteratorSeek(t *testing.T) { - it := newSimpleIterator([]string{"1", "3", "7"}, []string{"a1", "a3", "a7"}, false) - it2 := newSimpleIterator([]string{"2", "3", "5"}, []string{"b2", "b3", "b5"}, false) - it3 := newSimpleIterator([]string{"1"}, []string{"c1"}, false) - it4 := newSimpleIterator([]string{"1", "7", "9"}, []string{"d1", "d7", "d9"}, false) - mergeIt := NewMergeIterator([]Iterator{it, it2, it3, it4}, false) - mergeIt.Seek([]byte("4")) - k, v := getAll(mergeIt) - require.EqualValues(t, []string{"5", "7", "9"}, k) - require.EqualValues(t, []string{"b5", "a7", "d9"}, v) - closeAndCheck(t, mergeIt, 4) -} - -func TestMergeIteratorSeekReversed(t *testing.T) { - it := newSimpleIterator([]string{"1", "3", "7"}, []string{"a1", "a3", "a7"}, true) - it2 := newSimpleIterator([]string{"2", "3", "5"}, []string{"b2", "b3", "b5"}, true) - it3 := newSimpleIterator([]string{"1"}, []string{"c1"}, true) - it4 := newSimpleIterator([]string{"1", "7", "9"}, []string{"d1", "d7", "d9"}, true) - mergeIt := NewMergeIterator([]Iterator{it, it2, it3, it4}, true) - mergeIt.Seek([]byte("5")) - k, v := getAll(mergeIt) - require.EqualValues(t, []string{"5", "3", "2", "1"}, k) - require.EqualValues(t, []string{"b5", "a3", "b2", "a1"}, v) - closeAndCheck(t, mergeIt, 4) -} - -func TestMergeIteratorSeekInvalid(t *testing.T) { - it := newSimpleIterator([]string{"1", "3", "7"}, []string{"a1", "a3", "a7"}, false) - it2 := newSimpleIterator([]string{"2", "3", "5"}, []string{"b2", "b3", "b5"}, false) - it3 := newSimpleIterator([]string{"1"}, []string{"c1"}, false) - it4 := newSimpleIterator([]string{"1", "7", "9"}, []string{"d1", "d7", "d9"}, false) - mergeIt := NewMergeIterator([]Iterator{it, it2, it3, it4}, false) - mergeIt.Seek([]byte("f")) - require.False(t, mergeIt.Valid()) - closeAndCheck(t, mergeIt, 4) -} - -func TestMergeIteratorSeekInvalidReversed(t *testing.T) { - it := newSimpleIterator([]string{"1", "3", "7"}, []string{"a1", "a3", "a7"}, true) - it2 := newSimpleIterator([]string{"2", "3", "5"}, []string{"b2", "b3", "b5"}, true) - it3 := newSimpleIterator([]string{"1"}, []string{"c1"}, true) - it4 := newSimpleIterator([]string{"1", "7", "9"}, []string{"d1", "d7", "d9"}, true) - mergeIt := NewMergeIterator([]Iterator{it, it2, it3, it4}, true) - mergeIt.Seek([]byte("0")) - require.False(t, mergeIt.Valid()) - closeAndCheck(t, mergeIt, 4) -} diff --git a/vendor/github.com/dgraph-io/badger/y/mmap_unix.go b/vendor/github.com/dgraph-io/badger/y/mmap_unix.go index 0d6d70ea..f9203a01 100644 --- a/vendor/github.com/dgraph-io/badger/y/mmap_unix.go +++ b/vendor/github.com/dgraph-io/badger/y/mmap_unix.go @@ -23,7 +23,7 @@ import ( "syscall" "unsafe" - "gx/ipfs/QmVGjyM9i2msKvLXwh9VosCTgP4mL91kC7hDmqnwTTx6Hu/sys/unix" + "golang.org/x/sys/unix" ) // Mmap uses the mmap system call to memory-map a file. If writable is true, diff --git a/vendor/github.com/dgraph-io/badger/y/watermark.go b/vendor/github.com/dgraph-io/badger/y/watermark.go index 6d17c3cb..c0bbb194 100644 --- a/vendor/github.com/dgraph-io/badger/y/watermark.go +++ b/vendor/github.com/dgraph-io/badger/y/watermark.go @@ -21,15 +21,15 @@ import ( "context" "sync/atomic" - "gx/ipfs/QmRvYNctevGUW52urgmoFZscT6buMKqhHezLUS64WepGWn/go-net/trace" + "golang.org/x/net/trace" ) type uint64Heap []uint64 -func (u uint64Heap) Len() int { return len(u) } -func (u uint64Heap) Less(i int, j int) bool { return u[i] < u[j] } -func (u uint64Heap) Swap(i int, j int) { u[i], u[j] = u[j], u[i] } -func (u *uint64Heap) Push(x interface{}) { *u = append(*u, x.(uint64)) } +func (u uint64Heap) Len() int { return len(u) } +func (u uint64Heap) Less(i, j int) bool { return u[i] < u[j] } +func (u uint64Heap) Swap(i, j int) { u[i], u[j] = u[j], u[i] } +func (u *uint64Heap) Push(x interface{}) { *u = append(*u, x.(uint64)) } func (u *uint64Heap) Pop() interface{} { old := *u n := len(old) diff --git a/vendor/github.com/dgraph-io/badger/y/y.go b/vendor/github.com/dgraph-io/badger/y/y.go index 47cb2922..4948315a 100644 --- a/vendor/github.com/dgraph-io/badger/y/y.go +++ b/vendor/github.com/dgraph-io/badger/y/y.go @@ -26,7 +26,7 @@ import ( "sync" "time" - "gx/ipfs/QmVmDhyTTUcQXFD1rRQ64fGLMSAoaQvNH3hwuaCFAPq2hy/errors" + "github.com/pkg/errors" ) // ErrEOF indicates an end of file when trying to read from a memory mapped file @@ -48,6 +48,9 @@ var ( // CastagnoliCrcTable is a CRC32 polynomial table CastagnoliCrcTable = crc32.MakeTable(crc32.Castagnoli) + + // Dummy channel for nil closers. + dummyCloserChan = make(chan struct{}) ) // OpenExistingFile opens an existing file, errors if it doesn't exist. @@ -91,7 +94,7 @@ func OpenTruncFile(filename string, sync bool) (*os.File, error) { } // SafeCopy does append(a[:0], src...). -func SafeCopy(a []byte, src []byte) []byte { +func SafeCopy(a, src []byte) []byte { return append(a[:0], src...) } @@ -122,7 +125,7 @@ func ParseTs(key []byte) uint64 { // is same. // a would be sorted higher than aa if we use bytes.compare // All keys should have timestamp. -func CompareKeys(key1 []byte, key2 []byte) int { +func CompareKeys(key1, key2 []byte) int { AssertTrue(len(key1) > 8 && len(key2) > 8) if cmp := bytes.Compare(key1[:len(key1)-8], key2[:len(key2)-8]); cmp != 0 { return cmp @@ -203,11 +206,17 @@ func (lc *Closer) Signal() { // HasBeenClosed gets signaled when Signal() is called. func (lc *Closer) HasBeenClosed() <-chan struct{} { + if lc == nil { + return dummyCloserChan + } return lc.closed } // Done calls Done() on the WaitGroup. func (lc *Closer) Done() { + if lc == nil { + return + } lc.waiting.Done() } @@ -227,9 +236,11 @@ func (lc *Closer) SignalAndWait() { // provides a mechanism to check for errors encountered by workers and wait for // them to finish. type Throttle struct { - wg sync.WaitGroup - ch chan struct{} - errCh chan error + once sync.Once + wg sync.WaitGroup + ch chan struct{} + errCh chan error + finishErr error } // NewThrottle creates a new throttle with a max number of workers. @@ -271,16 +282,21 @@ func (t *Throttle) Done(err error) { t.wg.Done() } -// Finish waits until all workers have finished working. It would return any -// error passed by Done. +// Finish waits until all workers have finished working. It would return any error passed by Done. +// If Finish is called multiple time, it will wait for workers to finish only once(first time). +// From next calls, it will return same error as found on first call. func (t *Throttle) Finish() error { - t.wg.Wait() - close(t.ch) - close(t.errCh) - for err := range t.errCh { - if err != nil { - return err + t.once.Do(func() { + t.wg.Wait() + close(t.ch) + close(t.errCh) + for err := range t.errCh { + if err != nil { + t.finishErr = err + return + } } - } - return nil + }) + + return t.finishErr }