mirror of
https://github.com/facebook/rocksdb.git
synced 2026-07-07 14:47:40 +08:00
Compare commits
104 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c54d239d4 | |||
| 1f96e652b3 | |||
| 1347bfb07f | |||
| d5345a8ff7 | |||
| 2a9a8da97c | |||
| debd87596a | |||
| 138c7b6182 | |||
| d46d1fa105 | |||
| 3b91fe817f | |||
| 80c3705262 | |||
| 7dab484aa9 | |||
| b045f4a122 | |||
| a1be80c5c2 | |||
| b96432aadd | |||
| 4ed79f5bd1 | |||
| 346055a9ea | |||
| 1a76289be4 | |||
| 6f9d8260f0 | |||
| a294529ca9 | |||
| f20d12adc8 | |||
| e9b15738d9 | |||
| 26b480609c | |||
| 0f35db55d8 | |||
| bee8d5560e | |||
| 8a36543326 | |||
| f69a5fe8ee | |||
| d3296260c2 | |||
| 3495c94761 | |||
| ef119c9811 | |||
| 9a136e18b3 | |||
| 4adf691e39 | |||
| 1c7652fcef | |||
| 7cb6b93eee | |||
| 925435bbd9 | |||
| 1f0ccd9a15 | |||
| aa889eb5ed | |||
| a6ee297ac9 | |||
| 87b4043a67 | |||
| 9b95fbbf24 | |||
| ee258619be | |||
| 485ee4f45c | |||
| ba164ac373 | |||
| 282f5a463b | |||
| 2ba4dceb4c | |||
| dc34a0ff1e | |||
| 8089eae240 | |||
| a7ecbfd590 | |||
| 3becc9409e | |||
| e7ffca9493 | |||
| 2ce6902cf5 | |||
| 24045549a6 | |||
| 1006eddd63 | |||
| 7c98a2d130 | |||
| af2a36d2c7 | |||
| a28cc4a38c | |||
| ef535039f3 | |||
| 1987313a94 | |||
| e34087c524 | |||
| 8109046222 | |||
| ddafba870d | |||
| 9ad772e652 | |||
| 57a8e69d4e | |||
| 3fd1f11d35 | |||
| 9c94559de7 | |||
| 8b38d4b400 | |||
| c0be6a4b90 | |||
| 0ca691654f | |||
| 58fc9d61b7 | |||
| ac24f152a1 | |||
| a44f4b8653 | |||
| f22557886e | |||
| 8ad4c7efc4 | |||
| 787730c859 | |||
| ecc084d301 | |||
| da5e11310b | |||
| 55de26580a | |||
| 2cb00c6921 | |||
| 2abbb02d14 | |||
| cbebbad7d9 | |||
| dd76862b00 | |||
| 351d2fd2b6 | |||
| 8592517c89 | |||
| f7237e3395 | |||
| a571cbed17 | |||
| 2c9aa69a93 | |||
| fe6c8cb1d6 | |||
| 632746bb5b | |||
| 8181dfb1c4 | |||
| d963661d6f | |||
| 263fa15b44 | |||
| bceb2dfe6a | |||
| 32dd657bad | |||
| 917e98ff9e | |||
| 12960f0b57 | |||
| 9375c3b635 | |||
| dd23e84cad | |||
| 389e66bef5 | |||
| 2c2776f1f3 | |||
| 79790cf2a8 | |||
| 22105366d9 | |||
| d327d56081 | |||
| d02f63cc54 | |||
| 2a5ff78c12 | |||
| fbbb08770f |
@@ -149,6 +149,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"memtable/hash_skiplist_rep.cc",
|
||||
"memtable/skiplistrep.cc",
|
||||
"memtable/vectorrep.cc",
|
||||
"memtable/wbwi_memtable.cc",
|
||||
"memtable/write_buffer_manager.cc",
|
||||
"monitoring/histogram.cc",
|
||||
"monitoring/histogram_windowing.cc",
|
||||
+2
-1
@@ -32,7 +32,7 @@
|
||||
# 3. cmake ..
|
||||
# 4. make -j
|
||||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
cmake_minimum_required(VERSION 3.12)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/modules/")
|
||||
include(ReadVersion)
|
||||
@@ -779,6 +779,7 @@ set(SOURCES
|
||||
memtable/hash_skiplist_rep.cc
|
||||
memtable/skiplistrep.cc
|
||||
memtable/vectorrep.cc
|
||||
memtable/wbwi_memtable.cc
|
||||
memtable/write_buffer_manager.cc
|
||||
monitoring/histogram.cc
|
||||
monitoring/histogram_windowing.cc
|
||||
|
||||
+76
-11
@@ -1,9 +1,74 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
|
||||
## 9.9.0 (11/18/2024)
|
||||
### New Features
|
||||
* Multi-Column-Family-Iterator (CoalescingIterator/AttributeGroupIterator) is no longer marked as experimental
|
||||
* Adds a new table property "rocksdb.newest.key.time" which records the unix timestamp of the newest key. Uses this table property for FIFO TTL and temperature change compaction.
|
||||
|
||||
### Public API Changes
|
||||
* Added a new API `Transaction::GetAttributeGroupIterator` that can be used to create a multi-column-family attribute group iterator over the specified column families, including the data from both the transaction and the underlying database. This API is currently supported for optimistic and write-committed pessimistic transactions.
|
||||
* Added a new API `Transaction::GetCoalescingIterator` that can be used to create a multi-column-family coalescing iterator over the specified column families, including the data from both the transaction and the underlying database. This API is currently supported for optimistic and write-committed pessimistic transactions.
|
||||
|
||||
### Behavior Changes
|
||||
* `BaseDeltaIterator` now honors the read option `allow_unprepared_value`.
|
||||
|
||||
### Bug Fixes
|
||||
* `BaseDeltaIterator` now calls `PrepareValue` on the base iterator in case it has been created with the `allow_unprepared_value` read option set. Earlier, such base iterators could lead to incorrect values being exposed from `BaseDeltaIterator`.
|
||||
* Fix a leak of obsolete blob files left open until DB::Close(). This bug was introduced in version 9.4.0.
|
||||
* Fix missing cases of corruption retry during DB open and read API processing.
|
||||
* Fix a bug for transaction db with 2pc where an old WAL may be retained longer than needed (#13127).
|
||||
* Fix leaks of some open SST files (until `DB::Close()`) that are written but never become live due to various failures. (We now have a check for such leaks with no outstanding issues.)
|
||||
* Fix a bug for replaying WALs for WriteCommitted transaction DB when its user-defined timestamps setting is toggled on/off between DB sessions.
|
||||
|
||||
### Performance Improvements
|
||||
* Fix regression in issue #12038 due to `Options::compaction_readahead_size` greater than `max_sectors_kb` (i.e, largest I/O size that the OS issues to a block device defined in linux)
|
||||
|
||||
## 9.8.0 (10/25/2024)
|
||||
### New Features
|
||||
* All non-`block_cache` options in `BlockBasedTableOptions` are now mutable with `DB::SetOptions()`. See also Bug Fixes below.
|
||||
* When using iterators with BlobDB, it is now possible to load large values on an on-demand basis, i.e. only if they are actually needed by the application. This can save I/O in use cases where the values associated with certain keys are not needed. For more details, see the new read option `allow_unprepared_value` and the iterator API `PrepareValue`.
|
||||
* Add a new file ingestion option `IngestExternalFileOptions::fill_cache` to support not adding blocks from ingested files into block cache during file ingestion.
|
||||
* The option `allow_unprepared_value` is now also supported for multi-column-family iterators (i.e. `CoalescingIterator` and `AttributeGroupIterator`).
|
||||
* When a file with just one range deletion (standalone range deletion file) is ingested via bulk loading, it will be marked for compaction. During compaction, this type of files can be used to directly filter out some input files that are not protected by any snapshots and completely deleted by the standalone range deletion file.
|
||||
|
||||
### Behavior Changes
|
||||
* During file ingestion, overlapping files level assignment are done in multiple batches, so that they can potentially be assigned to lower levels other than always land on L0.
|
||||
* OPTIONS file to be loaded by remote worker is now preserved so that it does not get purged by the primary host. A similar technique as how we are preserving new SST files from getting purged is used for this. min_options_file_numbers_ is tracked like pending_outputs_ is tracked.
|
||||
* Trim readahead_size during scans so data blocks containing keys that are not in the same prefix as the seek key in `Seek()` are not prefetched when `ReadOptions::auto_readahead_size=true` (default value) and `ReadOptions::prefix_same_as_start = true`
|
||||
* Assigning levels for external files are done in the same way for universal compaction and leveled compaction. The old behavior tends to assign files to L0 while the new behavior will assign the files to the lowest level possible.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a longstanding race condition in SetOptions for `block_based_table_factory` options. The fix has some subtle behavior changes because of copying and replacing the TableFactory on a change with SetOptions, including requiring an Iterator::Refresh() for an existing Iterator to use the latest options.
|
||||
* Fix under counting of allocated memory in the compressed secondary cache due to looking at the compressed block size rather than the actual memory allocated, which could be larger due to internal fragmentation.
|
||||
* `GetApproximateMemTableStats()` could return disastrously bad estimates 5-25% of the time. The function has been re-engineered to return much better estimates with similar CPU cost.
|
||||
* Skip insertion of compressed blocks in the secondary cache if the lowest_used_cache_tier DB option is kVolatileTier.
|
||||
* Fix an issue in level compaction where a small CF with small compaction debt can cause the DB to allow parallel compactions. (#13054)
|
||||
* Several DB option settings could be lost through `GetOptionsFromString()`, possibly elsewhere as well. Affected options, now fixed:`background_close_inactive_wals`, `write_dbid_to_manifest`, `write_identity_file`, `prefix_seek_opt_in_only`
|
||||
|
||||
## 9.7.0 (09/20/2024)
|
||||
### New Features
|
||||
* Make Cache a customizable class that can be instantiated by the object registry.
|
||||
* Add new option `prefix_seek_opt_in_only` that makes iterators generally safer when you might set a `prefix_extractor`. When `prefix_seek_opt_in_only=true`, which is expected to be the future default, prefix seek is only used when `prefix_same_as_start` or `auto_prefix_mode` are set. Also, `prefix_same_as_start` and `auto_prefix_mode` now allow prefix filtering even with `total_order_seek=true`.
|
||||
* Add a new table property "rocksdb.key.largest.seqno" which records the largest sequence number of all keys in file. It is verified to be zero during SST file ingestion.
|
||||
|
||||
### Behavior Changes
|
||||
* Changed the semantics of the BlobDB configuration option `blob_garbage_collection_force_threshold` to define a threshold for the overall garbage ratio of all blob files currently eligible for garbage collection (according to `blob_garbage_collection_age_cutoff`). This can provide better control over space amplification at the cost of slightly higher write amplification.
|
||||
* Set `write_dbid_to_manifest=true` by default. This means DB ID will now be preserved through backups, checkpoints, etc. by default. Also add `write_identity_file` option which can be set to false for anticipated future behavior.
|
||||
* In FIFO compaction, compactions for changing file temperature (configured by option `file_temperature_age_thresholds`) will compact one file at a time, instead of merging multiple eligible file together (#13018).
|
||||
* Support ingesting db generated files using hard link, i.e. IngestExternalFileOptions::move_files/link_files and IngestExternalFileOptions::allow_db_generated_files.
|
||||
* Add a new file ingestion option `IngestExternalFileOptions::link_files` to hard link input files and preserve original files links after ingestion.
|
||||
* DB::Close now untracks files in SstFileManager, making avaialble any space used
|
||||
by them. Prior to this change they would be orphaned until the DB is re-opened.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug in CompactRange() where result files may not be compacted in any future compaction. This can only happen when users configure CompactRangeOptions::change_level to true and the change level step of manual compaction fails (#13009).
|
||||
* Fix handling of dynamic change of `prefix_extractor` with memtable prefix filter. Previously, prefix seek could mix different prefix interpretations between memtable and SST files. Now the latest `prefix_extractor` at the time of iterator creation or refresh is respected.
|
||||
* Fix a bug with manual_wal_flush and auto error recovery from WAL failure that may cause CFs to be inconsistent (#12995). The fix will set potential WAL write failure as fatal error when manual_wal_flush is true, and disables auto error recovery from these errors.
|
||||
|
||||
## 9.6.0 (08/19/2024)
|
||||
### New Features
|
||||
* *Best efforts recovery supports recovering to incomplete Version with a clean seqno cut that presents a valid point in time view from the user's perspective, if versioning history doesn't include atomic flush.
|
||||
* Best efforts recovery supports recovering to incomplete Version with a clean seqno cut that presents a valid point in time view from the user's perspective, if versioning history doesn't include atomic flush.
|
||||
* New option `BlockBasedTableOptions::decouple_partitioned_filters` should improve efficiency in serving read queries because filter and index partitions can consistently target the configured `metadata_block_size`. This option is currently opt-in.
|
||||
* Introduce a new mutable CF option `paranoid_memory_checks`. It enables additional validation on data integrity during reads/scanning. Currently, skip list based memtable will validate key ordering during look up and scans.
|
||||
|
||||
@@ -15,11 +80,11 @@
|
||||
* There may be less intra-L0 compaction triggered by total L0 size being too small. We now use compensated file size (tombstones are assigned some value size) when calculating L0 size and reduce the threshold for L0 size limit. This is to avoid accumulating too much data/tombstones in L0.
|
||||
|
||||
### Bug Fixes
|
||||
* *Make DestroyDB supports slow deletion when it's configured in `SstFileManager`. The slow deletion is subject to the configured `rate_bytes_per_sec`, but not subject to the `max_trash_db_ratio`.
|
||||
* Make DestroyDB supports slow deletion when it's configured in `SstFileManager`. The slow deletion is subject to the configured `rate_bytes_per_sec`, but not subject to the `max_trash_db_ratio`.
|
||||
* Fixed a bug where we set unprep_seqs_ even when WriteImpl() fails. This was caught by stress test write fault injection in WriteImpl(). This may have incorrectly caused iteration creation failure for unvalidated writes or returned wrong result for WriteUnpreparedTxn::GetUnpreparedSequenceNumbers().
|
||||
* Fixed a bug where successful write right after error recovery for last failed write finishes causes duplicate WAL entries
|
||||
* Fixed a data race involving the background error status in `unordered_write` mode.
|
||||
* *Fix a bug where file snapshot functions like backup, checkpoint may attempt to copy a non-existing manifest file. #12882
|
||||
* Fix a bug where file snapshot functions like backup, checkpoint may attempt to copy a non-existing manifest file. #12882
|
||||
* Fix a bug where per kv checksum corruption may be ignored in MultiGet().
|
||||
* Fix a race condition in pessimistic transactions that could allow multiple transactions with the same name to be registered simultaneously, resulting in a crash or other unpredictable behavior.
|
||||
|
||||
@@ -98,7 +163,7 @@
|
||||
* Added a new API `GetEntityFromBatchAndDB` to `WriteBatchWithIndex` that can be used for wide-column point lookups with read-your-own-writes consistency. Similarly to `GetFromBatchAndDB`, the API can combine data from the write batch with data from the underlying database if needed. See the API comments for more details.
|
||||
* [Experimental] Introduce two new cross-column-family iterators - CoalescingIterator and AttributeGroupIterator. The CoalescingIterator enables users to iterate over multiple column families and access their values and columns. During this iteration, if the same key exists in more than one column family, the keys in the later column family will overshadow the previous ones. The AttributeGroupIterator allows users to gather wide columns per Column Family and create attribute groups while iterating over keys across all CFs.
|
||||
* Added a new API `MultiGetEntityFromBatchAndDB` to `WriteBatchWithIndex` that can be used for batched wide-column point lookups with read-your-own-writes consistency. Similarly to `MultiGetFromBatchAndDB`, the API can combine data from the write batch with data from the underlying database if needed. See the API comments for more details.
|
||||
* *Adds a `SstFileReader::NewTableIterator` API to support programmatically read a SST file as a raw table file.
|
||||
* Adds a `SstFileReader::NewTableIterator` API to support programmatically read a SST file as a raw table file.
|
||||
* Add an option to `WaitForCompactOptions` - `wait_for_purge` to make `WaitForCompact()` API wait for background purge to complete
|
||||
|
||||
### Public API Changes
|
||||
@@ -127,24 +192,24 @@ the whole DB to be dropped right after migration if the migrated data is larger
|
||||
* Fixed a bug where wrong padded bytes are used to generate file checksum and `DataVerificationInfo::checksum` upon file creation
|
||||
* Correctly implemented the move semantics of `PinnableWideColumns`.
|
||||
* Fixed a bug when the recycle_log_file_num in DBOptions is changed from 0 to non-zero when a DB is reopened. On a subsequent reopen, if a log file created when recycle_log_file_num==0 was reused previously, is alive and is empty, we could end up inserting stale WAL records into the memtable.
|
||||
* *Fix a bug where obsolete files' deletion during DB::Open are not rate limited with `SstFilemManager`'s slow deletion feature even if it's configured.
|
||||
* Fix a bug where obsolete files' deletion during DB::Open are not rate limited with `SstFilemManager`'s slow deletion feature even if it's configured.
|
||||
|
||||
## 9.1.0 (03/22/2024)
|
||||
### New Features
|
||||
* Added an option, `GetMergeOperandsOptions::continue_cb`, to give users the ability to end `GetMergeOperands()`'s lookup process before all merge operands were found.
|
||||
* *Add sanity checks for ingesting external files that currently checks if the user key comparator used to create the file is compatible with the column family's user key comparator.
|
||||
* Add sanity checks for ingesting external files that currently checks if the user key comparator used to create the file is compatible with the column family's user key comparator.
|
||||
*Support ingesting external files for column family that has user-defined timestamps in memtable only enabled.
|
||||
* On file systems that support storage level data checksum and reconstruction, retry SST block reads for point lookups, scans, and flush and compaction if there's a checksum mismatch on the initial read.
|
||||
* Some enhancements and fixes to experimental Temperature handling features, including new `default_write_temperature` CF option and opening an `SstFileWriter` with a temperature.
|
||||
* `WriteBatchWithIndex` now supports wide-column point lookups via the `GetEntityFromBatch` API. See the API comments for more details.
|
||||
* *Implement experimental features: API `Iterator::GetProperty("rocksdb.iterator.write-time")` to allow users to get data's approximate write unix time and write data with a specific write time via `WriteBatch::TimedPut` API.
|
||||
* Implement experimental features: API `Iterator::GetProperty("rocksdb.iterator.write-time")` to allow users to get data's approximate write unix time and write data with a specific write time via `WriteBatch::TimedPut` API.
|
||||
|
||||
### Public API Changes
|
||||
* Best-effort recovery (`best_efforts_recovery == true`) may now be used together with atomic flush (`atomic_flush == true`). The all-or-nothing recovery guarantee for atomically flushed data will be upheld.
|
||||
* Remove deprecated option `bottommost_temperature`, already replaced by `last_level_temperature`
|
||||
* Added new PerfContext counters for block cache bytes read - block_cache_index_read_byte, block_cache_filter_read_byte, block_cache_compression_dict_read_byte, and block_cache_read_byte.
|
||||
* Deprecate experimental Remote Compaction APIs - StartV2() and WaitForCompleteV2() and introduce Schedule() and Wait(). The new APIs essentially does the same thing as the old APIs. They allow taking externally generated unique id to wait for remote compaction to complete.
|
||||
* *For API `WriteCommittedTransaction::GetForUpdate`, if the column family enables user-defined timestamp, it was mandated that argument `do_validate` cannot be false, and UDT based validation has to be done with a user set read timestamp. It's updated to make the UDT based validation optional if user sets `do_validate` to false and does not set a read timestamp. With this, `GetForUpdate` skips UDT based validation and it's users' responsibility to enforce the UDT invariant. SO DO NOT skip this UDT-based validation if users do not have ways to enforce the UDT invariant. Ways to enforce the invariant on the users side include manage a monotonically increasing timestamp, commit transactions in a single thread etc.
|
||||
* For API `WriteCommittedTransaction::GetForUpdate`, if the column family enables user-defined timestamp, it was mandated that argument `do_validate` cannot be false, and UDT based validation has to be done with a user set read timestamp. It's updated to make the UDT based validation optional if user sets `do_validate` to false and does not set a read timestamp. With this, `GetForUpdate` skips UDT based validation and it's users' responsibility to enforce the UDT invariant. SO DO NOT skip this UDT-based validation if users do not have ways to enforce the UDT invariant. Ways to enforce the invariant on the users side include manage a monotonically increasing timestamp, commit transactions in a single thread etc.
|
||||
* Defined a new PerfLevel `kEnableWait` to measure time spent by user threads blocked in RocksDB other than mutex, such as a write thread waiting to be added to a write group, a write thread delayed or stalled etc.
|
||||
* `RateLimiter`'s API no longer requires the burst size to be the refill size. Users of `NewGenericRateLimiter()` can now provide burst size in `single_burst_bytes`. Implementors of `RateLimiter::SetSingleBurstBytes()` need to adapt their implementations to match the changed API doc.
|
||||
* Add `write_memtable_time` to the newly introduced PerfLevel `kEnableWait`.
|
||||
@@ -177,7 +242,7 @@ MultiGetBenchmarks.multiGetList10 no_column_family 10000 16 100 1024 thrpt 25 76
|
||||
## 9.0.0 (02/16/2024)
|
||||
### New Features
|
||||
* Provide support for FSBuffer for point lookups. Also added support for scans and compactions that don't go through prefetching.
|
||||
* *Make `SstFileWriter` create SST files without persisting user defined timestamps when the `Option.persist_user_defined_timestamps` flag is set to false.
|
||||
* Make `SstFileWriter` create SST files without persisting user defined timestamps when the `Option.persist_user_defined_timestamps` flag is set to false.
|
||||
* Add support for user-defined timestamps in APIs `DeleteFilesInRanges` and `GetPropertiesOfTablesInRange`.
|
||||
* Mark wal\_compression feature as production-ready. Currently only compatible with ZSTD compression.
|
||||
|
||||
@@ -187,9 +252,9 @@ MultiGetBenchmarks.multiGetList10 no_column_family 10000 16 100 1024 thrpt 25 76
|
||||
* Exposed mode option to Rate Limiter via c api.
|
||||
* Removed deprecated option `access_hint_on_compaction_start`
|
||||
* Removed deprecated option `ColumnFamilyOptions::check_flush_compaction_key_order`
|
||||
* *Remove the default `WritableFile::GetFileSize` and `FSWritableFile::GetFileSize` implementation that returns 0 and make it pure virtual, so that subclasses are enforced to explicitly provide an implementation.
|
||||
* Remove the default `WritableFile::GetFileSize` and `FSWritableFile::GetFileSize` implementation that returns 0 and make it pure virtual, so that subclasses are enforced to explicitly provide an implementation.
|
||||
* Removed deprecated option `ColumnFamilyOptions::level_compaction_dynamic_file_size`
|
||||
* *Removed tickers with typos "rocksdb.error.handler.bg.errro.count", "rocksdb.error.handler.bg.io.errro.count", "rocksdb.error.handler.bg.retryable.io.errro.count".
|
||||
* Removed tickers with typos "rocksdb.error.handler.bg.errro.count", "rocksdb.error.handler.bg.io.errro.count", "rocksdb.error.handler.bg.retryable.io.errro.count".
|
||||
* Remove the force mode for `EnableFileDeletions` API because it is unsafe with no known legitimate use.
|
||||
* Removed deprecated option `ColumnFamilyOptions::ignore_max_compaction_bytes_for_input`
|
||||
* `sst_dump --command=check` now compares the number of records in a table with `num_entries` in table property, and reports corruption if there is a mismatch. API `SstFileDumper::ReadSequential()` is updated to optionally do this verification. (#12322)
|
||||
|
||||
@@ -2489,7 +2489,7 @@ checkout_folly:
|
||||
fi
|
||||
@# Pin to a particular version for public CI, so that PR authors don't
|
||||
@# need to worry about folly breaking our integration. Update periodically
|
||||
cd third-party/folly && git reset --hard 03041f014b6e6ebb6119ffae8b7a37308f52e913
|
||||
cd third-party/folly && git reset --hard 33f5b67fcaeb8705b04fd1b850873a180dc89aaa
|
||||
@# NOTE: this hack is required for clang in some cases
|
||||
perl -pi -e 's/int rv = syscall/int rv = (int)syscall/' third-party/folly/folly/detail/Futex.cpp
|
||||
@# NOTE: this hack is required for gcc in some cases
|
||||
|
||||
@@ -38,12 +38,11 @@ Snowflake [uses](https://www.snowflake.com/blog/how-foundationdb-powers-snowflak
|
||||
The Bing search engine from Microsoft uses RocksDB as the storage engine for its web data platform: https://blogs.bing.com/Engineering-Blog/october-2021/RocksDB-in-Microsoft-Bing
|
||||
|
||||
## LinkedIn
|
||||
Two different use cases at Linkedin are using RocksDB as a storage engine:
|
||||
1. [Venice](https://venicedb.org/) is a derived data platform using RocksDB as its storage engine. It is LinkedIn's ML feature store, powering thousands of recommender use cases, including the Feed, Video recommendations, and People You May Know.
|
||||
2. LinkedIn's follow feed for storing user's activities. Check out the blog post: https://engineering.linkedin.com/blog/2016/03/followfeed--linkedin-s-feed-made-faster-and-smarter
|
||||
3. Apache Samza, open source framework for stream processing.
|
||||
|
||||
1. LinkedIn's follow feed for storing user's activities. Check out the blog post: https://engineering.linkedin.com/blog/2016/03/followfeed--linkedin-s-feed-made-faster-and-smarter
|
||||
2. Apache Samza, open source framework for stream processing
|
||||
|
||||
Learn more about those use cases in a Tech Talk by Ankit Gupta and Naveen Somasundaram: http://www.youtube.com/watch?v=plqVp_OnSzg
|
||||
Learn more about LinkedIn's follow feed and Apache Samza in a Tech Talk by Ankit Gupta and Naveen Somasundaram: http://www.youtube.com/watch?v=plqVp_OnSzg
|
||||
|
||||
## Yahoo
|
||||
Yahoo is using RocksDB as a storage engine for their biggest distributed data store Sherpa. Learn more about it here: http://yahooeng.tumblr.com/post/120730204806/sherpa-scales-new-heights
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
try:
|
||||
from builtins import str
|
||||
@@ -15,10 +14,10 @@ from targets_builder import TARGETSBuilder, LiteralValue
|
||||
|
||||
from util import ColorString
|
||||
|
||||
# This script generates TARGETS file for Buck.
|
||||
# This script generates BUCK file for Buck.
|
||||
# Buck is a build tool specifying dependencies among different build targets.
|
||||
# User can pass extra dependencies as a JSON object via command line, and this
|
||||
# script can include these dependencies in the generate TARGETS file.
|
||||
# script can include these dependencies in the generate BUCK file.
|
||||
# Usage:
|
||||
# $python3 buckifier/buckify_rocksdb.py
|
||||
# (This generates a TARGET file without user-specified dependency for unit
|
||||
@@ -29,7 +28,7 @@ from util import ColorString
|
||||
# "extra_compiler_flags": ["-DFOO_BAR", "-Os"]
|
||||
# }
|
||||
# }'
|
||||
# (Generated TARGETS file has test_dep and mock1 as dependencies for RocksDB
|
||||
# (Generated BUCK file has test_dep and mock1 as dependencies for RocksDB
|
||||
# unit tests, and will use the extra_compiler_flags to compile the unit test
|
||||
# source.)
|
||||
|
||||
@@ -115,9 +114,9 @@ def get_dependencies():
|
||||
return deps_map
|
||||
|
||||
|
||||
# Prepare TARGETS file for buck
|
||||
def generate_targets(repo_path, deps_map):
|
||||
print(ColorString.info("Generating TARGETS"))
|
||||
# Prepare BUCK file for buck
|
||||
def generate_buck(repo_path, deps_map):
|
||||
print(ColorString.info("Generating BUCK"))
|
||||
# parsed src.mk file
|
||||
src_mk = parse_src_mk(repo_path)
|
||||
# get all .cc files
|
||||
@@ -132,12 +131,12 @@ def generate_targets(repo_path, deps_map):
|
||||
if len(sys.argv) >= 2:
|
||||
# Heuristically quote and canonicalize whitespace for inclusion
|
||||
# in how the file was generated.
|
||||
extra_argv = " '{0}'".format(" ".join(sys.argv[1].split()))
|
||||
extra_argv = " '{}'".format(" ".join(sys.argv[1].split()))
|
||||
|
||||
TARGETS = TARGETSBuilder("%s/TARGETS" % repo_path, extra_argv)
|
||||
BUCK = TARGETSBuilder("%s/BUCK" % repo_path, extra_argv)
|
||||
|
||||
# rocksdb_lib
|
||||
TARGETS.add_library(
|
||||
BUCK.add_library(
|
||||
"rocksdb_lib",
|
||||
src_mk["LIB_SOURCES"] +
|
||||
# always add range_tree, it's only excluded on ppc64, which we don't use internally
|
||||
@@ -153,7 +152,7 @@ def generate_targets(repo_path, deps_map):
|
||||
headers=LiteralValue("glob([\"**/*.h\"])")
|
||||
)
|
||||
# rocksdb_whole_archive_lib
|
||||
TARGETS.add_library(
|
||||
BUCK.add_library(
|
||||
"rocksdb_whole_archive_lib",
|
||||
[],
|
||||
deps=[
|
||||
@@ -163,7 +162,7 @@ def generate_targets(repo_path, deps_map):
|
||||
link_whole=True,
|
||||
)
|
||||
# rocksdb_test_lib
|
||||
TARGETS.add_library(
|
||||
BUCK.add_library(
|
||||
"rocksdb_test_lib",
|
||||
src_mk.get("MOCK_LIB_SOURCES", [])
|
||||
+ src_mk.get("TEST_LIB_SOURCES", [])
|
||||
@@ -173,7 +172,7 @@ def generate_targets(repo_path, deps_map):
|
||||
extra_test_libs=True,
|
||||
)
|
||||
# rocksdb_tools_lib
|
||||
TARGETS.add_library(
|
||||
BUCK.add_library(
|
||||
"rocksdb_tools_lib",
|
||||
src_mk.get("BENCH_LIB_SOURCES", [])
|
||||
+ src_mk.get("ANALYZER_LIB_SOURCES", [])
|
||||
@@ -181,51 +180,51 @@ def generate_targets(repo_path, deps_map):
|
||||
[":rocksdb_lib"],
|
||||
)
|
||||
# rocksdb_cache_bench_tools_lib
|
||||
TARGETS.add_library(
|
||||
BUCK.add_library(
|
||||
"rocksdb_cache_bench_tools_lib",
|
||||
src_mk.get("CACHE_BENCH_LIB_SOURCES", []),
|
||||
[":rocksdb_lib"],
|
||||
)
|
||||
# rocksdb_stress_lib
|
||||
TARGETS.add_rocksdb_library(
|
||||
BUCK.add_rocksdb_library(
|
||||
"rocksdb_stress_lib",
|
||||
src_mk.get("ANALYZER_LIB_SOURCES", [])
|
||||
+ src_mk.get("STRESS_LIB_SOURCES", [])
|
||||
+ ["test_util/testutil.cc"],
|
||||
)
|
||||
# ldb binary
|
||||
TARGETS.add_binary(
|
||||
BUCK.add_binary(
|
||||
"ldb", ["tools/ldb.cc"], [":rocksdb_tools_lib"]
|
||||
)
|
||||
# db_stress binary
|
||||
TARGETS.add_binary(
|
||||
BUCK.add_binary(
|
||||
"db_stress", ["db_stress_tool/db_stress.cc"], [":rocksdb_stress_lib"]
|
||||
)
|
||||
# db_bench binary
|
||||
TARGETS.add_binary(
|
||||
BUCK.add_binary(
|
||||
"db_bench", ["tools/db_bench.cc"], [":rocksdb_tools_lib"]
|
||||
)
|
||||
# cache_bench binary
|
||||
TARGETS.add_binary(
|
||||
BUCK.add_binary(
|
||||
"cache_bench", ["cache/cache_bench.cc"], [":rocksdb_cache_bench_tools_lib"]
|
||||
)
|
||||
# bench binaries
|
||||
for src in src_mk.get("MICROBENCH_SOURCES", []):
|
||||
name = src.rsplit("/", 1)[1].split(".")[0] if "/" in src else src.split(".")[0]
|
||||
TARGETS.add_binary(name, [src], [], extra_bench_libs=True)
|
||||
print("Extra dependencies:\n{0}".format(json.dumps(deps_map)))
|
||||
BUCK.add_binary(name, [src], [], extra_bench_libs=True)
|
||||
print(f"Extra dependencies:\n{json.dumps(deps_map)}")
|
||||
|
||||
# Dictionary test executable name -> relative source file path
|
||||
test_source_map = {}
|
||||
|
||||
# c_test.c is added through TARGETS.add_c_test(). If there
|
||||
# c_test.c is added through BUCK.add_c_test(). If there
|
||||
# are more than one .c test file, we need to extend
|
||||
# TARGETS.add_c_test() to include other C tests too.
|
||||
# BUCK.add_c_test() to include other C tests too.
|
||||
for test_src in src_mk.get("TEST_MAIN_SOURCES_C", []):
|
||||
if test_src != "db/c_test.c":
|
||||
print("Don't know how to deal with " + test_src)
|
||||
return False
|
||||
TARGETS.add_c_test()
|
||||
BUCK.add_c_test()
|
||||
|
||||
try:
|
||||
with open(f"{repo_path}/buckifier/bench.json") as json_file:
|
||||
@@ -240,7 +239,7 @@ def generate_targets(repo_path, deps_map):
|
||||
for metric in overloaded_metric_list:
|
||||
if not isinstance(metric, dict):
|
||||
clean_benchmarks[binary][benchmark].append(metric)
|
||||
TARGETS.add_fancy_bench_config(
|
||||
BUCK.add_fancy_bench_config(
|
||||
config_dict["name"],
|
||||
clean_benchmarks,
|
||||
False,
|
||||
@@ -262,7 +261,7 @@ def generate_targets(repo_path, deps_map):
|
||||
if not isinstance(metric, dict):
|
||||
clean_benchmarks[binary][benchmark].append(metric)
|
||||
for config_dict in slow_fancy_bench_config_list:
|
||||
TARGETS.add_fancy_bench_config(
|
||||
BUCK.add_fancy_bench_config(
|
||||
config_dict["name"] + "_slow",
|
||||
clean_benchmarks,
|
||||
True,
|
||||
@@ -275,7 +274,7 @@ def generate_targets(repo_path, deps_map):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
TARGETS.add_test_header()
|
||||
BUCK.add_test_header()
|
||||
|
||||
for test_src in src_mk.get("TEST_MAIN_SOURCES", []):
|
||||
test = test_src.split(".c")[0].strip().split("/")[-1].strip()
|
||||
@@ -292,31 +291,31 @@ def generate_targets(repo_path, deps_map):
|
||||
|
||||
if test in _EXPORTED_TEST_LIBS:
|
||||
test_library = "%s_lib" % test_target_name
|
||||
TARGETS.add_library(
|
||||
BUCK.add_library(
|
||||
test_library,
|
||||
[test_src],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
extra_test_libs=True,
|
||||
)
|
||||
TARGETS.register_test(
|
||||
BUCK.register_test(
|
||||
test_target_name,
|
||||
test_src,
|
||||
deps=json.dumps(deps["extra_deps"] + [":" + test_library]),
|
||||
extra_compiler_flags=json.dumps(deps["extra_compiler_flags"]),
|
||||
)
|
||||
else:
|
||||
TARGETS.register_test(
|
||||
BUCK.register_test(
|
||||
test_target_name,
|
||||
test_src,
|
||||
deps=json.dumps(deps["extra_deps"] + [":rocksdb_test_lib"]),
|
||||
extra_compiler_flags=json.dumps(deps["extra_compiler_flags"]),
|
||||
)
|
||||
TARGETS.export_file("tools/db_crashtest.py")
|
||||
BUCK.export_file("tools/db_crashtest.py")
|
||||
|
||||
print(ColorString.info("Generated TARGETS Summary:"))
|
||||
print(ColorString.info("- %d libs" % TARGETS.total_lib))
|
||||
print(ColorString.info("- %d binarys" % TARGETS.total_bin))
|
||||
print(ColorString.info("- %d tests" % TARGETS.total_test))
|
||||
print(ColorString.info("Generated BUCK Summary:"))
|
||||
print(ColorString.info("- %d libs" % BUCK.total_lib))
|
||||
print(ColorString.info("- %d binarys" % BUCK.total_bin))
|
||||
print(ColorString.info("- %d tests" % BUCK.total_test))
|
||||
return True
|
||||
|
||||
|
||||
@@ -336,10 +335,10 @@ def exit_with_error(msg):
|
||||
|
||||
def main():
|
||||
deps_map = get_dependencies()
|
||||
# Generate TARGETS file for buck
|
||||
ok = generate_targets(get_rocksdb_path(), deps_map)
|
||||
# Generate BUCK file for buck
|
||||
ok = generate_buck(get_rocksdb_path(), deps_map)
|
||||
if not ok:
|
||||
exit_with_error("Failed to generate TARGETS files")
|
||||
exit_with_error("Failed to generate BUCK files")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
try:
|
||||
from builtins import object, str
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
rocksdb_target_header_template = """# This file \100generated by:
|
||||
#$ python3 buckifier/buckify_rocksdb.py{extra_argv}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"""
|
||||
This module keeps commonly used components.
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
try:
|
||||
from builtins import object
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#
|
||||
# The solution is to move the include out of the #ifdef.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import re
|
||||
@@ -62,7 +61,7 @@ def expand_include(
|
||||
|
||||
included.add(include_path)
|
||||
with open(include_path) as f:
|
||||
print('#line 1 "{}"'.format(include_path), file=source_out)
|
||||
print(f'#line 1 "{include_path}"', file=source_out)
|
||||
process_file(
|
||||
f, include_path, source_out, header_out, include_paths, public_include_paths
|
||||
)
|
||||
@@ -118,7 +117,7 @@ def process_file(
|
||||
)
|
||||
|
||||
if expanded:
|
||||
print('#line {} "{}"'.format(line + 1, abs_path), file=source_out)
|
||||
print(f'#line {line + 1} "{abs_path}"', file=source_out)
|
||||
elif text != "#pragma once\n":
|
||||
source_out.write(text)
|
||||
|
||||
@@ -157,8 +156,8 @@ def main():
|
||||
with open(filename) as f, open(args.source_out, "w") as source_out, open(
|
||||
args.header_out, "w"
|
||||
) as header_out:
|
||||
print('#line 1 "{}"'.format(filename), file=source_out)
|
||||
print('#include "{}"'.format(header_out.name), file=source_out)
|
||||
print(f'#line 1 "{filename}"', file=source_out)
|
||||
print(f'#include "{header_out.name}"', file=source_out)
|
||||
process_file(
|
||||
f, abs_path, source_out, header_out, include_paths, public_include_paths
|
||||
)
|
||||
|
||||
@@ -102,7 +102,7 @@ class BenchmarkUtils:
|
||||
|
||||
|
||||
class ResultParser:
|
||||
def __init__(self, field="(\w|[+-:.%])+", intrafield="(\s)+", separator="\t"):
|
||||
def __init__(self, field=r"(\w|[+-:.%])+", intrafield=r"(\s)+", separator="\t"):
|
||||
self.field = re.compile(field)
|
||||
self.intra = re.compile(intrafield)
|
||||
self.sep = re.compile(separator)
|
||||
@@ -159,7 +159,7 @@ class ResultParser:
|
||||
|
||||
|
||||
def load_report_from_tsv(filename: str):
|
||||
file = open(filename, "r")
|
||||
file = open(filename)
|
||||
contents = file.readlines()
|
||||
file.close()
|
||||
parser = ResultParser()
|
||||
|
||||
+10
-11
@@ -9,7 +9,6 @@
|
||||
- Prints those error messages to stdout
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
import re
|
||||
import sys
|
||||
@@ -43,7 +42,7 @@ class GTestErrorParser(ErrorParserBase):
|
||||
return None
|
||||
gtest_fail_match = self._GTEST_FAIL_PATTERN.match(line)
|
||||
if gtest_fail_match:
|
||||
return "%s failed: %s" % (self._last_gtest_name, gtest_fail_match.group(1))
|
||||
return "{} failed: {}".format(self._last_gtest_name, gtest_fail_match.group(1))
|
||||
return None
|
||||
|
||||
|
||||
@@ -66,52 +65,52 @@ class CompilerErrorParser(MatchErrorParser):
|
||||
# format (link error):
|
||||
# '<filename>:<line #>: error: <error msg>'
|
||||
# The below regex catches both
|
||||
super(CompilerErrorParser, self).__init__(r"\S+:\d+: error:")
|
||||
super().__init__(r"\S+:\d+: error:")
|
||||
|
||||
|
||||
class ScanBuildErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
super(ScanBuildErrorParser, self).__init__(r"scan-build: \d+ bugs found.$")
|
||||
super().__init__(r"scan-build: \d+ bugs found.$")
|
||||
|
||||
|
||||
class DbCrashErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
super(DbCrashErrorParser, self).__init__(r"\*\*\*.*\^$|TEST FAILED.")
|
||||
super().__init__(r"\*\*\*.*\^$|TEST FAILED.")
|
||||
|
||||
|
||||
class WriteStressErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
super(WriteStressErrorParser, self).__init__(
|
||||
super().__init__(
|
||||
r"ERROR: write_stress died with exitcode=\d+"
|
||||
)
|
||||
|
||||
|
||||
class AsanErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
super(AsanErrorParser, self).__init__(r"==\d+==ERROR: AddressSanitizer:")
|
||||
super().__init__(r"==\d+==ERROR: AddressSanitizer:")
|
||||
|
||||
|
||||
class UbsanErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
# format: '<filename>:<line #>:<column #>: runtime error: <error msg>'
|
||||
super(UbsanErrorParser, self).__init__(r"\S+:\d+:\d+: runtime error:")
|
||||
super().__init__(r"\S+:\d+:\d+: runtime error:")
|
||||
|
||||
|
||||
class ValgrindErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
# just grab the summary, valgrind doesn't clearly distinguish errors
|
||||
# from other log messages.
|
||||
super(ValgrindErrorParser, self).__init__(r"==\d+== ERROR SUMMARY:")
|
||||
super().__init__(r"==\d+== ERROR SUMMARY:")
|
||||
|
||||
|
||||
class CompatErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
super(CompatErrorParser, self).__init__(r"==== .*[Ee]rror.* ====$")
|
||||
super().__init__(r"==== .*[Ee]rror.* ====$")
|
||||
|
||||
|
||||
class TsanErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
super(TsanErrorParser, self).__init__(r"WARNING: ThreadSanitizer:")
|
||||
super().__init__(r"WARNING: ThreadSanitizer:")
|
||||
|
||||
|
||||
_TEST_NAME_TO_PARSERS = {
|
||||
|
||||
Vendored
+3
-1
@@ -133,7 +133,9 @@ Status Cache::CreateFromString(const ConfigOptions& config_options,
|
||||
std::shared_ptr<Cache>* result) {
|
||||
Status status;
|
||||
std::shared_ptr<Cache> cache;
|
||||
if (value.find("://") == std::string::npos) {
|
||||
if (StartsWith(value, "null")) {
|
||||
cache = nullptr;
|
||||
} else if (value.find("://") == std::string::npos) {
|
||||
if (value.find('=') == std::string::npos) {
|
||||
cache = NewLRUCache(ParseSizeT(value));
|
||||
} else {
|
||||
|
||||
Vendored
+38
-8
@@ -79,7 +79,11 @@ std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
|
||||
data_ptr = GetVarint32Ptr(data_ptr, data_ptr + 1,
|
||||
static_cast<uint32_t*>(&source_32));
|
||||
source = static_cast<CacheTier>(source_32);
|
||||
handle_value_charge -= (data_ptr - ptr->get());
|
||||
uint64_t data_size = 0;
|
||||
data_ptr = GetVarint64Ptr(data_ptr, ptr->get() + handle_value_charge,
|
||||
static_cast<uint64_t*>(&data_size));
|
||||
assert(handle_value_charge > data_size);
|
||||
handle_value_charge = data_size;
|
||||
}
|
||||
MemoryAllocator* allocator = cache_options_.memory_allocator.get();
|
||||
|
||||
@@ -169,13 +173,15 @@ Status CompressedSecondaryCache::InsertInternal(
|
||||
}
|
||||
|
||||
auto internal_helper = GetHelper(cache_options_.enable_custom_split_merge);
|
||||
char header[10];
|
||||
char header[20];
|
||||
char* payload = header;
|
||||
payload = EncodeVarint32(payload, static_cast<uint32_t>(type));
|
||||
payload = EncodeVarint32(payload, static_cast<uint32_t>(source));
|
||||
size_t data_size = (*helper->size_cb)(value);
|
||||
char* data_size_ptr = payload;
|
||||
payload = EncodeVarint64(payload, data_size);
|
||||
|
||||
size_t header_size = payload - header;
|
||||
size_t data_size = (*helper->size_cb)(value);
|
||||
size_t total_size = data_size + header_size;
|
||||
CacheAllocationPtr ptr =
|
||||
AllocateBlock(total_size, cache_options_.memory_allocator.get());
|
||||
@@ -210,6 +216,8 @@ Status CompressedSecondaryCache::InsertInternal(
|
||||
|
||||
val = Slice(compressed_val);
|
||||
data_size = compressed_val.size();
|
||||
payload = EncodeVarint64(data_size_ptr, data_size);
|
||||
header_size = payload - header;
|
||||
total_size = header_size + data_size;
|
||||
PERF_COUNTER_ADD(compressed_sec_cache_compressed_bytes, data_size);
|
||||
|
||||
@@ -222,14 +230,21 @@ Status CompressedSecondaryCache::InsertInternal(
|
||||
|
||||
PERF_COUNTER_ADD(compressed_sec_cache_insert_real_count, 1);
|
||||
if (cache_options_.enable_custom_split_merge) {
|
||||
size_t charge{0};
|
||||
CacheValueChunk* value_chunks_head =
|
||||
SplitValueIntoChunks(val, cache_options_.compression_type, charge);
|
||||
return cache_->Insert(key, value_chunks_head, internal_helper, charge);
|
||||
size_t split_charge{0};
|
||||
CacheValueChunk* value_chunks_head = SplitValueIntoChunks(
|
||||
val, cache_options_.compression_type, split_charge);
|
||||
return cache_->Insert(key, value_chunks_head, internal_helper,
|
||||
split_charge);
|
||||
} else {
|
||||
#ifdef ROCKSDB_MALLOC_USABLE_SIZE
|
||||
size_t charge = malloc_usable_size(ptr.get());
|
||||
#else
|
||||
size_t charge = total_size;
|
||||
#endif
|
||||
std::memcpy(ptr.get(), header, header_size);
|
||||
CacheAllocationPtr* buf = new CacheAllocationPtr(std::move(ptr));
|
||||
return cache_->Insert(key, buf, internal_helper, total_size);
|
||||
charge += sizeof(CacheAllocationPtr);
|
||||
return cache_->Insert(key, buf, internal_helper, charge);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,6 +413,21 @@ const Cache::CacheItemHelper* CompressedSecondaryCache::GetHelper(
|
||||
}
|
||||
}
|
||||
|
||||
size_t CompressedSecondaryCache::TEST_GetCharge(const Slice& key) {
|
||||
Cache::Handle* lru_handle = cache_->Lookup(key);
|
||||
if (lru_handle == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t charge = cache_->GetCharge(lru_handle);
|
||||
if (cache_->Value(lru_handle) != nullptr &&
|
||||
!cache_options_.enable_custom_split_merge) {
|
||||
charge -= 10;
|
||||
}
|
||||
cache_->Release(lru_handle, /*erase_if_last_ref=*/false);
|
||||
return charge;
|
||||
}
|
||||
|
||||
std::shared_ptr<SecondaryCache>
|
||||
CompressedSecondaryCacheOptions::MakeSharedSecondaryCache() const {
|
||||
return std::make_shared<CompressedSecondaryCache>(*this);
|
||||
|
||||
Vendored
+2
@@ -139,6 +139,8 @@ class CompressedSecondaryCache : public SecondaryCache {
|
||||
const Cache::CacheItemHelper* helper,
|
||||
CompressionType type, CacheTier source);
|
||||
|
||||
size_t TEST_GetCharge(const Slice& key);
|
||||
|
||||
// TODO: clean up to use cleaner interfaces in typed_cache.h
|
||||
const Cache::CacheItemHelper* GetHelper(bool enable_custom_split_merge) const;
|
||||
std::shared_ptr<Cache> cache_;
|
||||
|
||||
+4
@@ -39,6 +39,8 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
protected:
|
||||
void BasicTestHelper(std::shared_ptr<SecondaryCache> sec_cache,
|
||||
bool sec_cache_is_compressed) {
|
||||
CompressedSecondaryCache* comp_sec_cache =
|
||||
static_cast<CompressedSecondaryCache*>(sec_cache.get());
|
||||
get_perf_context()->Reset();
|
||||
bool kept_in_sec_cache{true};
|
||||
// Lookup an non-existent key.
|
||||
@@ -66,6 +68,8 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper(), false));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 1);
|
||||
|
||||
ASSERT_GT(comp_sec_cache->TEST_GetCharge(key1), 1000);
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1_2 =
|
||||
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/true,
|
||||
/*stats=*/nullptr, kept_in_sec_cache);
|
||||
|
||||
Vendored
+2
-1
@@ -271,7 +271,8 @@ Status CacheWithSecondaryAdapter::Insert(const Slice& key, ObjectPtr value,
|
||||
// Warm up the secondary cache with the compressed block. The secondary
|
||||
// cache may choose to ignore it based on the admission policy.
|
||||
if (value != nullptr && !compressed_value.empty() &&
|
||||
adm_policy_ == TieredAdmissionPolicy::kAdmPolicyThreeQueue) {
|
||||
adm_policy_ == TieredAdmissionPolicy::kAdmPolicyThreeQueue &&
|
||||
helper->IsSecondaryCacheCompatible()) {
|
||||
Status status = secondary_cache_->InsertSaved(key, compressed_value, type);
|
||||
assert(status.ok() || status.IsNotSupported());
|
||||
}
|
||||
|
||||
Vendored
+48
@@ -765,6 +765,54 @@ TEST_F(DBTieredSecondaryCacheTest, IterateTest) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBTieredSecondaryCacheTest, VolatileTierTest) {
|
||||
if (!LZ4_Supported()) {
|
||||
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
||||
return;
|
||||
}
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
// We want a block cache of size 5KB, and a compressed secondary cache of
|
||||
// size 5KB. However, we specify a block cache size of 256KB here in order
|
||||
// to take into account the cache reservation in the block cache on
|
||||
// behalf of the compressed cache. The unit of cache reservation is 256KB.
|
||||
// The effective block cache capacity will be calculated as 256 + 5 = 261KB,
|
||||
// and 256KB will be reserved for the compressed cache, leaving 5KB for
|
||||
// the primary block cache. We only have to worry about this here because
|
||||
// the cache size is so small.
|
||||
table_options.block_cache = NewCache(256 * 1024, 5 * 1024, 256 * 1024);
|
||||
table_options.block_size = 4 * 1024;
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.compression = kLZ4Compression;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
// Disable paranoid_file_checks so that flush will not read back the newly
|
||||
// written file
|
||||
options.paranoid_file_checks = false;
|
||||
options.lowest_used_cache_tier = CacheTier::kVolatileTier;
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
const int N = 256;
|
||||
for (int i = 0; i < N; i++) {
|
||||
std::string p_v;
|
||||
test::CompressibleString(&rnd, 0.5, 1007, &p_v);
|
||||
ASSERT_OK(Put(Key(i), p_v));
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Since lowest_used_cache_tier is the volatile tier, nothing should be
|
||||
// inserted in the secondary cache.
|
||||
std::string v = Get(Key(0));
|
||||
ASSERT_EQ(1007, v.size());
|
||||
ASSERT_EQ(nvm_sec_cache()->num_insert_saved(), 0u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_misses(), 0u);
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
class DBTieredAdmPolicyTest
|
||||
: public DBTieredSecondaryCacheTest,
|
||||
public testing::WithParamInterface<TieredAdmissionPolicy> {};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import optparse
|
||||
import re
|
||||
@@ -109,11 +108,11 @@ def report_coverage():
|
||||
|
||||
# Check if we need to display coverage info for interested files.
|
||||
if len(interested_files):
|
||||
per_file_coverage = dict(
|
||||
(fname, per_file_coverage[fname])
|
||||
per_file_coverage = {
|
||||
fname: per_file_coverage[fname]
|
||||
for fname in interested_files
|
||||
if fname in per_file_coverage
|
||||
)
|
||||
}
|
||||
# If we only interested in several files, it makes no sense to report
|
||||
# the total_coverage
|
||||
total_coverage = None
|
||||
|
||||
@@ -83,6 +83,8 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
Status Refresh() override;
|
||||
Status Refresh(const Snapshot*) override;
|
||||
|
||||
bool PrepareValue() override { return db_iter_->PrepareValue(); }
|
||||
|
||||
void Init(Env* env, const ReadOptions& read_options,
|
||||
const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options, const Version* version,
|
||||
|
||||
@@ -13,14 +13,11 @@ namespace ROCKSDB_NAMESPACE {
|
||||
class AttributeGroupIteratorImpl : public AttributeGroupIterator {
|
||||
public:
|
||||
AttributeGroupIteratorImpl(
|
||||
const Comparator* comparator,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
const std::vector<Iterator*>& child_iterators)
|
||||
: impl_(
|
||||
comparator, column_families, child_iterators, [this]() { Reset(); },
|
||||
[this](const autovector<MultiCfIteratorInfo>& items) {
|
||||
AddToAttributeGroups(items);
|
||||
}) {}
|
||||
const ReadOptions& read_options, const Comparator* comparator,
|
||||
std::vector<std::pair<ColumnFamilyHandle*, std::unique_ptr<Iterator>>>&&
|
||||
cfh_iter_pairs)
|
||||
: impl_(read_options, comparator, std::move(cfh_iter_pairs),
|
||||
ResetFunc(this), PopulateFunc(this)) {}
|
||||
~AttributeGroupIteratorImpl() override {}
|
||||
|
||||
// No copy allowed
|
||||
@@ -45,8 +42,36 @@ class AttributeGroupIteratorImpl : public AttributeGroupIterator {
|
||||
|
||||
void Reset() { attribute_groups_.clear(); }
|
||||
|
||||
bool PrepareValue() override { return impl_.PrepareValue(); }
|
||||
|
||||
private:
|
||||
MultiCfIteratorImpl impl_;
|
||||
class ResetFunc {
|
||||
public:
|
||||
explicit ResetFunc(AttributeGroupIteratorImpl* iter) : iter_(iter) {}
|
||||
|
||||
void operator()() const {
|
||||
assert(iter_);
|
||||
iter_->Reset();
|
||||
}
|
||||
|
||||
private:
|
||||
AttributeGroupIteratorImpl* iter_;
|
||||
};
|
||||
|
||||
class PopulateFunc {
|
||||
public:
|
||||
explicit PopulateFunc(AttributeGroupIteratorImpl* iter) : iter_(iter) {}
|
||||
|
||||
void operator()(const autovector<MultiCfIteratorInfo>& items) const {
|
||||
assert(iter_);
|
||||
iter_->AddToAttributeGroups(items);
|
||||
}
|
||||
|
||||
private:
|
||||
AttributeGroupIteratorImpl* iter_;
|
||||
};
|
||||
|
||||
MultiCfIteratorImpl<ResetFunc, PopulateFunc> impl_;
|
||||
IteratorAttributeGroups attribute_groups_;
|
||||
void AddToAttributeGroups(const autovector<MultiCfIteratorInfo>& items);
|
||||
};
|
||||
|
||||
@@ -42,6 +42,7 @@ Status BlobFileCache::GetBlobFileReader(
|
||||
assert(blob_file_reader);
|
||||
assert(blob_file_reader->IsEmpty());
|
||||
|
||||
// NOTE: sharing same Cache with table_cache
|
||||
const Slice key = GetSliceForKey(&blob_file_number);
|
||||
|
||||
assert(cache_);
|
||||
@@ -98,4 +99,13 @@ Status BlobFileCache::GetBlobFileReader(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void BlobFileCache::Evict(uint64_t blob_file_number) {
|
||||
// NOTE: sharing same Cache with table_cache
|
||||
const Slice key = GetSliceForKey(&blob_file_number);
|
||||
|
||||
assert(cache_);
|
||||
|
||||
cache_.get()->Erase(key);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -36,6 +36,15 @@ class BlobFileCache {
|
||||
uint64_t blob_file_number,
|
||||
CacheHandleGuard<BlobFileReader>* blob_file_reader);
|
||||
|
||||
// Called when a blob file is obsolete to ensure it is removed from the cache
|
||||
// to avoid effectively leaking the open file and assicated memory
|
||||
void Evict(uint64_t blob_file_number);
|
||||
|
||||
// Used to identify cache entries for blob files (not normally useful)
|
||||
static const Cache::CacheItemHelper* GetHelper() {
|
||||
return CacheInterface::GetBasicHelper();
|
||||
}
|
||||
|
||||
private:
|
||||
using CacheInterface =
|
||||
BasicTypedCacheInterface<BlobFileReader, CacheEntryRole::kMisc>;
|
||||
|
||||
@@ -20,23 +20,24 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
BlobSource::BlobSource(const ImmutableOptions* immutable_options,
|
||||
BlobSource::BlobSource(const ImmutableOptions& immutable_options,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const std::string& db_id,
|
||||
const std::string& db_session_id,
|
||||
BlobFileCache* blob_file_cache)
|
||||
: db_id_(db_id),
|
||||
db_session_id_(db_session_id),
|
||||
statistics_(immutable_options->statistics.get()),
|
||||
statistics_(immutable_options.statistics.get()),
|
||||
blob_file_cache_(blob_file_cache),
|
||||
blob_cache_(immutable_options->blob_cache),
|
||||
lowest_used_cache_tier_(immutable_options->lowest_used_cache_tier) {
|
||||
blob_cache_(immutable_options.blob_cache),
|
||||
lowest_used_cache_tier_(immutable_options.lowest_used_cache_tier) {
|
||||
auto bbto =
|
||||
immutable_options->table_factory->GetOptions<BlockBasedTableOptions>();
|
||||
mutable_cf_options.table_factory->GetOptions<BlockBasedTableOptions>();
|
||||
if (bbto &&
|
||||
bbto->cache_usage_options.options_overrides.at(CacheEntryRole::kBlobCache)
|
||||
.charged == CacheEntryRoleOptions::Decision::kEnabled) {
|
||||
blob_cache_ = SharedCacheInterface{std::make_shared<ChargedCache>(
|
||||
immutable_options->blob_cache, bbto->block_cache)};
|
||||
immutable_options.blob_cache, bbto->block_cache)};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
struct ImmutableOptions;
|
||||
struct MutableCFOptions;
|
||||
class Status;
|
||||
class FilePrefetchBuffer;
|
||||
class Slice;
|
||||
@@ -31,7 +32,10 @@ class Slice;
|
||||
// storage with minimal cost.
|
||||
class BlobSource {
|
||||
public:
|
||||
BlobSource(const ImmutableOptions* immutable_options,
|
||||
// NOTE: db_id, db_session_id, and blob_file_cache are saved by reference or
|
||||
// pointer.
|
||||
BlobSource(const ImmutableOptions& immutable_options,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const std::string& db_id, const std::string& db_session_id,
|
||||
BlobFileCache* blob_file_cache);
|
||||
|
||||
|
||||
+22
-14
@@ -148,6 +148,7 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
|
||||
DestroyAndReopen(options_);
|
||||
|
||||
ImmutableOptions immutable_options(options_);
|
||||
MutableCFOptions mutable_cf_options(options_);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -193,8 +194,8 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
|
||||
backing_cache.get(), &immutable_options, &file_options,
|
||||
column_family_id, blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
|
||||
BlobSource blob_source(&immutable_options, db_id_, db_session_id_,
|
||||
blob_file_cache.get());
|
||||
BlobSource blob_source(immutable_options, mutable_cf_options, db_id_,
|
||||
db_session_id_, blob_file_cache.get());
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = true;
|
||||
@@ -464,6 +465,7 @@ TEST_F(BlobSourceTest, GetCompressedBlobs) {
|
||||
DestroyAndReopen(options_);
|
||||
|
||||
ImmutableOptions immutable_options(options_);
|
||||
MutableCFOptions mutable_cf_options(options_);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -498,8 +500,8 @@ TEST_F(BlobSourceTest, GetCompressedBlobs) {
|
||||
backing_cache.get(), &immutable_options, &file_options,
|
||||
column_family_id, nullptr /*HistogramImpl*/, nullptr /*IOTracer*/);
|
||||
|
||||
BlobSource blob_source(&immutable_options, db_id_, db_session_id_,
|
||||
blob_file_cache.get());
|
||||
BlobSource blob_source(immutable_options, mutable_cf_options, db_id_,
|
||||
db_session_id_, blob_file_cache.get());
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = true;
|
||||
@@ -589,6 +591,7 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromMultiFiles) {
|
||||
DestroyAndReopen(options_);
|
||||
|
||||
ImmutableOptions immutable_options(options_);
|
||||
MutableCFOptions mutable_cf_options(options_);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -644,8 +647,8 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromMultiFiles) {
|
||||
backing_cache.get(), &immutable_options, &file_options,
|
||||
column_family_id, blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
|
||||
BlobSource blob_source(&immutable_options, db_id_, db_session_id_,
|
||||
blob_file_cache.get());
|
||||
BlobSource blob_source(immutable_options, mutable_cf_options, db_id_,
|
||||
db_session_id_, blob_file_cache.get());
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = true;
|
||||
@@ -782,6 +785,7 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromCache) {
|
||||
DestroyAndReopen(options_);
|
||||
|
||||
ImmutableOptions immutable_options(options_);
|
||||
MutableCFOptions mutable_cf_options(options_);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -827,8 +831,8 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromCache) {
|
||||
backing_cache.get(), &immutable_options, &file_options,
|
||||
column_family_id, blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
|
||||
BlobSource blob_source(&immutable_options, db_id_, db_session_id_,
|
||||
blob_file_cache.get());
|
||||
BlobSource blob_source(immutable_options, mutable_cf_options, db_id_,
|
||||
db_session_id_, blob_file_cache.get());
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = true;
|
||||
@@ -1105,6 +1109,7 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
|
||||
DestroyAndReopen(options_);
|
||||
|
||||
ImmutableOptions immutable_options(options_);
|
||||
MutableCFOptions mutable_cf_options(options_);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -1137,8 +1142,8 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
|
||||
backing_cache.get(), &immutable_options, &file_options, column_family_id,
|
||||
blob_file_read_hist, nullptr /*IOTracer*/));
|
||||
|
||||
BlobSource blob_source(&immutable_options, db_id_, db_session_id_,
|
||||
blob_file_cache.get());
|
||||
BlobSource blob_source(immutable_options, mutable_cf_options, db_id_,
|
||||
db_session_id_, blob_file_cache.get());
|
||||
|
||||
CacheHandleGuard<BlobFileReader> file_reader;
|
||||
ReadOptions read_options;
|
||||
@@ -1405,6 +1410,7 @@ TEST_F(BlobSourceCacheReservationTest, SimpleCacheReservation) {
|
||||
DestroyAndReopen(options_);
|
||||
|
||||
ImmutableOptions immutable_options(options_);
|
||||
MutableCFOptions mutable_cf_options(options_);
|
||||
|
||||
constexpr ExpirationRange expiration_range;
|
||||
|
||||
@@ -1426,8 +1432,8 @@ TEST_F(BlobSourceCacheReservationTest, SimpleCacheReservation) {
|
||||
backing_cache.get(), &immutable_options, &file_options,
|
||||
kColumnFamilyId, blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
|
||||
BlobSource blob_source(&immutable_options, db_id_, db_session_id_,
|
||||
blob_file_cache.get());
|
||||
BlobSource blob_source(immutable_options, mutable_cf_options, db_id_,
|
||||
db_session_id_, blob_file_cache.get());
|
||||
|
||||
ConcurrentCacheReservationManager* cache_res_mgr =
|
||||
static_cast<ChargedCache*>(blob_source.GetBlobCache())
|
||||
@@ -1519,6 +1525,8 @@ TEST_F(BlobSourceCacheReservationTest, IncreaseCacheReservation) {
|
||||
DestroyAndReopen(options_);
|
||||
|
||||
ImmutableOptions immutable_options(options_);
|
||||
MutableCFOptions mutable_cf_options(options_);
|
||||
|
||||
constexpr size_t blob_size = 24 << 10; // 24KB
|
||||
for (size_t i = 0; i < kNumBlobs; ++i) {
|
||||
blob_file_size_ -= blobs_[i].size(); // old blob size
|
||||
@@ -1546,8 +1554,8 @@ TEST_F(BlobSourceCacheReservationTest, IncreaseCacheReservation) {
|
||||
backing_cache.get(), &immutable_options, &file_options,
|
||||
kColumnFamilyId, blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
|
||||
BlobSource blob_source(&immutable_options, db_id_, db_session_id_,
|
||||
blob_file_cache.get());
|
||||
BlobSource blob_source(immutable_options, mutable_cf_options, db_id_,
|
||||
db_session_id_, blob_file_cache.get());
|
||||
|
||||
ConcurrentCacheReservationManager* cache_res_mgr =
|
||||
static_cast<ChargedCache*>(blob_source.GetBlobCache())
|
||||
|
||||
@@ -374,6 +374,115 @@ TEST_F(DBBlobBasicTest, IterateBlobsFromCachePinning) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, IterateBlobsAllowUnpreparedValue) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr size_t num_blobs = 5;
|
||||
std::vector<std::string> keys;
|
||||
std::vector<std::string> blobs;
|
||||
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
keys.emplace_back("key" + std::to_string(i));
|
||||
blobs.emplace_back("blob" + std::to_string(i));
|
||||
ASSERT_OK(Put(keys[i], blobs[i]));
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.allow_unprepared_value = true;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
|
||||
{
|
||||
size_t i = 0;
|
||||
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
ASSERT_EQ(iter->key(), keys[i]);
|
||||
ASSERT_TRUE(iter->value().empty());
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
ASSERT_TRUE(iter->PrepareValue());
|
||||
|
||||
ASSERT_EQ(iter->key(), keys[i]);
|
||||
ASSERT_EQ(iter->value(), blobs[i]);
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(i, num_blobs);
|
||||
}
|
||||
|
||||
{
|
||||
size_t i = 0;
|
||||
|
||||
for (iter->SeekToLast(); iter->Valid(); iter->Prev()) {
|
||||
ASSERT_EQ(iter->key(), keys[num_blobs - 1 - i]);
|
||||
ASSERT_TRUE(iter->value().empty());
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
ASSERT_TRUE(iter->PrepareValue());
|
||||
|
||||
ASSERT_EQ(iter->key(), keys[num_blobs - 1 - i]);
|
||||
ASSERT_EQ(iter->value(), blobs[num_blobs - 1 - i]);
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(i, num_blobs);
|
||||
}
|
||||
|
||||
{
|
||||
size_t i = 1;
|
||||
|
||||
for (iter->Seek(keys[i]); iter->Valid(); iter->Next()) {
|
||||
ASSERT_EQ(iter->key(), keys[i]);
|
||||
ASSERT_TRUE(iter->value().empty());
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
ASSERT_TRUE(iter->PrepareValue());
|
||||
|
||||
ASSERT_EQ(iter->key(), keys[i]);
|
||||
ASSERT_EQ(iter->value(), blobs[i]);
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(i, num_blobs);
|
||||
}
|
||||
|
||||
{
|
||||
size_t i = 1;
|
||||
|
||||
for (iter->SeekForPrev(keys[num_blobs - 1 - i]); iter->Valid();
|
||||
iter->Prev()) {
|
||||
ASSERT_EQ(iter->key(), keys[num_blobs - 1 - i]);
|
||||
ASSERT_TRUE(iter->value().empty());
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
ASSERT_TRUE(iter->PrepareValue());
|
||||
|
||||
ASSERT_EQ(iter->key(), keys[num_blobs - 1 - i]);
|
||||
ASSERT_EQ(iter->value(), blobs[num_blobs - 1 - i]);
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(i, num_blobs);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, MultiGetBlobs) {
|
||||
constexpr size_t min_blob_size = 6;
|
||||
|
||||
@@ -1655,6 +1764,46 @@ TEST_P(DBBlobBasicIOErrorTest, CompactionFilterReadBlob_IOError) {
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_P(DBBlobBasicIOErrorTest, IterateBlobsAllowUnpreparedValue_IOError) {
|
||||
Options options;
|
||||
options.env = fault_injection_env_.get();
|
||||
options.enable_blob_files = true;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr char key[] = "key";
|
||||
constexpr char blob_value[] = "blob_value";
|
||||
|
||||
ASSERT_OK(Put(key, blob_value));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* /* arg */) {
|
||||
fault_injection_env_->SetFilesystemActive(false,
|
||||
Status::IOError(sync_point_));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.allow_unprepared_value = true;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
iter->SeekToFirst();
|
||||
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key(), key);
|
||||
ASSERT_TRUE(iter->value().empty());
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
ASSERT_FALSE(iter->PrepareValue());
|
||||
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_TRUE(iter->status().IsIOError());
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, WarmCacheWithBlobsDuringFlush) {
|
||||
Options options = GetDefaultOptions();
|
||||
|
||||
|
||||
+13
-24
@@ -53,7 +53,7 @@ TableBuilder* NewTableBuilder(const TableBuilderOptions& tboptions,
|
||||
assert((tboptions.column_family_id ==
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
|
||||
tboptions.column_family_name.empty());
|
||||
return tboptions.ioptions.table_factory->NewTableBuilder(tboptions, file);
|
||||
return tboptions.moptions.table_factory->NewTableBuilder(tboptions, file);
|
||||
}
|
||||
|
||||
Status BuildTable(
|
||||
@@ -206,10 +206,6 @@ Status BuildTable(
|
||||
/*compaction=*/nullptr, compaction_filter.get(),
|
||||
/*shutting_down=*/nullptr, db_options.info_log, full_history_ts_low);
|
||||
|
||||
const size_t ts_sz = ucmp->timestamp_size();
|
||||
const bool logical_strip_timestamp =
|
||||
ts_sz > 0 && !ioptions.persist_user_defined_timestamps;
|
||||
|
||||
SequenceNumber smallest_preferred_seqno = kMaxSequenceNumber;
|
||||
std::string key_after_flush_buf;
|
||||
std::string value_buf;
|
||||
@@ -222,16 +218,6 @@ Status BuildTable(
|
||||
Slice key_after_flush = key_after_flush_buf;
|
||||
Slice value_after_flush = value;
|
||||
|
||||
// If user defined timestamps will be stripped from user key after flush,
|
||||
// the in memory version of the key act logically the same as one with a
|
||||
// minimum timestamp. We update the timestamp here so file boundary and
|
||||
// output validator, block builder all see the effect of the stripping.
|
||||
if (logical_strip_timestamp) {
|
||||
key_after_flush_buf.clear();
|
||||
ReplaceInternalKeyWithMinTimestamp(&key_after_flush_buf, key, ts_sz);
|
||||
key_after_flush = key_after_flush_buf;
|
||||
}
|
||||
|
||||
if (ikey.type == kTypeValuePreferredSeqno) {
|
||||
auto [unpacked_value, unix_write_time] =
|
||||
ParsePackedValueWithWriteTime(value);
|
||||
@@ -291,11 +277,7 @@ Status BuildTable(
|
||||
Slice last_tombstone_start_user_key{};
|
||||
for (range_del_it->SeekToFirst(); range_del_it->Valid();
|
||||
range_del_it->Next()) {
|
||||
// When user timestamp should not be persisted, we logically strip a
|
||||
// range tombstone's start and end key's timestamp (replace it with min
|
||||
// timestamp) before passing them along to table builder and to update
|
||||
// file boundaries.
|
||||
auto tombstone = range_del_it->Tombstone(logical_strip_timestamp);
|
||||
auto tombstone = range_del_it->Tombstone();
|
||||
std::pair<InternalKey, Slice> kv = tombstone.Serialize();
|
||||
builder->Add(kv.first.Encode(), kv.second);
|
||||
InternalKey tombstone_end = tombstone.SerializeEndKey();
|
||||
@@ -438,8 +420,7 @@ Status BuildTable(
|
||||
// the goal is to cache it here for further user reads.
|
||||
std::unique_ptr<InternalIterator> it(table_cache->NewIterator(
|
||||
tboptions.read_options, file_options, tboptions.internal_comparator,
|
||||
*meta, nullptr /* range_del_agg */,
|
||||
mutable_cf_options.prefix_extractor, nullptr,
|
||||
*meta, nullptr /* range_del_agg */, mutable_cf_options, nullptr,
|
||||
(internal_stats == nullptr) ? nullptr
|
||||
: internal_stats->GetFileReadHist(0),
|
||||
TableReaderCaller::kFlush, /*arena=*/nullptr,
|
||||
@@ -447,8 +428,7 @@ Status BuildTable(
|
||||
MaxFileSizeForL0MetaPin(mutable_cf_options),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key*/ nullptr,
|
||||
/*allow_unprepared_value*/ false,
|
||||
mutable_cf_options.block_protection_bytes_per_key));
|
||||
/*allow_unprepared_value*/ false));
|
||||
s = it->status();
|
||||
if (s.ok() && paranoid_file_checks) {
|
||||
OutputValidator file_validator(tboptions.internal_comparator,
|
||||
@@ -480,9 +460,18 @@ Status BuildTable(
|
||||
Status prepare =
|
||||
WritableFileWriter::PrepareIOOptions(tboptions.write_options, opts);
|
||||
if (prepare.ok()) {
|
||||
// FIXME: track file for "slow" deletion, e.g. into the
|
||||
// VersionSet::obsolete_files_ pipeline
|
||||
Status ignored = fs->DeleteFile(fname, opts, dbg);
|
||||
ignored.PermitUncheckedError();
|
||||
}
|
||||
// Ensure we don't leak table cache entries when throwing away output
|
||||
// files. (The usual logic in PurgeObsoleteFiles is not applicable because
|
||||
// this function deletes the obsolete file itself, while they should
|
||||
// probably go into the VersionSet::obsolete_files_ pipeline.)
|
||||
TableCache::ReleaseObsolete(table_cache->get_cache().get(),
|
||||
meta->fd.GetNumber(), nullptr /*handle*/,
|
||||
mutable_cf_options.uncache_aggressiveness);
|
||||
}
|
||||
|
||||
assert(blob_file_additions || blob_file_paths.empty());
|
||||
|
||||
+35
-10
@@ -9,17 +9,14 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// EXPERIMENTAL
|
||||
class CoalescingIterator : public Iterator {
|
||||
public:
|
||||
CoalescingIterator(const Comparator* comparator,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
const std::vector<Iterator*>& child_iterators)
|
||||
: impl_(
|
||||
comparator, column_families, child_iterators, [this]() { Reset(); },
|
||||
[this](const autovector<MultiCfIteratorInfo>& items) {
|
||||
Coalesce(items);
|
||||
}) {}
|
||||
CoalescingIterator(
|
||||
const ReadOptions& read_options, const Comparator* comparator,
|
||||
std::vector<std::pair<ColumnFamilyHandle*, std::unique_ptr<Iterator>>>&&
|
||||
cfh_iter_pairs)
|
||||
: impl_(read_options, comparator, std::move(cfh_iter_pairs),
|
||||
ResetFunc(this), PopulateFunc(this)) {}
|
||||
~CoalescingIterator() override {}
|
||||
|
||||
// No copy allowed
|
||||
@@ -50,8 +47,36 @@ class CoalescingIterator : public Iterator {
|
||||
wide_columns_.clear();
|
||||
}
|
||||
|
||||
bool PrepareValue() override { return impl_.PrepareValue(); }
|
||||
|
||||
private:
|
||||
MultiCfIteratorImpl impl_;
|
||||
class ResetFunc {
|
||||
public:
|
||||
explicit ResetFunc(CoalescingIterator* iter) : iter_(iter) {}
|
||||
|
||||
void operator()() const {
|
||||
assert(iter_);
|
||||
iter_->Reset();
|
||||
}
|
||||
|
||||
private:
|
||||
CoalescingIterator* iter_;
|
||||
};
|
||||
|
||||
class PopulateFunc {
|
||||
public:
|
||||
explicit PopulateFunc(CoalescingIterator* iter) : iter_(iter) {}
|
||||
|
||||
void operator()(const autovector<MultiCfIteratorInfo>& items) const {
|
||||
assert(iter_);
|
||||
iter_->Coalesce(items);
|
||||
}
|
||||
|
||||
private:
|
||||
CoalescingIterator* iter_;
|
||||
};
|
||||
|
||||
MultiCfIteratorImpl<ResetFunc, PopulateFunc> impl_;
|
||||
Slice value_;
|
||||
WideColumns wide_columns_;
|
||||
|
||||
|
||||
+21
-10
@@ -466,7 +466,7 @@ void SuperVersion::Cleanup() {
|
||||
// decrement reference to the immutable MemtableList
|
||||
// this SV object was pointing to.
|
||||
imm->Unref(&to_delete);
|
||||
MemTable* m = mem->Unref();
|
||||
ReadOnlyMemTable* m = mem->Unref();
|
||||
if (m != nullptr) {
|
||||
auto* memory_usage = current->cfd()->imm()->current_memory_usage();
|
||||
assert(*memory_usage >= m->ApproximateMemoryUsage());
|
||||
@@ -595,8 +595,8 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
blob_file_cache_.reset(
|
||||
new BlobFileCache(_table_cache, ioptions(), soptions(), id_,
|
||||
internal_stats_->GetBlobFileReadHist(), io_tracer));
|
||||
blob_source_.reset(new BlobSource(ioptions(), db_id, db_session_id,
|
||||
blob_file_cache_.get()));
|
||||
blob_source_.reset(new BlobSource(ioptions_, mutable_cf_options_, db_id,
|
||||
db_session_id, blob_file_cache_.get()));
|
||||
|
||||
if (ioptions_.compaction_style == kCompactionStyleLevel) {
|
||||
compaction_picker_.reset(
|
||||
@@ -693,9 +693,9 @@ ColumnFamilyData::~ColumnFamilyData() {
|
||||
if (mem_ != nullptr) {
|
||||
delete mem_->Unref();
|
||||
}
|
||||
autovector<MemTable*> to_delete;
|
||||
autovector<ReadOnlyMemTable*> to_delete;
|
||||
imm_.current()->Unref(&to_delete);
|
||||
for (MemTable* m : to_delete) {
|
||||
for (auto* m : to_delete) {
|
||||
delete m;
|
||||
}
|
||||
|
||||
@@ -901,7 +901,11 @@ uint64_t GetPendingCompactionBytesForCompactionSpeedup(
|
||||
return slowdown_threshold;
|
||||
}
|
||||
|
||||
uint64_t size_threshold = bottommost_files_size / kBottommostSizeDivisor;
|
||||
// Prevent a small CF from triggering parallel compactions for other CFs.
|
||||
// Require compaction debt to be more than a full L0 to Lbase compaction.
|
||||
const uint64_t kMinDebtSize = 2 * mutable_cf_options.max_bytes_for_level_base;
|
||||
uint64_t size_threshold =
|
||||
std::max(bottommost_files_size / kBottommostSizeDivisor, kMinDebtSize);
|
||||
return std::min(size_threshold, slowdown_threshold);
|
||||
}
|
||||
|
||||
@@ -1172,10 +1176,12 @@ bool ColumnFamilyData::NeedsCompaction() const {
|
||||
|
||||
Compaction* ColumnFamilyData::PickCompaction(
|
||||
const MutableCFOptions& mutable_options,
|
||||
const MutableDBOptions& mutable_db_options, LogBuffer* log_buffer) {
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, LogBuffer* log_buffer) {
|
||||
auto* result = compaction_picker_->PickCompaction(
|
||||
GetName(), mutable_options, mutable_db_options, current_->storage_info(),
|
||||
log_buffer);
|
||||
GetName(), mutable_options, mutable_db_options, existing_snapshots,
|
||||
snapshot_checker, current_->storage_info(), log_buffer);
|
||||
if (result != nullptr) {
|
||||
result->FinalizeInputInfo(current_);
|
||||
}
|
||||
@@ -1202,7 +1208,7 @@ Status ColumnFamilyData::RangesOverlapWithMemtables(
|
||||
MergeIteratorBuilder merge_iter_builder(&internal_comparator_, &arena);
|
||||
merge_iter_builder.AddIterator(super_version->mem->NewIterator(
|
||||
read_opts, /*seqno_to_time_mapping=*/nullptr, &arena,
|
||||
/*prefix_extractor=*/nullptr));
|
||||
/*prefix_extractor=*/nullptr, /*for_flush=*/false));
|
||||
super_version->imm->AddIterators(read_opts, /*seqno_to_time_mapping=*/nullptr,
|
||||
/*prefix_extractor=*/nullptr,
|
||||
&merge_iter_builder,
|
||||
@@ -1555,6 +1561,11 @@ Status ColumnFamilyData::SetOptions(
|
||||
BuildColumnFamilyOptions(initial_cf_options_, mutable_cf_options_);
|
||||
ConfigOptions config_opts;
|
||||
config_opts.mutable_options_only = true;
|
||||
#ifndef NDEBUG
|
||||
if (TEST_allowSetOptionsImmutableInMutable) {
|
||||
config_opts.mutable_options_only = false;
|
||||
}
|
||||
#endif
|
||||
Status s = GetColumnFamilyOptionsFromMap(config_opts, cf_opts, options_map,
|
||||
&cf_opts);
|
||||
if (s.ok()) {
|
||||
|
||||
+16
-8
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "cache/cache_reservation_manager.h"
|
||||
#include "db/memtable_list.h"
|
||||
#include "db/snapshot_checker.h"
|
||||
#include "db/table_cache.h"
|
||||
#include "db/table_properties_collector.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
@@ -206,7 +207,7 @@ struct SuperVersion {
|
||||
// Accessing members of this class is not thread-safe and requires external
|
||||
// synchronization (ie db mutex held or on write thread).
|
||||
ColumnFamilyData* cfd;
|
||||
MemTable* mem;
|
||||
ReadOnlyMemTable* mem;
|
||||
MemTableListVersion* imm;
|
||||
Version* current;
|
||||
MutableCFOptions mutable_cf_options;
|
||||
@@ -268,7 +269,7 @@ struct SuperVersion {
|
||||
// We need to_delete because during Cleanup(), imm->Unref() returns
|
||||
// all memtables that we need to free through this vector. We then
|
||||
// delete all those memtables outside of mutex, during destruction
|
||||
autovector<MemTable*> to_delete;
|
||||
autovector<ReadOnlyMemTable*> to_delete;
|
||||
};
|
||||
|
||||
Status CheckCompressionSupported(const ColumnFamilyOptions& cf_options);
|
||||
@@ -385,12 +386,16 @@ class ColumnFamilyData {
|
||||
uint64_t GetTotalSstFilesSize() const; // REQUIRE: DB mutex held
|
||||
uint64_t GetLiveSstFilesSize() const; // REQUIRE: DB mutex held
|
||||
uint64_t GetTotalBlobFileSize() const; // REQUIRE: DB mutex held
|
||||
// REQUIRE: DB mutex held
|
||||
void SetMemtable(MemTable* new_mem) {
|
||||
uint64_t memtable_id = last_memtable_id_.fetch_add(1) + 1;
|
||||
new_mem->SetID(memtable_id);
|
||||
AssignMemtableID(new_mem);
|
||||
mem_ = new_mem;
|
||||
}
|
||||
|
||||
void AssignMemtableID(ReadOnlyMemTable* new_imm) {
|
||||
new_imm->SetID(++last_memtable_id_);
|
||||
}
|
||||
|
||||
// calculate the oldest log needed for the durability of this column family
|
||||
uint64_t OldestLogToKeep();
|
||||
|
||||
@@ -401,15 +406,18 @@ class ColumnFamilyData {
|
||||
SequenceNumber earliest_seq);
|
||||
|
||||
TableCache* table_cache() const { return table_cache_.get(); }
|
||||
BlobFileCache* blob_file_cache() const { return blob_file_cache_.get(); }
|
||||
BlobSource* blob_source() const { return blob_source_.get(); }
|
||||
|
||||
// See documentation in compaction_picker.h
|
||||
// REQUIRES: DB mutex held
|
||||
bool NeedsCompaction() const;
|
||||
// REQUIRES: DB mutex held
|
||||
Compaction* PickCompaction(const MutableCFOptions& mutable_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
LogBuffer* log_buffer);
|
||||
Compaction* PickCompaction(
|
||||
const MutableCFOptions& mutable_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, LogBuffer* log_buffer);
|
||||
|
||||
// Check if the passed range overlap with any running compactions.
|
||||
// REQUIRES: DB mutex held
|
||||
@@ -669,7 +677,7 @@ class ColumnFamilyData {
|
||||
bool allow_2pc_;
|
||||
|
||||
// Memtable id to track flush.
|
||||
std::atomic<uint64_t> last_memtable_id_;
|
||||
uint64_t last_memtable_id_;
|
||||
|
||||
// Directories corresponding to cf_paths.
|
||||
std::vector<std::shared_ptr<FSDirectory>> data_dirs_;
|
||||
|
||||
@@ -3012,19 +3012,25 @@ TEST_P(ColumnFamilyTest, CompactionSpeedupForCompactionDebt) {
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
|
||||
{
|
||||
// 1MB debt is way bigger than bottommost data so definitely triggers
|
||||
// speedup.
|
||||
VersionStorageInfo* vstorage = cfd->current()->storage_info();
|
||||
vstorage->TEST_set_estimated_compaction_needed_bytes(1048576 /* 1MB */,
|
||||
dbmu);
|
||||
RecalculateWriteStallConditions(cfd, mutable_cf_options);
|
||||
ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed());
|
||||
|
||||
// Eight bytes is way smaller than bottommost data so definitely does not
|
||||
// trigger speedup.
|
||||
vstorage->TEST_set_estimated_compaction_needed_bytes(8, dbmu);
|
||||
RecalculateWriteStallConditions(cfd, mutable_cf_options);
|
||||
ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed());
|
||||
|
||||
// 1MB is much larger than bottommost level size. However, since it's too
|
||||
// small in terms of absolute size, it does not trigger parallel compaction
|
||||
// in this case (see GetPendingCompactionBytesForCompactionSpeedup()).
|
||||
vstorage->TEST_set_estimated_compaction_needed_bytes(1048576 /* 1MB */,
|
||||
dbmu);
|
||||
RecalculateWriteStallConditions(cfd, mutable_cf_options);
|
||||
ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed());
|
||||
|
||||
vstorage->TEST_set_estimated_compaction_needed_bytes(
|
||||
2 * mutable_cf_options.max_bytes_for_level_base, dbmu);
|
||||
RecalculateWriteStallConditions(cfd, mutable_cf_options);
|
||||
ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3870,6 +3876,91 @@ TEST_F(ManualFlushSkipRetainUDTTest, ManualFlush) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(ManualFlushSkipRetainUDTTest, FlushRemovesStaleEntries) {
|
||||
column_family_options_.max_write_buffer_number = 4;
|
||||
Open();
|
||||
ASSERT_OK(db_->IncreaseFullHistoryTsLow(handles_[0], EncodeAsUint64(0)));
|
||||
|
||||
ColumnFamilyHandle* cfh = db_->DefaultColumnFamily();
|
||||
ColumnFamilyData* cfd =
|
||||
static_cast_with_check<ColumnFamilyHandleImpl>(cfh)->cfd();
|
||||
for (int version = 0; version < 100; version++) {
|
||||
if (version == 50) {
|
||||
ASSERT_OK(static_cast_with_check<DBImpl>(db_)->TEST_SwitchMemtable(cfd));
|
||||
}
|
||||
ASSERT_OK(
|
||||
Put(0, "foo", EncodeAsUint64(version), "v" + std::to_string(version)));
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush(0));
|
||||
TablePropertiesCollection tables_properties;
|
||||
ASSERT_OK(db_->GetPropertiesOfAllTables(&tables_properties));
|
||||
ASSERT_EQ(1, tables_properties.size());
|
||||
std::shared_ptr<const TableProperties> table_properties =
|
||||
tables_properties.begin()->second;
|
||||
ASSERT_EQ(1, table_properties->num_entries);
|
||||
ASSERT_EQ(0, table_properties->num_deletions);
|
||||
ASSERT_EQ(0, table_properties->num_range_deletions);
|
||||
CheckEffectiveCutoffTime(100);
|
||||
CheckAutomaticFlushRetainUDT(101);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(ManualFlushSkipRetainUDTTest, RangeDeletionFlushRemovesStaleEntries) {
|
||||
column_family_options_.max_write_buffer_number = 4;
|
||||
Open();
|
||||
// TODO(yuzhangyu): a non 0 full history ts low is needed for this garbage
|
||||
// collection to kick in. This doesn't work well for the very first flush of
|
||||
// the column family. Not a big issue, but would be nice to improve this.
|
||||
ASSERT_OK(db_->IncreaseFullHistoryTsLow(handles_[0], EncodeAsUint64(9)));
|
||||
|
||||
for (int i = 10; i < 100; i++) {
|
||||
ASSERT_OK(Put(0, "foo" + std::to_string(i), EncodeAsUint64(i),
|
||||
"val" + std::to_string(i)));
|
||||
if (i % 2 == 1) {
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), "foo" + std::to_string(i - 1),
|
||||
"foo" + std::to_string(i), EncodeAsUint64(i)));
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush(0));
|
||||
CheckEffectiveCutoffTime(100);
|
||||
std::string read_ts = EncodeAsUint64(100);
|
||||
std::string min_ts = EncodeAsUint64(0);
|
||||
ReadOptions ropts;
|
||||
Slice read_ts_slice = read_ts;
|
||||
std::string value;
|
||||
ropts.timestamp = &read_ts_slice;
|
||||
{
|
||||
Iterator* iter = db_->NewIterator(ropts);
|
||||
iter->SeekToFirst();
|
||||
int i = 11;
|
||||
while (iter->Valid()) {
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("foo" + std::to_string(i), iter->key());
|
||||
ASSERT_EQ("val" + std::to_string(i), iter->value());
|
||||
ASSERT_EQ(min_ts, iter->timestamp());
|
||||
iter->Next();
|
||||
i += 2;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
delete iter;
|
||||
}
|
||||
TablePropertiesCollection tables_properties;
|
||||
ASSERT_OK(db_->GetPropertiesOfAllTables(&tables_properties));
|
||||
ASSERT_EQ(1, tables_properties.size());
|
||||
std::shared_ptr<const TableProperties> table_properties =
|
||||
tables_properties.begin()->second;
|
||||
// 45 point data + 45 range deletions. 45 obsolete point data are garbage
|
||||
// collected.
|
||||
ASSERT_EQ(90, table_properties->num_entries);
|
||||
ASSERT_EQ(45, table_properties->num_deletions);
|
||||
ASSERT_EQ(45, table_properties->num_range_deletions);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(ManualFlushSkipRetainUDTTest, ManualCompaction) {
|
||||
Open();
|
||||
ASSERT_OK(db_->IncreaseFullHistoryTsLow(handles_[0], EncodeAsUint64(0)));
|
||||
|
||||
+126
-16
@@ -283,9 +283,10 @@ Compaction::Compaction(
|
||||
uint32_t _output_path_id, CompressionType _compression,
|
||||
CompressionOptions _compression_opts, Temperature _output_temperature,
|
||||
uint32_t _max_subcompactions, std::vector<FileMetaData*> _grandparents,
|
||||
bool _manual_compaction, const std::string& _trim_ts, double _score,
|
||||
bool _deletion_compaction, bool l0_files_might_overlap,
|
||||
CompactionReason _compaction_reason,
|
||||
std::optional<SequenceNumber> _earliest_snapshot,
|
||||
const SnapshotChecker* _snapshot_checker, bool _manual_compaction,
|
||||
const std::string& _trim_ts, double _score, bool _deletion_compaction,
|
||||
bool l0_files_might_overlap, CompactionReason _compaction_reason,
|
||||
BlobGarbageCollectionPolicy _blob_garbage_collection_policy,
|
||||
double _blob_garbage_collection_age_cutoff)
|
||||
: input_vstorage_(vstorage),
|
||||
@@ -307,6 +308,8 @@ Compaction::Compaction(
|
||||
l0_files_might_overlap_(l0_files_might_overlap),
|
||||
inputs_(PopulateWithAtomicBoundaries(vstorage, std::move(_inputs))),
|
||||
grandparents_(std::move(_grandparents)),
|
||||
earliest_snapshot_(_earliest_snapshot),
|
||||
snapshot_checker_(_snapshot_checker),
|
||||
score_(_score),
|
||||
bottommost_level_(
|
||||
// For simplicity, we don't support the concept of "bottommost level"
|
||||
@@ -342,8 +345,9 @@ Compaction::Compaction(
|
||||
_compaction_reason == CompactionReason::kExternalSstIngestion ||
|
||||
_compaction_reason == CompactionReason::kRefitLevel
|
||||
? Compaction::kInvalidLevel
|
||||
: EvaluatePenultimateLevel(vstorage, immutable_options_,
|
||||
start_level_, output_level_)) {
|
||||
: EvaluatePenultimateLevel(vstorage, mutable_cf_options_,
|
||||
immutable_options_, start_level_,
|
||||
output_level_)) {
|
||||
MarkFilesBeingCompacted(true);
|
||||
if (is_manual_compaction_) {
|
||||
compaction_reason_ = CompactionReason::kManualCompaction;
|
||||
@@ -364,12 +368,17 @@ Compaction::Compaction(
|
||||
}
|
||||
#endif
|
||||
|
||||
// setup input_levels_
|
||||
// setup input_levels_ and filtered_input_levels_
|
||||
{
|
||||
input_levels_.resize(num_input_levels());
|
||||
for (size_t which = 0; which < num_input_levels(); which++) {
|
||||
DoGenerateLevelFilesBrief(&input_levels_[which], inputs_[which].files,
|
||||
&arena_);
|
||||
filtered_input_levels_.resize(num_input_levels());
|
||||
if (earliest_snapshot_.has_value()) {
|
||||
FilterInputsForCompactionIterator();
|
||||
} else {
|
||||
for (size_t which = 0; which < num_input_levels(); which++) {
|
||||
DoGenerateLevelFilesBrief(&input_levels_[which], inputs_[which].files,
|
||||
&arena_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -745,8 +754,10 @@ void Compaction::ResetNextCompactionIndex() {
|
||||
}
|
||||
|
||||
namespace {
|
||||
int InputSummary(const std::vector<FileMetaData*>& files, char* output,
|
||||
int InputSummary(const std::vector<FileMetaData*>& files,
|
||||
const std::vector<bool>& files_filtered, char* output,
|
||||
int len) {
|
||||
assert(files_filtered.empty() || (files.size() == files_filtered.size()));
|
||||
*output = '\0';
|
||||
int write = 0;
|
||||
for (size_t i = 0; i < files.size(); i++) {
|
||||
@@ -754,8 +765,14 @@ int InputSummary(const std::vector<FileMetaData*>& files, char* output,
|
||||
int ret;
|
||||
char sztxt[16];
|
||||
AppendHumanBytes(files.at(i)->fd.GetFileSize(), sztxt, 16);
|
||||
ret = snprintf(output + write, sz, "%" PRIu64 "(%s) ",
|
||||
files.at(i)->fd.GetNumber(), sztxt);
|
||||
if (files_filtered.empty()) {
|
||||
ret = snprintf(output + write, sz, "%" PRIu64 "(%s) ",
|
||||
files.at(i)->fd.GetNumber(), sztxt);
|
||||
} else {
|
||||
ret = snprintf(output + write, sz, "%" PRIu64 "(%s filtered:%s) ",
|
||||
files.at(i)->fd.GetNumber(), sztxt,
|
||||
files_filtered.at(i) ? "true" : "false");
|
||||
}
|
||||
if (ret < 0 || ret >= sz) {
|
||||
break;
|
||||
}
|
||||
@@ -781,8 +798,15 @@ void Compaction::Summary(char* output, int len) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
write +=
|
||||
InputSummary(inputs_[level_iter].files, output + write, len - write);
|
||||
|
||||
assert(non_start_level_input_files_filtered_.empty() ||
|
||||
non_start_level_input_files_filtered_.size() == inputs_.size() - 1);
|
||||
write += InputSummary(
|
||||
inputs_[level_iter].files,
|
||||
(level_iter == 0 || non_start_level_input_files_filtered_.empty())
|
||||
? std::vector<bool>{}
|
||||
: non_start_level_input_files_filtered_[level_iter - 1],
|
||||
output + write, len - write);
|
||||
if (write < 0 || write >= len) {
|
||||
return;
|
||||
}
|
||||
@@ -865,7 +889,7 @@ bool Compaction::ShouldFormSubcompactions() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cfd_->ioptions()->table_factory->Name() ==
|
||||
if (mutable_cf_options_.table_factory->Name() ==
|
||||
TableFactory::kPlainTableName()) {
|
||||
return false;
|
||||
}
|
||||
@@ -913,6 +937,25 @@ bool Compaction::DoesInputReferenceBlobFiles() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t Compaction::MaxInputFileNewestKeyTime(const InternalKey* start,
|
||||
const InternalKey* end) const {
|
||||
uint64_t newest_key_time = kUnknownNewestKeyTime;
|
||||
const InternalKeyComparator& icmp =
|
||||
column_family_data()->internal_comparator();
|
||||
for (const auto& level_files : inputs_) {
|
||||
for (const auto& file : level_files.files) {
|
||||
if (start != nullptr && icmp.Compare(file->largest, *start) < 0) {
|
||||
continue;
|
||||
}
|
||||
if (end != nullptr && icmp.Compare(file->smallest, *end) > 0) {
|
||||
continue;
|
||||
}
|
||||
newest_key_time = std::max(newest_key_time, file->TryGetNewestKeyTime());
|
||||
}
|
||||
}
|
||||
return newest_key_time;
|
||||
}
|
||||
|
||||
uint64_t Compaction::MinInputFileOldestAncesterTime(
|
||||
const InternalKey* start, const InternalKey* end) const {
|
||||
uint64_t min_oldest_ancester_time = std::numeric_limits<uint64_t>::max();
|
||||
@@ -948,6 +991,7 @@ uint64_t Compaction::MinInputFileEpochNumber() const {
|
||||
|
||||
int Compaction::EvaluatePenultimateLevel(
|
||||
const VersionStorageInfo* vstorage,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const ImmutableOptions& immutable_options, const int start_level,
|
||||
const int output_level) {
|
||||
// TODO: currently per_key_placement feature only support level and universal
|
||||
@@ -979,7 +1023,7 @@ int Compaction::EvaluatePenultimateLevel(
|
||||
}
|
||||
|
||||
bool supports_per_key_placement =
|
||||
immutable_options.preclude_last_level_data_seconds > 0;
|
||||
mutable_cf_options.preclude_last_level_data_seconds > 0;
|
||||
|
||||
// it could be overridden by unittest
|
||||
TEST_SYNC_POINT_CALLBACK("Compaction::SupportsPerKeyPlacement:Enabled",
|
||||
@@ -991,4 +1035,70 @@ int Compaction::EvaluatePenultimateLevel(
|
||||
return penultimate_level;
|
||||
}
|
||||
|
||||
void Compaction::FilterInputsForCompactionIterator() {
|
||||
assert(earliest_snapshot_.has_value());
|
||||
// cfd_ is not populated at Compaction construction time, get it from
|
||||
// VersionStorageInfo instead.
|
||||
assert(input_vstorage_);
|
||||
const auto* ucmp = input_vstorage_->user_comparator();
|
||||
assert(ucmp);
|
||||
// Simply comparing file boundaries when user-defined timestamp is defined
|
||||
// is not as safe because we need to also compare timestamp to know for
|
||||
// sure. Although entries with higher timestamp is also supposed to have
|
||||
// higher sequence number for the same user key (without timestamp).
|
||||
assert(ucmp->timestamp_size() == 0);
|
||||
size_t num_input_levels = inputs_.size();
|
||||
// TODO(yuzhangyu): filtering of older L0 file by new L0 file is not
|
||||
// supported yet.
|
||||
FileMetaData* rangedel_candidate = inputs_[0].level == 0
|
||||
? inputs_[0].files.back()
|
||||
: inputs_[0].files.front();
|
||||
assert(rangedel_candidate);
|
||||
if (!rangedel_candidate->FileIsStandAloneRangeTombstone() ||
|
||||
!DataIsDefinitelyInSnapshot(rangedel_candidate->fd.smallest_seqno,
|
||||
earliest_snapshot_.value(),
|
||||
snapshot_checker_)) {
|
||||
for (size_t level = 0; level < num_input_levels; level++) {
|
||||
DoGenerateLevelFilesBrief(&input_levels_[level], inputs_[level].files,
|
||||
&arena_);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Slice rangedel_start_ukey = rangedel_candidate->smallest.user_key();
|
||||
Slice rangedel_end_ukey = rangedel_candidate->largest.user_key();
|
||||
SequenceNumber rangedel_seqno = rangedel_candidate->fd.smallest_seqno;
|
||||
|
||||
std::vector<std::vector<FileMetaData*>> non_start_level_input_files;
|
||||
non_start_level_input_files.reserve(num_input_levels - 1);
|
||||
non_start_level_input_files_filtered_.reserve(num_input_levels - 1);
|
||||
for (size_t level = 1; level < num_input_levels; level++) {
|
||||
non_start_level_input_files.emplace_back();
|
||||
non_start_level_input_files_filtered_.emplace_back();
|
||||
for (FileMetaData* file : inputs_[level].files) {
|
||||
non_start_level_input_files_filtered_.back().push_back(false);
|
||||
// When range data and point data has the same sequence number, point
|
||||
// data wins. Range deletion end key is exclusive, so check it's bigger
|
||||
// than file right boundary user key.
|
||||
if (rangedel_seqno > file->fd.largest_seqno &&
|
||||
ucmp->CompareWithoutTimestamp(rangedel_start_ukey,
|
||||
file->smallest.user_key()) <= 0 &&
|
||||
ucmp->CompareWithoutTimestamp(rangedel_end_ukey,
|
||||
file->largest.user_key()) > 0) {
|
||||
non_start_level_input_files_filtered_.back().back() = true;
|
||||
filtered_input_levels_[level].push_back(file);
|
||||
} else {
|
||||
non_start_level_input_files.back().push_back(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DoGenerateLevelFilesBrief(&input_levels_[0], inputs_[0].files, &arena_);
|
||||
assert(non_start_level_input_files.size() == num_input_levels - 1);
|
||||
for (size_t level = 1; level < num_input_levels; level++) {
|
||||
DoGenerateLevelFilesBrief(&input_levels_[level],
|
||||
non_start_level_input_files[level - 1], &arena_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "db/snapshot_checker.h"
|
||||
#include "db/version_set.h"
|
||||
#include "memory/arena.h"
|
||||
#include "options/cf_options.h"
|
||||
@@ -90,6 +92,8 @@ class Compaction {
|
||||
CompressionOptions compression_opts,
|
||||
Temperature output_temperature, uint32_t max_subcompactions,
|
||||
std::vector<FileMetaData*> grandparents,
|
||||
std::optional<SequenceNumber> earliest_snapshot,
|
||||
const SnapshotChecker* snapshot_checker,
|
||||
bool manual_compaction = false, const std::string& trim_ts = "",
|
||||
double score = -1, bool deletion_compaction = false,
|
||||
bool l0_files_might_overlap = true,
|
||||
@@ -180,6 +184,16 @@ class Compaction {
|
||||
return &input_levels_[compaction_input_level];
|
||||
}
|
||||
|
||||
// Returns the filtered input files of the specified compaction input level.
|
||||
// For now, only non start level is filtered.
|
||||
const std::vector<FileMetaData*>& filtered_input_levels(
|
||||
size_t compaction_input_level) const {
|
||||
const std::vector<FileMetaData*>& filtered_input_level =
|
||||
filtered_input_levels_[compaction_input_level];
|
||||
assert(compaction_input_level != 0 || filtered_input_level.size() == 0);
|
||||
return filtered_input_level;
|
||||
}
|
||||
|
||||
// Maximum size of files to build during this compaction.
|
||||
uint64_t max_output_file_size() const { return max_output_file_size_; }
|
||||
|
||||
@@ -401,6 +415,12 @@ class Compaction {
|
||||
return blob_garbage_collection_age_cutoff_;
|
||||
}
|
||||
|
||||
// start and end are sub compact range. Null if no boundary.
|
||||
// This is used to calculate the newest_key_time table property after
|
||||
// compaction.
|
||||
uint64_t MaxInputFileNewestKeyTime(const InternalKey* start,
|
||||
const InternalKey* end) const;
|
||||
|
||||
// start and end are sub compact range. Null if no boundary.
|
||||
// This is used to filter out some input files' ancester's time range.
|
||||
uint64_t MinInputFileOldestAncesterTime(const InternalKey* start,
|
||||
@@ -430,10 +450,11 @@ class Compaction {
|
||||
// penultimate level. The safe key range is populated by
|
||||
// `PopulatePenultimateLevelOutputRange()`.
|
||||
// Which could potentially disable all penultimate level output.
|
||||
static int EvaluatePenultimateLevel(const VersionStorageInfo* vstorage,
|
||||
const ImmutableOptions& immutable_options,
|
||||
const int start_level,
|
||||
const int output_level);
|
||||
static int EvaluatePenultimateLevel(
|
||||
const VersionStorageInfo* vstorage,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const ImmutableOptions& immutable_options, const int start_level,
|
||||
const int output_level);
|
||||
|
||||
// mark (or clear) all files that are being compacted
|
||||
void MarkFilesBeingCompacted(bool being_compacted) const;
|
||||
@@ -460,6 +481,13 @@ class Compaction {
|
||||
// `Compaction::WithinPenultimateLevelOutputRange()`.
|
||||
void PopulatePenultimateLevelOutputRange();
|
||||
|
||||
// If oldest snapshot is specified at Compaction construction time, we have
|
||||
// an opportunity to optimize inputs for compaction iterator for this case:
|
||||
// When a standalone range deletion file on the start level is recognized and
|
||||
// can be determined to completely shadow some input files on non-start level.
|
||||
// These files will be filtered out and later not feed to compaction iterator.
|
||||
void FilterInputsForCompactionIterator();
|
||||
|
||||
// Get the atomic file boundaries for all files in the compaction. Necessary
|
||||
// in order to avoid the scenario described in
|
||||
// https://github.com/facebook/rocksdb/pull/4432#discussion_r221072219 and
|
||||
@@ -510,12 +538,30 @@ class Compaction {
|
||||
// Compaction input files organized by level. Constant after construction
|
||||
const std::vector<CompactionInputFiles> inputs_;
|
||||
|
||||
// A copy of inputs_, organized more closely in memory
|
||||
// All files from inputs_ that are not filtered and will be fed to compaction
|
||||
// iterator, organized more closely in memory.
|
||||
autovector<LevelFilesBrief, 2> input_levels_;
|
||||
|
||||
// State used to check for number of overlapping grandparent files
|
||||
// (grandparent == "output_level_ + 1")
|
||||
std::vector<FileMetaData*> grandparents_;
|
||||
|
||||
// The earliest snapshot and snapshot checker at compaction picking time.
|
||||
// These fields are only set for deletion triggered compactions picked in
|
||||
// universal compaction. And when user-defined timestamp is not enabled.
|
||||
// It will be used to possibly filter out some non start level input files.
|
||||
std::optional<SequenceNumber> earliest_snapshot_;
|
||||
const SnapshotChecker* snapshot_checker_;
|
||||
|
||||
// Markers for which non start level input files are filtered out if
|
||||
// applicable. Only applicable if earliest_snapshot_ is provided and input
|
||||
// start level has a standalone range deletion file. Filtered files are
|
||||
// tracked in `filtered_input_levels_`.
|
||||
std::vector<std::vector<bool>> non_start_level_input_files_filtered_;
|
||||
|
||||
// All files from inputs_ that are filtered.
|
||||
std::vector<std::vector<FileMetaData*>> filtered_input_levels_;
|
||||
|
||||
const double score_; // score that was used to pick this compaction.
|
||||
|
||||
// Is this compaction creating a file in the bottom most level?
|
||||
|
||||
@@ -872,8 +872,8 @@ void CompactionIterator::NextFromInput() {
|
||||
if (Valid()) {
|
||||
at_next_ = true;
|
||||
}
|
||||
} else if (last_snapshot == current_user_key_snapshot_ ||
|
||||
(last_snapshot > 0 &&
|
||||
} else if (last_sequence != kMaxSequenceNumber &&
|
||||
(last_snapshot == current_user_key_snapshot_ ||
|
||||
last_snapshot < current_user_key_snapshot_)) {
|
||||
// If the earliest snapshot is which this key is visible in
|
||||
// is the same as the visibility of a previous instance of the
|
||||
|
||||
@@ -540,18 +540,12 @@ class CompactionIterator {
|
||||
|
||||
inline bool CompactionIterator::DefinitelyInSnapshot(SequenceNumber seq,
|
||||
SequenceNumber snapshot) {
|
||||
return ((seq) <= (snapshot) &&
|
||||
(snapshot_checker_ == nullptr ||
|
||||
LIKELY(snapshot_checker_->CheckInSnapshot((seq), (snapshot)) ==
|
||||
SnapshotCheckerResult::kInSnapshot)));
|
||||
return DataIsDefinitelyInSnapshot(seq, snapshot, snapshot_checker_);
|
||||
}
|
||||
|
||||
inline bool CompactionIterator::DefinitelyNotInSnapshot(
|
||||
SequenceNumber seq, SequenceNumber snapshot) {
|
||||
return ((seq) > (snapshot) ||
|
||||
(snapshot_checker_ != nullptr &&
|
||||
UNLIKELY(snapshot_checker_->CheckInSnapshot((seq), (snapshot)) ==
|
||||
SnapshotCheckerResult::kNotInSnapshot)));
|
||||
return DataIsDefinitelyNotInSnapshot(seq, snapshot, snapshot_checker_);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -833,6 +833,14 @@ TEST_P(CompactionIteratorTest, ConvertToPutAtBottom) {
|
||||
true /*bottomost_level*/);
|
||||
}
|
||||
|
||||
TEST_P(CompactionIteratorTest, ZeroSeqOfKeyAndSnapshot) {
|
||||
AddSnapshot(0);
|
||||
const std::vector<std::string> input_keys = {
|
||||
test::KeyStr("a", 0, kTypeValue), test::KeyStr("b", 0, kTypeValue)};
|
||||
const std::vector<std::string> input_values = {"a1", "b1"};
|
||||
RunTest(input_keys, input_values, input_keys, input_values);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(CompactionIteratorTestInstance, CompactionIteratorTest,
|
||||
testing::Values(true, false));
|
||||
|
||||
@@ -1846,6 +1854,22 @@ TEST_P(CompactionIteratorTsGcTest, SingleDeleteAllKeysOlderThanThreshold) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CompactionIteratorTsGcTest, ZeroSeqOfKeyAndSnapshot) {
|
||||
AddSnapshot(0);
|
||||
std::string full_history_ts_low;
|
||||
PutFixed64(&full_history_ts_low, std::numeric_limits<uint64_t>::max());
|
||||
const std::vector<std::string> input_keys = {
|
||||
test::KeyStr(101, "a", 0, kTypeValue),
|
||||
test::KeyStr(102, "b", 0, kTypeValue)};
|
||||
const std::vector<std::string> input_values = {"a1", "b1"};
|
||||
RunTest(input_keys, input_values, input_keys, input_values,
|
||||
/*last_committed_seq=*/kMaxSequenceNumber,
|
||||
/*merge_operator=*/nullptr, /*compaction_filter=*/nullptr,
|
||||
/*bottommost_level=*/false,
|
||||
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
|
||||
/*key_not_exists_beyond_output_level=*/false, &full_history_ts_low);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(CompactionIteratorTsGcTestInstance,
|
||||
CompactionIteratorTsGcTest,
|
||||
testing::Values(true, false));
|
||||
|
||||
@@ -288,8 +288,8 @@ void CompactionJob::Prepare() {
|
||||
// to encode seqno->time to the output files.
|
||||
|
||||
uint64_t preserve_time_duration =
|
||||
std::max(c->immutable_options()->preserve_internal_time_seconds,
|
||||
c->immutable_options()->preclude_last_level_data_seconds);
|
||||
std::max(c->mutable_cf_options()->preserve_internal_time_seconds,
|
||||
c->mutable_cf_options()->preclude_last_level_data_seconds);
|
||||
|
||||
if (preserve_time_duration > 0) {
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
@@ -326,8 +326,8 @@ void CompactionJob::Prepare() {
|
||||
seqno_to_time_mapping_.Enforce(_current_time);
|
||||
seqno_to_time_mapping_.GetCurrentTieringCutoffSeqnos(
|
||||
static_cast<uint64_t>(_current_time),
|
||||
c->immutable_options()->preserve_internal_time_seconds,
|
||||
c->immutable_options()->preclude_last_level_data_seconds,
|
||||
c->mutable_cf_options()->preserve_internal_time_seconds,
|
||||
c->mutable_cf_options()->preclude_last_level_data_seconds,
|
||||
&preserve_time_min_seqno_, &preclude_last_level_min_seqno_);
|
||||
}
|
||||
// For accuracy of the GetProximalSeqnoBeforeTime queries above, we only
|
||||
@@ -469,7 +469,7 @@ void CompactionJob::GenSubcompactionBoundaries() {
|
||||
ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
read_options.rate_limiter_priority = GetRateLimiterPriority();
|
||||
auto* c = compact_->compaction;
|
||||
if (c->immutable_options()->table_factory->Name() ==
|
||||
if (c->mutable_cf_options()->table_factory->Name() ==
|
||||
TableFactory::kPlainTableName()) {
|
||||
return;
|
||||
}
|
||||
@@ -506,9 +506,7 @@ void CompactionJob::GenSubcompactionBoundaries() {
|
||||
FileMetaData* f = flevel->files[i].file_metadata;
|
||||
std::vector<TableReader::Anchor> my_anchors;
|
||||
Status s = cfd->table_cache()->ApproximateKeyAnchors(
|
||||
read_options, icomp, *f,
|
||||
c->mutable_cf_options()->block_protection_bytes_per_key,
|
||||
my_anchors);
|
||||
read_options, icomp, *f, *c->mutable_cf_options(), my_anchors);
|
||||
if (!s.ok() || my_anchors.empty()) {
|
||||
my_anchors.emplace_back(f->largest.user_key(), f->fd.GetFileSize());
|
||||
}
|
||||
@@ -711,8 +709,6 @@ Status CompactionJob::Run() {
|
||||
}
|
||||
}
|
||||
ColumnFamilyData* cfd = compact_->compaction->column_family_data();
|
||||
auto& prefix_extractor =
|
||||
compact_->compaction->mutable_cf_options()->prefix_extractor;
|
||||
std::atomic<size_t> next_file_idx(0);
|
||||
auto verify_table = [&](Status& output_status) {
|
||||
while (true) {
|
||||
@@ -733,7 +729,8 @@ Status CompactionJob::Run() {
|
||||
InternalIterator* iter = cfd->table_cache()->NewIterator(
|
||||
verify_table_read_options, file_options_,
|
||||
cfd->internal_comparator(), files_output[file_idx]->meta,
|
||||
/*range_del_agg=*/nullptr, prefix_extractor,
|
||||
/*range_del_agg=*/nullptr,
|
||||
*compact_->compaction->mutable_cf_options(),
|
||||
/*table_reader_ptr=*/nullptr,
|
||||
cfd->internal_stats()->GetFileReadHist(
|
||||
compact_->compaction->output_level()),
|
||||
@@ -743,9 +740,7 @@ Status CompactionJob::Run() {
|
||||
*compact_->compaction->mutable_cf_options()),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr,
|
||||
/*allow_unprepared_value=*/false,
|
||||
compact_->compaction->mutable_cf_options()
|
||||
->block_protection_bytes_per_key);
|
||||
/*allow_unprepared_value=*/false);
|
||||
auto s = iter->status();
|
||||
|
||||
if (s.ok() && paranoid_file_checks_) {
|
||||
@@ -806,6 +801,12 @@ Status CompactionJob::Run() {
|
||||
}
|
||||
}
|
||||
|
||||
// Before the compaction starts, is_remote_compaction was set to true if
|
||||
// compaction_service is set. We now know whether each sub_compaction was
|
||||
// done remotely or not. Reset is_remote_compaction back to false and allow
|
||||
// AggregateCompactionStats() to set the right value.
|
||||
compaction_job_stats_->is_remote_compaction = false;
|
||||
|
||||
// Finish up all bookkeeping to unify the subcompaction results.
|
||||
compact_->AggregateCompactionStats(compaction_stats_, *compaction_job_stats_);
|
||||
uint64_t num_input_range_del = 0;
|
||||
@@ -910,19 +911,23 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options,
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] compacted to: %s, MB/sec: %.1f rd, %.1f wr, level %d, "
|
||||
"files in(%d, %d) out(%d +%d blob) "
|
||||
"MB in(%.1f, %.1f +%.1f blob) out(%.1f +%.1f blob), "
|
||||
"files in(%d, %d) filtered(%d, %d) out(%d +%d blob) "
|
||||
"MB in(%.1f, %.1f +%.1f blob) filtered(%.1f, %.1f) out(%.1f +%.1f blob), "
|
||||
"read-write-amplify(%.1f) write-amplify(%.1f) %s, records in: %" PRIu64
|
||||
", records dropped: %" PRIu64 " output_compression: %s\n",
|
||||
column_family_name.c_str(), vstorage->LevelSummary(&tmp),
|
||||
bytes_read_per_sec, bytes_written_per_sec,
|
||||
compact_->compaction->output_level(),
|
||||
stats.num_input_files_in_non_output_levels,
|
||||
stats.num_input_files_in_output_level, stats.num_output_files,
|
||||
stats.num_input_files_in_output_level,
|
||||
stats.num_filtered_input_files_in_non_output_levels,
|
||||
stats.num_filtered_input_files_in_output_level, stats.num_output_files,
|
||||
stats.num_output_files_blob, stats.bytes_read_non_output_levels / kMB,
|
||||
stats.bytes_read_output_level / kMB, stats.bytes_read_blob / kMB,
|
||||
stats.bytes_written / kMB, stats.bytes_written_blob / kMB, read_write_amp,
|
||||
write_amp, status.ToString().c_str(), stats.num_input_records,
|
||||
stats.bytes_skipped_non_output_levels / kMB,
|
||||
stats.bytes_skipped_output_level / kMB, stats.bytes_written / kMB,
|
||||
stats.bytes_written_blob / kMB, read_write_amp, write_amp,
|
||||
status.ToString().c_str(), stats.num_input_records,
|
||||
stats.num_dropped_records,
|
||||
CompressionTypeToString(compact_->compaction->output_compression())
|
||||
.c_str());
|
||||
@@ -1084,6 +1089,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
}
|
||||
// fallback to local compaction
|
||||
assert(comp_status == CompactionServiceJobStatus::kUseLocal);
|
||||
sub_compact->compaction_job_stats.is_remote_compaction = false;
|
||||
}
|
||||
|
||||
uint64_t prev_cpu_micros = db_options_.clock->CPUMicros();
|
||||
@@ -1580,6 +1586,8 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
const uint64_t current_entries = outputs.NumEntries();
|
||||
|
||||
s = outputs.Finish(s, seqno_to_time_mapping_);
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CompactionJob::FinishCompactionOutputFile()::AfterFinish", &s);
|
||||
|
||||
if (s.ok()) {
|
||||
// With accurate smallest and largest key, we can get a slightly more
|
||||
@@ -1912,6 +1920,10 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
oldest_ancester_time = current_time;
|
||||
}
|
||||
|
||||
uint64_t newest_key_time = sub_compact->compaction->MaxInputFileNewestKeyTime(
|
||||
sub_compact->start.has_value() ? &tmp_start : nullptr,
|
||||
sub_compact->end.has_value() ? &tmp_end : nullptr);
|
||||
|
||||
// Initialize a SubcompactionState::Output and add it to sub_compact->outputs
|
||||
uint64_t epoch_number = sub_compact->compaction->MinInputFileEpochNumber();
|
||||
{
|
||||
@@ -1961,7 +1973,7 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
cfd->internal_tbl_prop_coll_factories(),
|
||||
sub_compact->compaction->output_compression(),
|
||||
sub_compact->compaction->output_compression_opts(), cfd->GetID(),
|
||||
cfd->GetName(), sub_compact->compaction->output_level(),
|
||||
cfd->GetName(), sub_compact->compaction->output_level(), newest_key_time,
|
||||
bottommost_level_, TableFileCreationReason::kCompaction,
|
||||
0 /* oldest_key_time */, current_time, db_id_, db_session_id_,
|
||||
sub_compact->compaction->max_output_file_size(), file_number,
|
||||
@@ -2004,7 +2016,8 @@ bool CompactionJob::UpdateCompactionStats(uint64_t* num_input_range_del) {
|
||||
for (int input_level = 0;
|
||||
input_level < static_cast<int>(compaction->num_input_levels());
|
||||
++input_level) {
|
||||
size_t num_input_files = compaction->num_input_files(input_level);
|
||||
const LevelFilesBrief* flevel = compaction->input_levels(input_level);
|
||||
size_t num_input_files = flevel->num_files;
|
||||
uint64_t* bytes_read;
|
||||
if (compaction->level(input_level) != compaction->output_level()) {
|
||||
compaction_stats_.stats.num_input_files_in_non_output_levels +=
|
||||
@@ -2016,7 +2029,7 @@ bool CompactionJob::UpdateCompactionStats(uint64_t* num_input_range_del) {
|
||||
bytes_read = &compaction_stats_.stats.bytes_read_output_level;
|
||||
}
|
||||
for (size_t i = 0; i < num_input_files; ++i) {
|
||||
const FileMetaData* file_meta = compaction->input(input_level, i);
|
||||
const FileMetaData* file_meta = flevel->files[i].file_metadata;
|
||||
*bytes_read += file_meta->fd.GetFileSize();
|
||||
uint64_t file_input_entries = file_meta->num_entries;
|
||||
uint64_t file_num_range_del = file_meta->num_range_deletions;
|
||||
@@ -2039,6 +2052,23 @@ bool CompactionJob::UpdateCompactionStats(uint64_t* num_input_range_del) {
|
||||
*num_input_range_del += file_num_range_del;
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<FileMetaData*>& filtered_flevel =
|
||||
compaction->filtered_input_levels(input_level);
|
||||
size_t num_filtered_input_files = filtered_flevel.size();
|
||||
uint64_t* bytes_skipped;
|
||||
if (compaction->level(input_level) != compaction->output_level()) {
|
||||
compaction_stats_.stats.num_filtered_input_files_in_non_output_levels +=
|
||||
static_cast<int>(num_filtered_input_files);
|
||||
bytes_skipped = &compaction_stats_.stats.bytes_skipped_non_output_levels;
|
||||
} else {
|
||||
compaction_stats_.stats.num_filtered_input_files_in_output_level +=
|
||||
static_cast<int>(num_filtered_input_files);
|
||||
bytes_skipped = &compaction_stats_.stats.bytes_skipped_output_level;
|
||||
}
|
||||
for (const FileMetaData* filtered_file_meta : filtered_flevel) {
|
||||
*bytes_skipped += filtered_file_meta->fd.GetFileSize();
|
||||
}
|
||||
}
|
||||
|
||||
assert(compaction_job_stats_);
|
||||
@@ -2063,6 +2093,13 @@ void CompactionJob::UpdateCompactionJobStats(
|
||||
stats.num_input_files_in_output_level;
|
||||
compaction_job_stats_->num_input_files_at_output_level =
|
||||
stats.num_input_files_in_output_level;
|
||||
compaction_job_stats_->num_filtered_input_files =
|
||||
stats.num_filtered_input_files_in_non_output_levels +
|
||||
stats.num_filtered_input_files_in_output_level;
|
||||
compaction_job_stats_->num_filtered_input_files_at_output_level =
|
||||
stats.num_filtered_input_files_in_output_level;
|
||||
compaction_job_stats_->total_skipped_input_bytes =
|
||||
stats.bytes_skipped_non_output_levels + stats.bytes_skipped_output_level;
|
||||
|
||||
// output information
|
||||
compaction_job_stats_->total_output_bytes = stats.bytes_written;
|
||||
|
||||
@@ -209,12 +209,13 @@ class CompactionJob {
|
||||
// Returns true iff compaction_stats_.stats.num_input_records and
|
||||
// num_input_range_del are calculated successfully.
|
||||
bool UpdateCompactionStats(uint64_t* num_input_range_del = nullptr);
|
||||
virtual void UpdateCompactionJobStats(
|
||||
const InternalStats::CompactionStats& stats) const;
|
||||
void LogCompaction();
|
||||
virtual void RecordCompactionIOStats();
|
||||
void CleanupCompaction();
|
||||
|
||||
// Call compaction filter. Then iterate through input and compact the
|
||||
// kv-pairs
|
||||
// Iterate through input and compact the kv-pairs.
|
||||
void ProcessKeyValueCompaction(SubcompactionState* sub_compact);
|
||||
|
||||
CompactionState* compact_;
|
||||
@@ -279,8 +280,7 @@ class CompactionJob {
|
||||
bool* compaction_released);
|
||||
Status OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
CompactionOutputs& outputs);
|
||||
void UpdateCompactionJobStats(
|
||||
const InternalStats::CompactionStats& stats) const;
|
||||
|
||||
void RecordDroppedKeys(const CompactionIterationStats& c_iter_stats,
|
||||
CompactionJobStats* compaction_job_stats = nullptr);
|
||||
|
||||
@@ -385,7 +385,7 @@ struct CompactionServiceInput {
|
||||
// files needed for this compaction, for both input level files and output
|
||||
// level files.
|
||||
std::vector<std::string> input_files;
|
||||
int output_level;
|
||||
int output_level = 0;
|
||||
|
||||
// db_id is used to generate unique id of sst on the remote compactor
|
||||
std::string db_id;
|
||||
@@ -396,6 +396,8 @@ struct CompactionServiceInput {
|
||||
bool has_end = false;
|
||||
std::string end;
|
||||
|
||||
uint64_t options_file_number = 0;
|
||||
|
||||
// serialization interface to read and write the object
|
||||
static Status Read(const std::string& data_str, CompactionServiceInput* obj);
|
||||
Status Write(std::string* output);
|
||||
@@ -413,20 +415,25 @@ struct CompactionServiceOutputFile {
|
||||
SequenceNumber largest_seqno;
|
||||
std::string smallest_internal_key;
|
||||
std::string largest_internal_key;
|
||||
uint64_t oldest_ancester_time;
|
||||
uint64_t file_creation_time;
|
||||
uint64_t epoch_number;
|
||||
uint64_t oldest_ancester_time = kUnknownOldestAncesterTime;
|
||||
uint64_t file_creation_time = kUnknownFileCreationTime;
|
||||
uint64_t epoch_number = kUnknownEpochNumber;
|
||||
std::string file_checksum = kUnknownFileChecksum;
|
||||
std::string file_checksum_func_name = kUnknownFileChecksumFuncName;
|
||||
uint64_t paranoid_hash;
|
||||
bool marked_for_compaction;
|
||||
UniqueId64x2 unique_id;
|
||||
UniqueId64x2 unique_id{};
|
||||
TableProperties table_properties;
|
||||
|
||||
CompactionServiceOutputFile() = default;
|
||||
CompactionServiceOutputFile(
|
||||
const std::string& name, SequenceNumber smallest, SequenceNumber largest,
|
||||
std::string _smallest_internal_key, std::string _largest_internal_key,
|
||||
uint64_t _oldest_ancester_time, uint64_t _file_creation_time,
|
||||
uint64_t _epoch_number, uint64_t _paranoid_hash,
|
||||
bool _marked_for_compaction, UniqueId64x2 _unique_id)
|
||||
uint64_t _epoch_number, const std::string& _file_checksum,
|
||||
const std::string& _file_checksum_func_name, uint64_t _paranoid_hash,
|
||||
bool _marked_for_compaction, UniqueId64x2 _unique_id,
|
||||
const TableProperties& _table_properties)
|
||||
: file_name(name),
|
||||
smallest_seqno(smallest),
|
||||
largest_seqno(largest),
|
||||
@@ -435,9 +442,12 @@ struct CompactionServiceOutputFile {
|
||||
oldest_ancester_time(_oldest_ancester_time),
|
||||
file_creation_time(_file_creation_time),
|
||||
epoch_number(_epoch_number),
|
||||
file_checksum(_file_checksum),
|
||||
file_checksum_func_name(_file_checksum_func_name),
|
||||
paranoid_hash(_paranoid_hash),
|
||||
marked_for_compaction(_marked_for_compaction),
|
||||
unique_id(std::move(_unique_id)) {}
|
||||
unique_id(std::move(_unique_id)),
|
||||
table_properties(_table_properties) {}
|
||||
};
|
||||
|
||||
// CompactionServiceResult contains the compaction result from a different db
|
||||
@@ -446,14 +456,11 @@ struct CompactionServiceOutputFile {
|
||||
struct CompactionServiceResult {
|
||||
Status status;
|
||||
std::vector<CompactionServiceOutputFile> output_files;
|
||||
int output_level;
|
||||
int output_level = 0;
|
||||
|
||||
// location of the output files
|
||||
std::string output_path;
|
||||
|
||||
// some statistics about the compaction
|
||||
uint64_t num_output_records = 0;
|
||||
uint64_t total_bytes = 0;
|
||||
uint64_t bytes_read = 0;
|
||||
uint64_t bytes_written = 0;
|
||||
CompactionJobStats stats;
|
||||
@@ -499,6 +506,9 @@ class CompactionServiceCompactionJob : private CompactionJob {
|
||||
protected:
|
||||
void RecordCompactionIOStats() override;
|
||||
|
||||
void UpdateCompactionJobStats(
|
||||
const InternalStats::CompactionStats& stats) const override;
|
||||
|
||||
private:
|
||||
// Get table file name in output_path
|
||||
std::string GetTableFileName(uint64_t file_number) override;
|
||||
|
||||
@@ -50,7 +50,8 @@ void VerifyInitializationOfCompactionJobStats(
|
||||
ASSERT_EQ(compaction_job_stats.num_output_records, 0U);
|
||||
ASSERT_EQ(compaction_job_stats.num_output_files, 0U);
|
||||
|
||||
ASSERT_EQ(compaction_job_stats.is_manual_compaction, true);
|
||||
ASSERT_TRUE(compaction_job_stats.is_manual_compaction);
|
||||
ASSERT_FALSE(compaction_job_stats.is_remote_compaction);
|
||||
|
||||
ASSERT_EQ(compaction_job_stats.total_input_bytes, 0U);
|
||||
ASSERT_EQ(compaction_job_stats.total_output_bytes, 0U);
|
||||
@@ -249,6 +250,7 @@ class CompactionJobTestBase : public testing::Test {
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
mutable_cf_options_.table_factory = cf_options_.table_factory;
|
||||
}
|
||||
|
||||
std::string GenerateFileName(uint64_t file_number) {
|
||||
@@ -299,13 +301,13 @@ class CompactionJobTestBase : public testing::Test {
|
||||
const WriteOptions write_options;
|
||||
std::unique_ptr<TableBuilder> table_builder(
|
||||
cf_options_.table_factory->NewTableBuilder(
|
||||
TableBuilderOptions(*cfd_->ioptions(), mutable_cf_options_,
|
||||
read_options, write_options,
|
||||
cfd_->internal_comparator(),
|
||||
cfd_->internal_tbl_prop_coll_factories(),
|
||||
CompressionType::kNoCompression,
|
||||
CompressionOptions(), 0 /* column_family_id */,
|
||||
kDefaultColumnFamilyName, -1 /* level */),
|
||||
TableBuilderOptions(
|
||||
*cfd_->ioptions(), mutable_cf_options_, read_options,
|
||||
write_options, cfd_->internal_comparator(),
|
||||
cfd_->internal_tbl_prop_coll_factories(),
|
||||
CompressionType::kNoCompression, CompressionOptions(),
|
||||
0 /* column_family_id */, kDefaultColumnFamilyName,
|
||||
-1 /* level */, kUnknownNewestKeyTime),
|
||||
file_writer.get()));
|
||||
// Build table.
|
||||
for (const auto& kv : contents) {
|
||||
@@ -650,7 +652,8 @@ class CompactionJobTestBase : public testing::Test {
|
||||
mutable_cf_options_.target_file_size_base,
|
||||
mutable_cf_options_.max_compaction_bytes, 0, kNoCompression,
|
||||
cfd->GetLatestMutableCFOptions()->compression_opts,
|
||||
Temperature::kUnknown, max_subcompactions, grandparents, true);
|
||||
Temperature::kUnknown, max_subcompactions, grandparents,
|
||||
/*earliest_snapshot*/ std::nullopt, /*snapshot_checker*/ nullptr, true);
|
||||
compaction.FinalizeInputInfo(cfd->current());
|
||||
|
||||
assert(db_options_.info_log);
|
||||
@@ -1655,20 +1658,40 @@ TEST_F(CompactionJobTest, ResultSerialization) {
|
||||
};
|
||||
result.status =
|
||||
status_list.at(rnd.Uniform(static_cast<int>(status_list.size())));
|
||||
|
||||
std::string file_checksum = rnd.RandomBinaryString(rnd.Uniform(kStrMaxLen));
|
||||
std::string file_checksum_func_name = "MyAwesomeChecksumGenerator";
|
||||
while (!rnd.OneIn(10)) {
|
||||
TableProperties tp;
|
||||
tp.user_collected_properties.emplace(
|
||||
"UCP_Key1", rnd.RandomString(rnd.Uniform(kStrMaxLen)));
|
||||
tp.user_collected_properties.emplace(
|
||||
"UCP_Key2", rnd.RandomString(rnd.Uniform(kStrMaxLen)));
|
||||
tp.readable_properties.emplace("RP_Key1",
|
||||
rnd.RandomString(rnd.Uniform(kStrMaxLen)));
|
||||
tp.readable_properties.emplace("RP_K2y2",
|
||||
rnd.RandomString(rnd.Uniform(kStrMaxLen)));
|
||||
|
||||
UniqueId64x2 id{rnd64.Uniform(UINT64_MAX), rnd64.Uniform(UINT64_MAX)};
|
||||
result.output_files.emplace_back(
|
||||
rnd.RandomString(rnd.Uniform(kStrMaxLen)), rnd64.Uniform(UINT64_MAX),
|
||||
rnd64.Uniform(UINT64_MAX),
|
||||
rnd.RandomBinaryString(rnd.Uniform(kStrMaxLen)),
|
||||
rnd.RandomBinaryString(rnd.Uniform(kStrMaxLen)),
|
||||
rnd64.Uniform(UINT64_MAX), rnd64.Uniform(UINT64_MAX),
|
||||
rnd64.Uniform(UINT64_MAX), rnd64.Uniform(UINT64_MAX), rnd.OneIn(2), id);
|
||||
rnd.RandomString(rnd.Uniform(kStrMaxLen)) /* file_name */,
|
||||
rnd64.Uniform(UINT64_MAX) /* smallest_seqno */,
|
||||
rnd64.Uniform(UINT64_MAX) /* largest_seqno */,
|
||||
rnd.RandomBinaryString(
|
||||
rnd.Uniform(kStrMaxLen)) /* smallest_internal_key */,
|
||||
rnd.RandomBinaryString(
|
||||
rnd.Uniform(kStrMaxLen)) /* largest_internal_key */,
|
||||
rnd64.Uniform(UINT64_MAX) /* oldest_ancester_time */,
|
||||
rnd64.Uniform(UINT64_MAX) /* file_creation_time */,
|
||||
rnd64.Uniform(UINT64_MAX) /* epoch_number */,
|
||||
file_checksum /* file_checksum */,
|
||||
file_checksum_func_name /* file_checksum_func_name */,
|
||||
rnd64.Uniform(UINT64_MAX) /* paranoid_hash */,
|
||||
rnd.OneIn(2) /* marked_for_compaction */, id /* unique_id */, tp);
|
||||
}
|
||||
result.output_level = rnd.Uniform(10);
|
||||
result.output_path = rnd.RandomString(rnd.Uniform(kStrMaxLen));
|
||||
result.num_output_records = rnd64.Uniform(UINT64_MAX);
|
||||
result.total_bytes = rnd64.Uniform(UINT64_MAX);
|
||||
result.stats.num_output_records = rnd64.Uniform(UINT64_MAX);
|
||||
result.bytes_read = 123;
|
||||
result.bytes_written = rnd64.Uniform(UINT64_MAX);
|
||||
result.stats.elapsed_micros = rnd64.Uniform(UINT64_MAX);
|
||||
@@ -1685,6 +1708,21 @@ TEST_F(CompactionJobTest, ResultSerialization) {
|
||||
ASSERT_OK(CompactionServiceResult::Read(output, &deserialized1));
|
||||
ASSERT_TRUE(deserialized1.TEST_Equals(&result));
|
||||
|
||||
for (size_t i = 0; i < result.output_files.size(); i++) {
|
||||
for (const auto& prop :
|
||||
result.output_files[i].table_properties.user_collected_properties) {
|
||||
ASSERT_EQ(deserialized1.output_files[i]
|
||||
.table_properties.user_collected_properties[prop.first],
|
||||
prop.second);
|
||||
}
|
||||
for (const auto& prop :
|
||||
result.output_files[i].table_properties.readable_properties) {
|
||||
ASSERT_EQ(deserialized1.output_files[i]
|
||||
.table_properties.readable_properties[prop.first],
|
||||
prop.second);
|
||||
}
|
||||
}
|
||||
|
||||
// Test mismatch
|
||||
deserialized1.stats.num_input_files += 10;
|
||||
std::string mismatch;
|
||||
@@ -1699,6 +1737,10 @@ TEST_F(CompactionJobTest, ResultSerialization) {
|
||||
ASSERT_FALSE(deserialized_tmp.TEST_Equals(&result, &mismatch));
|
||||
ASSERT_EQ(mismatch, "output_files.unique_id");
|
||||
deserialized_tmp.status.PermitUncheckedError();
|
||||
|
||||
ASSERT_EQ(deserialized_tmp.output_files[0].file_checksum, file_checksum);
|
||||
ASSERT_EQ(deserialized_tmp.output_files[0].file_checksum_func_name,
|
||||
file_checksum_func_name);
|
||||
}
|
||||
|
||||
// Test unknown field
|
||||
|
||||
@@ -62,8 +62,9 @@ class CompactionOutputs {
|
||||
}
|
||||
|
||||
// TODO: Remove it when remote compaction support tiered compaction
|
||||
void SetTotalBytes(uint64_t bytes) { stats_.bytes_written += bytes; }
|
||||
void AddBytesWritten(uint64_t bytes) { stats_.bytes_written += bytes; }
|
||||
void SetNumOutputRecords(uint64_t num) { stats_.num_output_records = num; }
|
||||
void SetNumOutputFiles(uint64_t num) { stats_.num_output_files = num; }
|
||||
|
||||
// TODO: Move the BlobDB builder into CompactionOutputs
|
||||
const std::vector<BlobFileAddition>& GetBlobFileAdditions() const {
|
||||
@@ -107,6 +108,12 @@ class CompactionOutputs {
|
||||
Status Finish(const Status& intput_status,
|
||||
const SeqnoToTimeMapping& seqno_to_time_mapping);
|
||||
|
||||
// Update output table properties from already populated TableProperties.
|
||||
// Used for remote compaction
|
||||
void UpdateTableProperties(const TableProperties& table_properties) {
|
||||
current_output().table_properties =
|
||||
std::make_shared<TableProperties>(table_properties);
|
||||
}
|
||||
// Update output table properties from table builder
|
||||
void UpdateTableProperties() {
|
||||
current_output().table_properties =
|
||||
|
||||
@@ -351,11 +351,11 @@ Compaction* CompactionPicker::CompactFiles(
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(output_level == 0 ||
|
||||
!FilesRangeOverlapWithCompaction(
|
||||
input_files, output_level,
|
||||
Compaction::EvaluatePenultimateLevel(vstorage, ioptions_,
|
||||
start_level, output_level)));
|
||||
assert(output_level == 0 || !FilesRangeOverlapWithCompaction(
|
||||
input_files, output_level,
|
||||
Compaction::EvaluatePenultimateLevel(
|
||||
vstorage, mutable_cf_options, ioptions_,
|
||||
start_level, output_level)));
|
||||
#endif /* !NDEBUG */
|
||||
|
||||
CompressionType compression_type;
|
||||
@@ -380,7 +380,8 @@ Compaction* CompactionPicker::CompactFiles(
|
||||
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
|
||||
mutable_cf_options.default_write_temperature,
|
||||
compact_options.max_subcompactions,
|
||||
/* grandparents */ {}, true);
|
||||
/* grandparents */ {}, /* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr, true);
|
||||
RegisterCompaction(c);
|
||||
return c;
|
||||
}
|
||||
@@ -658,8 +659,9 @@ Compaction* CompactionPicker::CompactRange(
|
||||
// overlaping outputs in the same level.
|
||||
if (FilesRangeOverlapWithCompaction(
|
||||
inputs, output_level,
|
||||
Compaction::EvaluatePenultimateLevel(vstorage, ioptions_,
|
||||
start_level, output_level))) {
|
||||
Compaction::EvaluatePenultimateLevel(vstorage, mutable_cf_options,
|
||||
ioptions_, start_level,
|
||||
output_level))) {
|
||||
// This compaction output could potentially conflict with the output
|
||||
// of a currently running compaction, we cannot run it.
|
||||
*manual_conflict = true;
|
||||
@@ -677,7 +679,9 @@ Compaction* CompactionPicker::CompactRange(
|
||||
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
|
||||
mutable_cf_options.default_write_temperature,
|
||||
compact_range_options.max_subcompactions,
|
||||
/* grandparents */ {}, /* is manual */ true, trim_ts, /* score */ -1,
|
||||
/* grandparents */ {}, /* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr,
|
||||
/* is manual */ true, trim_ts, /* score */ -1,
|
||||
/* deletion_compaction */ false, /* l0_files_might_overlap */ true,
|
||||
CompactionReason::kUnknown,
|
||||
compact_range_options.blob_garbage_collection_policy,
|
||||
@@ -843,7 +847,8 @@ Compaction* CompactionPicker::CompactRange(
|
||||
// overlaping outputs in the same level.
|
||||
if (FilesRangeOverlapWithCompaction(
|
||||
compaction_inputs, output_level,
|
||||
Compaction::EvaluatePenultimateLevel(vstorage, ioptions_, input_level,
|
||||
Compaction::EvaluatePenultimateLevel(vstorage, mutable_cf_options,
|
||||
ioptions_, input_level,
|
||||
output_level))) {
|
||||
// This compaction output could potentially conflict with the output
|
||||
// of a currently running compaction, we cannot run it.
|
||||
@@ -866,6 +871,7 @@ Compaction* CompactionPicker::CompactRange(
|
||||
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
|
||||
mutable_cf_options.default_write_temperature,
|
||||
compact_range_options.max_subcompactions, std::move(grandparents),
|
||||
/* earliest_snapshot */ std::nullopt, /* snapshot_checker */ nullptr,
|
||||
/* is manual */ true, trim_ts, /* score */ -1,
|
||||
/* deletion_compaction */ false, /* l0_files_might_overlap */ true,
|
||||
CompactionReason::kUnknown,
|
||||
@@ -1045,10 +1051,12 @@ Status CompactionPicker::SanitizeCompactionInputFilesForAllLevels(
|
||||
}
|
||||
|
||||
Status CompactionPicker::SanitizeAndConvertCompactionInputFiles(
|
||||
std::unordered_set<uint64_t>* input_files,
|
||||
const ColumnFamilyMetaData& cf_meta, const int output_level,
|
||||
const VersionStorageInfo* vstorage,
|
||||
std::unordered_set<uint64_t>* input_files, const int output_level,
|
||||
Version* version,
|
||||
std::vector<CompactionInputFiles>* converted_input_files) const {
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
version->GetColumnFamilyMetaData(&cf_meta);
|
||||
|
||||
assert(static_cast<int>(cf_meta.levels.size()) - 1 ==
|
||||
cf_meta.levels[cf_meta.levels.size() - 1].level);
|
||||
assert(converted_input_files);
|
||||
@@ -1119,7 +1127,8 @@ Status CompactionPicker::SanitizeAndConvertCompactionInputFiles(
|
||||
}
|
||||
|
||||
s = GetCompactionInputsFromFileNumbers(converted_input_files, input_files,
|
||||
vstorage, CompactionOptions());
|
||||
version->storage_info(),
|
||||
CompactionOptions());
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -1128,8 +1137,8 @@ Status CompactionPicker::SanitizeAndConvertCompactionInputFiles(
|
||||
FilesRangeOverlapWithCompaction(
|
||||
*converted_input_files, output_level,
|
||||
Compaction::EvaluatePenultimateLevel(
|
||||
vstorage, ioptions_, (*converted_input_files)[0].level,
|
||||
output_level))) {
|
||||
version->storage_info(), version->GetMutableCFOptions(),
|
||||
ioptions_, (*converted_input_files)[0].level, output_level))) {
|
||||
return Status::Aborted(
|
||||
"A running compaction is writing to the same output level(s) in an "
|
||||
"overlapping key range");
|
||||
@@ -1171,7 +1180,8 @@ void CompactionPicker::UnregisterCompaction(Compaction* c) {
|
||||
|
||||
void CompactionPicker::PickFilesMarkedForCompaction(
|
||||
const std::string& cf_name, VersionStorageInfo* vstorage, int* start_level,
|
||||
int* output_level, CompactionInputFiles* start_level_inputs) {
|
||||
int* output_level, CompactionInputFiles* start_level_inputs,
|
||||
std::function<bool(const FileMetaData*)> skip_marked_file) {
|
||||
if (vstorage->FilesMarkedForCompaction().empty()) {
|
||||
return;
|
||||
}
|
||||
@@ -1181,6 +1191,9 @@ void CompactionPicker::PickFilesMarkedForCompaction(
|
||||
// If this assert() fails that means that some function marked some
|
||||
// files as being_compacted, but didn't call ComputeCompactionScore()
|
||||
assert(!level_file.second->being_compacted);
|
||||
if (skip_marked_file(level_file.second)) {
|
||||
return false;
|
||||
}
|
||||
*start_level = level_file.first;
|
||||
*output_level =
|
||||
(*start_level == 0) ? vstorage->base_level() : *start_level + 1;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "db/compaction/compaction.h"
|
||||
#include "db/snapshot_checker.h"
|
||||
#include "db/version_set.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "rocksdb/env.h"
|
||||
@@ -55,17 +56,17 @@ class CompactionPicker {
|
||||
// Returns nullptr if there is no compaction to be done.
|
||||
// Otherwise returns a pointer to a heap-allocated object that
|
||||
// describes the compaction. Caller should delete the result.
|
||||
virtual Compaction* PickCompaction(const std::string& cf_name,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) = 0;
|
||||
// Currently, only universal compaction will query existing snapshots and
|
||||
// pass it to aid compaction picking. And it's only passed when user-defined
|
||||
// timestamps is not enabled. The other compaction styles do not pass or use
|
||||
// `existing_snapshots` or `snapshot_checker`.
|
||||
virtual Compaction* PickCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) = 0;
|
||||
|
||||
// Return a compaction object for compacting the range [begin,end] in
|
||||
// the specified level. Returns nullptr if there is nothing in that
|
||||
// level that overlaps the specified range. Caller should delete
|
||||
// the result.
|
||||
//
|
||||
// The returned Compaction might not include the whole requested range.
|
||||
// In that case, compaction_end will be set to the next key that needs
|
||||
// compacting. In case the compaction will compact the whole range,
|
||||
@@ -96,9 +97,8 @@ class CompactionPicker {
|
||||
// non-ok status with specific reason.
|
||||
//
|
||||
Status SanitizeAndConvertCompactionInputFiles(
|
||||
std::unordered_set<uint64_t>* input_files,
|
||||
const ColumnFamilyMetaData& cf_meta, const int output_level,
|
||||
const VersionStorageInfo* vstorage,
|
||||
std::unordered_set<uint64_t>* input_files, const int output_level,
|
||||
Version* version,
|
||||
std::vector<CompactionInputFiles>* converted_input_files) const;
|
||||
|
||||
// Free up the files that participated in a compaction
|
||||
@@ -203,10 +203,11 @@ class CompactionPicker {
|
||||
const CompactionInputFiles& output_level_inputs,
|
||||
std::vector<FileMetaData*>* grandparents);
|
||||
|
||||
void PickFilesMarkedForCompaction(const std::string& cf_name,
|
||||
VersionStorageInfo* vstorage,
|
||||
int* start_level, int* output_level,
|
||||
CompactionInputFiles* start_level_inputs);
|
||||
void PickFilesMarkedForCompaction(
|
||||
const std::string& cf_name, VersionStorageInfo* vstorage,
|
||||
int* start_level, int* output_level,
|
||||
CompactionInputFiles* start_level_inputs,
|
||||
std::function<bool(const FileMetaData*)> skip_marked_file);
|
||||
|
||||
bool GetOverlappingL0Files(VersionStorageInfo* vstorage,
|
||||
CompactionInputFiles* start_level_inputs,
|
||||
@@ -257,11 +258,13 @@ class NullCompactionPicker : public CompactionPicker {
|
||||
virtual ~NullCompactionPicker() {}
|
||||
|
||||
// Always return "nullptr"
|
||||
Compaction* PickCompaction(const std::string& /*cf_name*/,
|
||||
const MutableCFOptions& /*mutable_cf_options*/,
|
||||
const MutableDBOptions& /*mutable_db_options*/,
|
||||
VersionStorageInfo* /*vstorage*/,
|
||||
LogBuffer* /* log_buffer */) override {
|
||||
Compaction* PickCompaction(
|
||||
const std::string& /*cf_name*/,
|
||||
const MutableCFOptions& /*mutable_cf_options*/,
|
||||
const MutableDBOptions& /*mutable_db_options*/,
|
||||
const std::vector<SequenceNumber>& /*existing_snapshots*/,
|
||||
const SnapshotChecker* /*snapshot_checker*/,
|
||||
VersionStorageInfo* /*vstorage*/, LogBuffer* /* log_buffer */) override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -79,10 +79,14 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
|
||||
FileMetaData* f = *ritr;
|
||||
assert(f);
|
||||
if (f->fd.table_reader && f->fd.table_reader->GetTableProperties()) {
|
||||
uint64_t newest_key_time = f->TryGetNewestKeyTime();
|
||||
uint64_t creation_time =
|
||||
f->fd.table_reader->GetTableProperties()->creation_time;
|
||||
if (creation_time == 0 ||
|
||||
creation_time >= (current_time - mutable_cf_options.ttl)) {
|
||||
uint64_t est_newest_key_time = newest_key_time == kUnknownNewestKeyTime
|
||||
? creation_time
|
||||
: newest_key_time;
|
||||
if (est_newest_key_time == kUnknownNewestKeyTime ||
|
||||
est_newest_key_time >= (current_time - mutable_cf_options.ttl)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -102,15 +106,19 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
|
||||
}
|
||||
|
||||
for (const auto& f : inputs[0].files) {
|
||||
uint64_t creation_time = 0;
|
||||
assert(f);
|
||||
uint64_t newest_key_time = f->TryGetNewestKeyTime();
|
||||
uint64_t creation_time = 0;
|
||||
if (f->fd.table_reader && f->fd.table_reader->GetTableProperties()) {
|
||||
creation_time = f->fd.table_reader->GetTableProperties()->creation_time;
|
||||
}
|
||||
uint64_t est_newest_key_time = newest_key_time == kUnknownNewestKeyTime
|
||||
? creation_time
|
||||
: newest_key_time;
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] FIFO compaction: picking file %" PRIu64
|
||||
" with creation time %" PRIu64 " for deletion",
|
||||
cf_name.c_str(), f->fd.GetNumber(), creation_time);
|
||||
" with estimated newest key time %" PRIu64 " for deletion",
|
||||
cf_name.c_str(), f->fd.GetNumber(), est_newest_key_time);
|
||||
}
|
||||
|
||||
Compaction* c = new Compaction(
|
||||
@@ -118,7 +126,9 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
|
||||
std::move(inputs), 0, 0, 0, 0, kNoCompression,
|
||||
mutable_cf_options.compression_opts,
|
||||
mutable_cf_options.default_write_temperature,
|
||||
/* max_subcompactions */ 0, {}, /* is manual */ false,
|
||||
/* max_subcompactions */ 0, {}, /* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr,
|
||||
/* is manual */ false,
|
||||
/* trim_ts */ "", vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ true, /* l0_files_might_overlap */ true,
|
||||
CompactionReason::kFIFOTtl);
|
||||
@@ -188,7 +198,9 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
0 /* output path ID */, mutable_cf_options.compression,
|
||||
mutable_cf_options.compression_opts,
|
||||
mutable_cf_options.default_write_temperature,
|
||||
0 /* max_subcompactions */, {}, /* is manual */ false,
|
||||
0 /* max_subcompactions */, {},
|
||||
/* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr, /* is manual */ false,
|
||||
/* trim_ts */ "", vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ false,
|
||||
/* l0_files_might_overlap */ true,
|
||||
@@ -284,7 +296,9 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
/* output_path_id */ 0, kNoCompression,
|
||||
mutable_cf_options.compression_opts,
|
||||
mutable_cf_options.default_write_temperature,
|
||||
/* max_subcompactions */ 0, {}, /* is manual */ false,
|
||||
/* max_subcompactions */ 0, {}, /* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr,
|
||||
/* is manual */ false,
|
||||
/* trim_ts */ "", vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ true,
|
||||
/* l0_files_might_overlap */ true, CompactionReason::kFIFOMaxSize);
|
||||
@@ -344,38 +358,29 @@ Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
|
||||
Temperature compaction_target_temp = Temperature::kLastTemperature;
|
||||
if (current_time > min_age) {
|
||||
uint64_t create_time_threshold = current_time - min_age;
|
||||
// We will ideally identify a file qualifying for temperature change by
|
||||
// knowing the timestamp for the youngest entry in the file. However, right
|
||||
// now we don't have the information. We infer it by looking at timestamp of
|
||||
// the previous file's (which is just younger) oldest entry's timestamp.
|
||||
// avoid index underflow
|
||||
assert(level_files.size() >= 1);
|
||||
for (size_t index = level_files.size() - 1; index >= 1; --index) {
|
||||
for (size_t index = level_files.size(); index >= 1; --index) {
|
||||
// Try to add cur_file to compaction inputs.
|
||||
FileMetaData* cur_file = level_files[index];
|
||||
// prev_file is just younger than cur_file
|
||||
FileMetaData* prev_file = level_files[index - 1];
|
||||
FileMetaData* cur_file = level_files[index - 1];
|
||||
FileMetaData* prev_file = index < 2 ? nullptr : level_files[index - 2];
|
||||
if (cur_file->being_compacted) {
|
||||
// Should not happen since we check for
|
||||
// `level0_compactions_in_progress_` above. Here we simply just don't
|
||||
// schedule anything.
|
||||
return nullptr;
|
||||
}
|
||||
uint64_t oldest_ancestor_time = prev_file->TryGetOldestAncesterTime();
|
||||
if (oldest_ancestor_time == kUnknownOldestAncesterTime) {
|
||||
// Older files might not have enough information. It is possible to
|
||||
// handle these files by looking at newer files, but maintaining the
|
||||
// logic isn't worth it.
|
||||
break;
|
||||
uint64_t est_newest_key_time = cur_file->TryGetNewestKeyTime(prev_file);
|
||||
// Newer file could have newest_key_time populated
|
||||
if (est_newest_key_time == kUnknownNewestKeyTime) {
|
||||
continue;
|
||||
}
|
||||
if (oldest_ancestor_time > create_time_threshold) {
|
||||
// cur_file is too fresh
|
||||
if (est_newest_key_time > create_time_threshold) {
|
||||
break;
|
||||
}
|
||||
Temperature cur_target_temp = ages[0].temperature;
|
||||
for (size_t i = 1; i < ages.size(); ++i) {
|
||||
if (current_time >= ages[i].age &&
|
||||
oldest_ancestor_time <= current_time - ages[i].age) {
|
||||
est_newest_key_time <= current_time - ages[i].age) {
|
||||
cur_target_temp = ages[i].temperature;
|
||||
}
|
||||
}
|
||||
@@ -390,8 +395,8 @@ Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer,
|
||||
"[%s] FIFO compaction: picking file %" PRIu64
|
||||
" with next file's oldest time %" PRIu64 " for temperature %s.",
|
||||
cf_name.c_str(), cur_file->fd.GetNumber(), oldest_ancestor_time,
|
||||
" with estimated newest key time %" PRIu64 " for temperature %s.",
|
||||
cf_name.c_str(), cur_file->fd.GetNumber(), est_newest_key_time,
|
||||
temperature_to_string[cur_target_temp].c_str());
|
||||
break;
|
||||
}
|
||||
@@ -410,8 +415,9 @@ Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
|
||||
0 /* max compaction bytes, not applicable */, 0 /* output path ID */,
|
||||
mutable_cf_options.compression, mutable_cf_options.compression_opts,
|
||||
compaction_target_temp,
|
||||
/* max_subcompactions */ 0, {}, /* is manual */ false, /* trim_ts */ "",
|
||||
vstorage->CompactionScore(0),
|
||||
/* max_subcompactions */ 0, {}, /* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr,
|
||||
/* is manual */ false, /* trim_ts */ "", vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ false, /* l0_files_might_overlap */ true,
|
||||
CompactionReason::kChangeTemperature);
|
||||
return c;
|
||||
@@ -419,7 +425,9 @@ Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
|
||||
|
||||
Compaction* FIFOCompactionPicker::PickCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& /* existing_snapshots */,
|
||||
const SnapshotChecker* /* snapshot_checker */, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) {
|
||||
Compaction* c = nullptr;
|
||||
if (mutable_cf_options.ttl > 0) {
|
||||
@@ -454,8 +462,10 @@ Compaction* FIFOCompactionPicker::CompactRange(
|
||||
assert(output_level == 0);
|
||||
*compaction_end = nullptr;
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, ioptions_.logger);
|
||||
Compaction* c = PickCompaction(cf_name, mutable_cf_options,
|
||||
mutable_db_options, vstorage, &log_buffer);
|
||||
Compaction* c =
|
||||
PickCompaction(cf_name, mutable_cf_options, mutable_db_options,
|
||||
/*existing_snapshots*/ {}, /*snapshot_checker*/ nullptr,
|
||||
vstorage, &log_buffer);
|
||||
log_buffer.FlushBufferToLog();
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -18,11 +18,12 @@ class FIFOCompactionPicker : public CompactionPicker {
|
||||
const InternalKeyComparator* icmp)
|
||||
: CompactionPicker(ioptions, icmp) {}
|
||||
|
||||
Compaction* PickCompaction(const std::string& cf_name,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
VersionStorageInfo* version,
|
||||
LogBuffer* log_buffer) override;
|
||||
Compaction* PickCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& /* existing_snapshots */,
|
||||
const SnapshotChecker* /* snapshot_checker */,
|
||||
VersionStorageInfo* version, LogBuffer* log_buffer) override;
|
||||
|
||||
Compaction* CompactRange(const std::string& cf_name,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
|
||||
@@ -262,7 +262,10 @@ void LevelCompactionBuilder::SetupInitialFiles() {
|
||||
parent_index_ = base_index_ = -1;
|
||||
|
||||
compaction_picker_->PickFilesMarkedForCompaction(
|
||||
cf_name_, vstorage_, &start_level_, &output_level_, &start_level_inputs_);
|
||||
cf_name_, vstorage_, &start_level_, &output_level_, &start_level_inputs_,
|
||||
/*skip_marked_file*/ [](const FileMetaData* /* file */) {
|
||||
return false;
|
||||
});
|
||||
if (!start_level_inputs_.empty()) {
|
||||
compaction_reason_ = CompactionReason::kFilesMarkedForCompaction;
|
||||
return;
|
||||
@@ -411,8 +414,9 @@ void LevelCompactionBuilder::SetupOtherFilesWithRoundRobinExpansion() {
|
||||
&tmp_start_level_inputs) ||
|
||||
compaction_picker_->FilesRangeOverlapWithCompaction(
|
||||
{tmp_start_level_inputs}, output_level_,
|
||||
Compaction::EvaluatePenultimateLevel(
|
||||
vstorage_, ioptions_, start_level_, output_level_))) {
|
||||
Compaction::EvaluatePenultimateLevel(vstorage_, mutable_cf_options_,
|
||||
ioptions_, start_level_,
|
||||
output_level_))) {
|
||||
// Constraint 1a
|
||||
tmp_start_level_inputs.clear();
|
||||
return;
|
||||
@@ -486,8 +490,9 @@ bool LevelCompactionBuilder::SetupOtherInputsIfNeeded() {
|
||||
// We need to disallow this from happening.
|
||||
if (compaction_picker_->FilesRangeOverlapWithCompaction(
|
||||
compaction_inputs_, output_level_,
|
||||
Compaction::EvaluatePenultimateLevel(
|
||||
vstorage_, ioptions_, start_level_, output_level_))) {
|
||||
Compaction::EvaluatePenultimateLevel(vstorage_, mutable_cf_options_,
|
||||
ioptions_, start_level_,
|
||||
output_level_))) {
|
||||
// This compaction output could potentially conflict with the output
|
||||
// of a currently running compaction, we cannot run it.
|
||||
return false;
|
||||
@@ -554,7 +559,9 @@ Compaction* LevelCompactionBuilder::GetCompaction() {
|
||||
vstorage_->base_level()),
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level_),
|
||||
mutable_cf_options_.default_write_temperature,
|
||||
/* max_subcompactions */ 0, std::move(grandparents_), is_manual_,
|
||||
/* max_subcompactions */ 0, std::move(grandparents_),
|
||||
/* earliest_snapshot */ std::nullopt, /* snapshot_checker */ nullptr,
|
||||
is_manual_,
|
||||
/* trim_ts */ "", start_level_score_, false /* deletion_compaction */,
|
||||
l0_files_might_overlap, compaction_reason_);
|
||||
|
||||
@@ -839,8 +846,9 @@ bool LevelCompactionBuilder::PickFileToCompact() {
|
||||
&start_level_inputs_) ||
|
||||
compaction_picker_->FilesRangeOverlapWithCompaction(
|
||||
{start_level_inputs_}, output_level_,
|
||||
Compaction::EvaluatePenultimateLevel(
|
||||
vstorage_, ioptions_, start_level_, output_level_))) {
|
||||
Compaction::EvaluatePenultimateLevel(vstorage_, mutable_cf_options_,
|
||||
ioptions_, start_level_,
|
||||
output_level_))) {
|
||||
// A locked (pending compaction) input-level file was pulled in due to
|
||||
// user-key overlap.
|
||||
start_level_inputs_.clear();
|
||||
@@ -967,7 +975,9 @@ bool LevelCompactionBuilder::PickSizeBasedIntraL0Compaction() {
|
||||
|
||||
Compaction* LevelCompactionPicker::PickCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& /*existing_snapshots */,
|
||||
const SnapshotChecker* /*snapshot_checker*/, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) {
|
||||
LevelCompactionBuilder builder(cf_name, vstorage, this, log_buffer,
|
||||
mutable_cf_options, ioptions_,
|
||||
|
||||
@@ -20,11 +20,12 @@ class LevelCompactionPicker : public CompactionPicker {
|
||||
LevelCompactionPicker(const ImmutableOptions& ioptions,
|
||||
const InternalKeyComparator* icmp)
|
||||
: CompactionPicker(ioptions, icmp) {}
|
||||
Compaction* PickCompaction(const std::string& cf_name,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) override;
|
||||
Compaction* PickCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& /* existing_snapshots */,
|
||||
const SnapshotChecker* /* snapshot_checker */,
|
||||
VersionStorageInfo* vstorage, LogBuffer* log_buffer) override;
|
||||
|
||||
bool NeedsCompaction(const VersionStorageInfo* vstorage) const override;
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -35,7 +35,9 @@ class UniversalCompactionBuilder {
|
||||
UniversalCompactionBuilder(
|
||||
const ImmutableOptions& ioptions, const InternalKeyComparator* icmp,
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
|
||||
UniversalCompactionPicker* picker, LogBuffer* log_buffer)
|
||||
: ioptions_(ioptions),
|
||||
icmp_(icmp),
|
||||
@@ -44,7 +46,19 @@ class UniversalCompactionBuilder {
|
||||
mutable_db_options_(mutable_db_options),
|
||||
vstorage_(vstorage),
|
||||
picker_(picker),
|
||||
log_buffer_(log_buffer) {}
|
||||
log_buffer_(log_buffer) {
|
||||
assert(icmp_);
|
||||
const auto* ucmp = icmp_->user_comparator();
|
||||
assert(ucmp);
|
||||
// These parameters are only passed when user-defined timestamp is not
|
||||
// enabled.
|
||||
if (ucmp->timestamp_size() == 0) {
|
||||
earliest_snapshot_ = existing_snapshots.empty()
|
||||
? kMaxSequenceNumber
|
||||
: existing_snapshots.at(0);
|
||||
snapshot_checker_ = snapshot_checker;
|
||||
}
|
||||
}
|
||||
|
||||
// Form and return the compaction object. The caller owns return object.
|
||||
Compaction* PickCompaction();
|
||||
@@ -52,12 +66,15 @@ class UniversalCompactionBuilder {
|
||||
private:
|
||||
struct SortedRun {
|
||||
SortedRun(int _level, FileMetaData* _file, uint64_t _size,
|
||||
uint64_t _compensated_file_size, bool _being_compacted)
|
||||
uint64_t _compensated_file_size, bool _being_compacted,
|
||||
bool _level_has_marked_standalone_rangedel)
|
||||
: level(_level),
|
||||
file(_file),
|
||||
size(_size),
|
||||
compensated_file_size(_compensated_file_size),
|
||||
being_compacted(_being_compacted) {
|
||||
being_compacted(_being_compacted),
|
||||
level_has_marked_standalone_rangedel(
|
||||
_level_has_marked_standalone_rangedel) {
|
||||
assert(compensated_file_size > 0);
|
||||
assert(level != 0 || file != nullptr);
|
||||
}
|
||||
@@ -79,6 +96,10 @@ class UniversalCompactionBuilder {
|
||||
uint64_t size;
|
||||
uint64_t compensated_file_size;
|
||||
bool being_compacted;
|
||||
// True if this level has any file that is a standalone range deletion file
|
||||
// marked for compaction. Best effort is made to make only deletion
|
||||
// triggered compaction pick this type of file.
|
||||
bool level_has_marked_standalone_rangedel;
|
||||
};
|
||||
|
||||
// Pick Universal compaction to limit read amplification
|
||||
@@ -98,6 +119,11 @@ class UniversalCompactionBuilder {
|
||||
|
||||
Compaction* PickDeleteTriggeredCompaction();
|
||||
|
||||
// Returns true if this given file (that is marked be compaction) should be
|
||||
// skipped from being picked for now. We do this to best use standalone range
|
||||
// tombstone files.
|
||||
bool ShouldSkipMarkedFile(const FileMetaData* file) const;
|
||||
|
||||
// Form a compaction from the sorted run indicated by start_index to the
|
||||
// oldest sorted run.
|
||||
// The caller is responsible for making sure that those files are not in
|
||||
@@ -116,7 +142,7 @@ class UniversalCompactionBuilder {
|
||||
|
||||
bool ShouldSkipLastSortedRunForSizeAmpCompaction() const {
|
||||
assert(!sorted_runs_.empty());
|
||||
return ioptions_.preclude_last_level_data_seconds > 0 &&
|
||||
return mutable_cf_options_.preclude_last_level_data_seconds > 0 &&
|
||||
ioptions_.num_levels > 2 &&
|
||||
sorted_runs_.back().level == ioptions_.num_levels - 1 &&
|
||||
sorted_runs_.size() > 1;
|
||||
@@ -234,8 +260,18 @@ class UniversalCompactionBuilder {
|
||||
VersionStorageInfo* vstorage_;
|
||||
UniversalCompactionPicker* picker_;
|
||||
LogBuffer* log_buffer_;
|
||||
// Optional earliest snapshot at time of compaction picking. This is only
|
||||
// provided if the column family doesn't enable user-defined timestamps.
|
||||
// And this information is only passed to `Compaction` picked by deletion
|
||||
// triggered compaction for possible optimizations.
|
||||
std::optional<SequenceNumber> earliest_snapshot_;
|
||||
const SnapshotChecker* snapshot_checker_;
|
||||
// Mapping from file id to its index in the sorted run for the files that are
|
||||
// marked for compaction. This is only populated when snapshot info is
|
||||
// populated.
|
||||
std::map<uint64_t, size_t> file_marked_for_compaction_to_sorted_run_index_;
|
||||
|
||||
static std::vector<UniversalCompactionBuilder::SortedRun> CalculateSortedRuns(
|
||||
std::vector<UniversalCompactionBuilder::SortedRun> CalculateSortedRuns(
|
||||
const VersionStorageInfo& vstorage, int last_level,
|
||||
uint64_t* max_run_size);
|
||||
|
||||
@@ -394,11 +430,13 @@ bool UniversalCompactionPicker::NeedsCompaction(
|
||||
|
||||
Compaction* UniversalCompactionPicker::PickCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) {
|
||||
UniversalCompactionBuilder builder(ioptions_, icmp_, cf_name,
|
||||
mutable_cf_options, mutable_db_options,
|
||||
vstorage, this, log_buffer);
|
||||
UniversalCompactionBuilder builder(
|
||||
ioptions_, icmp_, cf_name, mutable_cf_options, mutable_db_options,
|
||||
existing_snapshots, snapshot_checker, vstorage, this, log_buffer);
|
||||
return builder.PickCompaction();
|
||||
}
|
||||
|
||||
@@ -448,14 +486,20 @@ UniversalCompactionBuilder::CalculateSortedRuns(
|
||||
*max_run_size = 0;
|
||||
std::vector<UniversalCompactionBuilder::SortedRun> ret;
|
||||
for (FileMetaData* f : vstorage.LevelFiles(0)) {
|
||||
ret.emplace_back(0, f, f->fd.GetFileSize(), f->compensated_file_size,
|
||||
f->being_compacted);
|
||||
if (earliest_snapshot_.has_value() && f->marked_for_compaction) {
|
||||
file_marked_for_compaction_to_sorted_run_index_.emplace(f->fd.GetNumber(),
|
||||
ret.size());
|
||||
}
|
||||
ret.emplace_back(
|
||||
0, f, f->fd.GetFileSize(), f->compensated_file_size, f->being_compacted,
|
||||
f->marked_for_compaction && f->FileIsStandAloneRangeTombstone());
|
||||
*max_run_size = std::max(*max_run_size, f->fd.GetFileSize());
|
||||
}
|
||||
for (int level = 1; level <= last_level; level++) {
|
||||
uint64_t total_compensated_size = 0U;
|
||||
uint64_t total_size = 0U;
|
||||
bool being_compacted = false;
|
||||
bool level_has_marked_standalone_rangedel = false;
|
||||
for (FileMetaData* f : vstorage.LevelFiles(level)) {
|
||||
total_compensated_size += f->compensated_file_size;
|
||||
total_size += f->fd.GetFileSize();
|
||||
@@ -467,16 +511,57 @@ UniversalCompactionBuilder::CalculateSortedRuns(
|
||||
if (f->being_compacted) {
|
||||
being_compacted = f->being_compacted;
|
||||
}
|
||||
level_has_marked_standalone_rangedel =
|
||||
level_has_marked_standalone_rangedel ||
|
||||
(f->marked_for_compaction && f->FileIsStandAloneRangeTombstone());
|
||||
if (earliest_snapshot_.has_value() && f->marked_for_compaction) {
|
||||
file_marked_for_compaction_to_sorted_run_index_.emplace(
|
||||
f->fd.GetNumber(), ret.size());
|
||||
}
|
||||
}
|
||||
if (total_compensated_size > 0) {
|
||||
ret.emplace_back(level, nullptr, total_size, total_compensated_size,
|
||||
being_compacted);
|
||||
being_compacted, level_has_marked_standalone_rangedel);
|
||||
}
|
||||
*max_run_size = std::max(*max_run_size, total_size);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool UniversalCompactionBuilder::ShouldSkipMarkedFile(
|
||||
const FileMetaData* file) const {
|
||||
assert(file->marked_for_compaction);
|
||||
if (!earliest_snapshot_.has_value()) {
|
||||
return false;
|
||||
}
|
||||
if (!file->FileIsStandAloneRangeTombstone()) {
|
||||
return false;
|
||||
}
|
||||
// Skip until earliest snapshot advances at or above this standalone range
|
||||
// tombstone file. `DB::ReleaseSnapshot` will re-examine and schedule
|
||||
// compaction for it.
|
||||
if (!DataIsDefinitelyInSnapshot(file->fd.largest_seqno,
|
||||
earliest_snapshot_.value(),
|
||||
snapshot_checker_)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto iter = file_marked_for_compaction_to_sorted_run_index_.find(
|
||||
file->fd.GetNumber());
|
||||
assert(iter != file_marked_for_compaction_to_sorted_run_index_.end());
|
||||
size_t idx = iter->second;
|
||||
const SortedRun* succeeding_sorted_run =
|
||||
idx < sorted_runs_.size() - 1 ? &sorted_runs_[idx + 1] : nullptr;
|
||||
// Marked standalone range tombstone file is best used if it's in the start
|
||||
// input level. Skip to let that compaction happen first.
|
||||
if (succeeding_sorted_run &&
|
||||
succeeding_sorted_run->level_has_marked_standalone_rangedel) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Universal style of compaction. Pick files that are contiguous in
|
||||
// time-range to compact.
|
||||
Compaction* UniversalCompactionBuilder::PickCompaction() {
|
||||
@@ -580,7 +665,8 @@ Compaction* UniversalCompactionBuilder::PickCompaction() {
|
||||
// Get the total number of sorted runs that are not being compacted
|
||||
int num_sr_not_compacted = 0;
|
||||
for (size_t i = 0; i < sorted_runs_.size(); i++) {
|
||||
if (sorted_runs_[i].being_compacted == false) {
|
||||
if (sorted_runs_[i].being_compacted == false &&
|
||||
!sorted_runs_[i].level_has_marked_standalone_rangedel) {
|
||||
num_sr_not_compacted++;
|
||||
}
|
||||
}
|
||||
@@ -743,16 +829,24 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
for (sr = nullptr; loop < sorted_runs_.size(); loop++) {
|
||||
sr = &sorted_runs_[loop];
|
||||
|
||||
if (!sr->being_compacted) {
|
||||
if (!sr->being_compacted && !sr->level_has_marked_standalone_rangedel) {
|
||||
candidate_count = 1;
|
||||
break;
|
||||
}
|
||||
char file_num_buf[kFormatFileNumberBufSize];
|
||||
sr->Dump(file_num_buf, sizeof(file_num_buf));
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: %s"
|
||||
"[%d] being compacted, skipping",
|
||||
cf_name_.c_str(), file_num_buf, loop);
|
||||
if (sr->being_compacted) {
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: %s"
|
||||
"[%d] being compacted, skipping",
|
||||
cf_name_.c_str(), file_num_buf, loop);
|
||||
} else if (sr->level_has_marked_standalone_rangedel) {
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: %s"
|
||||
"[%d] has standalone range tombstone files marked for "
|
||||
"compaction, skipping",
|
||||
cf_name_.c_str(), file_num_buf, loop);
|
||||
}
|
||||
|
||||
sr = nullptr;
|
||||
}
|
||||
@@ -773,7 +867,8 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
candidate_count < max_files_to_compact && i < sorted_runs_.size();
|
||||
i++) {
|
||||
const SortedRun* succeeding_sr = &sorted_runs_[i];
|
||||
if (succeeding_sr->being_compacted) {
|
||||
if (succeeding_sr->being_compacted ||
|
||||
succeeding_sr->level_has_marked_standalone_rangedel) {
|
||||
break;
|
||||
}
|
||||
// Pick files if the total/last candidate file size (increased by the
|
||||
@@ -899,11 +994,11 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
grandparents = vstorage_->LevelFiles(sorted_runs_[first_index_after].level);
|
||||
}
|
||||
|
||||
if (output_level != 0 &&
|
||||
picker_->FilesRangeOverlapWithCompaction(
|
||||
inputs, output_level,
|
||||
Compaction::EvaluatePenultimateLevel(vstorage_, ioptions_,
|
||||
start_level, output_level))) {
|
||||
if (output_level != 0 && picker_->FilesRangeOverlapWithCompaction(
|
||||
inputs, output_level,
|
||||
Compaction::EvaluatePenultimateLevel(
|
||||
vstorage_, mutable_cf_options_, ioptions_,
|
||||
start_level, output_level))) {
|
||||
return nullptr;
|
||||
}
|
||||
CompactionReason compaction_reason;
|
||||
@@ -923,6 +1018,8 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
output_level, enable_compression),
|
||||
mutable_cf_options_.default_write_temperature,
|
||||
/* max_subcompactions */ 0, grandparents,
|
||||
/* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr,
|
||||
/* is manual */ false, /* trim_ts */ "", score_,
|
||||
false /* deletion_compaction */,
|
||||
/* l0_files_might_overlap */ true, compaction_reason);
|
||||
@@ -939,7 +1036,8 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSizeAmp() {
|
||||
const size_t end_index = ShouldSkipLastSortedRunForSizeAmpCompaction()
|
||||
? sorted_runs_.size() - 2
|
||||
: sorted_runs_.size() - 1;
|
||||
if (sorted_runs_[end_index].being_compacted) {
|
||||
if (sorted_runs_[end_index].being_compacted ||
|
||||
sorted_runs_[end_index].level_has_marked_standalone_rangedel) {
|
||||
return nullptr;
|
||||
}
|
||||
const uint64_t base_sr_size = sorted_runs_[end_index].size;
|
||||
@@ -950,14 +1048,23 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSizeAmp() {
|
||||
// Get longest span (i.e, [start_index, end_index]) of available sorted runs
|
||||
while (start_index > 0) {
|
||||
const SortedRun* sr = &sorted_runs_[start_index - 1];
|
||||
if (sr->being_compacted) {
|
||||
if (sr->being_compacted || sr->level_has_marked_standalone_rangedel) {
|
||||
char file_num_buf[kFormatFileNumberBufSize];
|
||||
sr->Dump(file_num_buf, sizeof(file_num_buf), true);
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] Universal: stopping at sorted run undergoing compaction: "
|
||||
"%s[%" ROCKSDB_PRIszt "]",
|
||||
cf_name_.c_str(), file_num_buf, start_index - 1);
|
||||
if (sr->being_compacted) {
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] Universal: stopping at sorted run undergoing compaction: "
|
||||
"%s[%" ROCKSDB_PRIszt "]",
|
||||
cf_name_.c_str(), file_num_buf, start_index - 1);
|
||||
} else if (sr->level_has_marked_standalone_rangedel) {
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] Universal: stopping at sorted run that has standalone range "
|
||||
"tombstone files marked for compaction: "
|
||||
"%s[%" ROCKSDB_PRIszt "]",
|
||||
cf_name_.c_str(), file_num_buf, start_index - 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
candidate_size += sr->compensated_file_size;
|
||||
@@ -1236,11 +1343,11 @@ Compaction* UniversalCompactionBuilder::PickIncrementalForReduceSizeAmp(
|
||||
}
|
||||
|
||||
// intra L0 compactions outputs could have overlap
|
||||
if (output_level != 0 &&
|
||||
picker_->FilesRangeOverlapWithCompaction(
|
||||
inputs, output_level,
|
||||
Compaction::EvaluatePenultimateLevel(vstorage_, ioptions_,
|
||||
start_level, output_level))) {
|
||||
if (output_level != 0 && picker_->FilesRangeOverlapWithCompaction(
|
||||
inputs, output_level,
|
||||
Compaction::EvaluatePenultimateLevel(
|
||||
vstorage_, mutable_cf_options_, ioptions_,
|
||||
start_level, output_level))) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1257,7 +1364,10 @@ Compaction* UniversalCompactionBuilder::PickIncrementalForReduceSizeAmp(
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level,
|
||||
true /* enable_compression */),
|
||||
mutable_cf_options_.default_write_temperature,
|
||||
/* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ false,
|
||||
/* max_subcompactions */ 0, /* grandparents */ {},
|
||||
/* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr,
|
||||
/* is manual */ false,
|
||||
/* trim_ts */ "", score_, false /* deletion_compaction */,
|
||||
/* l0_files_might_overlap */ true,
|
||||
CompactionReason::kUniversalSizeAmplification);
|
||||
@@ -1288,7 +1398,7 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
continue;
|
||||
}
|
||||
FileMetaData* f = vstorage_->LevelFiles(0)[loop];
|
||||
if (f->marked_for_compaction) {
|
||||
if (f->marked_for_compaction && !ShouldSkipMarkedFile(f)) {
|
||||
start_level_inputs.files.push_back(f);
|
||||
start_index =
|
||||
static_cast<int>(loop); // Consider this as the first candidate.
|
||||
@@ -1302,7 +1412,7 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
|
||||
for (size_t loop = start_index + 1; loop < sorted_runs_.size(); loop++) {
|
||||
SortedRun* sr = &sorted_runs_[loop];
|
||||
if (sr->being_compacted) {
|
||||
if (sr->being_compacted || sr->level_has_marked_standalone_rangedel) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1321,7 +1431,10 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
// leveled. We pick one of the files marked for compaction and compact with
|
||||
// overlapping files in the adjacent level.
|
||||
picker_->PickFilesMarkedForCompaction(cf_name_, vstorage_, &start_level,
|
||||
&output_level, &start_level_inputs);
|
||||
&output_level, &start_level_inputs,
|
||||
[this](const FileMetaData* file) {
|
||||
return ShouldSkipMarkedFile(file);
|
||||
});
|
||||
if (start_level_inputs.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1374,7 +1487,8 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
if (picker_->FilesRangeOverlapWithCompaction(
|
||||
inputs, output_level,
|
||||
Compaction::EvaluatePenultimateLevel(
|
||||
vstorage_, ioptions_, start_level, output_level))) {
|
||||
vstorage_, mutable_cf_options_, ioptions_, start_level,
|
||||
output_level))) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1401,7 +1515,9 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
GetCompressionType(vstorage_, mutable_cf_options_, output_level, 1),
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level),
|
||||
mutable_cf_options_.default_write_temperature,
|
||||
/* max_subcompactions */ 0, grandparents, /* is manual */ false,
|
||||
/* max_subcompactions */ 0, grandparents, earliest_snapshot_,
|
||||
snapshot_checker_,
|
||||
/* is manual */ false,
|
||||
/* trim_ts */ "", score_, false /* deletion_compaction */,
|
||||
/* l0_files_might_overlap */ true,
|
||||
CompactionReason::kFilesMarkedForCompaction);
|
||||
@@ -1472,11 +1588,11 @@ Compaction* UniversalCompactionBuilder::PickCompactionWithSortedRunRange(
|
||||
}
|
||||
|
||||
// intra L0 compactions outputs could have overlap
|
||||
if (output_level != 0 &&
|
||||
picker_->FilesRangeOverlapWithCompaction(
|
||||
inputs, output_level,
|
||||
Compaction::EvaluatePenultimateLevel(vstorage_, ioptions_,
|
||||
start_level, output_level))) {
|
||||
if (output_level != 0 && picker_->FilesRangeOverlapWithCompaction(
|
||||
inputs, output_level,
|
||||
Compaction::EvaluatePenultimateLevel(
|
||||
vstorage_, mutable_cf_options_, ioptions_,
|
||||
start_level, output_level))) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1494,7 +1610,10 @@ Compaction* UniversalCompactionBuilder::PickCompactionWithSortedRunRange(
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level,
|
||||
true /* enable_compression */),
|
||||
mutable_cf_options_.default_write_temperature,
|
||||
/* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ false,
|
||||
/* max_subcompactions */ 0, /* grandparents */ {},
|
||||
/* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr,
|
||||
/* is manual */ false,
|
||||
/* trim_ts */ "", score_, false /* deletion_compaction */,
|
||||
/* l0_files_might_overlap */ true, compaction_reason);
|
||||
}
|
||||
@@ -1515,7 +1634,8 @@ Compaction* UniversalCompactionBuilder::PickPeriodicCompaction() {
|
||||
// included in the compaction.
|
||||
|
||||
size_t start_index = sorted_runs_.size();
|
||||
while (start_index > 0 && !sorted_runs_[start_index - 1].being_compacted) {
|
||||
while (start_index > 0 && !sorted_runs_[start_index - 1].being_compacted &&
|
||||
!sorted_runs_[start_index - 1].level_has_marked_standalone_rangedel) {
|
||||
start_index--;
|
||||
}
|
||||
if (start_index == sorted_runs_.size()) {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "db/compaction/compaction_picker.h"
|
||||
#include "db/snapshot_checker.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
class UniversalCompactionPicker : public CompactionPicker {
|
||||
@@ -17,11 +18,12 @@ class UniversalCompactionPicker : public CompactionPicker {
|
||||
UniversalCompactionPicker(const ImmutableOptions& ioptions,
|
||||
const InternalKeyComparator* icmp)
|
||||
: CompactionPicker(ioptions, icmp) {}
|
||||
Compaction* PickCompaction(const std::string& cf_name,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) override;
|
||||
Compaction* PickCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) override;
|
||||
int MaxOutputLevel() const override { return NumberLevels() - 1; }
|
||||
|
||||
bool NeedsCompaction(const VersionStorageInfo* vstorage) const override;
|
||||
|
||||
@@ -48,6 +48,14 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
compaction_input.has_end = sub_compact->end.has_value();
|
||||
compaction_input.end =
|
||||
compaction_input.has_end ? sub_compact->end->ToString() : "";
|
||||
compaction_input.options_file_number =
|
||||
sub_compact->compaction->input_version()
|
||||
->version_set()
|
||||
->options_file_number();
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CompactionServiceJob::ProcessKeyValueCompactionWithCompactionService",
|
||||
&compaction_input);
|
||||
|
||||
std::string compaction_input_binary;
|
||||
Status s = compaction_input.Write(&compaction_input_binary);
|
||||
@@ -68,8 +76,11 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
"[%s] [JOB %d] Starting remote compaction (output level: %d): %s",
|
||||
compaction->column_family_data()->GetName().c_str(), job_id_,
|
||||
compaction_input.output_level, input_files_oss.str().c_str());
|
||||
CompactionServiceJobInfo info(dbname_, db_id_, db_session_id_,
|
||||
GetCompactionId(sub_compact), thread_pri_);
|
||||
CompactionServiceJobInfo info(
|
||||
dbname_, db_id_, db_session_id_, GetCompactionId(sub_compact),
|
||||
thread_pri_, compaction->compaction_reason(),
|
||||
compaction->is_full_compaction(), compaction->is_manual_compaction(),
|
||||
compaction->bottommost_level());
|
||||
CompactionServiceScheduleResponse response =
|
||||
db_options_.compaction_service->Schedule(info, compaction_input_binary);
|
||||
switch (response.status) {
|
||||
@@ -192,6 +203,8 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
meta.oldest_ancester_time = file.oldest_ancester_time;
|
||||
meta.file_creation_time = file.file_creation_time;
|
||||
meta.epoch_number = file.epoch_number;
|
||||
meta.file_checksum = file.file_checksum;
|
||||
meta.file_checksum_func_name = file.file_checksum_func_name;
|
||||
meta.marked_for_compaction = file.marked_for_compaction;
|
||||
meta.unique_id = file.unique_id;
|
||||
|
||||
@@ -199,11 +212,14 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
sub_compact->Current().AddOutput(std::move(meta),
|
||||
cfd->internal_comparator(), false, true,
|
||||
file.paranoid_hash);
|
||||
sub_compact->Current().UpdateTableProperties(file.table_properties);
|
||||
}
|
||||
sub_compact->compaction_job_stats = compaction_result.stats;
|
||||
sub_compact->Current().SetNumOutputRecords(
|
||||
compaction_result.num_output_records);
|
||||
sub_compact->Current().SetTotalBytes(compaction_result.total_bytes);
|
||||
compaction_result.stats.num_output_records);
|
||||
sub_compact->Current().SetNumOutputFiles(
|
||||
compaction_result.stats.num_output_files);
|
||||
sub_compact->Current().AddBytesWritten(compaction_result.bytes_written);
|
||||
RecordTick(stats_, REMOTE_COMPACT_READ_BYTES, compaction_result.bytes_read);
|
||||
RecordTick(stats_, REMOTE_COMPACT_WRITE_BYTES,
|
||||
compaction_result.bytes_written);
|
||||
@@ -223,6 +239,18 @@ void CompactionServiceCompactionJob::RecordCompactionIOStats() {
|
||||
CompactionJob::RecordCompactionIOStats();
|
||||
}
|
||||
|
||||
void CompactionServiceCompactionJob::UpdateCompactionJobStats(
|
||||
const InternalStats::CompactionStats& stats) const {
|
||||
compaction_job_stats_->elapsed_micros = stats.micros;
|
||||
|
||||
// output information only in remote compaction
|
||||
compaction_job_stats_->total_output_bytes = stats.bytes_written;
|
||||
compaction_job_stats_->total_output_bytes_blob = stats.bytes_written_blob;
|
||||
compaction_job_stats_->num_output_records = stats.num_output_records;
|
||||
compaction_job_stats_->num_output_files = stats.num_output_files;
|
||||
compaction_job_stats_->num_output_files_blob = stats.num_output_files_blob;
|
||||
}
|
||||
|
||||
CompactionServiceCompactionJob::CompactionServiceCompactionJob(
|
||||
int job_id, Compaction* compaction, const ImmutableDBOptions& db_options,
|
||||
const MutableDBOptions& mutable_db_options, const FileOptions& file_options,
|
||||
@@ -277,6 +305,9 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
|
||||
log_buffer_->FlushBufferToLog();
|
||||
LogCompaction();
|
||||
|
||||
compaction_result_->stats.Reset();
|
||||
|
||||
const uint64_t start_micros = db_options_.clock->NowMicros();
|
||||
c->GetOrInitInputTableProperties();
|
||||
|
||||
@@ -317,38 +348,50 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
if (status.ok()) {
|
||||
status = io_s;
|
||||
}
|
||||
if (status.ok()) {
|
||||
// TODO: Add verify_table()
|
||||
}
|
||||
|
||||
// Finish up all book-keeping to unify the subcompaction results
|
||||
compact_->AggregateCompactionStats(compaction_stats_, *compaction_job_stats_);
|
||||
UpdateCompactionStats();
|
||||
RecordCompactionIOStats();
|
||||
|
||||
LogFlush(db_options_.info_log);
|
||||
compact_->status = status;
|
||||
compact_->status.PermitUncheckedError();
|
||||
|
||||
// Build compaction result
|
||||
// Build Compaction Job Stats
|
||||
|
||||
// 1. Aggregate CompactionOutputStats into Internal Compaction Stats
|
||||
// (compaction_stats_) and aggregate Compaction Job Stats
|
||||
// (compaction_job_stats_) from the sub compactions
|
||||
compact_->AggregateCompactionStats(compaction_stats_, *compaction_job_stats_);
|
||||
|
||||
// 2. Update the Output information in the Compaction Job Stats with
|
||||
// aggregated Internal Compaction Stats.
|
||||
UpdateCompactionJobStats(compaction_stats_.stats);
|
||||
|
||||
// 3. Set fields that are not propagated as part of aggregations above
|
||||
compaction_result_->stats.is_manual_compaction = c->is_manual_compaction();
|
||||
compaction_result_->stats.is_full_compaction = c->is_full_compaction();
|
||||
compaction_result_->stats.is_remote_compaction = true;
|
||||
|
||||
// 4. Update IO Stats that are not part of the aggregations above (bytes_read,
|
||||
// bytes_written)
|
||||
RecordCompactionIOStats();
|
||||
|
||||
// Build Output
|
||||
compaction_result_->output_level = compact_->compaction->output_level();
|
||||
compaction_result_->output_path = output_path_;
|
||||
for (const auto& output_file : sub_compact->GetOutputs()) {
|
||||
auto& meta = output_file.meta;
|
||||
compaction_result_->output_files.emplace_back(
|
||||
MakeTableFileName(meta.fd.GetNumber()), meta.fd.smallest_seqno,
|
||||
meta.fd.largest_seqno, meta.smallest.Encode().ToString(),
|
||||
meta.largest.Encode().ToString(), meta.oldest_ancester_time,
|
||||
meta.file_creation_time, meta.epoch_number,
|
||||
output_file.validator.GetHash(), meta.marked_for_compaction,
|
||||
meta.unique_id);
|
||||
if (status.ok()) {
|
||||
for (const auto& output_file : sub_compact->GetOutputs()) {
|
||||
auto& meta = output_file.meta;
|
||||
compaction_result_->output_files.emplace_back(
|
||||
MakeTableFileName(meta.fd.GetNumber()), meta.fd.smallest_seqno,
|
||||
meta.fd.largest_seqno, meta.smallest.Encode().ToString(),
|
||||
meta.largest.Encode().ToString(), meta.oldest_ancester_time,
|
||||
meta.file_creation_time, meta.epoch_number, meta.file_checksum,
|
||||
meta.file_checksum_func_name, output_file.validator.GetHash(),
|
||||
meta.marked_for_compaction, meta.unique_id,
|
||||
*output_file.table_properties);
|
||||
}
|
||||
}
|
||||
InternalStats::CompactionStatsFull compaction_stats;
|
||||
sub_compact->AggregateCompactionStats(compaction_stats);
|
||||
compaction_result_->num_output_records =
|
||||
compaction_stats.stats.num_output_records;
|
||||
compaction_result_->total_bytes = compaction_stats.TotalBytesWritten();
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionServiceCompactionJob::Run:0",
|
||||
&compaction_result_);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -431,6 +474,10 @@ static std::unordered_map<std::string, OptionTypeInfo> cs_input_type_info = {
|
||||
{"end",
|
||||
{offsetof(struct CompactionServiceInput, end), OptionType::kEncodedString,
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone}},
|
||||
{"options_file_number",
|
||||
{offsetof(struct CompactionServiceInput, options_file_number),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
};
|
||||
|
||||
static std::unordered_map<std::string, OptionTypeInfo>
|
||||
@@ -467,6 +514,14 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
{offsetof(struct CompactionServiceOutputFile, epoch_number),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"file_checksum",
|
||||
{offsetof(struct CompactionServiceOutputFile, file_checksum),
|
||||
OptionType::kEncodedString, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"file_checksum_func_name",
|
||||
{offsetof(struct CompactionServiceOutputFile, file_checksum_func_name),
|
||||
OptionType::kEncodedString, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"paranoid_hash",
|
||||
{offsetof(struct CompactionServiceOutputFile, paranoid_hash),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
@@ -480,7 +535,30 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
offsetof(struct CompactionServiceOutputFile, unique_id),
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone,
|
||||
{0, OptionType::kUInt64T})},
|
||||
};
|
||||
{"table_properties",
|
||||
{offsetof(struct CompactionServiceOutputFile, table_properties),
|
||||
OptionType::kStruct, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone,
|
||||
[](const ConfigOptions& opts, const std::string& /*name*/,
|
||||
const std::string& value, void* addr) {
|
||||
auto table_properties = static_cast<TableProperties*>(addr);
|
||||
return TableProperties::Parse(opts, value, table_properties);
|
||||
},
|
||||
[](const ConfigOptions& opts, const std::string& /*name*/,
|
||||
const void* addr, std::string* value) {
|
||||
const auto table_properties =
|
||||
static_cast<const TableProperties*>(addr);
|
||||
std::string result;
|
||||
auto status = table_properties->Serialize(opts, &result);
|
||||
*value = "{" + result + "}";
|
||||
return status;
|
||||
},
|
||||
[](const ConfigOptions& opts, const std::string& /*name*/,
|
||||
const void* addr1, const void* addr2, std::string* mismatch) {
|
||||
const auto this_one = static_cast<const TableProperties*>(addr1);
|
||||
const auto that_one = static_cast<const TableProperties*>(addr2);
|
||||
return this_one->AreEqual(opts, that_one, mismatch);
|
||||
}}}};
|
||||
|
||||
static std::unordered_map<std::string, OptionTypeInfo>
|
||||
compaction_job_stats_type_info = {
|
||||
@@ -527,6 +605,10 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
{offsetof(struct CompactionJobStats, is_manual_compaction),
|
||||
OptionType::kBoolean, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"is_remote_compaction",
|
||||
{offsetof(struct CompactionJobStats, is_remote_compaction),
|
||||
OptionType::kBoolean, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"total_input_bytes",
|
||||
{offsetof(struct CompactionJobStats, total_input_bytes),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
@@ -695,14 +777,6 @@ static std::unordered_map<std::string, OptionTypeInfo> cs_result_type_info = {
|
||||
{offsetof(struct CompactionServiceResult, output_path),
|
||||
OptionType::kEncodedString, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"num_output_records",
|
||||
{offsetof(struct CompactionServiceResult, num_output_records),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"total_bytes",
|
||||
{offsetof(struct CompactionServiceResult, total_bytes),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"bytes_read",
|
||||
{offsetof(struct CompactionServiceResult, bytes_read),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/utilities/options_util.h"
|
||||
#include "table/unique_id_impl.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -21,8 +21,10 @@ class MyTestCompactionService : public CompactionService {
|
||||
: db_path_(std::move(db_path)),
|
||||
options_(options),
|
||||
statistics_(statistics),
|
||||
start_info_("na", "na", "na", 0, Env::TOTAL),
|
||||
wait_info_("na", "na", "na", 0, Env::TOTAL),
|
||||
start_info_("na", "na", "na", 0, Env::TOTAL, CompactionReason::kUnknown,
|
||||
false, false, false),
|
||||
wait_info_("na", "na", "na", 0, Env::TOTAL, CompactionReason::kUnknown,
|
||||
false, false, false),
|
||||
listeners_(listeners),
|
||||
table_properties_collector_factories_(
|
||||
std::move(table_properties_collector_factories)) {}
|
||||
@@ -97,8 +99,12 @@ class MyTestCompactionService : public CompactionService {
|
||||
Status s =
|
||||
DB::OpenAndCompact(options, db_path_, db_path_ + "/" + scheduled_job_id,
|
||||
compaction_input, result, options_override);
|
||||
if (is_override_wait_result_) {
|
||||
*result = override_wait_result_;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
if (is_override_wait_result_) {
|
||||
*result = override_wait_result_;
|
||||
}
|
||||
result_ = *result;
|
||||
}
|
||||
compaction_num_.fetch_add(1);
|
||||
if (s.ok()) {
|
||||
@@ -141,6 +147,10 @@ class MyTestCompactionService : public CompactionService {
|
||||
|
||||
void SetCanceled(bool canceled) { canceled_ = canceled; }
|
||||
|
||||
void GetResult(CompactionServiceResult* deserialized) {
|
||||
CompactionServiceResult::Read(result_, deserialized).PermitUncheckedError();
|
||||
}
|
||||
|
||||
CompactionServiceJobStatus GetFinalCompactionServiceJobStatus() {
|
||||
return final_updated_status_.load();
|
||||
}
|
||||
@@ -162,6 +172,7 @@ class MyTestCompactionService : public CompactionService {
|
||||
CompactionServiceJobStatus override_wait_status_ =
|
||||
CompactionServiceJobStatus::kFailure;
|
||||
bool is_override_wait_result_ = false;
|
||||
std::string result_;
|
||||
std::string override_wait_result_;
|
||||
std::vector<std::shared_ptr<EventListener>> listeners_;
|
||||
std::vector<std::shared_ptr<TablePropertiesCollectorFactory>>
|
||||
@@ -331,6 +342,34 @@ TEST_F(CompactionServiceTest, BasicCompactions) {
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "cf_1", "cf_2", "cf_3"},
|
||||
options);
|
||||
ASSERT_GT(verify_passed, 0);
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
if (s.IsAborted()) {
|
||||
ASSERT_NOK(result.status);
|
||||
} else {
|
||||
ASSERT_OK(result.status);
|
||||
}
|
||||
ASSERT_GE(result.stats.elapsed_micros, 1);
|
||||
ASSERT_GE(result.stats.cpu_micros, 1);
|
||||
|
||||
ASSERT_EQ(20, result.stats.num_output_records);
|
||||
ASSERT_EQ(result.output_files.size(), result.stats.num_output_files);
|
||||
|
||||
uint64_t total_size = 0;
|
||||
for (auto output_file : result.output_files) {
|
||||
std::string file_name = result.output_path + "/" + output_file.file_name;
|
||||
|
||||
uint64_t file_size = 0;
|
||||
ASSERT_OK(options.env->GetFileSize(file_name, &file_size));
|
||||
ASSERT_GT(file_size, 0);
|
||||
total_size += file_size;
|
||||
}
|
||||
ASSERT_EQ(total_size, result.stats.total_output_bytes);
|
||||
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_FALSE(result.stats.is_full_compaction);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
@@ -369,6 +408,547 @@ TEST_F(CompactionServiceTest, ManualCompaction) {
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
VerifyTestData();
|
||||
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, CompactionOutputFileIOError) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::FinishCompactionOutputFile()::AfterFinish",
|
||||
[&](void* status) {
|
||||
// override status
|
||||
auto s = static_cast<Status*>(status);
|
||||
*s = Status::IOError("Injected IOError!");
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
ASSERT_NOK(db_->CompactRange(CompactRangeOptions(), &start, &end));
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_NOK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, PreservedOptionsLocalCompaction) {
|
||||
Options options = CurrentOptions();
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.disable_auto_compactions = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
for (auto i = 0; i < 2; ++i) {
|
||||
for (auto j = 0; j < 10; ++j) {
|
||||
ASSERT_OK(
|
||||
Put("foo" + std::to_string(i * 10 + j), rnd.RandomString(1024)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::ProcessKeyValueCompaction()::Processing", [&](void* arg) {
|
||||
auto compaction = static_cast<Compaction*>(arg);
|
||||
std::string options_file_name = OptionsFileName(
|
||||
dbname_,
|
||||
compaction->input_version()->version_set()->options_file_number());
|
||||
|
||||
// Change option twice to make sure the very first OPTIONS file gets
|
||||
// purged
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"level0_file_num_compaction_trigger", "4"}}));
|
||||
ASSERT_EQ(4, dbfull()->GetOptions().level0_file_num_compaction_trigger);
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"level0_file_num_compaction_trigger", "6"}}));
|
||||
ASSERT_EQ(6, dbfull()->GetOptions().level0_file_num_compaction_trigger);
|
||||
dbfull()->TEST_DeleteObsoleteFiles();
|
||||
|
||||
// For non-remote compactions, OPTIONS file can be deleted while
|
||||
// using option at the start of the compaction
|
||||
Status s = env_->FileExists(options_file_name);
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
// Should be old value
|
||||
ASSERT_EQ(2, compaction->mutable_cf_options()
|
||||
->level0_file_num_compaction_trigger);
|
||||
ASSERT_TRUE(dbfull()->min_options_file_numbers_.empty());
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
Status s = dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_TRUE(s.ok());
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, PreservedOptionsRemoteCompaction) {
|
||||
// For non-remote compaction do not preserve options file
|
||||
Options options = CurrentOptions();
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.disable_auto_compactions = true;
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
Random rnd(301);
|
||||
for (auto i = 0; i < 2; ++i) {
|
||||
for (auto j = 0; j < 10; ++j) {
|
||||
ASSERT_OK(
|
||||
Put("foo" + std::to_string(i * 10 + j), rnd.RandomString(1024)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"CompactionServiceTest::OptionsFileChanged",
|
||||
"DBImplSecondary::OpenAndCompact::BeforeLoadingOptions:1"}});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImplSecondary::OpenAndCompact::BeforeLoadingOptions:0",
|
||||
[&](void* arg) {
|
||||
auto options_file_number = static_cast<uint64_t*>(arg);
|
||||
// Change the option twice before the compaction run
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"level0_file_num_compaction_trigger", "4"}}));
|
||||
ASSERT_EQ(4, dbfull()->GetOptions().level0_file_num_compaction_trigger);
|
||||
ASSERT_TRUE(dbfull()->versions_->options_file_number() >
|
||||
*options_file_number);
|
||||
|
||||
// Change the option twice before the compaction run
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"level0_file_num_compaction_trigger", "5"}}));
|
||||
ASSERT_EQ(5, dbfull()->GetOptions().level0_file_num_compaction_trigger);
|
||||
ASSERT_TRUE(dbfull()->versions_->options_file_number() >
|
||||
*options_file_number);
|
||||
|
||||
TEST_SYNC_POINT("CompactionServiceTest::OptionsFileChanged");
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionServiceJob::ProcessKeyValueCompactionWithCompactionService",
|
||||
[&](void* arg) {
|
||||
auto input = static_cast<CompactionServiceInput*>(arg);
|
||||
std::string options_file_name =
|
||||
OptionsFileName(dbname_, input->options_file_number);
|
||||
|
||||
ASSERT_OK(env_->FileExists(options_file_name));
|
||||
ASSERT_FALSE(dbfull()->min_options_file_numbers_.empty());
|
||||
ASSERT_EQ(dbfull()->min_options_file_numbers_.front(),
|
||||
input->options_file_number);
|
||||
|
||||
DBOptions db_options;
|
||||
ConfigOptions config_options;
|
||||
std::vector<ColumnFamilyDescriptor> all_column_families;
|
||||
config_options.env = env_;
|
||||
ASSERT_OK(LoadOptionsFromFile(config_options, options_file_name,
|
||||
&db_options, &all_column_families));
|
||||
bool has_cf = false;
|
||||
for (auto& cf : all_column_families) {
|
||||
if (cf.name == input->cf_name) {
|
||||
// Should be old value
|
||||
ASSERT_EQ(2, cf.options.level0_file_num_compaction_trigger);
|
||||
has_cf = true;
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(has_cf);
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::ProcessKeyValueCompaction()::Processing", [&](void* arg) {
|
||||
auto compaction = static_cast<Compaction*>(arg);
|
||||
ASSERT_EQ(2, compaction->mutable_cf_options()
|
||||
->level0_file_num_compaction_trigger);
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
Status s = dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_TRUE(s.ok());
|
||||
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
}
|
||||
|
||||
class EventVerifier : public EventListener {
|
||||
public:
|
||||
explicit EventVerifier(uint64_t expected_num_input_records,
|
||||
size_t expected_num_input_files,
|
||||
uint64_t expected_num_output_records,
|
||||
size_t expected_num_output_files,
|
||||
const std::string& expected_smallest_output_key_prefix,
|
||||
const std::string& expected_largest_output_key_prefix,
|
||||
bool expected_is_remote_compaction_on_begin,
|
||||
bool expected_is_remote_compaction_on_complete)
|
||||
: expected_num_input_records_(expected_num_input_records),
|
||||
expected_num_input_files_(expected_num_input_files),
|
||||
expected_num_output_records_(expected_num_output_records),
|
||||
expected_num_output_files_(expected_num_output_files),
|
||||
expected_smallest_output_key_prefix_(
|
||||
expected_smallest_output_key_prefix),
|
||||
expected_largest_output_key_prefix_(expected_largest_output_key_prefix),
|
||||
expected_is_remote_compaction_on_begin_(
|
||||
expected_is_remote_compaction_on_begin),
|
||||
expected_is_remote_compaction_on_complete_(
|
||||
expected_is_remote_compaction_on_complete) {}
|
||||
void OnCompactionBegin(DB* /*db*/, const CompactionJobInfo& ci) override {
|
||||
ASSERT_EQ(expected_num_input_files_, ci.input_files.size());
|
||||
ASSERT_EQ(expected_num_input_files_, ci.input_file_infos.size());
|
||||
ASSERT_EQ(expected_is_remote_compaction_on_begin_,
|
||||
ci.stats.is_remote_compaction);
|
||||
ASSERT_TRUE(ci.stats.is_manual_compaction);
|
||||
ASSERT_FALSE(ci.stats.is_full_compaction);
|
||||
}
|
||||
void OnCompactionCompleted(DB* /*db*/, const CompactionJobInfo& ci) override {
|
||||
ASSERT_GT(ci.stats.elapsed_micros, 0);
|
||||
ASSERT_GT(ci.stats.cpu_micros, 0);
|
||||
ASSERT_EQ(expected_num_input_records_, ci.stats.num_input_records);
|
||||
ASSERT_EQ(expected_num_input_files_, ci.stats.num_input_files);
|
||||
ASSERT_EQ(expected_num_output_records_, ci.stats.num_output_records);
|
||||
ASSERT_EQ(expected_num_output_files_, ci.stats.num_output_files);
|
||||
ASSERT_EQ(expected_smallest_output_key_prefix_,
|
||||
ci.stats.smallest_output_key_prefix);
|
||||
ASSERT_EQ(expected_largest_output_key_prefix_,
|
||||
ci.stats.largest_output_key_prefix);
|
||||
ASSERT_GT(ci.stats.total_input_bytes, 0);
|
||||
ASSERT_GT(ci.stats.total_output_bytes, 0);
|
||||
ASSERT_EQ(ci.stats.num_input_records,
|
||||
ci.stats.num_output_records + ci.stats.num_records_replaced);
|
||||
ASSERT_EQ(expected_is_remote_compaction_on_complete_,
|
||||
ci.stats.is_remote_compaction);
|
||||
ASSERT_TRUE(ci.stats.is_manual_compaction);
|
||||
ASSERT_FALSE(ci.stats.is_full_compaction);
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t expected_num_input_records_;
|
||||
size_t expected_num_input_files_;
|
||||
uint64_t expected_num_output_records_;
|
||||
size_t expected_num_output_files_;
|
||||
std::string expected_smallest_output_key_prefix_;
|
||||
std::string expected_largest_output_key_prefix_;
|
||||
bool expected_is_remote_compaction_on_begin_;
|
||||
bool expected_is_remote_compaction_on_complete_;
|
||||
};
|
||||
|
||||
TEST_F(CompactionServiceTest, VerifyStats) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
auto event_verifier = std::make_shared<EventVerifier>(
|
||||
30 /* expected_num_input_records */, 3 /* expected_num_input_files */,
|
||||
20 /* expected_num_output_records */, 1 /* expected_num_output_files */,
|
||||
"key00000" /* expected_smallest_output_key_prefix */,
|
||||
"key00001" /* expected_largest_output_key_prefix */,
|
||||
true /* expected_is_remote_compaction_on_begin */,
|
||||
true /* expected_is_remote_compaction_on_complete */);
|
||||
options.listeners.push_back(event_verifier);
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
std::string start_str = Key(0);
|
||||
std::string end_str = Key(1);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, &end));
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
VerifyTestData();
|
||||
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, VerifyStatsLocalFallback) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
auto event_verifier = std::make_shared<EventVerifier>(
|
||||
30 /* expected_num_input_records */, 3 /* expected_num_input_files */,
|
||||
20 /* expected_num_output_records */, 1 /* expected_num_output_files */,
|
||||
"key00000" /* expected_smallest_output_key_prefix */,
|
||||
"key00001" /* expected_largest_output_key_prefix */,
|
||||
true /* expected_is_remote_compaction_on_begin */,
|
||||
false /* expected_is_remote_compaction_on_complete */);
|
||||
options.listeners.push_back(event_verifier);
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
my_cs->OverrideStartStatus(CompactionServiceJobStatus::kUseLocal);
|
||||
|
||||
std::string start_str = Key(0);
|
||||
std::string end_str = Key(1);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, &end));
|
||||
// Remote Compaction did not happen
|
||||
ASSERT_EQ(my_cs->GetCompactionNum(), comp_num);
|
||||
VerifyTestData();
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, CorruptedOutput) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionServiceCompactionJob::Run:0", [&](void* arg) {
|
||||
CompactionServiceResult* compaction_result =
|
||||
*(static_cast<CompactionServiceResult**>(arg));
|
||||
ASSERT_TRUE(compaction_result != nullptr &&
|
||||
!compaction_result->output_files.empty());
|
||||
// Corrupt files here
|
||||
for (const auto& output_file : compaction_result->output_files) {
|
||||
std::string file_name =
|
||||
compaction_result->output_path + "/" + output_file.file_name;
|
||||
|
||||
uint64_t file_size = 0;
|
||||
Status s = options.env->GetFileSize(file_name, &file_size);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_GT(file_size, 0);
|
||||
|
||||
ASSERT_OK(test::CorruptFile(env_, file_name, 0,
|
||||
static_cast<int>(file_size),
|
||||
true /* verifyChecksum */));
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// CompactRange() should fail
|
||||
Status s = db_->CompactRange(CompactRangeOptions(), &start, &end);
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
// On the worker side, the compaction is considered success
|
||||
// Verification is done on the primary side
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, CorruptedOutputParanoidFileCheck) {
|
||||
for (bool paranoid_file_check_enabled : {false, true}) {
|
||||
SCOPED_TRACE("paranoid_file_check_enabled=" +
|
||||
std::to_string(paranoid_file_check_enabled));
|
||||
|
||||
Options options = CurrentOptions();
|
||||
Destroy(options);
|
||||
options.disable_auto_compactions = true;
|
||||
options.paranoid_file_checks = paranoid_file_check_enabled;
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionServiceCompactionJob::Run:0", [&](void* arg) {
|
||||
CompactionServiceResult* compaction_result =
|
||||
*(static_cast<CompactionServiceResult**>(arg));
|
||||
ASSERT_TRUE(compaction_result != nullptr &&
|
||||
!compaction_result->output_files.empty());
|
||||
// Corrupt files here
|
||||
for (const auto& output_file : compaction_result->output_files) {
|
||||
std::string file_name =
|
||||
compaction_result->output_path + "/" + output_file.file_name;
|
||||
|
||||
// Corrupt very small range of bytes. This corruption is so small
|
||||
// that this isn't caught by default light-weight check
|
||||
ASSERT_OK(test::CorruptFile(env_, file_name, 0, 1,
|
||||
false /* verifyChecksum */));
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Status s = db_->CompactRange(CompactRangeOptions(), &start, &end);
|
||||
if (paranoid_file_check_enabled) {
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_EQ(Status::Corruption("Paranoid checksums do not match"), s);
|
||||
} else {
|
||||
// CompactRange() goes through if paranoid file check is not enabled
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
// On the worker side, the compaction is considered success
|
||||
// Verification is done on the primary side
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, TruncatedOutput) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionServiceCompactionJob::Run:0", [&](void* arg) {
|
||||
CompactionServiceResult* compaction_result =
|
||||
*(static_cast<CompactionServiceResult**>(arg));
|
||||
ASSERT_TRUE(compaction_result != nullptr &&
|
||||
!compaction_result->output_files.empty());
|
||||
// Truncate files here
|
||||
for (const auto& output_file : compaction_result->output_files) {
|
||||
std::string file_name =
|
||||
compaction_result->output_path + "/" + output_file.file_name;
|
||||
|
||||
uint64_t file_size = 0;
|
||||
Status s = options.env->GetFileSize(file_name, &file_size);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_GT(file_size, 0);
|
||||
|
||||
ASSERT_OK(test::TruncateFile(env_, file_name, file_size / 2));
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// CompactRange() should fail
|
||||
Status s = db_->CompactRange(CompactRangeOptions(), &start, &end);
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
// On the worker side, the compaction is considered success
|
||||
// Verification is done on the primary side
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, CustomFileChecksum) {
|
||||
Options options = CurrentOptions();
|
||||
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionServiceCompactionJob::Run:0", [&](void* arg) {
|
||||
CompactionServiceResult* compaction_result =
|
||||
*(static_cast<CompactionServiceResult**>(arg));
|
||||
ASSERT_TRUE(compaction_result != nullptr &&
|
||||
!compaction_result->output_files.empty());
|
||||
// Validate Checksum files here
|
||||
for (const auto& output_file : compaction_result->output_files) {
|
||||
std::string file_name =
|
||||
compaction_result->output_path + "/" + output_file.file_name;
|
||||
|
||||
FileChecksumGenContext gen_context;
|
||||
gen_context.file_name = file_name;
|
||||
std::unique_ptr<FileChecksumGenerator> file_checksum_gen =
|
||||
options.file_checksum_gen_factory->CreateFileChecksumGenerator(
|
||||
gen_context);
|
||||
|
||||
std::unique_ptr<SequentialFile> file_reader;
|
||||
uint64_t file_size = 0;
|
||||
Status s = options.env->GetFileSize(file_name, &file_size);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_GT(file_size, 0);
|
||||
|
||||
s = options.env->NewSequentialFile(file_name, &file_reader,
|
||||
EnvOptions());
|
||||
ASSERT_OK(s);
|
||||
|
||||
Slice result;
|
||||
std::unique_ptr<char[]> scratch(new char[file_size]);
|
||||
s = file_reader->Read(file_size, &result, scratch.get());
|
||||
ASSERT_OK(s);
|
||||
|
||||
file_checksum_gen->Update(scratch.get(), result.size());
|
||||
file_checksum_gen->Finalize();
|
||||
|
||||
// Verify actual checksum and the func name
|
||||
ASSERT_EQ(file_checksum_gen->Name(),
|
||||
output_file.file_checksum_func_name);
|
||||
ASSERT_EQ(file_checksum_gen->GetChecksum(),
|
||||
output_file.file_checksum);
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, &end));
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, CancelCompactionOnRemoteSide) {
|
||||
@@ -601,11 +1181,20 @@ TEST_F(CompactionServiceTest, CompactionInfo) {
|
||||
{file.db_path + "/" + file.name}, 2));
|
||||
info = my_cs->GetCompactionInfoForStart();
|
||||
ASSERT_EQ(Env::USER, info.priority);
|
||||
ASSERT_EQ(CompactionReason::kManualCompaction, info.compaction_reason);
|
||||
ASSERT_EQ(true, info.is_manual_compaction);
|
||||
ASSERT_EQ(false, info.is_full_compaction);
|
||||
ASSERT_EQ(true, info.bottommost_level);
|
||||
info = my_cs->GetCompactionInfoForWait();
|
||||
ASSERT_EQ(Env::USER, info.priority);
|
||||
ASSERT_EQ(CompactionReason::kManualCompaction, info.compaction_reason);
|
||||
ASSERT_EQ(true, info.is_manual_compaction);
|
||||
ASSERT_EQ(false, info.is_full_compaction);
|
||||
ASSERT_EQ(true, info.bottommost_level);
|
||||
|
||||
// Test priority BOTTOM
|
||||
env_->SetBackgroundThreads(1, Env::BOTTOM);
|
||||
// This will set bottommost_level = true but is_full_compaction = false
|
||||
options.num_levels = 2;
|
||||
ReopenWithCompactionService(&options);
|
||||
my_cs =
|
||||
@@ -628,9 +1217,71 @@ TEST_F(CompactionServiceTest, CompactionInfo) {
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
info = my_cs->GetCompactionInfoForStart();
|
||||
ASSERT_EQ(CompactionReason::kLevelL0FilesNum, info.compaction_reason);
|
||||
ASSERT_EQ(false, info.is_manual_compaction);
|
||||
ASSERT_EQ(false, info.is_full_compaction);
|
||||
ASSERT_EQ(true, info.bottommost_level);
|
||||
ASSERT_EQ(Env::BOTTOM, info.priority);
|
||||
info = my_cs->GetCompactionInfoForWait();
|
||||
ASSERT_EQ(Env::BOTTOM, info.priority);
|
||||
ASSERT_EQ(CompactionReason::kLevelL0FilesNum, info.compaction_reason);
|
||||
ASSERT_EQ(false, info.is_manual_compaction);
|
||||
ASSERT_EQ(false, info.is_full_compaction);
|
||||
ASSERT_EQ(true, info.bottommost_level);
|
||||
|
||||
// Test Non-Bottommost Level
|
||||
options.num_levels = 4;
|
||||
ReopenWithCompactionService(&options);
|
||||
my_cs =
|
||||
static_cast_with_check<MyTestCompactionService>(GetCompactionService());
|
||||
|
||||
for (int i = 0; i < options.level0_file_num_compaction_trigger; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
int key_id = i * 10 + j;
|
||||
ASSERT_OK(Put(Key(key_id), "value_new_new" + std::to_string(key_id)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
info = my_cs->GetCompactionInfoForStart();
|
||||
ASSERT_EQ(false, info.is_manual_compaction);
|
||||
ASSERT_EQ(false, info.is_full_compaction);
|
||||
ASSERT_EQ(false, info.bottommost_level);
|
||||
info = my_cs->GetCompactionInfoForWait();
|
||||
ASSERT_EQ(false, info.is_manual_compaction);
|
||||
ASSERT_EQ(false, info.is_full_compaction);
|
||||
ASSERT_EQ(false, info.bottommost_level);
|
||||
|
||||
// Test Full Compaction + Bottommost Level
|
||||
options.num_levels = 6;
|
||||
ReopenWithCompactionService(&options);
|
||||
my_cs =
|
||||
static_cast_with_check<MyTestCompactionService>(GetCompactionService());
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
int key_id = i * 10 + j;
|
||||
ASSERT_OK(Put(Key(key_id), "value_new_new" + std::to_string(key_id)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
info = my_cs->GetCompactionInfoForStart();
|
||||
ASSERT_EQ(true, info.is_manual_compaction);
|
||||
ASSERT_EQ(true, info.is_full_compaction);
|
||||
ASSERT_EQ(true, info.bottommost_level);
|
||||
ASSERT_EQ(CompactionReason::kManualCompaction, info.compaction_reason);
|
||||
info = my_cs->GetCompactionInfoForWait();
|
||||
ASSERT_EQ(true, info.is_manual_compaction);
|
||||
ASSERT_EQ(true, info.is_full_compaction);
|
||||
ASSERT_EQ(true, info.bottommost_level);
|
||||
ASSERT_EQ(CompactionReason::kManualCompaction, info.compaction_reason);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, FallbackLocalAuto) {
|
||||
|
||||
@@ -39,7 +39,7 @@ void CompactionState::AggregateCompactionStats(
|
||||
InternalStats::CompactionStatsFull& compaction_stats,
|
||||
CompactionJobStats& compaction_job_stats) {
|
||||
for (const auto& sc : sub_compact_states) {
|
||||
sc.AggregateCompactionStats(compaction_stats);
|
||||
sc.AggregateCompactionOutputStats(compaction_stats);
|
||||
compaction_job_stats.Add(sc.compaction_job_stats);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include "rocksdb/sst_partitioner.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
void SubcompactionState::AggregateCompactionStats(
|
||||
void SubcompactionState::AggregateCompactionOutputStats(
|
||||
InternalStats::CompactionStatsFull& compaction_stats) const {
|
||||
compaction_stats.stats.Add(compaction_outputs_.stats_);
|
||||
if (HasPenultimateLevelOutputs()) {
|
||||
@@ -34,9 +34,16 @@ void SubcompactionState::Cleanup(Cache* cache) {
|
||||
|
||||
if (!status.ok()) {
|
||||
for (const auto& out : GetOutputs()) {
|
||||
// If this file was inserted into the table cache then remove
|
||||
// them here because this compaction was not committed.
|
||||
TableCache::Evict(cache, out.meta.fd.GetNumber());
|
||||
// If this file was inserted into the table cache then remove it here
|
||||
// because this compaction was not committed. This is not strictly
|
||||
// required because of a backstop TableCache::Evict() in
|
||||
// PurgeObsoleteFiles() but is our opportunity to apply
|
||||
// uncache_aggressiveness. TODO: instead, put these files into the
|
||||
// VersionSet::obsolete_files_ pipeline so that they don't have to
|
||||
// be picked up by scanning the DB directory.
|
||||
TableCache::ReleaseObsolete(
|
||||
cache, out.meta.fd.GetNumber(), nullptr /*handle*/,
|
||||
compaction->mutable_cf_options()->uncache_aggressiveness);
|
||||
}
|
||||
}
|
||||
// TODO: sub_compact.io_status is not checked like status. Not sure if thats
|
||||
|
||||
@@ -179,7 +179,7 @@ class SubcompactionState {
|
||||
|
||||
void Cleanup(Cache* cache);
|
||||
|
||||
void AggregateCompactionStats(
|
||||
void AggregateCompactionOutputStats(
|
||||
InternalStats::CompactionStatsFull& compaction_stats) const;
|
||||
|
||||
CompactionOutputs& Current() const {
|
||||
|
||||
@@ -9,15 +9,26 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/iostats_context.h"
|
||||
#include "rocksdb/listener.h"
|
||||
#include "rocksdb/utilities/debug.h"
|
||||
#include "rocksdb/utilities/table_properties_collectors.h"
|
||||
#include "test_util/mock_time_env.h"
|
||||
#include "util/defer.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
namespace {
|
||||
ConfigOptions GetStrictConfigOptions() {
|
||||
ConfigOptions config_options;
|
||||
config_options.ignore_unknown_options = false;
|
||||
config_options.ignore_unsupported_options = false;
|
||||
config_options.input_strings_escaped = false;
|
||||
return config_options;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class TieredCompactionTest : public DBTestBase {
|
||||
public:
|
||||
@@ -1234,82 +1245,9 @@ TEST_F(TieredCompactionTest, RangeBasedTieredStorageLevel) {
|
||||
db_->ReleaseSnapshot(temp_snap);
|
||||
}
|
||||
|
||||
TEST_F(TieredCompactionTest, CheckInternalKeyRange) {
|
||||
// When compacting keys from the last level to penultimate level,
|
||||
// output to penultimate level should be within internal key range
|
||||
// of input files from penultimate level.
|
||||
// Set up:
|
||||
// L5:
|
||||
// File 1: DeleteRange[1, 3)@4, File 2: [3@5, 100@6]
|
||||
// L6:
|
||||
// File 3: [2@1, 3@2], File 4: [50@3]
|
||||
//
|
||||
// When File 1 and File 3 are being compacted,
|
||||
// Key(3) cannot be compacted up, otherwise it causes
|
||||
// inconsistency where File 3's Key(3) has a lower sequence number
|
||||
// than File 2's Key(3).
|
||||
const int kNumLevels = 7;
|
||||
auto options = CurrentOptions();
|
||||
SetColdTemperature(options);
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.num_levels = kNumLevels;
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.max_subcompactions = 10;
|
||||
options.preclude_last_level_data_seconds = 10000;
|
||||
DestroyAndReopen(options);
|
||||
auto cmp = options.comparator;
|
||||
|
||||
std::string hot_start = Key(0);
|
||||
std::string hot_end = Key(0);
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionIterator::PrepareOutput.context", [&](void* arg) {
|
||||
auto context = static_cast<PerKeyPlacementContext*>(arg);
|
||||
context->output_to_penultimate_level =
|
||||
cmp->Compare(context->key, hot_start) >= 0 &&
|
||||
cmp->Compare(context->key, hot_end) < 0;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
// File 1
|
||||
ASSERT_OK(Put(Key(2), "val2"));
|
||||
ASSERT_OK(Put(Key(3), "val3"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(6);
|
||||
// File 2
|
||||
ASSERT_OK(Put(Key(50), "val50"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(6);
|
||||
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
hot_end = Key(100);
|
||||
std::string start = Key(1);
|
||||
std::string end = Key(3);
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), start, end));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(5);
|
||||
// File 3
|
||||
ASSERT_OK(Put(Key(3), "vall"));
|
||||
ASSERT_OK(Put(Key(100), "val100"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(5);
|
||||
// Try to compact keys up
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
start = Key(1);
|
||||
end = Key(2);
|
||||
Slice begin_slice(start);
|
||||
Slice end_slice(end);
|
||||
ASSERT_OK(db_->CompactRange(cro, &begin_slice, &end_slice));
|
||||
// Without internal key range checking, we get the following error:
|
||||
// Corruption: force_consistency_checks(DEBUG): VersionBuilder: L5 has
|
||||
// overlapping ranges: file #18 largest key: '6B6579303030303033' seq:102,
|
||||
// type:1 vs. file #15 smallest key: '6B6579303030303033' seq:104, type:1
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
class PrecludeLastLevelTest : public DBTestBase {
|
||||
class PrecludeLastLevelTestBase : public DBTestBase {
|
||||
public:
|
||||
PrecludeLastLevelTest(std::string test_name = "preclude_last_level_test")
|
||||
PrecludeLastLevelTestBase(std::string test_name = "preclude_last_level_test")
|
||||
: DBTestBase(test_name, /*env_do_fsync=*/false) {
|
||||
mock_clock_ = std::make_shared<MockSystemClock>(env_->GetSystemClock());
|
||||
mock_clock_->SetCurrentTime(kMockStartTime);
|
||||
@@ -1334,9 +1272,52 @@ class PrecludeLastLevelTest : public DBTestBase {
|
||||
});
|
||||
mock_clock_->SetCurrentTime(kMockStartTime);
|
||||
}
|
||||
|
||||
void ApplyConfigChangeImpl(
|
||||
bool dynamic, Options* options,
|
||||
const std::unordered_map<std::string, std::string>& config_change,
|
||||
const std::unordered_map<std::string, std::string>& db_config_change) {
|
||||
if (dynamic) {
|
||||
if (config_change.size() > 0) {
|
||||
// FIXME: temporary while preserve/preclude options are not user mutable
|
||||
SaveAndRestore<bool> m(&TEST_allowSetOptionsImmutableInMutable, true);
|
||||
ASSERT_OK(db_->SetOptions(config_change));
|
||||
}
|
||||
if (db_config_change.size() > 0) {
|
||||
ASSERT_OK(db_->SetDBOptions(db_config_change));
|
||||
}
|
||||
} else {
|
||||
if (config_change.size() > 0) {
|
||||
ASSERT_OK(GetColumnFamilyOptionsFromMap(
|
||||
GetStrictConfigOptions(), *options, config_change, options));
|
||||
}
|
||||
if (db_config_change.size() > 0) {
|
||||
ASSERT_OK(GetDBOptionsFromMap(GetStrictConfigOptions(), *options,
|
||||
db_config_change, options));
|
||||
}
|
||||
Reopen(*options);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(PrecludeLastLevelTest, MigrationFromPreserveTimeManualCompaction) {
|
||||
class PrecludeLastLevelTest : public PrecludeLastLevelTestBase,
|
||||
public testing::WithParamInterface<bool> {
|
||||
public:
|
||||
using PrecludeLastLevelTestBase::PrecludeLastLevelTestBase;
|
||||
|
||||
bool UseDynamicConfig() const { return GetParam(); }
|
||||
|
||||
void ApplyConfigChange(
|
||||
Options* options,
|
||||
const std::unordered_map<std::string, std::string>& config_change,
|
||||
const std::unordered_map<std::string, std::string>& db_config_change =
|
||||
{}) {
|
||||
ApplyConfigChangeImpl(UseDynamicConfig(), options, config_change,
|
||||
db_config_change);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_P(PrecludeLastLevelTest, MigrationFromPreserveTimeManualCompaction) {
|
||||
const int kNumTrigger = 4;
|
||||
const int kNumLevels = 7;
|
||||
const int kNumKeys = 100;
|
||||
@@ -1367,9 +1348,8 @@ TEST_F(PrecludeLastLevelTest, MigrationFromPreserveTimeManualCompaction) {
|
||||
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
|
||||
|
||||
// enable preclude feature
|
||||
options.preclude_last_level_data_seconds = 10000;
|
||||
options.last_level_temperature = Temperature::kCold;
|
||||
Reopen(options);
|
||||
ApplyConfigChange(&options, {{"preclude_last_level_data_seconds", "10000"},
|
||||
{"last_level_temperature", "kCold"}});
|
||||
|
||||
// all data is hot, even they're in the last level
|
||||
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
@@ -1393,7 +1373,7 @@ TEST_F(PrecludeLastLevelTest, MigrationFromPreserveTimeManualCompaction) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(PrecludeLastLevelTest, MigrationFromPreserveTimeAutoCompaction) {
|
||||
TEST_P(PrecludeLastLevelTest, MigrationFromPreserveTimeAutoCompaction) {
|
||||
const int kNumTrigger = 4;
|
||||
const int kNumLevels = 7;
|
||||
const int kNumKeys = 100;
|
||||
@@ -1423,16 +1403,17 @@ TEST_F(PrecludeLastLevelTest, MigrationFromPreserveTimeAutoCompaction) {
|
||||
// all data is pushed to the last level
|
||||
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
|
||||
|
||||
// enable preclude feature
|
||||
options.preclude_last_level_data_seconds = 10000;
|
||||
options.last_level_temperature = Temperature::kCold;
|
||||
// enable preclude feature, and...
|
||||
// make sure it won't trigger Size Amp compaction, unlike normal Size Amp
|
||||
// compaction which is typically a last level compaction, when tiered Storage
|
||||
// ("preclude_last_level") is enabled, size amp won't include the last level.
|
||||
// As the last level would be in cold tier and the size would not be a
|
||||
// problem, which also avoid frequent hot to cold storage compaction.
|
||||
options.compaction_options_universal.max_size_amplification_percent = 400;
|
||||
Reopen(options);
|
||||
ApplyConfigChange(
|
||||
&options,
|
||||
{{"preclude_last_level_data_seconds", "10000"},
|
||||
{"last_level_temperature", "kCold"},
|
||||
{"compaction_options_universal.max_size_amplification_percent", "400"}});
|
||||
|
||||
// all data is hot, even they're in the last level
|
||||
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
@@ -1464,7 +1445,7 @@ TEST_F(PrecludeLastLevelTest, MigrationFromPreserveTimeAutoCompaction) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(PrecludeLastLevelTest, MigrationFromPreserveTimePartial) {
|
||||
TEST_P(PrecludeLastLevelTest, MigrationFromPreserveTimePartial) {
|
||||
const int kNumTrigger = 4;
|
||||
const int kNumLevels = 7;
|
||||
const int kNumKeys = 100;
|
||||
@@ -1512,9 +1493,8 @@ TEST_F(PrecludeLastLevelTest, MigrationFromPreserveTimePartial) {
|
||||
}
|
||||
|
||||
// enable preclude feature
|
||||
options.preclude_last_level_data_seconds = 2000;
|
||||
options.last_level_temperature = Temperature::kCold;
|
||||
Reopen(options);
|
||||
ApplyConfigChange(&options, {{"preclude_last_level_data_seconds", "2000"},
|
||||
{"last_level_temperature", "kCold"}});
|
||||
|
||||
// Generate a sstable and trigger manual compaction
|
||||
ASSERT_OK(Put(Key(10), "value"));
|
||||
@@ -1532,7 +1512,7 @@ TEST_F(PrecludeLastLevelTest, MigrationFromPreserveTimePartial) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(PrecludeLastLevelTest, SmallPrecludeTime) {
|
||||
TEST_P(PrecludeLastLevelTest, SmallPrecludeTime) {
|
||||
const int kNumTrigger = 4;
|
||||
const int kNumLevels = 7;
|
||||
const int kNumKeys = 100;
|
||||
@@ -1585,13 +1565,203 @@ TEST_F(PrecludeLastLevelTest, SmallPrecludeTime) {
|
||||
Close();
|
||||
}
|
||||
|
||||
// Test Param: protection_bytes_per_key for WriteBatch
|
||||
TEST_P(PrecludeLastLevelTest, CheckInternalKeyRange) {
|
||||
// When compacting keys from the last level to penultimate level,
|
||||
// output to penultimate level should be within internal key range
|
||||
// of input files from penultimate level.
|
||||
// Set up:
|
||||
// L5:
|
||||
// File 1: DeleteRange[1, 3)@4, File 2: [3@5, 100@6]
|
||||
// L6:
|
||||
// File 3: [2@1, 3@2], File 4: [50@3]
|
||||
//
|
||||
// When File 1 and File 3 are being compacted,
|
||||
// Key(3) cannot be compacted up, otherwise it causes
|
||||
// inconsistency where File 3's Key(3) has a lower sequence number
|
||||
// than File 2's Key(3).
|
||||
const int kNumLevels = 7;
|
||||
auto options = CurrentOptions();
|
||||
options.env = mock_env_.get();
|
||||
options.last_level_temperature = Temperature::kCold;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.num_levels = kNumLevels;
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.max_subcompactions = 10;
|
||||
options.preserve_internal_time_seconds = 10000;
|
||||
DestroyAndReopen(options);
|
||||
// File 3
|
||||
ASSERT_OK(Put(Key(2), "val2"));
|
||||
ASSERT_OK(Put(Key(3), "val3"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(6);
|
||||
// File 4
|
||||
ASSERT_OK(Put(Key(50), "val50"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(6);
|
||||
|
||||
ApplyConfigChange(&options, {{"preclude_last_level_data_seconds", "10000"}});
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
|
||||
// File 1
|
||||
std::string start = Key(1);
|
||||
std::string end = Key(3);
|
||||
ASSERT_OK(db_->DeleteRange({}, db_->DefaultColumnFamily(), start, end));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(5);
|
||||
// File 2
|
||||
ASSERT_OK(Put(Key(3), "vall"));
|
||||
ASSERT_OK(Put(Key(100), "val100"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(5);
|
||||
|
||||
ASSERT_EQ("0,0,0,0,0,2,2", FilesPerLevel());
|
||||
|
||||
auto VerifyLogicalState = [&]() {
|
||||
// First with snapshot
|
||||
ASSERT_EQ("val2", Get(Key(2), snapshot));
|
||||
ASSERT_EQ("val3", Get(Key(3), snapshot));
|
||||
ASSERT_EQ("val50", Get(Key(50), snapshot));
|
||||
ASSERT_EQ("NOT_FOUND", Get(Key(100), snapshot));
|
||||
|
||||
// Then without snapshot
|
||||
ASSERT_EQ("NOT_FOUND", Get(Key(2)));
|
||||
ASSERT_EQ("vall", Get(Key(3)));
|
||||
ASSERT_EQ("val50", Get(Key(50)));
|
||||
ASSERT_EQ("val100", Get(Key(100)));
|
||||
};
|
||||
|
||||
VerifyLogicalState();
|
||||
|
||||
// Try to compact keys up
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
// Without internal key range checking, we get the following error:
|
||||
// Corruption: force_consistency_checks(DEBUG): VersionBuilder: L5 has
|
||||
// overlapping ranges: file #18 largest key: '6B6579303030303033' seq:102,
|
||||
// type:1 vs. file #15 smallest key: '6B6579303030303033' seq:104, type:1
|
||||
ASSERT_OK(CompactRange(cro, Key(1), Key(2)));
|
||||
|
||||
VerifyLogicalState();
|
||||
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_P(PrecludeLastLevelTest, RangeTombstoneSnapshotMigrateFromLast) {
|
||||
// Reproducer for issue originally described in
|
||||
// https://github.com/facebook/rocksdb/pull/9964/files#r1024449523
|
||||
if (!UseDynamicConfig()) {
|
||||
// Depends on config change while holding a snapshot
|
||||
return;
|
||||
}
|
||||
const int kNumLevels = 7;
|
||||
auto options = CurrentOptions();
|
||||
options.env = mock_env_.get();
|
||||
options.last_level_temperature = Temperature::kCold;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.num_levels = kNumLevels;
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.max_subcompactions = 10;
|
||||
options.preserve_internal_time_seconds = 30000;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Entries with much older write time
|
||||
ASSERT_OK(Put(Key(2), "val2"));
|
||||
ASSERT_OK(Put(Key(6), "val6"));
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun(
|
||||
[&] { mock_clock_->MockSleepForSeconds(static_cast<int>(1000)); });
|
||||
}
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
|
||||
ASSERT_OK(db_->DeleteRange({}, db_->DefaultColumnFamily(), Key(1), Key(5)));
|
||||
ASSERT_OK(Put(Key(1), "val1"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(6);
|
||||
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
|
||||
|
||||
ApplyConfigChange(&options, {{"preclude_last_level_data_seconds", "10000"}});
|
||||
|
||||
// To exercise the WithinPenultimateLevelOutputRange feature, we want files
|
||||
// around the middle file to be compacted on the penultimate level
|
||||
ASSERT_OK(Put(Key(0), "val0"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put(Key(3), "val3"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put(Key(7), "val7"));
|
||||
|
||||
// FIXME: ideally this wouldn't be necessary to get a seqno to time entry
|
||||
// into a later compaction to get data into the last level
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun(
|
||||
[&] { mock_clock_->MockSleepForSeconds(static_cast<int>(1000)); });
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(5);
|
||||
ASSERT_EQ("0,0,0,0,0,3,1", FilesPerLevel());
|
||||
|
||||
auto VerifyLogicalState = [&]() {
|
||||
// First with snapshot
|
||||
if (snapshot) {
|
||||
ASSERT_EQ("NOT_FOUND", Get(Key(0), snapshot));
|
||||
ASSERT_EQ("NOT_FOUND", Get(Key(1), snapshot));
|
||||
ASSERT_EQ("val2", Get(Key(2), snapshot));
|
||||
ASSERT_EQ("NOT_FOUND", Get(Key(3), snapshot));
|
||||
ASSERT_EQ("val6", Get(Key(6), snapshot));
|
||||
ASSERT_EQ("NOT_FOUND", Get(Key(7), snapshot));
|
||||
}
|
||||
|
||||
// Then without snapshot
|
||||
ASSERT_EQ("val0", Get(Key(0)));
|
||||
ASSERT_EQ("val1", Get(Key(1)));
|
||||
ASSERT_EQ("NOT_FOUND", Get(Key(2)));
|
||||
ASSERT_EQ("val3", Get(Key(3)));
|
||||
ASSERT_EQ("val6", Get(Key(6)));
|
||||
ASSERT_EQ("val7", Get(Key(7)));
|
||||
};
|
||||
|
||||
VerifyLogicalState();
|
||||
|
||||
// Try a limited range compaction
|
||||
// FIXME: this currently hits the "Unsafe to store Seq later than snapshot"
|
||||
// error. Needs to work safely for preclude option to be user mutable.
|
||||
// ASSERT_OK(CompactRange({}, Key(3), Key(4)));
|
||||
EXPECT_EQ("0,0,0,0,0,3,1", FilesPerLevel());
|
||||
VerifyLogicalState();
|
||||
|
||||
// Compact everything, but some data still goes to both penultimate and last
|
||||
// levels. A full-range compaction should be safe to "migrate" data from the
|
||||
// last level to penultimate (because of preclude setting change).
|
||||
ASSERT_OK(CompactRange({}, {}, {}));
|
||||
EXPECT_EQ("0,0,0,0,0,1,1", FilesPerLevel());
|
||||
VerifyLogicalState();
|
||||
// Key1 should have been migrated out of the last level
|
||||
auto& meta = *GetLevelFileMetadatas(6)[0];
|
||||
ASSERT_LT(Key(1), meta.smallest.user_key().ToString());
|
||||
|
||||
// Make data eligible for last level
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
snapshot = nullptr;
|
||||
mock_clock_->MockSleepForSeconds(static_cast<int>(10000));
|
||||
|
||||
ASSERT_OK(CompactRange({}, {}, {}));
|
||||
EXPECT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
|
||||
VerifyLogicalState();
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(PrecludeLastLevelTest, PrecludeLastLevelTest,
|
||||
::testing::Bool());
|
||||
|
||||
class TimedPutPrecludeLastLevelTest
|
||||
: public PrecludeLastLevelTest,
|
||||
: public PrecludeLastLevelTestBase,
|
||||
public testing::WithParamInterface<size_t> {
|
||||
public:
|
||||
TimedPutPrecludeLastLevelTest()
|
||||
: PrecludeLastLevelTest("timed_put_preclude_last_level_test") {}
|
||||
: PrecludeLastLevelTestBase("timed_put_preclude_last_level_test") {}
|
||||
|
||||
size_t ProtectionBytesPerKey() const { return GetParam(); }
|
||||
};
|
||||
|
||||
TEST_P(TimedPutPrecludeLastLevelTest, FastTrackTimedPutToLastLevel) {
|
||||
@@ -1609,7 +1779,7 @@ TEST_P(TimedPutPrecludeLastLevelTest, FastTrackTimedPutToLastLevel) {
|
||||
options.last_level_temperature = Temperature::kCold;
|
||||
DestroyAndReopen(options);
|
||||
WriteOptions wo;
|
||||
wo.protection_bytes_per_key = GetParam();
|
||||
wo.protection_bytes_per_key = ProtectionBytesPerKey();
|
||||
|
||||
Random rnd(301);
|
||||
|
||||
@@ -1663,7 +1833,7 @@ TEST_P(TimedPutPrecludeLastLevelTest, InterleavedTimedPutAndPut) {
|
||||
options.default_write_temperature = Temperature::kHot;
|
||||
DestroyAndReopen(options);
|
||||
WriteOptions wo;
|
||||
wo.protection_bytes_per_key = GetParam();
|
||||
wo.protection_bytes_per_key = ProtectionBytesPerKey();
|
||||
|
||||
// Start time: kMockStartTime = 10000000;
|
||||
ASSERT_OK(TimedPut(0, Key(0), "v0", kMockStartTime - 1 * 24 * 60 * 60, wo));
|
||||
@@ -1689,7 +1859,7 @@ TEST_P(TimedPutPrecludeLastLevelTest, PreserveTimedPutOnPenultimateLevel) {
|
||||
options.default_write_temperature = Temperature::kHot;
|
||||
DestroyAndReopen(options);
|
||||
WriteOptions wo;
|
||||
wo.protection_bytes_per_key = GetParam();
|
||||
wo.protection_bytes_per_key = ProtectionBytesPerKey();
|
||||
|
||||
// Creating a snapshot to manually control when preferred sequence number is
|
||||
// swapped in. An entry's preferred seqno won't get swapped in until it's
|
||||
@@ -1759,7 +1929,7 @@ TEST_P(TimedPutPrecludeLastLevelTest, AutoTriggerCompaction) {
|
||||
options.table_properties_collector_factories.push_back(factory);
|
||||
DestroyAndReopen(options);
|
||||
WriteOptions wo;
|
||||
wo.protection_bytes_per_key = GetParam();
|
||||
wo.protection_bytes_per_key = ProtectionBytesPerKey();
|
||||
|
||||
Random rnd(301);
|
||||
|
||||
@@ -1814,7 +1984,7 @@ TEST_P(TimedPutPrecludeLastLevelTest, AutoTriggerCompaction) {
|
||||
INSTANTIATE_TEST_CASE_P(TimedPutPrecludeLastLevelTest,
|
||||
TimedPutPrecludeLastLevelTest, ::testing::Values(0, 8));
|
||||
|
||||
TEST_F(PrecludeLastLevelTest, LastLevelOnlyCompactionPartial) {
|
||||
TEST_P(PrecludeLastLevelTest, LastLevelOnlyCompactionPartial) {
|
||||
const int kNumTrigger = 4;
|
||||
const int kNumLevels = 7;
|
||||
const int kNumKeys = 100;
|
||||
@@ -1845,9 +2015,8 @@ TEST_F(PrecludeLastLevelTest, LastLevelOnlyCompactionPartial) {
|
||||
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
|
||||
|
||||
// enable preclude feature
|
||||
options.preclude_last_level_data_seconds = 2000;
|
||||
options.last_level_temperature = Temperature::kCold;
|
||||
Reopen(options);
|
||||
ApplyConfigChange(&options, {{"preclude_last_level_data_seconds", "2000"},
|
||||
{"last_level_temperature", "kCold"}});
|
||||
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
@@ -1878,21 +2047,30 @@ TEST_F(PrecludeLastLevelTest, LastLevelOnlyCompactionPartial) {
|
||||
Close();
|
||||
}
|
||||
|
||||
class PrecludeLastLevelTestWithParms
|
||||
: public PrecludeLastLevelTest,
|
||||
public testing::WithParamInterface<bool> {
|
||||
class PrecludeLastLevelOptionalTest
|
||||
: public PrecludeLastLevelTestBase,
|
||||
public testing::WithParamInterface<std::tuple<bool, bool>> {
|
||||
public:
|
||||
PrecludeLastLevelTestWithParms() : PrecludeLastLevelTest() {}
|
||||
bool UseDynamicConfig() const { return std::get<0>(GetParam()); }
|
||||
|
||||
void ApplyConfigChange(
|
||||
Options* options,
|
||||
const std::unordered_map<std::string, std::string>& config_change,
|
||||
const std::unordered_map<std::string, std::string>& db_config_change =
|
||||
{}) {
|
||||
ApplyConfigChangeImpl(UseDynamicConfig(), options, config_change,
|
||||
db_config_change);
|
||||
}
|
||||
|
||||
bool EnablePrecludeLastLevel() const { return std::get<1>(GetParam()); }
|
||||
};
|
||||
|
||||
TEST_P(PrecludeLastLevelTestWithParms, LastLevelOnlyCompactionNoPreclude) {
|
||||
TEST_P(PrecludeLastLevelOptionalTest, LastLevelOnlyCompactionNoPreclude) {
|
||||
const int kNumTrigger = 4;
|
||||
const int kNumLevels = 7;
|
||||
const int kNumKeys = 100;
|
||||
const int kKeyPerSec = 10;
|
||||
|
||||
bool enable_preclude_last_level = GetParam();
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.preserve_internal_time_seconds = 2000;
|
||||
@@ -1950,7 +2128,7 @@ TEST_P(PrecludeLastLevelTestWithParms, LastLevelOnlyCompactionNoPreclude) {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"UniversalCompactionBuilder::PickCompaction:Return", [&](void* arg) {
|
||||
auto compaction = static_cast<Compaction*>(arg);
|
||||
if (enable_preclude_last_level && is_manual_compaction_running) {
|
||||
if (EnablePrecludeLastLevel() && is_manual_compaction_running) {
|
||||
ASSERT_TRUE(compaction == nullptr);
|
||||
verified_compaction_order = true;
|
||||
} else {
|
||||
@@ -1977,12 +2155,11 @@ TEST_P(PrecludeLastLevelTestWithParms, LastLevelOnlyCompactionNoPreclude) {
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// only enable if the Parameter is true
|
||||
if (enable_preclude_last_level) {
|
||||
options.preclude_last_level_data_seconds = 2000;
|
||||
}
|
||||
options.max_background_jobs = 8;
|
||||
options.last_level_temperature = Temperature::kCold;
|
||||
Reopen(options);
|
||||
ApplyConfigChange(&options,
|
||||
{{"preclude_last_level_data_seconds",
|
||||
EnablePrecludeLastLevel() ? "2000" : "0"},
|
||||
{"last_level_temperature", "kCold"}},
|
||||
{{"max_background_jobs", "8"}});
|
||||
|
||||
auto manual_compaction_thread = port::Thread([this]() {
|
||||
CompactRangeOptions cro;
|
||||
@@ -2011,7 +2188,7 @@ TEST_P(PrecludeLastLevelTestWithParms, LastLevelOnlyCompactionNoPreclude) {
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
if (enable_preclude_last_level) {
|
||||
if (EnablePrecludeLastLevel()) {
|
||||
ASSERT_NE("0,0,0,0,0,1,1", FilesPerLevel());
|
||||
} else {
|
||||
ASSERT_EQ("0,0,0,0,0,1,1", FilesPerLevel());
|
||||
@@ -2025,7 +2202,7 @@ TEST_P(PrecludeLastLevelTestWithParms, LastLevelOnlyCompactionNoPreclude) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_P(PrecludeLastLevelTestWithParms, PeriodicCompactionToPenultimateLevel) {
|
||||
TEST_P(PrecludeLastLevelOptionalTest, PeriodicCompactionToPenultimateLevel) {
|
||||
// Test the last level only periodic compaction should also be blocked by an
|
||||
// ongoing compaction in penultimate level if tiered compaction is enabled
|
||||
// otherwise, the periodic compaction should just run for the last level.
|
||||
@@ -2035,8 +2212,6 @@ TEST_P(PrecludeLastLevelTestWithParms, PeriodicCompactionToPenultimateLevel) {
|
||||
const int kKeyPerSec = 1;
|
||||
const int kNumKeys = 100;
|
||||
|
||||
bool enable_preclude_last_level = GetParam();
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.preserve_internal_time_seconds = 20000;
|
||||
@@ -2063,12 +2238,11 @@ TEST_P(PrecludeLastLevelTestWithParms, PeriodicCompactionToPenultimateLevel) {
|
||||
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
|
||||
|
||||
// enable preclude feature
|
||||
if (enable_preclude_last_level) {
|
||||
options.preclude_last_level_data_seconds = 20000;
|
||||
}
|
||||
options.max_background_jobs = 8;
|
||||
options.last_level_temperature = Temperature::kCold;
|
||||
Reopen(options);
|
||||
ApplyConfigChange(&options,
|
||||
{{"preclude_last_level_data_seconds",
|
||||
EnablePrecludeLastLevel() ? "2000" : "0"},
|
||||
{"last_level_temperature", "kCold"}},
|
||||
{{"max_background_jobs", "8"}});
|
||||
|
||||
std::atomic_bool is_size_ratio_compaction_running = false;
|
||||
std::atomic_bool verified_last_level_compaction = false;
|
||||
@@ -2093,7 +2267,7 @@ TEST_P(PrecludeLastLevelTestWithParms, PeriodicCompactionToPenultimateLevel) {
|
||||
auto compaction = static_cast<Compaction*>(arg);
|
||||
|
||||
if (is_size_ratio_compaction_running) {
|
||||
if (enable_preclude_last_level) {
|
||||
if (EnablePrecludeLastLevel()) {
|
||||
ASSERT_TRUE(compaction == nullptr);
|
||||
} else {
|
||||
ASSERT_TRUE(compaction != nullptr);
|
||||
@@ -2151,8 +2325,10 @@ TEST_P(PrecludeLastLevelTestWithParms, PeriodicCompactionToPenultimateLevel) {
|
||||
Close();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(PrecludeLastLevelTestWithParms,
|
||||
PrecludeLastLevelTestWithParms, testing::Bool());
|
||||
INSTANTIATE_TEST_CASE_P(PrecludeLastLevelOptionalTest,
|
||||
PrecludeLastLevelOptionalTest,
|
||||
::testing::Combine(::testing::Bool(),
|
||||
::testing::Bool()));
|
||||
|
||||
// partition the SST into 3 ranges [0, 19] [20, 39] [40, ...]
|
||||
class ThreeRangesPartitioner : public SstPartitioner {
|
||||
@@ -2196,7 +2372,7 @@ class ThreeRangesPartitionerFactory : public SstPartitionerFactory {
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(PrecludeLastLevelTest, PartialPenultimateLevelCompaction) {
|
||||
TEST_P(PrecludeLastLevelTest, PartialPenultimateLevelCompaction) {
|
||||
const int kNumTrigger = 4;
|
||||
const int kNumLevels = 7;
|
||||
const int kKeyPerSec = 10;
|
||||
@@ -2207,6 +2383,7 @@ TEST_F(PrecludeLastLevelTest, PartialPenultimateLevelCompaction) {
|
||||
options.level0_file_num_compaction_trigger = kNumTrigger;
|
||||
options.preserve_internal_time_seconds = 10000;
|
||||
options.num_levels = kNumLevels;
|
||||
options.statistics = CreateDBStatistics();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
@@ -2244,11 +2421,10 @@ TEST_F(PrecludeLastLevelTest, PartialPenultimateLevelCompaction) {
|
||||
// L6: [0, 299]
|
||||
ASSERT_EQ("0,0,0,0,0,3,1", FilesPerLevel());
|
||||
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
// enable tiered storage feature
|
||||
options.preclude_last_level_data_seconds = 10000;
|
||||
options.last_level_temperature = Temperature::kCold;
|
||||
options.statistics = CreateDBStatistics();
|
||||
Reopen(options);
|
||||
ApplyConfigChange(&options, {{"preclude_last_level_data_seconds", "10000"},
|
||||
{"last_level_temperature", "kCold"}});
|
||||
|
||||
ColumnFamilyMetaData meta;
|
||||
db_->GetColumnFamilyMetaData(&meta);
|
||||
@@ -2294,7 +2470,7 @@ TEST_F(PrecludeLastLevelTest, PartialPenultimateLevelCompaction) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(PrecludeLastLevelTest, RangeDelsCauseFileEndpointsToOverlap) {
|
||||
TEST_P(PrecludeLastLevelTest, RangeDelsCauseFileEndpointsToOverlap) {
|
||||
const int kNumLevels = 7;
|
||||
const int kSecondsPerKey = 10;
|
||||
const int kNumFiles = 3;
|
||||
@@ -2433,12 +2609,25 @@ TEST_F(PrecludeLastLevelTest, RangeDelsCauseFileEndpointsToOverlap) {
|
||||
|
||||
// Tests DBIter::GetProperty("rocksdb.iterator.write-time") return a data's
|
||||
// approximate write unix time.
|
||||
// Test Param:
|
||||
// 1) use tailing iterator or regular iterator (when it applies)
|
||||
class IteratorWriteTimeTest : public PrecludeLastLevelTest,
|
||||
public testing::WithParamInterface<bool> {
|
||||
class IteratorWriteTimeTest
|
||||
: public PrecludeLastLevelTestBase,
|
||||
public testing::WithParamInterface<std::tuple<bool, bool>> {
|
||||
public:
|
||||
IteratorWriteTimeTest() : PrecludeLastLevelTest("iterator_write_time_test") {}
|
||||
IteratorWriteTimeTest()
|
||||
: PrecludeLastLevelTestBase("iterator_write_time_test") {}
|
||||
|
||||
bool UseTailingIterator() const { return std::get<0>(GetParam()); }
|
||||
|
||||
bool UseDynamicConfig() const { return std::get<1>(GetParam()); }
|
||||
|
||||
void ApplyConfigChange(
|
||||
Options* options,
|
||||
const std::unordered_map<std::string, std::string>& config_change,
|
||||
const std::unordered_map<std::string, std::string>& db_config_change =
|
||||
{}) {
|
||||
ApplyConfigChangeImpl(UseDynamicConfig(), options, config_change,
|
||||
db_config_change);
|
||||
}
|
||||
|
||||
uint64_t VerifyKeyAndGetWriteTime(Iterator* iter,
|
||||
const std::string& expected_key) {
|
||||
@@ -2478,10 +2667,14 @@ TEST_P(IteratorWriteTimeTest, ReadFromMemtables) {
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.env = mock_env_.get();
|
||||
options.level0_file_num_compaction_trigger = kNumTrigger;
|
||||
options.preserve_internal_time_seconds = 10000;
|
||||
options.num_levels = kNumLevels;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// While there are issues with tracking seqno 0
|
||||
ASSERT_OK(Delete("something_to_bump_seqno"));
|
||||
|
||||
ApplyConfigChange(&options, {{"preserve_internal_time_seconds", "10000"}});
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < kNumKeys; i++) {
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun(
|
||||
@@ -2495,7 +2688,7 @@ TEST_P(IteratorWriteTimeTest, ReadFromMemtables) {
|
||||
}
|
||||
|
||||
ReadOptions ropts;
|
||||
ropts.tailing = GetParam();
|
||||
ropts.tailing = UseTailingIterator();
|
||||
int i;
|
||||
|
||||
// Forward iteration
|
||||
@@ -2512,6 +2705,7 @@ TEST_P(IteratorWriteTimeTest, ReadFromMemtables) {
|
||||
start_time + kSecondsPerRecording * (i + 1));
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(kNumKeys, i);
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
@@ -2531,12 +2725,12 @@ TEST_P(IteratorWriteTimeTest, ReadFromMemtables) {
|
||||
}
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(-1, i);
|
||||
}
|
||||
|
||||
// Reopen the DB and disable the seqno to time recording, data with user
|
||||
// specified write time can still get a write time before it's flushed.
|
||||
options.preserve_internal_time_seconds = 0;
|
||||
DestroyAndReopen(options);
|
||||
// Disable the seqno to time recording. Data with user specified write time
|
||||
// can still get a write time before it's flushed.
|
||||
ApplyConfigChange(&options, {{"preserve_internal_time_seconds", "0"}});
|
||||
ASSERT_OK(TimedPut(Key(kKeyWithWriteTime), rnd.RandomString(100),
|
||||
kUserSpecifiedWriteTime));
|
||||
{
|
||||
@@ -2572,10 +2766,14 @@ TEST_P(IteratorWriteTimeTest, ReadFromSstFile) {
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.env = mock_env_.get();
|
||||
options.level0_file_num_compaction_trigger = kNumTrigger;
|
||||
options.preserve_internal_time_seconds = 10000;
|
||||
options.num_levels = kNumLevels;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// While there are issues with tracking seqno 0
|
||||
ASSERT_OK(Delete("something_to_bump_seqno"));
|
||||
|
||||
ApplyConfigChange(&options, {{"preserve_internal_time_seconds", "10000"}});
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < kNumKeys; i++) {
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun(
|
||||
@@ -2590,7 +2788,7 @@ TEST_P(IteratorWriteTimeTest, ReadFromSstFile) {
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
ReadOptions ropts;
|
||||
ropts.tailing = GetParam();
|
||||
ropts.tailing = UseTailingIterator();
|
||||
std::string prop;
|
||||
int i;
|
||||
|
||||
@@ -2613,6 +2811,7 @@ TEST_P(IteratorWriteTimeTest, ReadFromSstFile) {
|
||||
}
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(kNumKeys, i);
|
||||
}
|
||||
|
||||
// Backward iteration
|
||||
@@ -2632,12 +2831,12 @@ TEST_P(IteratorWriteTimeTest, ReadFromSstFile) {
|
||||
}
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(-1, i);
|
||||
}
|
||||
|
||||
// Reopen the DB and disable the seqno to time recording. Data retrieved from
|
||||
// SST files still have write time available.
|
||||
options.preserve_internal_time_seconds = 0;
|
||||
DestroyAndReopen(options);
|
||||
// Disable the seqno to time recording. Data retrieved from SST files still
|
||||
// have write time available.
|
||||
ApplyConfigChange(&options, {{"preserve_internal_time_seconds", "0"}});
|
||||
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun(
|
||||
[&] { mock_clock_->MockSleepForSeconds(kSecondsPerRecording); });
|
||||
@@ -2663,6 +2862,7 @@ TEST_P(IteratorWriteTimeTest, ReadFromSstFile) {
|
||||
start_time + kSecondsPerRecording * (i + 1));
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(kNumKeys, i);
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
@@ -2686,6 +2886,7 @@ TEST_P(IteratorWriteTimeTest, ReadFromSstFile) {
|
||||
VerifyKeyAndWriteTime(iter.get(), Key(i), 0);
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(kNumKeys, i);
|
||||
}
|
||||
Close();
|
||||
}
|
||||
@@ -2699,11 +2900,12 @@ TEST_P(IteratorWriteTimeTest, MergeReturnsBaseValueWriteTime) {
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.env = mock_env_.get();
|
||||
options.level0_file_num_compaction_trigger = kNumTrigger;
|
||||
options.preserve_internal_time_seconds = 10000;
|
||||
options.num_levels = kNumLevels;
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ApplyConfigChange(&options, {{"preserve_internal_time_seconds", "10000"}});
|
||||
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun(
|
||||
[&] { mock_clock_->MockSleepForSeconds(kSecondsPerRecording); });
|
||||
ASSERT_OK(Put("foo", "fv1"));
|
||||
@@ -2714,7 +2916,7 @@ TEST_P(IteratorWriteTimeTest, MergeReturnsBaseValueWriteTime) {
|
||||
ASSERT_OK(Merge("foo", "bv1"));
|
||||
|
||||
ReadOptions ropts;
|
||||
ropts.tailing = GetParam();
|
||||
ropts.tailing = UseTailingIterator();
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(dbfull()->NewIterator(ropts));
|
||||
iter->SeekToFirst();
|
||||
@@ -2732,7 +2934,7 @@ TEST_P(IteratorWriteTimeTest, MergeReturnsBaseValueWriteTime) {
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(IteratorWriteTimeTest, IteratorWriteTimeTest,
|
||||
testing::Bool());
|
||||
testing::Combine(testing::Bool(), testing::Bool()));
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ Status VerifySstFileChecksumInternal(const Options& options,
|
||||
options.block_protection_bytes_per_key, false /* skip_filters */,
|
||||
!kImmortal, false /* force_direct_prefetch */, -1 /* level */);
|
||||
reader_options.largest_seqno = largest_seqno;
|
||||
s = ioptions.table_factory->NewTableReader(
|
||||
s = options.table_factory->NewTableReader(
|
||||
read_options, reader_options, std::move(file_reader), file_size,
|
||||
&table_reader, false /* prefetch_index_and_filter_in_cache */);
|
||||
if (!s.ok()) {
|
||||
|
||||
+62
-27
@@ -563,7 +563,7 @@ TEST_P(DBBlockCacheTest1, WarmCacheWithBlocksDuringFlush) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlockCacheTest, DynamicallyWarmCacheDuringFlush) {
|
||||
TEST_F(DBBlockCacheTest, DynamicOptions) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
@@ -578,39 +578,74 @@ TEST_F(DBBlockCacheTest, DynamicallyWarmCacheDuringFlush) {
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::string value(kValueSize, 'a');
|
||||
auto st = options.statistics;
|
||||
|
||||
for (size_t i = 1; i <= 5; i++) {
|
||||
ASSERT_OK(Put(std::to_string(i), value));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(1,
|
||||
options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
size_t i = 1;
|
||||
ASSERT_OK(Put(std::to_string(i), value));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(1, st->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
|
||||
ASSERT_EQ(value, Get(std::to_string(i)));
|
||||
ASSERT_EQ(0,
|
||||
options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
ASSERT_EQ(
|
||||
0, options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
ASSERT_EQ(1,
|
||||
options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
}
|
||||
ASSERT_EQ(value, Get(std::to_string(i)));
|
||||
ASSERT_EQ(0, st->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
ASSERT_EQ(0, st->getAndResetTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
ASSERT_EQ(1, st->getAndResetTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
|
||||
++i;
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"block_based_table_factory", "{prepopulate_block_cache=kDisable;}"}}));
|
||||
|
||||
for (size_t i = 6; i <= kNumBlocks; i++) {
|
||||
ASSERT_OK(Put(std::to_string(i), value));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(0,
|
||||
options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
ASSERT_OK(Put(std::to_string(i), value));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(0, st->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
|
||||
ASSERT_EQ(value, Get(std::to_string(i)));
|
||||
ASSERT_EQ(1,
|
||||
options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
ASSERT_EQ(
|
||||
1, options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
ASSERT_EQ(0,
|
||||
options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
}
|
||||
ASSERT_EQ(value, Get(std::to_string(i)));
|
||||
ASSERT_EQ(1, st->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
ASSERT_EQ(1, st->getAndResetTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
ASSERT_EQ(0, st->getAndResetTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
|
||||
++i;
|
||||
ASSERT_OK(dbfull()->SetOptions({{"block_based_table_factory",
|
||||
"{prepopulate_block_cache=kFlushOnly;}"}}));
|
||||
|
||||
ASSERT_OK(Put(std::to_string(i), value));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(1, st->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
|
||||
ASSERT_EQ(value, Get(std::to_string(i)));
|
||||
ASSERT_EQ(0, st->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
ASSERT_EQ(0, st->getAndResetTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
ASSERT_EQ(1, st->getAndResetTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
|
||||
++i;
|
||||
// NOT YET SUPPORTED
|
||||
// FIXME: find a way to make this fail again (until well supported)
|
||||
// ASSERT_NOK(dbfull()->SetOptions(
|
||||
// {{"block_based_table_factory", "{block_cache=null;}"}}));
|
||||
|
||||
// ASSERT_OK(Put(std::to_string(i), value));
|
||||
// ASSERT_OK(Flush());
|
||||
// ASSERT_EQ(0, st->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
|
||||
// ASSERT_EQ(value, Get(std::to_string(i)));
|
||||
// ASSERT_EQ(0, st->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
// ASSERT_EQ(0, st->getAndResetTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
// ASSERT_EQ(0, st->getAndResetTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
|
||||
// ++i;
|
||||
|
||||
// NOT YET SUPPORTED
|
||||
// FIXME: find a way to make this fail again (until well supported)
|
||||
// ASSERT_NOK(dbfull()->SetOptions(
|
||||
// {{"block_based_table_factory", "{block_cache=1M;}"}}));
|
||||
|
||||
// ASSERT_OK(Put(std::to_string(i), value));
|
||||
// ASSERT_OK(Flush());
|
||||
// ASSERT_EQ(1, st->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
|
||||
// ASSERT_EQ(value, Get(std::to_string(i)));
|
||||
// ASSERT_EQ(0, st->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
// ASSERT_EQ(0, st->getAndResetTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
// ASSERT_EQ(1, st->getAndResetTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1975,10 +1975,24 @@ TEST_F(DBBloomFilterTest, MutatingRibbonFilterPolicy) {
|
||||
if (configs.empty()) {
|
||||
break;
|
||||
}
|
||||
std::string factory_field =
|
||||
(v[0] & 1) ? "table_factory" : "block_based_table_factory";
|
||||
|
||||
// Some irrelevant SetOptions to be sure they don't interfere
|
||||
ASSERT_OK(db_->SetOptions({{"level0_file_num_compaction_trigger", "10"}}));
|
||||
ASSERT_OK(
|
||||
db_->SetOptions({{"table_factory.filter_policy.bloom_before_level",
|
||||
db_->SetOptions({{"block_based_table_factory", "{block_size=1234}"}}));
|
||||
ASSERT_OK(db_->SetOptions({{factory_field + ".block_size", "12345"}}));
|
||||
|
||||
// Test the mutable field we're interested in
|
||||
ASSERT_OK(
|
||||
db_->SetOptions({{factory_field + ".filter_policy.bloom_before_level",
|
||||
configs.back().first}}));
|
||||
// FilterPolicy pointer should not have changed
|
||||
ASSERT_EQ(db_->GetOptions()
|
||||
.table_factory->GetOptions<BlockBasedTableOptions>()
|
||||
->filter_policy.get(),
|
||||
table_options.filter_policy.get());
|
||||
|
||||
// Ensure original object is mutated
|
||||
std::string val;
|
||||
@@ -1991,6 +2005,59 @@ TEST_F(DBBloomFilterTest, MutatingRibbonFilterPolicy) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBloomFilterTest, MutableFilterPolicy) {
|
||||
// Test that BlockBasedTableOptions::filter_policy is mutable (replaceable)
|
||||
// with SetOptions.
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.statistics = CreateDBStatistics();
|
||||
auto& stats = *options.statistics;
|
||||
BlockBasedTableOptions table_options;
|
||||
// First config, to make sure there's no issues with this shared ptr
|
||||
// etc. when the DB switches filter policies.
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
|
||||
double expected_bpk = 10.0;
|
||||
// Other configs to try
|
||||
std::vector<std::pair<std::string, double>> configs = {
|
||||
{"ribbonfilter:10:-1", 7.0}, {"bloomfilter:5", 5.0}, {"nullptr", 0.0}};
|
||||
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.level0_file_num_compaction_trigger =
|
||||
static_cast<int>(configs.size()) + 2;
|
||||
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
char v[] = "a";
|
||||
|
||||
for (;; ++(v[0])) {
|
||||
const int maxKey = 8000;
|
||||
for (int i = 0; i < maxKey; i++) {
|
||||
ASSERT_OK(Put(Key(i), v));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
for (int i = 0; i < maxKey; i++) {
|
||||
ASSERT_EQ(Get(Key(i)), v);
|
||||
}
|
||||
|
||||
uint64_t filter_bytes =
|
||||
stats.getAndResetTickerCount(BLOCK_CACHE_FILTER_BYTES_INSERT);
|
||||
|
||||
EXPECT_NEAR(filter_bytes * 8.0 / maxKey, expected_bpk, 0.3);
|
||||
|
||||
if (configs.empty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
ASSERT_OK(
|
||||
db_->SetOptions({{"block_based_table_factory",
|
||||
"{filter_policy=" + configs.back().first + "}"}}));
|
||||
expected_bpk = configs.back().second;
|
||||
configs.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
class SliceTransformLimitedDomain : public SliceTransform {
|
||||
const char* Name() const override { return "SliceTransformLimitedDomain"; }
|
||||
|
||||
|
||||
@@ -10623,6 +10623,97 @@ TEST_F(DBCompactionTest, ReleaseCompactionDuringManifestWrite) {
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, RecordNewestKeyTimeForTtlCompaction) {
|
||||
Options options;
|
||||
SetTimeElapseOnlySleepOnReopen(&options);
|
||||
options.env = CurrentOptions().env;
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
options.write_buffer_size = 10 << 10; // 10KB
|
||||
options.arena_block_size = 4096;
|
||||
options.compression = kNoCompression;
|
||||
options.create_if_missing = true;
|
||||
options.compaction_options_fifo.allow_compaction = false;
|
||||
options.num_levels = 1;
|
||||
env_->SetMockSleep();
|
||||
options.env = env_;
|
||||
options.ttl = 1 * 60 * 60; // 1 hour
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
// Generate and flush 4 files, each about 10KB
|
||||
// Compaction is manually disabled at this point so we can check
|
||||
// each file's newest_key_time
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
ASSERT_OK(Put(std::to_string(i * 20 + j), rnd.RandomString(980)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
env_->MockSleepForSeconds(5);
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 4);
|
||||
|
||||
// Check that we are populating newest_key_time on flush
|
||||
std::vector<FileMetaData*> file_metadatas = GetLevelFileMetadatas(0);
|
||||
ASSERT_EQ(file_metadatas.size(), 4);
|
||||
uint64_t first_newest_key_time =
|
||||
file_metadatas[0]->fd.table_reader->GetTableProperties()->newest_key_time;
|
||||
ASSERT_NE(first_newest_key_time, kUnknownNewestKeyTime);
|
||||
// Check that the newest_key_times are in expected ordering
|
||||
uint64_t prev_newest_key_time = first_newest_key_time;
|
||||
for (size_t idx = 1; idx < file_metadatas.size(); idx++) {
|
||||
uint64_t newest_key_time = file_metadatas[idx]
|
||||
->fd.table_reader->GetTableProperties()
|
||||
->newest_key_time;
|
||||
|
||||
ASSERT_LT(newest_key_time, prev_newest_key_time);
|
||||
prev_newest_key_time = newest_key_time;
|
||||
ASSERT_EQ(newest_key_time, file_metadatas[idx]
|
||||
->fd.table_reader->GetTableProperties()
|
||||
->creation_time);
|
||||
}
|
||||
// The delta between the first and last newest_key_times is 15s
|
||||
uint64_t last_newest_key_time = prev_newest_key_time;
|
||||
ASSERT_EQ(15, first_newest_key_time - last_newest_key_time);
|
||||
|
||||
// After compaction, the newest_key_time of the output file should be the max
|
||||
// of the input files
|
||||
options.compaction_options_fifo.allow_compaction = true;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 1);
|
||||
file_metadatas = GetLevelFileMetadatas(0);
|
||||
ASSERT_EQ(file_metadatas.size(), 1);
|
||||
ASSERT_EQ(
|
||||
file_metadatas[0]->fd.table_reader->GetTableProperties()->newest_key_time,
|
||||
first_newest_key_time);
|
||||
// Contrast newest_key_time with creation_time, which records the oldest
|
||||
// ancestor time (15s older than newest_key_time)
|
||||
ASSERT_EQ(
|
||||
file_metadatas[0]->fd.table_reader->GetTableProperties()->creation_time,
|
||||
last_newest_key_time);
|
||||
ASSERT_EQ(file_metadatas[0]->oldest_ancester_time, last_newest_key_time);
|
||||
|
||||
// Make sure TTL of 5s causes compaction
|
||||
env_->MockSleepForSeconds(6);
|
||||
|
||||
// The oldest input file is older than 15s
|
||||
// However the newest of the compaction input files is younger than 15s, so
|
||||
// we don't compact
|
||||
ASSERT_OK(dbfull()->SetOptions({{"ttl", "15"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().ttl, 15);
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 1);
|
||||
|
||||
// Now even the youngest input file is too old
|
||||
ASSERT_OK(dbfull()->SetOptions({{"ttl", "5"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().ttl, 5);
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+159
-71
@@ -659,8 +659,9 @@ Status DBImpl::CloseHelper() {
|
||||
// We need to release them before the block cache is destroyed. The block
|
||||
// cache may be destroyed inside versions_.reset(), when column family data
|
||||
// list is destroyed, so leaving handles in table cache after
|
||||
// versions_.reset() may cause issues.
|
||||
// Here we clean all unreferenced handles in table cache.
|
||||
// versions_.reset() may cause issues. Here we clean all unreferenced handles
|
||||
// in table cache, and (for certain builds/conditions) assert that no obsolete
|
||||
// files are hanging around unreferenced (leak) in the table/blob file cache.
|
||||
// Now we assume all user queries have finished, so only version set itself
|
||||
// can possibly hold the blocks from block cache. After releasing unreferenced
|
||||
// handles here, only handles held by version set left and inside
|
||||
@@ -668,6 +669,9 @@ Status DBImpl::CloseHelper() {
|
||||
// time a handle is released, we erase it from the cache too. By doing that,
|
||||
// we can guarantee that after versions_.reset(), table cache is empty
|
||||
// so the cache can be safely destroyed.
|
||||
#ifndef NDEBUG
|
||||
TEST_VerifyNoObsoleteFilesCached(/*db_mutex_already_held=*/true);
|
||||
#endif // !NDEBUG
|
||||
table_cache_->EraseUnRefEntries();
|
||||
|
||||
for (auto& txn_entry : recovered_transactions_) {
|
||||
@@ -843,20 +847,22 @@ Status DBImpl::RegisterRecordSeqnoTimeWorker(const ReadOptions& read_options,
|
||||
|
||||
uint64_t min_preserve_seconds = std::numeric_limits<uint64_t>::max();
|
||||
uint64_t max_preserve_seconds = std::numeric_limits<uint64_t>::min();
|
||||
bool mapping_was_empty = false;
|
||||
std::vector<SuperVersionContext> sv_contexts;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
auto& mopts = *cfd->GetLatestMutableCFOptions();
|
||||
// preserve time is the max of 2 options.
|
||||
uint64_t preserve_seconds =
|
||||
std::max(cfd->ioptions()->preserve_internal_time_seconds,
|
||||
cfd->ioptions()->preclude_last_level_data_seconds);
|
||||
std::max(mopts.preserve_internal_time_seconds,
|
||||
mopts.preclude_last_level_data_seconds);
|
||||
if (!cfd->IsDropped() && preserve_seconds > 0) {
|
||||
min_preserve_seconds = std::min(preserve_seconds, min_preserve_seconds);
|
||||
max_preserve_seconds = std::max(preserve_seconds, max_preserve_seconds);
|
||||
}
|
||||
}
|
||||
size_t old_mapping_size = seqno_to_time_mapping_.Size();
|
||||
if (min_preserve_seconds == std::numeric_limits<uint64_t>::max()) {
|
||||
// Don't track
|
||||
seqno_to_time_mapping_.SetCapacity(0);
|
||||
@@ -868,9 +874,17 @@ Status DBImpl::RegisterRecordSeqnoTimeWorker(const ReadOptions& read_options,
|
||||
seqno_to_time_mapping_.SetCapacity(cap);
|
||||
seqno_to_time_mapping_.SetMaxTimeSpan(max_preserve_seconds);
|
||||
}
|
||||
mapping_was_empty = seqno_to_time_mapping_.Empty();
|
||||
if (old_mapping_size != seqno_to_time_mapping_.Size()) {
|
||||
InstallSeqnoToTimeMappingInSV(&sv_contexts);
|
||||
}
|
||||
}
|
||||
|
||||
// clean up outside db mutex
|
||||
for (SuperVersionContext& sv_context : sv_contexts) {
|
||||
sv_context.Clean();
|
||||
}
|
||||
sv_contexts.clear();
|
||||
|
||||
uint64_t seqno_time_cadence = 0;
|
||||
if (min_preserve_seconds != std::numeric_limits<uint64_t>::max()) {
|
||||
// round up to 1 when the time_duration is smaller than
|
||||
@@ -910,8 +924,6 @@ Status DBImpl::RegisterRecordSeqnoTimeWorker(const ReadOptions& read_options,
|
||||
assert(!is_new_db || last_seqno_zero);
|
||||
if (is_new_db && last_seqno_zero) {
|
||||
// Pre-allocate seqnos and pre-populate historical mapping
|
||||
assert(mapping_was_empty);
|
||||
|
||||
// We can simply modify these, before writes are allowed
|
||||
constexpr uint64_t kMax = kMaxSeqnoTimePairsPerSST;
|
||||
versions_->SetLastAllocatedSequence(kMax);
|
||||
@@ -936,9 +948,10 @@ Status DBImpl::RegisterRecordSeqnoTimeWorker(const ReadOptions& read_options,
|
||||
|
||||
// Pre-populate mappings for reserved sequence numbers.
|
||||
RecordSeqnoToTimeMapping(max_preserve_seconds);
|
||||
} else if (mapping_was_empty) {
|
||||
} else {
|
||||
if (!last_seqno_zero) {
|
||||
// Ensure at least one mapping (or log a warning)
|
||||
// Ensure at least one mapping (or log a warning), and
|
||||
// an updated entry whenever relevant SetOptions is called
|
||||
RecordSeqnoToTimeMapping(/*populate_historical_seconds=*/0);
|
||||
} else {
|
||||
// FIXME (see limitation described above)
|
||||
@@ -1153,6 +1166,13 @@ void DBImpl::DumpStats() {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* table_factory =
|
||||
cfd->GetCurrentMutableCFOptions()->table_factory.get();
|
||||
assert(table_factory != nullptr);
|
||||
// FIXME: need to a shared_ptr if/when block_cache is going to be mutable
|
||||
Cache* cache =
|
||||
table_factory->GetOptions<Cache>(TableFactory::kBlockCacheOpts());
|
||||
|
||||
// Release DB mutex for gathering cache entry stats. Pass over all
|
||||
// column families for this first so that other stats are dumped
|
||||
// near-atomically.
|
||||
@@ -1161,10 +1181,6 @@ void DBImpl::DumpStats() {
|
||||
|
||||
// Probe block cache for problems (if not already via another CF)
|
||||
if (immutable_db_options_.info_log) {
|
||||
auto* table_factory = cfd->ioptions()->table_factory.get();
|
||||
assert(table_factory != nullptr);
|
||||
Cache* cache =
|
||||
table_factory->GetOptions<Cache>(TableFactory::kBlockCacheOpts());
|
||||
if (cache && probed_caches.insert(cache).second) {
|
||||
cache->ReportProblems(immutable_db_options_.info_log);
|
||||
}
|
||||
@@ -1296,6 +1312,12 @@ Status DBImpl::SetOptions(
|
||||
}
|
||||
sv_context.Clean();
|
||||
|
||||
if (s.ok() && (options_map.count("preserve_internal_time_seconds") > 0 ||
|
||||
options_map.count("preclude_last_level_data_seconds") > 0)) {
|
||||
s = RegisterRecordSeqnoTimeWorker(read_options, write_options,
|
||||
false /* is_new_db*/);
|
||||
}
|
||||
|
||||
ROCKS_LOG_INFO(
|
||||
immutable_db_options_.info_log,
|
||||
"SetOptions() on column family [%s], inputs:", cfd->GetName().c_str());
|
||||
@@ -1453,8 +1475,6 @@ Status DBImpl::SetDBOptions(
|
||||
// TODO(xiez): clarify why apply optimize for read to write options
|
||||
file_options_for_compaction_ = fs_->OptimizeForCompactionTableRead(
|
||||
file_options_for_compaction_, immutable_db_options_);
|
||||
file_options_for_compaction_.compaction_readahead_size =
|
||||
mutable_db_options_.compaction_readahead_size;
|
||||
if (wal_other_option_changed || wal_size_option_changed) {
|
||||
WriteThread::Writer w;
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
@@ -2078,7 +2098,8 @@ InternalIterator* DBImpl::NewInternalIterator(
|
||||
// Collect iterator for mutable memtable
|
||||
auto mem_iter = super_version->mem->NewIterator(
|
||||
read_options, super_version->GetSeqnoToTimeMapping(), arena,
|
||||
super_version->mutable_cf_options.prefix_extractor.get());
|
||||
super_version->mutable_cf_options.prefix_extractor.get(),
|
||||
/*for_flush=*/false);
|
||||
Status s;
|
||||
if (!read_options.ignore_range_deletions) {
|
||||
std::unique_ptr<TruncatedRangeDelIterator> mem_tombstone_iter;
|
||||
@@ -3227,6 +3248,13 @@ Status DBImpl::MultiGetImpl(
|
||||
s = Status::Aborted();
|
||||
break;
|
||||
}
|
||||
|
||||
// This could be a long-running operation
|
||||
bool aborted = ROCKSDB_THREAD_YIELD_CHECK_ABORT();
|
||||
if (aborted) {
|
||||
s = Status::Aborted("Query abort.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Post processing (decrement reference counts and record statistics)
|
||||
@@ -3471,6 +3499,8 @@ void DBImpl::MultiGetEntityWithCallback(
|
||||
Status DBImpl::WrapUpCreateColumnFamilies(
|
||||
const ReadOptions& read_options, const WriteOptions& write_options,
|
||||
const std::vector<const ColumnFamilyOptions*>& cf_options) {
|
||||
options_mutex_.AssertHeld();
|
||||
|
||||
// NOTE: this function is skipped for create_missing_column_families and
|
||||
// DB::Open, so new functionality here might need to go into Open also.
|
||||
bool register_worker = false;
|
||||
@@ -3693,6 +3723,8 @@ Status DBImpl::DropColumnFamilies(
|
||||
}
|
||||
|
||||
Status DBImpl::DropColumnFamilyImpl(ColumnFamilyHandle* column_family) {
|
||||
options_mutex_.AssertHeld();
|
||||
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
const ReadOptions read_options;
|
||||
const WriteOptions write_options;
|
||||
@@ -3710,6 +3742,9 @@ Status DBImpl::DropColumnFamilyImpl(ColumnFamilyHandle* column_family) {
|
||||
edit.SetColumnFamily(cfd->GetID());
|
||||
|
||||
Status s;
|
||||
// Save re-aquiring lock for RegisterRecordSeqnoTimeWorker when not
|
||||
// applicable
|
||||
bool used_preserve_preclude = false;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
if (cfd->IsDropped()) {
|
||||
@@ -3725,9 +3760,11 @@ Status DBImpl::DropColumnFamilyImpl(ColumnFamilyHandle* column_family) {
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
}
|
||||
if (s.ok()) {
|
||||
auto* mutable_cf_options = cfd->GetLatestMutableCFOptions();
|
||||
max_total_in_memory_state_ -= mutable_cf_options->write_buffer_size *
|
||||
mutable_cf_options->max_write_buffer_number;
|
||||
auto& moptions = *cfd->GetLatestMutableCFOptions();
|
||||
max_total_in_memory_state_ -=
|
||||
moptions.write_buffer_size * moptions.max_write_buffer_number;
|
||||
used_preserve_preclude = moptions.preserve_internal_time_seconds > 0 ||
|
||||
moptions.preclude_last_level_data_seconds > 0;
|
||||
}
|
||||
|
||||
if (!cf_support_snapshot) {
|
||||
@@ -3745,8 +3782,7 @@ Status DBImpl::DropColumnFamilyImpl(ColumnFamilyHandle* column_family) {
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
|
||||
if (cfd->ioptions()->preserve_internal_time_seconds > 0 ||
|
||||
cfd->ioptions()->preclude_last_level_data_seconds > 0) {
|
||||
if (used_preserve_preclude) {
|
||||
s = RegisterRecordSeqnoTimeWorker(read_options, write_options,
|
||||
/* is_new_db */ false);
|
||||
}
|
||||
@@ -3772,7 +3808,6 @@ bool DBImpl::KeyMayExist(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
std::string* value, std::string* timestamp,
|
||||
bool* value_found) {
|
||||
assert(value != nullptr);
|
||||
assert(read_options.io_activity == Env::IOActivity::kUnknown);
|
||||
|
||||
if (value_found != nullptr) {
|
||||
@@ -3789,7 +3824,9 @@ bool DBImpl::KeyMayExist(const ReadOptions& read_options,
|
||||
get_impl_options.value_found = value_found;
|
||||
get_impl_options.timestamp = timestamp;
|
||||
auto s = GetImpl(roptions, key, get_impl_options);
|
||||
value->assign(pinnable_val.data(), pinnable_val.size());
|
||||
if (value_found && *value_found && value) {
|
||||
value->assign(pinnable_val.data(), pinnable_val.size());
|
||||
}
|
||||
|
||||
// If block_cache is enabled and the index block of the table didn't
|
||||
// not present in block_cache, the return value will be Status::Incomplete.
|
||||
@@ -3984,14 +4021,25 @@ std::unique_ptr<IterType> DBImpl::NewMultiCfIterator(
|
||||
"Different comparators are being used across CFs"));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Iterator*> child_iterators;
|
||||
Status s = NewIterators(_read_options, column_families, &child_iterators);
|
||||
if (!s.ok()) {
|
||||
return error_iterator_func(s);
|
||||
}
|
||||
return std::make_unique<ImplType>(column_families[0]->GetComparator(),
|
||||
column_families,
|
||||
std::move(child_iterators));
|
||||
|
||||
assert(column_families.size() == child_iterators.size());
|
||||
|
||||
std::vector<std::pair<ColumnFamilyHandle*, std::unique_ptr<Iterator>>>
|
||||
cfh_iter_pairs;
|
||||
cfh_iter_pairs.reserve(column_families.size());
|
||||
for (size_t i = 0; i < column_families.size(); ++i) {
|
||||
cfh_iter_pairs.emplace_back(column_families[i], child_iterators[i]);
|
||||
}
|
||||
|
||||
return std::make_unique<ImplType>(_read_options,
|
||||
column_families[0]->GetComparator(),
|
||||
std::move(cfh_iter_pairs));
|
||||
}
|
||||
|
||||
Status DBImpl::NewIterators(
|
||||
@@ -4295,8 +4343,8 @@ void DBImpl::ReleaseSnapshot(const Snapshot* s) {
|
||||
}
|
||||
// Avoid to go through every column family by checking a global threshold
|
||||
// first.
|
||||
CfdList cf_scheduled;
|
||||
if (oldest_snapshot > bottommost_files_mark_threshold_) {
|
||||
CfdList cf_scheduled;
|
||||
for (auto* cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (!cfd->ioptions()->allow_ingest_behind) {
|
||||
cfd->current()->storage_info()->UpdateOldestSnapshot(
|
||||
@@ -4328,6 +4376,24 @@ void DBImpl::ReleaseSnapshot(const Snapshot* s) {
|
||||
}
|
||||
bottommost_files_mark_threshold_ = new_bottommost_files_mark_threshold;
|
||||
}
|
||||
|
||||
// Avoid to go through every column family by checking a global threshold
|
||||
// first.
|
||||
if (oldest_snapshot >= standalone_range_deletion_files_mark_threshold_) {
|
||||
for (auto* cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->IsDropped() || CfdListContains(cf_scheduled, cfd)) {
|
||||
continue;
|
||||
}
|
||||
if (oldest_snapshot >=
|
||||
cfd->current()
|
||||
->storage_info()
|
||||
->standalone_range_tombstone_files_mark_threshold()) {
|
||||
EnqueuePendingCompaction(cfd);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
cf_scheduled.push_back(cfd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
delete casted_s;
|
||||
}
|
||||
@@ -4703,9 +4769,9 @@ void DBImpl::GetApproximateMemTableStats(ColumnFamilyHandle* column_family,
|
||||
// Convert user_key into a corresponding internal key.
|
||||
InternalKey k1(start.value(), kMaxSequenceNumber, kValueTypeForSeek);
|
||||
InternalKey k2(limit.value(), kMaxSequenceNumber, kValueTypeForSeek);
|
||||
MemTable::MemTableStats memStats =
|
||||
ReadOnlyMemTable::MemTableStats memStats =
|
||||
sv->mem->ApproximateStats(k1.Encode(), k2.Encode());
|
||||
MemTable::MemTableStats immStats =
|
||||
ReadOnlyMemTable::MemTableStats immStats =
|
||||
sv->imm->ApproximateStats(k1.Encode(), k2.Encode());
|
||||
*count = memStats.count + immStats.count;
|
||||
*size = memStats.size + immStats.size;
|
||||
@@ -4779,6 +4845,24 @@ void DBImpl::ReleaseFileNumberFromPendingOutputs(
|
||||
}
|
||||
}
|
||||
|
||||
std::list<uint64_t>::iterator DBImpl::CaptureOptionsFileNumber() {
|
||||
// We need to remember the iterator of our insert, because after the
|
||||
// compaction is done, we need to remove that element from
|
||||
// min_options_file_numbers_.
|
||||
min_options_file_numbers_.push_back(versions_->options_file_number());
|
||||
auto min_options_file_numbers_inserted_elem = min_options_file_numbers_.end();
|
||||
--min_options_file_numbers_inserted_elem;
|
||||
return min_options_file_numbers_inserted_elem;
|
||||
}
|
||||
|
||||
void DBImpl::ReleaseOptionsFileNumber(
|
||||
std::unique_ptr<std::list<uint64_t>::iterator>& v) {
|
||||
if (v.get() != nullptr) {
|
||||
min_options_file_numbers_.erase(*v.get());
|
||||
v.reset();
|
||||
}
|
||||
}
|
||||
|
||||
Status DBImpl::GetUpdatesSince(
|
||||
SequenceNumber seq, std::unique_ptr<TransactionLogIterator>* iter,
|
||||
const TransactionLogIterator::ReadOptions& read_options) {
|
||||
@@ -5142,11 +5226,12 @@ Status DBImpl::GetDbIdentity(std::string& identity) const {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImpl::GetDbIdentityFromIdentityFile(std::string* identity) const {
|
||||
Status DBImpl::GetDbIdentityFromIdentityFile(const IOOptions& opts,
|
||||
std::string* identity) const {
|
||||
std::string idfilename = IdentityFileName(dbname_);
|
||||
const FileOptions soptions;
|
||||
|
||||
Status s = ReadFileToString(fs_.get(), idfilename, identity);
|
||||
Status s = ReadFileToString(fs_.get(), idfilename, opts, identity);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -5457,7 +5542,8 @@ Status DBImpl::WriteOptionsFile(const WriteOptions& write_options,
|
||||
file_name, fs_.get());
|
||||
|
||||
if (s.ok()) {
|
||||
s = RenameTempFileToOptionsFile(file_name);
|
||||
s = RenameTempFileToOptionsFile(file_name,
|
||||
db_options.compaction_service != nullptr);
|
||||
}
|
||||
|
||||
if (!s.ok() && GetEnv()->FileExists(file_name).ok()) {
|
||||
@@ -5534,7 +5620,8 @@ Status DBImpl::DeleteObsoleteOptionsFiles() {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImpl::RenameTempFileToOptionsFile(const std::string& file_name) {
|
||||
Status DBImpl::RenameTempFileToOptionsFile(const std::string& file_name,
|
||||
bool is_remote_compaction_enabled) {
|
||||
Status s;
|
||||
|
||||
uint64_t options_file_number = versions_->NewFileNumber();
|
||||
@@ -5578,7 +5665,7 @@ Status DBImpl::RenameTempFileToOptionsFile(const std::string& file_name) {
|
||||
my_disable_delete_obsolete_files = disable_delete_obsolete_files_;
|
||||
}
|
||||
|
||||
if (!my_disable_delete_obsolete_files) {
|
||||
if (!my_disable_delete_obsolete_files && !is_remote_compaction_enabled) {
|
||||
// TODO: Should we check for errors here?
|
||||
DeleteObsoleteOptionsFiles().PermitUncheckedError();
|
||||
}
|
||||
@@ -5811,7 +5898,6 @@ Status DBImpl::IngestExternalFile(
|
||||
Status DBImpl::IngestExternalFiles(
|
||||
const std::vector<IngestExternalFileArg>& args) {
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
const ReadOptions read_options;
|
||||
const WriteOptions write_options;
|
||||
|
||||
if (args.empty()) {
|
||||
@@ -5833,10 +5919,14 @@ Status DBImpl::IngestExternalFiles(
|
||||
const size_t num_cfs = args.size();
|
||||
for (size_t i = 0; i != num_cfs; ++i) {
|
||||
if (args[i].external_files.empty()) {
|
||||
char err_msg[128] = {0};
|
||||
snprintf(err_msg, 128, "external_files[%zu] is empty", i);
|
||||
std::string err_msg =
|
||||
"external_files[" + std::to_string(i) + "] is empty";
|
||||
return Status::InvalidArgument(err_msg);
|
||||
}
|
||||
if (i && args[i].options.fill_cache != args[i - 1].options.fill_cache) {
|
||||
return Status::InvalidArgument(
|
||||
"fill_cache should be the same across ingestion options.");
|
||||
}
|
||||
}
|
||||
for (const auto& arg : args) {
|
||||
const IngestExternalFileOptions& ingest_opts = arg.options;
|
||||
@@ -5896,9 +5986,9 @@ Status DBImpl::IngestExternalFiles(
|
||||
uint64_t start_file_number = next_file_number;
|
||||
for (size_t i = 1; i != num_cfs; ++i) {
|
||||
start_file_number += args[i - 1].external_files.size();
|
||||
auto* cfd =
|
||||
static_cast<ColumnFamilyHandleImpl*>(args[i].column_family)->cfd();
|
||||
SuperVersion* super_version = cfd->GetReferencedSuperVersion(this);
|
||||
SuperVersion* super_version =
|
||||
ingestion_jobs[i].GetColumnFamilyData()->GetReferencedSuperVersion(
|
||||
this);
|
||||
Status es = ingestion_jobs[i].Prepare(
|
||||
args[i].external_files, args[i].files_checksums,
|
||||
args[i].files_checksum_func_names, args[i].file_temperature,
|
||||
@@ -5912,9 +6002,9 @@ Status DBImpl::IngestExternalFiles(
|
||||
TEST_SYNC_POINT("DBImpl::IngestExternalFiles:BeforeLastJobPrepare:0");
|
||||
TEST_SYNC_POINT("DBImpl::IngestExternalFiles:BeforeLastJobPrepare:1");
|
||||
{
|
||||
auto* cfd =
|
||||
static_cast<ColumnFamilyHandleImpl*>(args[0].column_family)->cfd();
|
||||
SuperVersion* super_version = cfd->GetReferencedSuperVersion(this);
|
||||
SuperVersion* super_version =
|
||||
ingestion_jobs[0].GetColumnFamilyData()->GetReferencedSuperVersion(
|
||||
this);
|
||||
Status es = ingestion_jobs[0].Prepare(
|
||||
args[0].external_files, args[0].files_checksums,
|
||||
args[0].files_checksum_func_names, args[0].file_temperature,
|
||||
@@ -5965,8 +6055,7 @@ Status DBImpl::IngestExternalFiles(
|
||||
bool at_least_one_cf_need_flush = false;
|
||||
std::vector<bool> need_flush(num_cfs, false);
|
||||
for (size_t i = 0; i != num_cfs; ++i) {
|
||||
auto* cfd =
|
||||
static_cast<ColumnFamilyHandleImpl*>(args[i].column_family)->cfd();
|
||||
auto* cfd = ingestion_jobs[i].GetColumnFamilyData();
|
||||
if (cfd->IsDropped()) {
|
||||
// TODO (yanqin) investigate whether we should abort ingestion or
|
||||
// proceed with other non-dropped column families.
|
||||
@@ -5998,12 +6087,10 @@ Status DBImpl::IngestExternalFiles(
|
||||
for (size_t i = 0; i != num_cfs; ++i) {
|
||||
if (need_flush[i]) {
|
||||
mutex_.Unlock();
|
||||
auto* cfd =
|
||||
static_cast<ColumnFamilyHandleImpl*>(args[i].column_family)
|
||||
->cfd();
|
||||
status = FlushMemTable(cfd, flush_opts,
|
||||
FlushReason::kExternalFileIngestion,
|
||||
true /* entered_write_thread */);
|
||||
status =
|
||||
FlushMemTable(ingestion_jobs[i].GetColumnFamilyData(),
|
||||
flush_opts, FlushReason::kExternalFileIngestion,
|
||||
true /* entered_write_thread */);
|
||||
mutex_.Lock();
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
@@ -6011,6 +6098,13 @@ Status DBImpl::IngestExternalFiles(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (status.ok()) {
|
||||
for (size_t i = 0; i != num_cfs; ++i) {
|
||||
if (immutable_db_options_.atomic_flush || need_flush[i]) {
|
||||
ingestion_jobs[i].SetFlushedBeforeRun();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Run ingestion jobs.
|
||||
if (status.ok()) {
|
||||
@@ -6024,16 +6118,15 @@ Status DBImpl::IngestExternalFiles(
|
||||
}
|
||||
}
|
||||
if (status.ok()) {
|
||||
ReadOptions read_options;
|
||||
read_options.fill_cache = args[0].options.fill_cache;
|
||||
autovector<ColumnFamilyData*> cfds_to_commit;
|
||||
autovector<const MutableCFOptions*> mutable_cf_options_list;
|
||||
autovector<autovector<VersionEdit*>> edit_lists;
|
||||
uint32_t num_entries = 0;
|
||||
for (size_t i = 0; i != num_cfs; ++i) {
|
||||
auto* cfd =
|
||||
static_cast<ColumnFamilyHandleImpl*>(args[i].column_family)->cfd();
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
auto* cfd = ingestion_jobs[i].GetColumnFamilyData();
|
||||
assert(!cfd->IsDropped());
|
||||
cfds_to_commit.push_back(cfd);
|
||||
mutable_cf_options_list.push_back(cfd->GetLatestMutableCFOptions());
|
||||
autovector<VersionEdit*> edit_list;
|
||||
@@ -6083,20 +6176,16 @@ Status DBImpl::IngestExternalFiles(
|
||||
|
||||
if (status.ok()) {
|
||||
for (size_t i = 0; i != num_cfs; ++i) {
|
||||
auto* cfd =
|
||||
static_cast<ColumnFamilyHandleImpl*>(args[i].column_family)->cfd();
|
||||
if (!cfd->IsDropped()) {
|
||||
InstallSuperVersionAndScheduleWork(cfd, &sv_ctxs[i],
|
||||
*cfd->GetLatestMutableCFOptions());
|
||||
auto* cfd = ingestion_jobs[i].GetColumnFamilyData();
|
||||
assert(!cfd->IsDropped());
|
||||
InstallSuperVersionAndScheduleWork(cfd, &sv_ctxs[i],
|
||||
*cfd->GetLatestMutableCFOptions());
|
||||
#ifndef NDEBUG
|
||||
if (0 == i && num_cfs > 1) {
|
||||
TEST_SYNC_POINT(
|
||||
"DBImpl::IngestExternalFiles:InstallSVForFirstCF:0");
|
||||
TEST_SYNC_POINT(
|
||||
"DBImpl::IngestExternalFiles:InstallSVForFirstCF:1");
|
||||
}
|
||||
#endif // !NDEBUG
|
||||
if (0 == i && num_cfs > 1) {
|
||||
TEST_SYNC_POINT("DBImpl::IngestExternalFiles:InstallSVForFirstCF:0");
|
||||
TEST_SYNC_POINT("DBImpl::IngestExternalFiles:InstallSVForFirstCF:1");
|
||||
}
|
||||
#endif // !NDEBUG
|
||||
}
|
||||
} else if (versions_->io_status().IsIOError()) {
|
||||
// Error while writing to MANIFEST.
|
||||
@@ -6138,8 +6227,7 @@ Status DBImpl::IngestExternalFiles(
|
||||
}
|
||||
if (status.ok()) {
|
||||
for (size_t i = 0; i != num_cfs; ++i) {
|
||||
auto* cfd =
|
||||
static_cast<ColumnFamilyHandleImpl*>(args[i].column_family)->cfd();
|
||||
auto* cfd = ingestion_jobs[i].GetColumnFamilyData();
|
||||
if (!cfd->IsDropped()) {
|
||||
NotifyOnExternalFileIngested(cfd, ingestion_jobs[i]);
|
||||
}
|
||||
|
||||
+92
-24
@@ -48,6 +48,7 @@
|
||||
#include "db/write_controller.h"
|
||||
#include "db/write_thread.h"
|
||||
#include "logging/event_logger.h"
|
||||
#include "memtable/wbwi_memtable.h"
|
||||
#include "monitoring/instrumented_mutex.h"
|
||||
#include "options/db_options.h"
|
||||
#include "port/port.h"
|
||||
@@ -60,6 +61,7 @@
|
||||
#include "rocksdb/transaction_log.h"
|
||||
#include "rocksdb/user_write_callback.h"
|
||||
#include "rocksdb/utilities/replayer.h"
|
||||
#include "rocksdb/utilities/write_batch_with_index.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "table/merging_iterator.h"
|
||||
#include "util/autovector.h"
|
||||
@@ -363,12 +365,10 @@ class DBImpl : public DB {
|
||||
const Snapshot* GetSnapshot() override;
|
||||
void ReleaseSnapshot(const Snapshot* snapshot) override;
|
||||
|
||||
// EXPERIMENTAL
|
||||
std::unique_ptr<Iterator> NewCoalescingIterator(
|
||||
const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families) override;
|
||||
|
||||
// EXPERIMENTAL
|
||||
std::unique_ptr<AttributeGroupIterator> NewAttributeGroupIterator(
|
||||
const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families) override;
|
||||
@@ -482,7 +482,8 @@ class DBImpl : public DB {
|
||||
|
||||
Status GetDbIdentity(std::string& identity) const override;
|
||||
|
||||
virtual Status GetDbIdentityFromIdentityFile(std::string* identity) const;
|
||||
virtual Status GetDbIdentityFromIdentityFile(const IOOptions& opts,
|
||||
std::string* identity) const;
|
||||
|
||||
Status GetDbSessionId(std::string& session_id) const override;
|
||||
|
||||
@@ -853,6 +854,8 @@ class DBImpl : public DB {
|
||||
|
||||
uint64_t GetObsoleteSstFilesSize();
|
||||
|
||||
uint64_t MinOptionsFileNumberToKeep();
|
||||
|
||||
// Returns the list of live files in 'live' and the list
|
||||
// of all files in the filesystem in 'candidate_files'.
|
||||
// If force == false and the last call was less than
|
||||
@@ -1197,9 +1200,7 @@ class DBImpl : public DB {
|
||||
|
||||
uint64_t TEST_total_log_size() const { return total_log_size_; }
|
||||
|
||||
// Returns column family name to ImmutableCFOptions map.
|
||||
Status TEST_GetAllImmutableCFOptions(
|
||||
std::unordered_map<std::string, const ImmutableCFOptions*>* iopts_map);
|
||||
void TEST_GetAllBlockCaches(std::unordered_set<const Cache*>* cache_set);
|
||||
|
||||
// Return the lastest MutableCFOptions of a column family
|
||||
Status TEST_GetLatestMutableCFOptions(ColumnFamilyHandle* column_family,
|
||||
@@ -1239,9 +1240,14 @@ class DBImpl : public DB {
|
||||
static Status TEST_ValidateOptions(const DBOptions& db_options) {
|
||||
return ValidateOptions(db_options);
|
||||
}
|
||||
|
||||
#endif // NDEBUG
|
||||
|
||||
// In certain configurations, verify that the table/blob file cache only
|
||||
// contains entries for live files, to check for effective leaks of open
|
||||
// files. This can only be called when purging of obsolete files has
|
||||
// "settled," such as during parts of DB Close().
|
||||
void TEST_VerifyNoObsoleteFilesCached(bool db_mutex_already_held) const;
|
||||
|
||||
// persist stats to column family "_persistent_stats"
|
||||
void PersistStats();
|
||||
|
||||
@@ -1463,7 +1469,8 @@ class DBImpl : public DB {
|
||||
// The following two functions can only be called when:
|
||||
// 1. WriteThread::Writer::EnterUnbatched() is used.
|
||||
// 2. db_mutex is NOT held
|
||||
Status RenameTempFileToOptionsFile(const std::string& file_name);
|
||||
Status RenameTempFileToOptionsFile(const std::string& file_name,
|
||||
bool is_remote_compaction_enabled);
|
||||
Status DeleteObsoleteOptionsFiles();
|
||||
|
||||
void NotifyOnManualFlushScheduled(autovector<ColumnFamilyData*> cfds,
|
||||
@@ -1502,6 +1509,23 @@ class DBImpl : public DB {
|
||||
|
||||
void EraseThreadStatusDbInfo() const;
|
||||
|
||||
// For CFs that has updates in `wbwi`, their memtable will be switched,
|
||||
// and `wbwi` will be added as the latest immutable memtable.
|
||||
//
|
||||
// REQUIRES: this thread is currently at the front of the main writer queue.
|
||||
// @param prep_log refers to the WAL that contains prepare record
|
||||
// for the transaction based on wbwi.
|
||||
// @param assigned_seqno Sequence numbers for the ingested memtable.
|
||||
// @param last_seqno the value of versions_->LastSequence() after the write
|
||||
// ingests `wbwi` is done.
|
||||
// @param memtable_updated Whether the same write that ingests wbwi has
|
||||
// updated memtable. This is useful for determining whether to set bg
|
||||
// error when IngestWBWI fails.
|
||||
Status IngestWBWI(std::shared_ptr<WriteBatchWithIndex> wbwi,
|
||||
const WBWIMemTable::SeqnoRange& assigned_seqno,
|
||||
uint64_t min_prep_log, SequenceNumber last_seqno,
|
||||
bool memtable_updated, bool ignore_missing_cf);
|
||||
|
||||
// If disable_memtable is set the application logic must guarantee that the
|
||||
// batch will still be skipped from memtable during the recovery. An excption
|
||||
// to this is seq_per_batch_ mode, in which since each batch already takes one
|
||||
@@ -1517,6 +1541,16 @@ class DBImpl : public DB {
|
||||
// batch_cnt is expected to be non-zero in seq_per_batch mode and
|
||||
// indicates the number of sub-patches. A sub-patch is a subset of the write
|
||||
// batch that does not have duplicate keys.
|
||||
// `callback` is called before WAL write.
|
||||
// See more in comment above WriteCallback::Callback().
|
||||
// pre_release_callback is called after WAL write and before memtable write.
|
||||
// See more in comment above PreReleaseCallback::Callback().
|
||||
// post_memtable_callback is called after memtable write but before publishing
|
||||
// the sequence number to readers.
|
||||
//
|
||||
// The main write queue. This is the only write queue that updates
|
||||
// LastSequence. When using one write queue, the same sequence also indicates
|
||||
// the last published sequence.
|
||||
Status WriteImpl(const WriteOptions& options, WriteBatch* updates,
|
||||
WriteCallback* callback = nullptr,
|
||||
UserWriteCallback* user_write_cb = nullptr,
|
||||
@@ -1524,7 +1558,9 @@ class DBImpl : public DB {
|
||||
bool disable_memtable = false, uint64_t* seq_used = nullptr,
|
||||
size_t batch_cnt = 0,
|
||||
PreReleaseCallback* pre_release_callback = nullptr,
|
||||
PostMemTableCallback* post_memtable_callback = nullptr);
|
||||
PostMemTableCallback* post_memtable_callback = nullptr,
|
||||
std::shared_ptr<WriteBatchWithIndex> wbwi = nullptr,
|
||||
uint64_t min_prep_log = 0);
|
||||
|
||||
Status PipelinedWriteImpl(const WriteOptions& options, WriteBatch* updates,
|
||||
WriteCallback* callback = nullptr,
|
||||
@@ -1587,7 +1623,7 @@ class DBImpl : public DB {
|
||||
// Read/create DB identity file (as appropriate), and write DB ID to
|
||||
// version_edit if provided.
|
||||
Status SetupDBId(const WriteOptions& write_options, bool read_only,
|
||||
bool is_new_db, VersionEdit* version_edit);
|
||||
bool is_new_db, bool is_retry, VersionEdit* version_edit);
|
||||
// Assign db_id_ and write DB ID to version_edit if provided.
|
||||
void SetDBId(std::string&& id, bool read_only, VersionEdit* version_edit);
|
||||
|
||||
@@ -1694,6 +1730,8 @@ class DBImpl : public DB {
|
||||
friend class XFTransactionWriteHandler;
|
||||
friend class DBBlobIndexTest;
|
||||
friend class WriteUnpreparedTransactionTest_RecoveryTest_Test;
|
||||
friend class CompactionServiceTest_PreservedOptionsLocalCompaction_Test;
|
||||
friend class CompactionServiceTest_PreservedOptionsRemoteCompaction_Test;
|
||||
#endif
|
||||
|
||||
struct CompactionState;
|
||||
@@ -1702,7 +1740,7 @@ class DBImpl : public DB {
|
||||
|
||||
struct WriteContext {
|
||||
SuperVersionContext superversion_context;
|
||||
autovector<MemTable*> memtables_to_free_;
|
||||
autovector<ReadOnlyMemTable*> memtables_to_free_;
|
||||
|
||||
explicit WriteContext(bool create_superversion = false)
|
||||
: superversion_context(create_superversion) {}
|
||||
@@ -1965,6 +2003,12 @@ class DBImpl : public DB {
|
||||
void ReleaseFileNumberFromPendingOutputs(
|
||||
std::unique_ptr<std::list<uint64_t>::iterator>& v);
|
||||
|
||||
// Similar to pending_outputs, preserve OPTIONS file. Used for remote
|
||||
// compaction.
|
||||
std::list<uint64_t>::iterator CaptureOptionsFileNumber();
|
||||
void ReleaseOptionsFileNumber(
|
||||
std::unique_ptr<std::list<uint64_t>::iterator>& v);
|
||||
|
||||
// Sets bg error if there is an error writing to WAL.
|
||||
IOStatus SyncClosedWals(const WriteOptions& write_options,
|
||||
JobContext* job_context, VersionEdit* synced_wals,
|
||||
@@ -2038,7 +2082,21 @@ class DBImpl : public DB {
|
||||
|
||||
Status TrimMemtableHistory(WriteContext* context);
|
||||
|
||||
Status SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context);
|
||||
// Switches the current live memtable to immutable/read-only memtable.
|
||||
// A new WAL is created if the current WAL is not empty.
|
||||
// If `new_imm` is not nullptr, it will be added as the newest immutable
|
||||
// memtable, if and only if OK status is returned.
|
||||
// `last_seqno` needs to be provided if `new_imm` is not nullptr. It is
|
||||
// the value of versions_->LastSequence() after the write that ingests new_imm
|
||||
// is done.
|
||||
//
|
||||
// REQUIRES: mutex_ is held
|
||||
// REQUIRES: this thread is currently at the front of the writer queue
|
||||
// REQUIRES: this thread is currently at the front of the 2nd writer queue if
|
||||
// two_write_queues_ is true (This is to simplify the reasoning.)
|
||||
Status SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
ReadOnlyMemTable* new_imm = nullptr,
|
||||
SequenceNumber last_seqno = 0);
|
||||
|
||||
// Select and output column families qualified for atomic flush in
|
||||
// `selected_cfds`. If `provided_candidate_cfds` is non-empty, it will be used
|
||||
@@ -2076,17 +2134,18 @@ class DBImpl : public DB {
|
||||
// memtable pending flush.
|
||||
// resuming_from_bg_err indicates whether the caller is attempting to resume
|
||||
// from background error.
|
||||
Status WaitForFlushMemTable(ColumnFamilyData* cfd,
|
||||
const uint64_t* flush_memtable_id = nullptr,
|
||||
bool resuming_from_bg_err = false) {
|
||||
Status WaitForFlushMemTable(
|
||||
ColumnFamilyData* cfd, const uint64_t* flush_memtable_id = nullptr,
|
||||
bool resuming_from_bg_err = false,
|
||||
std::optional<FlushReason> flush_reason = std::nullopt) {
|
||||
return WaitForFlushMemTables({cfd}, {flush_memtable_id},
|
||||
resuming_from_bg_err);
|
||||
resuming_from_bg_err, flush_reason);
|
||||
}
|
||||
// Wait for memtables to be flushed for multiple column families.
|
||||
Status WaitForFlushMemTables(
|
||||
const autovector<ColumnFamilyData*>& cfds,
|
||||
const autovector<const uint64_t*>& flush_memtable_ids,
|
||||
bool resuming_from_bg_err);
|
||||
bool resuming_from_bg_err, std::optional<FlushReason> flush_reason);
|
||||
|
||||
inline void WaitForPendingWrites() {
|
||||
mutex_.AssertHeld();
|
||||
@@ -2201,8 +2260,6 @@ class DBImpl : public DB {
|
||||
void TrackOrUntrackFiles(const std::vector<std::string>& existing_data_files,
|
||||
bool track);
|
||||
|
||||
ColumnFamilyData* GetColumnFamilyDataByName(const std::string& cf_name);
|
||||
|
||||
void MaybeScheduleFlushOrCompaction();
|
||||
|
||||
struct FlushRequest {
|
||||
@@ -2755,6 +2812,11 @@ class DBImpl : public DB {
|
||||
// State is protected with db mutex.
|
||||
std::list<uint64_t> pending_outputs_;
|
||||
|
||||
// Similar to pending_outputs_, FindObsoleteFiles()/PurgeObsoleteFiles() never
|
||||
// deletes any OPTIONS file that has number bigger than any of the file number
|
||||
// in min_options_file_numbers_.
|
||||
std::list<uint64_t> min_options_file_numbers_;
|
||||
|
||||
// flush_queue_ and compaction_queue_ hold column families that we need to
|
||||
// flush and compact, respectively.
|
||||
// A column family is inserted into flush_queue_ when it satisfies condition
|
||||
@@ -2877,6 +2939,11 @@ class DBImpl : public DB {
|
||||
// garbages, among all column families.
|
||||
SequenceNumber bottommost_files_mark_threshold_ = kMaxSequenceNumber;
|
||||
|
||||
// The min threshold to trigger compactions for standalone range deletion
|
||||
// files that are marked for compaction.
|
||||
SequenceNumber standalone_range_deletion_files_mark_threshold_ =
|
||||
kMaxSequenceNumber;
|
||||
|
||||
LogsWithPrepTracker logs_with_prep_tracker_;
|
||||
|
||||
// Callback for compaction to check if a key is visible to a snapshot.
|
||||
@@ -2983,7 +3050,8 @@ CompressionType GetCompressionFlush(const ImmutableCFOptions& ioptions,
|
||||
VersionEdit GetDBRecoveryEditForObsoletingMemTables(
|
||||
VersionSet* vset, const ColumnFamilyData& cfd,
|
||||
const autovector<VersionEdit*>& edit_list,
|
||||
const autovector<MemTable*>& memtables, LogsWithPrepTracker* prep_tracker);
|
||||
const autovector<ReadOnlyMemTable*>& memtables,
|
||||
LogsWithPrepTracker* prep_tracker);
|
||||
|
||||
// Return the earliest log file to keep after the memtable flush is
|
||||
// finalized.
|
||||
@@ -2994,13 +3062,13 @@ VersionEdit GetDBRecoveryEditForObsoletingMemTables(
|
||||
uint64_t PrecomputeMinLogNumberToKeep2PC(
|
||||
VersionSet* vset, const ColumnFamilyData& cfd_to_flush,
|
||||
const autovector<VersionEdit*>& edit_list,
|
||||
const autovector<MemTable*>& memtables_to_flush,
|
||||
const autovector<ReadOnlyMemTable*>& memtables_to_flush,
|
||||
LogsWithPrepTracker* prep_tracker);
|
||||
// For atomic flush.
|
||||
uint64_t PrecomputeMinLogNumberToKeep2PC(
|
||||
VersionSet* vset, const autovector<ColumnFamilyData*>& cfds_to_flush,
|
||||
const autovector<autovector<VersionEdit*>>& edit_lists,
|
||||
const autovector<const autovector<MemTable*>*>& memtables_to_flush,
|
||||
const autovector<const autovector<ReadOnlyMemTable*>*>& memtables_to_flush,
|
||||
LogsWithPrepTracker* prep_tracker);
|
||||
|
||||
// In non-2PC mode, WALs with log number < the returned number can be
|
||||
@@ -3017,11 +3085,11 @@ uint64_t PrecomputeMinLogNumberToKeepNon2PC(
|
||||
// will not depend on any WAL file. nullptr means no memtable is being flushed.
|
||||
// The function is only applicable to 2pc mode.
|
||||
uint64_t FindMinPrepLogReferencedByMemTable(
|
||||
VersionSet* vset, const autovector<MemTable*>& memtables_to_flush);
|
||||
VersionSet* vset, const autovector<ReadOnlyMemTable*>& memtables_to_flush);
|
||||
// For atomic flush.
|
||||
uint64_t FindMinPrepLogReferencedByMemTable(
|
||||
VersionSet* vset,
|
||||
const autovector<const autovector<MemTable*>*>& memtables_to_flush);
|
||||
const autovector<const autovector<ReadOnlyMemTable*>*>& memtables_to_flush);
|
||||
|
||||
// Fix user-supplied options to be reasonable
|
||||
template <class T, class V>
|
||||
|
||||
@@ -753,7 +753,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
|
||||
if (s.ok()) {
|
||||
autovector<ColumnFamilyData*> tmp_cfds;
|
||||
autovector<const autovector<MemTable*>*> mems_list;
|
||||
autovector<const autovector<ReadOnlyMemTable*>*> mems_list;
|
||||
autovector<const MutableCFOptions*> mutable_cf_options_list;
|
||||
autovector<FileMetaData*> tmp_file_meta;
|
||||
autovector<std::list<std::unique_ptr<FlushJobInfo>>*>
|
||||
@@ -1457,11 +1457,6 @@ Status DBImpl::CompactFilesImpl(
|
||||
input_set.insert(TableFileNameToNumber(file_name));
|
||||
}
|
||||
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
// TODO(yhchiang): can directly use version here if none of the
|
||||
// following functions call is pluggable to external developers.
|
||||
version->GetColumnFamilyMetaData(&cf_meta);
|
||||
|
||||
if (output_path_id < 0) {
|
||||
if (cfd->ioptions()->cf_paths.size() == 1U) {
|
||||
output_path_id = 0;
|
||||
@@ -1482,7 +1477,7 @@ Status DBImpl::CompactFilesImpl(
|
||||
|
||||
std::vector<CompactionInputFiles> input_files;
|
||||
Status s = cfd->compaction_picker()->SanitizeAndConvertCompactionInputFiles(
|
||||
&input_set, cf_meta, output_level, version->storage_info(), &input_files);
|
||||
&input_set, output_level, version, &input_files);
|
||||
TEST_SYNC_POINT(
|
||||
"DBImpl::CompactFilesImpl::PostSanitizeAndConvertCompactionInputFiles");
|
||||
if (!s.ok()) {
|
||||
@@ -1561,6 +1556,12 @@ Status DBImpl::CompactFilesImpl(
|
||||
|
||||
compaction_job.Prepare();
|
||||
|
||||
std::unique_ptr<std::list<uint64_t>::iterator> min_options_file_number_elem;
|
||||
if (immutable_db_options().compaction_service != nullptr) {
|
||||
min_options_file_number_elem.reset(
|
||||
new std::list<uint64_t>::iterator(CaptureOptionsFileNumber()));
|
||||
}
|
||||
|
||||
mutex_.Unlock();
|
||||
TEST_SYNC_POINT("CompactFilesImpl:0");
|
||||
TEST_SYNC_POINT("CompactFilesImpl:1");
|
||||
@@ -1570,6 +1571,10 @@ Status DBImpl::CompactFilesImpl(
|
||||
TEST_SYNC_POINT("CompactFilesImpl:3");
|
||||
mutex_.Lock();
|
||||
|
||||
if (immutable_db_options().compaction_service != nullptr) {
|
||||
ReleaseOptionsFileNumber(min_options_file_number_elem);
|
||||
}
|
||||
|
||||
bool compaction_released = false;
|
||||
Status status =
|
||||
compaction_job.Install(*c->mutable_cf_options(), &compaction_released);
|
||||
@@ -1852,8 +1857,9 @@ Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) {
|
||||
mutable_cf_options.compression_opts,
|
||||
mutable_cf_options.default_write_temperature,
|
||||
0 /* max_subcompactions, not applicable */,
|
||||
{} /* grandparents, not applicable */, false /* is manual */,
|
||||
"" /* trim_ts */, -1 /* score, not applicable */,
|
||||
{} /* grandparents, not applicable */,
|
||||
std::nullopt /* earliest_snapshot */, nullptr /* snapshot_checker */,
|
||||
false /* is manual */, "" /* trim_ts */, -1 /* score, not applicable */,
|
||||
false /* is deletion compaction, not applicable */,
|
||||
false /* l0_files_might_overlap, not applicable */,
|
||||
CompactionReason::kRefitLevel));
|
||||
@@ -2407,7 +2413,8 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
}
|
||||
s = WaitForFlushMemTables(
|
||||
cfds, flush_memtable_ids,
|
||||
flush_reason == FlushReason::kErrorRecovery /* resuming_from_bg_err */);
|
||||
flush_reason == FlushReason::kErrorRecovery /* resuming_from_bg_err */,
|
||||
flush_reason);
|
||||
InstrumentedMutexLock lock_guard(&mutex_);
|
||||
for (auto* tmp_cfd : cfds) {
|
||||
tmp_cfd->UnrefAndTryDelete();
|
||||
@@ -2549,7 +2556,8 @@ Status DBImpl::AtomicFlushMemTables(
|
||||
}
|
||||
s = WaitForFlushMemTables(
|
||||
cfds, flush_memtable_ids,
|
||||
flush_reason == FlushReason::kErrorRecovery /* resuming_from_bg_err */);
|
||||
flush_reason == FlushReason::kErrorRecovery /* resuming_from_bg_err */,
|
||||
flush_reason);
|
||||
InstrumentedMutexLock lock_guard(&mutex_);
|
||||
for (auto* cfd : cfds) {
|
||||
cfd->UnrefAndTryDelete();
|
||||
@@ -2612,7 +2620,7 @@ Status DBImpl::RetryFlushesForErrorRecovery(FlushReason flush_reason,
|
||||
flush_memtable_id_ptrs.push_back(&flush_memtable_id);
|
||||
}
|
||||
s = WaitForFlushMemTables(cfds, flush_memtable_id_ptrs,
|
||||
true /* resuming_from_bg_err */);
|
||||
true /* resuming_from_bg_err */, flush_reason);
|
||||
mutex_.Lock();
|
||||
}
|
||||
|
||||
@@ -2712,7 +2720,7 @@ Status DBImpl::WaitUntilFlushWouldNotStallWrites(ColumnFamilyData* cfd,
|
||||
Status DBImpl::WaitForFlushMemTables(
|
||||
const autovector<ColumnFamilyData*>& cfds,
|
||||
const autovector<const uint64_t*>& flush_memtable_ids,
|
||||
bool resuming_from_bg_err) {
|
||||
bool resuming_from_bg_err, std::optional<FlushReason> flush_reason) {
|
||||
int num = static_cast<int>(cfds.size());
|
||||
// Wait until the compaction completes
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
@@ -2750,7 +2758,15 @@ Status DBImpl::WaitForFlushMemTables(
|
||||
(flush_memtable_ids[i] != nullptr &&
|
||||
cfds[i]->imm()->GetEarliestMemTableID() >
|
||||
*flush_memtable_ids[i])) {
|
||||
++num_finished;
|
||||
// Make file ingestion's flush wait until SuperVersion is also updated
|
||||
// since after flush, it does range overlapping check and file level
|
||||
// assignment with the current SuperVersion.
|
||||
if (!flush_reason.has_value() ||
|
||||
flush_reason.value() != FlushReason::kExternalFileIngestion ||
|
||||
cfds[i]->GetSuperVersion()->imm->GetID() ==
|
||||
cfds[i]->imm()->current()->GetID()) {
|
||||
++num_finished;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (1 == num_dropped && 1 == num) {
|
||||
@@ -3541,6 +3557,14 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
is_manual && manual_compaction->disallow_trivial_move;
|
||||
|
||||
CompactionJobStats compaction_job_stats;
|
||||
// Set is_remote_compaction to true on CompactionBegin Event if
|
||||
// compaction_service is set except for trivial moves. We do not know whether
|
||||
// remote compaction will actually be successfully scheduled, or fall back to
|
||||
// local at this time. CompactionCompleted event will tell the truth where
|
||||
// the compaction actually happened.
|
||||
compaction_job_stats.is_remote_compaction =
|
||||
immutable_db_options().compaction_service != nullptr;
|
||||
|
||||
Status status;
|
||||
if (!error_handler_.IsBGWorkStopped()) {
|
||||
if (shutting_down_.load(std::memory_order_acquire)) {
|
||||
@@ -3661,8 +3685,20 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
// compaction is not necessary. Need to make sure mutex is held
|
||||
// until we make a copy in the following code
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCompaction():BeforePickCompaction");
|
||||
SnapshotChecker* snapshot_checker = nullptr;
|
||||
std::vector<SequenceNumber> snapshot_seqs;
|
||||
// This info is not useful for other scenarios, so save querying existing
|
||||
// snapshots for those cases.
|
||||
if (cfd->ioptions()->compaction_style == kCompactionStyleUniversal &&
|
||||
cfd->user_comparator()->timestamp_size() == 0) {
|
||||
SequenceNumber earliest_write_conflict_snapshot;
|
||||
GetSnapshotContext(job_context, &snapshot_seqs,
|
||||
&earliest_write_conflict_snapshot,
|
||||
&snapshot_checker);
|
||||
assert(is_snapshot_supported_ || snapshots_.empty());
|
||||
}
|
||||
c.reset(cfd->PickCompaction(*mutable_cf_options, mutable_db_options_,
|
||||
log_buffer));
|
||||
snapshot_seqs, snapshot_checker, log_buffer));
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCompaction():AfterPickCompaction");
|
||||
|
||||
if (c != nullptr) {
|
||||
@@ -3766,6 +3802,8 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
ThreadStatusUtil::SetThreadOperation(ThreadStatus::OP_COMPACTION);
|
||||
|
||||
compaction_job_stats.num_input_files = c->num_input_files(0);
|
||||
// Trivial moves do not get compacted remotely
|
||||
compaction_job_stats.is_remote_compaction = false;
|
||||
|
||||
NotifyOnCompactionBegin(c->column_family_data(), c.get(), status,
|
||||
compaction_job_stats, job_context->job_id);
|
||||
@@ -3901,6 +3939,12 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
&bg_bottom_compaction_scheduled_);
|
||||
compaction_job.Prepare();
|
||||
|
||||
std::unique_ptr<std::list<uint64_t>::iterator> min_options_file_number_elem;
|
||||
if (immutable_db_options().compaction_service != nullptr) {
|
||||
min_options_file_number_elem.reset(
|
||||
new std::list<uint64_t>::iterator(CaptureOptionsFileNumber()));
|
||||
}
|
||||
|
||||
NotifyOnCompactionBegin(c->column_family_data(), c.get(), status,
|
||||
compaction_job_stats, job_context->job_id);
|
||||
mutex_.Unlock();
|
||||
@@ -3910,6 +3954,11 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
compaction_job.Run().PermitUncheckedError();
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCompaction:NonTrivial:AfterRun");
|
||||
mutex_.Lock();
|
||||
|
||||
if (immutable_db_options().compaction_service != nullptr) {
|
||||
ReleaseOptionsFileNumber(min_options_file_number_elem);
|
||||
}
|
||||
|
||||
status =
|
||||
compaction_job.Install(*c->mutable_cf_options(), &compaction_released);
|
||||
io_s = compaction_job.io_status();
|
||||
@@ -3937,7 +3986,10 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
// Sanity checking that compaction files are freed.
|
||||
for (size_t i = 0; i < c->num_input_levels(); i++) {
|
||||
for (size_t j = 0; j < c->inputs(i)->size(); j++) {
|
||||
assert(!c->input(i, j)->being_compacted);
|
||||
// When status is not OK, compaction's result installation failed and
|
||||
// no new Version installed. The files could have been released and
|
||||
// picked up again by other compaction attempts.
|
||||
assert(!c->input(i, j)->being_compacted || !status.ok());
|
||||
}
|
||||
}
|
||||
std::unordered_set<Compaction*>* cip = c->column_family_data()
|
||||
@@ -4256,12 +4308,18 @@ void DBImpl::InstallSuperVersionAndScheduleWork(
|
||||
// newer snapshot created and released frequently, the compaction will be
|
||||
// triggered soon anyway.
|
||||
bottommost_files_mark_threshold_ = kMaxSequenceNumber;
|
||||
standalone_range_deletion_files_mark_threshold_ = kMaxSequenceNumber;
|
||||
for (auto* my_cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (!my_cfd->ioptions()->allow_ingest_behind) {
|
||||
bottommost_files_mark_threshold_ = std::min(
|
||||
bottommost_files_mark_threshold_,
|
||||
my_cfd->current()->storage_info()->bottommost_files_mark_threshold());
|
||||
}
|
||||
standalone_range_deletion_files_mark_threshold_ =
|
||||
std::min(standalone_range_deletion_files_mark_threshold_,
|
||||
cfd->current()
|
||||
->storage_info()
|
||||
->standalone_range_tombstone_files_mark_threshold());
|
||||
}
|
||||
|
||||
// Whenever we install new SuperVersion, we might need to issue new flushes or
|
||||
|
||||
+76
-17
@@ -6,9 +6,10 @@
|
||||
// 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.
|
||||
|
||||
#ifndef NDEBUG
|
||||
#include <iostream>
|
||||
|
||||
#include "db/blob/blob_file_cache.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/error_handler.h"
|
||||
@@ -232,23 +233,16 @@ uint64_t DBImpl::TEST_LogfileNumber() {
|
||||
return logfile_number_;
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_GetAllImmutableCFOptions(
|
||||
std::unordered_map<std::string, const ImmutableCFOptions*>* iopts_map) {
|
||||
std::vector<std::string> cf_names;
|
||||
std::vector<const ImmutableCFOptions*> iopts;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
cf_names.push_back(cfd->GetName());
|
||||
iopts.push_back(cfd->ioptions());
|
||||
void DBImpl::TEST_GetAllBlockCaches(
|
||||
std::unordered_set<const Cache*>* cache_set) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (const auto bbto =
|
||||
cfd->GetCurrentMutableCFOptions()
|
||||
->table_factory->GetOptions<BlockBasedTableOptions>()) {
|
||||
cache_set->insert(bbto->block_cache.get());
|
||||
}
|
||||
}
|
||||
iopts_map->clear();
|
||||
for (size_t i = 0; i < cf_names.size(); ++i) {
|
||||
iopts_map->insert({cf_names[i], iopts[i]});
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
uint64_t DBImpl::TEST_FindMinLogContainingOutstandingPrep() {
|
||||
@@ -264,7 +258,7 @@ size_t DBImpl::TEST_LogsWithPrepSize() {
|
||||
}
|
||||
|
||||
uint64_t DBImpl::TEST_FindMinPrepLogReferencedByMemTable() {
|
||||
autovector<MemTable*> empty_list;
|
||||
autovector<ReadOnlyMemTable*> empty_list;
|
||||
return FindMinPrepLogReferencedByMemTable(versions_.get(), empty_list);
|
||||
}
|
||||
|
||||
@@ -328,5 +322,70 @@ size_t DBImpl::TEST_EstimateInMemoryStatsHistorySize() const {
|
||||
InstrumentedMutexLock l(&const_cast<DBImpl*>(this)->stats_history_mutex_);
|
||||
return EstimateInMemoryStatsHistorySize();
|
||||
}
|
||||
|
||||
void DBImpl::TEST_VerifyNoObsoleteFilesCached(
|
||||
bool db_mutex_already_held) const {
|
||||
// This check is somewhat expensive and obscure to make a part of every
|
||||
// unit test in every build variety. Thus, we only enable it for ASAN builds.
|
||||
if (!kMustFreeHeapAllocations) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::optional<InstrumentedMutexLock> l;
|
||||
if (db_mutex_already_held) {
|
||||
mutex_.AssertHeld();
|
||||
} else {
|
||||
l.emplace(&mutex_);
|
||||
}
|
||||
|
||||
if (!opened_successfully_) {
|
||||
// We don't need to pro-actively clean up open files during DB::Open()
|
||||
// if we know we are about to fail and clean up in Close().
|
||||
return;
|
||||
}
|
||||
if (disable_delete_obsolete_files_ > 0) {
|
||||
// For better or worse, DB::Close() is allowed with deletions disabled.
|
||||
// Since we generally associate clean-up of open files with deleting them,
|
||||
// we allow "obsolete" open files when deletions are disabled.
|
||||
return;
|
||||
}
|
||||
|
||||
// Live and "quarantined" files are allowed to be open in table cache
|
||||
std::set<uint64_t> live_and_quar_files;
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
// Iterate over live versions
|
||||
Version* current = cfd->current();
|
||||
Version* ver = current;
|
||||
do {
|
||||
// Sneakily add both SST and blob files to the same list
|
||||
std::vector<uint64_t> live_files_vec;
|
||||
ver->AddLiveFiles(&live_files_vec, &live_files_vec);
|
||||
live_and_quar_files.insert(live_files_vec.begin(), live_files_vec.end());
|
||||
|
||||
ver = ver->Next();
|
||||
} while (ver != current);
|
||||
}
|
||||
{
|
||||
const auto& quar_files = error_handler_.GetFilesToQuarantine();
|
||||
live_and_quar_files.insert(quar_files.begin(), quar_files.end());
|
||||
}
|
||||
auto fn = [&live_and_quar_files](const Slice& key, Cache::ObjectPtr, size_t,
|
||||
const Cache::CacheItemHelper*) {
|
||||
// See TableCache and BlobFileCache
|
||||
assert(key.size() == sizeof(uint64_t));
|
||||
uint64_t file_number;
|
||||
GetUnaligned(reinterpret_cast<const uint64_t*>(key.data()), &file_number);
|
||||
// Assert file is in live/quarantined set
|
||||
if (live_and_quar_files.find(file_number) == live_and_quar_files.end()) {
|
||||
std::cerr << "File " << file_number << " is not live nor quarantined"
|
||||
<< std::endl;
|
||||
assert(false);
|
||||
}
|
||||
};
|
||||
table_cache_->ApplyToAllEntries(fn, {});
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
#endif // NDEBUG
|
||||
|
||||
+42
-20
@@ -43,6 +43,14 @@ uint64_t DBImpl::GetObsoleteSstFilesSize() {
|
||||
return versions_->GetObsoleteSstFilesSize();
|
||||
}
|
||||
|
||||
uint64_t DBImpl::MinOptionsFileNumberToKeep() {
|
||||
mutex_.AssertHeld();
|
||||
if (!min_options_file_numbers_.empty()) {
|
||||
return *min_options_file_numbers_.begin();
|
||||
}
|
||||
return std::numeric_limits<uint64_t>::max();
|
||||
}
|
||||
|
||||
Status DBImpl::DisableFileDeletions() {
|
||||
Status s;
|
||||
int my_disable_delete_obsolete_files;
|
||||
@@ -147,6 +155,7 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
// here but later find newer generated unfinalized files while scanning.
|
||||
job_context->min_pending_output = MinObsoleteSstNumberToKeep();
|
||||
job_context->files_to_quarantine = error_handler_.GetFilesToQuarantine();
|
||||
job_context->min_options_file_number = MinOptionsFileNumberToKeep();
|
||||
|
||||
// Get obsolete files. This function will also update the list of
|
||||
// pending files in VersionSet().
|
||||
@@ -440,14 +449,8 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
// File is being deleted (actually obsolete)
|
||||
auto number = file.metadata->fd.GetNumber();
|
||||
candidate_files.emplace_back(MakeTableFileName(number), file.path);
|
||||
if (handle == nullptr) {
|
||||
// For files not "pinned" in table cache
|
||||
handle = TableCache::Lookup(table_cache_.get(), number);
|
||||
}
|
||||
if (handle) {
|
||||
TableCache::ReleaseObsolete(table_cache_.get(), handle,
|
||||
file.uncache_aggressiveness);
|
||||
}
|
||||
TableCache::ReleaseObsolete(table_cache_.get(), number, handle,
|
||||
file.uncache_aggressiveness);
|
||||
}
|
||||
file.DeleteMetadata();
|
||||
}
|
||||
@@ -498,7 +501,7 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
dbname_);
|
||||
|
||||
// File numbers of most recent two OPTIONS file in candidate_files (found in
|
||||
// previos FindObsoleteFiles(full_scan=true))
|
||||
// previous FindObsoleteFiles(full_scan=true))
|
||||
// At this point, there must not be any duplicate file numbers in
|
||||
// candidate_files.
|
||||
uint64_t optsfile_num1 = std::numeric_limits<uint64_t>::min();
|
||||
@@ -519,6 +522,11 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
}
|
||||
}
|
||||
|
||||
// For remote compactions, we need to keep OPTIONS file that may get
|
||||
// referenced by the remote worker
|
||||
|
||||
optsfile_num2 = std::min(optsfile_num2, state.min_options_file_number);
|
||||
|
||||
// Close WALs before trying to delete them.
|
||||
for (const auto w : state.logs_to_free) {
|
||||
// TODO: maybe check the return value of Close.
|
||||
@@ -558,9 +566,17 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
case kTableFile:
|
||||
// If the second condition is not there, this makes
|
||||
// DontDeletePendingOutputs fail
|
||||
// FIXME: but should NOT keep if it came from sst_delete_files?
|
||||
keep = (sst_live_set.find(number) != sst_live_set.end()) ||
|
||||
number >= state.min_pending_output;
|
||||
if (!keep) {
|
||||
// NOTE: sometimes redundant (if came from sst_delete_files)
|
||||
// We don't know which column family is applicable here so we don't
|
||||
// know what uncache_aggressiveness would be used with
|
||||
// ReleaseObsolete(). Anyway, obsolete files ideally go into
|
||||
// sst_delete_files for better/quicker handling, and this is just a
|
||||
// backstop.
|
||||
TableCache::Evict(table_cache_.get(), number);
|
||||
files_to_del.insert(number);
|
||||
}
|
||||
break;
|
||||
@@ -725,7 +741,8 @@ void DBImpl::DeleteObsoleteFiles() {
|
||||
VersionEdit GetDBRecoveryEditForObsoletingMemTables(
|
||||
VersionSet* vset, const ColumnFamilyData& cfd,
|
||||
const autovector<VersionEdit*>& edit_list,
|
||||
const autovector<MemTable*>& memtables, LogsWithPrepTracker* prep_tracker) {
|
||||
const autovector<ReadOnlyMemTable*>& memtables,
|
||||
LogsWithPrepTracker* prep_tracker) {
|
||||
VersionEdit wal_deletion_edit;
|
||||
uint64_t min_wal_number_to_keep = 0;
|
||||
assert(edit_list.size() > 0);
|
||||
@@ -755,12 +772,12 @@ VersionEdit GetDBRecoveryEditForObsoletingMemTables(
|
||||
}
|
||||
|
||||
uint64_t FindMinPrepLogReferencedByMemTable(
|
||||
VersionSet* vset, const autovector<MemTable*>& memtables_to_flush) {
|
||||
VersionSet* vset, const autovector<ReadOnlyMemTable*>& memtables_to_flush) {
|
||||
uint64_t min_log = 0;
|
||||
|
||||
// we must look through the memtables for two phase transactions
|
||||
// that have been committed but not yet flushed
|
||||
std::unordered_set<MemTable*> memtables_to_flush_set(
|
||||
std::unordered_set<ReadOnlyMemTable*> memtables_to_flush_set(
|
||||
memtables_to_flush.begin(), memtables_to_flush.end());
|
||||
for (auto loop_cfd : *vset->GetColumnFamilySet()) {
|
||||
if (loop_cfd->IsDropped()) {
|
||||
@@ -785,12 +802,12 @@ uint64_t FindMinPrepLogReferencedByMemTable(
|
||||
}
|
||||
|
||||
uint64_t FindMinPrepLogReferencedByMemTable(
|
||||
VersionSet* vset,
|
||||
const autovector<const autovector<MemTable*>*>& memtables_to_flush) {
|
||||
VersionSet* vset, const autovector<const autovector<ReadOnlyMemTable*>*>&
|
||||
memtables_to_flush) {
|
||||
uint64_t min_log = 0;
|
||||
|
||||
std::unordered_set<MemTable*> memtables_to_flush_set;
|
||||
for (const autovector<MemTable*>* memtables : memtables_to_flush) {
|
||||
std::unordered_set<ReadOnlyMemTable*> memtables_to_flush_set;
|
||||
for (const autovector<ReadOnlyMemTable*>* memtables : memtables_to_flush) {
|
||||
memtables_to_flush_set.insert(memtables->begin(), memtables->end());
|
||||
}
|
||||
for (auto loop_cfd : *vset->GetColumnFamilySet()) {
|
||||
@@ -882,7 +899,7 @@ uint64_t PrecomputeMinLogNumberToKeepNon2PC(
|
||||
uint64_t PrecomputeMinLogNumberToKeep2PC(
|
||||
VersionSet* vset, const ColumnFamilyData& cfd_to_flush,
|
||||
const autovector<VersionEdit*>& edit_list,
|
||||
const autovector<MemTable*>& memtables_to_flush,
|
||||
const autovector<ReadOnlyMemTable*>& memtables_to_flush,
|
||||
LogsWithPrepTracker* prep_tracker) {
|
||||
assert(vset != nullptr);
|
||||
assert(prep_tracker != nullptr);
|
||||
@@ -923,7 +940,7 @@ uint64_t PrecomputeMinLogNumberToKeep2PC(
|
||||
uint64_t PrecomputeMinLogNumberToKeep2PC(
|
||||
VersionSet* vset, const autovector<ColumnFamilyData*>& cfds_to_flush,
|
||||
const autovector<autovector<VersionEdit*>>& edit_lists,
|
||||
const autovector<const autovector<MemTable*>*>& memtables_to_flush,
|
||||
const autovector<const autovector<ReadOnlyMemTable*>*>& memtables_to_flush,
|
||||
LogsWithPrepTracker* prep_tracker) {
|
||||
assert(vset != nullptr);
|
||||
assert(prep_tracker != nullptr);
|
||||
@@ -966,7 +983,8 @@ void DBImpl::SetDBId(std::string&& id, bool read_only,
|
||||
}
|
||||
|
||||
Status DBImpl::SetupDBId(const WriteOptions& write_options, bool read_only,
|
||||
bool is_new_db, VersionEdit* version_edit) {
|
||||
bool is_new_db, bool is_retry,
|
||||
VersionEdit* version_edit) {
|
||||
Status s;
|
||||
if (!is_new_db) {
|
||||
// Check for the IDENTITY file and create it if not there or
|
||||
@@ -974,7 +992,11 @@ Status DBImpl::SetupDBId(const WriteOptions& write_options, bool read_only,
|
||||
std::string db_id_in_file;
|
||||
s = fs_->FileExists(IdentityFileName(dbname_), IOOptions(), nullptr);
|
||||
if (s.ok()) {
|
||||
s = GetDbIdentityFromIdentityFile(&db_id_in_file);
|
||||
IOOptions opts;
|
||||
if (is_retry) {
|
||||
opts.verify_and_reconstruct_read = true;
|
||||
}
|
||||
s = GetDbIdentityFromIdentityFile(opts, &db_id_in_file);
|
||||
if (s.ok() && !db_id_in_file.empty()) {
|
||||
if (db_id_.empty()) {
|
||||
// Loaded from file and wasn't already known from manifest
|
||||
|
||||
+38
-25
@@ -301,7 +301,7 @@ Status DBImpl::NewDB(std::vector<std::string>* new_filenames) {
|
||||
VersionEdit new_db_edit;
|
||||
const WriteOptions write_options(Env::IOActivity::kDBOpen);
|
||||
Status s = SetupDBId(write_options, /*read_only=*/false, /*is_new_db=*/true,
|
||||
&new_db_edit);
|
||||
/*is_retry=*/false, &new_db_edit);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -575,6 +575,7 @@ Status DBImpl::Recover(
|
||||
}
|
||||
if (s.ok() && !read_only) {
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
auto& moptions = *cfd->GetLatestMutableCFOptions();
|
||||
// Try to trivially move files down the LSM tree to start from bottommost
|
||||
// level when level_compaction_dynamic_level_bytes is enabled. This should
|
||||
// only be useful when user is migrating to turning on this option.
|
||||
@@ -592,14 +593,14 @@ Status DBImpl::Recover(
|
||||
if (cfd->ioptions()->compaction_style ==
|
||||
CompactionStyle::kCompactionStyleLevel &&
|
||||
cfd->ioptions()->level_compaction_dynamic_level_bytes &&
|
||||
!cfd->GetLatestMutableCFOptions()->disable_auto_compactions) {
|
||||
!moptions.disable_auto_compactions) {
|
||||
int to_level = cfd->ioptions()->num_levels - 1;
|
||||
// last level is reserved
|
||||
// allow_ingest_behind does not support Level Compaction,
|
||||
// and per_key_placement can have infinite compaction loop for Level
|
||||
// Compaction. Adjust to_level here just to be safe.
|
||||
if (cfd->ioptions()->allow_ingest_behind ||
|
||||
cfd->ioptions()->preclude_last_level_data_seconds > 0) {
|
||||
moptions.preclude_last_level_data_seconds > 0) {
|
||||
to_level -= 1;
|
||||
}
|
||||
// Whether this column family has a level trivially moved
|
||||
@@ -675,11 +676,11 @@ Status DBImpl::Recover(
|
||||
// Already set up DB ID in NewDB
|
||||
} else if (immutable_db_options_.write_dbid_to_manifest && recovery_ctx) {
|
||||
VersionEdit edit;
|
||||
s = SetupDBId(write_options, read_only, is_new_db, &edit);
|
||||
s = SetupDBId(write_options, read_only, is_new_db, is_retry, &edit);
|
||||
recovery_ctx->UpdateVersionEdits(
|
||||
versions_->GetColumnFamilySet()->GetDefault(), edit);
|
||||
} else {
|
||||
s = SetupDBId(write_options, read_only, is_new_db, nullptr);
|
||||
s = SetupDBId(write_options, read_only, is_new_db, is_retry, nullptr);
|
||||
}
|
||||
assert(!s.ok() || !db_id_.empty());
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "DB ID: %s\n", db_id_.c_str());
|
||||
@@ -1274,7 +1275,8 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
reader.GetRecordedTimestampSize();
|
||||
status = HandleWriteBatchTimestampSizeDifference(
|
||||
&batch, running_ts_sz, record_ts_sz,
|
||||
TimestampSizeConsistencyMode::kReconcileInconsistency, &new_batch);
|
||||
TimestampSizeConsistencyMode::kReconcileInconsistency, seq_per_batch_,
|
||||
batch_per_txn_, &new_batch);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -1371,6 +1373,9 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
}
|
||||
}
|
||||
}
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Recovered to log #%" PRIu64 " seq #%" PRIu64, wal_number,
|
||||
*next_sequence);
|
||||
|
||||
if (!status.ok() || old_log_record) {
|
||||
if (status.IsNotSupported()) {
|
||||
@@ -1403,10 +1408,6 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
if (corrupted_wal_found != nullptr) {
|
||||
*corrupted_wal_found = true;
|
||||
}
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Point in time recovered to log #%" PRIu64
|
||||
" seq #%" PRIu64,
|
||||
wal_number, *next_sequence);
|
||||
} else {
|
||||
assert(immutable_db_options_.wal_recovery_mode ==
|
||||
WALRecoveryMode::kTolerateCorruptedTailRecords ||
|
||||
@@ -1667,10 +1668,20 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
Arena arena;
|
||||
Status s;
|
||||
TableProperties table_properties;
|
||||
const auto* ucmp = cfd->internal_comparator().user_comparator();
|
||||
assert(ucmp);
|
||||
const size_t ts_sz = ucmp->timestamp_size();
|
||||
const bool logical_strip_timestamp =
|
||||
ts_sz > 0 && !cfd->ioptions()->persist_user_defined_timestamps;
|
||||
{
|
||||
ScopedArenaPtr<InternalIterator> iter(
|
||||
mem->NewIterator(ro, /*seqno_to_time_mapping=*/nullptr, &arena,
|
||||
/*prefix_extractor=*/nullptr));
|
||||
logical_strip_timestamp
|
||||
? mem->NewTimestampStrippingIterator(
|
||||
ro, /*seqno_to_time_mapping=*/nullptr, &arena,
|
||||
/*prefix_extractor=*/nullptr, ts_sz)
|
||||
: mem->NewIterator(ro, /*seqno_to_time_mapping=*/nullptr, &arena,
|
||||
/*prefix_extractor=*/nullptr,
|
||||
/*for_flush=*/true));
|
||||
ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
|
||||
"[%s] [WriteLevel0TableForRecovery]"
|
||||
" Level-0 table #%" PRIu64 ": started",
|
||||
@@ -1705,11 +1716,14 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
range_del_iters;
|
||||
auto range_del_iter =
|
||||
// This is called during recovery, where a live memtable is flushed
|
||||
// directly. In this case, no fragmented tombstone list is cached in
|
||||
// this memtable yet.
|
||||
mem->NewRangeTombstoneIterator(ro, kMaxSequenceNumber,
|
||||
false /* immutable_memtable */);
|
||||
logical_strip_timestamp
|
||||
? mem->NewTimestampStrippingRangeTombstoneIterator(
|
||||
ro, kMaxSequenceNumber, ts_sz)
|
||||
// This is called during recovery, where a live memtable is
|
||||
// flushed directly. In this case, no fragmented tombstone list is
|
||||
// cached in this memtable yet.
|
||||
: mem->NewRangeTombstoneIterator(ro, kMaxSequenceNumber,
|
||||
false /* immutable_memtable */);
|
||||
if (range_del_iter != nullptr) {
|
||||
range_del_iters.emplace_back(range_del_iter);
|
||||
}
|
||||
@@ -1723,10 +1737,11 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
cfd->internal_comparator(), cfd->internal_tbl_prop_coll_factories(),
|
||||
GetCompressionFlush(*cfd->ioptions(), mutable_cf_options),
|
||||
mutable_cf_options.compression_opts, cfd->GetID(), cfd->GetName(),
|
||||
0 /* level */, false /* is_bottommost */,
|
||||
TableFileCreationReason::kRecovery, 0 /* oldest_key_time */,
|
||||
0 /* file_creation_time */, db_id_, db_session_id_,
|
||||
0 /* target_file_size */, meta.fd.GetNumber(), kMaxSequenceNumber);
|
||||
0 /* level */, current_time /* newest_key_time */,
|
||||
false /* is_bottommost */, TableFileCreationReason::kRecovery,
|
||||
0 /* oldest_key_time */, 0 /* file_creation_time */, db_id_,
|
||||
db_session_id_, 0 /* target_file_size */, meta.fd.GetNumber(),
|
||||
kMaxSequenceNumber);
|
||||
Version* version = cfd->current();
|
||||
version->Ref();
|
||||
uint64_t num_input_entries = 0;
|
||||
@@ -1756,7 +1771,7 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
s = io_s;
|
||||
}
|
||||
|
||||
uint64_t total_num_entries = mem->num_entries();
|
||||
uint64_t total_num_entries = mem->NumEntries();
|
||||
if (s.ok() && total_num_entries != num_input_entries) {
|
||||
std::string msg = "Expected " + std::to_string(total_num_entries) +
|
||||
" entries in memtable, but read " +
|
||||
@@ -1795,9 +1810,7 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
|
||||
// For UDT in memtable only feature, move up the cutoff timestamp whenever
|
||||
// a flush happens.
|
||||
const Comparator* ucmp = cfd->user_comparator();
|
||||
size_t ts_sz = ucmp->timestamp_size();
|
||||
if (ts_sz > 0 && !cfd->ioptions()->persist_user_defined_timestamps) {
|
||||
if (logical_strip_timestamp) {
|
||||
Slice mem_newest_udt = mem->GetNewestUDT();
|
||||
std::string full_history_ts_low = cfd->GetFullHistoryTsLow();
|
||||
if (full_history_ts_low.empty() ||
|
||||
|
||||
@@ -265,7 +265,8 @@ Status OpenForReadOnlyCheckExistence(const DBOptions& db_options,
|
||||
const std::shared_ptr<FileSystem>& fs = db_options.env->GetFileSystem();
|
||||
std::string manifest_path;
|
||||
uint64_t manifest_file_number;
|
||||
s = VersionSet::GetCurrentManifestPath(dbname, fs.get(), &manifest_path,
|
||||
s = VersionSet::GetCurrentManifestPath(dbname, fs.get(), /*is_retry=*/false,
|
||||
&manifest_path,
|
||||
&manifest_file_number);
|
||||
} else {
|
||||
// Historic behavior that doesn't necessarily make sense
|
||||
|
||||
@@ -233,7 +233,8 @@ Status DBImplSecondary::RecoverLogFiles(
|
||||
reader->GetRecordedTimestampSize();
|
||||
status = HandleWriteBatchTimestampSizeDifference(
|
||||
&batch, running_ts_sz, record_ts_sz,
|
||||
TimestampSizeConsistencyMode::kVerifyConsistency);
|
||||
TimestampSizeConsistencyMode::kVerifyConsistency, seq_per_batch_,
|
||||
batch_per_txn_);
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
}
|
||||
@@ -247,9 +248,7 @@ Status DBImplSecondary::RecoverLogFiles(
|
||||
if (cfd == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (cfds_changed->count(cfd) == 0) {
|
||||
cfds_changed->insert(cfd);
|
||||
}
|
||||
cfds_changed->insert(cfd);
|
||||
const std::vector<FileMetaData*>& l0_files =
|
||||
cfd->current()->storage_info()->LevelFiles(0);
|
||||
SequenceNumber seq =
|
||||
@@ -951,21 +950,23 @@ Status DB::OpenAndCompact(
|
||||
return s;
|
||||
}
|
||||
|
||||
// 2. Load the options from latest OPTIONS file
|
||||
// 2. Load the options
|
||||
DBOptions db_options;
|
||||
ConfigOptions config_options;
|
||||
config_options.env = override_options.env;
|
||||
std::vector<ColumnFamilyDescriptor> all_column_families;
|
||||
s = LoadLatestOptions(config_options, name, &db_options,
|
||||
&all_column_families);
|
||||
// In a very rare scenario, loading options may fail if the options changed by
|
||||
// the primary host at the same time. Just retry once for now.
|
||||
if (!s.ok()) {
|
||||
s = LoadLatestOptions(config_options, name, &db_options,
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"DBImplSecondary::OpenAndCompact::BeforeLoadingOptions:0",
|
||||
&compaction_input.options_file_number);
|
||||
TEST_SYNC_POINT("DBImplSecondary::OpenAndCompact::BeforeLoadingOptions:1");
|
||||
std::string options_file_name =
|
||||
OptionsFileName(name, compaction_input.options_file_number);
|
||||
|
||||
s = LoadOptionsFromFile(config_options, options_file_name, &db_options,
|
||||
&all_column_families);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
// 3. Override pointer configurations in DBOptions with
|
||||
|
||||
+258
-41
@@ -12,6 +12,7 @@
|
||||
#include "db/error_handler.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "logging/logging.h"
|
||||
#include "memtable/wbwi_memtable.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "test_util/sync_point.h"
|
||||
@@ -189,16 +190,137 @@ Status DBImpl::WriteWithCallback(const WriteOptions& write_options,
|
||||
return s;
|
||||
}
|
||||
|
||||
// The main write queue. This is the only write queue that updates LastSequence.
|
||||
// When using one write queue, the same sequence also indicates the last
|
||||
// published sequence.
|
||||
Status DBImpl::IngestWBWI(std::shared_ptr<WriteBatchWithIndex> wbwi,
|
||||
const WBWIMemTable::SeqnoRange& assigned_seqno,
|
||||
uint64_t prep_log,
|
||||
SequenceNumber last_seqno_after_ingest,
|
||||
bool memtable_updated, bool ignore_missing_cf) {
|
||||
// Keys in new memtable have seqno > last_seqno_after_ingest >= keys in wbwi.
|
||||
assert(assigned_seqno.upper_bound <= last_seqno_after_ingest);
|
||||
// Keys in the current memtable have seqno <= LastSequence() < keys in wbwi.
|
||||
assert(assigned_seqno.lower_bound > versions_->LastSequence());
|
||||
autovector<ReadOnlyMemTable*> memtables;
|
||||
autovector<ColumnFamilyData*> cfds;
|
||||
InstrumentedMutexLock lock(&mutex_);
|
||||
ColumnFamilySet* cf_set = versions_->GetColumnFamilySet();
|
||||
|
||||
// Create WBWIMemTables
|
||||
for (const auto [cf_id, stat] : wbwi->GetCFStats()) {
|
||||
ColumnFamilyData* cfd = cf_set->GetColumnFamily(cf_id);
|
||||
if (!cfd) {
|
||||
if (ignore_missing_cf) {
|
||||
continue;
|
||||
}
|
||||
for (auto mem : memtables) {
|
||||
mem->Unref();
|
||||
delete mem;
|
||||
}
|
||||
for (auto cfd_ptr : cfds) {
|
||||
cfd_ptr->UnrefAndTryDelete();
|
||||
}
|
||||
Status s = Status::InvalidArgument(
|
||||
"Invalid column family id from WriteBatchWithIndex: " +
|
||||
std::to_string(cf_id));
|
||||
if (memtable_updated) {
|
||||
s = Status::Corruption(
|
||||
"Part of the write batch is applied. Memtable is in a inconsistent "
|
||||
"state. " +
|
||||
s.ToString());
|
||||
error_handler_.SetBGError(s, BackgroundErrorReason::kMemTable);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
WBWIMemTable* wbwi_memtable =
|
||||
new WBWIMemTable(wbwi, cfd->user_comparator(), cf_id, cfd->ioptions(),
|
||||
cfd->GetLatestMutableCFOptions(), stat);
|
||||
wbwi_memtable->Ref();
|
||||
wbwi_memtable->AssignSequenceNumbers(assigned_seqno);
|
||||
// This is needed to keep the WAL that contains Prepare alive until
|
||||
// committed data in this memtable is persisted.
|
||||
wbwi_memtable->SetMinPrepLog(prep_log);
|
||||
memtables.push_back(wbwi_memtable);
|
||||
cfd->Ref();
|
||||
cfds.push_back(cfd);
|
||||
}
|
||||
|
||||
// Stop writes to the DB by entering both write threads
|
||||
WriteThread::Writer nonmem_w;
|
||||
if (two_write_queues_) {
|
||||
nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_);
|
||||
}
|
||||
WaitForPendingWrites();
|
||||
|
||||
// Switch memtable and add WBWIMemTables
|
||||
Status s;
|
||||
for (size_t i = 0; i < memtables.size(); ++i) {
|
||||
assert(!immutable_db_options_.atomic_flush);
|
||||
// NOTE: to support atomic flush, need to call
|
||||
// SelectColumnFamiliesForAtomicFlush()
|
||||
WriteContext write_context;
|
||||
// TODO: not switch on empty memtable, may need to update metadata
|
||||
// like NextLogNumber(), earliest_seqno and memtable id.
|
||||
s = SwitchMemtable(cfds[i], &write_context, memtables[i],
|
||||
last_seqno_after_ingest);
|
||||
if (!s.ok()) {
|
||||
// SwitchMemtable() can only fail if a new WAL is to be created, this
|
||||
// should only happen for the first call to SwitchMemtable(). log will
|
||||
// be empty and no new WAL is created for the rest of the calls.
|
||||
assert(i == 0);
|
||||
if (i != 0 || memtable_updated) {
|
||||
// escalate error to non-recoverable
|
||||
s = Status::Corruption(
|
||||
"Part of the write batch is applied. Memtable is in a inconsistent "
|
||||
"state. " +
|
||||
s.ToString());
|
||||
error_handler_.SetBGError(s, BackgroundErrorReason::kMemTable);
|
||||
} else {
|
||||
// SwitchMemtable() already sets appropriate bg error
|
||||
}
|
||||
for (size_t j = i; j < memtables.size(); j++) {
|
||||
memtables[j]->Unref();
|
||||
delete memtables[j];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < cfds.size(); ++i) {
|
||||
if (cfds[i]->UnrefAndTryDelete()) {
|
||||
cfds[i] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// exit the second queue before returning
|
||||
if (two_write_queues_) {
|
||||
nonmem_write_thread_.ExitUnbatched(&nonmem_w);
|
||||
}
|
||||
if (s.ok()) {
|
||||
// Trigger flushes for the new immutable memtables.
|
||||
for (const auto cfd : cfds) {
|
||||
if (cfd == nullptr) {
|
||||
continue;
|
||||
}
|
||||
cfd->imm()->FlushRequested();
|
||||
FlushRequest flush_req;
|
||||
// TODO: a new flush reason for ingesting memtable
|
||||
GenerateFlushRequest({cfd}, FlushReason::kExternalFileIngestion,
|
||||
&flush_req);
|
||||
EnqueuePendingFlush(flush_req);
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
WriteBatch* my_batch, WriteCallback* callback,
|
||||
UserWriteCallback* user_write_cb, uint64_t* log_used,
|
||||
uint64_t log_ref, bool disable_memtable,
|
||||
uint64_t* seq_used, size_t batch_cnt,
|
||||
PreReleaseCallback* pre_release_callback,
|
||||
PostMemTableCallback* post_memtable_callback) {
|
||||
PostMemTableCallback* post_memtable_callback,
|
||||
std::shared_ptr<WriteBatchWithIndex> wbwi,
|
||||
uint64_t prep_log) {
|
||||
assert(!seq_per_batch_ || batch_cnt != 0);
|
||||
assert(my_batch == nullptr || my_batch->Count() == 0 ||
|
||||
write_options.protection_bytes_per_key == 0 ||
|
||||
@@ -287,6 +409,23 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
return Status::NotSupported(
|
||||
"DeleteRange is not compatible with row cache.");
|
||||
}
|
||||
if (wbwi) {
|
||||
assert(prep_log > 0);
|
||||
// Used only in WriteCommittedTxn::CommitInternal() with no `callback`.
|
||||
assert(!callback);
|
||||
if (immutable_db_options_.unordered_write) {
|
||||
return Status::NotSupported(
|
||||
"Ingesting WriteBatch does not support unordered_write");
|
||||
}
|
||||
if (immutable_db_options_.enable_pipelined_write) {
|
||||
return Status::NotSupported(
|
||||
"Ingesting WriteBatch does not support pipelined_write");
|
||||
}
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
return Status::NotSupported(
|
||||
"Ingesting WriteBatch does not support atomic_flush");
|
||||
}
|
||||
}
|
||||
// Otherwise IsLatestPersistentState optimization does not make sense
|
||||
assert(!WriteBatchInternal::IsLatestPersistentState(my_batch) ||
|
||||
disable_memtable);
|
||||
@@ -344,7 +483,8 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
PERF_TIMER_GUARD(write_pre_and_post_process_time);
|
||||
WriteThread::Writer w(write_options, my_batch, callback, user_write_cb,
|
||||
log_ref, disable_memtable, batch_cnt,
|
||||
pre_release_callback, post_memtable_callback);
|
||||
pre_release_callback, post_memtable_callback,
|
||||
/*_ingest_wbwi=*/wbwi != nullptr);
|
||||
StopWatch write_sw(immutable_db_options_.clock, stats_, DB_WRITE);
|
||||
|
||||
write_thread_.JoinBatchGroup(&w);
|
||||
@@ -441,6 +581,9 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
TEST_SYNC_POINT("DBImpl::WriteImpl:BeforeLeaderEnters");
|
||||
last_batch_group_size_ =
|
||||
write_thread_.EnterAsBatchGroupLeader(&w, &write_group);
|
||||
if (wbwi) {
|
||||
assert(write_group.size == 1);
|
||||
}
|
||||
|
||||
IOStatus io_s;
|
||||
Status pre_release_cb_status;
|
||||
@@ -494,10 +637,25 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
// Note about seq_per_batch_: either disableWAL is set for the entire write
|
||||
// group or not. In either case we inc seq for each write batch with no
|
||||
// failed callback. This means that there could be a batch with
|
||||
// disalbe_memtable in between; although we do not write this batch to
|
||||
// disable_memtable in between; although we do not write this batch to
|
||||
// memtable it still consumes a seq. Otherwise, if !seq_per_batch_, we inc
|
||||
// the seq per valid written key to mem.
|
||||
size_t seq_inc = seq_per_batch_ ? valid_batches : total_count;
|
||||
if (wbwi) {
|
||||
// Reserve sequence numbers for the ingested memtable. We need to reserve
|
||||
// at lease this amount for recovery. During recovery,
|
||||
// transactions do not commit by ingesting WBWI. The sequence number
|
||||
// associated with the commit entry in WAL is used as the starting
|
||||
// sequence number for inserting into memtable. We need to reserve
|
||||
// enough sequence numbers here (at least the number of operations
|
||||
// in write batch) to assign to memtable entries for this transaction.
|
||||
// This prevents updates in different transactions from using out-of-order
|
||||
// sequence numbers or the same key+seqno.
|
||||
//
|
||||
// WBWI ingestion requires not grouping writes, so we don't need to
|
||||
// consider incrementing sequence number for WBWI from other writers.
|
||||
seq_inc += wbwi->GetWriteBatch()->Count();
|
||||
}
|
||||
|
||||
const bool concurrent_update = two_write_queues_;
|
||||
// Update stats while we are an exclusive group leader, so we know
|
||||
@@ -674,6 +832,27 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
// handle exit, false means somebody else did
|
||||
should_exit_batch_group = write_thread_.CompleteParallelMemTableWriter(&w);
|
||||
}
|
||||
if (wbwi) {
|
||||
if (status.ok() && w.status.ok()) {
|
||||
// w.batch contains (potentially empty) commit time batch updates,
|
||||
// only ingest wbwi if w.batch is applied to memtable successfully
|
||||
assert(wbwi->GetWriteBatch()->Count() > 0);
|
||||
|
||||
uint32_t memtable_update_count = w.batch->Count();
|
||||
SequenceNumber lb = versions_->LastSequence() + memtable_update_count + 1;
|
||||
SequenceNumber ub = versions_->LastSequence() + memtable_update_count +
|
||||
wbwi->GetWriteBatch()->Count();
|
||||
assert(ub == last_sequence);
|
||||
if (two_write_queues_) {
|
||||
assert(ub <= versions_->LastAllocatedSequence());
|
||||
}
|
||||
status = IngestWBWI(wbwi, {/*lower_bound=*/lb, /*upper_bound=*/ub},
|
||||
prep_log, last_sequence,
|
||||
/*memtable_updated=*/memtable_update_count > 0,
|
||||
write_options.ignore_missing_column_families);
|
||||
}
|
||||
}
|
||||
|
||||
if (should_exit_batch_group) {
|
||||
if (status.ok()) {
|
||||
for (auto* tmp_w : write_group) {
|
||||
@@ -687,7 +866,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
}
|
||||
}
|
||||
// Note: if we are to resume after non-OK statuses we need to revisit how
|
||||
// we reacts to non-OK statuses here.
|
||||
// we react to non-OK statuses here.
|
||||
versions_->SetLastSequence(last_sequence);
|
||||
}
|
||||
MemTableInsertStatusCheck(w.status);
|
||||
@@ -735,17 +914,6 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
size_t total_byte_size = 0;
|
||||
|
||||
if (w.status.ok()) {
|
||||
// TODO: this use of operator bool on `tracer_` can avoid unnecessary lock
|
||||
// grabs but does not seem thread-safe.
|
||||
if (tracer_) {
|
||||
InstrumentedMutexLock lock(&trace_mutex_);
|
||||
if (tracer_ != nullptr && tracer_->IsWriteOrderPreserved()) {
|
||||
for (auto* writer : wal_write_group) {
|
||||
// TODO: maybe handle the tracing status?
|
||||
tracer_->Write(writer->batch).PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
}
|
||||
SequenceNumber next_sequence = current_sequence;
|
||||
for (auto* writer : wal_write_group) {
|
||||
assert(writer);
|
||||
@@ -760,6 +928,22 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: this use of operator bool on `tracer_` can avoid unnecessary lock
|
||||
// grabs but does not seem thread-safe.
|
||||
if (tracer_) {
|
||||
InstrumentedMutexLock lock(&trace_mutex_);
|
||||
if (tracer_ != nullptr && tracer_->IsWriteOrderPreserved()) {
|
||||
for (auto* writer : wal_write_group) {
|
||||
if (writer->CallbackFailed()) {
|
||||
// When optimisitc txn conflict checking fails, we should
|
||||
// not record to trace.
|
||||
continue;
|
||||
}
|
||||
// TODO: maybe handle the tracing status?
|
||||
tracer_->Write(writer->batch).PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (w.disable_wal) {
|
||||
has_unpersisted_data_.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
@@ -1005,19 +1189,6 @@ Status DBImpl::WriteImplWALOnly(
|
||||
WriteThread::WriteGroup write_group;
|
||||
uint64_t last_sequence;
|
||||
write_thread->EnterAsBatchGroupLeader(&w, &write_group);
|
||||
// Note: no need to update last_batch_group_size_ here since the batch writes
|
||||
// to WAL only
|
||||
// TODO: this use of operator bool on `tracer_` can avoid unnecessary lock
|
||||
// grabs but does not seem thread-safe.
|
||||
if (tracer_) {
|
||||
InstrumentedMutexLock lock(&trace_mutex_);
|
||||
if (tracer_ != nullptr && tracer_->IsWriteOrderPreserved()) {
|
||||
for (auto* writer : write_group) {
|
||||
// TODO: maybe handle the tracing status?
|
||||
tracer_->Write(writer->batch).PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t pre_release_callback_cnt = 0;
|
||||
size_t total_byte_size = 0;
|
||||
@@ -1032,6 +1203,23 @@ Status DBImpl::WriteImplWALOnly(
|
||||
}
|
||||
}
|
||||
|
||||
// Note: no need to update last_batch_group_size_ here since the batch writes
|
||||
// to WAL only
|
||||
// TODO: this use of operator bool on `tracer_` can avoid unnecessary lock
|
||||
// grabs but does not seem thread-safe.
|
||||
if (tracer_) {
|
||||
InstrumentedMutexLock lock(&trace_mutex_);
|
||||
if (tracer_ != nullptr && tracer_->IsWriteOrderPreserved()) {
|
||||
for (auto* writer : write_group) {
|
||||
if (writer->CallbackFailed()) {
|
||||
continue;
|
||||
}
|
||||
// TODO: maybe handle the tracing status?
|
||||
tracer_->Write(writer->batch).PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bool concurrent_update = true;
|
||||
// Update stats while we are an exclusive group leader, so we know
|
||||
// that nobody else can be writing to these particular stats.
|
||||
@@ -1601,6 +1789,8 @@ IOStatus DBImpl::ConcurrentWriteToWAL(
|
||||
Status DBImpl::WriteRecoverableState() {
|
||||
mutex_.AssertHeld();
|
||||
if (!cached_recoverable_state_empty_) {
|
||||
// Only for write-prepared and write-unprepared.
|
||||
assert(seq_per_batch_);
|
||||
bool dont_care_bool;
|
||||
SequenceNumber next_seq;
|
||||
if (two_write_queues_) {
|
||||
@@ -2193,16 +2383,13 @@ void DBImpl::NotifyOnMemTableSealed(ColumnFamilyData* /*cfd*/,
|
||||
mutex_.Lock();
|
||||
}
|
||||
|
||||
// REQUIRES: mutex_ is held
|
||||
// REQUIRES: this thread is currently at the front of the writer queue
|
||||
// REQUIRES: this thread is currently at the front of the 2nd writer queue if
|
||||
// two_write_queues_ is true (This is to simplify the reasoning.)
|
||||
Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
ReadOnlyMemTable* new_imm,
|
||||
SequenceNumber last_seqno) {
|
||||
mutex_.AssertHeld();
|
||||
assert(lock_wal_count_ == 0);
|
||||
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
const ReadOptions read_options;
|
||||
const WriteOptions write_options;
|
||||
|
||||
log::Writer* new_log = nullptr;
|
||||
@@ -2238,12 +2425,13 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
const MutableCFOptions mutable_cf_options = *cfd->GetLatestMutableCFOptions();
|
||||
|
||||
// Set memtable_info for memtable sealed callback
|
||||
// TODO: memtable_info for `new_imm`
|
||||
MemTableInfo memtable_info;
|
||||
memtable_info.cf_name = cfd->GetName();
|
||||
memtable_info.first_seqno = cfd->mem()->GetFirstSequenceNumber();
|
||||
memtable_info.earliest_seqno = cfd->mem()->GetEarliestSequenceNumber();
|
||||
memtable_info.num_entries = cfd->mem()->num_entries();
|
||||
memtable_info.num_deletes = cfd->mem()->num_deletes();
|
||||
memtable_info.num_entries = cfd->mem()->NumEntries();
|
||||
memtable_info.num_deletes = cfd->mem()->NumDeletion();
|
||||
if (!cfd->ioptions()->persist_user_defined_timestamps &&
|
||||
cfd->user_comparator()->timestamp_size() > 0) {
|
||||
const Slice& newest_udt = cfd->mem()->GetNewestUDT();
|
||||
@@ -2265,8 +2453,20 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
SequenceNumber seq = versions_->LastSequence();
|
||||
new_mem = cfd->ConstructNewMemtable(mutable_cf_options, seq);
|
||||
// FIXME: from the comment for GetEarliestSequenceNumber(), any key with
|
||||
// seqno >= earliest_seqno should be in this or later memtable. This means
|
||||
// we should use LastSequence() + 1 or last_seqno + 1 here. And it needs to
|
||||
// be incremented with file ingestion and other operations that consumes
|
||||
// sequence number.
|
||||
SequenceNumber seq;
|
||||
if (new_imm) {
|
||||
assert(last_seqno > versions_->LastSequence());
|
||||
seq = last_seqno;
|
||||
} else {
|
||||
seq = versions_->LastSequence();
|
||||
}
|
||||
new_mem =
|
||||
cfd->ConstructNewMemtable(mutable_cf_options, /*earliest_seq=*/seq);
|
||||
context->superversion_context.NewSuperVersion();
|
||||
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
@@ -2348,6 +2548,8 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
versions_->PreComputeMinLogNumberWithUnflushedData(logfile_number_);
|
||||
if (min_wal_number_to_keep >
|
||||
versions_->GetWalSet().GetMinWalNumberToKeep()) {
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
const ReadOptions read_options;
|
||||
// Get a snapshot of the empty column families.
|
||||
// LogAndApply may release and reacquire db
|
||||
// mutex, during that period, column family may become empty (e.g. its
|
||||
@@ -2405,6 +2607,18 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
cfd->mem()->SetNextLogNumber(logfile_number_);
|
||||
assert(new_mem != nullptr);
|
||||
cfd->imm()->Add(cfd->mem(), &context->memtables_to_free_);
|
||||
if (new_imm) {
|
||||
// Need to assign memtable id here before SetMemtable() below assigns id to
|
||||
// the new live memtable
|
||||
cfd->AssignMemtableID(new_imm);
|
||||
// NOTE: new_imm and cfd->mem() references the same WAL and has the same
|
||||
// NextLogNumber(). They should be flushed together. For non-atomic-flush,
|
||||
// we always try to flush all immutable memtable. For atomic flush, these
|
||||
// two memtables will be marked eligible for flush in the same call to
|
||||
// AssignAtomicFlushSeq().
|
||||
new_imm->SetNextLogNumber(logfile_number_);
|
||||
cfd->imm()->Add(new_imm, &context->memtables_to_free_);
|
||||
}
|
||||
new_mem->Ref();
|
||||
cfd->SetMemtable(new_mem);
|
||||
InstallSuperVersionAndScheduleWork(cfd, &context->superversion_context,
|
||||
@@ -2417,6 +2631,9 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
// that is okay. If we did, it most likely means that s was already an error.
|
||||
// In any case, ignore any unchecked error for i_os here.
|
||||
io_s.PermitUncheckedError();
|
||||
// We guarantee that if a non-ok status is returned, `new_imm` was not added
|
||||
// to the db.
|
||||
assert(s.ok());
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
+161
-9
@@ -27,12 +27,14 @@ class CorruptionFS : public FileSystemWrapper {
|
||||
num_writable_file_errors_(0),
|
||||
corruption_trigger_(INT_MAX),
|
||||
read_count_(0),
|
||||
corrupt_offset_(0),
|
||||
corrupt_len_(0),
|
||||
rnd_(300),
|
||||
fs_buffer_(fs_buffer),
|
||||
verify_read_(verify_read) {}
|
||||
~CorruptionFS() override {
|
||||
// Assert that the corruption was reset, which means it got triggered
|
||||
assert(corruption_trigger_ == INT_MAX);
|
||||
assert(corruption_trigger_ == INT_MAX || corrupt_len_ > 0);
|
||||
}
|
||||
const char* Name() const override { return "ErrorEnv"; }
|
||||
|
||||
@@ -48,8 +50,10 @@ class CorruptionFS : public FileSystemWrapper {
|
||||
}
|
||||
|
||||
void SetCorruptionTrigger(const int trigger) {
|
||||
MutexLock l(&mutex_);
|
||||
corruption_trigger_ = trigger;
|
||||
read_count_ = 0;
|
||||
corrupt_fname_.clear();
|
||||
}
|
||||
|
||||
IOStatus NewRandomAccessFile(const std::string& fname,
|
||||
@@ -58,25 +62,31 @@ class CorruptionFS : public FileSystemWrapper {
|
||||
IODebugContext* dbg) override {
|
||||
class CorruptionRandomAccessFile : public FSRandomAccessFileOwnerWrapper {
|
||||
public:
|
||||
CorruptionRandomAccessFile(CorruptionFS& fs,
|
||||
CorruptionRandomAccessFile(CorruptionFS& fs, const std::string& fname,
|
||||
std::unique_ptr<FSRandomAccessFile>& file)
|
||||
: FSRandomAccessFileOwnerWrapper(std::move(file)), fs_(fs) {}
|
||||
: FSRandomAccessFileOwnerWrapper(std::move(file)),
|
||||
fs_(fs),
|
||||
fname_(fname) {}
|
||||
|
||||
IOStatus Read(uint64_t offset, size_t len, const IOOptions& opts,
|
||||
Slice* result, char* scratch,
|
||||
IODebugContext* dbg) const override {
|
||||
IOStatus s = target()->Read(offset, len, opts, result, scratch, dbg);
|
||||
if (opts.verify_and_reconstruct_read) {
|
||||
fs_.MaybeResetOverlapWithCorruptedChunk(fname_, offset,
|
||||
result->size());
|
||||
return s;
|
||||
}
|
||||
|
||||
MutexLock l(&fs_.mutex_);
|
||||
if (s.ok() && ++fs_.read_count_ >= fs_.corruption_trigger_) {
|
||||
fs_.read_count_ = 0;
|
||||
fs_.corruption_trigger_ = INT_MAX;
|
||||
char* data = const_cast<char*>(result->data());
|
||||
std::memcpy(
|
||||
data,
|
||||
fs_.rnd_.RandomString(static_cast<int>(result->size())).c_str(),
|
||||
result->size());
|
||||
fs_.SetCorruptedChunk(fname_, offset, result->size());
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -101,14 +111,76 @@ class CorruptionFS : public FileSystemWrapper {
|
||||
return IOStatus::OK();
|
||||
}
|
||||
|
||||
IOStatus Prefetch(uint64_t /*offset*/, size_t /*n*/,
|
||||
const IOOptions& /*options*/,
|
||||
IODebugContext* /*dbg*/) override {
|
||||
return IOStatus::NotSupported("Prefetch");
|
||||
}
|
||||
|
||||
private:
|
||||
CorruptionFS& fs_;
|
||||
std::string fname_;
|
||||
};
|
||||
|
||||
std::unique_ptr<FSRandomAccessFile> file;
|
||||
IOStatus s = target()->NewRandomAccessFile(fname, opts, &file, dbg);
|
||||
EXPECT_OK(s);
|
||||
result->reset(new CorruptionRandomAccessFile(*this, file));
|
||||
result->reset(new CorruptionRandomAccessFile(*this, fname, file));
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
IOStatus NewSequentialFile(const std::string& fname,
|
||||
const FileOptions& file_opts,
|
||||
std::unique_ptr<FSSequentialFile>* result,
|
||||
IODebugContext* dbg) override {
|
||||
class CorruptionSequentialFile : public FSSequentialFileOwnerWrapper {
|
||||
public:
|
||||
CorruptionSequentialFile(CorruptionFS& fs, const std::string& fname,
|
||||
std::unique_ptr<FSSequentialFile>& file)
|
||||
: FSSequentialFileOwnerWrapper(std::move(file)),
|
||||
fs_(fs),
|
||||
fname_(fname),
|
||||
offset_(0) {}
|
||||
|
||||
IOStatus Read(size_t len, const IOOptions& opts, Slice* result,
|
||||
char* scratch, IODebugContext* dbg) override {
|
||||
IOStatus s = target()->Read(len, opts, result, scratch, dbg);
|
||||
if (result->size() == 0 ||
|
||||
fname_.find("IDENTITY") != std::string::npos) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (opts.verify_and_reconstruct_read) {
|
||||
fs_.MaybeResetOverlapWithCorruptedChunk(fname_, offset_,
|
||||
result->size());
|
||||
return s;
|
||||
}
|
||||
|
||||
MutexLock l(&fs_.mutex_);
|
||||
if (s.ok() && ++fs_.read_count_ >= fs_.corruption_trigger_) {
|
||||
fs_.corruption_trigger_ = INT_MAX;
|
||||
char* data = const_cast<char*>(result->data());
|
||||
std::memcpy(
|
||||
data,
|
||||
fs_.rnd_.RandomString(static_cast<int>(result->size())).c_str(),
|
||||
result->size());
|
||||
fs_.SetCorruptedChunk(fname_, offset_, result->size());
|
||||
}
|
||||
offset_ += result->size();
|
||||
return s;
|
||||
}
|
||||
|
||||
private:
|
||||
CorruptionFS& fs_;
|
||||
std::string fname_;
|
||||
size_t offset_;
|
||||
};
|
||||
|
||||
std::unique_ptr<FSSequentialFile> file;
|
||||
IOStatus s = target()->NewSequentialFile(fname, file_opts, &file, dbg);
|
||||
EXPECT_OK(s);
|
||||
result->reset(new CorruptionSequentialFile(*this, fname, file));
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -123,12 +195,40 @@ class CorruptionFS : public FileSystemWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
void SetCorruptedChunk(const std::string& fname, size_t offset, size_t len) {
|
||||
assert(corrupt_fname_.empty());
|
||||
|
||||
corrupt_fname_ = fname;
|
||||
corrupt_offset_ = offset;
|
||||
corrupt_len_ = len;
|
||||
}
|
||||
|
||||
void MaybeResetOverlapWithCorruptedChunk(const std::string& fname,
|
||||
size_t offset, size_t len) {
|
||||
if (fname == corrupt_fname_ &&
|
||||
((offset <= corrupt_offset_ && (offset + len) > corrupt_offset_) ||
|
||||
(offset >= corrupt_offset_ &&
|
||||
offset < (corrupt_offset_ + corrupt_len_)))) {
|
||||
corrupt_fname_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool VerifyRetry() { return corrupt_len_ > 0 && corrupt_fname_.empty(); }
|
||||
|
||||
int read_count() { return read_count_; }
|
||||
|
||||
int corruption_trigger() { return corruption_trigger_; }
|
||||
|
||||
private:
|
||||
int corruption_trigger_;
|
||||
int read_count_;
|
||||
std::string corrupt_fname_;
|
||||
size_t corrupt_offset_;
|
||||
size_t corrupt_len_;
|
||||
Random rnd_;
|
||||
bool fs_buffer_;
|
||||
bool verify_read_;
|
||||
port::Mutex mutex_;
|
||||
};
|
||||
} // anonymous namespace
|
||||
|
||||
@@ -717,6 +817,7 @@ class DBIOCorruptionTest
|
||||
bbto.num_file_reads_for_auto_readahead = 0;
|
||||
options_.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
options_.disable_auto_compactions = true;
|
||||
options_.max_file_opening_threads = 0;
|
||||
|
||||
Reopen(options_);
|
||||
}
|
||||
@@ -857,8 +958,8 @@ TEST_P(DBIOCorruptionTest, FlushReadCorruptionRetry) {
|
||||
Status s = Flush();
|
||||
if (std::get<2>(GetParam())) {
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 1);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_SUCCESS_COUNT),
|
||||
ASSERT_GT(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 1);
|
||||
ASSERT_GT(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_SUCCESS_COUNT),
|
||||
1);
|
||||
|
||||
std::string val;
|
||||
@@ -885,8 +986,8 @@ TEST_P(DBIOCorruptionTest, ManifestCorruptionRetry) {
|
||||
|
||||
if (std::get<2>(GetParam())) {
|
||||
ASSERT_OK(ReopenDB());
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 1);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_SUCCESS_COUNT),
|
||||
ASSERT_GT(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 1);
|
||||
ASSERT_GT(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_SUCCESS_COUNT),
|
||||
1);
|
||||
} else {
|
||||
ASSERT_EQ(ReopenDB(), Status::Corruption());
|
||||
@@ -970,6 +1071,57 @@ TEST_P(DBIOCorruptionTest, TablePropertiesCorruptionRetry) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_P(DBIOCorruptionTest, DBOpenReadCorruptionRetry) {
|
||||
if (!std::get<2>(GetParam())) {
|
||||
return;
|
||||
}
|
||||
CorruptionFS* fs =
|
||||
static_cast<CorruptionFS*>(env_guard_->GetFileSystem().get());
|
||||
|
||||
for (int sst = 0; sst < 3; ++sst) {
|
||||
for (int key = 0; key < 100; ++key) {
|
||||
std::stringstream ss;
|
||||
ss << std::setw(3) << 100 * sst + key;
|
||||
ASSERT_OK(Put("key" + ss.str(), "val" + ss.str()));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
Close();
|
||||
|
||||
// DB open will create table readers unless we reduce the table cache
|
||||
// capacity.
|
||||
// SanitizeOptions will set max_open_files to minimum of 20. Table cache
|
||||
// is allocated with max_open_files - 10 as capacity. So override
|
||||
// max_open_files to 11 so table cache capacity will become 1. This will
|
||||
// prevent file open during DB open and force the file to be opened
|
||||
// during MultiGet
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"SanitizeOptions::AfterChangeMaxOpenFiles", [&](void* arg) {
|
||||
int* max_open_files = (int*)arg;
|
||||
*max_open_files = 11;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Progressively increase the IO count trigger for corruption, and verify
|
||||
// that it was retried
|
||||
int corruption_trigger = 1;
|
||||
fs->SetCorruptionTrigger(corruption_trigger);
|
||||
do {
|
||||
fs->SetCorruptionTrigger(corruption_trigger);
|
||||
ASSERT_OK(ReopenDB());
|
||||
for (int sst = 0; sst < 3; ++sst) {
|
||||
for (int key = 0; key < 100; ++key) {
|
||||
std::stringstream ss;
|
||||
ss << std::setw(3) << 100 * sst + key;
|
||||
ASSERT_EQ(Get("key" + ss.str()), "val" + ss.str());
|
||||
}
|
||||
}
|
||||
// Verify that the injected corruption was repaired
|
||||
ASSERT_TRUE(fs->VerifyRetry());
|
||||
corruption_trigger++;
|
||||
} while (fs->corruption_trigger() == INT_MAX);
|
||||
}
|
||||
|
||||
// The parameters are - 1. Use FS provided buffer, 2. Use async IO ReadOption,
|
||||
// 3. Retry with verify_and_reconstruct_read IOOption
|
||||
INSTANTIATE_TEST_CASE_P(DBIOCorruptionTest, DBIOCorruptionTest,
|
||||
|
||||
+119
-85
@@ -52,7 +52,9 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
user_comparator_(cmp),
|
||||
merge_operator_(ioptions.merge_operator.get()),
|
||||
iter_(iter),
|
||||
version_(version),
|
||||
blob_reader_(version, read_options.read_tier,
|
||||
read_options.verify_checksums, read_options.fill_cache,
|
||||
read_options.io_activity),
|
||||
read_callback_(read_callback),
|
||||
sequence_(s),
|
||||
statistics_(ioptions.stats),
|
||||
@@ -71,13 +73,10 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
expect_total_order_inner_iter_(prefix_extractor_ == nullptr ||
|
||||
read_options.total_order_seek ||
|
||||
read_options.auto_prefix_mode),
|
||||
read_tier_(read_options.read_tier),
|
||||
fill_cache_(read_options.fill_cache),
|
||||
verify_checksums_(read_options.verify_checksums),
|
||||
expose_blob_index_(expose_blob_index),
|
||||
allow_unprepared_value_(read_options.allow_unprepared_value),
|
||||
is_blob_(false),
|
||||
arena_mode_(arena_mode),
|
||||
io_activity_(read_options.io_activity),
|
||||
cfh_(cfh),
|
||||
timestamp_ub_(read_options.timestamp),
|
||||
timestamp_lb_(read_options.iter_start_ts),
|
||||
@@ -151,7 +150,7 @@ void DBIter::Next() {
|
||||
PERF_CPU_TIMER_GUARD(iter_next_cpu_nanos, clock_);
|
||||
// Release temporarily pinned blocks from last operation
|
||||
ReleaseTempPinnedData();
|
||||
ResetBlobValue();
|
||||
ResetBlobData();
|
||||
ResetValueAndColumns();
|
||||
local_stats_.skip_count_ += num_internal_keys_skipped_;
|
||||
local_stats_.skip_count_--;
|
||||
@@ -194,29 +193,21 @@ void DBIter::Next() {
|
||||
}
|
||||
}
|
||||
|
||||
bool DBIter::SetBlobValueIfNeeded(const Slice& user_key,
|
||||
const Slice& blob_index) {
|
||||
assert(!is_blob_);
|
||||
Status DBIter::BlobReader::RetrieveAndSetBlobValue(const Slice& user_key,
|
||||
const Slice& blob_index) {
|
||||
assert(blob_value_.empty());
|
||||
|
||||
if (expose_blob_index_) { // Stacked BlobDB implementation
|
||||
is_blob_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!version_) {
|
||||
status_ = Status::Corruption("Encountered unexpected blob index.");
|
||||
valid_ = false;
|
||||
return false;
|
||||
return Status::Corruption("Encountered unexpected blob index.");
|
||||
}
|
||||
|
||||
// TODO: consider moving ReadOptions from ArenaWrappedDBIter to DBIter to
|
||||
// avoid having to copy options back and forth.
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
// TODO: plumb Env::IOPriority
|
||||
ReadOptions read_options;
|
||||
read_options.read_tier = read_tier_;
|
||||
read_options.fill_cache = fill_cache_;
|
||||
read_options.verify_checksums = verify_checksums_;
|
||||
read_options.fill_cache = fill_cache_;
|
||||
read_options.io_activity = io_activity_;
|
||||
constexpr FilePrefetchBuffer* prefetch_buffer = nullptr;
|
||||
constexpr uint64_t* bytes_read = nullptr;
|
||||
@@ -224,16 +215,51 @@ bool DBIter::SetBlobValueIfNeeded(const Slice& user_key,
|
||||
const Status s = version_->GetBlob(read_options, user_key, blob_index,
|
||||
prefetch_buffer, &blob_value_, bytes_read);
|
||||
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
bool DBIter::SetValueAndColumnsFromBlobImpl(const Slice& user_key,
|
||||
const Slice& blob_index) {
|
||||
const Status s = blob_reader_.RetrieveAndSetBlobValue(user_key, blob_index);
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
valid_ = false;
|
||||
is_blob_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
is_blob_ = true;
|
||||
SetValueAndColumnsFromPlain(blob_reader_.GetBlobValue());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBIter::SetValueAndColumnsFromBlob(const Slice& user_key,
|
||||
const Slice& blob_index) {
|
||||
assert(!is_blob_);
|
||||
is_blob_ = true;
|
||||
|
||||
if (expose_blob_index_) {
|
||||
SetValueAndColumnsFromPlain(blob_index);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (allow_unprepared_value_) {
|
||||
assert(value_.empty());
|
||||
assert(wide_columns_.empty());
|
||||
|
||||
assert(lazy_blob_index_.empty());
|
||||
lazy_blob_index_ = blob_index;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return SetValueAndColumnsFromBlobImpl(user_key, blob_index);
|
||||
}
|
||||
|
||||
bool DBIter::SetValueAndColumnsFromEntity(Slice slice) {
|
||||
assert(value_.empty());
|
||||
assert(wide_columns_.empty());
|
||||
@@ -279,6 +305,24 @@ bool DBIter::SetValueAndColumnsFromMergeResult(const Status& merge_status,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBIter::PrepareValue() {
|
||||
assert(valid_);
|
||||
|
||||
if (lazy_blob_index_.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
assert(allow_unprepared_value_);
|
||||
assert(is_blob_);
|
||||
|
||||
const bool result =
|
||||
SetValueAndColumnsFromBlobImpl(saved_key_.GetUserKey(), lazy_blob_index_);
|
||||
|
||||
lazy_blob_index_.clear();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// PRE: saved_key_ has the current user key if skipping_saved_key
|
||||
// POST: saved_key_ should have the next user key if valid_,
|
||||
// if the current entry is a result of merge
|
||||
@@ -408,7 +452,7 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
case kTypeValuePreferredSeqno:
|
||||
case kTypeBlobIndex:
|
||||
case kTypeWideColumnEntity:
|
||||
if (!PrepareValue()) {
|
||||
if (!PrepareValueInternal()) {
|
||||
return false;
|
||||
}
|
||||
if (timestamp_lb_) {
|
||||
@@ -420,12 +464,9 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
}
|
||||
|
||||
if (ikey_.type == kTypeBlobIndex) {
|
||||
if (!SetBlobValueIfNeeded(ikey_.user_key, iter_.value())) {
|
||||
if (!SetValueAndColumnsFromBlob(ikey_.user_key, iter_.value())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SetValueAndColumnsFromPlain(expose_blob_index_ ? iter_.value()
|
||||
: blob_value_);
|
||||
} else if (ikey_.type == kTypeWideColumnEntity) {
|
||||
if (!SetValueAndColumnsFromEntity(iter_.value())) {
|
||||
return false;
|
||||
@@ -445,7 +486,7 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
return true;
|
||||
break;
|
||||
case kTypeMerge:
|
||||
if (!PrepareValue()) {
|
||||
if (!PrepareValueInternal()) {
|
||||
return false;
|
||||
}
|
||||
saved_key_.SetUserKey(
|
||||
@@ -540,6 +581,14 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
} else {
|
||||
iter_.Next();
|
||||
}
|
||||
|
||||
// This could be a long-running operation due to tombstones, etc.
|
||||
bool aborted = ROCKSDB_THREAD_YIELD_CHECK_ABORT();
|
||||
if (aborted) {
|
||||
valid_ = false;
|
||||
status_ = Status::Aborted("Query abort.");
|
||||
return false;
|
||||
}
|
||||
} while (iter_.Valid());
|
||||
|
||||
valid_ = false;
|
||||
@@ -590,7 +639,7 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
iter_.Next();
|
||||
break;
|
||||
}
|
||||
if (!PrepareValue()) {
|
||||
if (!PrepareValueInternal()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -619,23 +668,9 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
iter_.value(), iter_.iter()->IsValuePinned() /* operand_pinned */);
|
||||
PERF_COUNTER_ADD(internal_merge_count, 1);
|
||||
} else if (kTypeBlobIndex == ikey.type) {
|
||||
if (expose_blob_index_) {
|
||||
status_ =
|
||||
Status::NotSupported("BlobDB does not support merge operator.");
|
||||
valid_ = false;
|
||||
if (!MergeWithBlobBaseValue(iter_.value(), ikey.user_key)) {
|
||||
return false;
|
||||
}
|
||||
// hit a put, merge the put value with operands and store the
|
||||
// final result in saved_value_. We are done!
|
||||
if (!SetBlobValueIfNeeded(ikey.user_key, iter_.value())) {
|
||||
return false;
|
||||
}
|
||||
valid_ = true;
|
||||
if (!MergeWithPlainBaseValue(blob_value_, ikey.user_key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ResetBlobValue();
|
||||
|
||||
// iter_ is positioned after put
|
||||
iter_.Next();
|
||||
@@ -643,6 +678,7 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (kTypeWideColumnEntity == ikey.type) {
|
||||
if (!MergeWithWideColumnBaseValue(iter_.value(), ikey.user_key)) {
|
||||
@@ -689,7 +725,7 @@ void DBIter::Prev() {
|
||||
PERF_COUNTER_ADD(iter_prev_count, 1);
|
||||
PERF_CPU_TIMER_GUARD(iter_prev_cpu_nanos, clock_);
|
||||
ReleaseTempPinnedData();
|
||||
ResetBlobValue();
|
||||
ResetBlobData();
|
||||
ResetValueAndColumns();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
bool ok = true;
|
||||
@@ -926,7 +962,7 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
return FindValueForCurrentKeyUsingSeek();
|
||||
}
|
||||
|
||||
if (!PrepareValue()) {
|
||||
if (!PrepareValueInternal()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1041,21 +1077,9 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
}
|
||||
return true;
|
||||
} else if (last_not_merge_type == kTypeBlobIndex) {
|
||||
if (expose_blob_index_) {
|
||||
status_ =
|
||||
Status::NotSupported("BlobDB does not support merge operator.");
|
||||
valid_ = false;
|
||||
if (!MergeWithBlobBaseValue(pinned_value_, saved_key_.GetUserKey())) {
|
||||
return false;
|
||||
}
|
||||
if (!SetBlobValueIfNeeded(saved_key_.GetUserKey(), pinned_value_)) {
|
||||
return false;
|
||||
}
|
||||
valid_ = true;
|
||||
if (!MergeWithPlainBaseValue(blob_value_, saved_key_.GetUserKey())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ResetBlobValue();
|
||||
|
||||
return true;
|
||||
} else if (last_not_merge_type == kTypeWideColumnEntity) {
|
||||
@@ -1080,13 +1104,9 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
|
||||
break;
|
||||
case kTypeBlobIndex:
|
||||
if (!SetBlobValueIfNeeded(saved_key_.GetUserKey(), pinned_value_)) {
|
||||
if (!SetValueAndColumnsFromBlob(saved_key_.GetUserKey(), pinned_value_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SetValueAndColumnsFromPlain(expose_blob_index_ ? pinned_value_
|
||||
: blob_value_);
|
||||
|
||||
break;
|
||||
case kTypeWideColumnEntity:
|
||||
if (!SetValueAndColumnsFromEntity(pinned_value_)) {
|
||||
@@ -1173,7 +1193,7 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!PrepareValue()) {
|
||||
if (!PrepareValueInternal()) {
|
||||
return false;
|
||||
}
|
||||
if (timestamp_size_ > 0) {
|
||||
@@ -1190,12 +1210,9 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
pinned_value_ = iter_.value();
|
||||
}
|
||||
if (ikey.type == kTypeBlobIndex) {
|
||||
if (!SetBlobValueIfNeeded(ikey.user_key, pinned_value_)) {
|
||||
if (!SetValueAndColumnsFromBlob(ikey.user_key, pinned_value_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SetValueAndColumnsFromPlain(expose_blob_index_ ? pinned_value_
|
||||
: blob_value_);
|
||||
} else if (ikey.type == kTypeWideColumnEntity) {
|
||||
if (!SetValueAndColumnsFromEntity(pinned_value_)) {
|
||||
return false;
|
||||
@@ -1243,7 +1260,7 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
ikey.type == kTypeDeletionWithTimestamp) {
|
||||
break;
|
||||
}
|
||||
if (!PrepareValue()) {
|
||||
if (!PrepareValueInternal()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1261,21 +1278,9 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
iter_.value(), iter_.iter()->IsValuePinned() /* operand_pinned */);
|
||||
PERF_COUNTER_ADD(internal_merge_count, 1);
|
||||
} else if (ikey.type == kTypeBlobIndex) {
|
||||
if (expose_blob_index_) {
|
||||
status_ =
|
||||
Status::NotSupported("BlobDB does not support merge operator.");
|
||||
valid_ = false;
|
||||
if (!MergeWithBlobBaseValue(iter_.value(), saved_key_.GetUserKey())) {
|
||||
return false;
|
||||
}
|
||||
if (!SetBlobValueIfNeeded(ikey.user_key, iter_.value())) {
|
||||
return false;
|
||||
}
|
||||
valid_ = true;
|
||||
if (!MergeWithPlainBaseValue(blob_value_, saved_key_.GetUserKey())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ResetBlobValue();
|
||||
|
||||
return true;
|
||||
} else if (ikey.type == kTypeWideColumnEntity) {
|
||||
@@ -1342,6 +1347,35 @@ bool DBIter::MergeWithPlainBaseValue(const Slice& value,
|
||||
return SetValueAndColumnsFromMergeResult(s, result_type);
|
||||
}
|
||||
|
||||
bool DBIter::MergeWithBlobBaseValue(const Slice& blob_index,
|
||||
const Slice& user_key) {
|
||||
assert(!is_blob_);
|
||||
|
||||
if (expose_blob_index_) {
|
||||
status_ =
|
||||
Status::NotSupported("Legacy BlobDB does not support merge operator.");
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
const Status s = blob_reader_.RetrieveAndSetBlobValue(user_key, blob_index);
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
valid_ = true;
|
||||
|
||||
if (!MergeWithPlainBaseValue(blob_reader_.GetBlobValue(), user_key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
blob_reader_.ResetBlobValue();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBIter::MergeWithWideColumnBaseValue(const Slice& entity,
|
||||
const Slice& user_key) {
|
||||
// `op_failure_scope` (an output parameter) is not provided (set to nullptr)
|
||||
@@ -1531,7 +1565,7 @@ void DBIter::Seek(const Slice& target) {
|
||||
|
||||
status_ = Status::OK();
|
||||
ReleaseTempPinnedData();
|
||||
ResetBlobValue();
|
||||
ResetBlobData();
|
||||
ResetValueAndColumns();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
|
||||
@@ -1607,7 +1641,7 @@ void DBIter::SeekForPrev(const Slice& target) {
|
||||
|
||||
status_ = Status::OK();
|
||||
ReleaseTempPinnedData();
|
||||
ResetBlobValue();
|
||||
ResetBlobData();
|
||||
ResetValueAndColumns();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
|
||||
@@ -1668,7 +1702,7 @@ void DBIter::SeekToFirst() {
|
||||
status_.PermitUncheckedError();
|
||||
direction_ = kForward;
|
||||
ReleaseTempPinnedData();
|
||||
ResetBlobValue();
|
||||
ResetBlobData();
|
||||
ResetValueAndColumns();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
ClearSavedValue();
|
||||
@@ -1731,7 +1765,7 @@ void DBIter::SeekToLast() {
|
||||
status_.PermitUncheckedError();
|
||||
direction_ = kReverse;
|
||||
ReleaseTempPinnedData();
|
||||
ResetBlobValue();
|
||||
ResetBlobData();
|
||||
ResetValueAndColumns();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
ClearSavedValue();
|
||||
|
||||
+44
-17
@@ -218,7 +218,34 @@ class DBIter final : public Iterator {
|
||||
}
|
||||
void set_valid(bool v) { valid_ = v; }
|
||||
|
||||
bool PrepareValue() override;
|
||||
|
||||
private:
|
||||
class BlobReader {
|
||||
public:
|
||||
BlobReader(const Version* version, ReadTier read_tier,
|
||||
bool verify_checksums, bool fill_cache,
|
||||
Env::IOActivity io_activity)
|
||||
: version_(version),
|
||||
read_tier_(read_tier),
|
||||
verify_checksums_(verify_checksums),
|
||||
fill_cache_(fill_cache),
|
||||
io_activity_(io_activity) {}
|
||||
|
||||
const Slice& GetBlobValue() const { return blob_value_; }
|
||||
Status RetrieveAndSetBlobValue(const Slice& user_key,
|
||||
const Slice& blob_index);
|
||||
void ResetBlobValue() { blob_value_.Reset(); }
|
||||
|
||||
private:
|
||||
PinnableSlice blob_value_;
|
||||
const Version* version_;
|
||||
ReadTier read_tier_;
|
||||
bool verify_checksums_;
|
||||
bool fill_cache_;
|
||||
Env::IOActivity io_activity_;
|
||||
};
|
||||
|
||||
// For all methods in this block:
|
||||
// PRE: iter_->Valid() && status_.ok()
|
||||
// Return false if there was an error, and status() is non-ok, valid_ = false;
|
||||
@@ -299,15 +326,6 @@ class DBIter final : public Iterator {
|
||||
: user_comparator_.CompareWithoutTimestamp(a, b);
|
||||
}
|
||||
|
||||
// Retrieves the blob value for the specified user key using the given blob
|
||||
// index when using the integrated BlobDB implementation.
|
||||
bool SetBlobValueIfNeeded(const Slice& user_key, const Slice& blob_index);
|
||||
|
||||
void ResetBlobValue() {
|
||||
is_blob_ = false;
|
||||
blob_value_.Reset();
|
||||
}
|
||||
|
||||
void SetValueAndColumnsFromPlain(const Slice& slice) {
|
||||
assert(value_.empty());
|
||||
assert(wide_columns_.empty());
|
||||
@@ -316,6 +334,11 @@ class DBIter final : public Iterator {
|
||||
wide_columns_.emplace_back(kDefaultWideColumnName, slice);
|
||||
}
|
||||
|
||||
bool SetValueAndColumnsFromBlobImpl(const Slice& user_key,
|
||||
const Slice& blob_index);
|
||||
bool SetValueAndColumnsFromBlob(const Slice& user_key,
|
||||
const Slice& blob_index);
|
||||
|
||||
bool SetValueAndColumnsFromEntity(Slice slice);
|
||||
|
||||
bool SetValueAndColumnsFromMergeResult(const Status& merge_status,
|
||||
@@ -326,14 +349,21 @@ class DBIter final : public Iterator {
|
||||
wide_columns_.clear();
|
||||
}
|
||||
|
||||
void ResetBlobData() {
|
||||
blob_reader_.ResetBlobValue();
|
||||
lazy_blob_index_.clear();
|
||||
is_blob_ = false;
|
||||
}
|
||||
|
||||
// The following methods perform the actual merge operation for the
|
||||
// no base value/plain base value/wide-column base value cases.
|
||||
// no/plain/blob/wide-column base value cases.
|
||||
// If user-defined timestamp is enabled, `user_key` includes timestamp.
|
||||
bool MergeWithNoBaseValue(const Slice& user_key);
|
||||
bool MergeWithPlainBaseValue(const Slice& value, const Slice& user_key);
|
||||
bool MergeWithBlobBaseValue(const Slice& blob_index, const Slice& user_key);
|
||||
bool MergeWithWideColumnBaseValue(const Slice& entity, const Slice& user_key);
|
||||
|
||||
bool PrepareValue() {
|
||||
bool PrepareValueInternal() {
|
||||
if (!iter_.PrepareValue()) {
|
||||
assert(!iter_.status().ok());
|
||||
valid_ = false;
|
||||
@@ -356,7 +386,7 @@ class DBIter final : public Iterator {
|
||||
UserComparatorWrapper user_comparator_;
|
||||
const MergeOperator* const merge_operator_;
|
||||
IteratorWrapper iter_;
|
||||
const Version* version_;
|
||||
BlobReader blob_reader_;
|
||||
ReadCallback* read_callback_;
|
||||
// Max visible sequence number. It is normally the snapshot seq unless we have
|
||||
// uncommitted data in db as in WriteUnCommitted.
|
||||
@@ -376,7 +406,6 @@ class DBIter final : public Iterator {
|
||||
std::string saved_value_;
|
||||
Slice pinned_value_;
|
||||
// for prefix seek mode to support prev()
|
||||
PinnableSlice blob_value_;
|
||||
// Value of the default column
|
||||
Slice value_;
|
||||
// All columns (i.e. name-value pairs)
|
||||
@@ -410,15 +439,13 @@ class DBIter final : public Iterator {
|
||||
// Expect the inner iterator to maintain a total order.
|
||||
// prefix_extractor_ must be non-NULL if the value is false.
|
||||
const bool expect_total_order_inner_iter_;
|
||||
ReadTier read_tier_;
|
||||
bool fill_cache_;
|
||||
bool verify_checksums_;
|
||||
// Whether the iterator is allowed to expose blob references. Set to true when
|
||||
// the stacked BlobDB implementation is used, false otherwise.
|
||||
bool expose_blob_index_;
|
||||
bool allow_unprepared_value_;
|
||||
Slice lazy_blob_index_;
|
||||
bool is_blob_;
|
||||
bool arena_mode_;
|
||||
const Env::IOActivity io_activity_;
|
||||
// List of operands for merge operator.
|
||||
MergeContext merge_context_;
|
||||
LocalStatistics local_stats_;
|
||||
|
||||
+27
-6
@@ -56,6 +56,11 @@ class DBOptionsTest : public DBTestBase {
|
||||
EXPECT_OK(GetStringFromMutableCFOptions(
|
||||
config_options, MutableCFOptions(options), &options_str));
|
||||
EXPECT_OK(StringToMap(options_str, &mutable_map));
|
||||
for (auto& opt : TEST_GetImmutableInMutableCFOptions()) {
|
||||
// Not yet mutable but migrated to MutableCFOptions in preparation for
|
||||
// being mutable
|
||||
mutable_map.erase(opt);
|
||||
}
|
||||
return mutable_map;
|
||||
}
|
||||
|
||||
@@ -231,21 +236,33 @@ TEST_F(DBOptionsTest, SetMutableTableOptions) {
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
cfh, {{"table_factory.block_size", "16384"},
|
||||
{"table_factory.block_restart_interval", "11"}}));
|
||||
// Old c_bbto
|
||||
ASSERT_EQ(c_bbto->block_size, 8192);
|
||||
ASSERT_EQ(c_bbto->block_restart_interval, 7);
|
||||
// New c_bbto
|
||||
c_opts = dbfull()->GetOptions(cfh);
|
||||
c_bbto = c_opts.table_factory->GetOptions<BlockBasedTableOptions>();
|
||||
ASSERT_EQ(c_bbto->block_size, 16384);
|
||||
ASSERT_EQ(c_bbto->block_restart_interval, 11);
|
||||
|
||||
// Now set an option that is not mutable - options should not change
|
||||
ASSERT_NOK(
|
||||
dbfull()->SetOptions(cfh, {{"table_factory.no_block_cache", "false"}}));
|
||||
// FIXME: find a way to make this fail again
|
||||
// ASSERT_NOK(
|
||||
// dbfull()->SetOptions(cfh, {{"table_factory.no_block_cache", "false"}}));
|
||||
c_opts = dbfull()->GetOptions(cfh);
|
||||
ASSERT_EQ(c_bbto, c_opts.table_factory->GetOptions<BlockBasedTableOptions>());
|
||||
ASSERT_EQ(c_bbto->no_block_cache, true);
|
||||
ASSERT_EQ(c_bbto->block_size, 16384);
|
||||
ASSERT_EQ(c_bbto->block_restart_interval, 11);
|
||||
|
||||
// Set some that are mutable and some that are not - options should not change
|
||||
ASSERT_NOK(dbfull()->SetOptions(
|
||||
cfh, {{"table_factory.no_block_cache", "false"},
|
||||
{"table_factory.block_size", "8192"},
|
||||
{"table_factory.block_restart_interval", "7"}}));
|
||||
// FIXME: find a way to make this fail again
|
||||
// ASSERT_NOK(dbfull()->SetOptions(
|
||||
// cfh, {{"table_factory.no_block_cache", "false"},
|
||||
// {"table_factory.block_size", "8192"},
|
||||
// {"table_factory.block_restart_interval", "7"}}));
|
||||
c_opts = dbfull()->GetOptions(cfh);
|
||||
ASSERT_EQ(c_bbto, c_opts.table_factory->GetOptions<BlockBasedTableOptions>());
|
||||
ASSERT_EQ(c_bbto->no_block_cache, true);
|
||||
ASSERT_EQ(c_bbto->block_size, 16384);
|
||||
ASSERT_EQ(c_bbto->block_restart_interval, 11);
|
||||
@@ -256,6 +273,8 @@ TEST_F(DBOptionsTest, SetMutableTableOptions) {
|
||||
cfh, {{"table_factory.block_size", "8192"},
|
||||
{"table_factory.does_not_exist", "true"},
|
||||
{"table_factory.block_restart_interval", "7"}}));
|
||||
c_opts = dbfull()->GetOptions(cfh);
|
||||
ASSERT_EQ(c_bbto, c_opts.table_factory->GetOptions<BlockBasedTableOptions>());
|
||||
ASSERT_EQ(c_bbto->no_block_cache, true);
|
||||
ASSERT_EQ(c_bbto->block_size, 16384);
|
||||
ASSERT_EQ(c_bbto->block_restart_interval, 11);
|
||||
@@ -271,6 +290,7 @@ TEST_F(DBOptionsTest, SetMutableTableOptions) {
|
||||
{"table_factory.block_restart_interval", "13"}}));
|
||||
c_opts = dbfull()->GetOptions(cfh);
|
||||
ASSERT_EQ(c_opts.blob_file_size, 32768);
|
||||
c_bbto = c_opts.table_factory->GetOptions<BlockBasedTableOptions>();
|
||||
ASSERT_EQ(c_bbto->block_size, 16384);
|
||||
ASSERT_EQ(c_bbto->block_restart_interval, 13);
|
||||
// Set some on the table and a bad one on the ColumnFamily - options should
|
||||
@@ -279,6 +299,7 @@ TEST_F(DBOptionsTest, SetMutableTableOptions) {
|
||||
cfh, {{"table_factory.block_size", "1024"},
|
||||
{"no_such_option", "32768"},
|
||||
{"table_factory.block_restart_interval", "7"}}));
|
||||
ASSERT_EQ(c_bbto, c_opts.table_factory->GetOptions<BlockBasedTableOptions>());
|
||||
ASSERT_EQ(c_bbto->block_size, 16384);
|
||||
ASSERT_EQ(c_bbto->block_restart_interval, 13);
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ TEST_F(DBSecondaryTest, SimpleInternalCompaction) {
|
||||
ASSERT_EQ(largest.user_key().ToString(), "foo");
|
||||
ASSERT_EQ(result.output_level, 1);
|
||||
ASSERT_EQ(result.output_path, this->secondary_path_);
|
||||
ASSERT_EQ(result.num_output_records, 2);
|
||||
ASSERT_EQ(result.stats.num_output_records, 2);
|
||||
ASSERT_GT(result.bytes_written, 0);
|
||||
ASSERT_OK(result.status);
|
||||
}
|
||||
|
||||
+48
-16
@@ -1826,21 +1826,30 @@ TEST_F(DBTest, GetApproximateMemTableStats) {
|
||||
uint64_t count;
|
||||
uint64_t size;
|
||||
|
||||
// Because Random::GetTLSInstance() seed is reset in DBTestBase,
|
||||
// this test is deterministic.
|
||||
|
||||
std::string start = Key(50);
|
||||
std::string end = Key(60);
|
||||
Range r(start, end);
|
||||
db_->GetApproximateMemTableStats(r, &count, &size);
|
||||
ASSERT_GT(count, 0);
|
||||
ASSERT_LE(count, N);
|
||||
ASSERT_GT(size, 6000);
|
||||
ASSERT_LT(size, 204800);
|
||||
// When actual count is <= 10, it returns that as the minimum
|
||||
EXPECT_EQ(count, 10);
|
||||
EXPECT_EQ(size, 10440);
|
||||
|
||||
start = Key(20);
|
||||
end = Key(100);
|
||||
r = Range(start, end);
|
||||
db_->GetApproximateMemTableStats(r, &count, &size);
|
||||
EXPECT_EQ(count, 72);
|
||||
EXPECT_EQ(size, 75168);
|
||||
|
||||
start = Key(500);
|
||||
end = Key(600);
|
||||
r = Range(start, end);
|
||||
db_->GetApproximateMemTableStats(r, &count, &size);
|
||||
ASSERT_EQ(count, 0);
|
||||
ASSERT_EQ(size, 0);
|
||||
EXPECT_EQ(count, 0);
|
||||
EXPECT_EQ(size, 0);
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
@@ -1848,8 +1857,8 @@ TEST_F(DBTest, GetApproximateMemTableStats) {
|
||||
end = Key(60);
|
||||
r = Range(start, end);
|
||||
db_->GetApproximateMemTableStats(r, &count, &size);
|
||||
ASSERT_EQ(count, 0);
|
||||
ASSERT_EQ(size, 0);
|
||||
EXPECT_EQ(count, 0);
|
||||
EXPECT_EQ(size, 0);
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
ASSERT_OK(Put(Key(1000 + i), rnd.RandomString(1024)));
|
||||
@@ -1857,10 +1866,11 @@ TEST_F(DBTest, GetApproximateMemTableStats) {
|
||||
|
||||
start = Key(100);
|
||||
end = Key(1020);
|
||||
// Actually 20 keys in the range ^^
|
||||
r = Range(start, end);
|
||||
db_->GetApproximateMemTableStats(r, &count, &size);
|
||||
ASSERT_GT(count, 20);
|
||||
ASSERT_GT(size, 6000);
|
||||
EXPECT_EQ(count, 20);
|
||||
EXPECT_EQ(size, 20880);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, ApproximateSizes) {
|
||||
@@ -5169,10 +5179,14 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
|
||||
options.max_bytes_for_level_multiplier = 4;
|
||||
options.max_background_compactions = 1;
|
||||
options.num_levels = 5;
|
||||
options.statistics = CreateDBStatistics();
|
||||
|
||||
options.compression_per_level.resize(3);
|
||||
// No compression for L0
|
||||
options.compression_per_level[0] = kNoCompression;
|
||||
// No compression for the Ln whre L0 is compacted to
|
||||
options.compression_per_level[1] = kNoCompression;
|
||||
// Snpapy compression for Ln+1
|
||||
options.compression_per_level[2] = kSnappyCompression;
|
||||
|
||||
OnFileDeletionListener* listener = new OnFileDeletionListener();
|
||||
@@ -5181,7 +5195,7 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Insert more than 80K. L4 should be base level. Neither L0 nor L4 should
|
||||
// be compressed, so total data size should be more than 80K.
|
||||
// be compressed, so there shouldn't be any compression.
|
||||
for (int i = 0; i < 20; i++) {
|
||||
ASSERT_OK(Put(Key(keys[i]), CompressibleString(&rnd, 4000)));
|
||||
}
|
||||
@@ -5191,10 +5205,17 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(2), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(3), 0);
|
||||
// Assuming each files' metadata is at least 50 bytes/
|
||||
ASSERT_GT(SizeAtLevel(0) + SizeAtLevel(4), 20U * 4000U + 50U * 4);
|
||||
ASSERT_TRUE(NumTableFilesAtLevel(0) > 0 || NumTableFilesAtLevel(4) > 0);
|
||||
|
||||
// Insert 400KB. Some data will be compressed
|
||||
// Verify there was no compression
|
||||
auto num_block_compressed =
|
||||
options.statistics->getTickerCount(NUMBER_BLOCK_COMPRESSED);
|
||||
ASSERT_EQ(num_block_compressed, 0);
|
||||
|
||||
// Insert 400KB and there will be some files end up in L3. According to the
|
||||
// above compression settings for each level, there will be some compression.
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
ASSERT_EQ(num_block_compressed, 0);
|
||||
for (int i = 21; i < 120; i++) {
|
||||
ASSERT_OK(Put(Key(keys[i]), CompressibleString(&rnd, 4000)));
|
||||
}
|
||||
@@ -5202,9 +5223,14 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(2), 0);
|
||||
ASSERT_GE(NumTableFilesAtLevel(3), 1);
|
||||
ASSERT_GE(NumTableFilesAtLevel(4), 1);
|
||||
|
||||
// Verify there was compression
|
||||
num_block_compressed =
|
||||
options.statistics->getTickerCount(NUMBER_BLOCK_COMPRESSED);
|
||||
ASSERT_GT(num_block_compressed, 0);
|
||||
|
||||
ASSERT_LT(SizeAtLevel(0) + SizeAtLevel(3) + SizeAtLevel(4),
|
||||
120U * 4000U + 50U * 24);
|
||||
// Make sure data in files in L3 is not compacted by removing all files
|
||||
// in L4 and calculate number of rows
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
@@ -5224,6 +5250,12 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
|
||||
num_keys++;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(2), 0);
|
||||
ASSERT_GE(NumTableFilesAtLevel(3), 1);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(4), 0);
|
||||
|
||||
ASSERT_GT(SizeAtLevel(0) + SizeAtLevel(3), num_keys * 4000U + num_keys * 10U);
|
||||
}
|
||||
|
||||
|
||||
+2
-15
@@ -36,18 +36,6 @@ namespace ROCKSDB_NAMESPACE {
|
||||
class DBTest2 : public DBTestBase {
|
||||
public:
|
||||
DBTest2() : DBTestBase("db_test2", /*env_do_fsync=*/true) {}
|
||||
std::vector<FileMetaData*> GetLevelFileMetadatas(int level, int cf = 0) {
|
||||
VersionSet* const versions = dbfull()->GetVersionSet();
|
||||
assert(versions);
|
||||
ColumnFamilyData* const cfd =
|
||||
versions->GetColumnFamilySet()->GetColumnFamily(cf);
|
||||
assert(cfd);
|
||||
Version* const current = cfd->current();
|
||||
assert(current);
|
||||
VersionStorageInfo* const storage_info = current->storage_info();
|
||||
assert(storage_info);
|
||||
return storage_info->LevelFiles(level);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DBTest2, OpenForReadOnly) {
|
||||
@@ -2115,16 +2103,15 @@ TEST_P(PinL0IndexAndFilterBlocksTest,
|
||||
ASSERT_EQ(2, TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_DATA_MISS));
|
||||
|
||||
std::string value;
|
||||
// Miss and hit count should remain the same, they're all pinned.
|
||||
ASSERT_TRUE(db_->KeyMayExist(ReadOptions(), handles_[1], "key", &value));
|
||||
ASSERT_TRUE(db_->KeyMayExist(ReadOptions(), handles_[1], "key", nullptr));
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
||||
ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
|
||||
ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
|
||||
|
||||
// Miss and hit count should remain the same, they're all pinned.
|
||||
value = Get(1, "key");
|
||||
std::string value = Get(1, "key");
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
||||
ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
|
||||
|
||||
+98
-45
@@ -934,6 +934,13 @@ Status DBTestBase::Get(const std::string& k, PinnableSlice* v) {
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBTestBase::CompactRange(const CompactRangeOptions& options,
|
||||
std::optional<Slice> begin,
|
||||
std::optional<Slice> end) {
|
||||
return db_->CompactRange(options, begin ? &begin.value() : nullptr,
|
||||
end ? &end.value() : nullptr);
|
||||
}
|
||||
|
||||
uint64_t DBTestBase::GetNumSnapshots() {
|
||||
uint64_t int_num;
|
||||
EXPECT_TRUE(dbfull()->GetIntProperty("rocksdb.num-snapshots", &int_num));
|
||||
@@ -1263,6 +1270,20 @@ Status DBTestBase::CountFiles(size_t* count) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
std::vector<FileMetaData*> DBTestBase::GetLevelFileMetadatas(int level,
|
||||
int cf) {
|
||||
VersionSet* const versions = dbfull()->GetVersionSet();
|
||||
assert(versions);
|
||||
ColumnFamilyData* const cfd =
|
||||
versions->GetColumnFamilySet()->GetColumnFamily(cf);
|
||||
assert(cfd);
|
||||
Version* const current = cfd->current();
|
||||
assert(current);
|
||||
VersionStorageInfo* const storage_info = current->storage_info();
|
||||
assert(storage_info);
|
||||
return storage_info->LevelFiles(level);
|
||||
}
|
||||
|
||||
Status DBTestBase::Size(const Slice& start, const Slice& limit, int cf,
|
||||
uint64_t* size) {
|
||||
Range r(start, limit);
|
||||
@@ -1579,42 +1600,74 @@ std::vector<std::uint64_t> DBTestBase::ListTableFiles(Env* env,
|
||||
return file_numbers;
|
||||
}
|
||||
|
||||
void DBTestBase::VerifyDBFromMap(std::map<std::string, std::string> true_data,
|
||||
size_t* total_reads_res, bool tailing_iter,
|
||||
std::map<std::string, Status> status) {
|
||||
size_t total_reads = 0;
|
||||
void DBTestBase::VerifyDBFromMap(
|
||||
std::map<std::string, std::string> true_data, size_t* total_reads_res,
|
||||
bool tailing_iter, ReadOptions* ro, ColumnFamilyHandle* cf,
|
||||
std::unordered_set<std::string>* not_found) const {
|
||||
ReadOptions temp_ro;
|
||||
if (!ro) {
|
||||
ro = &temp_ro;
|
||||
ro->verify_checksums = true;
|
||||
}
|
||||
if (!cf) {
|
||||
cf = db_->DefaultColumnFamily();
|
||||
}
|
||||
|
||||
for (auto& kv : true_data) {
|
||||
Status s = status[kv.first];
|
||||
if (s.ok()) {
|
||||
ASSERT_EQ(Get(kv.first), kv.second);
|
||||
} else {
|
||||
std::string value;
|
||||
ASSERT_EQ(s, db_->Get(ReadOptions(), kv.first, &value));
|
||||
}
|
||||
// Get
|
||||
size_t total_reads = 0;
|
||||
std::string result;
|
||||
for (auto& [k, v] : true_data) {
|
||||
ASSERT_OK(db_->Get(*ro, cf, k, &result)) << "key is " << k;
|
||||
ASSERT_EQ(v, result);
|
||||
total_reads++;
|
||||
}
|
||||
if (not_found) {
|
||||
for (const auto& k : *not_found) {
|
||||
ASSERT_TRUE(db_->Get(*ro, cf, k, &result).IsNotFound())
|
||||
<< "key is " << k << " val is " << result;
|
||||
}
|
||||
}
|
||||
|
||||
// MultiGet
|
||||
std::vector<Slice> key_slice;
|
||||
for (const auto& [k, _] : true_data) {
|
||||
key_slice.emplace_back(k);
|
||||
}
|
||||
std::vector<std::string> values;
|
||||
std::vector<ColumnFamilyHandle*> cfs(key_slice.size(), cf);
|
||||
std::vector<Status> status = db_->MultiGet(*ro, cfs, key_slice, &values);
|
||||
total_reads += key_slice.size();
|
||||
auto data_iter = true_data.begin();
|
||||
for (size_t i = 0; i < key_slice.size(); ++i, ++data_iter) {
|
||||
ASSERT_OK(status[i]);
|
||||
ASSERT_EQ(values[i], data_iter->second);
|
||||
}
|
||||
// MultiGet - not found
|
||||
if (not_found) {
|
||||
key_slice.clear();
|
||||
for (const auto& k : *not_found) {
|
||||
key_slice.emplace_back(k);
|
||||
}
|
||||
cfs = std::vector<ColumnFamilyHandle*>(key_slice.size(), cf);
|
||||
values.clear();
|
||||
status = db_->MultiGet(*ro, cfs, key_slice, &values);
|
||||
for (const auto& s : status) {
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
}
|
||||
}
|
||||
|
||||
// Normal Iterator
|
||||
{
|
||||
int iter_cnt = 0;
|
||||
ReadOptions ro;
|
||||
ro.total_order_seek = true;
|
||||
Iterator* iter = db_->NewIterator(ro);
|
||||
ReadOptions ro_ = *ro;
|
||||
ro_.total_order_seek = true;
|
||||
Iterator* iter = db_->NewIterator(ro_, cf);
|
||||
// Verify Iterator::Next()
|
||||
iter_cnt = 0;
|
||||
auto data_iter = true_data.begin();
|
||||
Status s;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next(), data_iter++) {
|
||||
data_iter = true_data.begin();
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next(), ++data_iter) {
|
||||
ASSERT_EQ(iter->key().ToString(), data_iter->first);
|
||||
Status current_status = status[data_iter->first];
|
||||
if (!current_status.ok()) {
|
||||
s = current_status;
|
||||
}
|
||||
ASSERT_EQ(iter->status(), s);
|
||||
if (current_status.ok()) {
|
||||
ASSERT_EQ(iter->value().ToString(), data_iter->second);
|
||||
}
|
||||
iter_cnt++;
|
||||
total_reads++;
|
||||
}
|
||||
@@ -1625,20 +1678,12 @@ void DBTestBase::VerifyDBFromMap(std::map<std::string, std::string> true_data,
|
||||
|
||||
// Verify Iterator::Prev()
|
||||
// Use a new iterator to make sure its status is clean.
|
||||
iter = db_->NewIterator(ro);
|
||||
iter = db_->NewIterator(ro_, cf);
|
||||
iter_cnt = 0;
|
||||
s = Status::OK();
|
||||
auto data_rev = true_data.rbegin();
|
||||
for (iter->SeekToLast(); iter->Valid(); iter->Prev(), data_rev++) {
|
||||
ASSERT_EQ(iter->key().ToString(), data_rev->first);
|
||||
Status current_status = status[data_rev->first];
|
||||
if (!current_status.ok()) {
|
||||
s = current_status;
|
||||
}
|
||||
ASSERT_EQ(iter->status(), s);
|
||||
if (current_status.ok()) {
|
||||
ASSERT_EQ(iter->value().ToString(), data_rev->second);
|
||||
}
|
||||
iter_cnt++;
|
||||
total_reads++;
|
||||
}
|
||||
@@ -1646,12 +1691,20 @@ void DBTestBase::VerifyDBFromMap(std::map<std::string, std::string> true_data,
|
||||
ASSERT_EQ(data_rev, true_data.rend())
|
||||
<< iter_cnt << " / " << true_data.size();
|
||||
|
||||
// Verify Iterator::Seek()
|
||||
for (const auto& kv : true_data) {
|
||||
iter->Seek(kv.first);
|
||||
ASSERT_EQ(kv.first, iter->key().ToString());
|
||||
ASSERT_EQ(kv.second, iter->value().ToString());
|
||||
total_reads++;
|
||||
// Verify Iterator::Seek() and SeekForPrev()
|
||||
for (const auto& [k, v] : true_data) {
|
||||
for (bool prev : {false, true}) {
|
||||
if (prev) {
|
||||
iter->SeekForPrev(k);
|
||||
} else {
|
||||
iter->Seek(k);
|
||||
}
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(iter->key(), k);
|
||||
ASSERT_EQ(iter->value(), v);
|
||||
++total_reads;
|
||||
}
|
||||
}
|
||||
delete iter;
|
||||
}
|
||||
@@ -1659,14 +1712,14 @@ void DBTestBase::VerifyDBFromMap(std::map<std::string, std::string> true_data,
|
||||
if (tailing_iter) {
|
||||
// Tailing iterator
|
||||
int iter_cnt = 0;
|
||||
ReadOptions ro;
|
||||
ro.tailing = true;
|
||||
ro.total_order_seek = true;
|
||||
Iterator* iter = db_->NewIterator(ro);
|
||||
ReadOptions ro_ = *ro;
|
||||
ro_.tailing = true;
|
||||
ro_.total_order_seek = true;
|
||||
Iterator* iter = db_->NewIterator(ro_, cf);
|
||||
|
||||
// Verify ForwardIterator::Next()
|
||||
iter_cnt = 0;
|
||||
auto data_iter = true_data.begin();
|
||||
data_iter = true_data.begin();
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next(), data_iter++) {
|
||||
ASSERT_EQ(iter->key().ToString(), data_iter->first);
|
||||
ASSERT_EQ(iter->value().ToString(), data_iter->second);
|
||||
|
||||
+7
-1
@@ -1225,6 +1225,9 @@ class DBTestBase : public testing::Test {
|
||||
const Snapshot* snapshot = nullptr,
|
||||
const bool async = false);
|
||||
|
||||
Status CompactRange(const CompactRangeOptions& options,
|
||||
std::optional<Slice> begin, std::optional<Slice> end);
|
||||
|
||||
uint64_t GetNumSnapshots();
|
||||
|
||||
uint64_t GetTimeOldestSnapshots();
|
||||
@@ -1273,6 +1276,8 @@ class DBTestBase : public testing::Test {
|
||||
|
||||
Status CountFiles(size_t* count);
|
||||
|
||||
std::vector<FileMetaData*> GetLevelFileMetadatas(int level, int cf = 0);
|
||||
|
||||
Status Size(const Slice& start, const Slice& limit, uint64_t* size) {
|
||||
return Size(start, limit, 0, size);
|
||||
}
|
||||
@@ -1362,7 +1367,8 @@ class DBTestBase : public testing::Test {
|
||||
void VerifyDBFromMap(
|
||||
std::map<std::string, std::string> true_data,
|
||||
size_t* total_reads_res = nullptr, bool tailing_iter = false,
|
||||
std::map<std::string, Status> status = std::map<std::string, Status>());
|
||||
ReadOptions* ro = nullptr, ColumnFamilyHandle* cf = nullptr,
|
||||
std::unordered_set<std::string>* not_found = nullptr) const;
|
||||
|
||||
void VerifyDBInternal(
|
||||
std::vector<std::pair<std::string, std::string>> true_data);
|
||||
|
||||
+15
-5
@@ -178,11 +178,9 @@ std::string ParsedInternalKey::DebugString(bool log_err_key, bool hex,
|
||||
result += "<redacted>";
|
||||
}
|
||||
|
||||
char buf[50];
|
||||
snprintf(buf, sizeof(buf), "' seq:%" PRIu64 ", type:%d", sequence,
|
||||
static_cast<int>(type));
|
||||
result += "' seq:" + std::to_string(sequence);
|
||||
result += ", type:" + std::to_string(type);
|
||||
|
||||
result += buf;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -272,11 +270,23 @@ LookupKey::LookupKey(const Slice& _user_key, SequenceNumber s,
|
||||
|
||||
void IterKey::EnlargeBuffer(size_t key_size) {
|
||||
// If size is smaller than buffer size, continue using current buffer,
|
||||
// or the static allocated one, as default
|
||||
// or the inline one, as default
|
||||
assert(key_size > buf_size_);
|
||||
// Need to enlarge the buffer.
|
||||
ResetBuffer();
|
||||
buf_ = new char[key_size];
|
||||
buf_size_ = key_size;
|
||||
}
|
||||
|
||||
void IterKey::EnlargeSecondaryBufferIfNeeded(size_t key_size) {
|
||||
// If size is smaller than buffer size, continue using current buffer,
|
||||
// or the inline one, as default
|
||||
if (key_size <= secondary_buf_size_) {
|
||||
return;
|
||||
}
|
||||
// Need to enlarge the secondary buffer.
|
||||
ResetSecondaryBuffer();
|
||||
secondary_buf_ = new char[key_size];
|
||||
secondary_buf_size_ = key_size;
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
+137
-55
@@ -10,6 +10,7 @@
|
||||
#pragma once
|
||||
#include <stdio.h>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -562,18 +563,28 @@ inline uint64_t GetInternalKeySeqno(const Slice& internal_key) {
|
||||
// allocation for smaller keys.
|
||||
// 3. It tracks user key or internal key, and allow conversion between them.
|
||||
class IterKey {
|
||||
static constexpr size_t kInlineBufferSize = 39;
|
||||
// This is only used by user-defined timestamps in MemTable only feature,
|
||||
// which only supports uint64_t timestamps.
|
||||
static constexpr char kTsMin[] = "\x00\x00\x00\x00\x00\x00\x00\x00";
|
||||
|
||||
public:
|
||||
IterKey()
|
||||
: buf_(space_),
|
||||
key_(buf_),
|
||||
key_size_(0),
|
||||
buf_size_(sizeof(space_)),
|
||||
is_user_key_(true) {}
|
||||
buf_size_(kInlineBufferSize),
|
||||
is_user_key_(true),
|
||||
secondary_buf_(space_for_secondary_buf_),
|
||||
secondary_buf_size_(kInlineBufferSize) {}
|
||||
// No copying allowed
|
||||
IterKey(const IterKey&) = delete;
|
||||
void operator=(const IterKey&) = delete;
|
||||
|
||||
~IterKey() { ResetBuffer(); }
|
||||
~IterKey() {
|
||||
ResetBuffer();
|
||||
ResetSecondaryBuffer();
|
||||
}
|
||||
|
||||
// The bool will be picked up by the next calls to SetKey
|
||||
void SetIsUserKey(bool is_user_key) { is_user_key_ = is_user_key; }
|
||||
@@ -641,13 +652,15 @@ class IterKey {
|
||||
const char* non_shared_data,
|
||||
const size_t non_shared_len,
|
||||
const size_t ts_sz) {
|
||||
std::string kTsMin(ts_sz, static_cast<unsigned char>(0));
|
||||
std::string key_with_ts;
|
||||
std::vector<Slice> key_parts_with_ts;
|
||||
// This function is only used by the UDT in memtable feature, which only
|
||||
// support built in comparators with uint64 timestamps.
|
||||
assert(ts_sz == sizeof(uint64_t));
|
||||
size_t next_key_slice_index = 0;
|
||||
if (IsUserKey()) {
|
||||
key_parts_with_ts = {Slice(key_, shared_len),
|
||||
Slice(non_shared_data, non_shared_len),
|
||||
Slice(kTsMin)};
|
||||
key_slices_[next_key_slice_index++] = Slice(key_, shared_len);
|
||||
key_slices_[next_key_slice_index++] =
|
||||
Slice(non_shared_data, non_shared_len);
|
||||
key_slices_[next_key_slice_index++] = Slice(kTsMin, ts_sz);
|
||||
} else {
|
||||
assert(shared_len + non_shared_len >= kNumInternalBytes);
|
||||
// Invaraint: shared_user_key_len + shared_internal_bytes_len = shared_len
|
||||
@@ -664,30 +677,46 @@ class IterKey {
|
||||
|
||||
// One Slice among the three Slices will get split into two Slices, plus
|
||||
// a timestamp slice.
|
||||
key_parts_with_ts.reserve(5);
|
||||
bool ts_added = false;
|
||||
// Add slice parts and find the right location to add the min timestamp.
|
||||
MaybeAddKeyPartsWithTimestamp(
|
||||
key_, shared_user_key_len,
|
||||
shared_internal_bytes_len + non_shared_len < kNumInternalBytes,
|
||||
shared_len + non_shared_len - kNumInternalBytes, kTsMin,
|
||||
key_parts_with_ts, &ts_added);
|
||||
shared_len + non_shared_len - kNumInternalBytes, ts_sz,
|
||||
&next_key_slice_index, &ts_added);
|
||||
MaybeAddKeyPartsWithTimestamp(
|
||||
key_ + user_key_len, shared_internal_bytes_len,
|
||||
non_shared_len < kNumInternalBytes,
|
||||
shared_internal_bytes_len + non_shared_len - kNumInternalBytes,
|
||||
kTsMin, key_parts_with_ts, &ts_added);
|
||||
shared_internal_bytes_len + non_shared_len - kNumInternalBytes, ts_sz,
|
||||
&next_key_slice_index, &ts_added);
|
||||
MaybeAddKeyPartsWithTimestamp(non_shared_data, non_shared_len,
|
||||
non_shared_len >= kNumInternalBytes,
|
||||
non_shared_len - kNumInternalBytes, kTsMin,
|
||||
key_parts_with_ts, &ts_added);
|
||||
non_shared_len - kNumInternalBytes, ts_sz,
|
||||
&next_key_slice_index, &ts_added);
|
||||
assert(ts_added);
|
||||
}
|
||||
SetKeyImpl(next_key_slice_index,
|
||||
/* total_bytes= */ shared_len + non_shared_len + ts_sz);
|
||||
}
|
||||
|
||||
Slice new_key(SliceParts(&key_parts_with_ts.front(),
|
||||
static_cast<int>(key_parts_with_ts.size())),
|
||||
&key_with_ts);
|
||||
SetKey(new_key);
|
||||
Slice SetKeyWithPaddedMinTimestamp(const Slice& key, size_t ts_sz) {
|
||||
// This function is only used by the UDT in memtable feature, which only
|
||||
// support built in comparators with uint64 timestamps.
|
||||
assert(ts_sz == sizeof(uint64_t));
|
||||
size_t num_key_slices = 0;
|
||||
if (is_user_key_) {
|
||||
key_slices_[0] = key;
|
||||
key_slices_[1] = Slice(kTsMin, ts_sz);
|
||||
num_key_slices = 2;
|
||||
} else {
|
||||
assert(key.size() >= kNumInternalBytes);
|
||||
size_t user_key_size = key.size() - kNumInternalBytes;
|
||||
key_slices_[0] = Slice(key.data(), user_key_size);
|
||||
key_slices_[1] = Slice(kTsMin, ts_sz);
|
||||
key_slices_[2] = Slice(key.data() + user_key_size, kNumInternalBytes);
|
||||
num_key_slices = 3;
|
||||
}
|
||||
return SetKeyImpl(num_key_slices, key.size() + ts_sz);
|
||||
}
|
||||
|
||||
Slice SetKey(const Slice& key, bool copy = true) {
|
||||
@@ -718,15 +747,6 @@ class IterKey {
|
||||
return Slice(key_, key_n);
|
||||
}
|
||||
|
||||
// Copy the key into IterKey own buf_
|
||||
void OwnKey() {
|
||||
assert(IsKeyPinned() == true);
|
||||
|
||||
Reserve(key_size_);
|
||||
memcpy(buf_, key_, key_size_);
|
||||
key_ = buf_;
|
||||
}
|
||||
|
||||
// Update the sequence number in the internal key. Guarantees not to
|
||||
// invalidate slices to the key (and the user key).
|
||||
void UpdateInternalKey(uint64_t seq, ValueType t, const Slice* ts = nullptr) {
|
||||
@@ -738,10 +758,15 @@ class IterKey {
|
||||
ts->size());
|
||||
}
|
||||
uint64_t newval = (seq << 8) | t;
|
||||
EncodeFixed64(&buf_[key_size_ - kNumInternalBytes], newval);
|
||||
if (key_ == buf_) {
|
||||
EncodeFixed64(&buf_[key_size_ - kNumInternalBytes], newval);
|
||||
} else {
|
||||
assert(key_ == secondary_buf_);
|
||||
EncodeFixed64(&secondary_buf_[key_size_ - kNumInternalBytes], newval);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsKeyPinned() const { return (key_ != buf_); }
|
||||
bool IsKeyPinned() const { return key_ != buf_ && key_ != secondary_buf_; }
|
||||
|
||||
// If `ts` is provided, user_key should not contain timestamp,
|
||||
// and `ts` is appended after user_key.
|
||||
@@ -806,8 +831,24 @@ class IterKey {
|
||||
const char* key_;
|
||||
size_t key_size_;
|
||||
size_t buf_size_;
|
||||
char space_[39]; // Avoid allocation for short keys
|
||||
char space_[kInlineBufferSize]; // Avoid allocation for short keys
|
||||
bool is_user_key_;
|
||||
// Below variables are only used by user-defined timestamps in MemTable only
|
||||
// feature for iterating keys in an index block or a data block.
|
||||
//
|
||||
// We will alternate between buf_ and secondary_buf_ to hold the key. key_
|
||||
// will be modified in accordance to point to the right one. This is to avoid
|
||||
// an extra copy when we need to copy some shared bytes from previous key
|
||||
// (delta encoding), and we need to pad a min timestamp at the right location.
|
||||
char space_for_secondary_buf_[kInlineBufferSize]; // Avoid allocation for
|
||||
// short keys
|
||||
char* secondary_buf_;
|
||||
size_t secondary_buf_size_;
|
||||
// Use to track the pieces that together make the whole key. We then copy
|
||||
// these pieces in order either into buf_ or secondary_buf_ depending on where
|
||||
// the previous key is held.
|
||||
std::array<Slice, 5> key_slices_;
|
||||
// End of variables used by user-defined timestamps in MemTable only feature.
|
||||
|
||||
Slice SetKeyImpl(const Slice& key, bool copy) {
|
||||
size_t size = key.size();
|
||||
@@ -824,18 +865,64 @@ class IterKey {
|
||||
return Slice(key_, key_size_);
|
||||
}
|
||||
|
||||
Slice SetKeyImpl(size_t num_key_slices, size_t total_bytes) {
|
||||
assert(num_key_slices <= 5);
|
||||
char* buf_start = nullptr;
|
||||
if (key_ == buf_) {
|
||||
// If the previous key is in buf_, we copy key_slices_ in order into
|
||||
// secondary_buf_.
|
||||
EnlargeSecondaryBufferIfNeeded(total_bytes);
|
||||
buf_start = secondary_buf_;
|
||||
key_ = secondary_buf_;
|
||||
} else {
|
||||
// Copy key_slices_ in order into buf_.
|
||||
EnlargeBufferIfNeeded(total_bytes);
|
||||
buf_start = buf_;
|
||||
key_ = buf_;
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
size_t actual_total_bytes = 0;
|
||||
#endif // NDEBUG
|
||||
for (size_t i = 0; i < num_key_slices; i++) {
|
||||
size_t key_slice_size = key_slices_[i].size();
|
||||
memcpy(buf_start, key_slices_[i].data(), key_slice_size);
|
||||
buf_start += key_slice_size;
|
||||
#ifndef NDEBUG
|
||||
actual_total_bytes += key_slice_size;
|
||||
#endif // NDEBUG
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
assert(actual_total_bytes == total_bytes);
|
||||
#endif // NDEBUG
|
||||
key_size_ = total_bytes;
|
||||
return Slice(key_, key_size_);
|
||||
}
|
||||
|
||||
void ResetBuffer() {
|
||||
if (key_ == buf_) {
|
||||
key_size_ = 0;
|
||||
}
|
||||
if (buf_ != space_) {
|
||||
delete[] buf_;
|
||||
buf_ = space_;
|
||||
}
|
||||
buf_size_ = sizeof(space_);
|
||||
key_size_ = 0;
|
||||
buf_size_ = kInlineBufferSize;
|
||||
}
|
||||
|
||||
void ResetSecondaryBuffer() {
|
||||
if (key_ == secondary_buf_) {
|
||||
key_size_ = 0;
|
||||
}
|
||||
if (secondary_buf_ != space_for_secondary_buf_) {
|
||||
delete[] secondary_buf_;
|
||||
secondary_buf_ = space_for_secondary_buf_;
|
||||
}
|
||||
secondary_buf_size_ = kInlineBufferSize;
|
||||
}
|
||||
|
||||
// Enlarge the buffer size if needed based on key_size.
|
||||
// By default, static allocated buffer is used. Once there is a key
|
||||
// larger than the static allocated buffer, another buffer is dynamically
|
||||
// By default, inline buffer is used. Once there is a key
|
||||
// larger than the inline buffer, another buffer is dynamically
|
||||
// allocated, until a larger key buffer is requested. In that case, we
|
||||
// reallocate buffer and delete the old one.
|
||||
void EnlargeBufferIfNeeded(size_t key_size) {
|
||||
@@ -846,23 +933,27 @@ class IterKey {
|
||||
}
|
||||
}
|
||||
|
||||
void EnlargeSecondaryBufferIfNeeded(size_t key_size);
|
||||
|
||||
void EnlargeBuffer(size_t key_size);
|
||||
|
||||
void MaybeAddKeyPartsWithTimestamp(const char* slice_data,
|
||||
const size_t slice_sz, bool add_timestamp,
|
||||
const size_t left_sz,
|
||||
const std::string& min_timestamp,
|
||||
std::vector<Slice>& key_parts,
|
||||
const size_t left_sz, const size_t ts_sz,
|
||||
size_t* next_key_slice_idx,
|
||||
bool* ts_added) {
|
||||
assert(next_key_slice_idx);
|
||||
if (add_timestamp && !*ts_added) {
|
||||
assert(slice_sz >= left_sz);
|
||||
key_parts.emplace_back(slice_data, left_sz);
|
||||
key_parts.emplace_back(min_timestamp);
|
||||
key_parts.emplace_back(slice_data + left_sz, slice_sz - left_sz);
|
||||
key_slices_[(*next_key_slice_idx)++] = Slice(slice_data, left_sz);
|
||||
key_slices_[(*next_key_slice_idx)++] = Slice(kTsMin, ts_sz);
|
||||
key_slices_[(*next_key_slice_idx)++] =
|
||||
Slice(slice_data + left_sz, slice_sz - left_sz);
|
||||
*ts_added = true;
|
||||
} else {
|
||||
key_parts.emplace_back(slice_data, slice_sz);
|
||||
key_slices_[(*next_key_slice_idx)++] = Slice(slice_data, slice_sz);
|
||||
}
|
||||
assert(*next_key_slice_idx <= 5);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -936,22 +1027,13 @@ struct RangeTombstone {
|
||||
// User-defined timestamp is enabled, `sk` and `ek` should be user key
|
||||
// with timestamp, `ts` will replace the timestamps in `sk` and
|
||||
// `ek`.
|
||||
// When `logical_strip_timestamp` is true, the timestamps in `sk` and `ek`
|
||||
// will be replaced with min timestamp.
|
||||
RangeTombstone(Slice sk, Slice ek, SequenceNumber sn, Slice ts,
|
||||
bool logical_strip_timestamp)
|
||||
: seq_(sn) {
|
||||
RangeTombstone(Slice sk, Slice ek, SequenceNumber sn, Slice ts) : seq_(sn) {
|
||||
const size_t ts_sz = ts.size();
|
||||
assert(ts_sz > 0);
|
||||
pinned_start_key_.reserve(sk.size());
|
||||
pinned_end_key_.reserve(ek.size());
|
||||
if (logical_strip_timestamp) {
|
||||
AppendUserKeyWithMinTimestamp(&pinned_start_key_, sk, ts_sz);
|
||||
AppendUserKeyWithMinTimestamp(&pinned_end_key_, ek, ts_sz);
|
||||
} else {
|
||||
AppendUserKeyWithDifferentTimestamp(&pinned_start_key_, sk, ts);
|
||||
AppendUserKeyWithDifferentTimestamp(&pinned_end_key_, ek, ts);
|
||||
}
|
||||
AppendUserKeyWithDifferentTimestamp(&pinned_start_key_, sk, ts);
|
||||
AppendUserKeyWithDifferentTimestamp(&pinned_end_key_, ek, ts);
|
||||
start_key_ = pinned_start_key_;
|
||||
end_key_ = pinned_end_key_;
|
||||
ts_ = Slice(pinned_start_key_.data() + sk.size() - ts_sz, ts_sz);
|
||||
|
||||
@@ -132,6 +132,7 @@ void EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
<< table_properties.compression_name << "compression_options"
|
||||
<< table_properties.compression_options << "creation_time"
|
||||
<< table_properties.creation_time << "oldest_key_time"
|
||||
<< table_properties.newest_key_time << "newest_key_time"
|
||||
<< table_properties.oldest_key_time << "file_creation_time"
|
||||
<< table_properties.file_creation_time
|
||||
<< "slow_compression_estimated_data_size"
|
||||
|
||||
@@ -182,6 +182,36 @@ class ExternalSSTFileBasicTest
|
||||
write_global_seqno, verify_checksums_before_ingest, true_data);
|
||||
}
|
||||
|
||||
void VerifyInputFilesInternalStatsForOutputLevel(
|
||||
int output_level, int num_input_files_in_non_output_levels,
|
||||
int num_input_files_in_output_level,
|
||||
int num_filtered_input_files_in_non_output_levels,
|
||||
int num_filtered_input_files_in_output_level,
|
||||
uint64_t bytes_skipped_non_output_levels,
|
||||
uint64_t bytes_skipped_output_level) {
|
||||
ColumnFamilyHandleImpl* cfh =
|
||||
static_cast<ColumnFamilyHandleImpl*>(dbfull()->DefaultColumnFamily());
|
||||
ColumnFamilyData* cfd = cfh->cfd();
|
||||
const InternalStats* internal_stats_ptr = cfd->internal_stats();
|
||||
const std::vector<InternalStats::CompactionStats>& comp_stats =
|
||||
internal_stats_ptr->TEST_GetCompactionStats();
|
||||
|
||||
EXPECT_EQ(num_input_files_in_non_output_levels,
|
||||
comp_stats[output_level].num_input_files_in_non_output_levels);
|
||||
EXPECT_EQ(num_input_files_in_output_level,
|
||||
comp_stats[output_level].num_input_files_in_output_level);
|
||||
EXPECT_EQ(
|
||||
num_filtered_input_files_in_non_output_levels,
|
||||
comp_stats[output_level].num_filtered_input_files_in_non_output_levels);
|
||||
EXPECT_EQ(
|
||||
num_filtered_input_files_in_output_level,
|
||||
comp_stats[output_level].num_filtered_input_files_in_output_level);
|
||||
EXPECT_EQ(bytes_skipped_non_output_levels,
|
||||
comp_stats[output_level].bytes_skipped_non_output_levels);
|
||||
EXPECT_EQ(bytes_skipped_output_level,
|
||||
comp_stats[output_level].bytes_skipped_output_level);
|
||||
}
|
||||
|
||||
~ExternalSSTFileBasicTest() override {
|
||||
DestroyDir(env_, sst_files_dir_).PermitUncheckedError();
|
||||
}
|
||||
@@ -241,6 +271,79 @@ TEST_F(ExternalSSTFileBasicTest, Basic) {
|
||||
DestroyAndRecreateExternalSSTFilesDir();
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileBasicTest, AlignedBufferedWrite) {
|
||||
class AlignedWriteFS : public FileSystemWrapper {
|
||||
public:
|
||||
explicit AlignedWriteFS(const std::shared_ptr<FileSystem>& _target)
|
||||
: FileSystemWrapper(_target) {}
|
||||
~AlignedWriteFS() override {}
|
||||
const char* Name() const override { return "AlignedWriteFS"; }
|
||||
|
||||
IOStatus NewWritableFile(const std::string& fname, const FileOptions& opts,
|
||||
std::unique_ptr<FSWritableFile>* result,
|
||||
IODebugContext* dbg) override {
|
||||
class AlignedWritableFile : public FSWritableFileOwnerWrapper {
|
||||
public:
|
||||
AlignedWritableFile(std::unique_ptr<FSWritableFile>& file)
|
||||
: FSWritableFileOwnerWrapper(std::move(file)), last_write_(false) {}
|
||||
|
||||
using FSWritableFileOwnerWrapper::Append;
|
||||
IOStatus Append(const Slice& data, const IOOptions& options,
|
||||
IODebugContext* dbg) override {
|
||||
EXPECT_FALSE(last_write_);
|
||||
if ((data.size() & (data.size() - 1)) != 0) {
|
||||
last_write_ = true;
|
||||
}
|
||||
return target()->Append(data, options, dbg);
|
||||
}
|
||||
|
||||
private:
|
||||
bool last_write_;
|
||||
};
|
||||
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
IOStatus s = target()->NewWritableFile(fname, opts, &file, dbg);
|
||||
if (s.ok()) {
|
||||
result->reset(new AlignedWritableFile(file));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
Options options = CurrentOptions();
|
||||
std::shared_ptr<AlignedWriteFS> aligned_fs =
|
||||
std::make_shared<AlignedWriteFS>(env_->GetFileSystem());
|
||||
std::unique_ptr<Env> wrap_env(
|
||||
new CompositeEnvWrapper(options.env, aligned_fs));
|
||||
options.env = wrap_env.get();
|
||||
|
||||
EnvOptions env_options;
|
||||
env_options.writable_file_max_buffer_size = 64 * 1024 * 1024;
|
||||
|
||||
SstFileWriter sst_file_writer(env_options, options);
|
||||
|
||||
// Current file size should be 0 after sst_file_writer init and before open a
|
||||
// file.
|
||||
ASSERT_EQ(sst_file_writer.FileSize(), 0);
|
||||
|
||||
// file1.sst (0 => 99)
|
||||
std::string file1 = sst_files_dir_ + "file1.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file1));
|
||||
Random r(301);
|
||||
for (int k = 0; k < 16 * 1024; k++) {
|
||||
uint32_t num = 4096 + r.Uniform(8192);
|
||||
std::string random_string = r.RandomString(num);
|
||||
ASSERT_OK(sst_file_writer.Put(Key(k), random_string));
|
||||
}
|
||||
Status s = sst_file_writer.Finish();
|
||||
ASSERT_OK(s) << s.ToString();
|
||||
|
||||
// Current file size should be non-zero after success write.
|
||||
ASSERT_GT(sst_file_writer.FileSize(), 0);
|
||||
|
||||
DestroyAndRecreateExternalSSTFilesDir();
|
||||
}
|
||||
|
||||
class ChecksumVerifyHelper {
|
||||
private:
|
||||
Options options_;
|
||||
@@ -1790,8 +1893,8 @@ TEST_F(ExternalSSTFileBasicTest, OverlappingFiles) {
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file1 = sst_files_dir_ + "file1.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file1));
|
||||
ASSERT_OK(sst_file_writer.Put("a", "z"));
|
||||
ASSERT_OK(sst_file_writer.Put("i", "m"));
|
||||
ASSERT_OK(sst_file_writer.Put("a", "a1"));
|
||||
ASSERT_OK(sst_file_writer.Put("i", "i1"));
|
||||
ExternalSstFileInfo file1_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file1_info));
|
||||
files.push_back(std::move(file1));
|
||||
@@ -1800,16 +1903,32 @@ TEST_F(ExternalSSTFileBasicTest, OverlappingFiles) {
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file2 = sst_files_dir_ + "file2.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file2));
|
||||
ASSERT_OK(sst_file_writer.Put("i", "k"));
|
||||
ASSERT_OK(sst_file_writer.Put("i", "i2"));
|
||||
ExternalSstFileInfo file2_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file2_info));
|
||||
files.push_back(std::move(file2));
|
||||
}
|
||||
|
||||
{
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file3 = sst_files_dir_ + "file3.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file3));
|
||||
ASSERT_OK(sst_file_writer.Put("j", "j1"));
|
||||
ASSERT_OK(sst_file_writer.Put("m", "m1"));
|
||||
ExternalSstFileInfo file3_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file3_info));
|
||||
files.push_back(std::move(file3));
|
||||
}
|
||||
|
||||
IngestExternalFileOptions ifo;
|
||||
ifo.allow_global_seqno = false;
|
||||
ASSERT_NOK(db_->IngestExternalFile(files, ifo));
|
||||
ifo.allow_global_seqno = true;
|
||||
ASSERT_OK(db_->IngestExternalFile(files, ifo));
|
||||
ASSERT_EQ(Get("a"), "z");
|
||||
ASSERT_EQ(Get("i"), "k");
|
||||
ASSERT_EQ(Get("a"), "a1");
|
||||
ASSERT_EQ(Get("i"), "i2");
|
||||
ASSERT_EQ(Get("j"), "j1");
|
||||
ASSERT_EQ(Get("m"), "m1");
|
||||
|
||||
int total_keys = 0;
|
||||
Iterator* iter = db_->NewIterator(ReadOptions());
|
||||
@@ -1817,10 +1936,495 @@ TEST_F(ExternalSSTFileBasicTest, OverlappingFiles) {
|
||||
ASSERT_OK(iter->status());
|
||||
total_keys++;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
delete iter;
|
||||
ASSERT_EQ(total_keys, 2);
|
||||
ASSERT_EQ(total_keys, 4);
|
||||
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(6));
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(5));
|
||||
}
|
||||
|
||||
class CompactionJobStatsCheckerForFilteredFiles : public EventListener {
|
||||
public:
|
||||
CompactionJobStatsCheckerForFilteredFiles(
|
||||
int num_input_files, int num_input_files_at_output_level,
|
||||
int num_filtered_input_files,
|
||||
int num_filtered_input_files_at_output_level)
|
||||
: num_input_files_(num_input_files),
|
||||
num_input_files_at_output_level_(num_input_files_at_output_level),
|
||||
num_filtered_input_files_(num_filtered_input_files),
|
||||
num_filtered_input_files_at_output_level_(
|
||||
num_filtered_input_files_at_output_level) {}
|
||||
|
||||
void OnCompactionCompleted(DB* /*db*/, const CompactionJobInfo& ci) override {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
ASSERT_EQ(num_input_files_, ci.stats.num_input_files);
|
||||
ASSERT_EQ(num_input_files_at_output_level_,
|
||||
ci.stats.num_input_files_at_output_level);
|
||||
ASSERT_EQ(num_filtered_input_files_, ci.stats.num_filtered_input_files);
|
||||
ASSERT_EQ(num_filtered_input_files_at_output_level_,
|
||||
ci.stats.num_filtered_input_files_at_output_level);
|
||||
ASSERT_EQ(ci.stats.total_skipped_input_bytes,
|
||||
expected_compaction_skipped_file_size_);
|
||||
}
|
||||
|
||||
void SetExpectedCompactionSkippedFileSize(uint64_t expected_size) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
expected_compaction_skipped_file_size_ = expected_size;
|
||||
}
|
||||
|
||||
private:
|
||||
int num_input_files_ = 0;
|
||||
int num_input_files_at_output_level_ = 0;
|
||||
int num_filtered_input_files_ = 0;
|
||||
int num_filtered_input_files_at_output_level_ = 0;
|
||||
std::mutex mutex_;
|
||||
uint64_t expected_compaction_skipped_file_size_ = 0;
|
||||
};
|
||||
|
||||
TEST_F(ExternalSSTFileBasicTest, AtomicReplaceDataWithStandaloneRangeDeletion) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = CompactionStyle::kCompactionStyleUniversal;
|
||||
int kCompactionNumInputFiles = 1;
|
||||
int kCompactionNumInputFilesAtOutputLevel = 0;
|
||||
int kCompactionNumFilteredInputFiles = 2;
|
||||
int kCompactionNumFilteredInputFilesAtOutputLevel = 2;
|
||||
auto compaction_listener =
|
||||
std::make_shared<CompactionJobStatsCheckerForFilteredFiles>(
|
||||
kCompactionNumInputFiles, kCompactionNumInputFilesAtOutputLevel,
|
||||
kCompactionNumFilteredInputFiles,
|
||||
kCompactionNumFilteredInputFilesAtOutputLevel);
|
||||
options.listeners.push_back(compaction_listener);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
size_t compaction_skipped_file_size = 0;
|
||||
std::vector<std::string> files;
|
||||
{
|
||||
// Writes first version of data in range partitioned files.
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file1 = sst_files_dir_ + "file1.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file1));
|
||||
ASSERT_OK(sst_file_writer.Put("a", "a1"));
|
||||
ASSERT_OK(sst_file_writer.Put("b", "b1"));
|
||||
ExternalSstFileInfo file1_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file1_info));
|
||||
compaction_skipped_file_size += file1_info.file_size;
|
||||
files.push_back(std::move(file1));
|
||||
|
||||
std::string file2 = sst_files_dir_ + "file2.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file2));
|
||||
ASSERT_OK(sst_file_writer.Put("x", "x1"));
|
||||
ASSERT_OK(sst_file_writer.Put("y", "y1"));
|
||||
ExternalSstFileInfo file2_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file2_info));
|
||||
compaction_skipped_file_size += file2_info.file_size;
|
||||
files.push_back(std::move(file2));
|
||||
compaction_listener->SetExpectedCompactionSkippedFileSize(
|
||||
compaction_skipped_file_size);
|
||||
}
|
||||
|
||||
IngestExternalFileOptions ifo;
|
||||
ASSERT_OK(db_->IngestExternalFile(files, ifo));
|
||||
ASSERT_EQ(Get("a"), "a1");
|
||||
ASSERT_EQ(Get("b"), "b1");
|
||||
ASSERT_EQ(Get("x"), "x1");
|
||||
ASSERT_EQ(Get("y"), "y1");
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(6));
|
||||
|
||||
{
|
||||
// Atomically delete old version of data with one range delete file.
|
||||
// And a new batch of range partitioned files with new version of data.
|
||||
files.clear();
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file2 = sst_files_dir_ + "file2.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file2));
|
||||
ASSERT_OK(sst_file_writer.DeleteRange("a", "z"));
|
||||
ExternalSstFileInfo file2_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file2_info));
|
||||
files.push_back(std::move(file2));
|
||||
|
||||
std::string file3 = sst_files_dir_ + "file3.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file3));
|
||||
ASSERT_OK(sst_file_writer.Put("a", "a2"));
|
||||
ASSERT_OK(sst_file_writer.Put("b", "b2"));
|
||||
ExternalSstFileInfo file3_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file3_info));
|
||||
files.push_back(std::move(file3));
|
||||
|
||||
std::string file4 = sst_files_dir_ + "file4.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file4));
|
||||
ASSERT_OK(sst_file_writer.Put("x", "x2"));
|
||||
ASSERT_OK(sst_file_writer.Put("y", "y2"));
|
||||
ExternalSstFileInfo file4_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file4_info));
|
||||
files.push_back(std::move(file4));
|
||||
}
|
||||
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
|
||||
auto seqno_before_ingestion = db_->GetLatestSequenceNumber();
|
||||
ASSERT_OK(db_->IngestExternalFile(files, ifo));
|
||||
// Overlapping files each occupy one new sequence number.
|
||||
ASSERT_EQ(db_->GetLatestSequenceNumber(), seqno_before_ingestion + 3);
|
||||
|
||||
// Check old version of data, big range deletion, new version of data are
|
||||
// on separate levels.
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(4));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(5));
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(6));
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(4));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(5));
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(6));
|
||||
|
||||
bool compaction_iter_input_checked = false;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::MakeInputIterator:NewCompactionMergingIterator",
|
||||
[&](void* arg) {
|
||||
size_t* num_input_files = static_cast<size_t*>(arg);
|
||||
EXPECT_EQ(1, *num_input_files);
|
||||
compaction_iter_input_checked = true;
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(4));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(5));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(6));
|
||||
ASSERT_TRUE(compaction_iter_input_checked);
|
||||
|
||||
ASSERT_EQ(Get("a"), "a2");
|
||||
ASSERT_EQ(Get("b"), "b2");
|
||||
ASSERT_EQ(Get("x"), "x2");
|
||||
ASSERT_EQ(Get("y"), "y2");
|
||||
|
||||
VerifyInputFilesInternalStatsForOutputLevel(
|
||||
/*output_level*/ 6,
|
||||
kCompactionNumInputFiles - kCompactionNumInputFilesAtOutputLevel,
|
||||
kCompactionNumInputFilesAtOutputLevel,
|
||||
kCompactionNumFilteredInputFiles -
|
||||
kCompactionNumFilteredInputFilesAtOutputLevel,
|
||||
kCompactionNumFilteredInputFilesAtOutputLevel,
|
||||
/*bytes_skipped_non_output_levels*/ 0,
|
||||
/*bytes_skipped_output_level*/ compaction_skipped_file_size);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileBasicTest,
|
||||
PartiallyReplaceDataWithOneStandaloneRangeDeletion) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = CompactionStyle::kCompactionStyleUniversal;
|
||||
int kCompactionNumInputFiles = 2;
|
||||
int kCompactionNumInputFilesAtOutputLevel = 1;
|
||||
int kCompactionNumFilteredInputFiles = 1;
|
||||
int kCompactionNumFilteredInputFilesAtOutputLevel = 1;
|
||||
auto compaction_listener =
|
||||
std::make_shared<CompactionJobStatsCheckerForFilteredFiles>(
|
||||
kCompactionNumInputFiles, kCompactionNumInputFilesAtOutputLevel,
|
||||
kCompactionNumFilteredInputFiles,
|
||||
kCompactionNumFilteredInputFilesAtOutputLevel);
|
||||
options.listeners.push_back(compaction_listener);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::vector<std::string> files;
|
||||
size_t compaction_skipped_file_size = 0;
|
||||
{
|
||||
// Writes first version of data in range partitioned files.
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file1 = sst_files_dir_ + "file1.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file1));
|
||||
ASSERT_OK(sst_file_writer.Put("a", "a1"));
|
||||
ASSERT_OK(sst_file_writer.Put("b", "b1"));
|
||||
ExternalSstFileInfo file1_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file1_info));
|
||||
compaction_skipped_file_size += file1_info.file_size;
|
||||
files.push_back(std::move(file1));
|
||||
compaction_listener->SetExpectedCompactionSkippedFileSize(
|
||||
compaction_skipped_file_size);
|
||||
|
||||
std::string file2 = sst_files_dir_ + "file2.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file2));
|
||||
ASSERT_OK(sst_file_writer.Put("x", "x1"));
|
||||
ASSERT_OK(sst_file_writer.Put("y", "y"));
|
||||
ExternalSstFileInfo file2_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file2_info));
|
||||
files.push_back(std::move(file2));
|
||||
}
|
||||
|
||||
IngestExternalFileOptions ifo;
|
||||
ASSERT_OK(db_->IngestExternalFile(files, ifo));
|
||||
ASSERT_EQ(Get("a"), "a1");
|
||||
ASSERT_EQ(Get("b"), "b1");
|
||||
ASSERT_EQ(Get("x"), "x1");
|
||||
ASSERT_EQ(Get("y"), "y");
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(6));
|
||||
|
||||
{
|
||||
// Partially delete old version of data with one range delete file. And
|
||||
// add new version of data for deleted range.
|
||||
files.clear();
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file2 = sst_files_dir_ + "file2.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file2));
|
||||
ASSERT_OK(sst_file_writer.DeleteRange("a", "y"));
|
||||
ExternalSstFileInfo file2_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file2_info));
|
||||
files.push_back(std::move(file2));
|
||||
std::string file3 = sst_files_dir_ + "file3.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file3));
|
||||
ASSERT_OK(sst_file_writer.Put("a", "a2"));
|
||||
ASSERT_OK(sst_file_writer.Put("b", "b2"));
|
||||
ExternalSstFileInfo file3_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file3_info));
|
||||
files.push_back(std::move(file3));
|
||||
std::string file4 = sst_files_dir_ + "file4.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file4));
|
||||
ASSERT_OK(sst_file_writer.Put("h", "h1"));
|
||||
ASSERT_OK(sst_file_writer.Put("x", "x2"));
|
||||
ExternalSstFileInfo file4_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file4_info));
|
||||
files.push_back(std::move(file4));
|
||||
}
|
||||
|
||||
bool compaction_iter_input_checked = false;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::MakeInputIterator:NewCompactionMergingIterator",
|
||||
[&](void* arg) {
|
||||
size_t* num_input_files = static_cast<size_t*>(arg);
|
||||
EXPECT_EQ(2, *num_input_files);
|
||||
compaction_iter_input_checked = true;
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(db_->IngestExternalFile(files, ifo));
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(4));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(5));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(6));
|
||||
ASSERT_TRUE(compaction_iter_input_checked);
|
||||
|
||||
ASSERT_EQ(Get("a"), "a2");
|
||||
ASSERT_EQ(Get("b"), "b2");
|
||||
ASSERT_EQ(Get("h"), "h1");
|
||||
ASSERT_EQ(Get("x"), "x2");
|
||||
ASSERT_EQ(Get("y"), "y");
|
||||
|
||||
VerifyInputFilesInternalStatsForOutputLevel(
|
||||
/*output_level*/ 6,
|
||||
kCompactionNumInputFiles - kCompactionNumInputFilesAtOutputLevel,
|
||||
kCompactionNumInputFilesAtOutputLevel,
|
||||
kCompactionNumFilteredInputFiles -
|
||||
kCompactionNumFilteredInputFilesAtOutputLevel,
|
||||
kCompactionNumFilteredInputFilesAtOutputLevel,
|
||||
/*bytes_skipped_non_output_levels*/ 0,
|
||||
/*bytes_skipped_output_level*/ compaction_skipped_file_size);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileBasicTest,
|
||||
PartiallyReplaceDataWithMultipleStandaloneRangeDeletions) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = CompactionStyle::kCompactionStyleUniversal;
|
||||
int kCompactionNumInputFiles = 2;
|
||||
int kCompactionNumInputFilesAtOutputLevel = 0;
|
||||
int kCompactionNumFilteredInputFiles = 2;
|
||||
int kCompactionNumFilteredInputFilesAtOutputLevel = 2;
|
||||
// Two compactions each included on standalone range deletion file that
|
||||
// filters input file on the non start level.
|
||||
auto compaction_listener =
|
||||
std::make_shared<CompactionJobStatsCheckerForFilteredFiles>(
|
||||
kCompactionNumInputFiles / 2,
|
||||
kCompactionNumInputFilesAtOutputLevel / 2,
|
||||
kCompactionNumFilteredInputFiles / 2,
|
||||
kCompactionNumFilteredInputFilesAtOutputLevel / 2);
|
||||
options.listeners.push_back(compaction_listener);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::vector<std::string> files;
|
||||
ExternalSstFileInfo file1_info;
|
||||
ExternalSstFileInfo file3_info;
|
||||
{
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file1 = sst_files_dir_ + "file1.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file1));
|
||||
ASSERT_OK(sst_file_writer.Put("a", "a1"));
|
||||
ASSERT_OK(sst_file_writer.Finish(&file1_info));
|
||||
files.push_back(std::move(file1));
|
||||
std::string file2 = sst_files_dir_ + "file2.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file2));
|
||||
ASSERT_OK(sst_file_writer.Put("h", "h"));
|
||||
ExternalSstFileInfo file2_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file2_info));
|
||||
files.push_back(std::move(file2));
|
||||
std::string file3 = sst_files_dir_ + "file3.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file3));
|
||||
ASSERT_OK(sst_file_writer.Put("x", "x1"));
|
||||
ASSERT_OK(sst_file_writer.Finish(&file3_info));
|
||||
files.push_back(std::move(file3));
|
||||
}
|
||||
|
||||
IngestExternalFileOptions ifo;
|
||||
ASSERT_OK(db_->IngestExternalFile(files, ifo));
|
||||
ASSERT_EQ(Get("a"), "a1");
|
||||
ASSERT_EQ(Get("h"), "h");
|
||||
ASSERT_EQ(Get("x"), "x1");
|
||||
ASSERT_EQ(3, NumTableFilesAtLevel(6));
|
||||
|
||||
{
|
||||
files.clear();
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file4 = sst_files_dir_ + "file4.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file4));
|
||||
ASSERT_OK(sst_file_writer.DeleteRange("a", "b"));
|
||||
ExternalSstFileInfo file4_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file4_info));
|
||||
files.push_back(std::move(file4));
|
||||
std::string file5 = sst_files_dir_ + "file5.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file5));
|
||||
ASSERT_OK(sst_file_writer.DeleteRange("x", "y"));
|
||||
ExternalSstFileInfo file5_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file5_info));
|
||||
files.push_back(std::move(file5));
|
||||
std::string file6 = sst_files_dir_ + "file6.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file6));
|
||||
ASSERT_OK(sst_file_writer.Put("a", "a2"));
|
||||
ExternalSstFileInfo file6_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file6_info));
|
||||
files.push_back(std::move(file6));
|
||||
std::string file7 = sst_files_dir_ + "file7.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file7));
|
||||
ASSERT_OK(sst_file_writer.Put("x", "x2"));
|
||||
ExternalSstFileInfo file7_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file7_info));
|
||||
files.push_back(std::move(file7));
|
||||
}
|
||||
|
||||
int num_compactions = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::MakeInputIterator:NewCompactionMergingIterator",
|
||||
[&](void* arg) {
|
||||
size_t* num_input_files = static_cast<size_t*>(arg);
|
||||
EXPECT_EQ(1, *num_input_files);
|
||||
num_compactions += 1;
|
||||
if (num_compactions == 2) {
|
||||
compaction_listener->SetExpectedCompactionSkippedFileSize(
|
||||
file3_info.file_size);
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
compaction_listener->SetExpectedCompactionSkippedFileSize(
|
||||
file1_info.file_size);
|
||||
ASSERT_OK(db_->IngestExternalFile(files, ifo));
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(4));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(5));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(6));
|
||||
ASSERT_EQ(2, num_compactions);
|
||||
|
||||
ASSERT_EQ(Get("a"), "a2");
|
||||
ASSERT_EQ(Get("h"), "h");
|
||||
ASSERT_EQ(Get("x"), "x2");
|
||||
VerifyInputFilesInternalStatsForOutputLevel(
|
||||
/*output_level*/ 6,
|
||||
kCompactionNumInputFiles - kCompactionNumInputFilesAtOutputLevel,
|
||||
kCompactionNumInputFilesAtOutputLevel,
|
||||
kCompactionNumFilteredInputFiles -
|
||||
kCompactionNumFilteredInputFilesAtOutputLevel,
|
||||
kCompactionNumFilteredInputFilesAtOutputLevel,
|
||||
/*bytes_skipped_non_output_levels*/ 0,
|
||||
/*bytes_skipped_output_level*/ file1_info.file_size +
|
||||
file3_info.file_size);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileBasicTest, StandaloneRangeDeletionEndKeyIsExclusive) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = CompactionStyle::kCompactionStyleUniversal;
|
||||
int kCompactionNumInputFiles = 2;
|
||||
int kCompactionNumInputFilesAtOutputLevel = 1;
|
||||
int kCompactionNumFilteredInputFiles = 0;
|
||||
int kCompactionNumFilteredInputFilesAtOutputLevel = 0;
|
||||
auto compaction_listener =
|
||||
std::make_shared<CompactionJobStatsCheckerForFilteredFiles>(
|
||||
kCompactionNumInputFiles, kCompactionNumInputFilesAtOutputLevel,
|
||||
kCompactionNumFilteredInputFiles,
|
||||
kCompactionNumFilteredInputFilesAtOutputLevel);
|
||||
options.listeners.push_back(compaction_listener);
|
||||
// No compaction input files are filtered because the range deletion file's
|
||||
// end is exclusive, so it cannot cover the whole file.
|
||||
compaction_listener->SetExpectedCompactionSkippedFileSize(0);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::vector<std::string> files;
|
||||
{
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file1 = sst_files_dir_ + "file1.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file1));
|
||||
ASSERT_OK(sst_file_writer.Put("a", "a"));
|
||||
ASSERT_OK(sst_file_writer.Put("b", "b"));
|
||||
ExternalSstFileInfo file1_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file1_info));
|
||||
files.push_back(std::move(file1));
|
||||
}
|
||||
|
||||
IngestExternalFileOptions ifo;
|
||||
ASSERT_OK(db_->IngestExternalFile(files, ifo));
|
||||
ASSERT_EQ(Get("a"), "a");
|
||||
ASSERT_EQ(Get("b"), "b");
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(6));
|
||||
|
||||
{
|
||||
// A standalone range deletion with its exclusive end matching the range end
|
||||
// of file doesn't fully delete it.
|
||||
files.clear();
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file2 = sst_files_dir_ + "file2.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file2));
|
||||
ASSERT_OK(sst_file_writer.DeleteRange("a", "b"));
|
||||
ExternalSstFileInfo file2_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file2_info));
|
||||
files.push_back(std::move(file2));
|
||||
}
|
||||
|
||||
bool compaction_iter_input_checked = false;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::MakeInputIterator:NewCompactionMergingIterator",
|
||||
[&](void* arg) {
|
||||
size_t* num_input_files = static_cast<size_t*>(arg);
|
||||
// Standalone range deletion file for ["a", "b") + file with ["a", "b"].
|
||||
EXPECT_EQ(2, *num_input_files);
|
||||
compaction_iter_input_checked = true;
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(db_->IngestExternalFile(files, ifo));
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(4));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(5));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(6));
|
||||
ASSERT_TRUE(compaction_iter_input_checked);
|
||||
|
||||
ASSERT_EQ(Get("a"), "NOT_FOUND");
|
||||
ASSERT_EQ(Get("b"), "b");
|
||||
|
||||
VerifyInputFilesInternalStatsForOutputLevel(
|
||||
/*output_level*/ 6,
|
||||
kCompactionNumInputFiles - kCompactionNumInputFilesAtOutputLevel,
|
||||
kCompactionNumInputFilesAtOutputLevel,
|
||||
kCompactionNumFilteredInputFiles -
|
||||
kCompactionNumFilteredInputFilesAtOutputLevel,
|
||||
kCompactionNumFilteredInputFilesAtOutputLevel,
|
||||
/*bytes_skipped_non_output_levels*/ 0,
|
||||
/*bytes_skipped_output_level*/ 0);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileBasicTest, IngestFileAfterDBPut) {
|
||||
@@ -2045,7 +2649,7 @@ TEST_F(ExternalSSTFileBasicTest, FailIfNotBottommostLevel) {
|
||||
ifo.fail_if_not_bottommost_level = true;
|
||||
ifo.snapshot_consistency = true;
|
||||
const Status s = db_->IngestExternalFile({file_path}, ifo);
|
||||
ASSERT_TRUE(s.IsTryAgain());
|
||||
ASSERT_TRUE(s.ok());
|
||||
}
|
||||
|
||||
// Test level compaction
|
||||
@@ -2217,6 +2821,57 @@ TEST_F(ExternalSSTFileBasicTest, StableSnapshotWhileLoggingToManifest) {
|
||||
ASSERT_EQ(db_->GetLatestSequenceNumber(), ingested_file_seqno + 1);
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileBasicTest, ConcurrentIngestionAndDropColumnFamily) {
|
||||
int kNumCFs = 10;
|
||||
Options options = CurrentOptions();
|
||||
CreateColumnFamilies({"cf_0", "cf_1", "cf_2", "cf_3", "cf_4", "cf_5", "cf_6",
|
||||
"cf_7", "cf_8", "cf_9"},
|
||||
options);
|
||||
|
||||
IngestExternalFileArg ingest_arg;
|
||||
IngestExternalFileOptions ifo;
|
||||
std::string external_file = sst_files_dir_ + "/file_to_ingest.sst";
|
||||
SstFileWriter sst_file_writer{EnvOptions(), CurrentOptions()};
|
||||
ASSERT_OK(sst_file_writer.Open(external_file));
|
||||
ASSERT_OK(sst_file_writer.Put("key", "value"));
|
||||
ASSERT_OK(sst_file_writer.Finish());
|
||||
ifo.move_files = false;
|
||||
ingest_arg.external_files = {external_file};
|
||||
ingest_arg.options = ifo;
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
threads.reserve(2 * kNumCFs);
|
||||
std::atomic<int> success_ingestion_count = 0;
|
||||
std::atomic<int> failed_ingestion_count = 0;
|
||||
for (int i = 0; i < kNumCFs; i++) {
|
||||
threads.emplace_back(
|
||||
[this, i]() { ASSERT_OK(db_->DropColumnFamily(handles_[i])); });
|
||||
threads.emplace_back([this, i, ingest_arg, &success_ingestion_count,
|
||||
&failed_ingestion_count]() {
|
||||
IngestExternalFileArg arg_copy = ingest_arg;
|
||||
arg_copy.column_family = handles_[i];
|
||||
Status s = db_->IngestExternalFiles({arg_copy});
|
||||
ReadOptions ropts;
|
||||
std::string value;
|
||||
if (s.ok()) {
|
||||
ASSERT_OK(db_->Get(ropts, handles_[i], "key", &value));
|
||||
ASSERT_EQ("value", value);
|
||||
success_ingestion_count.fetch_add(1);
|
||||
} else {
|
||||
ASSERT_TRUE(db_->Get(ropts, handles_[i], "key", &value).IsNotFound());
|
||||
failed_ingestion_count.fetch_add(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
ASSERT_EQ(kNumCFs, success_ingestion_count + failed_ingestion_count);
|
||||
Close();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(ExternalSSTFileBasicTest, ExternalSSTFileBasicTest,
|
||||
testing::Values(std::make_tuple(true, true),
|
||||
std::make_tuple(true, false),
|
||||
|
||||
@@ -67,7 +67,6 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
files_to_ingest_.emplace_back(std::move(file_to_ingest));
|
||||
}
|
||||
|
||||
const Comparator* ucmp = cfd_->internal_comparator().user_comparator();
|
||||
auto num_files = files_to_ingest_.size();
|
||||
if (num_files == 0) {
|
||||
return Status::InvalidArgument("The list of files is empty");
|
||||
@@ -78,16 +77,12 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
sorted_files.push_back(&files_to_ingest_[i]);
|
||||
}
|
||||
|
||||
std::sort(
|
||||
sorted_files.begin(), sorted_files.end(),
|
||||
[&ucmp](const IngestedFileInfo* info1, const IngestedFileInfo* info2) {
|
||||
return sstableKeyCompare(ucmp, info1->smallest_internal_key,
|
||||
info2->smallest_internal_key) < 0;
|
||||
});
|
||||
std::sort(sorted_files.begin(), sorted_files.end(), file_range_checker_);
|
||||
|
||||
for (size_t i = 0; i + 1 < num_files; i++) {
|
||||
if (sstableKeyCompare(ucmp, sorted_files[i]->largest_internal_key,
|
||||
sorted_files[i + 1]->smallest_internal_key) >= 0) {
|
||||
if (file_range_checker_.OverlapsWithPrev(sorted_files[i],
|
||||
sorted_files[i + 1],
|
||||
/* ranges_sorted= */ true)) {
|
||||
files_overlap_ = true;
|
||||
break;
|
||||
}
|
||||
@@ -100,7 +95,15 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
"behind mode.");
|
||||
}
|
||||
|
||||
if (ucmp->timestamp_size() > 0 && files_overlap_) {
|
||||
// Overlapping files need at least two different sequence numbers. If settings
|
||||
// disables global seqno, ingestion will fail anyway, so fail fast in prepare.
|
||||
if (!ingestion_options_.allow_global_seqno && files_overlap_) {
|
||||
return Status::InvalidArgument(
|
||||
"Global seqno is required, but disabled (because external files key "
|
||||
"range overlaps).");
|
||||
}
|
||||
|
||||
if (ucmp_->timestamp_size() > 0 && files_overlap_) {
|
||||
return Status::NotSupported(
|
||||
"Files with overlapping ranges cannot be ingested to column "
|
||||
"family with user-defined timestamp enabled.");
|
||||
@@ -336,9 +339,35 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
}
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
DivideInputFilesIntoBatches();
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void ExternalSstFileIngestionJob::DivideInputFilesIntoBatches() {
|
||||
if (!files_overlap_) {
|
||||
// No overlap, treat as one batch without the need of tracking overall batch
|
||||
// range.
|
||||
file_batches_to_ingest_.emplace_back(/* _track_batch_range= */ false);
|
||||
for (auto& file : files_to_ingest_) {
|
||||
file_batches_to_ingest_.back().AddFile(&file, file_range_checker_);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
file_batches_to_ingest_.emplace_back(/* _track_batch_range= */ true);
|
||||
for (auto& file : files_to_ingest_) {
|
||||
if (file_range_checker_.OverlapsWithPrev(&file_batches_to_ingest_.back(),
|
||||
&file,
|
||||
/* ranges_sorted= */ false)) {
|
||||
file_batches_to_ingest_.emplace_back(/* _track_batch_range= */ true);
|
||||
}
|
||||
file_batches_to_ingest_.back().AddFile(&file, file_range_checker_);
|
||||
}
|
||||
}
|
||||
|
||||
Status ExternalSstFileIngestionJob::NeedsFlush(bool* flush_needed,
|
||||
SuperVersion* super_version) {
|
||||
size_t n = files_to_ingest_.size();
|
||||
@@ -353,9 +382,7 @@ Status ExternalSstFileIngestionJob::NeedsFlush(bool* flush_needed,
|
||||
if (!ingestion_options_.allow_blocking_flush) {
|
||||
status = Status::InvalidArgument("External file requires flush");
|
||||
}
|
||||
auto ucmp = cfd_->user_comparator();
|
||||
assert(ucmp);
|
||||
if (ucmp->timestamp_size() > 0) {
|
||||
if (ucmp_->timestamp_size() > 0) {
|
||||
status = Status::InvalidArgument(
|
||||
"Column family enables user-defined timestamps, please make "
|
||||
"sure the key range (without timestamp) of external file does not "
|
||||
@@ -368,8 +395,16 @@ Status ExternalSstFileIngestionJob::NeedsFlush(bool* flush_needed,
|
||||
// REQUIRES: we have become the only writer by entering both write_thread_ and
|
||||
// nonmem_write_thread_
|
||||
Status ExternalSstFileIngestionJob::Run() {
|
||||
Status status;
|
||||
SuperVersion* super_version = cfd_->GetSuperVersion();
|
||||
// If column family is flushed after Prepare and before Run, we should have a
|
||||
// specific state of Memtables. The mutable Memtable should be empty, and the
|
||||
// immutable Memtable list should be empty.
|
||||
if (flushed_before_run_ && (super_version->imm->NumNotFlushed() != 0 ||
|
||||
!super_version->mem->IsEmpty())) {
|
||||
return Status::TryAgain(
|
||||
"Inconsistent memtable state detected when flushed before run.");
|
||||
}
|
||||
Status status;
|
||||
#ifndef NDEBUG
|
||||
// We should never run the job with a memtable that is overlapping
|
||||
// with the files we are ingesting
|
||||
@@ -397,14 +432,39 @@ Status ExternalSstFileIngestionJob::Run() {
|
||||
edit_.SetColumnFamily(cfd_->GetID());
|
||||
// The levels that the files will be ingested into
|
||||
|
||||
for (IngestedFileInfo& f : files_to_ingest_) {
|
||||
std::optional<int> prev_batch_uppermost_level;
|
||||
for (auto& batch : file_batches_to_ingest_) {
|
||||
int batch_uppermost_level = 0;
|
||||
status = AssignLevelsForOneBatch(batch, super_version, force_global_seqno,
|
||||
&last_seqno, &batch_uppermost_level,
|
||||
prev_batch_uppermost_level);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
prev_batch_uppermost_level = batch_uppermost_level;
|
||||
}
|
||||
|
||||
CreateEquivalentFileIngestingCompactions();
|
||||
return status;
|
||||
}
|
||||
|
||||
Status ExternalSstFileIngestionJob::AssignLevelsForOneBatch(
|
||||
FileBatchInfo& batch, SuperVersion* super_version, bool force_global_seqno,
|
||||
SequenceNumber* last_seqno, int* batch_uppermost_level,
|
||||
std::optional<int> prev_batch_uppermost_level) {
|
||||
Status status;
|
||||
assert(batch_uppermost_level);
|
||||
*batch_uppermost_level = std::numeric_limits<int>::max();
|
||||
for (IngestedFileInfo* file : batch.files) {
|
||||
assert(file);
|
||||
SequenceNumber assigned_seqno = 0;
|
||||
if (ingestion_options_.ingest_behind) {
|
||||
status = CheckLevelForIngestedBehindFile(&f);
|
||||
status = CheckLevelForIngestedBehindFile(file);
|
||||
} else {
|
||||
status = AssignLevelAndSeqnoForIngestedFile(
|
||||
super_version, force_global_seqno, cfd_->ioptions()->compaction_style,
|
||||
last_seqno, &f, &assigned_seqno);
|
||||
*last_seqno, file, &assigned_seqno, prev_batch_uppermost_level);
|
||||
}
|
||||
|
||||
// Modify the smallest/largest internal key to include the sequence number
|
||||
@@ -413,38 +473,38 @@ Status ExternalSstFileIngestionJob::Run() {
|
||||
// exclusive endpoint.
|
||||
ParsedInternalKey smallest_parsed, largest_parsed;
|
||||
if (status.ok()) {
|
||||
status = ParseInternalKey(*f.smallest_internal_key.rep(),
|
||||
status = ParseInternalKey(*(file->smallest_internal_key.rep()),
|
||||
&smallest_parsed, false /* log_err_key */);
|
||||
}
|
||||
if (status.ok()) {
|
||||
status = ParseInternalKey(*f.largest_internal_key.rep(), &largest_parsed,
|
||||
false /* log_err_key */);
|
||||
status = ParseInternalKey(*(file->largest_internal_key.rep()),
|
||||
&largest_parsed, false /* log_err_key */);
|
||||
}
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
if (smallest_parsed.sequence == 0 && assigned_seqno != 0) {
|
||||
UpdateInternalKey(f.smallest_internal_key.rep(), assigned_seqno,
|
||||
UpdateInternalKey(file->smallest_internal_key.rep(), assigned_seqno,
|
||||
smallest_parsed.type);
|
||||
}
|
||||
if (largest_parsed.sequence == 0 && assigned_seqno != 0) {
|
||||
UpdateInternalKey(f.largest_internal_key.rep(), assigned_seqno,
|
||||
UpdateInternalKey(file->largest_internal_key.rep(), assigned_seqno,
|
||||
largest_parsed.type);
|
||||
}
|
||||
|
||||
status = AssignGlobalSeqnoForIngestedFile(&f, assigned_seqno);
|
||||
status = AssignGlobalSeqnoForIngestedFile(file, assigned_seqno);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("ExternalSstFileIngestionJob::Run",
|
||||
&assigned_seqno);
|
||||
assert(assigned_seqno == 0 || assigned_seqno == last_seqno + 1);
|
||||
if (assigned_seqno > last_seqno) {
|
||||
last_seqno = assigned_seqno;
|
||||
assert(assigned_seqno == 0 || assigned_seqno == *last_seqno + 1);
|
||||
if (assigned_seqno > *last_seqno) {
|
||||
*last_seqno = assigned_seqno;
|
||||
++consumed_seqno_count_;
|
||||
}
|
||||
|
||||
status = GenerateChecksumForIngestedFile(&f);
|
||||
status = GenerateChecksumForIngestedFile(file);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -459,31 +519,40 @@ Status ExternalSstFileIngestionJob::Run() {
|
||||
static_cast<uint64_t>(temp_current_time);
|
||||
}
|
||||
uint64_t tail_size = 0;
|
||||
bool contain_no_data_blocks = f.table_properties.num_entries > 0 &&
|
||||
(f.table_properties.num_entries ==
|
||||
f.table_properties.num_range_deletions);
|
||||
if (f.table_properties.tail_start_offset > 0 || contain_no_data_blocks) {
|
||||
uint64_t file_size = f.fd.GetFileSize();
|
||||
assert(f.table_properties.tail_start_offset <= file_size);
|
||||
tail_size = file_size - f.table_properties.tail_start_offset;
|
||||
bool contain_no_data_blocks = file->table_properties.num_entries > 0 &&
|
||||
(file->table_properties.num_entries ==
|
||||
file->table_properties.num_range_deletions);
|
||||
if (file->table_properties.tail_start_offset > 0 ||
|
||||
contain_no_data_blocks) {
|
||||
uint64_t file_size = file->fd.GetFileSize();
|
||||
assert(file->table_properties.tail_start_offset <= file_size);
|
||||
tail_size = file_size - file->table_properties.tail_start_offset;
|
||||
}
|
||||
|
||||
bool marked_for_compaction =
|
||||
file->table_properties.num_range_deletions == 1 &&
|
||||
(file->table_properties.num_entries ==
|
||||
file->table_properties.num_range_deletions);
|
||||
FileMetaData f_metadata(
|
||||
f.fd.GetNumber(), f.fd.GetPathId(), f.fd.GetFileSize(),
|
||||
f.smallest_internal_key, f.largest_internal_key, f.assigned_seqno,
|
||||
f.assigned_seqno, false, f.file_temperature, kInvalidBlobFileNumber,
|
||||
oldest_ancester_time, current_time,
|
||||
file->fd.GetNumber(), file->fd.GetPathId(), file->fd.GetFileSize(),
|
||||
file->smallest_internal_key, file->largest_internal_key,
|
||||
file->assigned_seqno, file->assigned_seqno, false,
|
||||
file->file_temperature, kInvalidBlobFileNumber, oldest_ancester_time,
|
||||
current_time,
|
||||
ingestion_options_.ingest_behind
|
||||
? kReservedEpochNumberForFileIngestedBehind
|
||||
: cfd_->NewEpochNumber(),
|
||||
f.file_checksum, f.file_checksum_func_name, f.unique_id, 0, tail_size,
|
||||
f.user_defined_timestamps_persisted);
|
||||
f_metadata.temperature = f.file_temperature;
|
||||
edit_.AddFile(f.picked_level, f_metadata);
|
||||
file->file_checksum, file->file_checksum_func_name, file->unique_id, 0,
|
||||
tail_size, file->user_defined_timestamps_persisted);
|
||||
f_metadata.temperature = file->file_temperature;
|
||||
f_metadata.marked_for_compaction = marked_for_compaction;
|
||||
edit_.AddFile(file->picked_level, f_metadata);
|
||||
|
||||
*batch_uppermost_level =
|
||||
std::min(*batch_uppermost_level, file->picked_level);
|
||||
}
|
||||
|
||||
CreateEquivalentFileIngestingCompactions();
|
||||
return status;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void ExternalSstFileIngestionJob::CreateEquivalentFileIngestingCompactions() {
|
||||
@@ -519,20 +588,17 @@ void ExternalSstFileIngestionJob::CreateEquivalentFileIngestingCompactions() {
|
||||
file_ingesting_compactions_.push_back(new Compaction(
|
||||
cfd_->current()->storage_info(), *cfd_->ioptions(), mutable_cf_options,
|
||||
mutable_db_options_, {input}, output_level,
|
||||
MaxFileSizeForLevel(
|
||||
mutable_cf_options, output_level,
|
||||
cfd_->ioptions()->compaction_style) /* output file size
|
||||
limit,
|
||||
* not applicable
|
||||
*/
|
||||
,
|
||||
/* output file size limit not applicable */
|
||||
MaxFileSizeForLevel(mutable_cf_options, output_level,
|
||||
cfd_->ioptions()->compaction_style),
|
||||
LLONG_MAX /* max compaction bytes, not applicable */,
|
||||
0 /* output path ID, not applicable */, mutable_cf_options.compression,
|
||||
mutable_cf_options.compression_opts,
|
||||
mutable_cf_options.default_write_temperature,
|
||||
0 /* max_subcompaction, not applicable */,
|
||||
{} /* grandparents, not applicable */, false /* is manual */,
|
||||
"" /* trim_ts */, -1 /* score, not applicable */,
|
||||
{} /* grandparents, not applicable */,
|
||||
std::nullopt /* earliest_snapshot */, nullptr /* snapshot_checker */,
|
||||
false /* is manual */, "" /* trim_ts */, -1 /* score, not applicable */,
|
||||
false /* is deletion compaction, not applicable */,
|
||||
files_overlap_ /* l0_files_might_overlap, not applicable */,
|
||||
CompactionReason::kExternalSstIngestion));
|
||||
@@ -679,7 +745,10 @@ Status ExternalSstFileIngestionJob::ResetTableReader(
|
||||
new RandomAccessFileReader(std::move(sst_file), external_file,
|
||||
nullptr /*Env*/, io_tracer_));
|
||||
table_reader->reset();
|
||||
status = cfd_->ioptions()->table_factory->NewTableReader(
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = ingestion_options_.fill_cache;
|
||||
status = sv->mutable_cf_options.table_factory->NewTableReader(
|
||||
ro,
|
||||
TableReaderOptions(
|
||||
*cfd_->ioptions(), sv->mutable_cf_options.prefix_extractor,
|
||||
env_options_, cfd_->internal_comparator(),
|
||||
@@ -691,7 +760,9 @@ Status ExternalSstFileIngestionJob::ResetTableReader(
|
||||
/*cur_file_num*/ new_file_number,
|
||||
/* unique_id */ {}, /* largest_seqno */ 0,
|
||||
/* tail_size */ 0, user_defined_timestamps_persisted),
|
||||
std::move(sst_file_reader), file_to_ingest->file_size, table_reader);
|
||||
std::move(sst_file_reader), file_to_ingest->file_size, table_reader,
|
||||
// No need to prefetch index/filter if caching is not needed.
|
||||
/*prefetch_index_and_filter_in_cache=*/ingestion_options_.fill_cache);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -707,6 +778,7 @@ Status ExternalSstFileIngestionJob::SanityCheckTableProperties(
|
||||
// Get table version
|
||||
auto version_iter = uprops.find(ExternalSstFilePropertyNames::kVersion);
|
||||
if (version_iter == uprops.end()) {
|
||||
assert(!SstFileWriter::CreatedBySstFileWriter(*props));
|
||||
if (!ingestion_options_.allow_db_generated_files) {
|
||||
return Status::Corruption("External file version not found");
|
||||
} else {
|
||||
@@ -715,6 +787,7 @@ Status ExternalSstFileIngestionJob::SanityCheckTableProperties(
|
||||
file_to_ingest->version = 0;
|
||||
}
|
||||
} else {
|
||||
assert(SstFileWriter::CreatedBySstFileWriter(*props));
|
||||
file_to_ingest->version = DecodeFixed32(version_iter->second.c_str());
|
||||
}
|
||||
|
||||
@@ -787,9 +860,7 @@ Status ExternalSstFileIngestionJob::SanityCheckTableProperties(
|
||||
// `TableReader` is initialized with `user_defined_timestamps_persisted` flag
|
||||
// to be true. If its value changed to false after this sanity check, we
|
||||
// need to reset the `TableReader`.
|
||||
auto ucmp = cfd_->user_comparator();
|
||||
assert(ucmp);
|
||||
if (ucmp->timestamp_size() > 0 &&
|
||||
if (ucmp_->timestamp_size() > 0 &&
|
||||
!file_to_ingest->user_defined_timestamps_persisted) {
|
||||
s = ResetTableReader(external_file, new_file_number,
|
||||
file_to_ingest->user_defined_timestamps_persisted, sv,
|
||||
@@ -839,6 +910,7 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
ReadOptions ro;
|
||||
ro.readahead_size = ingestion_options_.verify_checksums_readahead_size;
|
||||
ro.fill_cache = ingestion_options_.fill_cache;
|
||||
status = table_reader->VerifyChecksum(
|
||||
ro, TableReaderCaller::kExternalSSTIngestion);
|
||||
if (!status.ok()) {
|
||||
@@ -849,16 +921,12 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
ParsedInternalKey key;
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = ingestion_options_.fill_cache;
|
||||
std::unique_ptr<InternalIterator> iter(table_reader->NewIterator(
|
||||
ro, sv->mutable_cf_options.prefix_extractor.get(), /*arena=*/nullptr,
|
||||
/*skip_filters=*/false, TableReaderCaller::kExternalSSTIngestion));
|
||||
|
||||
// Get first (smallest) and last (largest) key from file.
|
||||
file_to_ingest->smallest_internal_key =
|
||||
InternalKey("", 0, ValueType::kTypeValue);
|
||||
file_to_ingest->largest_internal_key =
|
||||
InternalKey("", 0, ValueType::kTypeValue);
|
||||
bool bounds_set = false;
|
||||
bool allow_data_in_errors = db_options_.allow_data_in_errors;
|
||||
iter->SeekToFirst();
|
||||
if (iter->Valid()) {
|
||||
@@ -874,7 +942,8 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
file_to_ingest->smallest_internal_key.SetFrom(key);
|
||||
|
||||
Slice largest;
|
||||
if (strcmp(cfd_->ioptions()->table_factory->Name(), "PlainTable") == 0) {
|
||||
if (strcmp(sv->mutable_cf_options.table_factory->Name(), "PlainTable") ==
|
||||
0) {
|
||||
// PlainTable iterator does not support SeekToLast().
|
||||
largest = iter->key();
|
||||
for (; iter->Valid(); iter->Next()) {
|
||||
@@ -908,8 +977,6 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
return Status::Corruption("External file has non zero sequence number");
|
||||
}
|
||||
file_to_ingest->largest_internal_key.SetFrom(key);
|
||||
|
||||
bounds_set = true;
|
||||
} else if (!iter->status().ok()) {
|
||||
return iter->status();
|
||||
}
|
||||
@@ -946,7 +1013,6 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
table_reader->NewRangeTombstoneIterator(ro));
|
||||
// We may need to adjust these key bounds, depending on whether any range
|
||||
// deletion tombstones extend past them.
|
||||
const Comparator* ucmp = cfd_->user_comparator();
|
||||
if (range_del_iter != nullptr) {
|
||||
for (range_del_iter->SeekToFirst(); range_del_iter->Valid();
|
||||
range_del_iter->Next()) {
|
||||
@@ -962,24 +1028,13 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
"number.");
|
||||
}
|
||||
RangeTombstone tombstone(key, range_del_iter->value());
|
||||
|
||||
InternalKey start_key = tombstone.SerializeKey();
|
||||
if (!bounds_set ||
|
||||
sstableKeyCompare(ucmp, start_key,
|
||||
file_to_ingest->smallest_internal_key) < 0) {
|
||||
file_to_ingest->smallest_internal_key = start_key;
|
||||
}
|
||||
InternalKey end_key = tombstone.SerializeEndKey();
|
||||
if (!bounds_set ||
|
||||
sstableKeyCompare(ucmp, end_key,
|
||||
file_to_ingest->largest_internal_key) > 0) {
|
||||
file_to_ingest->largest_internal_key = end_key;
|
||||
}
|
||||
bounds_set = true;
|
||||
file_range_checker_.MaybeUpdateRange(tombstone.SerializeKey(),
|
||||
tombstone.SerializeEndKey(),
|
||||
file_to_ingest);
|
||||
}
|
||||
}
|
||||
|
||||
const size_t ts_sz = ucmp->timestamp_size();
|
||||
const size_t ts_sz = ucmp_->timestamp_size();
|
||||
Slice smallest = file_to_ingest->smallest_internal_key.user_key();
|
||||
Slice largest = file_to_ingest->largest_internal_key.user_key();
|
||||
if (ts_sz > 0) {
|
||||
@@ -1008,16 +1063,19 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
|
||||
SuperVersion* sv, bool force_global_seqno, CompactionStyle compaction_style,
|
||||
SequenceNumber last_seqno, IngestedFileInfo* file_to_ingest,
|
||||
SequenceNumber* assigned_seqno) {
|
||||
SequenceNumber* assigned_seqno,
|
||||
std::optional<int> prev_batch_uppermost_level) {
|
||||
Status status;
|
||||
*assigned_seqno = 0;
|
||||
auto ucmp = cfd_->user_comparator();
|
||||
const size_t ts_sz = ucmp->timestamp_size();
|
||||
const size_t ts_sz = ucmp_->timestamp_size();
|
||||
assert(!prev_batch_uppermost_level.has_value() ||
|
||||
prev_batch_uppermost_level.value() < cfd_->NumberLevels());
|
||||
bool must_assign_to_l0 = prev_batch_uppermost_level.has_value() &&
|
||||
prev_batch_uppermost_level.value() == 0;
|
||||
if (force_global_seqno || files_overlap_ ||
|
||||
compaction_style == kCompactionStyleFIFO) {
|
||||
compaction_style == kCompactionStyleFIFO || must_assign_to_l0) {
|
||||
*assigned_seqno = last_seqno + 1;
|
||||
// If files overlap, we have to ingest them at level 0.
|
||||
if (files_overlap_ || compaction_style == kCompactionStyleFIFO) {
|
||||
if (compaction_style == kCompactionStyleFIFO || must_assign_to_l0) {
|
||||
assert(ts_sz == 0);
|
||||
file_to_ingest->picked_level = 0;
|
||||
if (ingestion_options_.fail_if_not_bottommost_level &&
|
||||
@@ -1034,11 +1092,16 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
|
||||
Arena arena;
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = ingestion_options_.fill_cache;
|
||||
ro.total_order_seek = true;
|
||||
int target_level = 0;
|
||||
auto* vstorage = cfd_->current()->storage_info();
|
||||
assert(!must_assign_to_l0);
|
||||
int exclusive_end_level = prev_batch_uppermost_level.has_value()
|
||||
? prev_batch_uppermost_level.value()
|
||||
: cfd_->NumberLevels();
|
||||
|
||||
for (int lvl = 0; lvl < cfd_->NumberLevels(); lvl++) {
|
||||
for (int lvl = 0; lvl < exclusive_end_level; lvl++) {
|
||||
if (lvl > 0 && lvl < vstorage->base_level()) {
|
||||
continue;
|
||||
}
|
||||
@@ -1065,8 +1128,6 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
|
||||
overlap_with_db = true;
|
||||
break;
|
||||
}
|
||||
} else if (compaction_style == kCompactionStyleUniversal) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We don't overlap with any keys in this level, but we still need to check
|
||||
|
||||
@@ -25,13 +25,74 @@ namespace ROCKSDB_NAMESPACE {
|
||||
class Directories;
|
||||
class SystemClock;
|
||||
|
||||
struct IngestedFileInfo {
|
||||
struct KeyRangeInfo {
|
||||
// Smallest internal key in an external file or for a batch of external files.
|
||||
InternalKey smallest_internal_key;
|
||||
// Largest internal key in an external file or for a batch of external files.
|
||||
InternalKey largest_internal_key;
|
||||
|
||||
bool empty() const {
|
||||
return smallest_internal_key.size() == 0 &&
|
||||
largest_internal_key.size() == 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Helper class to apply SST file key range checks to the external files.
|
||||
class ExternalFileRangeChecker {
|
||||
public:
|
||||
explicit ExternalFileRangeChecker(const Comparator* ucmp) : ucmp_(ucmp) {}
|
||||
|
||||
// Operator used for sorting ranges.
|
||||
bool operator()(const KeyRangeInfo* prev_range,
|
||||
const KeyRangeInfo* range) const {
|
||||
assert(prev_range);
|
||||
assert(range);
|
||||
return sstableKeyCompare(ucmp_, prev_range->smallest_internal_key,
|
||||
range->smallest_internal_key) < 0;
|
||||
}
|
||||
|
||||
// Check whether `range` overlaps with `prev_range`. `ranges_sorted` can be
|
||||
// set to true when the inputs are already sorted based on the sorting logic
|
||||
// provided by this checker's operator(), which can help simplify the check.
|
||||
bool OverlapsWithPrev(const KeyRangeInfo* prev_range,
|
||||
const KeyRangeInfo* range,
|
||||
bool ranges_sorted = false) const {
|
||||
assert(prev_range);
|
||||
assert(range);
|
||||
if (prev_range->empty() || range->empty()) {
|
||||
return false;
|
||||
}
|
||||
if (ranges_sorted) {
|
||||
return sstableKeyCompare(ucmp_, prev_range->largest_internal_key,
|
||||
range->smallest_internal_key) >= 0;
|
||||
}
|
||||
|
||||
return sstableKeyCompare(ucmp_, prev_range->largest_internal_key,
|
||||
range->smallest_internal_key) >= 0 &&
|
||||
sstableKeyCompare(ucmp_, prev_range->smallest_internal_key,
|
||||
range->largest_internal_key) <= 0;
|
||||
}
|
||||
|
||||
void MaybeUpdateRange(const InternalKey& start_key,
|
||||
const InternalKey& end_key, KeyRangeInfo* range) const {
|
||||
assert(range);
|
||||
if (range->smallest_internal_key.size() == 0 ||
|
||||
sstableKeyCompare(ucmp_, start_key, range->smallest_internal_key) < 0) {
|
||||
range->smallest_internal_key = start_key;
|
||||
}
|
||||
if (range->largest_internal_key.size() == 0 ||
|
||||
sstableKeyCompare(ucmp_, end_key, range->largest_internal_key) > 0) {
|
||||
range->largest_internal_key = end_key;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const Comparator* ucmp_;
|
||||
};
|
||||
|
||||
struct IngestedFileInfo : public KeyRangeInfo {
|
||||
// External file path
|
||||
std::string external_file_path;
|
||||
// Smallest internal key in external file
|
||||
InternalKey smallest_internal_key;
|
||||
// Largest internal key in external file
|
||||
InternalKey largest_internal_key;
|
||||
// NOTE: use below two fields for all `*Overlap*` types of checks instead of
|
||||
// smallest_internal_key.user_key() and largest_internal_key.user_key().
|
||||
// The smallest / largest user key contained in the file for key range checks.
|
||||
@@ -94,6 +155,30 @@ struct IngestedFileInfo {
|
||||
bool user_defined_timestamps_persisted = true;
|
||||
};
|
||||
|
||||
// A batch of files.
|
||||
struct FileBatchInfo : public KeyRangeInfo {
|
||||
autovector<IngestedFileInfo*> files;
|
||||
// When true, `smallest_internal_key` and `largest_internal_key` will be
|
||||
// tracked and updated as new file get added via `AddFile`. When false, we
|
||||
// bypass this tracking. This is used when the all input external files
|
||||
// are already checked and not overlapping, and they just need to be added
|
||||
// into one default batch.
|
||||
bool track_batch_range;
|
||||
|
||||
void AddFile(IngestedFileInfo* file,
|
||||
const ExternalFileRangeChecker& key_range_checker) {
|
||||
assert(file);
|
||||
files.push_back(file);
|
||||
if (track_batch_range) {
|
||||
key_range_checker.MaybeUpdateRange(file->smallest_internal_key,
|
||||
file->largest_internal_key, this);
|
||||
}
|
||||
}
|
||||
|
||||
explicit FileBatchInfo(bool _track_batch_range)
|
||||
: track_batch_range(_track_batch_range) {}
|
||||
};
|
||||
|
||||
class ExternalSstFileIngestionJob {
|
||||
public:
|
||||
ExternalSstFileIngestionJob(
|
||||
@@ -108,6 +193,8 @@ class ExternalSstFileIngestionJob {
|
||||
fs_(db_options.fs, io_tracer),
|
||||
versions_(versions),
|
||||
cfd_(cfd),
|
||||
ucmp_(cfd ? cfd->user_comparator() : nullptr),
|
||||
file_range_checker_(ucmp_),
|
||||
db_options_(db_options),
|
||||
mutable_db_options_(mutable_db_options),
|
||||
env_options_(env_options),
|
||||
@@ -119,10 +206,14 @@ class ExternalSstFileIngestionJob {
|
||||
consumed_seqno_count_(0),
|
||||
io_tracer_(io_tracer) {
|
||||
assert(directories != nullptr);
|
||||
assert(cfd_);
|
||||
assert(ucmp_);
|
||||
}
|
||||
|
||||
~ExternalSstFileIngestionJob() { UnregisterRange(); }
|
||||
|
||||
ColumnFamilyData* GetColumnFamilyData() const { return cfd_; }
|
||||
|
||||
// Prepare the job by copying external files into the DB.
|
||||
Status Prepare(const std::vector<std::string>& external_files_paths,
|
||||
const std::vector<std::string>& files_checksums,
|
||||
@@ -140,6 +231,8 @@ class ExternalSstFileIngestionJob {
|
||||
// Thread-safe
|
||||
Status NeedsFlush(bool* flush_needed, SuperVersion* super_version);
|
||||
|
||||
void SetFlushedBeforeRun() { flushed_before_run_ = true; }
|
||||
|
||||
// Will execute the ingestion job and prepare edit() to be applied.
|
||||
// REQUIRES: Mutex held
|
||||
Status Run();
|
||||
@@ -194,15 +287,38 @@ class ExternalSstFileIngestionJob {
|
||||
IngestedFileInfo* file_to_ingest,
|
||||
SuperVersion* sv);
|
||||
|
||||
// If the input files' key range overlaps themselves, this function divides
|
||||
// them in the user specified order into multiple batches. Where the files
|
||||
// within a batch do not overlap with each other, but key range could overlap
|
||||
// between batches.
|
||||
// If the input files' key range don't overlap themselves, they always just
|
||||
// make one batch.
|
||||
void DivideInputFilesIntoBatches();
|
||||
|
||||
// Assign level for the files in one batch. The files within one batch are not
|
||||
// overlapping, and we assign level to each file one after another.
|
||||
// If `prev_batch_uppermost_level` is specified, all files in this batch will
|
||||
// be assigned to levels that are higher than `prev_batch_uppermost_level`.
|
||||
// The uppermost level used by this batch of files is tracked too, so that it
|
||||
// can be used by the next batch.
|
||||
// REQUIRES: Mutex held
|
||||
Status AssignLevelsForOneBatch(FileBatchInfo& batch,
|
||||
SuperVersion* super_version,
|
||||
bool force_global_seqno,
|
||||
SequenceNumber* last_seqno,
|
||||
int* batch_uppermost_level,
|
||||
std::optional<int> prev_batch_uppermost_level);
|
||||
|
||||
// Assign `file_to_ingest` the appropriate sequence number and the lowest
|
||||
// possible level that it can be ingested to according to compaction_style.
|
||||
// If `prev_batch_uppermost_level` is specified, the file will only be
|
||||
// assigned to levels tha are higher than `prev_batch_uppermost_level`.
|
||||
// REQUIRES: Mutex held
|
||||
Status AssignLevelAndSeqnoForIngestedFile(SuperVersion* sv,
|
||||
bool force_global_seqno,
|
||||
CompactionStyle compaction_style,
|
||||
SequenceNumber last_seqno,
|
||||
IngestedFileInfo* file_to_ingest,
|
||||
SequenceNumber* assigned_seqno);
|
||||
Status AssignLevelAndSeqnoForIngestedFile(
|
||||
SuperVersion* sv, bool force_global_seqno,
|
||||
CompactionStyle compaction_style, SequenceNumber last_seqno,
|
||||
IngestedFileInfo* file_to_ingest, SequenceNumber* assigned_seqno,
|
||||
std::optional<int> prev_batch_uppermost_level);
|
||||
|
||||
// File that we want to ingest behind always goes to the lowest level;
|
||||
// we just check that it fits in the level, that DB allows ingest_behind,
|
||||
@@ -237,11 +353,14 @@ class ExternalSstFileIngestionJob {
|
||||
FileSystemPtr fs_;
|
||||
VersionSet* versions_;
|
||||
ColumnFamilyData* cfd_;
|
||||
const Comparator* ucmp_;
|
||||
ExternalFileRangeChecker file_range_checker_;
|
||||
const ImmutableDBOptions& db_options_;
|
||||
const MutableDBOptions& mutable_db_options_;
|
||||
const EnvOptions& env_options_;
|
||||
SnapshotList* db_snapshots_;
|
||||
autovector<IngestedFileInfo> files_to_ingest_;
|
||||
std::vector<FileBatchInfo> file_batches_to_ingest_;
|
||||
const IngestExternalFileOptions& ingestion_options_;
|
||||
Directories* directories_;
|
||||
EventLogger* event_logger_;
|
||||
@@ -256,6 +375,10 @@ class ExternalSstFileIngestionJob {
|
||||
bool need_generate_file_checksum_{true};
|
||||
std::shared_ptr<IOTracer> io_tracer_;
|
||||
|
||||
// Flag indicating whether the column family is flushed after `Prepare` and
|
||||
// before `Run`.
|
||||
bool flushed_before_run_{false};
|
||||
|
||||
// Below are variables used in (un)registering range for this ingestion job
|
||||
//
|
||||
// FileMetaData used in inputs of compactions equivalent to this ingestion
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include <table/block_based/block_based_table_factory.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
@@ -150,7 +152,7 @@ class ExternalSSTFileTest
|
||||
bool verify_checksums_before_ingest = true, bool ingest_behind = false,
|
||||
bool sort_data = false,
|
||||
std::map<std::string, std::string>* true_data = nullptr,
|
||||
ColumnFamilyHandle* cfh = nullptr) {
|
||||
ColumnFamilyHandle* cfh = nullptr, bool fill_cache = false) {
|
||||
// Generate a file id if not provided
|
||||
if (file_id == -1) {
|
||||
file_id = last_file_id_ + 1;
|
||||
@@ -194,6 +196,7 @@ class ExternalSSTFileTest
|
||||
ifo.write_global_seqno = allow_global_seqno ? write_global_seqno : false;
|
||||
ifo.verify_checksums_before_ingest = verify_checksums_before_ingest;
|
||||
ifo.ingest_behind = ingest_behind;
|
||||
ifo.fill_cache = fill_cache;
|
||||
if (cfh) {
|
||||
s = db_->IngestExternalFile(cfh, {file_path}, ifo);
|
||||
} else {
|
||||
@@ -267,15 +270,15 @@ class ExternalSSTFileTest
|
||||
bool verify_checksums_before_ingest = true, bool ingest_behind = false,
|
||||
bool sort_data = false,
|
||||
std::map<std::string, std::string>* true_data = nullptr,
|
||||
ColumnFamilyHandle* cfh = nullptr) {
|
||||
ColumnFamilyHandle* cfh = nullptr, bool fill_cache = false) {
|
||||
std::vector<std::pair<std::string, std::string>> file_data;
|
||||
for (auto& k : keys) {
|
||||
file_data.emplace_back(Key(k), Key(k) + std::to_string(file_id));
|
||||
}
|
||||
return GenerateAndAddExternalFile(options, file_data, file_id,
|
||||
allow_global_seqno, write_global_seqno,
|
||||
verify_checksums_before_ingest,
|
||||
ingest_behind, sort_data, true_data, cfh);
|
||||
return GenerateAndAddExternalFile(
|
||||
options, file_data, file_id, allow_global_seqno, write_global_seqno,
|
||||
verify_checksums_before_ingest, ingest_behind, sort_data, true_data,
|
||||
cfh, fill_cache);
|
||||
}
|
||||
|
||||
Status DeprecatedAddFile(const std::vector<std::string>& files,
|
||||
@@ -314,6 +317,49 @@ TEST_F(ExternalSSTFileTest, ComparatorMismatch) {
|
||||
ASSERT_NOK(DeprecatedAddFile({file}));
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileTest, NoBlockCache) {
|
||||
LRUCacheOptions co;
|
||||
co.capacity = 32 << 20;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(co);
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_cache = cache;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
Options options = CurrentOptions();
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
|
||||
size_t usage_before_ingestion = cache->GetUsage();
|
||||
std::map<std::string, std::string> true_data;
|
||||
// Ingest with fill_cache = true
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 2}, -1, false, false, true,
|
||||
false, false, &true_data, nullptr,
|
||||
/*fill_cache=*/true));
|
||||
ASSERT_EQ(FilesPerLevel(), "0,0,0,0,0,0,1");
|
||||
EXPECT_GT(cache->GetUsage(), usage_before_ingestion);
|
||||
|
||||
TablePropertiesCollection tp;
|
||||
ASSERT_OK(db_->GetPropertiesOfAllTables(&tp));
|
||||
for (const auto& entry : tp) {
|
||||
EXPECT_GT(entry.second->index_size, 0);
|
||||
EXPECT_GT(entry.second->filter_size, 0);
|
||||
}
|
||||
|
||||
usage_before_ingestion = cache->GetUsage();
|
||||
// Ingest with fill_cache = false
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {3, 4}, -1, false, false, true,
|
||||
false, false, &true_data, nullptr,
|
||||
/*fill_cache=*/false));
|
||||
EXPECT_EQ(usage_before_ingestion, cache->GetUsage());
|
||||
|
||||
tp.clear();
|
||||
ASSERT_OK(db_->GetPropertiesOfAllTables(&tp));
|
||||
for (const auto& entry : tp) {
|
||||
EXPECT_GT(entry.second->index_size, 0);
|
||||
EXPECT_GT(entry.second->filter_size, 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileTest, Basic) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
@@ -1941,9 +1987,9 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedUniversal) {
|
||||
options, file_data, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, false, &true_data));
|
||||
|
||||
// This file overlap with files in L4, we will ingest it into the last
|
||||
// non-overlapping and non-empty level, in this case, it's L0.
|
||||
ASSERT_EQ("3,0,0,0,3", FilesPerLevel());
|
||||
// This file overlap with files in L4, we will ingest it into the closest
|
||||
// non-overlapping level, in this case, it's L3.
|
||||
ASSERT_EQ("2,0,0,1,3", FilesPerLevel());
|
||||
|
||||
size_t kcnt = 0;
|
||||
VerifyDBFromMap(true_data, &kcnt, false);
|
||||
|
||||
+52
-29
@@ -157,7 +157,7 @@ void FlushJob::ReportStartedFlush() {
|
||||
IOSTATS_RESET(bytes_written);
|
||||
}
|
||||
|
||||
void FlushJob::ReportFlushInputSize(const autovector<MemTable*>& mems) {
|
||||
void FlushJob::ReportFlushInputSize(const autovector<ReadOnlyMemTable*>& mems) {
|
||||
uint64_t input_size = 0;
|
||||
for (auto* mem : mems) {
|
||||
input_size += mem->ApproximateMemoryUsage();
|
||||
@@ -204,7 +204,7 @@ void FlushJob::PickMemTable() {
|
||||
// entries mems are (implicitly) sorted in ascending order by their created
|
||||
// time. We will use the first memtable's `edit` to keep the meta info for
|
||||
// this flush.
|
||||
MemTable* m = mems_[0];
|
||||
ReadOnlyMemTable* m = mems_[0];
|
||||
edit_ = m->GetEdits();
|
||||
edit_->SetPrevLogNumber(0);
|
||||
// SetLogNumber(log_num) indicates logs with number smaller than log_num
|
||||
@@ -420,9 +420,10 @@ Status FlushJob::MemPurge() {
|
||||
std::vector<InternalIterator*> memtables;
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
range_del_iters;
|
||||
for (MemTable* m : mems_) {
|
||||
for (ReadOnlyMemTable* m : mems_) {
|
||||
memtables.push_back(m->NewIterator(ro, /*seqno_to_time_mapping=*/nullptr,
|
||||
&arena, /*prefix_extractor=*/nullptr));
|
||||
&arena, /*prefix_extractor=*/nullptr,
|
||||
/*for_flush=*/true));
|
||||
auto* range_del_iter = m->NewRangeTombstoneIterator(
|
||||
ro, kMaxSequenceNumber, true /* immutable_memtable */);
|
||||
if (range_del_iter != nullptr) {
|
||||
@@ -624,10 +625,13 @@ Status FlushJob::MemPurge() {
|
||||
// Construct fragmented memtable range tombstones without mutex
|
||||
new_mem->ConstructFragmentedRangeTombstones();
|
||||
db_mutex_->Lock();
|
||||
uint64_t new_mem_id = mems_[0]->GetID();
|
||||
// Take the newest id, so that memtables in MemtableList don't have
|
||||
// out-of-order memtable ids.
|
||||
uint64_t new_mem_id = mems_.back()->GetID();
|
||||
|
||||
new_mem->SetID(new_mem_id);
|
||||
new_mem->SetNextLogNumber(mems_[0]->GetNextLogNumber());
|
||||
// Take the latest memtable's next log number.
|
||||
new_mem->SetNextLogNumber(mems_.back()->GetNextLogNumber());
|
||||
|
||||
// This addition will not trigger another flush, because
|
||||
// we do not call EnqueuePendingFlush().
|
||||
@@ -713,11 +717,11 @@ bool FlushJob::MemPurgeDecider(double threshold) {
|
||||
|
||||
// Iterate over each memtable of the set.
|
||||
for (auto mem_iter = std::begin(mems_); mem_iter != std::end(mems_);
|
||||
mem_iter++) {
|
||||
MemTable* mt = *mem_iter;
|
||||
++mem_iter) {
|
||||
ReadOnlyMemTable* mt = *mem_iter;
|
||||
|
||||
// Else sample from the table.
|
||||
uint64_t nentries = mt->num_entries();
|
||||
uint64_t nentries = mt->NumEntries();
|
||||
// Corrected Cochran formula for small populations
|
||||
// (converges to n0 for large populations).
|
||||
uint64_t target_sample_size =
|
||||
@@ -858,6 +862,12 @@ Status FlushJob::WriteLevel0Table() {
|
||||
meta_.temperature = mutable_cf_options_.default_write_temperature;
|
||||
file_options_.temperature = meta_.temperature;
|
||||
|
||||
const auto* ucmp = cfd_->internal_comparator().user_comparator();
|
||||
assert(ucmp);
|
||||
const size_t ts_sz = ucmp->timestamp_size();
|
||||
const bool logical_strip_timestamp =
|
||||
ts_sz > 0 && !cfd_->ioptions()->persist_user_defined_timestamps;
|
||||
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
{
|
||||
@@ -888,23 +898,35 @@ Status FlushJob::WriteLevel0Table() {
|
||||
TEST_SYNC_POINT_CALLBACK("FlushJob::WriteLevel0Table:num_memtables",
|
||||
&mems_size);
|
||||
assert(job_context_);
|
||||
for (MemTable* m : mems_) {
|
||||
ROCKS_LOG_INFO(
|
||||
db_options_.info_log,
|
||||
"[%s] [JOB %d] Flushing memtable with next log file: %" PRIu64 "\n",
|
||||
cfd_->GetName().c_str(), job_context_->job_id, m->GetNextLogNumber());
|
||||
memtables.push_back(m->NewIterator(ro, /*seqno_to_time_mapping=*/nullptr,
|
||||
&arena, /*prefix_extractor=*/nullptr));
|
||||
auto* range_del_iter = m->NewRangeTombstoneIterator(
|
||||
ro, kMaxSequenceNumber, true /* immutable_memtable */);
|
||||
for (ReadOnlyMemTable* m : mems_) {
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"[%s] [JOB %d] Flushing memtable id %" PRIu64
|
||||
" with next log file: %" PRIu64 "\n",
|
||||
cfd_->GetName().c_str(), job_context_->job_id, m->GetID(),
|
||||
m->GetNextLogNumber());
|
||||
if (logical_strip_timestamp) {
|
||||
memtables.push_back(m->NewTimestampStrippingIterator(
|
||||
ro, /*seqno_to_time_mapping=*/nullptr, &arena,
|
||||
/*prefix_extractor=*/nullptr, ts_sz));
|
||||
} else {
|
||||
memtables.push_back(
|
||||
m->NewIterator(ro, /*seqno_to_time_mapping=*/nullptr, &arena,
|
||||
/*prefix_extractor=*/nullptr, /*for_flush=*/true));
|
||||
}
|
||||
auto* range_del_iter =
|
||||
logical_strip_timestamp
|
||||
? m->NewTimestampStrippingRangeTombstoneIterator(
|
||||
ro, kMaxSequenceNumber, ts_sz)
|
||||
: m->NewRangeTombstoneIterator(ro, kMaxSequenceNumber,
|
||||
true /* immutable_memtable */);
|
||||
if (range_del_iter != nullptr) {
|
||||
range_del_iters.emplace_back(range_del_iter);
|
||||
}
|
||||
total_num_entries += m->num_entries();
|
||||
total_num_deletes += m->num_deletes();
|
||||
total_data_size += m->get_data_size();
|
||||
total_num_entries += m->NumEntries();
|
||||
total_num_deletes += m->NumDeletion();
|
||||
total_data_size += m->GetDataSize();
|
||||
total_memory_usage += m->ApproximateMemoryUsage();
|
||||
total_num_range_deletes += m->num_range_deletes();
|
||||
total_num_range_deletes += m->NumRangeDeletion();
|
||||
}
|
||||
|
||||
// TODO(cbi): when memtable is flushed due to number of range deletions
|
||||
@@ -970,9 +992,10 @@ Status FlushJob::WriteLevel0Table() {
|
||||
cfd_->internal_comparator(), cfd_->internal_tbl_prop_coll_factories(),
|
||||
output_compression_, mutable_cf_options_.compression_opts,
|
||||
cfd_->GetID(), cfd_->GetName(), 0 /* level */,
|
||||
false /* is_bottommost */, TableFileCreationReason::kFlush,
|
||||
oldest_key_time, current_time, db_id_, db_session_id_,
|
||||
0 /* target_file_size */, meta_.fd.GetNumber(),
|
||||
current_time /* newest_key_time */, false /* is_bottommost */,
|
||||
TableFileCreationReason::kFlush, oldest_key_time, current_time,
|
||||
db_id_, db_session_id_, 0 /* target_file_size */,
|
||||
meta_.fd.GetNumber(),
|
||||
preclude_last_level_min_seqno_ == kMaxSequenceNumber
|
||||
? preclude_last_level_min_seqno_
|
||||
: std::min(earliest_snapshot_, preclude_last_level_min_seqno_));
|
||||
@@ -1154,7 +1177,7 @@ void FlushJob::GetEffectiveCutoffUDTForPickedMemTables() {
|
||||
return;
|
||||
}
|
||||
// Find the newest user-defined timestamps from all the flushed memtables.
|
||||
for (MemTable* m : mems_) {
|
||||
for (const ReadOnlyMemTable* m : mems_) {
|
||||
Slice table_newest_udt = m->GetNewestUDT();
|
||||
// Empty memtables can be legitimately created and flushed, for example
|
||||
// by error recovery flush attempts.
|
||||
@@ -1172,7 +1195,7 @@ void FlushJob::GetEffectiveCutoffUDTForPickedMemTables() {
|
||||
}
|
||||
|
||||
void FlushJob::GetPrecludeLastLevelMinSeqno() {
|
||||
if (cfd_->ioptions()->preclude_last_level_data_seconds == 0) {
|
||||
if (mutable_cf_options_.preclude_last_level_data_seconds == 0) {
|
||||
return;
|
||||
}
|
||||
int64_t current_time = 0;
|
||||
@@ -1185,8 +1208,8 @@ void FlushJob::GetPrecludeLastLevelMinSeqno() {
|
||||
SequenceNumber preserve_time_min_seqno;
|
||||
seqno_to_time_mapping_->GetCurrentTieringCutoffSeqnos(
|
||||
static_cast<uint64_t>(current_time),
|
||||
cfd_->ioptions()->preserve_internal_time_seconds,
|
||||
cfd_->ioptions()->preclude_last_level_data_seconds,
|
||||
mutable_cf_options_.preserve_internal_time_seconds,
|
||||
mutable_cf_options_.preclude_last_level_data_seconds,
|
||||
&preserve_time_min_seqno, &preclude_last_level_min_seqno_);
|
||||
}
|
||||
}
|
||||
|
||||
+5
-3
@@ -91,7 +91,7 @@ class FlushJob {
|
||||
bool* skipped_since_bg_error = nullptr,
|
||||
ErrorHandler* error_handler = nullptr);
|
||||
void Cancel();
|
||||
const autovector<MemTable*>& GetMemTables() const { return mems_; }
|
||||
const autovector<ReadOnlyMemTable*>& GetMemTables() const { return mems_; }
|
||||
|
||||
std::list<std::unique_ptr<FlushJobInfo>>* GetCommittedFlushJobsInfo() {
|
||||
return &committed_flush_jobs_info_;
|
||||
@@ -101,7 +101,7 @@ class FlushJob {
|
||||
friend class FlushJobTest_GetRateLimiterPriorityForWrite_Test;
|
||||
|
||||
void ReportStartedFlush();
|
||||
void ReportFlushInputSize(const autovector<MemTable*>& mems);
|
||||
static void ReportFlushInputSize(const autovector<ReadOnlyMemTable*>& mems);
|
||||
void RecordFlushIOStats();
|
||||
Status WriteLevel0Table();
|
||||
|
||||
@@ -205,7 +205,9 @@ class FlushJob {
|
||||
|
||||
// Variables below are set by PickMemTable():
|
||||
FileMetaData meta_;
|
||||
autovector<MemTable*> mems_;
|
||||
// Memtables to be flushed by this job.
|
||||
// Ordered by increasing memtable id, i.e., oldest memtable first.
|
||||
autovector<ReadOnlyMemTable*> mems_;
|
||||
VersionEdit* edit_;
|
||||
Version* base_;
|
||||
bool pick_memtable_called;
|
||||
|
||||
+18
-12
@@ -264,7 +264,7 @@ TEST_F(FlushJobTest, NonEmpty) {
|
||||
}
|
||||
mock::SortKVVector(&inserted_keys);
|
||||
|
||||
autovector<MemTable*> to_delete;
|
||||
autovector<ReadOnlyMemTable*> to_delete;
|
||||
new_mem->ConstructFragmentedRangeTombstones();
|
||||
cfd->imm()->Add(new_mem, &to_delete);
|
||||
for (auto& m : to_delete) {
|
||||
@@ -325,7 +325,7 @@ TEST_F(FlushJobTest, FlushMemTablesSingleColumnFamily) {
|
||||
}
|
||||
}
|
||||
|
||||
autovector<MemTable*> to_delete;
|
||||
autovector<ReadOnlyMemTable*> to_delete;
|
||||
for (auto mem : new_mems) {
|
||||
mem->ConstructFragmentedRangeTombstones();
|
||||
cfd->imm()->Add(mem, &to_delete);
|
||||
@@ -380,7 +380,7 @@ TEST_F(FlushJobTest, FlushMemtablesMultipleColumnFamilies) {
|
||||
std::vector<uint64_t> memtable_ids;
|
||||
std::vector<SequenceNumber> smallest_seqs;
|
||||
std::vector<SequenceNumber> largest_seqs;
|
||||
autovector<MemTable*> to_delete;
|
||||
autovector<ReadOnlyMemTable*> to_delete;
|
||||
SequenceNumber curr_seqno = 0;
|
||||
size_t k = 0;
|
||||
for (auto cfd : all_cfds) {
|
||||
@@ -439,7 +439,7 @@ TEST_F(FlushJobTest, FlushMemtablesMultipleColumnFamilies) {
|
||||
for (auto& meta : file_metas) {
|
||||
file_meta_ptrs.push_back(&meta);
|
||||
}
|
||||
autovector<const autovector<MemTable*>*> mems_list;
|
||||
autovector<const autovector<ReadOnlyMemTable*>*> mems_list;
|
||||
for (size_t i = 0; i != all_cfds.size(); ++i) {
|
||||
const auto& mems = flush_jobs[i]->GetMemTables();
|
||||
mems_list.push_back(&mems);
|
||||
@@ -528,7 +528,7 @@ TEST_F(FlushJobTest, Snapshots) {
|
||||
}
|
||||
mock::SortKVVector(&inserted_keys);
|
||||
|
||||
autovector<MemTable*> to_delete;
|
||||
autovector<ReadOnlyMemTable*> to_delete;
|
||||
new_mem->ConstructFragmentedRangeTombstones();
|
||||
cfd->imm()->Add(new_mem, &to_delete);
|
||||
for (auto& m : to_delete) {
|
||||
@@ -582,7 +582,7 @@ TEST_F(FlushJobTest, GetRateLimiterPriorityForWrite) {
|
||||
}
|
||||
}
|
||||
|
||||
autovector<MemTable*> to_delete;
|
||||
autovector<ReadOnlyMemTable*> to_delete;
|
||||
for (auto mem : new_mems) {
|
||||
mem->ConstructFragmentedRangeTombstones();
|
||||
cfd->imm()->Add(mem, &to_delete);
|
||||
@@ -654,7 +654,7 @@ TEST_F(FlushJobTest, ReplaceTimedPutWriteTimeWithPreferredSeqno) {
|
||||
InternalKey largest_internal_key("foo", SequenceNumber(18), kTypeValue);
|
||||
inserted_entries.push_back(
|
||||
{largest_internal_key.Encode().ToString(), "fval"});
|
||||
autovector<MemTable*> to_delete;
|
||||
autovector<ReadOnlyMemTable*> to_delete;
|
||||
new_mem->ConstructFragmentedRangeTombstones();
|
||||
cfd->imm()->Add(new_mem, &to_delete);
|
||||
for (auto& m : to_delete) {
|
||||
@@ -744,7 +744,7 @@ class FlushJobTimestampTest
|
||||
|
||||
TEST_P(FlushJobTimestampTest, AllKeysExpired) {
|
||||
ColumnFamilyData* cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
autovector<MemTable*> to_delete;
|
||||
autovector<ReadOnlyMemTable*> to_delete;
|
||||
|
||||
{
|
||||
MemTable* new_mem = cfd->ConstructNewMemtable(
|
||||
@@ -810,7 +810,7 @@ TEST_P(FlushJobTimestampTest, AllKeysExpired) {
|
||||
|
||||
TEST_P(FlushJobTimestampTest, NoKeyExpired) {
|
||||
ColumnFamilyData* cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
autovector<MemTable*> to_delete;
|
||||
autovector<ReadOnlyMemTable*> to_delete;
|
||||
|
||||
{
|
||||
MemTable* new_mem = cfd->ConstructNewMemtable(
|
||||
@@ -874,9 +874,15 @@ TEST_P(FlushJobTimestampTest, NoKeyExpired) {
|
||||
expected_full_history_ts_low = full_history_ts_low;
|
||||
}
|
||||
InternalKey smallest(smallest_key, curr_seq_ - 1, ValueType::kTypeValue);
|
||||
InternalKey largest(largest_key, kStartSeq, ValueType::kTypeValue);
|
||||
CheckFileMetaData(cfd, smallest, largest, &fmeta);
|
||||
CheckFullHistoryTsLow(cfd, expected_full_history_ts_low);
|
||||
if (!persist_udt_) {
|
||||
InternalKey largest(largest_key, curr_seq_ - 1, ValueType::kTypeValue);
|
||||
CheckFileMetaData(cfd, smallest, largest, &fmeta);
|
||||
CheckFullHistoryTsLow(cfd, expected_full_history_ts_low);
|
||||
} else {
|
||||
InternalKey largest(largest_key, kStartSeq, ValueType::kTypeValue);
|
||||
CheckFileMetaData(cfd, smallest, largest, &fmeta);
|
||||
CheckFullHistoryTsLow(cfd, expected_full_history_ts_low);
|
||||
}
|
||||
}
|
||||
job_context.Clean();
|
||||
ASSERT_TRUE(to_delete.empty());
|
||||
|
||||
+27
-29
@@ -32,11 +32,11 @@ namespace ROCKSDB_NAMESPACE {
|
||||
// iter.Next()
|
||||
class ForwardLevelIterator : public InternalIterator {
|
||||
public:
|
||||
ForwardLevelIterator(
|
||||
const ColumnFamilyData* const cfd, const ReadOptions& read_options,
|
||||
const std::vector<FileMetaData*>& files,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor,
|
||||
bool allow_unprepared_value, uint8_t block_protection_bytes_per_key)
|
||||
ForwardLevelIterator(const ColumnFamilyData* const cfd,
|
||||
const ReadOptions& read_options,
|
||||
const std::vector<FileMetaData*>& files,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
bool allow_unprepared_value)
|
||||
: cfd_(cfd),
|
||||
read_options_(read_options),
|
||||
files_(files),
|
||||
@@ -44,9 +44,8 @@ class ForwardLevelIterator : public InternalIterator {
|
||||
file_index_(std::numeric_limits<uint32_t>::max()),
|
||||
file_iter_(nullptr),
|
||||
pinned_iters_mgr_(nullptr),
|
||||
prefix_extractor_(prefix_extractor),
|
||||
allow_unprepared_value_(allow_unprepared_value),
|
||||
block_protection_bytes_per_key_(block_protection_bytes_per_key) {
|
||||
mutable_cf_options_(mutable_cf_options),
|
||||
allow_unprepared_value_(allow_unprepared_value) {
|
||||
status_.PermitUncheckedError(); // Allow uninitialized status through
|
||||
}
|
||||
|
||||
@@ -83,13 +82,12 @@ class ForwardLevelIterator : public InternalIterator {
|
||||
read_options_, *(cfd_->soptions()), cfd_->internal_comparator(),
|
||||
*files_[file_index_],
|
||||
read_options_.ignore_range_deletions ? nullptr : &range_del_agg,
|
||||
prefix_extractor_, /*table_reader_ptr=*/nullptr,
|
||||
mutable_cf_options_, /*table_reader_ptr=*/nullptr,
|
||||
/*file_read_hist=*/nullptr, TableReaderCaller::kUserIterator,
|
||||
/*arena=*/nullptr, /*skip_filters=*/false, /*level=*/-1,
|
||||
/*max_file_size_for_l0_meta_pin=*/0,
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr, allow_unprepared_value_,
|
||||
block_protection_bytes_per_key_);
|
||||
/*largest_compaction_key=*/nullptr, allow_unprepared_value_);
|
||||
file_iter_->SetPinnedItersMgr(pinned_iters_mgr_);
|
||||
valid_ = false;
|
||||
if (!range_del_agg.IsEmpty()) {
|
||||
@@ -167,6 +165,10 @@ class ForwardLevelIterator : public InternalIterator {
|
||||
assert(valid_);
|
||||
return file_iter_->value();
|
||||
}
|
||||
uint64_t write_unix_time() const override {
|
||||
assert(valid_);
|
||||
return file_iter_->write_unix_time();
|
||||
}
|
||||
Status status() const override {
|
||||
if (!status_.ok()) {
|
||||
return status_;
|
||||
@@ -210,10 +212,9 @@ class ForwardLevelIterator : public InternalIterator {
|
||||
Status status_;
|
||||
InternalIterator* file_iter_;
|
||||
PinnedIteratorsManager* pinned_iters_mgr_;
|
||||
// Kept alive by ForwardIterator::sv_->mutable_cf_options
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor_;
|
||||
const MutableCFOptions& mutable_cf_options_;
|
||||
|
||||
const bool allow_unprepared_value_;
|
||||
const uint8_t block_protection_bytes_per_key_;
|
||||
};
|
||||
|
||||
ForwardIterator::ForwardIterator(DBImpl* db, const ReadOptions& read_options,
|
||||
@@ -713,7 +714,8 @@ void ForwardIterator::RebuildIterators(bool refresh_sv) {
|
||||
sv_->GetSeqnoToTimeMapping();
|
||||
mutable_iter_ =
|
||||
sv_->mem->NewIterator(read_options_, seqno_to_time_mapping, &arena_,
|
||||
sv_->mutable_cf_options.prefix_extractor.get());
|
||||
sv_->mutable_cf_options.prefix_extractor.get(),
|
||||
/*for_flush=*/false);
|
||||
sv_->imm->AddIterators(read_options_, seqno_to_time_mapping,
|
||||
sv_->mutable_cf_options.prefix_extractor.get(),
|
||||
&imm_iters_, &arena_);
|
||||
@@ -746,14 +748,13 @@ void ForwardIterator::RebuildIterators(bool refresh_sv) {
|
||||
l0_iters_.push_back(cfd_->table_cache()->NewIterator(
|
||||
read_options_, *cfd_->soptions(), cfd_->internal_comparator(), *l0,
|
||||
read_options_.ignore_range_deletions ? nullptr : &range_del_agg,
|
||||
sv_->mutable_cf_options.prefix_extractor,
|
||||
sv_->mutable_cf_options,
|
||||
/*table_reader_ptr=*/nullptr, /*file_read_hist=*/nullptr,
|
||||
TableReaderCaller::kUserIterator, /*arena=*/nullptr,
|
||||
/*skip_filters=*/false, /*level=*/-1,
|
||||
MaxFileSizeForL0MetaPin(sv_->mutable_cf_options),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr, allow_unprepared_value_,
|
||||
sv_->mutable_cf_options.block_protection_bytes_per_key));
|
||||
/*largest_compaction_key=*/nullptr, allow_unprepared_value_));
|
||||
}
|
||||
BuildLevelIterators(vstorage, sv_);
|
||||
current_ = nullptr;
|
||||
@@ -784,7 +785,8 @@ void ForwardIterator::RenewIterators() {
|
||||
svnew->GetSeqnoToTimeMapping();
|
||||
mutable_iter_ =
|
||||
svnew->mem->NewIterator(read_options_, seqno_to_time_mapping, &arena_,
|
||||
svnew->mutable_cf_options.prefix_extractor.get());
|
||||
svnew->mutable_cf_options.prefix_extractor.get(),
|
||||
/*for_flush=*/false);
|
||||
svnew->imm->AddIterators(read_options_, seqno_to_time_mapping,
|
||||
svnew->mutable_cf_options.prefix_extractor.get(),
|
||||
&imm_iters_, &arena_);
|
||||
@@ -834,14 +836,13 @@ void ForwardIterator::RenewIterators() {
|
||||
read_options_, *cfd_->soptions(), cfd_->internal_comparator(),
|
||||
*l0_files_new[inew],
|
||||
read_options_.ignore_range_deletions ? nullptr : &range_del_agg,
|
||||
svnew->mutable_cf_options.prefix_extractor,
|
||||
svnew->mutable_cf_options,
|
||||
/*table_reader_ptr=*/nullptr, /*file_read_hist=*/nullptr,
|
||||
TableReaderCaller::kUserIterator, /*arena=*/nullptr,
|
||||
/*skip_filters=*/false, /*level=*/-1,
|
||||
MaxFileSizeForL0MetaPin(svnew->mutable_cf_options),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr, allow_unprepared_value_,
|
||||
svnew->mutable_cf_options.block_protection_bytes_per_key));
|
||||
/*largest_compaction_key=*/nullptr, allow_unprepared_value_));
|
||||
}
|
||||
|
||||
for (auto* f : l0_iters_) {
|
||||
@@ -884,9 +885,8 @@ void ForwardIterator::BuildLevelIterators(const VersionStorageInfo* vstorage,
|
||||
}
|
||||
} else {
|
||||
level_iters_.push_back(new ForwardLevelIterator(
|
||||
cfd_, read_options_, level_files,
|
||||
sv->mutable_cf_options.prefix_extractor, allow_unprepared_value_,
|
||||
sv->mutable_cf_options.block_protection_bytes_per_key));
|
||||
cfd_, read_options_, level_files, sv->mutable_cf_options,
|
||||
allow_unprepared_value_));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -901,15 +901,13 @@ void ForwardIterator::ResetIncompleteIterators() {
|
||||
DeleteIterator(l0_iters_[i]);
|
||||
l0_iters_[i] = cfd_->table_cache()->NewIterator(
|
||||
read_options_, *cfd_->soptions(), cfd_->internal_comparator(),
|
||||
*l0_files[i], /*range_del_agg=*/nullptr,
|
||||
sv_->mutable_cf_options.prefix_extractor,
|
||||
*l0_files[i], /*range_del_agg=*/nullptr, sv_->mutable_cf_options,
|
||||
/*table_reader_ptr=*/nullptr, /*file_read_hist=*/nullptr,
|
||||
TableReaderCaller::kUserIterator, /*arena=*/nullptr,
|
||||
/*skip_filters=*/false, /*level=*/-1,
|
||||
MaxFileSizeForL0MetaPin(sv_->mutable_cf_options),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr, allow_unprepared_value_,
|
||||
sv_->mutable_cf_options.block_protection_bytes_per_key);
|
||||
/*largest_compaction_key=*/nullptr, allow_unprepared_value_);
|
||||
l0_iters_[i]->SetPinnedItersMgr(pinned_iters_mgr_);
|
||||
}
|
||||
|
||||
|
||||
@@ -329,7 +329,7 @@ Status ImportColumnFamilyJob::GetIngestedFileInfo(
|
||||
// TODO(yuzhangyu): User-defined timestamps doesn't support importing column
|
||||
// family. Pass in the correct `user_defined_timestamps_persisted` flag for
|
||||
// creating `TableReaderOptions` when the support is there.
|
||||
status = cfd_->ioptions()->table_factory->NewTableReader(
|
||||
status = sv->mutable_cf_options.table_factory->NewTableReader(
|
||||
TableReaderOptions(
|
||||
*cfd_->ioptions(), sv->mutable_cf_options.prefix_extractor,
|
||||
env_options_, cfd_->internal_comparator(),
|
||||
@@ -371,7 +371,8 @@ Status ImportColumnFamilyJob::GetIngestedFileInfo(
|
||||
if (iter->Valid()) {
|
||||
file_to_import->smallest_internal_key.DecodeFrom(iter->key());
|
||||
Slice largest;
|
||||
if (strcmp(cfd_->ioptions()->table_factory->Name(), "PlainTable") == 0) {
|
||||
if (strcmp(sv->mutable_cf_options.table_factory->Name(), "PlainTable") ==
|
||||
0) {
|
||||
// PlainTable iterator does not support SeekToLast().
|
||||
largest = iter->key();
|
||||
for (; iter->Valid(); iter->Next()) {
|
||||
|
||||
@@ -951,6 +951,8 @@ TEST_F(ImportColumnFamilyTest, AssignEpochNumberToMultipleCF) {
|
||||
Options options = CurrentOptions();
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.max_background_jobs = 8;
|
||||
// Always allow parallel compaction
|
||||
options.soft_pending_compaction_bytes_limit = 10;
|
||||
env_->SetBackgroundThreads(2, Env::LOW);
|
||||
env_->SetBackgroundThreads(0, Env::BOTTOM);
|
||||
CreateAndReopenWithCF({"CF1", "CF2"}, options);
|
||||
|
||||
@@ -1301,7 +1301,7 @@ bool InternalStats::HandleNumEntriesActiveMemTable(uint64_t* value,
|
||||
DBImpl* /*db*/,
|
||||
Version* /*version*/) {
|
||||
// Current number of entires in the active memtable
|
||||
*value = cfd_->mem()->num_entries();
|
||||
*value = cfd_->mem()->NumEntries();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1317,7 +1317,7 @@ bool InternalStats::HandleNumDeletesActiveMemTable(uint64_t* value,
|
||||
DBImpl* /*db*/,
|
||||
Version* /*version*/) {
|
||||
// Current number of entires in the active memtable
|
||||
*value = cfd_->mem()->num_deletes();
|
||||
*value = cfd_->mem()->NumDeletion();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1334,11 +1334,11 @@ bool InternalStats::HandleEstimateNumKeys(uint64_t* value, DBImpl* /*db*/,
|
||||
// Estimate number of entries in the column family:
|
||||
// Use estimated entries in tables + total entries in memtables.
|
||||
const auto* vstorage = cfd_->current()->storage_info();
|
||||
uint64_t estimate_keys = cfd_->mem()->num_entries() +
|
||||
uint64_t estimate_keys = cfd_->mem()->NumEntries() +
|
||||
cfd_->imm()->current()->GetTotalNumEntries() +
|
||||
vstorage->GetEstimatedActiveKeys();
|
||||
uint64_t estimate_deletes =
|
||||
cfd_->mem()->num_deletes() + cfd_->imm()->current()->GetTotalNumDeletes();
|
||||
cfd_->mem()->NumDeletion() + cfd_->imm()->current()->GetTotalNumDeletes();
|
||||
*value = estimate_keys > estimate_deletes * 2
|
||||
? estimate_keys - (estimate_deletes * 2)
|
||||
: 0;
|
||||
@@ -1495,8 +1495,10 @@ bool InternalStats::HandleEstimateOldestKeyTime(uint64_t* value, DBImpl* /*db*/,
|
||||
}
|
||||
|
||||
Cache* InternalStats::GetBlockCacheForStats() {
|
||||
auto* table_factory = cfd_->ioptions()->table_factory.get();
|
||||
// NOTE: called in startup before GetCurrentMutableCFOptions() is ready
|
||||
auto* table_factory = cfd_->GetLatestMutableCFOptions()->table_factory.get();
|
||||
assert(table_factory != nullptr);
|
||||
// FIXME: need to a shared_ptr if/when block_cache is going to be mutable
|
||||
return table_factory->GetOptions<Cache>(TableFactory::kBlockCacheOpts());
|
||||
}
|
||||
|
||||
@@ -2161,7 +2163,8 @@ class BlockCachePropertyAggregator : public IntPropertyAggregator {
|
||||
virtual ~BlockCachePropertyAggregator() override = default;
|
||||
|
||||
void Add(ColumnFamilyData* cfd, uint64_t value) override {
|
||||
auto* table_factory = cfd->ioptions()->table_factory.get();
|
||||
auto* table_factory =
|
||||
cfd->GetCurrentMutableCFOptions()->table_factory.get();
|
||||
assert(table_factory != nullptr);
|
||||
Cache* cache =
|
||||
table_factory->GetOptions<Cache>(TableFactory::kBlockCacheOpts());
|
||||
|
||||
@@ -182,6 +182,14 @@ class InternalStats {
|
||||
// The number of bytes read from the compaction output level (table files)
|
||||
uint64_t bytes_read_output_level;
|
||||
|
||||
// The number of bytes skipped from all non-output levels because the input
|
||||
// files are filtered by compaction optimizations.
|
||||
uint64_t bytes_skipped_non_output_levels;
|
||||
|
||||
// The number of bytes skipped from the compaction output level because the
|
||||
// input files are filtered by compaction optimizations.
|
||||
uint64_t bytes_skipped_output_level;
|
||||
|
||||
// The number of bytes read from blob files
|
||||
uint64_t bytes_read_blob;
|
||||
|
||||
@@ -201,6 +209,14 @@ class InternalStats {
|
||||
// The number of compaction input files in the output level (table files)
|
||||
int num_input_files_in_output_level;
|
||||
|
||||
// The number of non output level compaction input files that are filtered
|
||||
// by compaction optimizations.
|
||||
int num_filtered_input_files_in_non_output_levels;
|
||||
|
||||
// The number of output level compaction input files that are filtered by
|
||||
// compaction optimizations.
|
||||
int num_filtered_input_files_in_output_level;
|
||||
|
||||
// The number of compaction output files (table files)
|
||||
int num_output_files;
|
||||
|
||||
@@ -228,12 +244,16 @@ class InternalStats {
|
||||
cpu_micros(0),
|
||||
bytes_read_non_output_levels(0),
|
||||
bytes_read_output_level(0),
|
||||
bytes_skipped_non_output_levels(0),
|
||||
bytes_skipped_output_level(0),
|
||||
bytes_read_blob(0),
|
||||
bytes_written(0),
|
||||
bytes_written_blob(0),
|
||||
bytes_moved(0),
|
||||
num_input_files_in_non_output_levels(0),
|
||||
num_input_files_in_output_level(0),
|
||||
num_filtered_input_files_in_non_output_levels(0),
|
||||
num_filtered_input_files_in_output_level(0),
|
||||
num_output_files(0),
|
||||
num_output_files_blob(0),
|
||||
num_input_records(0),
|
||||
@@ -251,12 +271,16 @@ class InternalStats {
|
||||
cpu_micros(0),
|
||||
bytes_read_non_output_levels(0),
|
||||
bytes_read_output_level(0),
|
||||
bytes_skipped_non_output_levels(0),
|
||||
bytes_skipped_output_level(0),
|
||||
bytes_read_blob(0),
|
||||
bytes_written(0),
|
||||
bytes_written_blob(0),
|
||||
bytes_moved(0),
|
||||
num_input_files_in_non_output_levels(0),
|
||||
num_input_files_in_output_level(0),
|
||||
num_filtered_input_files_in_non_output_levels(0),
|
||||
num_filtered_input_files_in_output_level(0),
|
||||
num_output_files(0),
|
||||
num_output_files_blob(0),
|
||||
num_input_records(0),
|
||||
@@ -280,6 +304,8 @@ class InternalStats {
|
||||
cpu_micros(c.cpu_micros),
|
||||
bytes_read_non_output_levels(c.bytes_read_non_output_levels),
|
||||
bytes_read_output_level(c.bytes_read_output_level),
|
||||
bytes_skipped_non_output_levels(c.bytes_skipped_non_output_levels),
|
||||
bytes_skipped_output_level(c.bytes_skipped_output_level),
|
||||
bytes_read_blob(c.bytes_read_blob),
|
||||
bytes_written(c.bytes_written),
|
||||
bytes_written_blob(c.bytes_written_blob),
|
||||
@@ -287,6 +313,10 @@ class InternalStats {
|
||||
num_input_files_in_non_output_levels(
|
||||
c.num_input_files_in_non_output_levels),
|
||||
num_input_files_in_output_level(c.num_input_files_in_output_level),
|
||||
num_filtered_input_files_in_non_output_levels(
|
||||
c.num_filtered_input_files_in_non_output_levels),
|
||||
num_filtered_input_files_in_output_level(
|
||||
c.num_filtered_input_files_in_output_level),
|
||||
num_output_files(c.num_output_files),
|
||||
num_output_files_blob(c.num_output_files_blob),
|
||||
num_input_records(c.num_input_records),
|
||||
@@ -304,6 +334,8 @@ class InternalStats {
|
||||
cpu_micros = c.cpu_micros;
|
||||
bytes_read_non_output_levels = c.bytes_read_non_output_levels;
|
||||
bytes_read_output_level = c.bytes_read_output_level;
|
||||
bytes_skipped_non_output_levels = c.bytes_skipped_non_output_levels;
|
||||
bytes_skipped_output_level = c.bytes_skipped_output_level;
|
||||
bytes_read_blob = c.bytes_read_blob;
|
||||
bytes_written = c.bytes_written;
|
||||
bytes_written_blob = c.bytes_written_blob;
|
||||
@@ -311,6 +343,10 @@ class InternalStats {
|
||||
num_input_files_in_non_output_levels =
|
||||
c.num_input_files_in_non_output_levels;
|
||||
num_input_files_in_output_level = c.num_input_files_in_output_level;
|
||||
num_filtered_input_files_in_non_output_levels =
|
||||
c.num_filtered_input_files_in_non_output_levels;
|
||||
num_filtered_input_files_in_output_level =
|
||||
c.num_filtered_input_files_in_output_level;
|
||||
num_output_files = c.num_output_files;
|
||||
num_output_files_blob = c.num_output_files_blob;
|
||||
num_input_records = c.num_input_records;
|
||||
@@ -330,12 +366,16 @@ class InternalStats {
|
||||
this->cpu_micros = 0;
|
||||
this->bytes_read_non_output_levels = 0;
|
||||
this->bytes_read_output_level = 0;
|
||||
this->bytes_skipped_non_output_levels = 0;
|
||||
this->bytes_skipped_output_level = 0;
|
||||
this->bytes_read_blob = 0;
|
||||
this->bytes_written = 0;
|
||||
this->bytes_written_blob = 0;
|
||||
this->bytes_moved = 0;
|
||||
this->num_input_files_in_non_output_levels = 0;
|
||||
this->num_input_files_in_output_level = 0;
|
||||
this->num_filtered_input_files_in_non_output_levels = 0;
|
||||
this->num_filtered_input_files_in_output_level = 0;
|
||||
this->num_output_files = 0;
|
||||
this->num_output_files_blob = 0;
|
||||
this->num_input_records = 0;
|
||||
@@ -353,6 +393,9 @@ class InternalStats {
|
||||
this->cpu_micros += c.cpu_micros;
|
||||
this->bytes_read_non_output_levels += c.bytes_read_non_output_levels;
|
||||
this->bytes_read_output_level += c.bytes_read_output_level;
|
||||
this->bytes_skipped_non_output_levels +=
|
||||
c.bytes_skipped_non_output_levels;
|
||||
this->bytes_skipped_output_level += c.bytes_skipped_output_level;
|
||||
this->bytes_read_blob += c.bytes_read_blob;
|
||||
this->bytes_written += c.bytes_written;
|
||||
this->bytes_written_blob += c.bytes_written_blob;
|
||||
@@ -361,6 +404,10 @@ class InternalStats {
|
||||
c.num_input_files_in_non_output_levels;
|
||||
this->num_input_files_in_output_level +=
|
||||
c.num_input_files_in_output_level;
|
||||
this->num_filtered_input_files_in_non_output_levels +=
|
||||
c.num_filtered_input_files_in_non_output_levels;
|
||||
this->num_filtered_input_files_in_output_level +=
|
||||
c.num_filtered_input_files_in_output_level;
|
||||
this->num_output_files += c.num_output_files;
|
||||
this->num_output_files_blob += c.num_output_files_blob;
|
||||
this->num_input_records += c.num_input_records;
|
||||
@@ -387,6 +434,9 @@ class InternalStats {
|
||||
this->cpu_micros -= c.cpu_micros;
|
||||
this->bytes_read_non_output_levels -= c.bytes_read_non_output_levels;
|
||||
this->bytes_read_output_level -= c.bytes_read_output_level;
|
||||
this->bytes_skipped_non_output_levels -=
|
||||
c.bytes_skipped_non_output_levels;
|
||||
this->bytes_skipped_output_level -= c.bytes_skipped_output_level;
|
||||
this->bytes_read_blob -= c.bytes_read_blob;
|
||||
this->bytes_written -= c.bytes_written;
|
||||
this->bytes_written_blob -= c.bytes_written_blob;
|
||||
@@ -395,6 +445,10 @@ class InternalStats {
|
||||
c.num_input_files_in_non_output_levels;
|
||||
this->num_input_files_in_output_level -=
|
||||
c.num_input_files_in_output_level;
|
||||
this->num_filtered_input_files_in_non_output_levels -=
|
||||
c.num_filtered_input_files_in_non_output_levels;
|
||||
this->num_filtered_input_files_in_output_level -=
|
||||
c.num_filtered_input_files_in_output_level;
|
||||
this->num_output_files -= c.num_output_files;
|
||||
this->num_output_files_blob -= c.num_output_files_blob;
|
||||
this->num_input_records -= c.num_input_records;
|
||||
|
||||
+5
-1
@@ -191,7 +191,7 @@ struct JobContext {
|
||||
std::vector<std::string> manifest_delete_files;
|
||||
|
||||
// a list of memtables to be free
|
||||
autovector<MemTable*> memtables_to_free;
|
||||
autovector<ReadOnlyMemTable*> memtables_to_free;
|
||||
|
||||
// contexts for installing superversions for multiple column families
|
||||
std::vector<SuperVersionContext> superversion_contexts;
|
||||
@@ -202,6 +202,10 @@ struct JobContext {
|
||||
// that corresponds to the set of files in 'live'.
|
||||
uint64_t manifest_file_number;
|
||||
uint64_t pending_manifest_file_number;
|
||||
|
||||
// Used for remote compaction. To prevent OPTIONS files from getting
|
||||
// purged by PurgeObsoleteFiles() of the primary host
|
||||
uint64_t min_options_file_number;
|
||||
uint64_t log_number;
|
||||
uint64_t prev_log_number;
|
||||
|
||||
|
||||
+6
-7
@@ -313,11 +313,11 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
break;
|
||||
|
||||
default: {
|
||||
char buf[40];
|
||||
snprintf(buf, sizeof(buf), "unknown record type %u", record_type);
|
||||
std::string reason =
|
||||
"unknown record type " + std::to_string(record_type);
|
||||
ReportCorruption(
|
||||
(fragment.size() + (in_fragmented_record ? scratch->size() : 0)),
|
||||
buf);
|
||||
reason.c_str());
|
||||
in_fragmented_record = false;
|
||||
scratch->clear();
|
||||
break;
|
||||
@@ -781,12 +781,11 @@ bool FragmentBufferedReader::ReadRecord(Slice* record, std::string* scratch,
|
||||
break;
|
||||
|
||||
default: {
|
||||
char buf[40];
|
||||
snprintf(buf, sizeof(buf), "unknown record type %u",
|
||||
fragment_type_or_err);
|
||||
std::string reason =
|
||||
"unknown record type " + std::to_string(fragment_type_or_err);
|
||||
ReportCorruption(
|
||||
fragment.size() + (in_fragmented_record_ ? fragments_.size() : 0),
|
||||
buf);
|
||||
reason.c_str());
|
||||
in_fragmented_record_ = false;
|
||||
fragments_.clear();
|
||||
break;
|
||||
|
||||
+165
-64
@@ -79,7 +79,6 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
SequenceNumber latest_seq, uint32_t column_family_id)
|
||||
: comparator_(cmp),
|
||||
moptions_(ioptions, mutable_cf_options),
|
||||
refs_(0),
|
||||
kArenaBlockSize(Arena::OptimizeBlockSize(moptions_.arena_block_size)),
|
||||
mem_tracker_(write_buffer_manager),
|
||||
arena_(moptions_.arena_block_size,
|
||||
@@ -101,13 +100,9 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
num_deletes_(0),
|
||||
num_range_deletes_(0),
|
||||
write_buffer_size_(mutable_cf_options.write_buffer_size),
|
||||
flush_in_progress_(false),
|
||||
flush_completed_(false),
|
||||
file_number_(0),
|
||||
first_seqno_(0),
|
||||
earliest_seqno_(latest_seq),
|
||||
creation_seq_(latest_seq),
|
||||
mem_next_logfile_number_(0),
|
||||
min_prep_log_referenced_(0),
|
||||
locks_(moptions_.inplace_update_support
|
||||
? moptions_.inplace_update_num_locks
|
||||
@@ -118,7 +113,6 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
insert_with_hint_prefix_extractor_(
|
||||
ioptions.memtable_insert_with_hint_prefix_extractor.get()),
|
||||
oldest_key_time_(std::numeric_limits<uint64_t>::max()),
|
||||
atomic_flush_seqno_(kMaxSequenceNumber),
|
||||
approximate_memory_usage_(0),
|
||||
memtable_max_range_deletions_(
|
||||
mutable_cf_options.memtable_max_range_deletions) {
|
||||
@@ -605,7 +599,7 @@ class MemTableIterator : public InternalIterator {
|
||||
InternalIterator* MemTable::NewIterator(
|
||||
const ReadOptions& read_options,
|
||||
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping, Arena* arena,
|
||||
const SliceTransform* prefix_extractor) {
|
||||
const SliceTransform* prefix_extractor, bool /*for_flush*/) {
|
||||
assert(arena != nullptr);
|
||||
auto mem = arena->AllocateAligned(sizeof(MemTableIterator));
|
||||
return new (mem)
|
||||
@@ -613,6 +607,135 @@ InternalIterator* MemTable::NewIterator(
|
||||
seqno_to_time_mapping, arena, prefix_extractor);
|
||||
}
|
||||
|
||||
// An iterator wrapper that wraps a MemTableIterator and logically strips each
|
||||
// key's user-defined timestamp.
|
||||
class TimestampStrippingIterator : public InternalIterator {
|
||||
public:
|
||||
TimestampStrippingIterator(
|
||||
MemTableIterator::Kind kind, const MemTable& memtable,
|
||||
const ReadOptions& read_options,
|
||||
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping, Arena* arena,
|
||||
const SliceTransform* cf_prefix_extractor, size_t ts_sz)
|
||||
: arena_mode_(arena != nullptr), kind_(kind), ts_sz_(ts_sz) {
|
||||
assert(ts_sz_ != 0);
|
||||
void* mem = arena ? arena->AllocateAligned(sizeof(MemTableIterator)) :
|
||||
operator new(sizeof(MemTableIterator));
|
||||
iter_ = new (mem)
|
||||
MemTableIterator(kind, memtable, read_options, seqno_to_time_mapping,
|
||||
arena, cf_prefix_extractor);
|
||||
}
|
||||
|
||||
// No copying allowed
|
||||
TimestampStrippingIterator(const TimestampStrippingIterator&) = delete;
|
||||
void operator=(const TimestampStrippingIterator&) = delete;
|
||||
|
||||
~TimestampStrippingIterator() override {
|
||||
if (arena_mode_) {
|
||||
iter_->~MemTableIterator();
|
||||
} else {
|
||||
delete iter_;
|
||||
}
|
||||
}
|
||||
|
||||
void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) override {
|
||||
iter_->SetPinnedItersMgr(pinned_iters_mgr);
|
||||
}
|
||||
|
||||
bool Valid() const override { return iter_->Valid(); }
|
||||
void Seek(const Slice& k) override {
|
||||
iter_->Seek(k);
|
||||
UpdateKeyAndValueBuffer();
|
||||
}
|
||||
void SeekForPrev(const Slice& k) override {
|
||||
iter_->SeekForPrev(k);
|
||||
UpdateKeyAndValueBuffer();
|
||||
}
|
||||
void SeekToFirst() override {
|
||||
iter_->SeekToFirst();
|
||||
UpdateKeyAndValueBuffer();
|
||||
}
|
||||
void SeekToLast() override {
|
||||
iter_->SeekToLast();
|
||||
UpdateKeyAndValueBuffer();
|
||||
}
|
||||
void Next() override {
|
||||
iter_->Next();
|
||||
UpdateKeyAndValueBuffer();
|
||||
}
|
||||
bool NextAndGetResult(IterateResult* result) override {
|
||||
iter_->Next();
|
||||
UpdateKeyAndValueBuffer();
|
||||
bool is_valid = Valid();
|
||||
if (is_valid) {
|
||||
result->key = key();
|
||||
result->bound_check_result = IterBoundCheck::kUnknown;
|
||||
result->value_prepared = true;
|
||||
}
|
||||
return is_valid;
|
||||
}
|
||||
void Prev() override {
|
||||
iter_->Prev();
|
||||
UpdateKeyAndValueBuffer();
|
||||
}
|
||||
Slice key() const override {
|
||||
assert(Valid());
|
||||
return key_buf_;
|
||||
}
|
||||
|
||||
uint64_t write_unix_time() const override { return iter_->write_unix_time(); }
|
||||
Slice value() const override {
|
||||
if (kind_ == MemTableIterator::Kind::kRangeDelEntries) {
|
||||
return value_buf_;
|
||||
}
|
||||
return iter_->value();
|
||||
}
|
||||
Status status() const override { return iter_->status(); }
|
||||
bool IsKeyPinned() const override {
|
||||
// Key is only in a buffer that is updated in each iteration.
|
||||
return false;
|
||||
}
|
||||
bool IsValuePinned() const override {
|
||||
if (kind_ == MemTableIterator::Kind::kRangeDelEntries) {
|
||||
return false;
|
||||
}
|
||||
return iter_->IsValuePinned();
|
||||
}
|
||||
|
||||
private:
|
||||
void UpdateKeyAndValueBuffer() {
|
||||
key_buf_.clear();
|
||||
if (kind_ == MemTableIterator::Kind::kRangeDelEntries) {
|
||||
value_buf_.clear();
|
||||
}
|
||||
if (!Valid()) {
|
||||
return;
|
||||
}
|
||||
Slice original_key = iter_->key();
|
||||
ReplaceInternalKeyWithMinTimestamp(&key_buf_, original_key, ts_sz_);
|
||||
if (kind_ == MemTableIterator::Kind::kRangeDelEntries) {
|
||||
Slice original_value = iter_->value();
|
||||
AppendUserKeyWithMinTimestamp(&value_buf_, original_value, ts_sz_);
|
||||
}
|
||||
}
|
||||
bool arena_mode_;
|
||||
MemTableIterator::Kind kind_;
|
||||
size_t ts_sz_;
|
||||
MemTableIterator* iter_;
|
||||
std::string key_buf_;
|
||||
std::string value_buf_;
|
||||
};
|
||||
|
||||
InternalIterator* MemTable::NewTimestampStrippingIterator(
|
||||
const ReadOptions& read_options,
|
||||
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping, Arena* arena,
|
||||
const SliceTransform* prefix_extractor, size_t ts_sz) {
|
||||
assert(arena != nullptr);
|
||||
auto mem = arena->AllocateAligned(sizeof(TimestampStrippingIterator));
|
||||
return new (mem) TimestampStrippingIterator(
|
||||
MemTableIterator::kPointEntries, *this, read_options,
|
||||
seqno_to_time_mapping, arena, prefix_extractor, ts_sz);
|
||||
}
|
||||
|
||||
FragmentedRangeTombstoneIterator* MemTable::NewRangeTombstoneIterator(
|
||||
const ReadOptions& read_options, SequenceNumber read_seq,
|
||||
bool immutable_memtable) {
|
||||
@@ -624,6 +747,30 @@ FragmentedRangeTombstoneIterator* MemTable::NewRangeTombstoneIterator(
|
||||
immutable_memtable);
|
||||
}
|
||||
|
||||
FragmentedRangeTombstoneIterator*
|
||||
MemTable::NewTimestampStrippingRangeTombstoneIterator(
|
||||
const ReadOptions& read_options, SequenceNumber read_seq, size_t ts_sz) {
|
||||
if (read_options.ignore_range_deletions ||
|
||||
is_range_del_table_empty_.load(std::memory_order_relaxed)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!timestamp_stripping_fragmented_range_tombstone_list_) {
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
auto* unfragmented_iter = new TimestampStrippingIterator(
|
||||
MemTableIterator::kRangeDelEntries, *this, ReadOptions(),
|
||||
/*seqno_to_time_mapping*/ nullptr, /* arena */ nullptr,
|
||||
/* prefix_extractor */ nullptr, ts_sz);
|
||||
|
||||
timestamp_stripping_fragmented_range_tombstone_list_ =
|
||||
std::make_unique<FragmentedRangeTombstoneList>(
|
||||
std::unique_ptr<InternalIterator>(unfragmented_iter),
|
||||
comparator_.comparator);
|
||||
}
|
||||
return new FragmentedRangeTombstoneIterator(
|
||||
timestamp_stripping_fragmented_range_tombstone_list_.get(),
|
||||
comparator_.comparator, read_seq, read_options.timestamp);
|
||||
}
|
||||
|
||||
FragmentedRangeTombstoneIterator* MemTable::NewRangeTombstoneIteratorInternal(
|
||||
const ReadOptions& read_options, SequenceNumber read_seq,
|
||||
bool immutable_memtable) {
|
||||
@@ -679,8 +826,8 @@ port::RWMutex* MemTable::GetLock(const Slice& key) {
|
||||
return &locks_[GetSliceRangedNPHash(key, locks_.size())];
|
||||
}
|
||||
|
||||
MemTable::MemTableStats MemTable::ApproximateStats(const Slice& start_ikey,
|
||||
const Slice& end_ikey) {
|
||||
ReadOnlyMemTable::MemTableStats MemTable::ApproximateStats(
|
||||
const Slice& start_ikey, const Slice& end_ikey) {
|
||||
uint64_t entry_count = table_->ApproximateNumEntries(start_ikey, end_ikey);
|
||||
entry_count += range_del_table_->ApproximateNumEntries(start_ikey, end_ikey);
|
||||
if (entry_count == 0) {
|
||||
@@ -1104,47 +1251,16 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
case kTypeValue:
|
||||
case kTypeValuePreferredSeqno: {
|
||||
Slice v = GetLengthPrefixedSlice(key_ptr + key_length);
|
||||
|
||||
if (type == kTypeValuePreferredSeqno) {
|
||||
v = ParsePackedValueForValue(v);
|
||||
}
|
||||
|
||||
*(s->status) = Status::OK();
|
||||
|
||||
if (!s->do_merge) {
|
||||
// Preserve the value with the goal of returning it as part of
|
||||
// raw merge operands to the user
|
||||
// TODO(yanqin) update MergeContext so that timestamps information
|
||||
// can also be retained.
|
||||
|
||||
merge_context->PushOperand(
|
||||
v, s->inplace_update_support == false /* operand_pinned */);
|
||||
} else if (*(s->merge_in_progress)) {
|
||||
assert(s->do_merge);
|
||||
|
||||
if (s->value || s->columns) {
|
||||
// `op_failure_scope` (an output parameter) is not provided (set to
|
||||
// nullptr) since a failure must be propagated regardless of its
|
||||
// value.
|
||||
*(s->status) = MergeHelper::TimedFullMerge(
|
||||
merge_operator, s->key->user_key(),
|
||||
MergeHelper::kPlainBaseValue, v, merge_context->GetOperands(),
|
||||
s->logger, s->statistics, s->clock,
|
||||
/* update_num_ops_stats */ true, /* op_failure_scope */ nullptr,
|
||||
s->value, s->columns);
|
||||
}
|
||||
} else if (s->value) {
|
||||
s->value->assign(v.data(), v.size());
|
||||
} else if (s->columns) {
|
||||
s->columns->SetPlainValue(v);
|
||||
}
|
||||
|
||||
ReadOnlyMemTable::HandleTypeValue(
|
||||
s->key->user_key(), v, s->inplace_update_support == false,
|
||||
s->do_merge, *(s->merge_in_progress), merge_context,
|
||||
s->merge_operator, s->clock, s->statistics, s->logger, s->status,
|
||||
s->value, s->columns, s->is_blob_index);
|
||||
*(s->found_final_value) = true;
|
||||
|
||||
if (s->is_blob_index != nullptr) {
|
||||
*(s->is_blob_index) = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
case kTypeWideColumnEntity: {
|
||||
@@ -1201,25 +1317,10 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
case kTypeDeletionWithTimestamp:
|
||||
case kTypeSingleDeletion:
|
||||
case kTypeRangeDeletion: {
|
||||
if (*(s->merge_in_progress)) {
|
||||
if (s->value || s->columns) {
|
||||
// `op_failure_scope` (an output parameter) is not provided (set to
|
||||
// nullptr) since a failure must be propagated regardless of its
|
||||
// value.
|
||||
*(s->status) = MergeHelper::TimedFullMerge(
|
||||
merge_operator, s->key->user_key(), MergeHelper::kNoBaseValue,
|
||||
merge_context->GetOperands(), s->logger, s->statistics,
|
||||
s->clock, /* update_num_ops_stats */ true,
|
||||
/* op_failure_scope */ nullptr, s->value, s->columns);
|
||||
} else {
|
||||
// We have found a final value (a base deletion) and have newer
|
||||
// merge operands that we do not intend to merge. Nothing remains
|
||||
// to be done so assign status to OK.
|
||||
*(s->status) = Status::OK();
|
||||
}
|
||||
} else {
|
||||
*(s->status) = Status::NotFound();
|
||||
}
|
||||
ReadOnlyMemTable::HandleTypeDeletion(
|
||||
s->key->user_key(), *(s->merge_in_progress), s->merge_context,
|
||||
s->merge_operator, s->clock, s->statistics, s->logger, s->status,
|
||||
s->value, s->columns);
|
||||
*(s->found_final_value) = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
+422
-243
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "db/kv_checksum.h"
|
||||
#include "db/merge_helper.h"
|
||||
#include "db/range_tombstone_fragmenter.h"
|
||||
#include "db/read_callback.h"
|
||||
#include "db/seqno_to_time_mapping.h"
|
||||
@@ -76,88 +77,48 @@ struct MemTablePostProcessInfo {
|
||||
};
|
||||
|
||||
using MultiGetRange = MultiGetContext::Range;
|
||||
// Note: Many of the methods in this class have comments indicating that
|
||||
|
||||
// For each CF, rocksdb maintains an active memtable that accept writes,
|
||||
// and zero or more sealed memtables that we call immutable memtables.
|
||||
// This interface contains all methods required for immutable memtables.
|
||||
// MemTable class inherit from `ReadOnlyMemTable` and implements additional
|
||||
// methods required for active memtables.
|
||||
// Immutable memtable list (MemTableList) maintains a list of ReadOnlyMemTable
|
||||
// objects. This interface enables feature like direct ingestion of an
|
||||
// immutable memtable with custom implementation, bypassing memtable writes.
|
||||
//
|
||||
// Note: Many of the methods in this class have comments indicating that
|
||||
// external synchronization is required as these methods are not thread-safe.
|
||||
// It is up to higher layers of code to decide how to prevent concurrent
|
||||
// invocation of these methods. This is usually done by acquiring either
|
||||
// invocation of these methods. This is usually done by acquiring either
|
||||
// the db mutex or the single writer thread.
|
||||
//
|
||||
// Some of these methods are documented to only require external
|
||||
// synchronization if this memtable is immutable. Calling MarkImmutable() is
|
||||
// synchronization if this memtable is immutable. Calling MarkImmutable() is
|
||||
// not sufficient to guarantee immutability. It is up to higher layers of
|
||||
// code to determine if this MemTable can still be modified by other threads.
|
||||
// Eg: The Superversion stores a pointer to the current MemTable (that can
|
||||
// be modified) and a separate list of the MemTables that can no longer be
|
||||
// written to (aka the 'immutable memtables').
|
||||
class MemTable {
|
||||
//
|
||||
// MemTables are reference counted. The initial reference count
|
||||
// is zero and the caller must call Ref() at least once.
|
||||
class ReadOnlyMemTable {
|
||||
public:
|
||||
struct KeyComparator : public MemTableRep::KeyComparator {
|
||||
const InternalKeyComparator comparator;
|
||||
explicit KeyComparator(const InternalKeyComparator& c) : comparator(c) {}
|
||||
int operator()(const char* prefix_len_key1,
|
||||
const char* prefix_len_key2) const override;
|
||||
int operator()(const char* prefix_len_key,
|
||||
const DecodedType& key) const override;
|
||||
};
|
||||
|
||||
// MemTables are reference counted. The initial reference count
|
||||
// is zero and the caller must call Ref() at least once.
|
||||
//
|
||||
// earliest_seq should be the current SequenceNumber in the db such that any
|
||||
// key inserted into this memtable will have an equal or larger seq number.
|
||||
// (When a db is first created, the earliest sequence number will be 0).
|
||||
// If the earliest sequence number is not known, kMaxSequenceNumber may be
|
||||
// used, but this may prevent some transactions from succeeding until the
|
||||
// first key is inserted into the memtable.
|
||||
explicit MemTable(const InternalKeyComparator& comparator,
|
||||
const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
SequenceNumber earliest_seq, uint32_t column_family_id);
|
||||
// No copying allowed
|
||||
MemTable(const MemTable&) = delete;
|
||||
MemTable& operator=(const MemTable&) = delete;
|
||||
|
||||
// Do not delete this MemTable unless Unref() indicates it not in use.
|
||||
~MemTable();
|
||||
virtual ~ReadOnlyMemTable() = default;
|
||||
|
||||
// Increase reference count.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable.
|
||||
void Ref() { ++refs_; }
|
||||
|
||||
// Drop reference count.
|
||||
// If the refcount goes to zero return this memtable, otherwise return null.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable.
|
||||
MemTable* Unref() {
|
||||
--refs_;
|
||||
assert(refs_ >= 0);
|
||||
if (refs_ <= 0) {
|
||||
return this;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
virtual const char* Name() const = 0;
|
||||
|
||||
// Returns an estimate of the number of bytes of data in use by this
|
||||
// data structure.
|
||||
//
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable (unless this Memtable is immutable).
|
||||
size_t ApproximateMemoryUsage();
|
||||
|
||||
// As a cheap version of `ApproximateMemoryUsage()`, this function doesn't
|
||||
// require external synchronization. The value may be less accurate though
|
||||
size_t ApproximateMemoryUsageFast() const {
|
||||
return approximate_memory_usage_.load(std::memory_order_relaxed);
|
||||
}
|
||||
virtual size_t ApproximateMemoryUsage() = 0;
|
||||
|
||||
// used by MemTableListVersion::MemoryAllocatedBytesExcludingLast
|
||||
size_t MemoryAllocatedBytes() const {
|
||||
return table_->ApproximateMemoryUsage() +
|
||||
range_del_table_->ApproximateMemoryUsage() +
|
||||
arena_.MemoryAllocatedBytes();
|
||||
}
|
||||
virtual size_t MemoryAllocatedBytes() const = 0;
|
||||
|
||||
// Returns a vector of unique random memtable entries of size 'sample_size'.
|
||||
//
|
||||
@@ -172,27 +133,8 @@ class MemTable {
|
||||
// REQUIRES: SkipList memtable representation. This function is not
|
||||
// implemented for any other type of memtable representation (vectorrep,
|
||||
// hashskiplist,...).
|
||||
void UniqueRandomSample(const uint64_t& target_sample_size,
|
||||
std::unordered_set<const char*>* entries) {
|
||||
// TODO(bjlemaire): at the moment, only supported by skiplistrep.
|
||||
// Extend it to all other memtable representations.
|
||||
table_->UniqueRandomSample(num_entries(), target_sample_size, entries);
|
||||
}
|
||||
|
||||
// This method heuristically determines if the memtable should continue to
|
||||
// host more data.
|
||||
bool ShouldScheduleFlush() const {
|
||||
return flush_state_.load(std::memory_order_relaxed) == FLUSH_REQUESTED;
|
||||
}
|
||||
|
||||
// Returns true if a flush should be scheduled and the caller should
|
||||
// be the one to schedule it
|
||||
bool MarkFlushScheduled() {
|
||||
auto before = FLUSH_REQUESTED;
|
||||
return flush_state_.compare_exchange_strong(before, FLUSH_SCHEDULED,
|
||||
std::memory_order_relaxed,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
virtual void UniqueRandomSample(const uint64_t& target_sample_size,
|
||||
std::unordered_set<const char*>* entries) = 0;
|
||||
|
||||
// Return an iterator that yields the contents of the memtable.
|
||||
//
|
||||
@@ -208,10 +150,18 @@ class MemTable {
|
||||
// those allocated in arena.
|
||||
// seqno_to_time_mapping: it's used to support return write unix time for the
|
||||
// data, currently only needed for iterators serving user reads.
|
||||
InternalIterator* NewIterator(
|
||||
virtual InternalIterator* NewIterator(
|
||||
const ReadOptions& read_options,
|
||||
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping, Arena* arena,
|
||||
const SliceTransform* prefix_extractor);
|
||||
const SliceTransform* prefix_extractor, bool for_flush) = 0;
|
||||
|
||||
// Returns an iterator that wraps a MemTableIterator and logically strips the
|
||||
// user-defined timestamp of each key. This API is only used by flush when
|
||||
// user-defined timestamps in MemTable only feature is enabled.
|
||||
virtual InternalIterator* NewTimestampStrippingIterator(
|
||||
const ReadOptions& read_options,
|
||||
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping, Arena* arena,
|
||||
const SliceTransform* prefix_extractor, size_t ts_sz) = 0;
|
||||
|
||||
// Returns an iterator that yields the range tombstones of the memtable.
|
||||
// The caller must ensure that the underlying MemTable remains live
|
||||
@@ -223,31 +173,23 @@ class MemTable {
|
||||
// is constructed when a memtable becomes immutable. Setting the flag to false
|
||||
// will always yield correct result, but may incur performance penalty as it
|
||||
// always creates a new fragmented range tombstone list.
|
||||
FragmentedRangeTombstoneIterator* NewRangeTombstoneIterator(
|
||||
virtual FragmentedRangeTombstoneIterator* NewRangeTombstoneIterator(
|
||||
const ReadOptions& read_options, SequenceNumber read_seq,
|
||||
bool immutable_memtable);
|
||||
bool immutable_memtable) = 0;
|
||||
|
||||
Status VerifyEncodedEntry(Slice encoded,
|
||||
const ProtectionInfoKVOS64& kv_prot_info);
|
||||
|
||||
// Add an entry into memtable that maps key to value at the
|
||||
// specified sequence number and with the specified type.
|
||||
// Typically value will be empty if type==kTypeDeletion.
|
||||
//
|
||||
// REQUIRES: if allow_concurrent = false, external synchronization to prevent
|
||||
// simultaneous operations on the same MemTable.
|
||||
//
|
||||
// Returns `Status::TryAgain` if the `seq`, `key` combination already exists
|
||||
// in the memtable and `MemTableRepFactory::CanHandleDuplicatedKey()` is true.
|
||||
// The next attempt should try a larger value for `seq`.
|
||||
Status Add(SequenceNumber seq, ValueType type, const Slice& key,
|
||||
const Slice& value, const ProtectionInfoKVOS64* kv_prot_info,
|
||||
bool allow_concurrent = false,
|
||||
MemTablePostProcessInfo* post_process_info = nullptr,
|
||||
void** hint = nullptr);
|
||||
// Returns an iterator that yields the range tombstones of the memtable and
|
||||
// logically strips the user-defined timestamp of each key (including start
|
||||
// key, and end key). This API is only used by flush when user-defined
|
||||
// timestamps in MemTable only feature is enabled.
|
||||
virtual FragmentedRangeTombstoneIterator*
|
||||
NewTimestampStrippingRangeTombstoneIterator(const ReadOptions& read_options,
|
||||
SequenceNumber read_seq,
|
||||
size_t ts_sz) = 0;
|
||||
|
||||
// Used to Get value associated with key or Get Merge Operands associated
|
||||
// with key.
|
||||
// Keys are considered if they are no larger than the parameter `key` in
|
||||
// the order defined by comparator and share the save user key with `key`.
|
||||
// If do_merge = true the default behavior which is Get value for key is
|
||||
// executed. Expected behavior is described right below.
|
||||
// If memtable contains a value for key, store it in *value and return true.
|
||||
@@ -276,14 +218,13 @@ class MemTable {
|
||||
// @param immutable_memtable Whether this memtable is immutable. Used
|
||||
// internally by NewRangeTombstoneIterator(). See comment above
|
||||
// NewRangeTombstoneIterator() for more detail.
|
||||
bool Get(const LookupKey& key, std::string* value,
|
||||
PinnableWideColumns* columns, std::string* timestamp, Status* s,
|
||||
MergeContext* merge_context,
|
||||
SequenceNumber* max_covering_tombstone_seq, SequenceNumber* seq,
|
||||
const ReadOptions& read_opts, bool immutable_memtable,
|
||||
ReadCallback* callback = nullptr, bool* is_blob_index = nullptr,
|
||||
bool do_merge = true);
|
||||
|
||||
virtual bool Get(const LookupKey& key, std::string* value,
|
||||
PinnableWideColumns* columns, std::string* timestamp,
|
||||
Status* s, MergeContext* merge_context,
|
||||
SequenceNumber* max_covering_tombstone_seq,
|
||||
SequenceNumber* seq, const ReadOptions& read_opts,
|
||||
bool immutable_memtable, ReadCallback* callback = nullptr,
|
||||
bool* is_blob_index = nullptr, bool do_merge = true) = 0;
|
||||
bool Get(const LookupKey& key, std::string* value,
|
||||
PinnableWideColumns* columns, std::string* timestamp, Status* s,
|
||||
MergeContext* merge_context,
|
||||
@@ -300,8 +241,351 @@ class MemTable {
|
||||
// @param immutable_memtable Whether this memtable is immutable. Used
|
||||
// internally by NewRangeTombstoneIterator(). See comment above
|
||||
// NewRangeTombstoneIterator() for more detail.
|
||||
virtual void MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
ReadCallback* callback, bool immutable_memtable) = 0;
|
||||
|
||||
// Get total number of entries in the mem table.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable (unless this Memtable is immutable).
|
||||
virtual uint64_t NumEntries() const = 0;
|
||||
|
||||
// Get total number of point deletes in the mem table.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable (unless this Memtable is immutable).
|
||||
virtual uint64_t NumDeletion() const = 0;
|
||||
|
||||
// Get total number of range deletions in the mem table.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable (unless this Memtable is immutable).
|
||||
virtual uint64_t NumRangeDeletion() const = 0;
|
||||
|
||||
virtual uint64_t GetDataSize() const = 0;
|
||||
|
||||
// Returns the sequence number of the first element that was inserted
|
||||
// into the memtable.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable (unless this Memtable is immutable).
|
||||
virtual SequenceNumber GetFirstSequenceNumber() = 0;
|
||||
|
||||
// Returns if there is no entry inserted to the mem table.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable (unless this Memtable is immutable).
|
||||
virtual bool IsEmpty() const = 0;
|
||||
|
||||
// Returns the sequence number that is guaranteed to be smaller than or equal
|
||||
// to the sequence number of any key that could be inserted into this
|
||||
// memtable. It can then be assumed that any write with a larger(or equal)
|
||||
// sequence number will be present in this memtable or a later memtable.
|
||||
//
|
||||
// If the earliest sequence number could not be determined,
|
||||
// kMaxSequenceNumber will be returned.
|
||||
virtual SequenceNumber GetEarliestSequenceNumber() = 0;
|
||||
|
||||
virtual uint64_t GetMinLogContainingPrepSection() = 0;
|
||||
|
||||
// Notify the underlying storage that no more items will be added.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable.
|
||||
// After MarkImmutable() is called, you should not attempt to
|
||||
// write anything to this MemTable(). (Ie. do not call Add() or Update()).
|
||||
virtual void MarkImmutable() = 0;
|
||||
|
||||
// Notify the underlying storage that all data it contained has been
|
||||
// persisted.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable.
|
||||
virtual void MarkFlushed() = 0;
|
||||
|
||||
struct MemTableStats {
|
||||
uint64_t size;
|
||||
uint64_t count;
|
||||
};
|
||||
virtual MemTableStats ApproximateStats(const Slice& start_ikey,
|
||||
const Slice& end_ikey) = 0;
|
||||
|
||||
virtual const InternalKeyComparator& GetInternalKeyComparator() const = 0;
|
||||
|
||||
virtual uint64_t ApproximateOldestKeyTime() const = 0;
|
||||
|
||||
// Returns whether a fragmented range tombstone list is already constructed
|
||||
// for this memtable. It should be constructed right before a memtable is
|
||||
// added to an immutable memtable list. Note that if a memtable does not have
|
||||
// any range tombstone, then no range tombstone list will ever be constructed
|
||||
// and true is returned in that case.
|
||||
virtual bool IsFragmentedRangeTombstonesConstructed() const = 0;
|
||||
|
||||
// Get the newest user-defined timestamp contained in this MemTable. Check
|
||||
// `newest_udt_` for what newer means. This method should only be invoked for
|
||||
// an MemTable that has enabled user-defined timestamp feature and set
|
||||
// `persist_user_defined_timestamps` to false. The tracked newest UDT will be
|
||||
// used by flush job in the background to help check the MemTable's
|
||||
// eligibility for Flush.
|
||||
virtual const Slice& GetNewestUDT() const = 0;
|
||||
|
||||
// Increase reference count.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable.
|
||||
void Ref() { ++refs_; }
|
||||
|
||||
// Drop reference count.
|
||||
// If the refcount goes to zero return this memtable, otherwise return null.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable.
|
||||
ReadOnlyMemTable* Unref() {
|
||||
--refs_;
|
||||
assert(refs_ >= 0);
|
||||
if (refs_ <= 0) {
|
||||
return this;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Returns the edits area that is needed for flushing the memtable
|
||||
VersionEdit* GetEdits() { return &edit_; }
|
||||
|
||||
// Returns the next active logfile number when this memtable is about to
|
||||
// be flushed to storage
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable.
|
||||
uint64_t GetNextLogNumber() const { return mem_next_logfile_number_; }
|
||||
|
||||
// Sets the next active logfile number when this memtable is about to
|
||||
// be flushed to storage
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable.
|
||||
void SetNextLogNumber(uint64_t num) { mem_next_logfile_number_ = num; }
|
||||
|
||||
// REQUIRES: db_mutex held.
|
||||
void SetID(uint64_t id) { id_ = id; }
|
||||
|
||||
uint64_t GetID() const { return id_; }
|
||||
|
||||
void SetFlushCompleted(bool completed) { flush_completed_ = completed; }
|
||||
|
||||
uint64_t GetFileNumber() const { return file_number_; }
|
||||
|
||||
void SetFileNumber(uint64_t file_num) { file_number_ = file_num; }
|
||||
|
||||
void SetFlushInProgress(bool in_progress) {
|
||||
flush_in_progress_ = in_progress;
|
||||
}
|
||||
|
||||
void SetFlushJobInfo(std::unique_ptr<FlushJobInfo>&& info) {
|
||||
flush_job_info_ = std::move(info);
|
||||
}
|
||||
|
||||
std::unique_ptr<FlushJobInfo> ReleaseFlushJobInfo() {
|
||||
return std::move(flush_job_info_);
|
||||
}
|
||||
|
||||
static void HandleTypeValue(
|
||||
const Slice& lookup_user_key, const Slice& value, bool value_pinned,
|
||||
bool do_merge, bool merge_in_progress, MergeContext* merge_context,
|
||||
const MergeOperator* merge_operator, SystemClock* clock,
|
||||
Statistics* statistics, Logger* info_log, Status* s,
|
||||
std::string* out_value, PinnableWideColumns* out_columns,
|
||||
bool* is_blob_index) {
|
||||
*s = Status::OK();
|
||||
|
||||
if (!do_merge) {
|
||||
// Preserve the value with the goal of returning it as part of
|
||||
// raw merge operands to the user
|
||||
// TODO(yanqin) update MergeContext so that timestamps information
|
||||
// can also be retained.
|
||||
merge_context->PushOperand(value, value_pinned);
|
||||
} else if (merge_in_progress) {
|
||||
assert(do_merge);
|
||||
// `op_failure_scope` (an output parameter) is not provided (set to
|
||||
// nullptr) since a failure must be propagated regardless of its
|
||||
// value.
|
||||
if (out_value || out_columns) {
|
||||
*s = MergeHelper::TimedFullMerge(
|
||||
merge_operator, lookup_user_key, MergeHelper::kPlainBaseValue,
|
||||
value, merge_context->GetOperands(), info_log, statistics, clock,
|
||||
/* update_num_ops_stats */ true,
|
||||
/* op_failure_scope */ nullptr, out_value, out_columns);
|
||||
}
|
||||
} else if (out_value) {
|
||||
out_value->assign(value.data(), value.size());
|
||||
} else if (out_columns) {
|
||||
out_columns->SetPlainValue(value);
|
||||
}
|
||||
|
||||
if (is_blob_index) {
|
||||
*is_blob_index = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void HandleTypeDeletion(
|
||||
const Slice& lookup_user_key, bool merge_in_progress,
|
||||
MergeContext* merge_context, const MergeOperator* merge_operator,
|
||||
SystemClock* clock, Statistics* statistics, Logger* logger, Status* s,
|
||||
std::string* out_value, PinnableWideColumns* out_columns) {
|
||||
if (merge_in_progress) {
|
||||
if (out_value || out_columns) {
|
||||
// `op_failure_scope` (an output parameter) is not provided (set to
|
||||
// nullptr) since a failure must be propagated regardless of its
|
||||
// value.
|
||||
*s = MergeHelper::TimedFullMerge(
|
||||
merge_operator, lookup_user_key, MergeHelper::kNoBaseValue,
|
||||
merge_context->GetOperands(), logger, statistics, clock,
|
||||
/* update_num_ops_stats */ true,
|
||||
/* op_failure_scope */ nullptr, out_value, out_columns);
|
||||
} else {
|
||||
// We have found a final value (a base deletion) and have newer
|
||||
// merge operands that we do not intend to merge. Nothing remains
|
||||
// to be done so assign status to OK.
|
||||
*s = Status::OK();
|
||||
}
|
||||
} else {
|
||||
*s = Status::NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
friend class MemTableList;
|
||||
|
||||
int refs_{0};
|
||||
|
||||
// These are used to manage memtable flushes to storage
|
||||
bool flush_in_progress_{false}; // started the flush
|
||||
bool flush_completed_{false}; // finished the flush
|
||||
uint64_t file_number_{0};
|
||||
|
||||
// The updates to be applied to the transaction log when this
|
||||
// memtable is flushed to storage.
|
||||
VersionEdit edit_;
|
||||
|
||||
// The log files earlier than this number can be deleted.
|
||||
uint64_t mem_next_logfile_number_{0};
|
||||
|
||||
// Memtable id to track flush.
|
||||
uint64_t id_ = 0;
|
||||
|
||||
// Sequence number of the atomic flush that is responsible for this memtable.
|
||||
// The sequence number of atomic flush is a seq, such that no writes with
|
||||
// sequence numbers greater than or equal to seq are flushed, while all
|
||||
// writes with sequence number smaller than seq are flushed.
|
||||
SequenceNumber atomic_flush_seqno_{kMaxSequenceNumber};
|
||||
|
||||
// Flush job info of the current memtable.
|
||||
std::unique_ptr<FlushJobInfo> flush_job_info_;
|
||||
};
|
||||
|
||||
class MemTable final : public ReadOnlyMemTable {
|
||||
public:
|
||||
struct KeyComparator final : public MemTableRep::KeyComparator {
|
||||
const InternalKeyComparator comparator;
|
||||
explicit KeyComparator(const InternalKeyComparator& c) : comparator(c) {}
|
||||
int operator()(const char* prefix_len_key1,
|
||||
const char* prefix_len_key2) const override;
|
||||
int operator()(const char* prefix_len_key,
|
||||
const DecodedType& key) const override;
|
||||
};
|
||||
|
||||
// earliest_seq should be the current SequenceNumber in the db such that any
|
||||
// key inserted into this memtable will have an equal or larger seq number.
|
||||
// (When a db is first created, the earliest sequence number will be 0).
|
||||
// If the earliest sequence number is not known, kMaxSequenceNumber may be
|
||||
// used, but this may prevent some transactions from succeeding until the
|
||||
// first key is inserted into the memtable.
|
||||
explicit MemTable(const InternalKeyComparator& comparator,
|
||||
const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
SequenceNumber earliest_seq, uint32_t column_family_id);
|
||||
// No copying allowed
|
||||
MemTable(const MemTable&) = delete;
|
||||
MemTable& operator=(const MemTable&) = delete;
|
||||
|
||||
~MemTable() override;
|
||||
|
||||
const char* Name() const override { return "MemTable"; }
|
||||
|
||||
size_t ApproximateMemoryUsage() override;
|
||||
|
||||
// As a cheap version of `ApproximateMemoryUsage()`, this function doesn't
|
||||
// require external synchronization. The value may be less accurate though
|
||||
size_t ApproximateMemoryUsageFast() const {
|
||||
return approximate_memory_usage_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
size_t MemoryAllocatedBytes() const override {
|
||||
return table_->ApproximateMemoryUsage() +
|
||||
range_del_table_->ApproximateMemoryUsage() +
|
||||
arena_.MemoryAllocatedBytes();
|
||||
}
|
||||
|
||||
void UniqueRandomSample(const uint64_t& target_sample_size,
|
||||
std::unordered_set<const char*>* entries) override {
|
||||
// TODO(bjlemaire): at the moment, only supported by skiplistrep.
|
||||
// Extend it to all other memtable representations.
|
||||
table_->UniqueRandomSample(NumEntries(), target_sample_size, entries);
|
||||
}
|
||||
|
||||
// This method heuristically determines if the memtable should continue to
|
||||
// host more data.
|
||||
bool ShouldScheduleFlush() const {
|
||||
return flush_state_.load(std::memory_order_relaxed) == FLUSH_REQUESTED;
|
||||
}
|
||||
|
||||
// Returns true if a flush should be scheduled and the caller should
|
||||
// be the one to schedule it
|
||||
bool MarkFlushScheduled() {
|
||||
auto before = FLUSH_REQUESTED;
|
||||
return flush_state_.compare_exchange_strong(before, FLUSH_SCHEDULED,
|
||||
std::memory_order_relaxed,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
InternalIterator* NewIterator(
|
||||
const ReadOptions& read_options,
|
||||
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping, Arena* arena,
|
||||
const SliceTransform* prefix_extractor, bool for_flush) override;
|
||||
|
||||
InternalIterator* NewTimestampStrippingIterator(
|
||||
const ReadOptions& read_options,
|
||||
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping, Arena* arena,
|
||||
const SliceTransform* prefix_extractor, size_t ts_sz) override;
|
||||
|
||||
FragmentedRangeTombstoneIterator* NewRangeTombstoneIterator(
|
||||
const ReadOptions& read_options, SequenceNumber read_seq,
|
||||
bool immutable_memtable) override;
|
||||
|
||||
FragmentedRangeTombstoneIterator* NewTimestampStrippingRangeTombstoneIterator(
|
||||
const ReadOptions& read_options, SequenceNumber read_seq,
|
||||
size_t ts_sz) override;
|
||||
|
||||
Status VerifyEncodedEntry(Slice encoded,
|
||||
const ProtectionInfoKVOS64& kv_prot_info);
|
||||
|
||||
// Add an entry into memtable that maps key to value at the
|
||||
// specified sequence number and with the specified type.
|
||||
// Typically, value will be empty if type==kTypeDeletion.
|
||||
//
|
||||
// REQUIRES: if allow_concurrent = false, external synchronization to prevent
|
||||
// simultaneous operations on the same MemTable.
|
||||
//
|
||||
// Returns `Status::TryAgain` if the `seq`, `key` combination already exists
|
||||
// in the memtable and `MemTableRepFactory::CanHandleDuplicatedKey()` is true.
|
||||
// The next attempt should try a larger value for `seq`.
|
||||
Status Add(SequenceNumber seq, ValueType type, const Slice& key,
|
||||
const Slice& value, const ProtectionInfoKVOS64* kv_prot_info,
|
||||
bool allow_concurrent = false,
|
||||
MemTablePostProcessInfo* post_process_info = nullptr,
|
||||
void** hint = nullptr);
|
||||
|
||||
using ReadOnlyMemTable::Get;
|
||||
bool Get(const LookupKey& key, std::string* value,
|
||||
PinnableWideColumns* columns, std::string* timestamp, Status* s,
|
||||
MergeContext* merge_context,
|
||||
SequenceNumber* max_covering_tombstone_seq, SequenceNumber* seq,
|
||||
const ReadOptions& read_opts, bool immutable_memtable,
|
||||
ReadCallback* callback = nullptr, bool* is_blob_index = nullptr,
|
||||
bool do_merge = true) override;
|
||||
|
||||
void MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
ReadCallback* callback, bool immutable_memtable);
|
||||
ReadCallback* callback, bool immutable_memtable) override;
|
||||
|
||||
// If `key` exists in current memtable with type value_type and the existing
|
||||
// value is at least as large as the new value, updates it in-place. Otherwise
|
||||
@@ -357,28 +641,19 @@ class MemTable {
|
||||
UpdateFlushState();
|
||||
}
|
||||
|
||||
// Get total number of entries in the mem table.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable (unless this Memtable is immutable).
|
||||
uint64_t num_entries() const {
|
||||
uint64_t NumEntries() const override {
|
||||
return num_entries_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Get total number of deletes in the mem table.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable (unless this Memtable is immutable).
|
||||
uint64_t num_deletes() const {
|
||||
uint64_t NumDeletion() const override {
|
||||
return num_deletes_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Get total number of range deletions in the mem table.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable (unless this Memtable is immutable).
|
||||
uint64_t num_range_deletes() const {
|
||||
uint64_t NumRangeDeletion() const override {
|
||||
return num_range_deletes_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
uint64_t get_data_size() const {
|
||||
uint64_t GetDataSize() const override {
|
||||
return data_size_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
@@ -398,19 +673,9 @@ class MemTable {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the edits area that is needed for flushing the memtable
|
||||
VersionEdit* GetEdits() { return &edit_; }
|
||||
bool IsEmpty() const override { return first_seqno_ == 0; }
|
||||
|
||||
// Returns if there is no entry inserted to the mem table.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable (unless this Memtable is immutable).
|
||||
bool IsEmpty() const { return first_seqno_ == 0; }
|
||||
|
||||
// Returns the sequence number of the first element that was inserted
|
||||
// into the memtable.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable (unless this Memtable is immutable).
|
||||
SequenceNumber GetFirstSequenceNumber() {
|
||||
SequenceNumber GetFirstSequenceNumber() override {
|
||||
return first_seqno_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
@@ -422,14 +687,8 @@ class MemTable {
|
||||
return first_seqno_.store(first_seqno, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Returns the sequence number that is guaranteed to be smaller than or equal
|
||||
// to the sequence number of any key that could be inserted into this
|
||||
// memtable. It can then be assumed that any write with a larger(or equal)
|
||||
// sequence number will be present in this memtable or a later memtable.
|
||||
//
|
||||
// If the earliest sequence number could not be determined,
|
||||
// kMaxSequenceNumber will be returned.
|
||||
SequenceNumber GetEarliestSequenceNumber() {
|
||||
SequenceNumber GetEarliestSequenceNumber() override {
|
||||
// With file ingestion and empty memtable, this seqno needs to be fixed.
|
||||
return earliest_seqno_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
@@ -448,40 +707,18 @@ class MemTable {
|
||||
|
||||
void SetCreationSeq(SequenceNumber sn) { creation_seq_ = sn; }
|
||||
|
||||
// Returns the next active logfile number when this memtable is about to
|
||||
// be flushed to storage
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable.
|
||||
uint64_t GetNextLogNumber() { return mem_next_logfile_number_; }
|
||||
|
||||
// Sets the next active logfile number when this memtable is about to
|
||||
// be flushed to storage
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable.
|
||||
void SetNextLogNumber(uint64_t num) { mem_next_logfile_number_ = num; }
|
||||
|
||||
// if this memtable contains data from a committed
|
||||
// two phase transaction we must take note of the
|
||||
// log which contains that data so we can know
|
||||
// when to relese that log
|
||||
// If this memtable contains data from a committed two phase transaction we
|
||||
// must take note of the log which contains that data so we can know when
|
||||
// to release that log.
|
||||
void RefLogContainingPrepSection(uint64_t log);
|
||||
uint64_t GetMinLogContainingPrepSection();
|
||||
uint64_t GetMinLogContainingPrepSection() override;
|
||||
|
||||
// Notify the underlying storage that no more items will be added.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable.
|
||||
// After MarkImmutable() is called, you should not attempt to
|
||||
// write anything to this MemTable(). (Ie. do not call Add() or Update()).
|
||||
void MarkImmutable() {
|
||||
void MarkImmutable() override {
|
||||
table_->MarkReadOnly();
|
||||
mem_tracker_.DoneAllocating();
|
||||
}
|
||||
|
||||
// Notify the underlying storage that all data it contained has been
|
||||
// persisted.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable.
|
||||
void MarkFlushed() { table_->MarkFlushed(); }
|
||||
void MarkFlushed() override { table_->MarkFlushed(); }
|
||||
|
||||
// return true if the current MemTableRep supports merge operator.
|
||||
bool IsMergeOperatorSupported() const {
|
||||
@@ -494,18 +731,13 @@ class MemTable {
|
||||
return table_->IsSnapshotSupported() && !moptions_.inplace_update_support;
|
||||
}
|
||||
|
||||
struct MemTableStats {
|
||||
uint64_t size;
|
||||
uint64_t count;
|
||||
};
|
||||
|
||||
MemTableStats ApproximateStats(const Slice& start_ikey,
|
||||
const Slice& end_ikey);
|
||||
const Slice& end_ikey) override;
|
||||
|
||||
// Get the lock associated for the key
|
||||
port::RWMutex* GetLock(const Slice& key);
|
||||
|
||||
const InternalKeyComparator& GetInternalKeyComparator() const {
|
||||
const InternalKeyComparator& GetInternalKeyComparator() const override {
|
||||
return comparator_.comparator;
|
||||
}
|
||||
|
||||
@@ -513,33 +745,10 @@ class MemTable {
|
||||
return &moptions_;
|
||||
}
|
||||
|
||||
uint64_t ApproximateOldestKeyTime() const {
|
||||
uint64_t ApproximateOldestKeyTime() const override {
|
||||
return oldest_key_time_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// REQUIRES: db_mutex held.
|
||||
void SetID(uint64_t id) { id_ = id; }
|
||||
|
||||
uint64_t GetID() const { return id_; }
|
||||
|
||||
void SetFlushCompleted(bool completed) { flush_completed_ = completed; }
|
||||
|
||||
uint64_t GetFileNumber() const { return file_number_; }
|
||||
|
||||
void SetFileNumber(uint64_t file_num) { file_number_ = file_num; }
|
||||
|
||||
void SetFlushInProgress(bool in_progress) {
|
||||
flush_in_progress_ = in_progress;
|
||||
}
|
||||
|
||||
void SetFlushJobInfo(std::unique_ptr<FlushJobInfo>&& info) {
|
||||
flush_job_info_ = std::move(info);
|
||||
}
|
||||
|
||||
std::unique_ptr<FlushJobInfo> ReleaseFlushJobInfo() {
|
||||
return std::move(flush_job_info_);
|
||||
}
|
||||
|
||||
// Returns a heuristic flush decision
|
||||
bool ShouldFlushNow();
|
||||
|
||||
@@ -550,23 +759,12 @@ class MemTable {
|
||||
// SwitchMemtable() may fail.
|
||||
void ConstructFragmentedRangeTombstones();
|
||||
|
||||
// Returns whether a fragmented range tombstone list is already constructed
|
||||
// for this memtable. It should be constructed right before a memtable is
|
||||
// added to an immutable memtable list. Note that if a memtable does not have
|
||||
// any range tombstone, then no range tombstone list will ever be constructed
|
||||
// and true is returned in that case.
|
||||
bool IsFragmentedRangeTombstonesConstructed() const {
|
||||
bool IsFragmentedRangeTombstonesConstructed() const override {
|
||||
return fragmented_range_tombstone_list_.get() != nullptr ||
|
||||
is_range_del_table_empty_;
|
||||
}
|
||||
|
||||
// Get the newest user-defined timestamp contained in this MemTable. Check
|
||||
// `newest_udt_` for what newer means. This method should only be invoked for
|
||||
// an MemTable that has enabled user-defined timestamp feature and set
|
||||
// `persist_user_defined_timestamps` to false. The tracked newest UDT will be
|
||||
// used by flush job in the background to help check the MemTable's
|
||||
// eligibility for Flush.
|
||||
const Slice& GetNewestUDT() const;
|
||||
const Slice& GetNewestUDT() const override;
|
||||
|
||||
// Returns Corruption status if verification fails.
|
||||
static Status VerifyEntryChecksum(const char* entry,
|
||||
@@ -582,7 +780,6 @@ class MemTable {
|
||||
|
||||
KeyComparator comparator_;
|
||||
const ImmutableMemTableOptions moptions_;
|
||||
int refs_;
|
||||
const size_t kArenaBlockSize;
|
||||
AllocTracker mem_tracker_;
|
||||
ConcurrentArena arena_;
|
||||
@@ -599,15 +796,6 @@ class MemTable {
|
||||
// Dynamically changeable memtable option
|
||||
std::atomic<size_t> write_buffer_size_;
|
||||
|
||||
// These are used to manage memtable flushes to storage
|
||||
bool flush_in_progress_; // started the flush
|
||||
bool flush_completed_; // finished the flush
|
||||
uint64_t file_number_; // filled up after flush is complete
|
||||
|
||||
// The updates to be applied to the transaction log when this
|
||||
// memtable is flushed to storage.
|
||||
VersionEdit edit_;
|
||||
|
||||
// The sequence number of the kv that was inserted first
|
||||
std::atomic<SequenceNumber> first_seqno_;
|
||||
|
||||
@@ -617,9 +805,6 @@ class MemTable {
|
||||
|
||||
SequenceNumber creation_seq_;
|
||||
|
||||
// The log files earlier than this number can be deleted.
|
||||
uint64_t mem_next_logfile_number_;
|
||||
|
||||
// the earliest log containing a prepared section
|
||||
// which has been inserted into this memtable.
|
||||
std::atomic<uint64_t> min_prep_log_referenced_;
|
||||
@@ -643,15 +828,6 @@ class MemTable {
|
||||
// Timestamp of oldest key
|
||||
std::atomic<uint64_t> oldest_key_time_;
|
||||
|
||||
// Memtable id to track flush.
|
||||
uint64_t id_ = 0;
|
||||
|
||||
// Sequence number of the atomic flush that is responsible for this memtable.
|
||||
// The sequence number of atomic flush is a seq, such that no writes with
|
||||
// sequence numbers greater than or equal to seq are flushed, while all
|
||||
// writes with sequence number smaller than seq are flushed.
|
||||
SequenceNumber atomic_flush_seqno_;
|
||||
|
||||
// keep track of memory usage in table_, arena_, and range_del_table_.
|
||||
// Gets refreshed inside `ApproximateMemoryUsage()` or `ShouldFlushNow`
|
||||
std::atomic<uint64_t> approximate_memory_usage_;
|
||||
@@ -660,9 +836,6 @@ class MemTable {
|
||||
// unlimited.
|
||||
uint32_t memtable_max_range_deletions_ = 0;
|
||||
|
||||
// Flush job info of the current memtable.
|
||||
std::unique_ptr<FlushJobInfo> flush_job_info_;
|
||||
|
||||
// Size in bytes for the user-defined timestamps.
|
||||
size_t ts_sz_;
|
||||
|
||||
@@ -704,6 +877,12 @@ class MemTable {
|
||||
std::unique_ptr<FragmentedRangeTombstoneList>
|
||||
fragmented_range_tombstone_list_;
|
||||
|
||||
// The fragmented range tombstone of this memtable with all keys' user-defined
|
||||
// timestamps logically stripped. This is constructed and used by flush when
|
||||
// user-defined timestamps in memtable only feature is enabled.
|
||||
std::unique_ptr<FragmentedRangeTombstoneList>
|
||||
timestamp_stripping_fragmented_range_tombstone_list_;
|
||||
|
||||
// makes sure there is a single range tombstone writer to invalidate cache
|
||||
std::mutex range_del_mutex_;
|
||||
CoreLocalArray<std::shared_ptr<FragmentedRangeTombstoneListCache>>
|
||||
|
||||
+99
-68
@@ -31,13 +31,17 @@ class InternalKeyComparator;
|
||||
class Mutex;
|
||||
class VersionSet;
|
||||
|
||||
void MemTableListVersion::AddMemTable(MemTable* m) {
|
||||
void MemTableListVersion::AddMemTable(ReadOnlyMemTable* m) {
|
||||
if (!memlist_.empty()) {
|
||||
// ID can be equal for MemPurge
|
||||
assert(m->GetID() >= memlist_.front()->GetID());
|
||||
}
|
||||
memlist_.push_front(m);
|
||||
*parent_memtable_list_memory_usage_ += m->ApproximateMemoryUsage();
|
||||
}
|
||||
|
||||
void MemTableListVersion::UnrefMemTable(autovector<MemTable*>* to_delete,
|
||||
MemTable* m) {
|
||||
void MemTableListVersion::UnrefMemTable(
|
||||
autovector<ReadOnlyMemTable*>* to_delete, ReadOnlyMemTable* m) {
|
||||
if (m->Unref()) {
|
||||
to_delete->push_back(m);
|
||||
assert(*parent_memtable_list_memory_usage_ >= m->ApproximateMemoryUsage());
|
||||
@@ -74,7 +78,7 @@ MemTableListVersion::MemTableListVersion(
|
||||
void MemTableListVersion::Ref() { ++refs_; }
|
||||
|
||||
// called by superversion::clean()
|
||||
void MemTableListVersion::Unref(autovector<MemTable*>* to_delete) {
|
||||
void MemTableListVersion::Unref(autovector<ReadOnlyMemTable*>* to_delete) {
|
||||
assert(refs_ >= 1);
|
||||
--refs_;
|
||||
if (refs_ == 0) {
|
||||
@@ -92,14 +96,12 @@ void MemTableListVersion::Unref(autovector<MemTable*>* to_delete) {
|
||||
}
|
||||
|
||||
int MemTableList::NumNotFlushed() const {
|
||||
int size = static_cast<int>(current_->memlist_.size());
|
||||
int size = current_->NumNotFlushed();
|
||||
assert(num_flush_not_started_ <= size);
|
||||
return size;
|
||||
}
|
||||
|
||||
int MemTableList::NumFlushed() const {
|
||||
return static_cast<int>(current_->memlist_history_.size());
|
||||
}
|
||||
int MemTableList::NumFlushed() const { return current_->NumFlushed(); }
|
||||
|
||||
// Search all the memtables starting from the most recent one.
|
||||
// Return the most recent value found, if any.
|
||||
@@ -131,7 +133,7 @@ void MemTableListVersion::MultiGet(const ReadOptions& read_options,
|
||||
bool MemTableListVersion::GetMergeOperands(
|
||||
const LookupKey& key, Status* s, MergeContext* merge_context,
|
||||
SequenceNumber* max_covering_tombstone_seq, const ReadOptions& read_opts) {
|
||||
for (MemTable* memtable : memlist_) {
|
||||
for (ReadOnlyMemTable* memtable : memlist_) {
|
||||
bool done = memtable->Get(
|
||||
key, /*value=*/nullptr, /*columns=*/nullptr, /*timestamp=*/nullptr, s,
|
||||
merge_context, max_covering_tombstone_seq, read_opts,
|
||||
@@ -154,11 +156,11 @@ bool MemTableListVersion::GetFromHistory(
|
||||
}
|
||||
|
||||
bool MemTableListVersion::GetFromList(
|
||||
std::list<MemTable*>* list, const LookupKey& key, std::string* value,
|
||||
PinnableWideColumns* columns, std::string* timestamp, Status* s,
|
||||
MergeContext* merge_context, SequenceNumber* max_covering_tombstone_seq,
|
||||
SequenceNumber* seq, const ReadOptions& read_opts, ReadCallback* callback,
|
||||
bool* is_blob_index) {
|
||||
std::list<ReadOnlyMemTable*>* list, const LookupKey& key,
|
||||
std::string* value, PinnableWideColumns* columns, std::string* timestamp,
|
||||
Status* s, MergeContext* merge_context,
|
||||
SequenceNumber* max_covering_tombstone_seq, SequenceNumber* seq,
|
||||
const ReadOptions& read_opts, ReadCallback* callback, bool* is_blob_index) {
|
||||
*seq = kMaxSequenceNumber;
|
||||
|
||||
for (auto& memtable : *list) {
|
||||
@@ -218,7 +220,8 @@ void MemTableListVersion::AddIterators(
|
||||
std::vector<InternalIterator*>* iterator_list, Arena* arena) {
|
||||
for (auto& m : memlist_) {
|
||||
iterator_list->push_back(m->NewIterator(options, seqno_to_time_mapping,
|
||||
arena, prefix_extractor));
|
||||
arena, prefix_extractor,
|
||||
/*for_flush=*/false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,7 +233,8 @@ void MemTableListVersion::AddIterators(
|
||||
for (auto& m : memlist_) {
|
||||
auto mem_iter =
|
||||
m->NewIterator(options, seqno_to_time_mapping,
|
||||
merge_iter_builder->GetArena(), prefix_extractor);
|
||||
merge_iter_builder->GetArena(), prefix_extractor,
|
||||
/*for_flush=*/false);
|
||||
if (!add_range_tombstone_iter || options.ignore_range_deletions) {
|
||||
merge_iter_builder->AddIterator(mem_iter);
|
||||
} else {
|
||||
@@ -259,14 +263,14 @@ void MemTableListVersion::AddIterators(
|
||||
uint64_t MemTableListVersion::GetTotalNumEntries() const {
|
||||
uint64_t total_num = 0;
|
||||
for (auto& m : memlist_) {
|
||||
total_num += m->num_entries();
|
||||
total_num += m->NumEntries();
|
||||
}
|
||||
return total_num;
|
||||
}
|
||||
|
||||
MemTable::MemTableStats MemTableListVersion::ApproximateStats(
|
||||
const Slice& start_ikey, const Slice& end_ikey) {
|
||||
MemTable::MemTableStats total_stats = {0, 0};
|
||||
ReadOnlyMemTable::MemTableStats MemTableListVersion::ApproximateStats(
|
||||
const Slice& start_ikey, const Slice& end_ikey) const {
|
||||
ReadOnlyMemTable::MemTableStats total_stats = {0, 0};
|
||||
for (auto& m : memlist_) {
|
||||
auto mStats = m->ApproximateStats(start_ikey, end_ikey);
|
||||
total_stats.size += mStats.size;
|
||||
@@ -278,7 +282,7 @@ MemTable::MemTableStats MemTableListVersion::ApproximateStats(
|
||||
uint64_t MemTableListVersion::GetTotalNumDeletes() const {
|
||||
uint64_t total_num = 0;
|
||||
for (auto& m : memlist_) {
|
||||
total_num += m->num_deletes();
|
||||
total_num += m->NumDeletion();
|
||||
}
|
||||
return total_num;
|
||||
}
|
||||
@@ -304,7 +308,8 @@ SequenceNumber MemTableListVersion::GetFirstSequenceNumber() const {
|
||||
}
|
||||
|
||||
// caller is responsible for referencing m
|
||||
void MemTableListVersion::Add(MemTable* m, autovector<MemTable*>* to_delete) {
|
||||
void MemTableListVersion::Add(ReadOnlyMemTable* m,
|
||||
autovector<ReadOnlyMemTable*>* to_delete) {
|
||||
assert(refs_ == 1); // only when refs_ == 1 is MemTableListVersion mutable
|
||||
AddMemTable(m);
|
||||
// m->MemoryAllocatedBytes() is added in MemoryAllocatedBytesExcludingLast
|
||||
@@ -312,8 +317,8 @@ void MemTableListVersion::Add(MemTable* m, autovector<MemTable*>* to_delete) {
|
||||
}
|
||||
|
||||
// Removes m from list of memtables not flushed. Caller should NOT Unref m.
|
||||
void MemTableListVersion::Remove(MemTable* m,
|
||||
autovector<MemTable*>* to_delete) {
|
||||
void MemTableListVersion::Remove(ReadOnlyMemTable* m,
|
||||
autovector<ReadOnlyMemTable*>* to_delete) {
|
||||
assert(refs_ == 1); // only when refs_ == 1 is MemTableListVersion mutable
|
||||
memlist_.remove(m);
|
||||
|
||||
@@ -359,12 +364,16 @@ bool MemTableListVersion::MemtableLimitExceeded(size_t usage) {
|
||||
}
|
||||
}
|
||||
|
||||
bool MemTableListVersion::HistoryShouldBeTrimmed(size_t usage) {
|
||||
return MemtableLimitExceeded(usage) && !memlist_history_.empty();
|
||||
}
|
||||
|
||||
// Make sure we don't use up too much space in history
|
||||
bool MemTableListVersion::TrimHistory(autovector<MemTable*>* to_delete,
|
||||
bool MemTableListVersion::TrimHistory(autovector<ReadOnlyMemTable*>* to_delete,
|
||||
size_t usage) {
|
||||
bool ret = false;
|
||||
while (MemtableLimitExceeded(usage) && !memlist_history_.empty()) {
|
||||
MemTable* x = memlist_history_.back();
|
||||
while (HistoryShouldBeTrimmed(usage)) {
|
||||
ReadOnlyMemTable* x = memlist_history_.back();
|
||||
memlist_history_.pop_back();
|
||||
|
||||
UnrefMemTable(to_delete, x);
|
||||
@@ -394,7 +403,7 @@ bool MemTableList::IsFlushPendingOrRunning() const {
|
||||
|
||||
// Returns the memtables that need to be flushed.
|
||||
void MemTableList::PickMemtablesToFlush(uint64_t max_memtable_id,
|
||||
autovector<MemTable*>* ret,
|
||||
autovector<ReadOnlyMemTable*>* ret,
|
||||
uint64_t* max_next_log_number) {
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_PICK_MEMTABLES_TO_FLUSH);
|
||||
@@ -407,8 +416,9 @@ void MemTableList::PickMemtablesToFlush(uint64_t max_memtable_id,
|
||||
// ret is filled with memtables already sorted in increasing MemTable ID.
|
||||
// However, when the mempurge feature is activated, new memtables with older
|
||||
// IDs will be added to the memlist.
|
||||
for (auto it = memlist.rbegin(); it != memlist.rend(); ++it) {
|
||||
MemTable* m = *it;
|
||||
auto it = memlist.rbegin();
|
||||
for (; it != memlist.rend(); ++it) {
|
||||
ReadOnlyMemTable* m = *it;
|
||||
if (!atomic_flush && m->atomic_flush_seqno_ != kMaxSequenceNumber) {
|
||||
atomic_flush = true;
|
||||
}
|
||||
@@ -436,25 +446,32 @@ void MemTableList::PickMemtablesToFlush(uint64_t max_memtable_id,
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ret->empty() && it != memlist.rend()) {
|
||||
// checks that the first memtable not picked to flush is not ingested wbwi.
|
||||
// Ingested memtable should be flushed together with the memtable before it
|
||||
// since they map to the same WAL and have the same NextLogNumber().
|
||||
assert(strcmp((*it)->Name(), "WBWIMemTable") != 0);
|
||||
}
|
||||
if (!atomic_flush || num_flush_not_started_ == 0) {
|
||||
flush_requested_ = false; // start-flush request is complete
|
||||
}
|
||||
}
|
||||
|
||||
void MemTableList::RollbackMemtableFlush(const autovector<MemTable*>& mems,
|
||||
bool rollback_succeeding_memtables) {
|
||||
void MemTableList::RollbackMemtableFlush(
|
||||
const autovector<ReadOnlyMemTable*>& mems,
|
||||
bool rollback_succeeding_memtables) {
|
||||
TEST_SYNC_POINT("RollbackMemtableFlush");
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_MEMTABLE_ROLLBACK);
|
||||
#ifndef NDEBUG
|
||||
for (MemTable* m : mems) {
|
||||
for (ReadOnlyMemTable* m : mems) {
|
||||
assert(m->flush_in_progress_);
|
||||
assert(m->file_number_ == 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (rollback_succeeding_memtables && !mems.empty()) {
|
||||
std::list<MemTable*>& memlist = current_->memlist_;
|
||||
std::list<ReadOnlyMemTable*>& memlist = current_->memlist_;
|
||||
auto it = memlist.rbegin();
|
||||
for (; *it != mems[0] && it != memlist.rend(); ++it) {
|
||||
}
|
||||
@@ -464,7 +481,7 @@ void MemTableList::RollbackMemtableFlush(const autovector<MemTable*>& mems,
|
||||
++it;
|
||||
}
|
||||
while (it != memlist.rend()) {
|
||||
MemTable* m = *it;
|
||||
ReadOnlyMemTable* m = *it;
|
||||
// Only rollback complete, not in-progress,
|
||||
// in_progress can be flushes that are still writing SSTs
|
||||
if (m->flush_completed_) {
|
||||
@@ -480,7 +497,7 @@ void MemTableList::RollbackMemtableFlush(const autovector<MemTable*>& mems,
|
||||
}
|
||||
}
|
||||
|
||||
for (MemTable* m : mems) {
|
||||
for (ReadOnlyMemTable* m : mems) {
|
||||
if (m->flush_in_progress_) {
|
||||
assert(m->file_number_ == 0);
|
||||
m->file_number_ = 0;
|
||||
@@ -499,10 +516,10 @@ void MemTableList::RollbackMemtableFlush(const autovector<MemTable*>& mems,
|
||||
// Status::OK letting a concurrent flush to do actual the recording..
|
||||
Status MemTableList::TryInstallMemtableFlushResults(
|
||||
ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options,
|
||||
const autovector<MemTable*>& mems, LogsWithPrepTracker* prep_tracker,
|
||||
VersionSet* vset, InstrumentedMutex* mu, uint64_t file_number,
|
||||
autovector<MemTable*>* to_delete, FSDirectory* db_directory,
|
||||
LogBuffer* log_buffer,
|
||||
const autovector<ReadOnlyMemTable*>& mems,
|
||||
LogsWithPrepTracker* prep_tracker, VersionSet* vset, InstrumentedMutex* mu,
|
||||
uint64_t file_number, autovector<ReadOnlyMemTable*>* to_delete,
|
||||
FSDirectory* db_directory, LogBuffer* log_buffer,
|
||||
std::list<std::unique_ptr<FlushJobInfo>>* committed_flush_jobs_info,
|
||||
bool write_edits) {
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
@@ -549,16 +566,16 @@ Status MemTableList::TryInstallMemtableFlushResults(
|
||||
// (in that order) that have finished flushing. Memtables
|
||||
// are always committed in the order that they were created.
|
||||
uint64_t batch_file_number = 0;
|
||||
size_t batch_count = 0;
|
||||
autovector<VersionEdit*> edit_list;
|
||||
autovector<MemTable*> memtables_to_flush;
|
||||
autovector<ReadOnlyMemTable*> memtables_to_flush;
|
||||
// enumerate from the last (earliest) element to see how many batch finished
|
||||
for (auto it = memlist.rbegin(); it != memlist.rend(); ++it) {
|
||||
MemTable* m = *it;
|
||||
ReadOnlyMemTable* m = *it;
|
||||
if (!m->flush_completed_) {
|
||||
break;
|
||||
}
|
||||
if (it == memlist.rbegin() || batch_file_number != m->file_number_) {
|
||||
// Oldest memtable in a new batch.
|
||||
batch_file_number = m->file_number_;
|
||||
if (m->edit_.GetBlobFileAdditions().empty()) {
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
@@ -574,17 +591,17 @@ Status MemTableList::TryInstallMemtableFlushResults(
|
||||
}
|
||||
|
||||
edit_list.push_back(&m->edit_);
|
||||
memtables_to_flush.push_back(m);
|
||||
std::unique_ptr<FlushJobInfo> info = m->ReleaseFlushJobInfo();
|
||||
if (info != nullptr) {
|
||||
committed_flush_jobs_info->push_back(std::move(info));
|
||||
}
|
||||
}
|
||||
batch_count++;
|
||||
memtables_to_flush.push_back(m);
|
||||
}
|
||||
|
||||
size_t num_mem_to_flush = memtables_to_flush.size();
|
||||
// TODO(myabandeh): Not sure how batch_count could be 0 here.
|
||||
if (batch_count > 0) {
|
||||
if (num_mem_to_flush > 0) {
|
||||
VersionEdit edit;
|
||||
#ifdef ROCKSDB_ASSERT_STATUS_CHECKED
|
||||
if (memtables_to_flush.size() == memlist.size()) {
|
||||
@@ -608,9 +625,9 @@ Status MemTableList::TryInstallMemtableFlushResults(
|
||||
nullptr);
|
||||
edit_list.push_back(&edit);
|
||||
|
||||
const auto manifest_write_cb = [this, cfd, batch_count, log_buffer,
|
||||
const auto manifest_write_cb = [this, cfd, num_mem_to_flush, log_buffer,
|
||||
to_delete, mu](const Status& status) {
|
||||
RemoveMemTablesOrRestoreFlags(status, cfd, batch_count, log_buffer,
|
||||
RemoveMemTablesOrRestoreFlags(status, cfd, num_mem_to_flush, log_buffer,
|
||||
to_delete, mu);
|
||||
};
|
||||
if (write_edits) {
|
||||
@@ -623,7 +640,7 @@ Status MemTableList::TryInstallMemtableFlushResults(
|
||||
// If write_edit is false (e.g: successful mempurge),
|
||||
// then remove old memtables, wake up manifest write queue threads,
|
||||
// and don't commit anything to the manifest file.
|
||||
RemoveMemTablesOrRestoreFlags(s, cfd, batch_count, log_buffer,
|
||||
RemoveMemTablesOrRestoreFlags(s, cfd, num_mem_to_flush, log_buffer,
|
||||
to_delete, mu);
|
||||
// Note: cfd->SetLogNumber is only called when a VersionEdit
|
||||
// is written to MANIFEST. When mempurge is succesful, we skip
|
||||
@@ -642,7 +659,8 @@ Status MemTableList::TryInstallMemtableFlushResults(
|
||||
}
|
||||
|
||||
// New memtables are inserted at the front of the list.
|
||||
void MemTableList::Add(MemTable* m, autovector<MemTable*>* to_delete) {
|
||||
void MemTableList::Add(ReadOnlyMemTable* m,
|
||||
autovector<ReadOnlyMemTable*>* to_delete) {
|
||||
assert(static_cast<int>(current_->memlist_.size()) >= num_flush_not_started_);
|
||||
InstallNewVersion();
|
||||
// this method is used to move mutable memtable into an immutable list.
|
||||
@@ -660,9 +678,18 @@ void MemTableList::Add(MemTable* m, autovector<MemTable*>* to_delete) {
|
||||
ResetTrimHistoryNeeded();
|
||||
}
|
||||
|
||||
bool MemTableList::TrimHistory(autovector<MemTable*>* to_delete, size_t usage) {
|
||||
bool MemTableList::TrimHistory(autovector<ReadOnlyMemTable*>* to_delete,
|
||||
size_t usage) {
|
||||
// Check if history trim is needed first, so that we can avoid installing a
|
||||
// new MemTableListVersion without installing a SuperVersion (installed based
|
||||
// on return value of this function).
|
||||
if (!current_->HistoryShouldBeTrimmed(usage)) {
|
||||
ResetTrimHistoryNeeded();
|
||||
return false;
|
||||
}
|
||||
InstallNewVersion();
|
||||
bool ret = current_->TrimHistory(to_delete, usage);
|
||||
assert(ret);
|
||||
UpdateCachedValuesFromMemTableListVersion();
|
||||
ResetTrimHistoryNeeded();
|
||||
return ret;
|
||||
@@ -714,14 +741,15 @@ void MemTableList::InstallNewVersion() {
|
||||
// somebody else holds the current version, we need to create new one
|
||||
MemTableListVersion* version = current_;
|
||||
current_ = new MemTableListVersion(¤t_memory_usage_, *version);
|
||||
current_->SetID(++last_memtable_list_version_id_);
|
||||
current_->Ref();
|
||||
version->Unref();
|
||||
}
|
||||
}
|
||||
|
||||
void MemTableList::RemoveMemTablesOrRestoreFlags(
|
||||
const Status& s, ColumnFamilyData* cfd, size_t batch_count,
|
||||
LogBuffer* log_buffer, autovector<MemTable*>* to_delete,
|
||||
const Status& s, ColumnFamilyData* cfd, size_t num_mem_to_flush,
|
||||
LogBuffer* log_buffer, autovector<ReadOnlyMemTable*>* to_delete,
|
||||
InstrumentedMutex* mu) {
|
||||
assert(mu);
|
||||
mu->AssertHeld();
|
||||
@@ -749,8 +777,11 @@ void MemTableList::RemoveMemTablesOrRestoreFlags(
|
||||
// read full data as long as column family handle is not deleted, even if
|
||||
// the column family is dropped.
|
||||
if (s.ok() && !cfd->IsDropped()) { // commit new state
|
||||
while (batch_count-- > 0) {
|
||||
MemTable* m = current_->memlist_.back();
|
||||
while (num_mem_to_flush-- > 0) {
|
||||
ReadOnlyMemTable* m = current_->memlist_.back();
|
||||
// TODO: The logging can be redundant when we flush multiple memtables
|
||||
// into one SST file. We should only check the edit_ of the oldest
|
||||
// memtable in the group in that case.
|
||||
if (m->edit_.GetBlobFileAdditions().empty()) {
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] Level-0 commit flush result of table #%" PRIu64
|
||||
@@ -772,8 +803,8 @@ void MemTableList::RemoveMemTablesOrRestoreFlags(
|
||||
++mem_id;
|
||||
}
|
||||
} else {
|
||||
for (auto it = current_->memlist_.rbegin(); batch_count-- > 0; ++it) {
|
||||
MemTable* m = *it;
|
||||
for (auto it = current_->memlist_.rbegin(); num_mem_to_flush-- > 0; ++it) {
|
||||
ReadOnlyMemTable* m = *it;
|
||||
// commit failed. setup state so that we can flush again.
|
||||
if (m->edit_.GetBlobFileAdditions().empty()) {
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
@@ -801,7 +832,7 @@ void MemTableList::RemoveMemTablesOrRestoreFlags(
|
||||
}
|
||||
|
||||
uint64_t MemTableList::PrecomputeMinLogContainingPrepSection(
|
||||
const std::unordered_set<MemTable*>* memtables_to_flush) {
|
||||
const std::unordered_set<ReadOnlyMemTable*>* memtables_to_flush) const {
|
||||
uint64_t min_log = 0;
|
||||
|
||||
for (auto& m : current_->memlist_) {
|
||||
@@ -824,12 +855,12 @@ Status InstallMemtableAtomicFlushResults(
|
||||
const autovector<MemTableList*>* imm_lists,
|
||||
const autovector<ColumnFamilyData*>& cfds,
|
||||
const autovector<const MutableCFOptions*>& mutable_cf_options_list,
|
||||
const autovector<const autovector<MemTable*>*>& mems_list, VersionSet* vset,
|
||||
LogsWithPrepTracker* prep_tracker, InstrumentedMutex* mu,
|
||||
const autovector<const autovector<ReadOnlyMemTable*>*>& mems_list,
|
||||
VersionSet* vset, LogsWithPrepTracker* prep_tracker, InstrumentedMutex* mu,
|
||||
const autovector<FileMetaData*>& file_metas,
|
||||
const autovector<std::list<std::unique_ptr<FlushJobInfo>>*>&
|
||||
committed_flush_jobs_info,
|
||||
autovector<MemTable*>* to_delete, FSDirectory* db_directory,
|
||||
autovector<ReadOnlyMemTable*>* to_delete, FSDirectory* db_directory,
|
||||
LogBuffer* log_buffer) {
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_MEMTABLE_INSTALL_FLUSH_RESULTS);
|
||||
@@ -993,14 +1024,14 @@ Status InstallMemtableAtomicFlushResults(
|
||||
return s;
|
||||
}
|
||||
|
||||
void MemTableList::RemoveOldMemTables(uint64_t log_number,
|
||||
autovector<MemTable*>* to_delete) {
|
||||
void MemTableList::RemoveOldMemTables(
|
||||
uint64_t log_number, autovector<ReadOnlyMemTable*>* to_delete) {
|
||||
assert(to_delete != nullptr);
|
||||
InstallNewVersion();
|
||||
auto& memlist = current_->memlist_;
|
||||
autovector<MemTable*> old_memtables;
|
||||
autovector<ReadOnlyMemTable*> old_memtables;
|
||||
for (auto it = memlist.rbegin(); it != memlist.rend(); ++it) {
|
||||
MemTable* mem = *it;
|
||||
ReadOnlyMemTable* mem = *it;
|
||||
if (mem->GetNextLogNumber() > log_number) {
|
||||
break;
|
||||
}
|
||||
@@ -1008,7 +1039,7 @@ void MemTableList::RemoveOldMemTables(uint64_t log_number,
|
||||
}
|
||||
|
||||
for (auto it = old_memtables.begin(); it != old_memtables.end(); ++it) {
|
||||
MemTable* mem = *it;
|
||||
ReadOnlyMemTable* mem = *it;
|
||||
current_->Remove(mem, to_delete);
|
||||
--num_flush_not_started_;
|
||||
if (0 == num_flush_not_started_) {
|
||||
@@ -1031,9 +1062,9 @@ VersionEdit MemTableList::GetEditForDroppingCurrentVersion(
|
||||
|
||||
uint64_t max_next_log_number = 0;
|
||||
autovector<VersionEdit*> edit_list;
|
||||
autovector<MemTable*> memtables_to_drop;
|
||||
autovector<ReadOnlyMemTable*> memtables_to_drop;
|
||||
for (auto it = memlist.rbegin(); it != memlist.rend(); ++it) {
|
||||
MemTable* m = *it;
|
||||
ReadOnlyMemTable* m = *it;
|
||||
memtables_to_drop.push_back(m);
|
||||
max_next_log_number = std::max(m->GetNextLogNumber(), max_next_log_number);
|
||||
}
|
||||
|
||||
+63
-38
@@ -34,11 +34,11 @@ class MemTableList;
|
||||
|
||||
struct FlushJobInfo;
|
||||
|
||||
// keeps a list of immutable memtables in a vector. the list is immutable
|
||||
// if refcount is bigger than one. It is used as a state for Get() and
|
||||
// Iterator code paths
|
||||
// keeps a list of immutable memtables (ReadOnlyMemtable*) in a vector.
|
||||
// The list is immutable if refcount is bigger than one. It is used as
|
||||
// a state for Get() and iterator code paths.
|
||||
//
|
||||
// This class is not thread-safe. External synchronization is required
|
||||
// This class is not thread-safe. External synchronization is required
|
||||
// (such as holding the db mutex or being on the write thread).
|
||||
class MemTableListVersion {
|
||||
public:
|
||||
@@ -49,7 +49,7 @@ class MemTableListVersion {
|
||||
int64_t max_write_buffer_size_to_maintain);
|
||||
|
||||
void Ref();
|
||||
void Unref(autovector<MemTable*>* to_delete = nullptr);
|
||||
void Unref(autovector<ReadOnlyMemTable*>* to_delete = nullptr);
|
||||
|
||||
// Search all the memtables starting from the most recent one.
|
||||
// Return the most recent value found, if any.
|
||||
@@ -127,8 +127,8 @@ class MemTableListVersion {
|
||||
|
||||
uint64_t GetTotalNumDeletes() const;
|
||||
|
||||
MemTable::MemTableStats ApproximateStats(const Slice& start_ikey,
|
||||
const Slice& end_ikey);
|
||||
ReadOnlyMemTable::MemTableStats ApproximateStats(const Slice& start_ikey,
|
||||
const Slice& end_ikey) const;
|
||||
|
||||
// Returns the value of MemTable::GetEarliestSequenceNumber() on the most
|
||||
// recent MemTable in this list or kMaxSequenceNumber if the list is empty.
|
||||
@@ -141,6 +141,15 @@ class MemTableListVersion {
|
||||
// Return kMaxSequenceNumber if the list is empty.
|
||||
SequenceNumber GetFirstSequenceNumber() const;
|
||||
|
||||
// REQUIRES: db_mutex held.
|
||||
void SetID(uint64_t id) { id_ = id; }
|
||||
|
||||
uint64_t GetID() const { return id_; }
|
||||
|
||||
int NumNotFlushed() const { return static_cast<int>(memlist_.size()); }
|
||||
|
||||
int NumFlushed() const { return static_cast<int>(memlist_history_.size()); }
|
||||
|
||||
private:
|
||||
friend class MemTableList;
|
||||
|
||||
@@ -148,23 +157,27 @@ class MemTableListVersion {
|
||||
const autovector<MemTableList*>* imm_lists,
|
||||
const autovector<ColumnFamilyData*>& cfds,
|
||||
const autovector<const MutableCFOptions*>& mutable_cf_options_list,
|
||||
const autovector<const autovector<MemTable*>*>& mems_list,
|
||||
const autovector<const autovector<ReadOnlyMemTable*>*>& mems_list,
|
||||
VersionSet* vset, LogsWithPrepTracker* prep_tracker,
|
||||
InstrumentedMutex* mu, const autovector<FileMetaData*>& file_meta,
|
||||
const autovector<std::list<std::unique_ptr<FlushJobInfo>>*>&
|
||||
committed_flush_jobs_info,
|
||||
autovector<MemTable*>* to_delete, FSDirectory* db_directory,
|
||||
autovector<ReadOnlyMemTable*>* to_delete, FSDirectory* db_directory,
|
||||
LogBuffer* log_buffer);
|
||||
|
||||
// REQUIRE: m is an immutable memtable
|
||||
void Add(MemTable* m, autovector<MemTable*>* to_delete);
|
||||
void Add(ReadOnlyMemTable* m, autovector<ReadOnlyMemTable*>* to_delete);
|
||||
// REQUIRE: m is an immutable memtable
|
||||
void Remove(MemTable* m, autovector<MemTable*>* to_delete);
|
||||
void Remove(ReadOnlyMemTable* m, autovector<ReadOnlyMemTable*>* to_delete);
|
||||
|
||||
// Return true if memtable is trimmed
|
||||
bool TrimHistory(autovector<MemTable*>* to_delete, size_t usage);
|
||||
// Return true if the memtable list should be trimmed to get memory usage
|
||||
// under budget.
|
||||
bool HistoryShouldBeTrimmed(size_t usage);
|
||||
|
||||
bool GetFromList(std::list<MemTable*>* list, const LookupKey& key,
|
||||
// Trim history, Return true if memtable is trimmed
|
||||
bool TrimHistory(autovector<ReadOnlyMemTable*>* to_delete, size_t usage);
|
||||
|
||||
bool GetFromList(std::list<ReadOnlyMemTable*>* list, const LookupKey& key,
|
||||
std::string* value, PinnableWideColumns* columns,
|
||||
std::string* timestamp, Status* s,
|
||||
MergeContext* merge_context,
|
||||
@@ -173,9 +186,10 @@ class MemTableListVersion {
|
||||
ReadCallback* callback = nullptr,
|
||||
bool* is_blob_index = nullptr);
|
||||
|
||||
void AddMemTable(MemTable* m);
|
||||
void AddMemTable(ReadOnlyMemTable* m);
|
||||
|
||||
void UnrefMemTable(autovector<MemTable*>* to_delete, MemTable* m);
|
||||
void UnrefMemTable(autovector<ReadOnlyMemTable*>* to_delete,
|
||||
ReadOnlyMemTable* m);
|
||||
|
||||
// Calculate the total amount of memory used by memlist_ and memlist_history_
|
||||
// excluding the last MemTable in memlist_history_. The reason for excluding
|
||||
@@ -190,11 +204,11 @@ class MemTableListVersion {
|
||||
bool MemtableLimitExceeded(size_t usage);
|
||||
|
||||
// Immutable MemTables that have not yet been flushed.
|
||||
std::list<MemTable*> memlist_;
|
||||
std::list<ReadOnlyMemTable*> memlist_;
|
||||
|
||||
// MemTables that have already been flushed
|
||||
// (used during Transaction validation)
|
||||
std::list<MemTable*> memlist_history_;
|
||||
std::list<ReadOnlyMemTable*> memlist_history_;
|
||||
|
||||
// Maximum number of MemTables to keep in memory (including both flushed
|
||||
const int max_write_buffer_number_to_maintain_;
|
||||
@@ -205,6 +219,9 @@ class MemTableListVersion {
|
||||
int refs_ = 0;
|
||||
|
||||
size_t* parent_memtable_list_memory_usage_;
|
||||
|
||||
// MemtableListVersion id to track for flush results checking.
|
||||
uint64_t id_ = 0;
|
||||
};
|
||||
|
||||
// This class stores references to all the immutable memtables.
|
||||
@@ -235,7 +252,8 @@ class MemTableList {
|
||||
flush_requested_(false),
|
||||
current_memory_usage_(0),
|
||||
current_memory_allocted_bytes_excluding_last_(0),
|
||||
current_has_history_(false) {
|
||||
current_has_history_(false),
|
||||
last_memtable_list_version_id_(0) {
|
||||
current_->Ref();
|
||||
}
|
||||
|
||||
@@ -270,7 +288,7 @@ class MemTableList {
|
||||
// Returns the earliest memtables that needs to be flushed. The returned
|
||||
// memtables are guaranteed to be in the ascending order of created time.
|
||||
void PickMemtablesToFlush(uint64_t max_memtable_id,
|
||||
autovector<MemTable*>* mems,
|
||||
autovector<ReadOnlyMemTable*>* mems,
|
||||
uint64_t* max_next_log_number = nullptr);
|
||||
|
||||
// Reset status of the given memtable list back to pending state so that
|
||||
@@ -287,16 +305,16 @@ class MemTableList {
|
||||
// Note that we also do rollback in `write_manifest_cb` by calling
|
||||
// `RemoveMemTablesOrRestoreFlags()`. There we rollback the entire batch so
|
||||
// it is similar to what we do here with rollback_succeeding_memtables=true.
|
||||
void RollbackMemtableFlush(const autovector<MemTable*>& mems,
|
||||
void RollbackMemtableFlush(const autovector<ReadOnlyMemTable*>& mems,
|
||||
bool rollback_succeeding_memtables);
|
||||
|
||||
// Try commit a successful flush in the manifest file. It might just return
|
||||
// Status::OK letting a concurrent flush to do the actual the recording.
|
||||
Status TryInstallMemtableFlushResults(
|
||||
ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options,
|
||||
const autovector<MemTable*>& m, LogsWithPrepTracker* prep_tracker,
|
||||
const autovector<ReadOnlyMemTable*>& m, LogsWithPrepTracker* prep_tracker,
|
||||
VersionSet* vset, InstrumentedMutex* mu, uint64_t file_number,
|
||||
autovector<MemTable*>* to_delete, FSDirectory* db_directory,
|
||||
autovector<ReadOnlyMemTable*>* to_delete, FSDirectory* db_directory,
|
||||
LogBuffer* log_buffer,
|
||||
std::list<std::unique_ptr<FlushJobInfo>>* committed_flush_jobs_info,
|
||||
bool write_edits = true);
|
||||
@@ -306,7 +324,7 @@ class MemTableList {
|
||||
// By default, adding memtables will flag that the memtable list needs to be
|
||||
// flushed, but in certain situations, like after a mempurge, we may want to
|
||||
// avoid flushing the memtable list upon addition of a memtable.
|
||||
void Add(MemTable* m, autovector<MemTable*>* to_delete);
|
||||
void Add(ReadOnlyMemTable* m, autovector<ReadOnlyMemTable*>* to_delete);
|
||||
|
||||
// Returns an estimate of the number of bytes of data in use.
|
||||
size_t ApproximateMemoryUsage();
|
||||
@@ -328,7 +346,7 @@ class MemTableList {
|
||||
// memtable list.
|
||||
//
|
||||
// Return true if memtable is trimmed
|
||||
bool TrimHistory(autovector<MemTable*>* to_delete, size_t usage);
|
||||
bool TrimHistory(autovector<ReadOnlyMemTable*>* to_delete, size_t usage);
|
||||
|
||||
// Returns an estimate of the number of bytes of data used by
|
||||
// the unflushed mem-tables.
|
||||
@@ -377,10 +395,13 @@ class MemTableList {
|
||||
|
||||
size_t* current_memory_usage() { return ¤t_memory_usage_; }
|
||||
|
||||
// Returns the min log containing the prep section after memtables listsed in
|
||||
// `memtables_to_flush` are flushed and their status is persisted in manifest.
|
||||
// Returns the WAL number of the oldest WAL that contains a prepared
|
||||
// transaction that corresponds to the content in this MemTableList,
|
||||
// after memtables listed in `memtables_to_flush` are flushed and their
|
||||
// status is persisted in manifest.
|
||||
uint64_t PrecomputeMinLogContainingPrepSection(
|
||||
const std::unordered_set<MemTable*>* memtables_to_flush = nullptr);
|
||||
const std::unordered_set<ReadOnlyMemTable*>* memtables_to_flush =
|
||||
nullptr) const;
|
||||
|
||||
uint64_t GetEarliestMemTableID() const {
|
||||
auto& memlist = current_->memlist_;
|
||||
@@ -398,7 +419,7 @@ class MemTableList {
|
||||
if (for_atomic_flush) {
|
||||
// Scan the memtable list from new to old
|
||||
for (auto it = memlist.begin(); it != memlist.end(); ++it) {
|
||||
MemTable* m = *it;
|
||||
ReadOnlyMemTable* m = *it;
|
||||
if (m->atomic_flush_seqno_ != kMaxSequenceNumber) {
|
||||
return m->GetID();
|
||||
}
|
||||
@@ -418,7 +439,7 @@ class MemTableList {
|
||||
// Iterating through the memlist starting at the end, the vector<MemTable*>
|
||||
// ret is filled with memtables already sorted in increasing MemTable ID.
|
||||
for (auto it = memlist.rbegin(); it != memlist.rend(); ++it) {
|
||||
MemTable* m = *it;
|
||||
ReadOnlyMemTable* m = *it;
|
||||
if (m->GetID() > max_memtable_id) {
|
||||
break;
|
||||
}
|
||||
@@ -431,7 +452,7 @@ class MemTableList {
|
||||
const auto& memlist = current_->memlist_;
|
||||
// Scan the memtable list from new to old
|
||||
for (auto it = memlist.begin(); it != memlist.end(); ++it) {
|
||||
MemTable* mem = *it;
|
||||
ReadOnlyMemTable* mem = *it;
|
||||
if (mem->atomic_flush_seqno_ == kMaxSequenceNumber) {
|
||||
mem->atomic_flush_seqno_ = seq;
|
||||
} else {
|
||||
@@ -447,7 +468,7 @@ class MemTableList {
|
||||
// was created, i.e. mem->GetNextLogNumber() <= log_number. The memtables are
|
||||
// not freed, but put into a vector for future deref and reclamation.
|
||||
void RemoveOldMemTables(uint64_t log_number,
|
||||
autovector<MemTable*>* to_delete);
|
||||
autovector<ReadOnlyMemTable*>* to_delete);
|
||||
|
||||
// This API is only used by atomic date replacement. To get an edit for
|
||||
// dropping the current `MemTableListVersion`.
|
||||
@@ -460,12 +481,12 @@ class MemTableList {
|
||||
const autovector<MemTableList*>* imm_lists,
|
||||
const autovector<ColumnFamilyData*>& cfds,
|
||||
const autovector<const MutableCFOptions*>& mutable_cf_options_list,
|
||||
const autovector<const autovector<MemTable*>*>& mems_list,
|
||||
const autovector<const autovector<ReadOnlyMemTable*>*>& mems_list,
|
||||
VersionSet* vset, LogsWithPrepTracker* prep_tracker,
|
||||
InstrumentedMutex* mu, const autovector<FileMetaData*>& file_meta,
|
||||
const autovector<std::list<std::unique_ptr<FlushJobInfo>>*>&
|
||||
committed_flush_jobs_info,
|
||||
autovector<MemTable*>* to_delete, FSDirectory* db_directory,
|
||||
autovector<ReadOnlyMemTable*>* to_delete, FSDirectory* db_directory,
|
||||
LogBuffer* log_buffer);
|
||||
|
||||
// DB mutex held
|
||||
@@ -475,7 +496,7 @@ class MemTableList {
|
||||
// Called after writing to MANIFEST
|
||||
void RemoveMemTablesOrRestoreFlags(const Status& s, ColumnFamilyData* cfd,
|
||||
size_t batch_count, LogBuffer* log_buffer,
|
||||
autovector<MemTable*>* to_delete,
|
||||
autovector<ReadOnlyMemTable*>* to_delete,
|
||||
InstrumentedMutex* mu);
|
||||
|
||||
const int min_write_buffer_number_to_merge_;
|
||||
@@ -500,6 +521,10 @@ class MemTableList {
|
||||
|
||||
// Cached value of current_->HasHistory().
|
||||
std::atomic<bool> current_has_history_;
|
||||
|
||||
// Last memtabe list version id, increase by 1 each time a new
|
||||
// MemtableListVersion is installed.
|
||||
uint64_t last_memtable_list_version_id_;
|
||||
};
|
||||
|
||||
// Installs memtable atomic flush results.
|
||||
@@ -512,11 +537,11 @@ Status InstallMemtableAtomicFlushResults(
|
||||
const autovector<MemTableList*>* imm_lists,
|
||||
const autovector<ColumnFamilyData*>& cfds,
|
||||
const autovector<const MutableCFOptions*>& mutable_cf_options_list,
|
||||
const autovector<const autovector<MemTable*>*>& mems_list, VersionSet* vset,
|
||||
LogsWithPrepTracker* prep_tracker, InstrumentedMutex* mu,
|
||||
const autovector<const autovector<ReadOnlyMemTable*>*>& mems_list,
|
||||
VersionSet* vset, LogsWithPrepTracker* prep_tracker, InstrumentedMutex* mu,
|
||||
const autovector<FileMetaData*>& file_meta,
|
||||
const autovector<std::list<std::unique_ptr<FlushJobInfo>>*>&
|
||||
committed_flush_jobs_info,
|
||||
autovector<MemTable*>* to_delete, FSDirectory* db_directory,
|
||||
autovector<ReadOnlyMemTable*>* to_delete, FSDirectory* db_directory,
|
||||
LogBuffer* log_buffer);
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
+28
-25
@@ -98,7 +98,8 @@ class MemTableListTest : public testing::Test {
|
||||
// structures needed to call this function.
|
||||
Status Mock_InstallMemtableFlushResults(
|
||||
MemTableList* list, const MutableCFOptions& mutable_cf_options,
|
||||
const autovector<MemTable*>& m, autovector<MemTable*>* to_delete) {
|
||||
const autovector<ReadOnlyMemTable*>& m,
|
||||
autovector<ReadOnlyMemTable*>* to_delete) {
|
||||
// Create a mock Logger
|
||||
test::NullLogger logger;
|
||||
LogBuffer log_buffer(DEBUG_LEVEL, &logger);
|
||||
@@ -148,8 +149,8 @@ class MemTableListTest : public testing::Test {
|
||||
Status Mock_InstallMemtableAtomicFlushResults(
|
||||
autovector<MemTableList*>& lists, const autovector<uint32_t>& cf_ids,
|
||||
const autovector<const MutableCFOptions*>& mutable_cf_options_list,
|
||||
const autovector<const autovector<MemTable*>*>& mems_list,
|
||||
autovector<MemTable*>* to_delete) {
|
||||
const autovector<const autovector<ReadOnlyMemTable*>*>& mems_list,
|
||||
autovector<ReadOnlyMemTable*>* to_delete) {
|
||||
// Create a mock Logger
|
||||
test::NullLogger logger;
|
||||
LogBuffer log_buffer(DEBUG_LEVEL, &logger);
|
||||
@@ -227,12 +228,12 @@ TEST_F(MemTableListTest, Empty) {
|
||||
ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire));
|
||||
ASSERT_FALSE(list.IsFlushPending());
|
||||
|
||||
autovector<MemTable*> mems;
|
||||
autovector<ReadOnlyMemTable*> mems;
|
||||
list.PickMemtablesToFlush(
|
||||
std::numeric_limits<uint64_t>::max() /* memtable_id */, &mems);
|
||||
ASSERT_EQ(0, mems.size());
|
||||
|
||||
autovector<MemTable*> to_delete;
|
||||
autovector<ReadOnlyMemTable*> to_delete;
|
||||
list.current()->Unref(&to_delete);
|
||||
ASSERT_EQ(0, to_delete.size());
|
||||
}
|
||||
@@ -252,7 +253,7 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
MergeContext merge_context;
|
||||
InternalKeyComparator ikey_cmp(options.comparator);
|
||||
SequenceNumber max_covering_tombstone_seq = 0;
|
||||
autovector<MemTable*> to_delete;
|
||||
autovector<ReadOnlyMemTable*> to_delete;
|
||||
|
||||
LookupKey lkey("key1", seq);
|
||||
bool found = list.current()->Get(lkey, &value, /*columns=*/nullptr,
|
||||
@@ -322,13 +323,14 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
ASSERT_TRUE(s.ok() && found);
|
||||
ASSERT_EQ(value, "value3.1");
|
||||
|
||||
ASSERT_EQ(5, mem->num_entries());
|
||||
ASSERT_EQ(1, mem->num_deletes());
|
||||
ASSERT_EQ(5, mem->NumEntries());
|
||||
ASSERT_EQ(1, mem->NumDeletion());
|
||||
|
||||
// Add memtable to list
|
||||
// This is to make assert(memtable->IsFragmentedRangeTombstonesConstructed())
|
||||
// in MemTableListVersion::GetFromList work.
|
||||
mem->ConstructFragmentedRangeTombstones();
|
||||
mem->SetID(1);
|
||||
list.Add(mem, &to_delete);
|
||||
|
||||
SequenceNumber saved_seq = seq;
|
||||
@@ -337,6 +339,7 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
WriteBufferManager wb2(options.db_write_buffer_size);
|
||||
MemTable* mem2 = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb2,
|
||||
kMaxSequenceNumber, 0 /* column_family_id */);
|
||||
mem2->SetID(2);
|
||||
mem2->Ref();
|
||||
|
||||
ASSERT_OK(
|
||||
@@ -398,7 +401,7 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
ASSERT_EQ(2, list.NumNotFlushed());
|
||||
|
||||
list.current()->Unref(&to_delete);
|
||||
for (MemTable* m : to_delete) {
|
||||
for (ReadOnlyMemTable* m : to_delete) {
|
||||
delete m;
|
||||
}
|
||||
}
|
||||
@@ -418,7 +421,7 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
MergeContext merge_context;
|
||||
InternalKeyComparator ikey_cmp(options.comparator);
|
||||
SequenceNumber max_covering_tombstone_seq = 0;
|
||||
autovector<MemTable*> to_delete;
|
||||
autovector<ReadOnlyMemTable*> to_delete;
|
||||
|
||||
LookupKey lkey("key1", seq);
|
||||
bool found = list.current()->Get(lkey, &value, /*columns=*/nullptr,
|
||||
@@ -491,7 +494,7 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
|
||||
// Flush this memtable from the list.
|
||||
// (It will then be a part of the memtable history).
|
||||
autovector<MemTable*> to_flush;
|
||||
autovector<ReadOnlyMemTable*> to_flush;
|
||||
list.PickMemtablesToFlush(
|
||||
std::numeric_limits<uint64_t>::max() /* memtable_id */, &to_flush);
|
||||
ASSERT_EQ(1, to_flush.size());
|
||||
@@ -636,7 +639,7 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
// Cleanup
|
||||
list.current()->Unref(&to_delete);
|
||||
ASSERT_EQ(3, to_delete.size());
|
||||
for (MemTable* m : to_delete) {
|
||||
for (ReadOnlyMemTable* m : to_delete) {
|
||||
delete m;
|
||||
}
|
||||
}
|
||||
@@ -651,7 +654,7 @@ TEST_F(MemTableListTest, FlushPendingTest) {
|
||||
ImmutableOptions ioptions(options);
|
||||
InternalKeyComparator cmp(BytewiseComparator());
|
||||
WriteBufferManager wb(options.db_write_buffer_size);
|
||||
autovector<MemTable*> to_delete;
|
||||
autovector<ReadOnlyMemTable*> to_delete;
|
||||
|
||||
// Create MemTableList
|
||||
int min_write_buffer_number_to_merge = 3;
|
||||
@@ -692,7 +695,7 @@ TEST_F(MemTableListTest, FlushPendingTest) {
|
||||
// Nothing to flush
|
||||
ASSERT_FALSE(list.IsFlushPending());
|
||||
ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire));
|
||||
autovector<MemTable*> to_flush;
|
||||
autovector<ReadOnlyMemTable*> to_flush;
|
||||
list.PickMemtablesToFlush(
|
||||
std::numeric_limits<uint64_t>::max() /* memtable_id */, &to_flush);
|
||||
ASSERT_EQ(0, to_flush.size());
|
||||
@@ -758,7 +761,7 @@ TEST_F(MemTableListTest, FlushPendingTest) {
|
||||
ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire));
|
||||
|
||||
// Pick tables to flush again
|
||||
autovector<MemTable*> to_flush2;
|
||||
autovector<ReadOnlyMemTable*> to_flush2;
|
||||
list.PickMemtablesToFlush(
|
||||
std::numeric_limits<uint64_t>::max() /* memtable_id */, &to_flush2);
|
||||
ASSERT_EQ(0, to_flush2.size());
|
||||
@@ -811,7 +814,7 @@ TEST_F(MemTableListTest, FlushPendingTest) {
|
||||
ASSERT_TRUE(list.imm_flush_needed.load(std::memory_order_acquire));
|
||||
|
||||
// Pick tables to flush again
|
||||
autovector<MemTable*> to_flush3;
|
||||
autovector<ReadOnlyMemTable*> to_flush3;
|
||||
list.PickMemtablesToFlush(
|
||||
std::numeric_limits<uint64_t>::max() /* memtable_id */, &to_flush3);
|
||||
// Picks newest (fifth oldest)
|
||||
@@ -821,7 +824,7 @@ TEST_F(MemTableListTest, FlushPendingTest) {
|
||||
ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire));
|
||||
|
||||
// Nothing left to flush
|
||||
autovector<MemTable*> to_flush4;
|
||||
autovector<ReadOnlyMemTable*> to_flush4;
|
||||
list.PickMemtablesToFlush(
|
||||
std::numeric_limits<uint64_t>::max() /* memtable_id */, &to_flush4);
|
||||
ASSERT_EQ(0, to_flush4.size());
|
||||
@@ -891,7 +894,7 @@ TEST_F(MemTableListTest, FlushPendingTest) {
|
||||
memtable_id = 4;
|
||||
// Pick tables to flush. The tables to pick must have ID smaller than or
|
||||
// equal to 4. Therefore, no table will be selected in this case.
|
||||
autovector<MemTable*> to_flush5;
|
||||
autovector<ReadOnlyMemTable*> to_flush5;
|
||||
list.FlushRequested();
|
||||
ASSERT_TRUE(list.HasFlushRequested());
|
||||
list.PickMemtablesToFlush(memtable_id, &to_flush5);
|
||||
@@ -932,8 +935,8 @@ TEST_F(MemTableListTest, EmptyAtomicFlushTest) {
|
||||
autovector<MemTableList*> lists;
|
||||
autovector<uint32_t> cf_ids;
|
||||
autovector<const MutableCFOptions*> options_list;
|
||||
autovector<const autovector<MemTable*>*> to_flush;
|
||||
autovector<MemTable*> to_delete;
|
||||
autovector<const autovector<ReadOnlyMemTable*>*> to_flush;
|
||||
autovector<ReadOnlyMemTable*> to_delete;
|
||||
Status s = Mock_InstallMemtableAtomicFlushResults(lists, cf_ids, options_list,
|
||||
to_flush, &to_delete);
|
||||
ASSERT_OK(s);
|
||||
@@ -995,7 +998,7 @@ TEST_F(MemTableListTest, AtomicFlushTest) {
|
||||
cf_ids.push_back(cf_id++);
|
||||
}
|
||||
|
||||
std::vector<autovector<MemTable*>> flush_candidates(num_cfs);
|
||||
std::vector<autovector<ReadOnlyMemTable*>> flush_candidates(num_cfs);
|
||||
|
||||
// Nothing to flush
|
||||
for (auto i = 0; i != num_cfs; ++i) {
|
||||
@@ -1014,7 +1017,7 @@ TEST_F(MemTableListTest, AtomicFlushTest) {
|
||||
ASSERT_FALSE(list->IsFlushPending());
|
||||
ASSERT_FALSE(list->imm_flush_needed.load(std::memory_order_acquire));
|
||||
}
|
||||
autovector<MemTable*> to_delete;
|
||||
autovector<ReadOnlyMemTable*> to_delete;
|
||||
// Add tables to the immutable memtalbe lists associated with column families
|
||||
for (auto i = 0; i != num_cfs; ++i) {
|
||||
for (auto j = 0; j != num_tables_per_cf; ++j) {
|
||||
@@ -1041,7 +1044,7 @@ TEST_F(MemTableListTest, AtomicFlushTest) {
|
||||
autovector<MemTableList*> tmp_lists;
|
||||
autovector<uint32_t> tmp_cf_ids;
|
||||
autovector<const MutableCFOptions*> tmp_options_list;
|
||||
autovector<const autovector<MemTable*>*> to_flush;
|
||||
autovector<const autovector<ReadOnlyMemTable*>*> to_flush;
|
||||
for (auto i = 0; i != num_cfs; ++i) {
|
||||
if (!flush_candidates[i].empty()) {
|
||||
to_flush.push_back(&flush_candidates[i]);
|
||||
@@ -1122,7 +1125,7 @@ TEST_F(MemTableListWithTimestampTest, GetTableNewestUDT) {
|
||||
std::vector<MemTable*> tables;
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
uint64_t current_ts = 0;
|
||||
autovector<MemTable*> to_delete;
|
||||
autovector<ReadOnlyMemTable*> to_delete;
|
||||
std::vector<std::string> newest_udts;
|
||||
|
||||
std::string key;
|
||||
@@ -1162,7 +1165,7 @@ TEST_F(MemTableListWithTimestampTest, GetTableNewestUDT) {
|
||||
}
|
||||
|
||||
list.current()->Unref(&to_delete);
|
||||
for (MemTable* m : to_delete) {
|
||||
for (ReadOnlyMemTable* m : to_delete) {
|
||||
delete m;
|
||||
}
|
||||
to_delete.clear();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user