2016-02-10 00:12:00 +01:00
|
|
|
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
2013-10-16 23:59:46 +02:00
|
|
|
// This source code is licensed under the BSD-style license found in the
|
|
|
|
// LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
// of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
//
|
2011-03-18 23:37:00 +01:00
|
|
|
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
|
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
|
|
|
|
2013-10-05 07:32:05 +02:00
|
|
|
#pragma once
|
2011-03-18 23:37:00 +01:00
|
|
|
#include <stddef.h>
|
|
|
|
#include <stdint.h>
|
2016-06-14 21:27:46 +02:00
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
Use malloc_usable_size() for accounting block cache size
Summary:
Currently, when we insert something into block cache, we say that the block cache capacity decreased by the size of the block. However, size of the block might be less than the actual memory used by this object. For example, 4.5KB block will actually use 8KB of memory. So even if we configure block cache to 10GB, our actually memory usage of block cache will be 20GB!
This problem showed up a lot in testing and just recently also showed up in MongoRocks production where we were using 30GB more memory than expected.
This diff will fix the problem. Instead of counting the block size, we will count memory used by the block. That way, a block cache configured to be 10GB will actually use only 10GB of memory.
I'm using non-portable function and I couldn't find info on portability on Google. However, it seems to work on Linux, which will cover majority of our use-cases.
Test Plan:
1. fill up mongo instance with 80GB of data
2. restart mongo with block cache size configured to 10GB
3. do a table scan in mongo
4. memory usage before the diff: 12GB. memory usage after the diff: 10.5GB
Reviewers: sdong, MarkCallaghan, rven, yhchiang
Reviewed By: yhchiang
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D40635
2015-06-26 20:48:09 +02:00
|
|
|
#ifdef ROCKSDB_MALLOC_USABLE_SIZE
|
|
|
|
#include <malloc.h>
|
|
|
|
#endif
|
2014-04-10 23:19:43 +02:00
|
|
|
|
2016-04-26 21:41:07 +02:00
|
|
|
#include "db/dbformat.h"
|
|
|
|
#include "db/pinned_iterators_manager.h"
|
2013-08-23 17:38:13 +02:00
|
|
|
#include "rocksdb/iterator.h"
|
2013-09-02 08:23:40 +02:00
|
|
|
#include "rocksdb/options.h"
|
2016-04-26 21:41:07 +02:00
|
|
|
#include "table/block_prefix_index.h"
|
2015-10-13 00:06:38 +02:00
|
|
|
#include "table/internal_iterator.h"
|
2014-08-16 00:05:09 +02:00
|
|
|
|
|
|
|
#include "format.h"
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2013-10-04 06:49:15 +02:00
|
|
|
namespace rocksdb {
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2012-04-17 17:36:46 +02:00
|
|
|
struct BlockContents;
|
2011-03-18 23:37:00 +01:00
|
|
|
class Comparator;
|
2014-07-31 01:34:35 +02:00
|
|
|
class BlockIter;
|
2014-06-13 04:03:22 +02:00
|
|
|
class BlockPrefixIndex;
|
2011-03-18 23:37:00 +01:00
|
|
|
|
|
|
|
class Block {
|
|
|
|
public:
|
|
|
|
// Initialize the block with the specified contents.
|
2014-08-16 00:05:09 +02:00
|
|
|
explicit Block(BlockContents&& contents);
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2014-08-16 00:05:09 +02:00
|
|
|
~Block() = default;
|
2011-03-18 23:37:00 +01:00
|
|
|
|
|
|
|
size_t size() const { return size_; }
|
2014-04-10 23:19:43 +02:00
|
|
|
const char* data() const { return data_; }
|
2014-08-16 00:05:09 +02:00
|
|
|
bool cachable() const { return contents_.cachable; }
|
Use malloc_usable_size() for accounting block cache size
Summary:
Currently, when we insert something into block cache, we say that the block cache capacity decreased by the size of the block. However, size of the block might be less than the actual memory used by this object. For example, 4.5KB block will actually use 8KB of memory. So even if we configure block cache to 10GB, our actually memory usage of block cache will be 20GB!
This problem showed up a lot in testing and just recently also showed up in MongoRocks production where we were using 30GB more memory than expected.
This diff will fix the problem. Instead of counting the block size, we will count memory used by the block. That way, a block cache configured to be 10GB will actually use only 10GB of memory.
I'm using non-portable function and I couldn't find info on portability on Google. However, it seems to work on Linux, which will cover majority of our use-cases.
Test Plan:
1. fill up mongo instance with 80GB of data
2. restart mongo with block cache size configured to 10GB
3. do a table scan in mongo
4. memory usage before the diff: 12GB. memory usage after the diff: 10.5GB
Reviewers: sdong, MarkCallaghan, rven, yhchiang
Reviewed By: yhchiang
Subscribers: dhruba, leveldb
Differential Revision: https://reviews.facebook.net/D40635
2015-06-26 20:48:09 +02:00
|
|
|
size_t usable_size() const {
|
|
|
|
#ifdef ROCKSDB_MALLOC_USABLE_SIZE
|
|
|
|
if (contents_.allocation.get() != nullptr) {
|
|
|
|
return malloc_usable_size(contents_.allocation.get());
|
|
|
|
}
|
|
|
|
#endif // ROCKSDB_MALLOC_USABLE_SIZE
|
|
|
|
return size_;
|
|
|
|
}
|
2014-04-10 23:19:43 +02:00
|
|
|
uint32_t NumRestarts() const;
|
2014-09-18 00:08:19 +02:00
|
|
|
CompressionType compression_type() const {
|
|
|
|
return contents_.compression_type;
|
|
|
|
}
|
2014-04-10 23:19:43 +02:00
|
|
|
|
|
|
|
// If hash index lookup is enabled and `use_hash_index` is true. This block
|
|
|
|
// will do hash lookup for the key prefix.
|
|
|
|
//
|
|
|
|
// NOTE: for the hash based lookup, if a key prefix doesn't match any key,
|
|
|
|
// the iterator will simply be set as "invalid", rather than returning
|
|
|
|
// the key that is just pass the target key.
|
2014-07-31 01:34:35 +02:00
|
|
|
//
|
|
|
|
// If iter is null, return new Iterator
|
|
|
|
// If iter is not null, update this one and return it as Iterator*
|
2014-08-26 01:14:30 +02:00
|
|
|
//
|
|
|
|
// If total_order_seek is true, hash_index_ and prefix_index_ are ignored.
|
|
|
|
// This option only applies for index block. For data block, hash_index_
|
|
|
|
// and prefix_index_ are null, so this option does not matter.
|
2015-10-13 00:06:38 +02:00
|
|
|
InternalIterator* NewIterator(const Comparator* comparator,
|
|
|
|
BlockIter* iter = nullptr,
|
|
|
|
bool total_order_seek = true);
|
2014-06-13 04:03:22 +02:00
|
|
|
void SetBlockPrefixIndex(BlockPrefixIndex* prefix_index);
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2014-08-05 20:27:34 +02:00
|
|
|
// Report an approximation of how much memory has been used.
|
|
|
|
size_t ApproximateMemoryUsage() const;
|
|
|
|
|
2011-03-18 23:37:00 +01:00
|
|
|
private:
|
2014-08-16 00:05:09 +02:00
|
|
|
BlockContents contents_;
|
2014-09-18 01:45:58 +02:00
|
|
|
const char* data_; // contents_.data.data()
|
|
|
|
size_t size_; // contents_.data.size()
|
2011-03-18 23:37:00 +01:00
|
|
|
uint32_t restart_offset_; // Offset in data_ of restart array
|
2014-06-13 04:03:22 +02:00
|
|
|
std::unique_ptr<BlockPrefixIndex> prefix_index_;
|
2011-03-18 23:37:00 +01:00
|
|
|
|
|
|
|
// No copying allowed
|
|
|
|
Block(const Block&);
|
|
|
|
void operator=(const Block&);
|
2014-07-31 01:34:35 +02:00
|
|
|
};
|
|
|
|
|
2015-10-13 00:06:38 +02:00
|
|
|
class BlockIter : public InternalIterator {
|
2014-07-31 01:34:35 +02:00
|
|
|
public:
|
|
|
|
BlockIter()
|
|
|
|
: comparator_(nullptr),
|
|
|
|
data_(nullptr),
|
|
|
|
restarts_(0),
|
|
|
|
num_restarts_(0),
|
|
|
|
current_(0),
|
|
|
|
restart_index_(0),
|
|
|
|
status_(Status::OK()),
|
2016-06-14 21:27:46 +02:00
|
|
|
prefix_index_(nullptr),
|
|
|
|
key_pinned_(false) {}
|
2014-07-31 01:34:35 +02:00
|
|
|
|
|
|
|
BlockIter(const Comparator* comparator, const char* data, uint32_t restarts,
|
2016-05-21 02:14:38 +02:00
|
|
|
uint32_t num_restarts, BlockPrefixIndex* prefix_index)
|
2014-07-31 01:34:35 +02:00
|
|
|
: BlockIter() {
|
2016-05-21 02:14:38 +02:00
|
|
|
Initialize(comparator, data, restarts, num_restarts, prefix_index);
|
2014-07-31 01:34:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Initialize(const Comparator* comparator, const char* data,
|
2016-05-21 02:14:38 +02:00
|
|
|
uint32_t restarts, uint32_t num_restarts,
|
|
|
|
BlockPrefixIndex* prefix_index) {
|
2014-07-31 01:34:35 +02:00
|
|
|
assert(data_ == nullptr); // Ensure it is called only once
|
|
|
|
assert(num_restarts > 0); // Ensure the param is valid
|
|
|
|
|
|
|
|
comparator_ = comparator;
|
|
|
|
data_ = data;
|
|
|
|
restarts_ = restarts;
|
|
|
|
num_restarts_ = num_restarts;
|
|
|
|
current_ = restarts_;
|
|
|
|
restart_index_ = num_restarts_;
|
|
|
|
prefix_index_ = prefix_index;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SetStatus(Status s) {
|
|
|
|
status_ = s;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual bool Valid() const override { return current_ < restarts_; }
|
|
|
|
virtual Status status() const override { return status_; }
|
|
|
|
virtual Slice key() const override {
|
|
|
|
assert(Valid());
|
|
|
|
return key_.GetKey();
|
|
|
|
}
|
|
|
|
virtual Slice value() const override {
|
|
|
|
assert(Valid());
|
|
|
|
return value_;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void Next() override;
|
|
|
|
|
|
|
|
virtual void Prev() override;
|
|
|
|
|
|
|
|
virtual void Seek(const Slice& target) override;
|
|
|
|
|
|
|
|
virtual void SeekToFirst() override;
|
|
|
|
|
|
|
|
virtual void SeekToLast() override;
|
|
|
|
|
2016-04-26 21:41:07 +02:00
|
|
|
#ifndef NDEBUG
|
|
|
|
~BlockIter() {
|
|
|
|
// Assert that the BlockIter is never deleted while Pinning is Enabled.
|
|
|
|
assert(!pinned_iters_mgr_ ||
|
|
|
|
(pinned_iters_mgr_ && !pinned_iters_mgr_->PinningEnabled()));
|
2015-12-16 21:08:30 +01:00
|
|
|
}
|
2016-04-26 21:41:07 +02:00
|
|
|
virtual void SetPinnedItersMgr(
|
|
|
|
PinnedIteratorsManager* pinned_iters_mgr) override {
|
|
|
|
pinned_iters_mgr_ = pinned_iters_mgr;
|
2015-12-16 21:08:30 +01:00
|
|
|
}
|
2016-04-26 21:41:07 +02:00
|
|
|
PinnedIteratorsManager* pinned_iters_mgr_ = nullptr;
|
|
|
|
#endif
|
2015-12-16 21:08:30 +01:00
|
|
|
|
2016-06-14 21:27:46 +02:00
|
|
|
virtual bool IsKeyPinned() const override { return key_pinned_; }
|
2015-12-16 21:08:30 +01:00
|
|
|
|
Introduce FullMergeV2 (eliminate memcpy from merge operators)
Summary:
This diff update the code to pin the merge operator operands while the merge operation is done, so that we can eliminate the memcpy cost, to do that we need a new public API for FullMerge that replace the std::deque<std::string> with std::vector<Slice>
This diff is stacked on top of D56493 and D56511
In this diff we
- Update FullMergeV2 arguments to be encapsulated in MergeOperationInput and MergeOperationOutput which will make it easier to add new arguments in the future
- Replace std::deque<std::string> with std::vector<Slice> to pass operands
- Replace MergeContext std::deque with std::vector (based on a simple benchmark I ran https://gist.github.com/IslamAbdelRahman/78fc86c9ab9f52b1df791e58943fb187)
- Allow FullMergeV2 output to be an existing operand
```
[Everything in Memtable | 10K operands | 10 KB each | 1 operand per key]
DEBUG_LEVEL=0 make db_bench -j64 && ./db_bench --benchmarks="mergerandom,readseq,readseq,readseq,readseq,readseq" --merge_operator="max" --merge_keys=10000 --num=10000 --disable_auto_compactions --value_size=10240 --write_buffer_size=1000000000
[FullMergeV2]
readseq : 0.607 micros/op 1648235 ops/sec; 16121.2 MB/s
readseq : 0.478 micros/op 2091546 ops/sec; 20457.2 MB/s
readseq : 0.252 micros/op 3972081 ops/sec; 38850.5 MB/s
readseq : 0.237 micros/op 4218328 ops/sec; 41259.0 MB/s
readseq : 0.247 micros/op 4043927 ops/sec; 39553.2 MB/s
[master]
readseq : 3.935 micros/op 254140 ops/sec; 2485.7 MB/s
readseq : 3.722 micros/op 268657 ops/sec; 2627.7 MB/s
readseq : 3.149 micros/op 317605 ops/sec; 3106.5 MB/s
readseq : 3.125 micros/op 320024 ops/sec; 3130.1 MB/s
readseq : 4.075 micros/op 245374 ops/sec; 2400.0 MB/s
```
```
[Everything in Memtable | 10K operands | 10 KB each | 10 operand per key]
DEBUG_LEVEL=0 make db_bench -j64 && ./db_bench --benchmarks="mergerandom,readseq,readseq,readseq,readseq,readseq" --merge_operator="max" --merge_keys=1000 --num=10000 --disable_auto_compactions --value_size=10240 --write_buffer_size=1000000000
[FullMergeV2]
readseq : 3.472 micros/op 288018 ops/sec; 2817.1 MB/s
readseq : 2.304 micros/op 434027 ops/sec; 4245.2 MB/s
readseq : 1.163 micros/op 859845 ops/sec; 8410.0 MB/s
readseq : 1.192 micros/op 838926 ops/sec; 8205.4 MB/s
readseq : 1.250 micros/op 800000 ops/sec; 7824.7 MB/s
[master]
readseq : 24.025 micros/op 41623 ops/sec; 407.1 MB/s
readseq : 18.489 micros/op 54086 ops/sec; 529.0 MB/s
readseq : 18.693 micros/op 53495 ops/sec; 523.2 MB/s
readseq : 23.621 micros/op 42335 ops/sec; 414.1 MB/s
readseq : 18.775 micros/op 53262 ops/sec; 521.0 MB/s
```
```
[Everything in Block cache | 10K operands | 10 KB each | 1 operand per key]
[FullMergeV2]
$ DEBUG_LEVEL=0 make db_bench -j64 && ./db_bench --benchmarks="readseq,readseq,readseq,readseq,readseq" --merge_operator="max" --num=100000 --db="/dev/shm/merge-random-10K-10KB" --cache_size=1000000000 --use_existing_db --disable_auto_compactions
readseq : 14.741 micros/op 67837 ops/sec; 663.5 MB/s
readseq : 1.029 micros/op 971446 ops/sec; 9501.6 MB/s
readseq : 0.974 micros/op 1026229 ops/sec; 10037.4 MB/s
readseq : 0.965 micros/op 1036080 ops/sec; 10133.8 MB/s
readseq : 0.943 micros/op 1060657 ops/sec; 10374.2 MB/s
[master]
readseq : 16.735 micros/op 59755 ops/sec; 584.5 MB/s
readseq : 3.029 micros/op 330151 ops/sec; 3229.2 MB/s
readseq : 3.136 micros/op 318883 ops/sec; 3119.0 MB/s
readseq : 3.065 micros/op 326245 ops/sec; 3191.0 MB/s
readseq : 3.014 micros/op 331813 ops/sec; 3245.4 MB/s
```
```
[Everything in Block cache | 10K operands | 10 KB each | 10 operand per key]
DEBUG_LEVEL=0 make db_bench -j64 && ./db_bench --benchmarks="readseq,readseq,readseq,readseq,readseq" --merge_operator="max" --num=100000 --db="/dev/shm/merge-random-10-operands-10K-10KB" --cache_size=1000000000 --use_existing_db --disable_auto_compactions
[FullMergeV2]
readseq : 24.325 micros/op 41109 ops/sec; 402.1 MB/s
readseq : 1.470 micros/op 680272 ops/sec; 6653.7 MB/s
readseq : 1.231 micros/op 812347 ops/sec; 7945.5 MB/s
readseq : 1.091 micros/op 916590 ops/sec; 8965.1 MB/s
readseq : 1.109 micros/op 901713 ops/sec; 8819.6 MB/s
[master]
readseq : 27.257 micros/op 36687 ops/sec; 358.8 MB/s
readseq : 4.443 micros/op 225073 ops/sec; 2201.4 MB/s
readseq : 5.830 micros/op 171526 ops/sec; 1677.7 MB/s
readseq : 4.173 micros/op 239635 ops/sec; 2343.8 MB/s
readseq : 4.150 micros/op 240963 ops/sec; 2356.8 MB/s
```
Test Plan: COMPILE_WITH_ASAN=1 make check -j64
Reviewers: yhchiang, andrewkr, sdong
Reviewed By: sdong
Subscribers: lovro, andrewkr, dhruba
Differential Revision: https://reviews.facebook.net/D57075
2016-07-20 18:49:03 +02:00
|
|
|
virtual bool IsValuePinned() const override { return true; }
|
|
|
|
|
2014-07-31 01:34:35 +02:00
|
|
|
private:
|
|
|
|
const Comparator* comparator_;
|
|
|
|
const char* data_; // underlying block contents
|
|
|
|
uint32_t restarts_; // Offset of restart array (list of fixed32)
|
|
|
|
uint32_t num_restarts_; // Number of uint32_t entries in restart array
|
|
|
|
|
|
|
|
// current_ is offset in data_ of current entry. >= restarts_ if !Valid
|
|
|
|
uint32_t current_;
|
|
|
|
uint32_t restart_index_; // Index of restart block in which current_ falls
|
|
|
|
IterKey key_;
|
|
|
|
Slice value_;
|
|
|
|
Status status_;
|
|
|
|
BlockPrefixIndex* prefix_index_;
|
2016-06-14 21:27:46 +02:00
|
|
|
bool key_pinned_;
|
|
|
|
|
|
|
|
struct CachedPrevEntry {
|
|
|
|
explicit CachedPrevEntry(uint32_t _offset, const char* _key_ptr,
|
|
|
|
size_t _key_offset, size_t _key_size, Slice _value)
|
|
|
|
: offset(_offset),
|
|
|
|
key_ptr(_key_ptr),
|
|
|
|
key_offset(_key_offset),
|
|
|
|
key_size(_key_size),
|
|
|
|
value(_value) {}
|
|
|
|
|
|
|
|
// offset of entry in block
|
|
|
|
uint32_t offset;
|
|
|
|
// Pointer to key data in block (nullptr if key is delta-encoded)
|
|
|
|
const char* key_ptr;
|
|
|
|
// offset of key in prev_entries_keys_buff_ (0 if key_ptr is not nullptr)
|
|
|
|
size_t key_offset;
|
|
|
|
// size of key
|
|
|
|
size_t key_size;
|
|
|
|
// value slice pointing to data in block
|
|
|
|
Slice value;
|
|
|
|
};
|
|
|
|
std::string prev_entries_keys_buff_;
|
|
|
|
std::vector<CachedPrevEntry> prev_entries_;
|
|
|
|
int32_t prev_entries_idx_ = -1;
|
2014-07-31 01:34:35 +02:00
|
|
|
|
|
|
|
inline int Compare(const Slice& a, const Slice& b) const {
|
|
|
|
return comparator_->Compare(a, b);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the offset in data_ just past the end of the current entry.
|
|
|
|
inline uint32_t NextEntryOffset() const {
|
2014-11-11 22:47:22 +01:00
|
|
|
// NOTE: We don't support files bigger than 2GB
|
|
|
|
return static_cast<uint32_t>((value_.data() + value_.size()) - data_);
|
2014-07-31 01:34:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t GetRestartPoint(uint32_t index) {
|
|
|
|
assert(index < num_restarts_);
|
|
|
|
return DecodeFixed32(data_ + restarts_ + index * sizeof(uint32_t));
|
|
|
|
}
|
|
|
|
|
|
|
|
void SeekToRestartPoint(uint32_t index) {
|
|
|
|
key_.Clear();
|
|
|
|
restart_index_ = index;
|
|
|
|
// current_ will be fixed by ParseNextKey();
|
|
|
|
|
|
|
|
// ParseNextKey() starts at the end of value_, so set value_ accordingly
|
|
|
|
uint32_t offset = GetRestartPoint(index);
|
|
|
|
value_ = Slice(data_ + offset, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CorruptionError();
|
|
|
|
|
|
|
|
bool ParseNextKey();
|
|
|
|
|
|
|
|
bool BinarySeek(const Slice& target, uint32_t left, uint32_t right,
|
|
|
|
uint32_t* index);
|
|
|
|
|
|
|
|
int CompareBlockKey(uint32_t block_index, const Slice& target);
|
|
|
|
|
|
|
|
bool BinaryBlockIndexSeek(const Slice& target, uint32_t* block_ids,
|
|
|
|
uint32_t left, uint32_t right,
|
|
|
|
uint32_t* index);
|
|
|
|
|
|
|
|
bool PrefixSeek(const Slice& target, uint32_t* index);
|
2011-03-18 23:37:00 +01:00
|
|
|
|
|
|
|
};
|
|
|
|
|
2013-10-04 06:49:15 +02:00
|
|
|
} // namespace rocksdb
|