fix backfill operations and dependency issue; hopefully travis will work now
This commit is contained in:
parent
4baea2923c
commit
723c7c6244
8
Gopkg.lock
generated
8
Gopkg.lock
generated
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ func init() {
|
||||
}
|
||||
|
||||
func syncPublishScreenAndServe() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
blockChain, ethClient, rpcClient := getBlockChainAndClients()
|
||||
|
||||
db := utils.LoadPostgres(databaseConfig, blockChain.Node())
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -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
|
||||
|
@ -14,20 +14,20 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
@ -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))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -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))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -18,7 +18,6 @@ package ipfs
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
||||
"github.com/ipfs/go-block-format"
|
||||
)
|
||||
|
||||
|
@ -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!!
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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))
|
||||
})
|
||||
})
|
||||
|
@ -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))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -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
|
||||
|
1
vendor/github.com/dgraph-io/badger/.gitignore
generated
vendored
Normal file
1
vendor/github.com/dgraph-io/badger/.gitignore
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
p/
|
20
vendor/github.com/dgraph-io/badger/.golangci.yml
generated
vendored
Normal file
20
vendor/github.com/dgraph-io/badger/.golangci.yml
generated
vendored
Normal file
@ -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
|
25
vendor/github.com/dgraph-io/badger/.travis.yml
generated
vendored
Normal file
25
vendor/github.com/dgraph-io/badger/.travis.yml
generated
vendored
Normal file
@ -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
|
7
vendor/github.com/dgraph-io/badger/README.md
generated
vendored
7
vendor/github.com/dgraph-io/badger/README.md
generated
vendored
@ -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.
|
||||
|
||||
|
145
vendor/github.com/dgraph-io/badger/backup.go
generated
vendored
145
vendor/github.com/dgraph-io/badger/backup.go
generated
vendored
@ -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
|
||||
}
|
||||
|
519
vendor/github.com/dgraph-io/badger/backup_test.go
generated
vendored
519
vendor/github.com/dgraph-io/badger/backup_test.go
generated
vendored
@ -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)
|
||||
}
|
1
vendor/github.com/dgraph-io/badger/badger/.gitignore
generated
vendored
1
vendor/github.com/dgraph-io/badger/badger/.gitignore
generated
vendored
@ -1 +0,0 @@
|
||||
/badger
|
72
vendor/github.com/dgraph-io/badger/badger/cmd/backup.go
generated
vendored
72
vendor/github.com/dgraph-io/badger/badger/cmd/backup.go
generated
vendored
@ -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
|
||||
}
|
451
vendor/github.com/dgraph-io/badger/badger/cmd/bank.go
generated
vendored
451
vendor/github.com/dgraph-io/badger/badger/cmd/bank.go
generated
vendored
@ -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")
|
||||
}
|
93
vendor/github.com/dgraph-io/badger/badger/cmd/fill.go
generated
vendored
93
vendor/github.com/dgraph-io/badger/badger/cmd/fill.go
generated
vendored
@ -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
|
||||
}
|
56
vendor/github.com/dgraph-io/badger/badger/cmd/flatten.go
generated
vendored
56
vendor/github.com/dgraph-io/badger/badger/cmd/flatten.go
generated
vendored
@ -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)
|
||||
}
|
294
vendor/github.com/dgraph-io/badger/badger/cmd/info.go
generated
vendored
294
vendor/github.com/dgraph-io/badger/badger/cmd/info.go
generated
vendored
@ -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"
|
||||
}
|
81
vendor/github.com/dgraph-io/badger/badger/cmd/restore.go
generated
vendored
81
vendor/github.com/dgraph-io/badger/badger/cmd/restore.go
generated
vendored
@ -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)
|
||||
}
|
65
vendor/github.com/dgraph-io/badger/badger/cmd/root.go
generated
vendored
65
vendor/github.com/dgraph-io/badger/badger/cmd/root.go
generated
vendored
@ -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
|
||||
}
|
42
vendor/github.com/dgraph-io/badger/badger/main.go
generated
vendored
42
vendor/github.com/dgraph-io/badger/badger/main.go
generated
vendored
@ -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()
|
||||
}
|
41
vendor/github.com/dgraph-io/badger/batch.go
generated
vendored
41
vendor/github.com/dgraph-io/badger/batch.go
generated
vendored
@ -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
|
||||
}
|
||||
|
||||
|
69
vendor/github.com/dgraph-io/badger/batch_test.go
generated
vendored
69
vendor/github.com/dgraph-io/badger/batch_test.go
generated
vendored
@ -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)
|
||||
})
|
||||
}
|
12
vendor/github.com/dgraph-io/badger/compaction.go
generated
vendored
12
vendor/github.com/dgraph-io/badger/compaction.go
generated
vendored
@ -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())
|
||||
|
23
vendor/github.com/dgraph-io/badger/contrib/cover.sh
generated
vendored
23
vendor/github.com/dgraph-io/badger/contrib/cover.sh
generated
vendored
@ -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
|
371
vendor/github.com/dgraph-io/badger/db.go
generated
vendored
371
vendor/github.com/dgraph-io/badger/db.go
generated
vendored
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
325
vendor/github.com/dgraph-io/badger/db2_test.go
generated
vendored
325
vendor/github.com/dgraph-io/badger/db2_test.go
generated
vendored
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
1708
vendor/github.com/dgraph-io/badger/db_test.go
generated
vendored
1708
vendor/github.com/dgraph-io/badger/db_test.go
generated
vendored
File diff suppressed because it is too large
Load Diff
4
vendor/github.com/dgraph-io/badger/dir_unix.go
generated
vendored
4
vendor/github.com/dgraph-io/badger/dir_unix.go
generated
vendored
@ -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
|
||||
|
2
vendor/github.com/dgraph-io/badger/dir_windows.go
generated
vendored
2
vendor/github.com/dgraph-io/badger/dir_windows.go
generated
vendored
@ -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.
|
||||
|
5
vendor/github.com/dgraph-io/badger/errors.go
generated
vendored
5
vendor/github.com/dgraph-io/badger/errors.go
generated
vendored
@ -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")
|
||||
)
|
||||
|
169
vendor/github.com/dgraph-io/badger/histogram.go
generated
vendored
Normal file
169
vendor/github.com/dgraph-io/badger/histogram.go
generated
vendored
Normal file
@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2019 Dgraph Labs, Inc. and Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package badger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// PrintHistogram builds and displays the key-value size histogram.
|
||||
// When keyPrefix is set, only the keys that have prefix "keyPrefix" are
|
||||
// considered for creating the histogram
|
||||
func (db *DB) PrintHistogram(keyPrefix []byte) {
|
||||
if db == nil {
|
||||
fmt.Println("\nCannot build histogram: DB is nil.")
|
||||
return
|
||||
}
|
||||
histogram := db.buildHistogram(keyPrefix)
|
||||
fmt.Printf("Histogram of key sizes (in bytes)\n")
|
||||
histogram.keySizeHistogram.printHistogram()
|
||||
fmt.Printf("Histogram of value sizes (in bytes)\n")
|
||||
histogram.valueSizeHistogram.printHistogram()
|
||||
}
|
||||
|
||||
// histogramData stores information about a histogram
|
||||
type histogramData struct {
|
||||
bins []int64
|
||||
countPerBin []int64
|
||||
totalCount int64
|
||||
min int64
|
||||
max int64
|
||||
sum int64
|
||||
}
|
||||
|
||||
// sizeHistogram contains keySize histogram and valueSize histogram
|
||||
type sizeHistogram struct {
|
||||
keySizeHistogram, valueSizeHistogram histogramData
|
||||
}
|
||||
|
||||
// newSizeHistogram returns a new instance of keyValueSizeHistogram with
|
||||
// properly initialized fields.
|
||||
func newSizeHistogram() *sizeHistogram {
|
||||
// TODO(ibrahim): find appropriate bin size.
|
||||
keyBins := createHistogramBins(1, 16)
|
||||
valueBins := createHistogramBins(1, 30)
|
||||
return &sizeHistogram{
|
||||
keySizeHistogram: histogramData{
|
||||
bins: keyBins,
|
||||
countPerBin: make([]int64, len(keyBins)+1),
|
||||
max: math.MinInt64,
|
||||
min: math.MaxInt64,
|
||||
sum: 0,
|
||||
},
|
||||
valueSizeHistogram: histogramData{
|
||||
bins: valueBins,
|
||||
countPerBin: make([]int64, len(valueBins)+1),
|
||||
max: math.MinInt64,
|
||||
min: math.MaxInt64,
|
||||
sum: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// createHistogramBins creates bins for an histogram. The bin sizes are powers
|
||||
// of two of the form [2^min_exponent, ..., 2^max_exponent].
|
||||
func createHistogramBins(minExponent, maxExponent uint32) []int64 {
|
||||
var bins []int64
|
||||
for i := minExponent; i <= maxExponent; i++ {
|
||||
bins = append(bins, int64(1)<<i)
|
||||
}
|
||||
return bins
|
||||
}
|
||||
|
||||
// Update the min and max fields if value is less than or greater than the
|
||||
// current min/max value.
|
||||
func (histogram *histogramData) Update(value int64) {
|
||||
if value > histogram.max {
|
||||
histogram.max = value
|
||||
}
|
||||
if value < histogram.min {
|
||||
histogram.min = value
|
||||
}
|
||||
|
||||
histogram.sum += value
|
||||
histogram.totalCount++
|
||||
|
||||
for index := 0; index <= len(histogram.bins); index++ {
|
||||
// Allocate value in the last buckets if we reached the end of the Bounds array.
|
||||
if index == len(histogram.bins) {
|
||||
histogram.countPerBin[index]++
|
||||
break
|
||||
}
|
||||
|
||||
// Check if the value should be added to the "index" bin
|
||||
if value < int64(histogram.bins[index]) {
|
||||
histogram.countPerBin[index]++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// buildHistogram builds the key-value size histogram.
|
||||
// When keyPrefix is set, only the keys that have prefix "keyPrefix" are
|
||||
// considered for creating the histogram
|
||||
func (db *DB) buildHistogram(keyPrefix []byte) *sizeHistogram {
|
||||
txn := db.NewTransaction(false)
|
||||
defer txn.Discard()
|
||||
|
||||
itr := txn.NewIterator(DefaultIteratorOptions)
|
||||
defer itr.Close()
|
||||
|
||||
badgerHistogram := newSizeHistogram()
|
||||
|
||||
// Collect key and value sizes.
|
||||
for itr.Seek(keyPrefix); itr.ValidForPrefix(keyPrefix); itr.Next() {
|
||||
item := itr.Item()
|
||||
badgerHistogram.keySizeHistogram.Update(item.KeySize())
|
||||
badgerHistogram.valueSizeHistogram.Update(item.ValueSize())
|
||||
}
|
||||
return badgerHistogram
|
||||
}
|
||||
|
||||
// printHistogram prints the histogram data in a human-readable format.
|
||||
func (histogram histogramData) printHistogram() {
|
||||
fmt.Printf("Total count: %d\n", histogram.totalCount)
|
||||
fmt.Printf("Min value: %d\n", histogram.min)
|
||||
fmt.Printf("Max value: %d\n", histogram.max)
|
||||
fmt.Printf("Mean: %.2f\n", float64(histogram.sum)/float64(histogram.totalCount))
|
||||
fmt.Printf("%24s %9s\n", "Range", "Count")
|
||||
|
||||
numBins := len(histogram.bins)
|
||||
for index, count := range histogram.countPerBin {
|
||||
if count == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// The last bin represents the bin that contains the range from
|
||||
// the last bin up to infinity so it's processed differently than the
|
||||
// other bins.
|
||||
if index == len(histogram.countPerBin)-1 {
|
||||
lowerBound := int(histogram.bins[numBins-1])
|
||||
fmt.Printf("[%10d, %10s) %9d\n", lowerBound, "infinity", count)
|
||||
continue
|
||||
}
|
||||
|
||||
upperBound := int(histogram.bins[index])
|
||||
lowerBound := 0
|
||||
if index > 0 {
|
||||
lowerBound = int(histogram.bins[index-1])
|
||||
}
|
||||
|
||||
fmt.Printf("[%10d, %10d) %9d\n", lowerBound, upperBound, count)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
BIN
vendor/github.com/dgraph-io/badger/images/benchmarks-rocksdb.png
generated
vendored
BIN
vendor/github.com/dgraph-io/badger/images/benchmarks-rocksdb.png
generated
vendored
Binary file not shown.
Before Width: | Height: | Size: 65 KiB |
BIN
vendor/github.com/dgraph-io/badger/images/diggy-shadow.png
generated
vendored
BIN
vendor/github.com/dgraph-io/badger/images/diggy-shadow.png
generated
vendored
Binary file not shown.
Before Width: | Height: | Size: 32 KiB |
1
vendor/github.com/dgraph-io/badger/integration/testgc/.gitignore
generated
vendored
1
vendor/github.com/dgraph-io/badger/integration/testgc/.gitignore
generated
vendored
@ -1 +0,0 @@
|
||||
/testgc
|
218
vendor/github.com/dgraph-io/badger/integration/testgc/main.go
generated
vendored
218
vendor/github.com/dgraph-io/badger/integration/testgc/main.go
generated
vendored
@ -1,218 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger"
|
||||
"gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/options"
|
||||
"gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y"
|
||||
)
|
||||
|
||||
var maxValue int64 = 10000000
|
||||
var suffix = make([]byte, 128)
|
||||
|
||||
type testSuite struct {
|
||||
sync.Mutex
|
||||
vals map[uint64]uint64
|
||||
|
||||
count uint64 // Not under mutex lock.
|
||||
}
|
||||
|
||||
func encoded(i uint64) []byte {
|
||||
out := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(out, i)
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *testSuite) write(db *badger.DB) error {
|
||||
return db.Update(func(txn *badger.Txn) error {
|
||||
for i := 0; i < 10; i++ {
|
||||
// These keys would be overwritten.
|
||||
keyi := uint64(rand.Int63n(maxValue))
|
||||
key := encoded(keyi)
|
||||
vali := atomic.AddUint64(&s.count, 1)
|
||||
val := encoded(vali)
|
||||
val = append(val, suffix...)
|
||||
if err := txn.Set(key, val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for i := 0; i < 20; i++ {
|
||||
// These keys would be new and never overwritten.
|
||||
keyi := atomic.AddUint64(&s.count, 1)
|
||||
if keyi%1000000 == 0 {
|
||||
log.Printf("Count: %d\n", keyi)
|
||||
}
|
||||
key := encoded(keyi)
|
||||
val := append(key, suffix...)
|
||||
if err := txn.Set(key, val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *testSuite) read(db *badger.DB) error {
|
||||
max := int64(atomic.LoadUint64(&s.count))
|
||||
keyi := uint64(rand.Int63n(max))
|
||||
key := encoded(keyi)
|
||||
|
||||
err := db.View(func(txn *badger.Txn) error {
|
||||
item, err := txn.Get(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
val, err := item.ValueCopy(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
y.AssertTruef(len(val) == len(suffix)+8, "Found val of len: %d\n", len(val))
|
||||
vali := binary.BigEndian.Uint64(val[0:8])
|
||||
s.Lock()
|
||||
expected := s.vals[keyi]
|
||||
if vali < expected {
|
||||
log.Fatalf("Expected: %d. Found: %d. Key: %d\n", expected, vali, keyi)
|
||||
} else if vali == expected {
|
||||
// pass
|
||||
} else {
|
||||
s.vals[keyi] = vali
|
||||
}
|
||||
s.Unlock()
|
||||
return nil
|
||||
})
|
||||
if err == badger.ErrKeyNotFound {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("Badger Integration test for value log GC.")
|
||||
|
||||
dir := "/mnt/drive/badgertest"
|
||||
os.RemoveAll(dir)
|
||||
|
||||
opts := badger.DefaultOptions
|
||||
opts.Dir = dir
|
||||
opts.ValueDir = dir
|
||||
opts.TableLoadingMode = options.MemoryMap
|
||||
opts.ValueLogLoadingMode = options.FileIO
|
||||
opts.SyncWrites = false
|
||||
|
||||
db, err := badger.Open(opts)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
go http.ListenAndServe("localhost:8080", nil)
|
||||
|
||||
closer := y.NewCloser(11)
|
||||
go func() {
|
||||
// Run value log GC.
|
||||
defer closer.Done()
|
||||
var count int
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
again:
|
||||
select {
|
||||
case <-closer.HasBeenClosed():
|
||||
log.Printf("Num times value log GC was successful: %d\n", count)
|
||||
return
|
||||
default:
|
||||
}
|
||||
log.Printf("Starting a value log GC")
|
||||
err := db.RunValueLogGC(0.1)
|
||||
log.Printf("Result of value log GC: %v\n", err)
|
||||
if err == nil {
|
||||
count++
|
||||
goto again
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
s := testSuite{
|
||||
count: uint64(maxValue),
|
||||
vals: make(map[uint64]uint64),
|
||||
}
|
||||
var numLoops uint64
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
for i := 0; i < 10; i++ {
|
||||
go func() {
|
||||
defer closer.Done()
|
||||
for {
|
||||
if err := s.write(db); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for j := 0; j < 10; j++ {
|
||||
if err := s.read(db); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
nl := atomic.AddUint64(&numLoops, 1)
|
||||
select {
|
||||
case <-closer.HasBeenClosed():
|
||||
return
|
||||
case <-ticker.C:
|
||||
log.Printf("Num loops: %d\n", nl)
|
||||
default:
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
time.Sleep(5 * time.Minute)
|
||||
log.Println("Signaling...")
|
||||
closer.SignalAndWait()
|
||||
log.Println("Wait done. Now iterating over everything.")
|
||||
|
||||
err = db.View(func(txn *badger.Txn) error {
|
||||
iopts := badger.DefaultIteratorOptions
|
||||
itr := txn.NewIterator(iopts)
|
||||
defer itr.Close()
|
||||
|
||||
var total, tested int
|
||||
for itr.Rewind(); itr.Valid(); itr.Next() {
|
||||
item := itr.Item()
|
||||
key := item.Key()
|
||||
keyi := binary.BigEndian.Uint64(key)
|
||||
total++
|
||||
|
||||
val, err := item.ValueCopy(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(val) < 8 {
|
||||
log.Printf("Unexpected value: %x\n", val)
|
||||
continue
|
||||
}
|
||||
vali := binary.BigEndian.Uint64(val[0:8])
|
||||
|
||||
expected, ok := s.vals[keyi] // Not all keys must be in vals map.
|
||||
if ok {
|
||||
tested++
|
||||
if vali < expected {
|
||||
// vali must be equal or greater than what's in the map.
|
||||
log.Fatalf("Expected: %d. Got: %d. Key: %d\n", expected, vali, keyi)
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Printf("Total iterated: %d. Tested values: %d\n", total, tested)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Error while iterating: %v", err)
|
||||
}
|
||||
log.Println("Iteration done. Test successful.")
|
||||
time.Sleep(time.Minute) // Time to do some poking around.
|
||||
}
|
14
vendor/github.com/dgraph-io/badger/iterator.go
generated
vendored
14
vendor/github.com/dgraph-io/badger/iterator.go
generated
vendored
@ -24,10 +24,10 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/options"
|
||||
"gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/table"
|
||||
"github.com/dgraph-io/badger/options"
|
||||
"github.com/dgraph-io/badger/table"
|
||||
|
||||
"gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y"
|
||||
"github.com/dgraph-io/badger/y"
|
||||
)
|
||||
|
||||
type prefetchStatus uint8
|
||||
@ -140,7 +140,7 @@ func (item *Item) IsDeletedOrExpired() bool {
|
||||
return isDeletedOrExpired(item.meta, item.expiresAt)
|
||||
}
|
||||
|
||||
// DiscardEarlierVersions returns whether the iterator was created with the
|
||||
// DiscardEarlierVersions returns whether the item was created with the
|
||||
// option to discard earlier versions of a key when multiple are available.
|
||||
func (item *Item) DiscardEarlierVersions() bool {
|
||||
return item.meta&bitDiscardEarlierVersions > 0
|
||||
@ -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
|
||||
|
244
vendor/github.com/dgraph-io/badger/iterator_test.go
generated
vendored
244
vendor/github.com/dgraph-io/badger/iterator_test.go
generated
vendored
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
67
vendor/github.com/dgraph-io/badger/level_handler.go
generated
vendored
67
vendor/github.com/dgraph-io/badger/level_handler.go
generated
vendored
@ -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
|
||||
})
|
||||
|
182
vendor/github.com/dgraph-io/badger/levels.go
generated
vendored
182
vendor/github.com/dgraph-io/badger/levels.go
generated
vendored
@ -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 {
|
||||
|
19
vendor/github.com/dgraph-io/badger/logger.go
generated
vendored
19
vendor/github.com/dgraph-io/badger/logger.go
generated
vendored
@ -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...)
|
||||
}
|
||||
|
67
vendor/github.com/dgraph-io/badger/logger_test.go
generated
vendored
67
vendor/github.com/dgraph-io/badger/logger_test.go
generated
vendored
@ -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)
|
||||
}
|
353
vendor/github.com/dgraph-io/badger/managed_db_test.go
generated
vendored
353
vendor/github.com/dgraph-io/badger/managed_db_test.go
generated
vendored
@ -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()
|
||||
}
|
6
vendor/github.com/dgraph-io/badger/manifest.go
generated
vendored
6
vendor/github.com/dgraph-io/badger/manifest.go
generated
vendored
@ -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.
|
||||
|
244
vendor/github.com/dgraph-io/badger/manifest_test.go
generated
vendored
244
vendor/github.com/dgraph-io/badger/manifest_test.go
generated
vendored
@ -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)
|
||||
}
|
14
vendor/github.com/dgraph-io/badger/merge.go
generated
vendored
14
vendor/github.com/dgraph-io/badger/merge.go
generated
vendored
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
35
vendor/github.com/dgraph-io/badger/options.go
generated
vendored
35
vendor/github.com/dgraph-io/badger/options.go
generated
vendored
@ -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
|
||||
|
72
vendor/github.com/dgraph-io/badger/package.json
generated
vendored
72
vendor/github.com/dgraph-io/badger/package.json
generated
vendored
@ -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"
|
||||
}
|
||||
|
2
vendor/github.com/dgraph-io/badger/pb/gen.sh
generated
vendored
Normal file → Executable file
2
vendor/github.com/dgraph-io/badger/pb/gen.sh
generated
vendored
Normal file → Executable file
@ -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
|
||||
|
139
vendor/github.com/dgraph-io/badger/pb/pb.pb.go
generated
vendored
139
vendor/github.com/dgraph-io/badger/pb/pb.pb.go
generated
vendored
@ -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
|
||||
}
|
||||
}
|
||||
|
3
vendor/github.com/dgraph-io/badger/pb/pb.proto
generated
vendored
3
vendor/github.com/dgraph-io/badger/pb/pb.proto
generated
vendored
@ -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 {
|
||||
|
151
vendor/github.com/dgraph-io/badger/publisher.go
generated
vendored
Normal file
151
vendor/github.com/dgraph-io/badger/publisher.go
generated
vendored
Normal file
@ -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)
|
||||
}
|
2
vendor/github.com/dgraph-io/badger/skl/arena.go
generated
vendored
2
vendor/github.com/dgraph-io/badger/skl/arena.go
generated
vendored
@ -20,7 +20,7 @@ import (
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
"gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y"
|
||||
"github.com/dgraph-io/badger/y"
|
||||
)
|
||||
|
||||
const (
|
||||
|
2
vendor/github.com/dgraph-io/badger/skl/skl.go
generated
vendored
2
vendor/github.com/dgraph-io/badger/skl/skl.go
generated
vendored
@ -38,7 +38,7 @@ import (
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
"gx/ipfs/QmU4emVTYFKnoJ5yK3pPEN9joyEx6U7y892PDx26ZtNxQd/badger/y"
|
||||
"github.com/dgraph-io/badger/y"
|
||||
)
|
||||
|
||||
const (
|
||||
|
475
vendor/github.com/dgraph-io/badger/skl/skl_test.go
generated
vendored
475
vendor/github.com/dgraph-io/badger/skl/skl_test.go
generated
vendored
@ -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()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
60
vendor/github.com/dgraph-io/badger/stream.go
generated
vendored
60
vendor/github.com/dgraph-io/badger/stream.go
generated
vendored
@ -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
|
||||
|
169
vendor/github.com/dgraph-io/badger/stream_test.go
generated
vendored
169
vendor/github.com/dgraph-io/badger/stream_test.go
generated
vendored
@ -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)
|
||||
}
|
||||
}
|
311
vendor/github.com/dgraph-io/badger/stream_writer.go
generated
vendored
Normal file
311
vendor/github.com/dgraph-io/badger/stream_writer.go
generated
vendored
Normal file
@ -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
|
||||
}
|
5
vendor/github.com/dgraph-io/badger/structs.go
generated
vendored
5
vendor/github.com/dgraph-io/badger/structs.go
generated
vendored
@ -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 {
|
||||
|
8
vendor/github.com/dgraph-io/badger/table/builder.go
generated
vendored
8
vendor/github.com/dgraph-io/badger/table/builder.go
generated
vendored
@ -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
|
||||
|
4
vendor/github.com/dgraph-io/badger/table/iterator.go
generated
vendored
4
vendor/github.com/dgraph-io/badger/table/iterator.go
generated
vendored
@ -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 {
|
||||
|
16
vendor/github.com/dgraph-io/badger/table/table.go
generated
vendored
16
vendor/github.com/dgraph-io/badger/table/table.go
generated
vendored
@ -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)
|
||||
|
729
vendor/github.com/dgraph-io/badger/table/table_test.go
generated
vendored
729
vendor/github.com/dgraph-io/badger/table/table_test.go
generated
vendored
@ -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() {
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
9
vendor/github.com/dgraph-io/badger/test.sh
generated
vendored
Normal file → Executable file
9
vendor/github.com/dgraph-io/badger/test.sh
generated
vendored
Normal file → Executable file
@ -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 ./...
|
||||
|
20
vendor/github.com/dgraph-io/badger/txn.go
generated
vendored
20
vendor/github.com/dgraph-io/badger/txn.go
generated
vendored
@ -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]))
|
||||
|
845
vendor/github.com/dgraph-io/badger/txn_test.go
generated
vendored
845
vendor/github.com/dgraph-io/badger/txn_test.go
generated
vendored
@ -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)
|
||||
}
|
||||
}
|
6
vendor/github.com/dgraph-io/badger/util.go
generated
vendored
6
vendor/github.com/dgraph-io/badger/util.go
generated
vendored
@ -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.
|
||||
|
132
vendor/github.com/dgraph-io/badger/value.go
generated
vendored
132
vendor/github.com/dgraph-io/badger/value.go
generated
vendored
@ -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
|
||||
}
|
||||
|
880
vendor/github.com/dgraph-io/badger/value_test.go
generated
vendored
880
vendor/github.com/dgraph-io/badger/value_test.go
generated
vendored
@ -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()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
2
vendor/github.com/dgraph-io/badger/y/error.go
generated
vendored
2
vendor/github.com/dgraph-io/badger/y/error.go
generated
vendored
@ -32,7 +32,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"gx/ipfs/QmVmDhyTTUcQXFD1rRQ64fGLMSAoaQvNH3hwuaCFAPq2hy/errors"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var debugMode = true
|
||||
|
2
vendor/github.com/dgraph-io/badger/y/file_dsync.go
generated
vendored
2
vendor/github.com/dgraph-io/badger/y/file_dsync.go
generated
vendored
@ -18,7 +18,7 @@
|
||||
|
||||
package y
|
||||
|
||||
import "gx/ipfs/QmVGjyM9i2msKvLXwh9VosCTgP4mL91kC7hDmqnwTTx6Hu/sys/unix"
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
func init() {
|
||||
datasyncFileFlag = unix.O_DSYNC
|
||||
|
2
vendor/github.com/dgraph-io/badger/y/iterator.go
generated
vendored
2
vendor/github.com/dgraph-io/badger/y/iterator.go
generated
vendored
@ -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
|
||||
|
234
vendor/github.com/dgraph-io/badger/y/iterator_test.go
generated
vendored
234
vendor/github.com/dgraph-io/badger/y/iterator_test.go
generated
vendored
@ -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)
|
||||
}
|
2
vendor/github.com/dgraph-io/badger/y/mmap_unix.go
generated
vendored
2
vendor/github.com/dgraph-io/badger/y/mmap_unix.go
generated
vendored
@ -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,
|
||||
|
10
vendor/github.com/dgraph-io/badger/y/watermark.go
generated
vendored
10
vendor/github.com/dgraph-io/badger/y/watermark.go
generated
vendored
@ -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)
|
||||
|
48
vendor/github.com/dgraph-io/badger/y/y.go
generated
vendored
48
vendor/github.com/dgraph-io/badger/y/y.go
generated
vendored
@ -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<timestamp> would be sorted higher than aa<timestamp> 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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user