Compare commits

...
39 Commits
Author SHA1 Message Date
Sujit Maharjan 87b7675f59 Fixing crash test and other refactoring 2025-06-16 15:00:42 -07:00
virajthakur 4bdfb7e7da support canceling ongoing CompactFiles (#13687)
Summary:
Add an atomic bool to CompactionOptions to cancel an ongoing CompactFiles() operation, in the same fashion we do for CompactRange().

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13687

Test Plan: ./db_test2 --gtest_filter=DBTest2.TestCancelCompactFiles

Reviewed By: jaykorean

Differential Revision: D76538529

Pulled By: virajthakur

fbshipit-source-id: 77db5b4fb4cbd5280584834df28e51a72b084dab
2025-06-16 14:01:29 -07:00
Sujit Maharjan 2dcfc54752 Mixed compressor adding RandomCompressorManager to db_stress_test (#13691)
Summary:
**Summary:**
This pull request configures RocksDB to optionally utilize this customized compressor (RandomCompressor) in the db stress test. It randomly selects the compression algorithm among the blocks.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13691

Test Plan: Testing was performed by verifying the stdout output from both RandomCompressor.

Reviewed By: hx235

Differential Revision: D76624220

Pulled By: shubhajeet

fbshipit-source-id: d9c458eeee930b25e8a87a77dc29f0647836310e
2025-06-15 06:08:56 -07:00
Sujit Maharjan 504ff4ed55 Auto skip Compression (#13674)
Summary:
**Context:**
RocksDB's current compression approach rejects blocks if the compressed size exceeds a predefined threshold. To optimize performance, we aim to develop an algorithm that dynamically stops and resumes block compression attempts based on past rejection data.

**Summary:**
The goal of this milestone is to design, implement, and evaluate an algorithm that intelligently skips and resumes block compression attempts in RocksDB. The algorithm tracks whether randomly selected blocks was rejected, compressed or bypassed and using data of window size to determine the current rejection rate. The calculate rejection rate is used to decide whether to pause and resume compression attempts. We measure the effectiveness of skipping and resuming compression using DB bench and identify any concerning regressions in correctness and performance.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13674

Test Plan:
1. Test case to see if it can automatically start compression on compression friendly workload and see if it can automatically stop compression on non-compression friendly workload (auto_skip_compresor_test.cc)
3. Regression analysis to prove that no significant performance attempt

```bash
SUFFIX=`tty | sed 's|/|_|g'`; for ARGS in "-compression_parallel_threads=1 -compression_type=zstd -compression_manager=none"  "-compression_parallel_threads=4 -compression_type=zstd -compression_manager=none" "-compression_parallel_threads=1 -compression_type=zstd -compression_manager=autoskip"  "-compression_parallel_threads=4 -compression_type=zstd -compression_manager=autoskip" ; do echo $ARGS; (for I in `seq 1 20`; do ./db_bench -db=/dev/shm/dbbench$SUFFIX --benchmarks=fillseq -num=10000000 -compaction_style=2 -fifo_compaction_max_table_files_size_mb=1000 -fifo_compaction_allow_compaction=0 -disable_wal -write_buffer_size=12000000 $ARGS 2>&1 | grep micros/op; done) | awk '{n++; sum += $5;} END { print int(sum / n); }'; done
```
Measurement experiment | throughput (% change from main branch) |
|---------------|--------------------------------|
compression manager = none (main branch) | 1106890.35 ops/s
compression manager = none (auto skip) | 1097574.55 ops/s (-0.84%)
compression manager = auto skip (auto skip branch) | 1133432.9 ops/s (+2.4%)

Reviewed By: hx235

Differential Revision: D76220795

Pulled By: shubhajeet

fbshipit-source-id: 0f46ab34da1b451f8907306afba221503e6e22a5
2025-06-14 05:45:56 -07:00
Andrew Chang e3a91ec1e3 Add copy constructor and assignment operator to IODebugContext (#13690)
Summary:
Since `request_id` is a raw pointer to a string, copying `IODebugContext` becomes a little bit more complicated. We need to ensure that `request_id` gets its memory freed, but by we don't have ownership of the memory by default. The `request_id` inside `IODebugContext` is meant to point to a string allocated outside of the RocksDB read request. To get around this issue without refactoring `request_id`'s type entirely, we can store a private member variable and have `request_id` point to it, so the memory deallocation happens automatically for us.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13690

Test Plan:
I updated the `RequestIdPlumbingTest` unit test from https://github.com/facebook/rocksdb/issues/13616
```
./db_test --gtest_filter=DBTest.RequestIdPlumbingTest
```

Reviewed By: anand1976

Differential Revision: D76613051

Pulled By: archang19

fbshipit-source-id: 053a5b9c4cde20606ec7854ada29904bdf11d40c
2025-06-13 14:12:10 -07:00
Jiffin Tony Thottan 58420b7c60 include cstdint to trace_record.h (#13651)
Summary:
There are compilation errors on gcc 15 in fedora 42 while compiling ceph.

This is similar to PR https://github.com/facebook/rocksdb/issues/13573.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13651

Reviewed By: jaykorean

Differential Revision: D76062855

Pulled By: cbi42

fbshipit-source-id: d213debbda39fdfac01641daa567687fc104d260
2025-06-13 09:47:52 -07:00
Andrew Chang 945fcbe820 Add cost info field to IODebugContext (#13666)
Summary:
This field will be used internally to feed Warm Storage cost information back through the Sally IO stack. This is needed for cost accounting / reporting.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13666

Test Plan: I made the additional changes needed to set/record the new cost info field, and confirmed that this information could be fed through.

Reviewed By: anand1976

Differential Revision: D76070434

Pulled By: archang19

fbshipit-source-id: 2fab975f14fd8f7c20b5d0d85c31686ccf682068
2025-06-12 18:39:28 -07:00
Hui Xiao 02bce9b1af Reduce universal compaction input lock time by forwarding intended compaction and re-picking (#13633)
Summary:
**Context:**
RocksDB currently selects files for long-running compaction outputs to the bottommost level, preventing these selected files files from being selected, but does not execute the compaction immediately like other compactions. Instead, this compaction is forwarded to another Env::Priority::bottom thread pool, where it waits (potentially for a long time) until its thread is ready to execute. This extended L0 lock time in universal compaction caused our users write stall and read performance regression.

**Summary:**
This PR is to eliminate L0 lock time during bottom priority compaction waiting to execute by the following
- Create and forward an intended compaction only consists of last input file (or sorted run if non-L0) instead of all the input files. This eliminate the locking for non-bottommost level input files while waiting for bottom priority thread is up to run.
- Re-pick compaction that outputs to max output level when bottom priority thread is up to run
- Refactor universal compaction picking logic to make it cleaner and easier to force picking compaction with max output level when bottom priority thread is up to run
- Guard feature behind a temporary option as requested

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13633

Test Plan:
- New unit test to cover the case that's not covered by existing tests - bottom priority thread re-picks compaction ends up picking nothing due to LSM shape changes
- Adapted existing unit tests to verify various bottom priority compaction behavior with this new option
- Stress test `python3 tools/db_crashtest.py --simple blackbox --compaction_style=1 --target_file_size_base=1000 --write_buffer_size=1000 --compact_range_one_in=10000 --compact_files_one_in=10000 `

Reviewed By: cbi42

Differential Revision: D76005505

Pulled By: hx235

fbshipit-source-id: 9688f22d4a84f619452820f12f15b765c17301fd
2025-06-12 18:16:47 -07:00
Ryan4253 85910fb575 event_helpers logging symmetry improvements (#13669) (#13670)
Summary:
1. LogAndNotifyTableFileDeletion checks for null event logger like other functions
2. LogAndNotifyBlobFileCreationFinished and LogAndNotifyTablebFileCreationFinished log on success similar to deletions
3. LogAndNotify functions log status on success

## Verification
Ran the code on [kvrocks](https://github.com/apache/kvrocks/tree/unstable) which implements event hooks, and the logging is now observable / consistent.
```
2025/06/05-10:00:49.644611 92065 EVENT_LOG_v1 {"time_micros": 1749132049644595, "cf_name": "metadata", "job": 5, "event": "blob_file_creation", "file_number": 34, "total_blob_count": 68, "total_blob_bytes": 272018457, "file_checksum": "", "file_checksum_func_name": "Unknown", "status": "OK"}
```
```
2025/06/02-09:42:29.343893 122068 EVENT_LOG_v1 {"time_micros": 1748871749343853, "cf_name": "metadata", "job": 93, "event": "table_file_creation", "file_number": 853, "file_size": 0, "file_checksum": "", "file_checksum_func_name": "Unknown", "smallest_seqno": 23371, "largest_seqno": 24182, "table_properties": {"data_size": 0, "index_size": 0, "index_partitions": 0, "top_level_index_size": 0, "index_key_is_user_key": 0, "index_value_is_delta_encoded": 0, "filter_size": 0, "raw_key_size": 0, "raw_average_key_size": 0, "raw_value_size": 0, "raw_average_value_size": 0, "num_data_blocks": 0, "num_entries": 0, "num_filter_entries": 0, "num_deletions": 0, "num_merge_operands": 0, "num_range_deletions": 0, "format_version": 0, "fixed_key_len": 0, "filter_policy": "", "column_family_name": "", "column_family_id": 2147483647, "comparator": "", "user_defined_timestamps_persisted": 1, "key_largest_seqno": 18446744073709551615, "merge_operator": "", "prefix_extractor_name": "", "property_collectors": "", "compression": "", "compression_options": "", "creation_time": 0, "oldest_key_time": 0, "newest_key_time": 0, "file_creation_time": 0, "slow_compression_estimated_data_size": 0, "fast_compression_estimated_data_size": 0, "db_id": "", "db_session_id": "", "orig_file_number": 0, "seqno_to_time_mapping": "N/A"}, "oldest_blob_file_number": 821, "status": "Shutdown in progress: Database shutdown"}
```

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13670

Reviewed By: jaykorean

Differential Revision: D76173710

Pulled By: hx235

fbshipit-source-id: 1f81623c1edade0c122bd0e73391a1b76abc13d9
2025-06-12 13:52:30 -07:00
Jay Huh 96305d9bb4 Fix to enable --Wunreachable-code-break (#13686)
Summary:
As title

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13686

Test Plan: CI

Reviewed By: archang19

Differential Revision: D76518029

Pulled By: jaykorean

fbshipit-source-id: cb04d8a79edde8f122e02cf761a1d42c203347cd
2025-06-12 09:43:45 -07:00
Jay Huh 82586e293e Upgrade Maven to 3.9.10 (#13684)
Summary:
https://dlcdn.apache.org/maven/maven-3/3.9.6/binaries/apache-maven-3.9.6-bin.tar.gz is no longer available. Because of that, CI for JAVA has been broken.

https://github.com/facebook/rocksdb/actions/runs/15596243797/job/43927189803?pr=13683

Instead of finding a new place to download from, taking this opportunity to upgrade to 3.9.10.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13684

Test Plan: CI

Reviewed By: pdillinger, archang19

Differential Revision: D76474615

Pulled By: jaykorean

fbshipit-source-id: 3c05efb9e0ef381c97fa43dc3c9960b627c6dd59
2025-06-12 09:21:04 -07:00
Jay Huh 873f7fe535 Add MergeOperator UnitTest for Remote Compaction (#13683)
Summary:
As title. Simple Unit Test to check MergeOperator in Remote Compaction flow.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13683

Test Plan:
```
./compaction_service_test --gtest_filter="*CompactionServiceTest.MergeOperator*"
```

Reviewed By: hx235

Differential Revision: D76459146

Pulled By: jaykorean

fbshipit-source-id: 50956824d50c503e7166304a2d52f624bbdda7ec
2025-06-11 17:30:54 -07:00
Hui Xiao 37a26591c7 Print note about the large hard-coded num_level for manifest dump (#13681)
Summary:
**Context/Summary:**
Since LDB manifest dump including printing the LSM shape does not open the db and manifest itself does not have info about Options.num_levels, LDB tool (the only caller of `DumpManifestHandler` has to set a "hopefully-large-enough" level number (i.e,64) to print info of every level for the LSM shape in the manifest. This can mislead whoever that's reading the manifest to believe there are actually 64 levels configured with the CF. This PR clarifies that.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13681

Test Plan:
Manual test
`./ldb  manifest_dump --hex --verbose --json --path=<some manifest file path>`
```
--------------- Column family "9"  (ID 9) --------------
log number: 115873
comparator: leveldb.BytewiseComparator
 --- level 0 --- version# 19 ---
 --- level 1 --- version# 19 --- compact_cursor: '000000000000000900000000000000DF78787878787878' seq:3418519, type:2 ---
 --- level 2 --- version# 19 --- compact_cursor: '000000000000000900000000000000D8' seq:3446619, type:2 ---
 --- level 3 --- version# 19 --- compact_cursor: '000000000000000900000000000000DF78787878787878' seq:3418519, type:2 ---
 --- level 4 --- version# 19 --- compact_cursor: '000000000000000900000000000000DF78787878787878' seq:3418519, type:2 ---
 --- level 5 --- version# 19 --- compact_cursor: '0000000000000009000000000000012B0000000000000065' seq:3447830, type:2 ---
 --- level 6 --- version# 19 ---
 115931:376281[0 .. 0]['0000000000000000' seq:0, type:1 .. '00000000000003E7000000000000012B00000000000002B1' seq:0, type:1]
 --- level 7 --- version# 19 ---
 --- level 8 --- version# 19 ---
 --- level 9 --- version# 19 ---
 --- level 10 --- version# 19 ---
 --- level 11 --- version# 19 ---
....
 --- level 61 --- version# 19 ---
 --- level 62 --- version# 19 ---
 --- level 63 --- version# 19 ---
By default, manifest file dump prints LSM trees as if 64 levels were configured, which is not necessarily true for the column family (CF) this manifest is associated with. Please consult other DB files, such as the OPTIONS file, to confirm.

```

Reviewed By: jaykorean

Differential Revision: D76391064

Pulled By: hx235

fbshipit-source-id: 3e1c58e0eeb39a5fa020040201b07b181f8977a6
2025-06-11 17:14:14 -07:00
jeffzfzheng ab1fb6cf8e Fix overflow of data_size in WritableFileWriter::WriteBufferedWithChecksum (#13641)
Summary:
In the function WritableFileWriter::WriteBufferedWithChecksum, since the alignment parameter passed to RequestToken defaults to 4096, when data_size is less than 4096, subtracting a larger value from data_size (which is of type unsigned long) will cause an underflow. This results in an infinite loop. Since WriteBuffered does not require alignment, it is sufficient to pass alignment == 0.

issue:https://github.com/facebook/rocksdb/issues/13640

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13641

Reviewed By: jaykorean

Differential Revision: D76341973

Pulled By: hx235

fbshipit-source-id: 8912f2b6598bb5a48b6b813c53146d9ecfd31d30
2025-06-10 19:03:53 -07:00
Ryan4253 6403642c02 Add missing fields in BuildSubcompactionJobInfo (#13667) (#13668)
Summary:
As title, BuildSubcompactionJobInfo doesn't update compaction_reason, compression, and blob_compression_type. event listeners depend on this information.

## Verification
Ran the code on [kvrocks](https://github.com/apache/kvrocks/tree/unstable) which implements event hooks when subcompaction happens.

Before:
```
[2025-06-03T10:31:33.660798-04:00][I][event_listener.cc:119] [event_listener/subcompaction_begin] column family: metadata, job_id: 7, compaction reason: Unknown, output compression type: no
```

After:
```
[2025-06-03T10:31:33.660798-04:00][I][event_listener.cc:119] [event_listener/subcompaction_begin] column family: metadata, job_id: 7, compaction reason: LevelL0FilesNum, output compression type: no
```

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13668

Reviewed By: virajthakur

Differential Revision: D76173031

Pulled By: hx235

fbshipit-source-id: 3ec8f5b0cbd73b75d4dc98ca788b07c31a590b4d
2025-06-10 19:03:29 -07:00
Hui Xiao de376be2ba Simplify RoundRobinSubcompactionsAgainstResources.SubcompactionsUsingResources test (#13672)
Summary:
**Context/Summary:**
`RoundRobinSubcompactionsAgainstResources.SubcompactionsUsingResources` has been flaky and difficult to de-flake. One of the reasons is the complicated usage of sync points and unnecessarily strict verification.
- The sync points don't seem necessary to verify the number of extra reserved threads for sub-compactions so are removed.
- The full reservation after compaction to verify extra reserved threads were release is indirect and hard to get right. So it's replaced with simpler sync-point callback check.
    - Since we already have tests (see https://github.com/facebook/rocksdb/blob/7d80ea45442e84c25669db61cb7376ba0cd10ba5/env/env_test.cc#L841 and )for testing pure functionality of reserve/release does reserve/release the threads, verifying the relevant code paths are called should be enough to verify extra reserved threads were released after compaction

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13672

Test Plan: Monitor future flakiness.

Reviewed By: cbi42

Differential Revision: D76108242

Pulled By: hx235

fbshipit-source-id: 30113f16455688f113f296bda0098a66a7a198a3
2025-06-06 12:40:45 -07:00
Sujit Maharjan adb750fdf4 Separating into cc and header file for simple_mixed_compressor.h (#13665)
Summary:
**Summary**
This pull request fixes the issue of having a single file simple_mixed_compressor.h containing both implementation and declaration. To improve code organization and follow best practices, I have separated the implementation into a new file simple_mixed_compressor.cc and updated the original file to only contain the necessary declarations.

**Testing**
Testing was performed by verifying the stdout output from both RoundRobinCompressor and BuiltInCompressorV2.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13665

Reviewed By: pdillinger

Differential Revision: D76060831

Pulled By: shubhajeet

fbshipit-source-id: c034868be51ea7b89c1a8dd12082b0159f49f588
2025-06-06 09:32:12 -07:00
Sujit Maharjan 20d051a00e Support for mixed compression type (round robin) in benchmark (#13655)
Summary:
**Summary**
This pull request aims to enhance the functionality of DB bench by introducing ability to use custom compression manager **mixed** that RoundRobin betweens all the compression algorithm within SST blow. The pull request also introduces the **same_value_percentage** that increases the probability of the generate value to be same.

**Verification:**
Manually verified the injection of custom compression manager by setting breakpoint in the debugger.
Verified the effectiveness of the tunable parameter
```bash
#!/bin/bash

# Script to run db_bench with different parameter combinations
# Parameters varied:
# - compression_manager: mixed, none
# - same_value_percentage: 0, 50, 100
#
# To make this script executable, run: chmod +x run_db_bench.sh

# Exit on error
set -e

# Check if db_bench exists
if [ ! -f "./db_bench" ]; then
    echo "Error: db_bench executable not found in current directory"
    exit 1
fi

# Define parameter arrays
compression_managers=("mixed" "none")
same_value_percentages=(0 50 100)

# Create output directory if it doesn't exist
mkdir -p results

# Loop through all combinations
for cm in "${compression_managers[@]}"; do
    for svp in "${same_value_percentages[@]}"; do
        # Define output file
        output_file="results/bench_${cm}_${svp}.log"

        echo "Running with compression_manager=${cm}, same_value_percentage=${svp}"
        # Run db_bench with current parameters
        ./db_bench -db=/dev/shm/dbbench \
            --benchmarks=fillseq \
            -num=10000000 \
            -compaction_style=2 \
            -fifo_compaction_max_table_files_size_mb=1000 \
            -fifo_compaction_allow_compaction=0 \
            -disable_wal \
            -write_buffer_size=12000000 \
            -compression_type=zstd \
            -compression_parallel_threads=1 \
            -compression_manager="${cm}" \
            -same_value_percentage="${svp}" \
            --stats_level=5 \
            --statistics > "${output_file}" 2>&1

        echo "Completed. Results saved to ${output_file}"
    done
done

echo "All benchmarks completed successfully!"

```
**Result**
compression manager | same_value_percentage | compressed byte from | compressed bytes to | ratio | compression time nanos sum | count | avg (compression time)
-- | -- | -- | -- | -- | -- | -- | --
mixed | 0 | 1203147502 | 637471319 | 1.887375112 | 37314989743 | 299756 | 124484.5466
mixed | 50 | 1203412251 | 398088802 | 3.022974384 | 34026215298 | 299846 | 113478.9702
mixed | 100 | 1206024000 | 109625322 | 11.00132686 | 20307741897 | 300557 | 67567.02355
none | 0 | 1209573133 | 559497700 | 2.161891162 | 6379855390 | 301301 | 21174.3585
none | 50 | 1209478701 | 348595024 | 3.469581083 | 4289921941 | 301295 | 14238.2779
none | 100 | 1209380499 | 72681369 | 16.63948431 | 2147469616 | 301303 | 7127.275918

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13655

Reviewed By: hx235

Differential Revision: D76092113

Pulled By: shubhajeet

fbshipit-source-id: 4a4e998650d78bfe1651257cb2f1b97016dcec56
2025-06-06 08:23:03 -07:00
anand76 7d80ea4544 Fix iterator errors for CFs with disallow_memtable_writes (#13663)
Summary:
Iterator seek returns "SeekAndValidate() not implemented" error if the disallow_memtable_writes CF option is set along with paranoid_memory_checks. The fix is to sanitize the paranoid_memory_checks option to false, which should be safe since the memtable is guaranteed to be empty.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13663

Test Plan: Update unit test in db_basic_test.cc

Reviewed By: pdillinger

Differential Revision: D75973515

Pulled By: anand1976

fbshipit-source-id: 3f381f19dcda72e3b78ee375f755fb4809c6b99c
2025-06-04 17:46:56 -07:00
Peter Dillinger eaa4f9d23b Fix tests broken by gtest upgrade (#13661)
Summary:
Some tests were failing due to apparent missing include of iomanip. I suspect this was from a gtest upgrade, because in open source, the include iomanip comes from gtest.h. To ensure we maintain compatibility with older gtest as well as the newer one, I pulled the include iomanip out of the in-repo gtest.h. Note that other places in gtest code only instantiate floating-point related templates with `float` and `double` types.

Also, to avoid `make format` being insanely slow on gtest.h, I've excluded third-party from the formatting check.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13661

Test Plan: make check, internal CI, manually ensure formatting check works outside of third-party/

Reviewed By: jaykorean

Differential Revision: D75963897

Pulled By: pdillinger

fbshipit-source-id: ed5737dd456e74068185f1ac5d57046d7509df7a
2025-06-04 10:44:17 -07:00
Sujit Maharjan fccc881894 Implement MixedCompressor that Round robins on compression algorithm (#13647)
Summary:
**Summary**
This pull request introduces a mixed compressor, RoundRobinManager and RoundRobinCompressor, which selects algorithms in a loop. This implementation replaces the current hacky approach to round-robin compression in BuiltInCompressorV2. Additionally, it configures RocksDB to optionally utilize this customized compressor in the db stress test.

**Testing**
Testing was performed by verifying the stdout output from both RoundRobinCompressor and BuiltInCompressorV2.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13647

Reviewed By: pdillinger

Differential Revision: D75921997

Pulled By: shubhajeet

fbshipit-source-id: 8f42ac46f08ba982b2cd70241bd7dc13ff5a1225
2025-06-04 10:18:44 -07:00
Changyu Bi 0119a8c78b Fix Checkpoint::ExportColumnFamily() returning staled data (#13654)
Summary:
`Checkpoint::ExportColumnFamily()` calls DB::Flush() before getting all SST file metadata through `GetColumnFamilyMetaData()`. `GetColumnFamilyMetaData()` gets metadata through the SuperVersion but Flush() does not guarantee the flush result is reflected in SuperVersion upon return (explained below). This PR updates `GetColumnFamilyMetaData()` to get metadata from version instead. Since `GetColumnFamilyMetaData()` [acquires db mutex](https://github.com/facebook/rocksdb/blob/0c533e61bc6d89fdf1295e8e0bcee4edb3aef401/db/db_impl/db_impl.cc#L5023-L5031), it should not need to acquire SV anyway.

Fixes https://github.com/facebook/rocksdb/issues/13652.

Here we explain how Flush(wait=true) does not guarantee that the flush result is in SuperVersion when the call returns.
- RocksDB uses group commit to do MANIFEST update.
- When a flush tries to install its flush result, it may be done by another MANIFEST writer.
- MANIFEST write is done atomically together with updating Version and cfd->imm() (the list of immutable memtables), but it does not install new SuperVresion
- When the MANIFEST writer releases db mutex, the flush wait thread finds that cfd->imm() does not have the relevant memtable anymore: https://github.com/facebook/rocksdb/blob/09175119d2464d7ceecdf1cb7d6d5b517b730965/db/db_impl/db_impl_compaction_flush.cc#L2739-L2742

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13654

Test Plan: the repro in https://github.com/pcholakov/rocksdb/commit/a52d426e82ff5a3dd181dbd5d676dbb54080f5fa pass after this change.

Reviewed By: hx235

Differential Revision: D75795658

Pulled By: cbi42

fbshipit-source-id: 4f10baff67944bcd762cf0d237d653a8a35dbca3
2025-06-04 10:08:46 -07:00
Zaidoon Abd Al Hadi 9727956436 Expose Options::memtable_avg_op_scan_flush_trigger via C API (#13631)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/13631

Reviewed By: pdillinger

Differential Revision: D75928433

Pulled By: cbi42

fbshipit-source-id: d9f13a17058cfac68e380ea7d227aa8197b1d028
2025-06-04 10:03:23 -07:00
Peter Dillinger 09175119d2 Allow SmallEnumSet on larger enum types (#13657)
Summary:
... to support SmallEnumSet over CompressionType with allowed custom compression types using most of the available byte. This is accomplished using an std::array<uint64_t> in place of just uint64_t. Also adds an std::bitset-like count() operation.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13657

Test Plan: unit tests included

Reviewed By: hx235

Differential Revision: D75827601

Pulled By: pdillinger

fbshipit-source-id: 519ae97ac671fd9885d6485976abbd969d1392d3
2025-06-03 19:03:38 -07:00
Sujit Maharjan 20d065d940 Populate Missing Compaction Input Statistics (#13637)
Summary:
**Summary**
This pull request aims to populate num_input_files and total_input_bytes in the CompactionJobStats object, which is accessible through EventListener::OnCompactionBegin(DB*, const CompactionJobInfo&). This change will enable RocksDB users to access accurate compaction input information.

**Context/Goals**
Provide accurate compaction input statistics to RocksDB users
Populate num_input_files and total_input_bytes in CompactionJobStats
Ensure correct population of these fields before EventListener::OnCompactionBegin() is called

**Test Plan**
Added test code to capture num_input_file and total_num_bytes when EventHandler is triggered
Asserted that these values are populated correctly

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13637

Reviewed By: cbi42

Differential Revision: D75690774

Pulled By: shubhajeet

fbshipit-source-id: 8236546f8ce7743f46048b302b376b7ef6429887
2025-06-02 15:36:32 -07:00
Peter Dillinger 0c533e61bc Fix XPRESS compression and enable in CI (#13649)
Summary:
Somehow this was previously not being tested in our Windows CI jobs so was accidentally broken in https://github.com/facebook/rocksdb/pull/13540  This fix will need to be backported to 10.3.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13649

Test Plan: CI

Reviewed By: hx235

Differential Revision: D75655418

Pulled By: pdillinger

fbshipit-source-id: a56bb213270904a1b7a13b905c2cc1919116df1c
2025-05-29 22:32:10 -07:00
Hui Xiao 6efdbe85e3 Detailed comment about setting ZSTD compression type for mixed compression (#13653)
Summary:
**Context/Summary:** .... to clarify things more explicitly

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13653

Test Plan: no code change

Reviewed By: pdillinger

Differential Revision: D75655419

Pulled By: hx235

fbshipit-source-id: d9ee2e669df15aacf7996a3122c382412b23229e
2025-05-29 21:12:05 -07:00
Peter Dillinger 7b2b4b7c53 Save some missing CompressionOptions to table properties (#13646)
Summary:
Also revamping test
GeneralTableTest::ApproximateOffsetOfCompressed so that it's not sensitive to adding new metadata to SST files

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13646

Test Plan: manually inspect new table property, which is not parsed anywhere, just for information to human reader

Reviewed By: hx235

Differential Revision: D75561241

Pulled By: pdillinger

fbshipit-source-id: c076c01a8b540bc4cb771964d48fa919c4c48ae4
2025-05-28 18:38:15 -07:00
Mahmood Ali f2a8ee8ff2 get block_based_table_builder.cc to compile on c++23 (#13638)
Summary:
Get table/block_based/block_based_table_builder.cc to compile on c++23 on clang, by re-ordering BlockBasedTableBuilder::Rep and BlockBasedTableBuilder::ParallelCompressionRep definitions.

Clang `--std=c++23` changed behavior of unique_ptr<> with incomplete types. Now, constructor/destructures involving types with unique_ptr fields, must have access to the complete type; and thus must be defined after all its dependencies: See [godbolt link for behavior](https://godbolt.org/#z:OYLghAFBqd5QCxAYwPYBMCmBRdBLAF1QCcAaPECAMzwBtMA7AQwFtMQByARg9KtQYEAysib0QXACx8BBAKoBnTAAUAHpwAMvAFYTStJg1DIApACYAQuYukl9ZATwDKjdAGFUtAK4sGe1wAyeAyYAHI%2BAEaYxBIAbKQADqgKhE4MHt6%2BekkpjgJBIeEsUTFc8XaYDmlCBEzEBBk%2Bfly2mPZ5DDV1BAVhkdFxtrX1jVktCsM9wX3FA2UAlLaoXsTI7BzmAMzByN5YANQmm25sLCQAnkfYJhoAgje3u0wKCvsAkgxoLAn0BJhHVjuDyeL32t0OAHZAbcAPQw/YI24QCboEAgLwMPAARy8mAA%2BgkCMQjm4Pl8fpg/ld9kx5gCHojkQRUejMTj8YTiccyahvr9/ptsDT5iAaXimSyzgA3TAQWnzSFWCEAEQZ%2BxRaIx2NxBKJJJ5fMpAqFTDx9KBKvN9zuINeBopf0VJktm2hDzh%2BwxXzYgn2RH25LomH2wQD1msZk2%2BzlDHQ%2B2ImGlwYICGDwVo0zjaAYE2IXgcJH2WBomI6dLuHtuaKRGtZ2o5eu5n15DuNwv2otNErRSbl8wVzqVqqBd2CBH2LCYwQgA6haoRCYIKwY%2Bw0Vudyo4i1onAArLw/BwtKRUJw3OHLOrlqtg1seKQCJot4sANYgMy7/ScSQHp8nzi8AoIAaA%2BT6LHAsBIJgqiVF4RBkBQcrEMACjKIYbRCAgqAAO6HvegYGB0aEhLQmE4Yex4EQMTxGFwAAcXAgVRxChKw6y8MxADycFkbhf7QZUtzIUBHC8AJyA1Pgh68PwggiGI7BSDIgiKCo6hHjoegGEYKAXjYGYREBkCLKghJpCJAC0FkokcyqmJYEabLwqAysQxB4FgRkzqQeaCHgbAACqoJ4XmLAo15rHoKLBMRGFYXx3C8NhxBMAknA8Nue6/hp/4cNgMHIHBhaqHRsQWbEkgBtpwD7PRAB0XB1Ro0bng5likPsuCEIWd7zLwj4af2pBvh%2BX4cD%2BpAUc5AG2MBoGDaQEGICA4lFQhlB1ChsWkfFeEcS2dBMER6E7eRf7MSg1X0YxpDMaxbAzVxPG7fxBVCShM3iZJwQzbJwiiOISl/apah/roZj6IYxh6foeCGfAJlmQIlnWcytn2VYlhmMeLnRO5nkIz5GKOIFwW0KFSwrJF4zMjFJ28XtpDJal6VbmN%2B6TX%2Bp55QVa37CVZUVVVUO1XRDVNS1MOdfg8GHJGXB9fNWhDSNkh1QAnJrWva9r8Q7uN2U4zNQEgQNyuLTAy2rfB5AbcJ20M6zt0HYRaQOy9OUXcAXC7i0d1sY9LvRNxwge8e4nvSJYkFd90nKf9CkSNIwNKKDOW6JskM6Rj1iw/DxknkjOacFZNmbHZemRs5rn45gFO%2BSTmBBSFhPhVTilDFJ7tnYlTMpWliWZRwHNTbl%2BWwbLAvlZVwDIMgtW7g1kttTY0vdcQcubAr/Vga%2B76fvrE2j9zgFzWbz5jWYhvTaJSsX65KTOJIQA%3D%3D%3D).

Interestingly, `gcc --std=c++23` accepts the code as-is.

Fixes https://github.com/facebook/rocksdb/issues/13574

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13638

Reviewed By: hx235

Differential Revision: D75472325

Pulled By: cbi42

fbshipit-source-id: 671df558cc0a54db94b7cc4af46591cd33c32ad6
2025-05-27 16:34:04 -07:00
Peter Dillinger 7208116105 Update API comments for mutable tiering options (#13642)
Summary:
Mutable as described in 9.11 release notes

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13642

Test Plan: already tested in tiered_compaction_test; search for ApplyConfigChange

Reviewed By: jowlyzhang

Differential Revision: D75458238

Pulled By: pdillinger

fbshipit-source-id: a2aa7273dbdc7be95aceed76edf502f883130172
2025-05-27 10:41:09 -07:00
Changyu Bi 11631c0609 Update default value for large txn options (#13636)
Summary:
to make it easier to use 0 for disabled. And deprecate the use of txn db option `txn_commit_bypass_memtable_threshold`.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13636

Test Plan: updated unit tests.

Reviewed By: jowlyzhang

Differential Revision: D75262136

Pulled By: cbi42

fbshipit-source-id: 9040e5a9c918c1d0906a2db4600cc012d2436b22
2025-05-22 20:03:51 -07:00
Changyu Bi a00391c729 Enable large txn optimization by transaction write batch size (#13634)
Summary:
Larger key/values can cause memtable write to take longer time. Add new option `TransactionOptions::large_txn_commit_optimize_byte_threshold` that enables the optimization by transaction write batch size.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13634

Test Plan:
- new unit test
- added option to stress test and ran stress test for some time: `python3 ./tools/db_crashtest.py --txn blackbox  --txn_write_policy=0 --commit_bypass_memtable_one_in=50 --test_batches_snapshots=0`

Reviewed By: jowlyzhang

Differential Revision: D75248126

Pulled By: cbi42

fbshipit-source-id: 9522db93457729ba60e4176f7d47f7c2c7778567
2025-05-22 17:29:23 -07:00
Changyu Bi 1d94aeea44 Refactor snapshot context into JobContext and fix deadlock on db mutex in WP/WUP (#13632)
Summary:
With WP/WUP, we can deadlock on db mutex here: https://github.com/facebook/rocksdb/blob/8dc3d77b591443e405b2b171b3eb4f8461ffd2a3/db/db_impl/db_impl_compaction_flush.cc#L4626. Here we release a snapshot (which will acquire db mutex) while already holding the mutex. This caused some transaction lock timeout error in crash test. This PR fixes this by refactoring snapshot related context into JobContext and only allow snapshot related context to be initialized once. This also reduces the number of parameters being passed around.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13632

Test Plan:
- existing tests
- this fails with timeout before this fix
```
./db_stress --WAL_size_limit_MB=0 --WAL_ttl_seconds=60 --acquire_snapshot_one_in=10000 --adaptive_readahead=1 --adm_policy=2 --advise_random_on_open=1 --allow_data_in_errors=True --allow_fallocate=0 --allow_unprepared_value=1 --async_io=0 --auto_readahead_size=0 --auto_refresh_iterator_with_snapshot=0 --avoid_flush_during_recovery=0 --avoid_flush_during_shutdown=1 --avoid_unnecessary_blocking_io=0 --backup_max_size=104857600 --backup_one_in=100000 --batch_protection_bytes_per_key=8 --bgerror_resume_retry_interval=1000000 --block_align=0 --block_protection_bytes_per_key=4 --block_size=16384 --bloom_before_level=6 --bloom_bits=2 --bottommost_compression_type=lz4 --bottommost_file_compaction_delay=0 --bytes_per_sync=0 --cache_index_and_filter_blocks=1 --cache_index_and_filter_blocks_with_high_priority=0 --cache_size=33554432 --cache_type=fixed_hyper_clock_cache --charge_compression_dictionary_building_buffer=1 --charge_file_metadata=0 --charge_filter_construction=1 --charge_table_reader=0 --check_multiget_consistency=1 --check_multiget_entity_consistency=0 --checkpoint_one_in=0 --checksum_type=kxxHash --clear_column_family_one_in=0 --commit_bypass_memtable_one_in=0 --compact_files_one_in=1000 --compact_range_one_in=1000000 --compaction_pri=1 --compaction_readahead_size=0 --compaction_style=1 --compaction_ttl=0 --compress_format_version=2 --compressed_secondary_cache_ratio=0.0 --compressed_secondary_cache_size=0 --compression_checksum=1 --compression_max_dict_buffer_bytes=0 --compression_max_dict_bytes=0 --compression_parallel_threads=1 --compression_type=zlib --compression_use_zstd_dict_trainer=0 --compression_zstd_max_train_bytes=0 --continuous_verification_interval=0 --create_timestamped_snapshot_one_in=0 --daily_offpeak_time_utc= --data_block_index_type=1 --db_write_buffer_size=0 --decouple_partitioned_filters=1 --default_temperature=kWarm --default_write_temperature=kHot --delete_obsolete_files_period_micros=30000000 --delpercent=5 --delrangepercent=0 --destroy_db_initially=1 --detect_filter_construct_corruption=1 --disable_file_deletions_one_in=1000000 --disable_manual_compaction_one_in=1000000 --disable_wal=0 --dump_malloc_stats=1 --enable_checksum_handoff=0 --enable_compaction_filter=0 --enable_custom_split_merge=0 --enable_do_not_compress_roles=0 --enable_index_compression=0 --enable_memtable_insert_with_hint_prefix_extractor=0 --enable_pipelined_write=0 --enable_remote_compaction=0 --enable_sst_partitioner_factory=0 --enable_thread_tracking=0 --enable_write_thread_adaptive_yield=1 --error_recovery_with_no_fault_injection=0 --exclude_wal_from_write_fault_injection=1 --fifo_allow_compaction=1 --file_checksum_impl=none --file_temperature_age_thresholds= --fill_cache=1 --flush_one_in=1000 --format_version=3 --get_all_column_family_metadata_one_in=10000 --get_current_wal_file_one_in=0 --get_live_files_apis_one_in=10000 --get_properties_of_all_tables_one_in=100000 --get_property_one_in=1000000 --get_sorted_wal_files_one_in=0 --hard_pending_compaction_bytes_limit=274877906944 --high_pri_pool_ratio=0 --index_block_restart_interval=2 --index_shortening=1 --index_type=0 --ingest_external_file_one_in=0 --ingest_wbwi_one_in=0 --initial_auto_readahead_size=16384 --inplace_update_support=0 --iterpercent=10 --key_len_percent_dist=1,30,69 --key_may_exist_one_in=100 --last_level_temperature=kWarm --level_compaction_dynamic_level_bytes=0 --lock_wal_one_in=1000000 --log2_keys_per_lock=10 --log_file_time_to_roll=60 --log_readahead_size=16777216 --long_running_snapshots=1 --low_pri_pool_ratio=0 --lowest_used_cache_tier=1 --manifest_preallocation_size=0 --manual_wal_flush_one_in=0 --mark_for_compaction_one_file_in=0 --max_auto_readahead_size=0 --max_background_compactions=20 --max_bytes_for_level_base=10485760 --max_key=100000 --max_key_len=3 --max_log_file_size=1048576 --max_manifest_file_size=1073741824 --max_sequential_skip_in_iterations=1 --max_total_wal_size=0 --max_write_batch_group_size_bytes=16777216 --max_write_buffer_number=3 --max_write_buffer_size_to_maintain=8388608 --memtable_insert_hint_per_batch=1 --memtable_max_range_deletions=1000 --memtable_op_scan_flush_trigger=100 --memtable_prefix_bloom_size_ratio=0 --memtable_protection_bytes_per_key=1 --memtable_whole_key_filtering=0 --memtablerep=skip_list --metadata_charge_policy=0 --metadata_read_fault_one_in=0 --metadata_write_fault_one_in=0 --min_write_buffer_number_to_merge=2 --mmap_read=1 --mock_direct_io=False --nooverwritepercent=1 --num_file_reads_for_auto_readahead=0 --open_files=500000 --open_metadata_read_fault_one_in=8 --open_metadata_write_fault_one_in=0 --open_read_fault_one_in=32 --open_write_fault_one_in=0 --ops_per_thread=200000 --optimize_filters_for_hits=1 --optimize_filters_for_memory=1 --optimize_multiget_for_io=1 --paranoid_file_checks=1 --paranoid_memory_checks=0 --partition_filters=0 --partition_pinning=0 --pause_background_one_in=10000 --periodic_compaction_seconds=0 --prefix_size=-1 --prefixpercent=0 --prepopulate_block_cache=0 --preserve_internal_time_seconds=60 --progress_reports=0 --promote_l0_one_in=0 --read_amp_bytes_per_bit=0 --read_fault_one_in=32 --readahead_size=524288 --readpercent=50 --recycle_log_file_num=0 --reopen=20 --report_bg_io_stats=1 --reset_stats_one_in=1000000 --sample_for_compression=5 --secondary_cache_fault_one_in=0 --snapshot_hold_ops=100000 --soft_pending_compaction_bytes_limit=68719476736 --sqfc_name=bar --sqfc_version=0 --sst_file_manager_bytes_per_sec=104857600 --sst_file_manager_bytes_per_truncate=1048576 --stats_dump_period_sec=10 --stats_history_buffer_size=0 --strict_bytes_per_sync=0 --subcompactions=4 --sync=0 --sync_fault_injection=0 --table_cache_numshardbits=6 --target_file_size_base=524288 --target_file_size_multiplier=2 --test_batches_snapshots=0 --test_ingest_standalone_range_deletion_one_in=0 --top_level_index_pinning=0 --two_write_queues=1 --txn_write_policy=2 --uncache_aggressiveness=2 --universal_max_read_amp=10 --unordered_write=0 --unpartitioned_pinning=1 --use_adaptive_mutex=1 --use_adaptive_mutex_lru=0 --use_attribute_group=0 --use_delta_encoding=0 --use_direct_io_for_flush_and_compaction=0 --use_direct_reads=0 --use_full_merge_v1=0 --use_get_entity=0 --use_merge=1 --use_multi_cf_iterator=0 --use_multi_get_entity=0 --use_multiget=1 --use_optimistic_txn=0 --use_put_entity_one_in=0 --use_sqfc_for_range_queries=1 --use_timed_put_one_in=0 --use_txn=1 --use_write_buffer_manager=0 --user_timestamp_size=0 --value_size_mult=32 --verification_only=0 --verify_checksum=1 --verify_checksum_one_in=1000 --verify_compression=1 --verify_db_one_in=10000 --verify_file_checksums_one_in=0 --verify_iterator_with_expected_state_one_in=5 --verify_sst_unique_id_in_manifest=1 --wal_bytes_per_sync=0 --wal_compression=zstd --write_buffer_size=4194304 --write_dbid_to_manifest=1 --write_fault_one_in=0 --write_identity_file=0 --writepercent=35 --db=/dev/shm/rocksdb_test/rocksdb_crashtest_whitebox
```

Reviewed By: hx235

Differential Revision: D75173149

Pulled By: cbi42

fbshipit-source-id: ec68cadc78469730dfe26824e20b8ca4ab993101
2025-05-22 09:42:15 -07:00
Peter Dillinger 8dc3d77b59 Experimental, preliminary support for custom CompressionManager (#13626)
Summary:
This exposes CompressionManager and related classes to the public API and adds `ColumnFamilyOptions::compression_manager` for tying a custom compression strategy to a column family. At the moment, this does not support custom/pluggable compression algorithms, just custom strategies around the built-in algorithms, e.g. which compression to use when and where.

A large part of the change is moving code from internal compression.h to a new public header advanced_compression.h, with some minor changes:
* `Decompressor::ExtractUncompressedSize()` is out-of-lined
* CompressionManager inherits Customizable and some related changes to members of CompressionManager are made. (Core functionality of CompressionManager is unchanged.)

This depends on a smart pointer I'm calling `ManagedPtr` which I'm adding to data_structure.h.

Additionally, advanced_compression.h gets CompressorWrapper and CompressionManagerWrapper as building blocks for overriding aspects of compression strategy while leveraging existing compression algorithms / schemas.

Some pieces needed to support the `compression_manager` option and rudimentary Customizable implementation are included. More work will be needed to make this general and well-behaved (see e.g. https://github.com/facebook/rocksdb/issues/8641; I still hit inscrutible problems every time I touch Customizable).

I'll add a release note for the experimental feature once pluggable compression algorithms and more of the Customizable things are working.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13626

Test Plan:
Added a unit test demonstrating how a custom compressor can "bypass" or "reject" compressions.

Expected next follow-up (probably someone else): use a custom CompressionManager/Compressor to replace the internal hack for testing mixed compressions.

Reviewed By: hx235

Differential Revision: D75028850

Pulled By: pdillinger

fbshipit-source-id: 8565bb8ba4b5fa923b1e29e76b4f7bb4faa42381
2025-05-21 10:09:46 -07:00
Peter Dillinger 09cd25f763 Fix another format compatibility failure (#13628)
Summary:
Some specific old versions around RocksDB 2.5 would compress the metaindex and properties blocks. This hasn't been done since, probably because it interferes with the properties block indicating how to set up for decompression (so the reader can read those blocks before doing any decompression).

To fix backward compatibility, we establish a decompressor early if format_version indicates the file could come from a sufficiently old version.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13628

Test Plan: local and CI runs of tools/check_format_compatible.sh. (I don't believe we need special code to set up a unit test for this case.)

Reviewed By: jowlyzhang

Differential Revision: D75107623

Pulled By: pdillinger

fbshipit-source-id: 97132b8c5e0602e8e27254a11386d866b23cb4f5
2025-05-20 18:50:56 -07:00
Changyu Bi 5bc8abc0ec New CF option to trigger flush based on average cost of scanning memtable (#13593)
Summary:
This PR introduces a new CF option, `memtable_avg_op_scan_flush_trigger`, to support triggering a memtable flush when an iterator skips too many invisible keys from the active memtable. This is a follow up to https://github.com/facebook/rocksdb/pull/13523#discussion_r2038261975, which introduced the option `memtable_op_scan_flush_trigger` for a single expensive iterator step. This PR focus on an expensive stretch of iterator steps, between Seeks and until iterator destruction. To avoid triggering a memtable flush for a stretch that is too small, this option only takes effect when the total number of entries skipped from the active memtable in a stretch of iterator steps exceeds `memtable_op_scan_flush_trigger`.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13593

Test Plan:
* New unit tests covering the new option
* Add the option to the crash test.

Reviewed By: hx235

Differential Revision: D74434263

Pulled By: cbi42

fbshipit-source-id: 64f1101efb79c7498e2038eff630713ead8f6f41
2025-05-20 15:49:01 -07:00
Jay Huh f91f6bd78e Include file_size in CompactionServiceOutputFile (#13620)
Summary:
Instead of using FileSystem::GetFileSize() for each CompactionOutputFile, use the file size that is being tracked internally as part of the output file's metadata. FileSize is now part of `CompactionServiceOutputFile` and serialized in the `CompactionServiceResult`

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13620

Test Plan:
Tested with logging Meta's internal offload Infra

```
./compaction_job_test
```

Reviewed By: jowlyzhang

Differential Revision: D75006961

Pulled By: jaykorean

fbshipit-source-id: 008f9dc22bd672746ac180380ada4188713a6b85
2025-05-19 15:33:59 -07:00
Peter Dillinger 7c9e50e37d check_format_compatible.sh fix (#13625)
Summary:
After I broke it in https://github.com/facebook/rocksdb/issues/13622

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13625

Test Plan: manual run of check_format_compatible.sh

Reviewed By: jowlyzhang

Differential Revision: D75003768

Pulled By: pdillinger

fbshipit-source-id: 6734ae5a8c9034a1e08230a840a04a4a2d7d6a15
2025-05-19 09:44:04 -07:00
Peter Dillinger 2ea356d0be Start 10.4 release development, and more (#13622)
Summary:
Usual release steps
* Release notes from 10.3 branch
* Update version.h
* Add 10.3.fb to check_format_compatible.sh
* Update folly commit hash. Added a few hacks to fix build errors.

Bonus:
* Add a check_format_compatible.sh sanity check to the per-PR GitHub actions jobs. It should be quick enough and catch typos in release diffs as we've seen in the past.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13622

Test Plan: CI

Reviewed By: jowlyzhang

Differential Revision: D74943843

Pulled By: pdillinger

fbshipit-source-id: 4ff1db9a635e111f8830cadff2d3ee51cf2de512
2025-05-17 21:21:14 -07:00
112 changed files with 4425 additions and 1966 deletions

No files matched your search

+4 -4
View File
@@ -4,8 +4,8 @@ runs:
steps:
- name: Install Maven
run: |
wget --no-check-certificate https://dlcdn.apache.org/maven/maven-3/3.9.6/binaries/apache-maven-3.9.6-bin.tar.gz
tar zxf apache-maven-3.9.6-bin.tar.gz
echo "export M2_HOME=$(pwd)/apache-maven-3.9.6" >> $GITHUB_ENV
echo "$(pwd)/apache-maven-3.9.6/bin" >> $GITHUB_PATH
wget --no-check-certificate https://dlcdn.apache.org/maven/maven-3/3.9.10/binaries/apache-maven-3.9.10-bin.tar.gz
tar zxf apache-maven-3.9.10-bin.tar.gz
echo "export M2_HOME=$(pwd)/apache-maven-3.9.10" >> $GITHUB_ENV
echo "$(pwd)/apache-maven-3.9.10/bin" >> $GITHUB_PATH
shell: bash
@@ -38,7 +38,7 @@ runs:
$env:Path = $env:JAVA_HOME + ";" + $env:Path
mkdir build
cd build
& cmake -G "$Env:CMAKE_GENERATOR" -DCMAKE_BUILD_TYPE=Debug -DOPTDBG=1 -DPORTABLE="$Env:CMAKE_PORTABLE" -DSNAPPY=1 -DJNI=1 ..
& cmake -G "$Env:CMAKE_GENERATOR" -DCMAKE_BUILD_TYPE=Debug -DOPTDBG=1 -DPORTABLE="$Env:CMAKE_PORTABLE" -DSNAPPY=1 -DXPRESS=1 -DJNI=1 ..
if(!$?) { Exit $LASTEXITCODE }
cd ..
echo "Building with VS version: $Env:CMAKE_GENERATOR"
+8
View File
@@ -52,6 +52,14 @@ jobs:
run: make check-buck-targets
- name: Simple source code checks
run: make check-sources
- name: Sanity check check_format_compatible.sh
run: |-
export TEST_TMPDIR=/dev/shm/rocksdb
rm -rf /dev/shm/rocksdb
mkdir /dev/shm/rocksdb
git reset --hard
git config --global --add safe.directory /__w/rocksdb/rocksdb
SANITY_CHECK=1 LONG_TEST=1 tools/check_format_compatible.sh
# ========================= Linux With Tests ======================== #
build-linux:
if: ${{ github.repository_owner == 'facebook' }}
+8
View File
@@ -249,6 +249,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"trace_replay/trace_record_result.cc",
"trace_replay/trace_replay.cc",
"util/async_file_reader.cc",
"util/auto_skip_compressor.cc",
"util/build_version.cc",
"util/cleanable.cc",
"util/coding.cc",
@@ -267,6 +268,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"util/random.cc",
"util/rate_limiter.cc",
"util/ribbon_config.cc",
"util/simple_mixed_compressor.cc",
"util/slice.cc",
"util/status.cc",
"util/stderr_logger.cc",
@@ -4709,6 +4711,12 @@ cpp_unittest_wrapper(name="compressed_secondary_cache_test",
extra_compiler_flags=[])
cpp_unittest_wrapper(name="compression_test",
srcs=["util/compression_test.cc"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
cpp_unittest_wrapper(name="configurable_test",
srcs=["options/configurable_test.cc"],
deps=[":rocksdb_test_lib"],
+3
View File
@@ -874,11 +874,13 @@ set(SOURCES
trace_replay/trace_record.cc
trace_replay/trace_replay.cc
util/async_file_reader.cc
util/auto_skip_compressor.cc
util/cleanable.cc
util/coding.cc
util/compaction_job_stats_impl.cc
util/comparator.cc
util/compression.cc
util/simple_mixed_compressor.cc
util/compression_context_cache.cc
util/concurrent_task_limiter_impl.cc
util/crc32c.cc
@@ -1456,6 +1458,7 @@ if(WITH_TESTS)
util/autovector_test.cc
util/bloom_test.cc
util/coding_test.cc
util/compression_test.cc
util/crc32c_test.cc
util/defer_test.cc
util/dynamic_bloom_test.cc
+13
View File
@@ -1,6 +1,19 @@
# Rocksdb Change Log
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
## 10.3.0 (05/17/2025)
### New Features
* Add new experimental `CompactionOptionsFIFO::allow_trivial_copy_when_change_temperature` along with `CompactionOptionsFIFO::trivial_copy_buffer_size` to allow optimizing FIFO compactions with tiering when kChangeTemperature to move files from source tier FileSystem to another tier FileSystem via trivial and direct copying raw sst file instead of reading thru the content of the SST file then rebuilding the table files.
* Add a new field to Compaction Stats in LOG files for the pre-compression size written to each level.
* Add new experimental `TransactionOptions::large_txn_commit_optimize_threshold` to enable optimizations for large transaction commit with per transaction threshold. `TransactionDBOptions::txn_commit_bypass_memtable_threshold` is deprecated in favor of this transaction option.
* [internal team use only] Allow an application-defined `request_id` to be passed to RocksDB and propagated to the filesystem via IODebugContext
### Bug Fixes
* Fix a bug where transaction lock upgrade can incorrectly fail with a Deadlock status. This happens when a transaction has a non-zero timeout and tries to upgrade a shared lock that is also held by another transaction.
* Pass wrapped WritableFileWriter pointer to ExternalTableBuilder so that the file checksum can be correctly calculated and returned by SstFileWriter for external table files.
* Fix an infinite-loop bug in transaction locking. This can happen if a transaction reaches lock limit and its time out expires before it attempts to wait for it.
* Fixed a potential data race with `CompressionOptions::parallel_threads > 1` and a `TablePropertiesCollector` overriding `BlockAdd()`.
## 10.2.0 (04/21/2025)
### New Features
* Provide histogram stats `COMPACTION_PREFETCH_BYTES` to measure number of bytes for RocksDB's prefetching (as opposed to file
+10 -5
View File
@@ -1491,6 +1491,9 @@ db_test: $(OBJ_DIR)/db/db_test.o $(TEST_LIBRARY) $(LIBRARY)
db_test2: $(OBJ_DIR)/db/db_test2.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
compression_test: $(OBJ_DIR)/util/compression_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
db_logical_block_size_cache_test: $(OBJ_DIR)/db/db_logical_block_size_cache_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
@@ -2489,11 +2492,13 @@ 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 8e8186f67de7a23d3a07366946b1617343927d84
@# 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
perl -pi -e 's/(__has_include.<experimental.memory_resource>.)/__cpp_rtti && $$1/' third-party/folly/folly/memory/MemoryResource.h
cd third-party/folly && git reset --hard d17bf897cb5bbf8f07b122a614e8cffdc38edcde
@# Apparently missing include
perl -pi -e 's/(#include <atomic>)/$$1\n#include <cstring>/' third-party/folly/folly/lang/Exception.h
@# Warning-as-error on memcpy
perl -pi -e 's/memcpy.&ptr/memcpy((void*)&ptr/' third-party/folly/folly/lang/Exception.cpp
@# const mismatch
perl -pi -e 's/: environ/: (const char**)(environ)/' third-party/folly/folly/Subprocess.cpp
@# NOTE: boost source will be needed for any build including `USE_FOLLY_LITE` builds as those depend on boost headers
cd third-party/folly && $(PYTHON) build/fbcode_builder/getdeps.py fetch boost
+7 -4
View File
@@ -118,6 +118,9 @@ fi
# fi
set -e
# Exclude third-party from formatting
EXCLUDE=':!third-party/'
uncommitted_code=`git diff HEAD`
# If there's no uncommitted changes, we assume user are doing post-commit
@@ -137,11 +140,11 @@ then
# should be relevant for formatting fixes.
FORMAT_UPSTREAM_MERGE_BASE="$(git merge-base "$FORMAT_UPSTREAM" HEAD)"
# Get the differences
diffs=$(git diff -U0 "$FORMAT_UPSTREAM_MERGE_BASE" | $CLANG_FORMAT_DIFF -p 1) || true
diffs=$(git diff -U0 "$FORMAT_UPSTREAM_MERGE_BASE" -- $EXCLUDE | $CLANG_FORMAT_DIFF -p 1) || true
echo "Checking format of changes not yet in $FORMAT_UPSTREAM..."
else
# Check the format of uncommitted lines,
diffs=$(git diff -U0 HEAD | $CLANG_FORMAT_DIFF -p 1) || true
diffs=$(git diff -U0 HEAD -- $EXCLUDE | $CLANG_FORMAT_DIFF -p 1) || true
echo "Checking format of uncommitted changes..."
fi
@@ -187,9 +190,9 @@ fi
# Do in-place format adjustment.
if [ -z "$uncommitted_code" ]
then
git diff -U0 "$FORMAT_UPSTREAM_MERGE_BASE" | $CLANG_FORMAT_DIFF -i -p 1
git diff -U0 "$FORMAT_UPSTREAM_MERGE_BASE" -- $EXCLUDE | $CLANG_FORMAT_DIFF -i -p 1
else
git diff -U0 HEAD | $CLANG_FORMAT_DIFF -i -p 1
git diff -U0 HEAD -- $EXCLUDE | $CLANG_FORMAT_DIFF -i -p 1
fi
echo "Files reformatted!"
+10
View File
@@ -3748,6 +3748,16 @@ uint32_t rocksdb_options_get_memtable_op_scan_flush_trigger(
return opt->rep.memtable_op_scan_flush_trigger;
}
void rocksdb_options_set_memtable_avg_op_scan_flush_trigger(
rocksdb_options_t* opt, uint32_t n) {
opt->rep.memtable_avg_op_scan_flush_trigger = n;
}
uint32_t rocksdb_options_get_memtable_avg_op_scan_flush_trigger(
rocksdb_options_t* opt) {
return opt->rep.memtable_avg_op_scan_flush_trigger;
}
void rocksdb_options_enable_statistics(rocksdb_options_t* opt) {
opt->rep.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
}
+10
View File
@@ -2220,6 +2220,10 @@ int main(int argc, char** argv) {
CheckCondition(100 ==
rocksdb_options_get_memtable_op_scan_flush_trigger(o));
rocksdb_options_set_memtable_avg_op_scan_flush_trigger(o, 150);
CheckCondition(150 ==
rocksdb_options_get_memtable_avg_op_scan_flush_trigger(o));
rocksdb_options_set_ttl(o, 5000);
CheckCondition(5000 == rocksdb_options_get_ttl(o));
@@ -2663,6 +2667,12 @@ int main(int argc, char** argv) {
CheckCondition(100 ==
rocksdb_options_get_memtable_op_scan_flush_trigger(o));
rocksdb_options_set_memtable_avg_op_scan_flush_trigger(copy, 900);
CheckCondition(
900 == rocksdb_options_get_memtable_avg_op_scan_flush_trigger(copy));
CheckCondition(150 ==
rocksdb_options_get_memtable_avg_op_scan_flush_trigger(o));
rocksdb_options_set_ttl(copy, 8000);
CheckCondition(8000 == rocksdb_options_get_ttl(copy));
CheckCondition(5000 == rocksdb_options_get_ttl(o));
+18 -8
View File
@@ -448,13 +448,21 @@ ColumnFamilyOptions SanitizeCfOptions(const ImmutableDBOptions& db_options,
result.preclude_last_level_data_seconds = 0;
}
if (read_only && result.memtable_op_scan_flush_trigger != 0) {
ROCKS_LOG_WARN(db_options.info_log.get(),
"option memtable_op_scan_flush_trigger is sanitized to "
"0(disabled) for read only DB.");
result.memtable_op_scan_flush_trigger = 0;
if (read_only) {
if (result.memtable_op_scan_flush_trigger) {
ROCKS_LOG_WARN(db_options.info_log.get(),
"option memtable_op_scan_flush_trigger is sanitized to "
"0(disabled) for read only DB.");
result.memtable_op_scan_flush_trigger = 0;
}
if (result.memtable_avg_op_scan_flush_trigger) {
ROCKS_LOG_WARN(
db_options.info_log.get(),
"option memtable_avg_op_scan_flush_trigger is sanitized to "
"0(disabled) for read only DB.");
result.memtable_avg_op_scan_flush_trigger = 0;
}
}
return result;
}
@@ -1209,10 +1217,12 @@ Compaction* ColumnFamilyData::PickCompaction(
const MutableCFOptions& mutable_options,
const MutableDBOptions& mutable_db_options,
const std::vector<SequenceNumber>& existing_snapshots,
const SnapshotChecker* snapshot_checker, LogBuffer* log_buffer) {
const SnapshotChecker* snapshot_checker, LogBuffer* log_buffer,
bool require_max_output_level) {
auto* result = compaction_picker_->PickCompaction(
GetName(), mutable_options, mutable_db_options, existing_snapshots,
snapshot_checker, current_->storage_info(), log_buffer);
snapshot_checker, current_->storage_info(), log_buffer,
require_max_output_level);
if (result != nullptr) {
result->FinalizeInputInfo(current_);
}
+2 -1
View File
@@ -424,7 +424,8 @@ class ColumnFamilyData {
const MutableCFOptions& mutable_options,
const MutableDBOptions& mutable_db_options,
const std::vector<SequenceNumber>& existing_snapshots,
const SnapshotChecker* snapshot_checker, LogBuffer* log_buffer);
const SnapshotChecker* snapshot_checker, LogBuffer* log_buffer,
bool require_max_output_level = false);
// Check if the passed range overlap with any running compactions.
// REQUIRES: DB mutex held
+5
View File
@@ -463,6 +463,11 @@ class Compaction {
const int start_level,
const int output_level);
static bool OutputToNonZeroMaxOutputLevel(int output_level,
int max_output_level) {
return output_level > 0 && output_level == max_output_level;
}
// If some data cannot be safely migrated "up" the LSM tree due to a change
// in the preclude_last_level_data_seconds setting, this indicates a sequence
// number for the newest data that must be kept in the last level.
+27 -28
View File
@@ -133,10 +133,7 @@ CompactionJob::CompactionJob(
LogBuffer* log_buffer, FSDirectory* db_directory,
FSDirectory* output_directory, FSDirectory* blob_output_directory,
Statistics* stats, InstrumentedMutex* db_mutex,
ErrorHandler* db_error_handler,
std::vector<SequenceNumber> existing_snapshots,
SequenceNumber earliest_write_conflict_snapshot,
const SnapshotChecker* snapshot_checker, JobContext* job_context,
ErrorHandler* db_error_handler, JobContext* job_context,
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
bool paranoid_file_checks, bool measure_io_stats, const std::string& dbname,
CompactionJobStats* compaction_job_stats, Env::Priority thread_pri,
@@ -173,12 +170,7 @@ CompactionJob::CompactionJob(
blob_output_directory_(blob_output_directory),
db_mutex_(db_mutex),
db_error_handler_(db_error_handler),
existing_snapshots_(std::move(existing_snapshots)),
earliest_snapshot_(existing_snapshots_.empty()
? kMaxSequenceNumber
: existing_snapshots_.at(0)),
earliest_write_conflict_snapshot_(earliest_write_conflict_snapshot),
snapshot_checker_(snapshot_checker),
earliest_snapshot_(job_context->GetEarliestSnapshotSequence()),
job_context_(job_context),
table_cache_(std::move(table_cache)),
event_logger_(event_logger),
@@ -193,6 +185,7 @@ CompactionJob::CompactionJob(
bg_bottom_compaction_scheduled_(bg_bottom_compaction_scheduled) {
assert(job_stats_ != nullptr);
assert(log_buffer_ != nullptr);
assert(job_context->snapshot_context_initialized);
const auto* cfd = compact_->compaction->column_family_data();
ThreadStatusUtil::SetEnableTracking(db_options_.enable_thread_tracking);
@@ -224,10 +217,9 @@ void CompactionJob::ReportStartedCompaction(Compaction* compaction) {
ThreadStatus::COMPACTION_PROP_FLAGS,
compaction->is_manual_compaction() +
(compaction->deletion_compaction() << 1));
auto total_input_bytes = compaction->CalculateTotalInputSize();
ThreadStatusUtil::SetThreadOperationProperty(
ThreadStatus::COMPACTION_TOTAL_INPUT_BYTES,
compaction->CalculateTotalInputSize());
ThreadStatus::COMPACTION_TOTAL_INPUT_BYTES, total_input_bytes);
IOSTATS_RESET(bytes_written);
IOSTATS_RESET(bytes_read);
@@ -242,6 +234,16 @@ void CompactionJob::ReportStartedCompaction(Compaction* compaction) {
job_stats_->is_manual_compaction = compaction->is_manual_compaction();
job_stats_->is_full_compaction = compaction->is_full_compaction();
// populate compaction stats num_input_files and total_num_of_bytes
size_t num_input_files = 0;
for (int input_level = 0;
input_level < static_cast<int>(compaction->num_input_levels());
++input_level) {
const LevelFilesBrief* flevel = compaction->input_levels(input_level);
num_input_files += flevel->num_files;
}
job_stats_->CompactionJobStats::num_input_files = num_input_files;
job_stats_->total_input_bytes = total_input_bytes;
}
void CompactionJob::Prepare(
@@ -837,8 +839,7 @@ Status CompactionJob::Run() {
}
ReleaseSubcompactionResources();
TEST_SYNC_POINT("CompactionJob::ReleaseSubcompactionResources:0");
TEST_SYNC_POINT("CompactionJob::ReleaseSubcompactionResources:1");
TEST_SYNC_POINT("CompactionJob::ReleaseSubcompactionResources");
for (const auto& state : compact_->sub_compact_states) {
for (const auto& output : state.GetOutputs()) {
@@ -1183,7 +1184,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
// creation across both CompactionJob and CompactionServiceCompactionJob
sub_compact->AssignRangeDelAggregator(
std::make_unique<CompactionRangeDelAggregator>(
&cfd->internal_comparator(), existing_snapshots_,
&cfd->internal_comparator(), job_context_->snapshot_seqs,
&full_history_ts_low_, &trim_ts_));
// TODO: since we already use C++17, should use
@@ -1324,8 +1325,8 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
env_, cfd->user_comparator(), cfd->ioptions().merge_operator.get(),
compaction_filter, db_options_.info_log.get(),
false /* internal key corruption is expected */,
existing_snapshots_.empty() ? 0 : existing_snapshots_.back(),
snapshot_checker_, compact_->compaction->level(), db_options_.stats);
job_context_->GetLatestSnapshotSequence(), job_context_->snapshot_checker,
compact_->compaction->level(), db_options_.stats);
const auto& mutable_cf_options =
sub_compact->compaction->mutable_cf_options();
@@ -1361,9 +1362,10 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
auto c_iter = std::make_unique<CompactionIterator>(
input, cfd->user_comparator(), &merge, versions_->LastSequence(),
&existing_snapshots_, earliest_snapshot_,
earliest_write_conflict_snapshot_, job_snapshot_seq, snapshot_checker_,
env_, ShouldReportDetailedTime(env_, stats_),
&(job_context_->snapshot_seqs), earliest_snapshot_,
job_context_->earliest_write_conflict_snapshot, job_snapshot_seq,
job_context_->snapshot_checker, env_,
ShouldReportDetailedTime(env_, stats_),
/*expect_valid_internal_key=*/true, sub_compact->RangeDelAgg(),
blob_file_builder.get(), db_options_.allow_data_in_errors,
db_options_.enforce_single_del_contracts, manual_compaction_canceled_,
@@ -1652,10 +1654,6 @@ Status CompactionJob::FinishCompactionOutputFile(
Status s = input_status;
// Add range tombstones
auto earliest_snapshot = kMaxSequenceNumber;
if (existing_snapshots_.size() > 0) {
earliest_snapshot = existing_snapshots_[0];
}
if (s.ok()) {
// Inclusive lower bound, exclusive upper bound
std::pair<SequenceNumber, SequenceNumber> keep_seqno_range{
@@ -1681,7 +1679,7 @@ Status CompactionJob::FinishCompactionOutputFile(
s = outputs.AddRangeDels(*sub_compact->RangeDelAgg(), comp_start_user_key,
comp_end_user_key, range_del_out_stats,
bottommost_level_, cfd->internal_comparator(),
earliest_snapshot, keep_seqno_range,
earliest_snapshot_, keep_seqno_range,
next_table_min_key, full_history_ts_low_);
}
RecordDroppedKeys(range_del_out_stats, &sub_compact->compaction_job_stats);
@@ -2313,9 +2311,10 @@ void CompactionJob::LogCompaction() {
}
stream << "score" << compaction->score() << "input_data_size"
<< compaction->CalculateTotalInputSize() << "oldest_snapshot_seqno"
<< (existing_snapshots_.empty()
<< (job_context_->snapshot_seqs.empty()
? int64_t{-1} // Use -1 for "none"
: static_cast<int64_t>(existing_snapshots_[0]));
: static_cast<int64_t>(
job_context_->GetEarliestSnapshotSequence()));
if (compaction->SupportsPerKeyPlacement()) {
stream << "proximal_after_seqno" << proximal_after_seqno_;
stream << "preserve_seqno_after" << preserve_seqno_after_;
+35 -45
View File
@@ -142,27 +142,27 @@ class SubcompactionState;
class CompactionJob {
public:
CompactionJob(
int job_id, Compaction* compaction, const ImmutableDBOptions& db_options,
const MutableDBOptions& mutable_db_options,
const FileOptions& file_options, VersionSet* versions,
const std::atomic<bool>* shutting_down, LogBuffer* log_buffer,
FSDirectory* db_directory, FSDirectory* output_directory,
FSDirectory* blob_output_directory, Statistics* stats,
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
std::vector<SequenceNumber> existing_snapshots,
SequenceNumber earliest_write_conflict_snapshot,
const SnapshotChecker* snapshot_checker, JobContext* job_context,
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
bool paranoid_file_checks, bool measure_io_stats,
const std::string& dbname, CompactionJobStats* compaction_job_stats,
Env::Priority thread_pri, const std::shared_ptr<IOTracer>& io_tracer,
const std::atomic<bool>& manual_compaction_canceled,
const std::string& db_id = "", const std::string& db_session_id = "",
std::string full_history_ts_low = "", std::string trim_ts = "",
BlobFileCompletionCallback* blob_callback = nullptr,
int* bg_compaction_scheduled = nullptr,
int* bg_bottom_compaction_scheduled = nullptr);
CompactionJob(int job_id, Compaction* compaction,
const ImmutableDBOptions& db_options,
const MutableDBOptions& mutable_db_options,
const FileOptions& file_options, VersionSet* versions,
const std::atomic<bool>* shutting_down, LogBuffer* log_buffer,
FSDirectory* db_directory, FSDirectory* output_directory,
FSDirectory* blob_output_directory, Statistics* stats,
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
JobContext* job_context, std::shared_ptr<Cache> table_cache,
EventLogger* event_logger, bool paranoid_file_checks,
bool measure_io_stats, const std::string& dbname,
CompactionJobStats* compaction_job_stats,
Env::Priority thread_pri,
const std::shared_ptr<IOTracer>& io_tracer,
const std::atomic<bool>& manual_compaction_canceled,
const std::string& db_id = "",
const std::string& db_session_id = "",
std::string full_history_ts_low = "", std::string trim_ts = "",
BlobFileCompletionCallback* blob_callback = nullptr,
int* bg_compaction_scheduled = nullptr,
int* bg_bottom_compaction_scheduled = nullptr);
virtual ~CompactionJob();
@@ -321,21 +321,8 @@ class CompactionJob {
FSDirectory* blob_output_directory_;
InstrumentedMutex* db_mutex_;
ErrorHandler* db_error_handler_;
// If there were two snapshots with seq numbers s1 and
// s2 and s1 < s2, and if we find two instances of a key k1 then lies
// entirely within s1 and s2, then the earlier version of k1 can be safely
// deleted because that version is not visible in any snapshot.
std::vector<SequenceNumber> existing_snapshots_;
SequenceNumber earliest_snapshot_;
// This is the earliest snapshot that could be used for write-conflict
// checking by a transaction. For any user-key newer than this snapshot, we
// should make sure not to remove evidence that a write occurred.
SequenceNumber earliest_write_conflict_snapshot_;
const SnapshotChecker* const snapshot_checker_;
JobContext* job_context_;
std::shared_ptr<Cache> table_cache_;
@@ -427,8 +414,9 @@ struct CompactionServiceInput {
// CompactionServiceOutputFile is the metadata for the output SST file
struct CompactionServiceOutputFile {
std::string file_name;
SequenceNumber smallest_seqno;
SequenceNumber largest_seqno;
uint64_t file_size{};
SequenceNumber smallest_seqno{};
SequenceNumber largest_seqno{};
std::string smallest_internal_key;
std::string largest_internal_key;
uint64_t oldest_ancester_time = kUnknownOldestAncesterTime;
@@ -436,24 +424,26 @@ struct CompactionServiceOutputFile {
uint64_t epoch_number = kUnknownEpochNumber;
std::string file_checksum = kUnknownFileChecksum;
std::string file_checksum_func_name = kUnknownFileChecksumFuncName;
uint64_t paranoid_hash;
uint64_t paranoid_hash{};
bool marked_for_compaction;
UniqueId64x2 unique_id{};
TableProperties table_properties;
bool is_proximal_level_output;
Temperature file_temperature;
Temperature file_temperature = Temperature::kUnknown;
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, const std::string& _file_checksum,
const std::string& name, uint64_t size, 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,
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, bool _is_proximal_level_output,
Temperature _file_temperature)
: file_name(name),
file_size(size),
smallest_seqno(smallest),
largest_seqno(largest),
smallest_internal_key(std::move(_smallest_internal_key)),
@@ -521,9 +511,9 @@ class CompactionServiceCompactionJob : private CompactionJob {
const std::atomic<bool>* shutting_down, LogBuffer* log_buffer,
FSDirectory* output_directory, Statistics* stats,
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
std::vector<SequenceNumber> existing_snapshots,
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
const std::string& dbname, const std::shared_ptr<IOTracer>& io_tracer,
JobContext* job_context, std::shared_ptr<Cache> table_cache,
EventLogger* event_logger, const std::string& dbname,
const std::shared_ptr<IOTracer>& io_tracer,
const std::atomic<bool>& manual_compaction_canceled,
const std::string& db_id, const std::string& db_session_id,
std::string output_path,
+13 -11
View File
@@ -43,7 +43,6 @@ void VerifyInitializationOfCompactionJobStats(
ASSERT_EQ(compaction_job_stats.elapsed_micros, 0U);
ASSERT_EQ(compaction_job_stats.num_input_records, 0U);
ASSERT_EQ(compaction_job_stats.num_input_files, 0U);
ASSERT_EQ(compaction_job_stats.num_input_files_at_output_level, 0U);
ASSERT_EQ(compaction_job_stats.num_output_records, 0U);
@@ -52,7 +51,6 @@ void VerifyInitializationOfCompactionJobStats(
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);
ASSERT_EQ(compaction_job_stats.total_input_raw_key_bytes, 0U);
@@ -595,11 +593,11 @@ class CompactionJobTestBase : public testing::Test {
const std::vector<std::vector<FileMetaData*>>& input_files,
const std::vector<int> input_levels,
std::function<void(Compaction& comp)>&& verify_func,
const std::vector<SequenceNumber>& snapshots = {}) {
std::vector<SequenceNumber>&& snapshots = {}) {
const int kLastLevel = cf_options_.num_levels - 1;
verify_per_key_placement_ = std::move(verify_func);
mock::KVVector empty_map;
RunCompaction(input_files, input_levels, {empty_map}, snapshots,
RunCompaction(input_files, input_levels, {empty_map}, std::move(snapshots),
kMaxSequenceNumber, kLastLevel, false);
}
@@ -608,7 +606,7 @@ class CompactionJobTestBase : public testing::Test {
const std::vector<std::vector<FileMetaData*>>& input_files,
const std::vector<int>& input_levels,
const std::vector<mock::KVVector>& expected_results,
const std::vector<SequenceNumber>& snapshots = {},
std::vector<SequenceNumber>&& snapshots = {},
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber,
int output_level = 1, bool verify = true,
std::vector<uint64_t> expected_oldest_blob_file_numbers = {},
@@ -665,13 +663,15 @@ class CompactionJobTestBase : public testing::Test {
ucmp_->timestamp_size() == full_history_ts_low_.size());
const std::atomic<bool> kManualCompactionCanceledFalse{false};
JobContext job_context(1, false /* create_superversion */);
job_context.InitSnapshotContext(snapshot_checker, nullptr,
earliest_write_conflict_snapshot,
std::move(snapshots));
CompactionJob compaction_job(
0, &compaction, db_options_, mutable_db_options_, env_options_,
versions_.get(), &shutting_down_, &log_buffer, nullptr, nullptr,
nullptr, nullptr, &mutex_, &error_handler_, snapshots,
earliest_write_conflict_snapshot, snapshot_checker, &job_context,
table_cache_, &event_logger, false, false, dbname_,
&compaction_job_stats_, Env::Priority::USER, nullptr /* IOTracer */,
nullptr, nullptr, &mutex_, &error_handler_, &job_context, table_cache_,
&event_logger, false, false, dbname_, &compaction_job_stats_,
Env::Priority::USER, nullptr /* IOTracer */,
/*manual_compaction_canceled=*/kManualCompactionCanceledFalse,
env_->GenerateUniqueId(), DBImpl::GenerateDbSessionId(nullptr),
full_history_ts_low_);
@@ -1669,6 +1669,7 @@ TEST_F(CompactionJobTest, ResultSerialization) {
UniqueId64x2 id{rnd64.Uniform(UINT64_MAX), rnd64.Uniform(UINT64_MAX)};
result.output_files.emplace_back(
rnd.RandomString(rnd.Uniform(kStrMaxLen)) /* file_name */,
rnd64.Uniform(UINT64_MAX) /* file_size */,
rnd64.Uniform(UINT64_MAX) /* smallest_seqno */,
rnd64.Uniform(UINT64_MAX) /* largest_seqno */,
rnd.RandomBinaryString(
@@ -2035,7 +2036,7 @@ TEST_F(CompactionJobTest, CutToAlignGrandparentBoundarySameKey) {
snapshots.emplace_back(i);
}
RunCompaction({lvl0_files, lvl1_files}, input_levels,
{expected_file1, expected_file2}, snapshots);
{expected_file1, expected_file2}, std::move(snapshots));
}
TEST_F(CompactionJobTest, CutForMaxCompactionBytesSameKey) {
@@ -2094,7 +2095,8 @@ TEST_F(CompactionJobTest, CutForMaxCompactionBytesSameKey) {
snapshots.emplace_back(i);
}
RunCompaction({lvl0_files, lvl1_files}, input_levels,
{expected_file1, expected_file2, expected_file3}, snapshots);
{expected_file1, expected_file2, expected_file3},
std::move(snapshots));
}
class CompactionJobTimestampTest : public CompactionJobTestBase {
+3 -2
View File
@@ -65,7 +65,7 @@ class CompactionPicker {
const MutableDBOptions& mutable_db_options,
const std::vector<SequenceNumber>& existing_snapshots,
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
LogBuffer* log_buffer) = 0;
LogBuffer* log_buffer, bool require_max_output_level) = 0;
// The returned Compaction might not include the whole requested range.
// In that case, compaction_end will be set to the next key that needs
@@ -272,7 +272,8 @@ class NullCompactionPicker : public CompactionPicker {
const MutableDBOptions& /*mutable_db_options*/,
const std::vector<SequenceNumber>& /*existing_snapshots*/,
const SnapshotChecker* /*snapshot_checker*/,
VersionStorageInfo* /*vstorage*/, LogBuffer* /* log_buffer */) override {
VersionStorageInfo* /*vstorage*/, LogBuffer* /* log_buffer */,
bool /*require_max_output_level*/ = false) override {
return nullptr;
}
+1 -1
View File
@@ -428,7 +428,7 @@ Compaction* FIFOCompactionPicker::PickCompaction(
const MutableDBOptions& mutable_db_options,
const std::vector<SequenceNumber>& /* existing_snapshots */,
const SnapshotChecker* /* snapshot_checker */, VersionStorageInfo* vstorage,
LogBuffer* log_buffer) {
LogBuffer* log_buffer, bool /* require_max_output_level*/) {
Compaction* c = nullptr;
if (mutable_cf_options.ttl > 0) {
c = PickTTLCompaction(cf_name, mutable_cf_options, mutable_db_options,
+2 -1
View File
@@ -23,7 +23,8 @@ class FIFOCompactionPicker : public CompactionPicker {
const MutableDBOptions& mutable_db_options,
const std::vector<SequenceNumber>& /* existing_snapshots */,
const SnapshotChecker* /* snapshot_checker */,
VersionStorageInfo* version, LogBuffer* log_buffer) override;
VersionStorageInfo* version, LogBuffer* log_buffer,
bool /* require_max_output_level*/ = false) override;
Compaction* CompactRange(const std::string& cf_name,
const MutableCFOptions& mutable_cf_options,
+1 -1
View File
@@ -978,7 +978,7 @@ Compaction* LevelCompactionPicker::PickCompaction(
const MutableDBOptions& mutable_db_options,
const std::vector<SequenceNumber>& /*existing_snapshots */,
const SnapshotChecker* /*snapshot_checker*/, VersionStorageInfo* vstorage,
LogBuffer* log_buffer) {
LogBuffer* log_buffer, bool /* require_max_output_level*/) {
LevelCompactionBuilder builder(cf_name, vstorage, this, log_buffer,
mutable_cf_options, ioptions_,
mutable_db_options);
+2 -1
View File
@@ -25,7 +25,8 @@ class LevelCompactionPicker : public CompactionPicker {
const MutableDBOptions& mutable_db_options,
const std::vector<SequenceNumber>& /* existing_snapshots */,
const SnapshotChecker* /* snapshot_checker */,
VersionStorageInfo* vstorage, LogBuffer* log_buffer) override;
VersionStorageInfo* vstorage, LogBuffer* log_buffer,
bool /*require_max_output_level*/ = false) override;
bool NeedsCompaction(const VersionStorageInfo* vstorage) const override;
};
+253 -152
View File
@@ -38,7 +38,8 @@ class UniversalCompactionBuilder {
const MutableDBOptions& mutable_db_options,
const std::vector<SequenceNumber>& existing_snapshots,
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
UniversalCompactionPicker* picker, LogBuffer* log_buffer)
UniversalCompactionPicker* picker, LogBuffer* log_buffer,
bool require_max_output_level)
: ioptions_(ioptions),
icmp_(icmp),
cf_name_(cf_name),
@@ -46,7 +47,8 @@ class UniversalCompactionBuilder {
mutable_db_options_(mutable_db_options),
vstorage_(vstorage),
picker_(picker),
log_buffer_(log_buffer) {
log_buffer_(log_buffer),
require_max_output_level_(require_max_output_level) {
assert(icmp_);
const auto* ucmp = icmp_->user_comparator();
assert(ucmp);
@@ -102,6 +104,174 @@ class UniversalCompactionBuilder {
bool level_has_marked_standalone_rangedel;
};
unsigned int GetMaxNumFilesToCompactBasedOnMaxReadAmp(
const int file_num_compaction_trigger, const unsigned int ratio,
int* num_sr_not_compacted_output, int* max_num_runs_output) const {
assert(num_sr_not_compacted_output);
assert(max_num_runs_output);
int max_num_runs =
mutable_cf_options_.compaction_options_universal.max_read_amp;
if (max_num_runs < 0) {
// any value < -1 is not valid
assert(max_num_runs == -1);
// By default, fall back to `level0_file_num_compaction_trigger`
max_num_runs = file_num_compaction_trigger;
} else if (max_num_runs == 0) {
if (mutable_cf_options_.compaction_options_universal.stop_style ==
kCompactionStopStyleTotalSize) {
// 0 means auto-tuning by RocksDB. We estimate max num run based on
// max_run_size, size_ratio and write buffer size:
// Assume the size of the lowest level size is equal to
// write_buffer_size. Each subsequent level is the max size without
// triggering size_ratio compaction. `max_num_runs` is the minimum
// number of levels required such that the target size of the
// largest level is at least `max_run_size_`.
max_num_runs = 1;
double cur_level_max_size =
static_cast<double>(mutable_cf_options_.write_buffer_size);
double total_run_size = 0;
while (cur_level_max_size < static_cast<double>(max_run_size_)) {
// This loop should not take too many iterations since
// cur_level_max_size at least doubles each iteration.
total_run_size += cur_level_max_size;
cur_level_max_size = (100.0 + ratio) / 100.0 * total_run_size;
++max_num_runs;
}
} else {
// TODO: implement the auto-tune logic for this stop style
max_num_runs = file_num_compaction_trigger;
}
} else {
// max_num_runs > 0, it's the limit on the number of sorted run
}
// 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 &&
!sorted_runs_[i].level_has_marked_standalone_rangedel) {
num_sr_not_compacted++;
}
}
*num_sr_not_compacted_output = num_sr_not_compacted;
*max_num_runs_output = max_num_runs;
if (num_sr_not_compacted > max_num_runs) {
return num_sr_not_compacted - max_num_runs + 1;
} else {
return 0;
}
}
Compaction* MaybePickPeriodicCompaction(Compaction* const prev_picked_c) {
if (prev_picked_c != nullptr ||
vstorage_->FilesMarkedForPeriodicCompaction().empty()) {
return prev_picked_c;
}
// Always need to do a full compaction for periodic compaction.
Compaction* c = PickPeriodicCompaction();
TEST_SYNC_POINT_CALLBACK("PostPickPeriodicCompaction", c);
if (c != nullptr) {
ROCKS_LOG_BUFFER(log_buffer_,
"[%s] Universal: picked for periodic compaction\n",
cf_name_.c_str());
}
return c;
}
Compaction* MaybePickSizeAmpCompaction(Compaction* const prev_picked_c,
int file_num_compaction_trigger) {
if (prev_picked_c != nullptr ||
sorted_runs_.size() <
static_cast<size_t>(file_num_compaction_trigger)) {
return prev_picked_c;
}
Compaction* c = PickCompactionToReduceSizeAmp();
if (c != nullptr) {
TEST_SYNC_POINT("PickCompactionToReduceSizeAmpReturnNonnullptr");
ROCKS_LOG_BUFFER(log_buffer_,
"[%s] Universal: picked for size amp compaction \n",
cf_name_.c_str());
}
return c;
}
Compaction* MaybePickCompactionToReduceSortedRunsBasedFileRatio(
Compaction* const prev_picked_c, int file_num_compaction_trigger,
unsigned int ratio) {
if (prev_picked_c != nullptr ||
sorted_runs_.size() <
static_cast<size_t>(file_num_compaction_trigger)) {
return prev_picked_c;
}
Compaction* c = PickCompactionToReduceSortedRuns(ratio, UINT_MAX);
if (c != nullptr) {
TEST_SYNC_POINT("PickCompactionToReduceSortedRunsReturnNonnullptr");
ROCKS_LOG_BUFFER(log_buffer_,
"[%s] Universal: picked for size ratio compaction to "
"reduce sorted run\n",
cf_name_.c_str());
}
return c;
}
Compaction* MaybePickCompactionToReduceSortedRuns(
Compaction* const prev_picked_c, int file_num_compaction_trigger,
unsigned int ratio) {
if (prev_picked_c != nullptr ||
sorted_runs_.size() <
static_cast<size_t>(file_num_compaction_trigger)) {
return prev_picked_c;
}
int num_sr_not_compacted = 0;
int max_num_runs = 0;
const unsigned int max_num_files_to_compact =
GetMaxNumFilesToCompactBasedOnMaxReadAmp(file_num_compaction_trigger,
ratio, &num_sr_not_compacted,
&max_num_runs);
if (max_num_files_to_compact == 0) {
ROCKS_LOG_BUFFER(
log_buffer_,
"[%s] Universal: skipping compaction to reduce sorted run, num "
"sorted runs not "
"being compacted -- %u, max num runs allowed -- %d, max_run_size "
"-- %" PRIu64 "\n",
cf_name_.c_str(), num_sr_not_compacted, max_num_runs, max_run_size_);
return nullptr;
}
Compaction* c =
PickCompactionToReduceSortedRuns(UINT_MAX, max_num_files_to_compact);
if (c != nullptr) {
ROCKS_LOG_BUFFER(log_buffer_,
"[%s] Universal: picked for sorted run num compaction "
"to reduce sorted run, to "
"compact file num -- %u, max num runs allowed"
"-- %d, max_run_size -- %" PRIu64 "\n",
cf_name_.c_str(), max_num_files_to_compact, max_num_runs,
max_run_size_);
}
return c;
}
Compaction* MaybePickDeleteTriggeredCompaction(
Compaction* const prev_picked_c) {
if (prev_picked_c != nullptr) {
return prev_picked_c;
}
Compaction* c = PickDeleteTriggeredCompaction();
if (c != nullptr) {
TEST_SYNC_POINT("PickDeleteTriggeredCompactionReturnNonnullptr");
ROCKS_LOG_BUFFER(
log_buffer_,
"[%s] Universal: picked for delete triggered compaction\n",
cf_name_.c_str());
}
return c;
}
// Pick Universal compaction to limit read amplification
Compaction* PickCompactionToReduceSortedRuns(
unsigned int ratio, unsigned int max_number_of_files_to_compact);
@@ -249,6 +419,13 @@ class UniversalCompactionBuilder {
return num_l0_to_exclude;
}
bool MeetsOutputLevelRequirements(int output_level) const {
return !require_max_output_level_ ||
Compaction::OutputToNonZeroMaxOutputLevel(
output_level,
vstorage_->MaxOutputLevel(ioptions_.allow_ingest_behind));
}
const ImmutableOptions& ioptions_;
const InternalKeyComparator* icmp_;
double score_;
@@ -260,6 +437,7 @@ class UniversalCompactionBuilder {
VersionStorageInfo* vstorage_;
UniversalCompactionPicker* picker_;
LogBuffer* log_buffer_;
bool require_max_output_level_;
// 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
@@ -420,10 +598,11 @@ Compaction* UniversalCompactionPicker::PickCompaction(
const MutableDBOptions& mutable_db_options,
const std::vector<SequenceNumber>& existing_snapshots,
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
LogBuffer* log_buffer) {
LogBuffer* log_buffer, bool require_max_output_level) {
UniversalCompactionBuilder builder(
ioptions_, icmp_, cf_name, mutable_cf_options, mutable_db_options,
existing_snapshots, snapshot_checker, vstorage, this, log_buffer);
existing_snapshots, snapshot_checker, vstorage, this, log_buffer,
require_max_output_level);
return builder.PickCompaction();
}
@@ -554,13 +733,21 @@ bool UniversalCompactionBuilder::ShouldSkipMarkedFile(
Compaction* UniversalCompactionBuilder::PickCompaction() {
const int kLevel0 = 0;
score_ = vstorage_->CompactionScore(kLevel0);
int max_output_level =
const int max_output_level =
vstorage_->MaxOutputLevel(ioptions_.allow_ingest_behind);
const int file_num_compaction_trigger =
mutable_cf_options_.level0_file_num_compaction_trigger;
const unsigned int ratio =
mutable_cf_options_.compaction_options_universal.size_ratio;
if (max_output_level == 0 &&
!MeetsOutputLevelRequirements(0 /* output_level */)) {
return nullptr;
}
max_run_size_ = 0;
sorted_runs_ =
CalculateSortedRuns(*vstorage_, max_output_level, &max_run_size_);
int file_num_compaction_trigger =
mutable_cf_options_.level0_file_num_compaction_trigger;
if (sorted_runs_.size() == 0 ||
(vstorage_->FilesMarkedForPeriodicCompaction().empty() &&
@@ -572,6 +759,7 @@ Compaction* UniversalCompactionBuilder::PickCompaction() {
"UniversalCompactionBuilder::PickCompaction:Return", nullptr);
return nullptr;
}
VersionStorageInfo::LevelSummaryStorage tmp;
ROCKS_LOG_BUFFER_MAX_SZ(
log_buffer_, 3072,
@@ -579,119 +767,14 @@ Compaction* UniversalCompactionBuilder::PickCompaction() {
cf_name_.c_str(), sorted_runs_.size(), vstorage_->LevelSummary(&tmp));
Compaction* c = nullptr;
// Periodic compaction has higher priority than other type of compaction
// because it's a hard requirement.
if (!vstorage_->FilesMarkedForPeriodicCompaction().empty()) {
// Always need to do a full compaction for periodic compaction.
c = PickPeriodicCompaction();
TEST_SYNC_POINT_CALLBACK("PostPickPeriodicCompaction", c);
}
if (c == nullptr &&
sorted_runs_.size() >= static_cast<size_t>(file_num_compaction_trigger)) {
// Check for size amplification.
if ((c = PickCompactionToReduceSizeAmp()) != nullptr) {
TEST_SYNC_POINT("PickCompactionToReduceSizeAmpReturnNonnullptr");
ROCKS_LOG_BUFFER(log_buffer_, "[%s] Universal: compacting for size amp\n",
cf_name_.c_str());
} else {
// Size amplification is within limits. Try reducing read
// amplification while maintaining file size ratios.
unsigned int ratio =
mutable_cf_options_.compaction_options_universal.size_ratio;
if ((c = PickCompactionToReduceSortedRuns(ratio, UINT_MAX)) != nullptr) {
TEST_SYNC_POINT("PickCompactionToReduceSortedRunsReturnNonnullptr");
ROCKS_LOG_BUFFER(log_buffer_,
"[%s] Universal: compacting for size ratio\n",
cf_name_.c_str());
} else {
// Size amplification and file size ratios are within configured limits.
// If max read amplification exceeds configured limits, then force
// compaction to reduce the number sorted runs without looking at file
// size ratios.
// This is guaranteed by NeedsCompaction()
assert(sorted_runs_.size() >=
static_cast<size_t>(file_num_compaction_trigger));
int max_num_runs =
mutable_cf_options_.compaction_options_universal.max_read_amp;
if (max_num_runs < 0) {
// any value < -1 is not valid
assert(max_num_runs == -1);
// By default, fall back to `level0_file_num_compaction_trigger`
max_num_runs = file_num_compaction_trigger;
} else if (max_num_runs == 0) {
if (mutable_cf_options_.compaction_options_universal.stop_style ==
kCompactionStopStyleTotalSize) {
// 0 means auto-tuning by RocksDB. We estimate max num run based on
// max_run_size, size_ratio and write buffer size:
// Assume the size of the lowest level size is equal to
// write_buffer_size. Each subsequent level is the max size without
// triggering size_ratio compaction. `max_num_runs` is the minimum
// number of levels required such that the target size of the
// largest level is at least `max_run_size_`.
max_num_runs = 1;
double cur_level_max_size =
static_cast<double>(mutable_cf_options_.write_buffer_size);
double total_run_size = 0;
while (cur_level_max_size < static_cast<double>(max_run_size_)) {
// This loop should not take too many iterations since
// cur_level_max_size at least doubles each iteration.
total_run_size += cur_level_max_size;
cur_level_max_size = (100.0 + ratio) / 100.0 * total_run_size;
++max_num_runs;
}
} else {
// TODO: implement the auto-tune logic for this stop style
max_num_runs = file_num_compaction_trigger;
}
} else {
// max_num_runs > 0, it's the limit on the number of sorted run
}
// 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 &&
!sorted_runs_[i].level_has_marked_standalone_rangedel) {
num_sr_not_compacted++;
}
}
// The number of sorted runs that are not being compacted is greater
// than the maximum allowed number of sorted runs
if (num_sr_not_compacted > max_num_runs) {
unsigned int num_files = num_sr_not_compacted - max_num_runs + 1;
if ((c = PickCompactionToReduceSortedRuns(UINT_MAX, num_files)) !=
nullptr) {
ROCKS_LOG_BUFFER(log_buffer_,
"[%s] Universal: compacting for file num, to "
"compact file num -- %u, max num runs allowed"
"-- %d, max_run_size -- %" PRIu64 "\n",
cf_name_.c_str(), num_files, max_num_runs,
max_run_size_);
}
} else {
ROCKS_LOG_BUFFER(
log_buffer_,
"[%s] Universal: skipping compaction for file num, num runs not "
"being compacted -- %u, max num runs allowed -- %d, max_run_size "
"-- %" PRIu64 "\n",
cf_name_.c_str(), num_sr_not_compacted, max_num_runs,
max_run_size_);
}
}
}
}
if (c == nullptr) {
if ((c = PickDeleteTriggeredCompaction()) != nullptr) {
TEST_SYNC_POINT("PickDeleteTriggeredCompactionReturnNonnullptr");
ROCKS_LOG_BUFFER(log_buffer_,
"[%s] Universal: delete triggered compaction\n",
cf_name_.c_str());
}
}
c = MaybePickPeriodicCompaction(c);
c = MaybePickSizeAmpCompaction(c, file_num_compaction_trigger);
c = MaybePickCompactionToReduceSortedRunsBasedFileRatio(
c, file_num_compaction_trigger, ratio);
c = MaybePickCompactionToReduceSortedRuns(c, file_num_compaction_trigger,
ratio);
c = MaybePickDeleteTriggeredCompaction(c);
if (c == nullptr) {
TEST_SYNC_POINT_CALLBACK(
@@ -700,6 +783,7 @@ Compaction* UniversalCompactionBuilder::PickCompaction() {
}
assert(c->output_level() <=
vstorage_->MaxOutputLevel(ioptions_.allow_ingest_behind));
assert(MeetsOutputLevelRequirements(c->output_level()));
if (mutable_cf_options_.compaction_options_universal.allow_trivial_move ==
true &&
@@ -825,14 +909,16 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
if (sr->being_compacted) {
ROCKS_LOG_BUFFER(log_buffer_,
"[%s] Universal: %s"
"[%d] being compacted, skipping",
"[%d] being compacted, skipping for compaction to "
"reduce sorted runs",
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);
ROCKS_LOG_BUFFER(
log_buffer_,
"[%s] Universal: %s"
"[%d] has standalone range tombstone files marked for "
"compaction, skipping for compaction to reduce sorted runs",
cf_name_.c_str(), file_num_buf, loop);
}
sr = nullptr;
@@ -845,7 +931,8 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
char file_num_buf[kFormatFileNumberBufSize];
sr->Dump(file_num_buf, sizeof(file_num_buf), true);
ROCKS_LOG_BUFFER(log_buffer_,
"[%s] Universal: Possible candidate %s[%d].",
"[%s] Universal: Possible candidate for compaction to "
"reduce sorted runs %s[%d].",
cf_name_.c_str(), file_num_buf, loop);
}
@@ -947,6 +1034,10 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
output_level = sorted_runs_[first_index_after].level - 1;
}
if (!MeetsOutputLevelRequirements(output_level)) {
return nullptr;
}
std::vector<CompactionInputFiles> inputs(max_output_level + 1);
for (size_t i = 0; i < inputs.size(); ++i) {
inputs[i].level = start_level + static_cast<int>(i);
@@ -1039,18 +1130,19 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSizeAmp() {
char file_num_buf[kFormatFileNumberBufSize];
sr->Dump(file_num_buf, sizeof(file_num_buf), true);
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);
ROCKS_LOG_BUFFER(log_buffer_,
"[%s] Universal: stopping for size amp compaction 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);
ROCKS_LOG_BUFFER(log_buffer_,
"[%s] Universal: stopping for size amp compaction 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;
}
@@ -1066,11 +1158,12 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSizeAmp() {
{
const size_t num_l0_to_exclude = MightExcludeNewL0sToReduceWriteStop(
num_l0_files, end_index, start_index, candidate_size);
ROCKS_LOG_BUFFER(log_buffer_,
"[%s] Universal: Excluding %" ROCKSDB_PRIszt
" latest L0 files to reduce potential write stop "
"triggered by `level0_stop_writes_trigger`",
cf_name_.c_str(), num_l0_to_exclude);
ROCKS_LOG_BUFFER(
log_buffer_,
"[%s] Universal: Excluding for size amp compaction %" ROCKSDB_PRIszt
" latest L0 files to reduce potential write stop "
"triggered by `level0_stop_writes_trigger`",
cf_name_.c_str(), num_l0_to_exclude);
}
{
@@ -1088,18 +1181,18 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSizeAmp() {
// size amplification = percentage of additional size
if (candidate_size * 100 < ratio * base_sr_size) {
ROCKS_LOG_BUFFER(
log_buffer_,
"[%s] Universal: size amp not needed. newer-files-total-size %" PRIu64
" earliest-file-size %" PRIu64,
cf_name_.c_str(), candidate_size, base_sr_size);
ROCKS_LOG_BUFFER(log_buffer_,
"[%s] Universal: size amp compction not needed. "
"newer-files-total-size %" PRIu64
" earliest-file-size %" PRIu64,
cf_name_.c_str(), candidate_size, base_sr_size);
return nullptr;
} else {
ROCKS_LOG_BUFFER(
log_buffer_,
"[%s] Universal: size amp needed. newer-files-total-size %" PRIu64
" earliest-file-size %" PRIu64,
cf_name_.c_str(), candidate_size, base_sr_size);
ROCKS_LOG_BUFFER(log_buffer_,
"[%s] Universal: size amp compaction needed. "
"newer-files-total-size %" PRIu64
" earliest-file-size %" PRIu64,
cf_name_.c_str(), candidate_size, base_sr_size);
}
// Since incremental compaction can't include more than second last
// level, it can introduce penalty, compared to full compaction. We
@@ -1450,6 +1543,10 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
}
assert(output_level <= max_output_level);
if (!MeetsOutputLevelRequirements(output_level)) {
return nullptr;
}
if (output_level != 0) {
if (start_level == 0) {
if (!picker_->GetOverlappingL0Files(vstorage_, &start_level_inputs,
@@ -1574,6 +1671,10 @@ Compaction* UniversalCompactionBuilder::PickCompactionWithSortedRunRange(
output_level = sorted_runs_[end_index + 1].level - 1;
}
if (!MeetsOutputLevelRequirements(output_level)) {
return nullptr;
}
// intra L0 compactions outputs could have overlap
if (output_level != 0 && picker_->FilesRangeOverlapWithCompaction(
inputs, output_level,
+4 -1
View File
@@ -18,12 +18,15 @@ class UniversalCompactionPicker : public CompactionPicker {
UniversalCompactionPicker(const ImmutableOptions& ioptions,
const InternalKeyComparator* icmp)
: CompactionPicker(ioptions, icmp) {}
// If `require_max_output_level` is true, only pick compaction
// with max output level or return nullptr if no such compaction exists.
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;
LogBuffer* log_buffer, bool require_max_output_level = false) override;
int MaxOutputLevel() const override { return NumberLevels() - 1; }
bool NeedsCompaction(const VersionStorageInfo* vstorage) const override;
+30 -21
View File
@@ -41,7 +41,7 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
}
compaction_input.cf_name = compaction->column_family_data()->GetName();
compaction_input.snapshots = existing_snapshots_;
compaction_input.snapshots = job_context_->snapshot_seqs;
compaction_input.has_begin = sub_compact->start.has_value();
compaction_input.begin =
compaction_input.has_begin ? sub_compact->start->ToString() : "";
@@ -221,18 +221,24 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
}
FileMetaData meta;
uint64_t file_size;
// FIXME: file_size should be part of CompactionServiceOutputFile so that
// we don't get DB corruption if the full file size has not been propagated
// back to the caller through the file system (which could have metadata
// lag or caching bugs).
s = fs_->GetFileSize(tgt_file, IOOptions(), &file_size, nullptr);
uint64_t file_size = file.file_size;
// TODO - Clean this up in the next release.
// For backward compatibility - in case the remote worker does not populate
// the file_size yet. If missing, continue to populate this from the file
// system.
if (file_size == 0) {
s = fs_->GetFileSize(tgt_file, IOOptions(), &file_size, nullptr);
}
if (!s.ok()) {
sub_compact->status = s;
db_options_.compaction_service->OnInstallation(
response.scheduled_job_id, CompactionServiceJobStatus::kFailure);
return CompactionServiceJobStatus::kFailure;
}
assert(file_size > 0);
meta.fd = FileDescriptor(file_num, compaction->output_path_id(), file_size,
file.smallest_seqno, file.largest_seqno);
meta.smallest.DecodeFrom(file.smallest_internal_key);
@@ -298,9 +304,9 @@ CompactionServiceCompactionJob::CompactionServiceCompactionJob(
VersionSet* versions, const std::atomic<bool>* shutting_down,
LogBuffer* log_buffer, FSDirectory* output_directory, Statistics* stats,
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
std::vector<SequenceNumber> existing_snapshots,
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
const std::string& dbname, const std::shared_ptr<IOTracer>& io_tracer,
JobContext* job_context, std::shared_ptr<Cache> table_cache,
EventLogger* event_logger, const std::string& dbname,
const std::shared_ptr<IOTracer>& io_tracer,
const std::atomic<bool>& manual_compaction_canceled,
const std::string& db_id, const std::string& db_session_id,
std::string output_path,
@@ -309,9 +315,8 @@ CompactionServiceCompactionJob::CompactionServiceCompactionJob(
: CompactionJob(job_id, compaction, db_options, mutable_db_options,
file_options, versions, shutting_down, log_buffer, nullptr,
output_directory, nullptr, stats, db_mutex,
db_error_handler, std::move(existing_snapshots),
kMaxSequenceNumber, nullptr, nullptr,
std::move(table_cache), event_logger,
db_error_handler, job_context, std::move(table_cache),
event_logger,
compaction->mutable_cf_options().paranoid_file_checks,
compaction->mutable_cf_options().report_bg_io_stats, dbname,
&(compaction_service_result->stats), Env::Priority::USER,
@@ -421,14 +426,14 @@ Status CompactionServiceCompactionJob::Run() {
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, output_file.is_proximal_level,
meta.temperature);
MakeTableFileName(meta.fd.GetNumber()), meta.fd.GetFileSize(),
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,
output_file.is_proximal_level, meta.temperature);
}
}
@@ -528,6 +533,10 @@ static std::unordered_map<std::string, OptionTypeInfo>
{offsetof(struct CompactionServiceOutputFile, file_name),
OptionType::kEncodedString, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"file_size",
{offsetof(struct CompactionServiceOutputFile, file_size),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"smallest_seqno",
{offsetof(struct CompactionServiceOutputFile, smallest_seqno),
OptionType::kUInt64T, OptionVerificationType::kNormal,
+27
View File
@@ -7,6 +7,7 @@
#include "port/stack_trace.h"
#include "rocksdb/utilities/options_util.h"
#include "table/unique_id_impl.h"
#include "utilities/merge_operators/string_append/stringappend.h"
namespace ROCKSDB_NAMESPACE {
@@ -1222,6 +1223,32 @@ TEST_F(CompactionServiceTest, CompactionFilter) {
ASSERT_GE(my_cs->GetCompactionNum(), 1);
}
TEST_F(CompactionServiceTest, MergeOperator) {
Options options = CurrentOptions();
options.merge_operator.reset(new StringAppendOperator(','));
ReopenWithCompactionService(&options);
GenerateTestData();
ASSERT_OK(dbfull()->TEST_WaitForCompact());
for (int i = 0; i < 200; i++) {
ASSERT_OK(db_->Merge(WriteOptions(), Key(i),
"merge_op_append_" + std::to_string(i)));
}
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
// verify result
for (int i = 0; i < 200; i++) {
auto result = Get(Key(i));
if (i % 2) {
ASSERT_EQ(result, "value" + std::to_string(i) + ",merge_op_append_" +
std::to_string(i));
} else {
ASSERT_EQ(result, "value_new" + std::to_string(i) + ",merge_op_append_" +
std::to_string(i));
}
}
auto my_cs = GetCompactionService();
ASSERT_GE(my_cs->GetCompactionNum(), 1);
}
TEST_F(CompactionServiceTest, Snapshot) {
Options options = CurrentOptions();
ReopenWithCompactionService(&options);
+4
View File
@@ -106,7 +106,11 @@ class SubcompactionState {
subcompaction_job_info.subcompaction_job_id = static_cast<int>(sub_job_id);
subcompaction_job_info.base_input_level = c->start_level();
subcompaction_job_info.output_level = c->output_level();
subcompaction_job_info.compaction_reason = c->compaction_reason();
subcompaction_job_info.compression = c->output_compression();
subcompaction_job_info.stats = compaction_job_stats;
subcompaction_job_info.blob_compression_type =
c->mutable_cf_options().blob_compression_type;
}
SubcompactionState() = delete;
+6
View File
@@ -5086,6 +5086,7 @@ TEST_F(DBBasicTest, DisallowMemtableWrite) {
options_allow.create_if_missing = true;
Options options_disallow = options_allow;
options_disallow.disallow_memtable_writes = true;
options_disallow.paranoid_memory_checks = true;
DestroyAndReopen(options_allow);
// CFs allowing and disallowing memtable write
@@ -5125,6 +5126,11 @@ TEST_F(DBBasicTest, DisallowMemtableWrite) {
EXPECT_EQ(Get(2, "b2"), "2");
EXPECT_EQ(Get(3, "b3"), "NOT_FOUND");
std::unique_ptr<Iterator> iter(
dbfull()->NewIterator(ReadOptions(), handles_[3]));
iter->Seek("a3");
ASSERT_OK(iter->status());
iter.reset();
// When the DB is re-opened with WAL entries for a CF that is newly setting
// disallow_memtable_writes, we detect that and fail the open gracefully.
ASSERT_EQ(TryReopenWithColumnFamilies(
+269 -143
View File
@@ -455,6 +455,72 @@ TEST_P(DBCompactionTestWithParam, CompactionDeletionTrigger) {
}
}
#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
TEST_F(DBCompactionTest, UniversalReduceFileLockingRepickNothing) {
const int kFileNumCompactionTrigger = 3;
Options options = CurrentOptions();
options.compaction_options_universal.reduce_file_locking = true;
// Set `max_background_jobs` to be 3 to allow low and bottom priority thread
// to run compaction together
options.max_background_jobs = 3;
Env::Default()->SetBackgroundThreads(1, Env::Priority::BOTTOM);
options.num_levels = 3;
options.compaction_style = kCompactionStyleUniversal;
options.level0_file_num_compaction_trigger = kFileNumCompactionTrigger;
options.compaction_options_universal.max_size_amplification_percent = 1;
DestroyAndReopen(options);
// Need to get a token to enable compaction parallelism up to
// `max_background_compactions` jobs.
auto pressure_token =
dbfull()->TEST_write_controler().GetCompactionPressureToken();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
{// Wait for the full (bottom-priority) compaction to be pre-picked as an
// intent (that is allowing files to be picked by other compactions and
// will pick later when the bottom-priority thread is available to
// execute the compaction) before triggering the low-priority compaction.
{"DBImpl::BackgroundCompaction:ForwardToBottomPriPool",
"LowPriCompaction"},
// Wait for low-priority compaction to start before
// repicking for the full compaction intent (bottom-priority), enabling
// them to run in parallel.
{"DBImpl::BackgroundCompaction:NonTrivial",
"DBImpl::BGWorkBottomCompaction"}});
bool bottom_pri_compaction_attempt_repick = false;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"DBImpl::BackgroundCompaction():AfterPickCompactionBottomPri",
[&](void* arg) {
bottom_pri_compaction_attempt_repick = true;
Compaction* c = static_cast<Compaction*>(arg);
// Verify the intended full compaction for bottom priority thread does
// not get to run (i.e, output to bottommost level) since when it
// repicks its files, some of the the intended input files are already
// compacted by the low priority thread
assert(c == nullptr);
});
SyncPoint::GetInstance()->EnableProcessing();
for (int i = 0; i < kFileNumCompactionTrigger; ++i) {
if (i == 0) {
ASSERT_OK(Put("file_locked_for_bottom_pri_compaction", "value"));
} else {
ASSERT_OK(
Put("file_not_locked_for_bottom_pri_compaction" + std::to_string(i),
"value"));
}
ASSERT_OK(Flush());
}
TEST_SYNC_POINT("LowPriCompaction");
ASSERT_OK(Put("a_new_file_to_pick_for_low_pri_compaction", "value"));
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_TRUE(bottom_pri_compaction_attempt_repick);
}
TEST_F(DBCompactionTest, SkipStatsUpdateTest) {
// This test verify UpdateAccumulatedStats is not on
@@ -2824,46 +2890,99 @@ TEST_F(DBCompactionTest, L0_CompactionBug_Issue44_b) {
}
TEST_F(DBCompactionTest, ManualAutoRace) {
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
{{"DBImpl::BGWorkCompaction", "DBCompactionTest::ManualAutoRace:1"},
{"DBImpl::RunManualCompaction:WaitScheduled",
"BackgroundCallCompaction:0"}});
const int kNumL0FilesTrigger = 4;
// Verify that the auto compaction is retried after the conflicting exclusive
// manual compaction finishes for:
// 1. Non-bottom-priority compactions (tested with level compaction)
// 2. Bottom-priority compactions (tested with universal compaction)
for (auto compaction_style :
{kCompactionStyleLevel, kCompactionStyleUniversal}) {
Env::Default()->SetBackgroundThreads(
compaction_style == kCompactionStyleUniversal ? 2 : 0,
Env::Priority::BOTTOM);
for (auto universal_reduce_file_locking : {false, true}) {
if (compaction_style != kCompactionStyleUniversal &&
universal_reduce_file_locking) {
continue;
}
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
Options options = CurrentOptions();
options.num_levels = 3;
options.level0_file_num_compaction_trigger = kNumL0FilesTrigger;
options.compaction_style = compaction_style;
options.compaction_options_universal.reduce_file_locking =
universal_reduce_file_locking;
ASSERT_OK(Put(1, "foo", ""));
ASSERT_OK(Put(1, "bar", ""));
ASSERT_OK(Flush(1));
ASSERT_OK(Put(1, "foo", ""));
ASSERT_OK(Put(1, "bar", ""));
// Generate four files in CF 0, which should trigger an auto compaction
ASSERT_OK(Put("foo", ""));
ASSERT_OK(Put("bar", ""));
ASSERT_OK(Flush());
ASSERT_OK(Put("foo", ""));
ASSERT_OK(Put("bar", ""));
ASSERT_OK(Flush());
ASSERT_OK(Put("foo", ""));
ASSERT_OK(Put("bar", ""));
ASSERT_OK(Flush());
ASSERT_OK(Put("foo", ""));
ASSERT_OK(Put("bar", ""));
ASSERT_OK(Flush());
DestroyAndReopen(options);
CreateAndReopenWithCF({"exclusive_manual_compaction_cf"}, options);
// The auto compaction is scheduled but waited until here
TEST_SYNC_POINT("DBCompactionTest::ManualAutoRace:1");
// The auto compaction will wait until the manual compaction is registerd
// before processing so that it will be cancelled.
CompactRangeOptions cro;
cro.exclusive_manual_compaction = true;
ASSERT_OK(dbfull()->CompactRange(cro, handles_[1], nullptr, nullptr));
ASSERT_EQ("0,1", FilesPerLevel(1));
// Set up sync points to ensure that the auto compaction
// encounters a conflict from exclusive manual compaction before the auto
// compaction gets to pick files, This will trigger a retry later.
//
// Specifically, the sync points are set up as following:
// 1. Wait until background low-pri scheduled (not picking files yet) or
// bottom-pri scheduled (not repicking files yet) for
// `universal_reduce_file_locking = true` before triggering
// CompactRange()
//
// 2. Wait until the triggered CompactRange()
// registers its compaction and creates conflict before the auto
// compaction picks or repicks files for the background compaction.
if (compaction_style == kCompactionStyleLevel ||
!universal_reduce_file_locking) {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
{{"DBImpl::BGWorkCompaction", "DBCompactionTest::ManualAutoRace:1"},
{"DBImpl::RunManualCompaction:WaitScheduled",
"BackgroundCallCompaction:0"}});
} else {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
{{"DBImpl::BackgroundCompaction:ForwardToBottomPriPool",
"DBCompactionTest::ManualAutoRace:1"},
{"DBImpl::RunManualCompaction:WaitScheduled",
"BackgroundCallCompaction:0:BottomPri"}});
}
// Eventually the cancelled compaction will be rescheduled and executed.
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_EQ("0,1", FilesPerLevel(0));
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
bool encounter_conflict = false;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"DBImpl::BackgroundCompaction()::Conflict",
[&](void* /*arg*/) { encounter_conflict = true; });
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
// Generate files in CF 1 for exclusive CompactRange()
ASSERT_OK(Put(1, "foo", ""));
ASSERT_OK(Put(1, "bar", ""));
ASSERT_OK(Flush(1));
ASSERT_OK(Put(1, "foo", ""));
ASSERT_OK(Put(1, "bar", ""));
// Generate files in CF0 to trigger full compaction
for (int i = 0; i < kNumL0FilesTrigger; ++i) {
ASSERT_OK(Put("foo", ""));
ASSERT_OK(Put("bar", ""));
ASSERT_OK(Flush());
}
TEST_SYNC_POINT("DBCompactionTest::ManualAutoRace:1");
CompactRangeOptions cro;
cro.exclusive_manual_compaction = true;
ASSERT_OK(dbfull()->CompactRange(cro, handles_[1], nullptr, nullptr));
ASSERT_EQ(compaction_style == kCompactionStyleLevel ? "0,1" : "0,0,1",
FilesPerLevel(1));
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_TRUE(encounter_conflict);
// Verify that the auto compaction is eventually executed after the
// exclusive CompactRange() finishes.
ASSERT_EQ(compaction_style == kCompactionStyleLevel ? "0,1" : "0,0,1",
FilesPerLevel(0));
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
}
Env::Default()->SetBackgroundThreads(0, Env::Priority::BOTTOM);
}
}
TEST_P(DBCompactionTestWithParam, ManualCompaction) {
@@ -3986,41 +4105,51 @@ TEST_P(DBCompactionTestWithParam, IntraL0CompactionDoesNotObsoleteDeletions) {
TEST_P(DBCompactionTestWithParam, FullCompactionInBottomPriThreadPool) {
const int kNumFilesTrigger = 3;
Env::Default()->SetBackgroundThreads(1, Env::Priority::BOTTOM);
for (bool use_universal_compaction : {false, true}) {
Options options = CurrentOptions();
if (use_universal_compaction) {
options.compaction_style = kCompactionStyleUniversal;
} else {
options.compaction_style = kCompactionStyleLevel;
options.level_compaction_dynamic_level_bytes = true;
for (auto compaction_style :
{kCompactionStyleLevel, kCompactionStyleUniversal}) {
for (auto universal_reduce_file_locking : {false, true}) {
if (compaction_style != kCompactionStyleUniversal &&
universal_reduce_file_locking) {
continue;
}
Options options = CurrentOptions();
options.compaction_style = compaction_style;
if (compaction_style == kCompactionStyleLevel) {
options.level_compaction_dynamic_level_bytes = true;
} else {
options.compaction_options_universal.reduce_file_locking =
universal_reduce_file_locking;
// Trigger compaction if size amplification exceeds 110%
options.compaction_options_universal.max_size_amplification_percent =
110;
}
options.num_levels = 4;
options.write_buffer_size = 100 << 10; // 100KB
options.target_file_size_base = 32 << 10; // 32KB
options.level0_file_num_compaction_trigger = kNumFilesTrigger;
DestroyAndReopen(options);
int num_bottom_pri_compactions = 0;
SyncPoint::GetInstance()->SetCallBack(
"DBImpl::BGWorkBottomCompaction",
[&](void* /*arg*/) { ++num_bottom_pri_compactions; });
SyncPoint::GetInstance()->EnableProcessing();
Random rnd(301);
for (int num = 0; num < kNumFilesTrigger; num++) {
ASSERT_EQ(NumSortedRuns(), num);
int key_idx = 0;
GenerateNewFile(&rnd, &key_idx);
}
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_EQ(1, num_bottom_pri_compactions);
// Verify that size amplification did occur
ASSERT_EQ(NumSortedRuns(), 1);
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
}
options.num_levels = 4;
options.write_buffer_size = 100 << 10; // 100KB
options.target_file_size_base = 32 << 10; // 32KB
options.level0_file_num_compaction_trigger = kNumFilesTrigger;
// Trigger compaction if size amplification exceeds 110%
options.compaction_options_universal.max_size_amplification_percent = 110;
DestroyAndReopen(options);
int num_bottom_pri_compactions = 0;
SyncPoint::GetInstance()->SetCallBack(
"DBImpl::BGWorkBottomCompaction",
[&](void* /*arg*/) { ++num_bottom_pri_compactions; });
SyncPoint::GetInstance()->EnableProcessing();
Random rnd(301);
for (int num = 0; num < kNumFilesTrigger; num++) {
ASSERT_EQ(NumSortedRuns(), num);
int key_idx = 0;
GenerateNewFile(&rnd, &key_idx);
}
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_EQ(1, num_bottom_pri_compactions);
// Verify that size amplification did occur
ASSERT_EQ(NumSortedRuns(), 1);
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
}
Env::Default()->SetBackgroundThreads(0, Env::Priority::BOTTOM);
}
@@ -6634,7 +6763,7 @@ TEST_P(RoundRobinSubcompactionsAgainstResources, SubcompactionsUsingResources) {
// compaction is enough to make post-compaction L1 size less than
// the maximum size (this test assumes only one round-robin compaction
// is triggered by kLevelMaxLevelSize)
options.max_compaction_bytes = 100000000;
options.max_compaction_bytes = std::numeric_limits<uint64_t>::max();
DestroyAndReopen(options);
env_->SetBackgroundThreads(total_low_pri_threads_, Env::LOW);
@@ -6667,41 +6796,33 @@ TEST_P(RoundRobinSubcompactionsAgainstResources, SubcompactionsUsingResources) {
// More than 10 files are selected for round-robin under auto
// compaction. The number of planned subcompaction is restricted by
// the minimum number between available threads and compaction limits
ASSERT_EQ(num_planned_subcompactions - options.max_subcompactions,
std::min(total_low_pri_threads_, max_compaction_limits_) - 1);
auto actual_reserved_threads =
num_planned_subcompactions - options.max_subcompactions;
auto expected_reserved_threads =
std::min(total_low_pri_threads_, max_compaction_limits_) - 1;
ASSERT_EQ(actual_reserved_threads, expected_reserved_threads);
num_planned_subcompactions_verified = true;
});
SyncPoint::GetInstance()->LoadDependency(
{{"RoundRobinSubcompactionsAgainstResources:0",
"BackgroundCallCompaction:0"},
{"CompactionJob::AcquireSubcompactionResources:0",
"RoundRobinSubcompactionsAgainstResources:1"},
{"RoundRobinSubcompactionsAgainstResources:2",
"CompactionJob::AcquireSubcompactionResources:1"},
{"CompactionJob::ReleaseSubcompactionResources:0",
"RoundRobinSubcompactionsAgainstResources:3"},
{"RoundRobinSubcompactionsAgainstResources:4",
"CompactionJob::ReleaseSubcompactionResources:1"}});
int acquire_count = 0;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"CompactionJob::AcquireSubcompactionResources:0",
[&](void* /*arg*/) { acquire_count++; });
int release_count = 0;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"CompactionJob::ReleaseSubcompactionResources",
[&](void* /*arg*/) { release_count++; });
SyncPoint::GetInstance()->EnableProcessing();
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_OK(dbfull()->EnableAutoCompaction({dbfull()->DefaultColumnFamily()}));
TEST_SYNC_POINT("RoundRobinSubcompactionsAgainstResources:0");
TEST_SYNC_POINT("RoundRobinSubcompactionsAgainstResources:1");
auto pressure_token =
dbfull()->TEST_write_controler().GetCompactionPressureToken();
TEST_SYNC_POINT("RoundRobinSubcompactionsAgainstResources:2");
TEST_SYNC_POINT("RoundRobinSubcompactionsAgainstResources:3");
// We can reserve more threads now except one is being used
ASSERT_EQ(total_low_pri_threads_ - 1,
env_->ReserveThreads(total_low_pri_threads_, Env::Priority::LOW));
ASSERT_EQ(
total_low_pri_threads_ - 1,
env_->ReleaseThreads(total_low_pri_threads_ - 1, Env::Priority::LOW));
TEST_SYNC_POINT("RoundRobinSubcompactionsAgainstResources:4");
ASSERT_OK(dbfull()->EnableAutoCompaction({dbfull()->DefaultColumnFamily()}));
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_TRUE(num_planned_subcompactions_verified);
ASSERT_EQ(acquire_count, release_count);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
}
@@ -10006,55 +10127,60 @@ TEST_F(DBCompactionTest, BottomPriCompactionCountsTowardConcurrencyLimit) {
env_->SetBackgroundThreads(1, Env::Priority::BOTTOM);
Options options = CurrentOptions();
options.level0_file_num_compaction_trigger = kNumL0Files;
options.num_levels = kNumLevels;
DestroyAndReopen(options);
for (bool universal_reduce_file_locking : {false, true}) {
Options options = CurrentOptions();
options.level0_file_num_compaction_trigger = kNumL0Files;
options.num_levels = kNumLevels;
options.compaction_style = kCompactionStyleUniversal;
options.compaction_options_universal.reduce_file_locking =
universal_reduce_file_locking;
DestroyAndReopen(options);
// Setup last level to be non-empty since it's a bit unclear whether
// compaction to an empty level would be considered "bottommost".
ASSERT_OK(Put(Key(0), "val"));
ASSERT_OK(Flush());
MoveFilesToLevel(kNumLevels - 1);
SyncPoint::GetInstance()->LoadDependency(
{{"DBImpl::BGWorkBottomCompaction",
"DBCompactionTest::BottomPriCompactionCountsTowardConcurrencyLimit:"
"PreTriggerCompaction"},
{"DBCompactionTest::BottomPriCompactionCountsTowardConcurrencyLimit:"
"PostTriggerCompaction",
"BackgroundCallCompaction:0"}});
SyncPoint::GetInstance()->EnableProcessing();
port::Thread compact_range_thread([&] {
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
cro.exclusive_manual_compaction = false;
ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr));
});
// Sleep in the low-pri thread so any newly scheduled compaction will be
// queued. Otherwise it might finish before we check its existence.
test::SleepingBackgroundTask sleeping_task_low;
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low,
Env::Priority::LOW);
sleeping_task_low.WaitUntilSleeping();
TEST_SYNC_POINT(
"DBCompactionTest::BottomPriCompactionCountsTowardConcurrencyLimit:"
"PreTriggerCompaction");
for (int i = 0; i < kNumL0Files; ++i) {
// Setup last level to be non-empty since it's a bit unclear whether
// compaction to an empty level would be considered "bottommost".
ASSERT_OK(Put(Key(0), "val"));
ASSERT_OK(Flush());
}
ASSERT_EQ(0u, env_->GetThreadPoolQueueLen(Env::Priority::LOW));
TEST_SYNC_POINT(
"DBCompactionTest::BottomPriCompactionCountsTowardConcurrencyLimit:"
"PostTriggerCompaction");
MoveFilesToLevel(kNumLevels - 1);
sleeping_task_low.WakeUp();
sleeping_task_low.WaitUntilDone();
compact_range_thread.join();
SyncPoint::GetInstance()->LoadDependency(
{{"DBImpl::BGWorkBottomCompaction",
"DBCompactionTest::BottomPriCompactionCountsTowardConcurrencyLimit:"
"PreTriggerCompaction"},
{"DBCompactionTest::BottomPriCompactionCountsTowardConcurrencyLimit:"
"PostTriggerCompaction",
"BackgroundCallCompaction:0"}});
SyncPoint::GetInstance()->EnableProcessing();
port::Thread compact_range_thread([&] {
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
cro.exclusive_manual_compaction = false;
ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr));
});
// Sleep in the low-pri thread so any newly scheduled compaction will be
// queued. Otherwise it might finish before we check its existence.
test::SleepingBackgroundTask sleeping_task_low;
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask,
&sleeping_task_low, Env::Priority::LOW);
sleeping_task_low.WaitUntilSleeping();
TEST_SYNC_POINT(
"DBCompactionTest::BottomPriCompactionCountsTowardConcurrencyLimit:"
"PreTriggerCompaction");
for (int i = 0; i < kNumL0Files; ++i) {
ASSERT_OK(Put(Key(0), "val"));
ASSERT_OK(Flush());
}
ASSERT_EQ(0u, env_->GetThreadPoolQueueLen(Env::Priority::LOW));
TEST_SYNC_POINT(
"DBCompactionTest::BottomPriCompactionCountsTowardConcurrencyLimit:"
"PostTriggerCompaction");
sleeping_task_low.WakeUp();
sleeping_task_low.WaitUntilDone();
compact_range_thread.join();
}
}
TEST_F(DBCompactionTest, BottommostFileCompactionAllowIngestBehind) {
+1 -3
View File
@@ -5018,7 +5018,6 @@ void DBImpl::GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
assert(column_family);
auto* cfd =
static_cast_with_check<ColumnFamilyHandleImpl>(column_family)->cfd();
auto* sv = GetAndRefSuperVersion(cfd);
{
// Without mutex, Version::GetColumnFamilyMetaData will have data race
// with Compaction::MarkFilesBeingCompacted. One solution is to use mutex,
@@ -5030,9 +5029,8 @@ void DBImpl::GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
// DB::GetColumnFamilyMetaData is not called frequently, the regression
// should not be big. We still need to keep an eye on it.
InstrumentedMutexLock l(&mutex_);
sv->current->GetColumnFamilyMetaData(cf_meta);
cfd->current()->GetColumnFamilyMetaData(cf_meta);
}
ReturnAndCleanupSuperVersion(cfd, sv);
}
void DBImpl::GetAllColumnFamilyMetaData(
+22 -12
View File
@@ -1090,10 +1090,7 @@ class DBImpl : public DB {
void SetSnapshotChecker(SnapshotChecker* snapshot_checker);
// Fill JobContext with snapshot information needed by flush and compaction.
void GetSnapshotContext(JobContext* job_context,
std::vector<SequenceNumber>* snapshot_seqs,
SequenceNumber* earliest_write_conflict_snapshot,
SnapshotChecker** snapshot_checker);
void InitSnapshotContext(JobContext* job_context);
// Not thread-safe.
void SetRecoverableStatePreReleaseCallback(PreReleaseCallback* callback);
@@ -1955,12 +1952,19 @@ class DBImpl : public DB {
};
struct PrepickedCompaction {
// background compaction takes ownership of `compaction`.
// TODO(hx235): consider using std::shared_ptr for easier ownership
// management
Compaction* compaction;
// caller retains ownership of `manual_compaction_state` as it is reused
// across background compactions.
ManualCompactionState* manual_compaction_state; // nullptr if non-manual
// task limiter token is requested during compaction picking.
std::unique_ptr<TaskLimiterToken> task_token;
// If true, `compaction` is picked temporarily to express compaction intent
// and will be released before re-picking a real compaction based on the
// updated LSM shape when thread associated with `compaction` is ready to
// run
bool need_repick;
};
struct CompactionArg {
@@ -2051,14 +2055,13 @@ class DBImpl : public DB {
// Flush the in-memory write buffer to storage. Switches to a new
// log-file/memtable and writes a new descriptor iff successful. Then
// installs a new super version for the column family.
Status FlushMemTableToOutputFile(
ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options,
bool* madeProgress, JobContext* job_context, FlushReason flush_reason,
SuperVersionContext* superversion_context,
std::vector<SequenceNumber>& snapshot_seqs,
SequenceNumber earliest_write_conflict_snapshot,
SnapshotChecker* snapshot_checker, LogBuffer* log_buffer,
Env::Priority thread_pri);
Status FlushMemTableToOutputFile(ColumnFamilyData* cfd,
const MutableCFOptions& mutable_cf_options,
bool* madeProgress, JobContext* job_context,
FlushReason flush_reason,
SuperVersionContext* superversion_context,
LogBuffer* log_buffer,
Env::Priority thread_pri);
// Flush the memtables of (multiple) column families to multiple files on
// persistent storage.
@@ -2460,6 +2463,8 @@ class DBImpl : public DB {
bool* flush_rescheduled_to_retain_udt,
Env::Priority thread_pri);
Compaction* CreateIntendedCompactionForwardedToBottomPriorityPool(
Compaction* c);
bool EnoughRoomForCompaction(ColumnFamilyData* cfd,
const std::vector<CompactionInputFiles>& inputs,
bool* sfm_bookkeeping, LogBuffer* log_buffer);
@@ -2727,6 +2732,11 @@ class DBImpl : public DB {
const std::vector<ColumnFamilyHandle*>& column_families,
ErrorIteratorFuncType error_iterator_func);
bool ShouldPickCompaction(bool is_prepicked,
const PrepickedCompaction* prepicked_compaction);
void ResetBottomPriCompactionIntent(ColumnFamilyData* cfd,
std::unique_ptr<Compaction>& c);
// Lock over the persistent DB state. Non-nullptr iff successfully acquired.
FileLock* db_lock_ = nullptr;
+224 -99
View File
@@ -145,10 +145,7 @@ IOStatus DBImpl::SyncClosedWals(const WriteOptions& write_options,
Status DBImpl::FlushMemTableToOutputFile(
ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options,
bool* made_progress, JobContext* job_context, FlushReason flush_reason,
SuperVersionContext* superversion_context,
std::vector<SequenceNumber>& snapshot_seqs,
SequenceNumber earliest_write_conflict_snapshot,
SnapshotChecker* snapshot_checker, LogBuffer* log_buffer,
SuperVersionContext* superversion_context, LogBuffer* log_buffer,
Env::Priority thread_pri) {
mutex_.AssertHeld();
assert(cfd);
@@ -212,7 +209,6 @@ Status DBImpl::FlushMemTableToOutputFile(
FlushJob flush_job(
dbname_, cfd, immutable_db_options_, mutable_cf_options, max_memtable_id,
file_options_for_compaction_, versions_.get(), &mutex_, &shutting_down_,
snapshot_seqs, earliest_write_conflict_snapshot, snapshot_checker,
job_context, flush_reason, log_buffer, directories_.GetDbDir(),
GetDataDir(cfd, 0U),
GetCompressionFlush(cfd->ioptions(), mutable_cf_options), stats_,
@@ -397,11 +393,8 @@ Status DBImpl::FlushMemTablesToOutputFiles(
bg_flush_args, made_progress, job_context, log_buffer, thread_pri);
}
assert(bg_flush_args.size() == 1);
std::vector<SequenceNumber> snapshot_seqs;
SequenceNumber earliest_write_conflict_snapshot;
SnapshotChecker* snapshot_checker;
GetSnapshotContext(job_context, &snapshot_seqs,
&earliest_write_conflict_snapshot, &snapshot_checker);
InitSnapshotContext(job_context);
const auto& bg_flush_arg = bg_flush_args[0];
ColumnFamilyData* cfd = bg_flush_arg.cfd_;
// intentional infrequent copy for each flush
@@ -412,8 +405,7 @@ Status DBImpl::FlushMemTablesToOutputFiles(
FlushReason flush_reason = bg_flush_arg.flush_reason_;
Status s = FlushMemTableToOutputFile(
cfd, mutable_cf_options_copy, made_progress, job_context, flush_reason,
superversion_context, snapshot_seqs, earliest_write_conflict_snapshot,
snapshot_checker, log_buffer, thread_pri);
superversion_context, log_buffer, thread_pri);
return s;
}
@@ -448,12 +440,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
}
#endif /* !NDEBUG */
std::vector<SequenceNumber> snapshot_seqs;
SequenceNumber earliest_write_conflict_snapshot;
SnapshotChecker* snapshot_checker;
GetSnapshotContext(job_context, &snapshot_seqs,
&earliest_write_conflict_snapshot, &snapshot_checker);
InitSnapshotContext(job_context);
autovector<FSDirectory*> distinct_output_dirs;
autovector<std::string> distinct_output_dir_paths;
std::vector<std::unique_ptr<FlushJob>> jobs;
@@ -487,8 +474,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
jobs.emplace_back(new FlushJob(
dbname_, cfd, immutable_db_options_, mutable_cf_options,
max_memtable_id, file_options_for_compaction_, versions_.get(), &mutex_,
&shutting_down_, snapshot_seqs, earliest_write_conflict_snapshot,
snapshot_checker, job_context, flush_reason, log_buffer,
&shutting_down_, job_context, flush_reason, log_buffer,
directories_.GetDbDir(), data_dir,
GetCompressionFlush(cfd->ioptions(), mutable_cf_options), stats_,
&event_logger_, mutable_cf_options.report_bg_io_stats,
@@ -1395,6 +1381,9 @@ Status DBImpl::CompactFiles(const CompactionOptions& compact_options,
TEST_SYNC_POINT_CALLBACK("TestCompactFiles:PausingManualCompaction:3",
static_cast<void*>(const_cast<std::atomic<int>*>(
&manual_compaction_paused_)));
TEST_SYNC_POINT_CALLBACK("TestCancelCompactFiles:SuccessfulCompaction",
static_cast<void*>(const_cast<std::atomic<int>*>(
&manual_compaction_paused_)));
{
InstrumentedMutexLock l(&mutex_);
auto* current = cfd->current();
@@ -1447,7 +1436,12 @@ Status DBImpl::CompactFilesImpl(
if (shutting_down_.load(std::memory_order_acquire)) {
return Status::ShutdownInProgress();
}
if (manual_compaction_paused_.load(std::memory_order_acquire) > 0) {
// triggered by DisableManualCompactions or by user-set canceled flag in
// CompactionOptions
if (manual_compaction_paused_.load(std::memory_order_acquire) > 0 ||
(compact_options.canceled &&
compact_options.canceled->load(std::memory_order_acquire))) {
return Status::Incomplete(Status::SubCode::kManualCompactionPaused);
}
@@ -1518,11 +1512,7 @@ Status DBImpl::CompactFilesImpl(
// deletion compaction currently not allowed in CompactFiles.
assert(!c->deletion_compaction());
std::vector<SequenceNumber> snapshot_seqs;
SequenceNumber earliest_write_conflict_snapshot;
SnapshotChecker* snapshot_checker;
GetSnapshotContext(job_context, &snapshot_seqs,
&earliest_write_conflict_snapshot, &snapshot_checker);
InitSnapshotContext(job_context);
std::unique_ptr<std::list<uint64_t>::iterator> pending_outputs_inserted_elem(
new std::list<uint64_t>::iterator(
@@ -1536,7 +1526,6 @@ Status DBImpl::CompactFilesImpl(
log_buffer, directories_.GetDbDir(),
GetDataDir(c->column_family_data(), c->output_path_id()),
GetDataDir(c->column_family_data(), 0), stats_, &mutex_, &error_handler_,
snapshot_seqs, earliest_write_conflict_snapshot, snapshot_checker,
job_context, table_cache_, &event_logger_,
c->mutable_cf_options().paranoid_file_checks,
c->mutable_cf_options().report_bg_io_stats, dbname_,
@@ -2189,6 +2178,7 @@ Status DBImpl::RunManualCompaction(
// Don't throttle manual compaction, only count outstanding tasks.
assert(false);
}
ca->prepicked_compaction->need_repick = false;
manual.incomplete = false;
if (compaction->bottommost_level() &&
env_->GetBackgroundThreads(Env::Priority::BOTTOM) > 0) {
@@ -2718,6 +2708,10 @@ Status DBImpl::WaitUntilFlushWouldNotStallWrites(ColumnFamilyData* cfd,
// Finish waiting when ALL column families finish flushing memtables.
// resuming_from_bg_err indicates whether the caller is trying to resume from
// background error or in normal processing.
// Note that the wait finishes when the flush result is installed to column
// families' Versions and persisted in MANIFEST. It doesn't wait until
// SuperVersion to reflect the flush result, except for the case when
// flush_reason is `kExternalFileIngestion`.
Status DBImpl::WaitForFlushMemTables(
const autovector<ColumnFamilyData*>& cfds,
const autovector<const uint64_t*>& flush_memtable_ids,
@@ -3411,6 +3405,10 @@ void DBImpl::BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction,
bool made_progress = false;
JobContext job_context(next_job_id_.fetch_add(1), true);
TEST_SYNC_POINT("BackgroundCallCompaction:0");
if (bg_thread_pri == Env::Priority::BOTTOM) {
TEST_SYNC_POINT("BackgroundCallCompaction:0:BottomPri");
}
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL,
immutable_db_options_.info_log.get());
{
@@ -3646,34 +3644,54 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
: m->manual_end->DebugString(true).c_str()));
}
}
} else if (!is_prepicked && !compaction_queue_.empty()) {
} else if (ShouldPickCompaction(is_prepicked, prepicked_compaction)) {
bool need_repick = is_prepicked && prepicked_compaction->need_repick;
if (HasExclusiveManualCompaction()) {
// Can't compact right now, but try again later
TEST_SYNC_POINT("DBImpl::BackgroundCompaction()::Conflict");
// Stay in the compaction queue.
unscheduled_compactions_++;
// TODO(hx235): Resolve conflict between intended
// bottom-priority compaction (requiring repick, i.e., need_repick = true)
// and exclusive manual compaction by releasing the intended
// bottom-priority compaction.
if (!need_repick) {
// Can't compact right now, but try again later
//
// Increase `unscheduled_compactions_` directly so we
// don't need to
// dequeue and enqueue the CFD again in the compaction queue and thus
// keep the CFD's position in the queue
unscheduled_compactions_++;
return Status::OK();
return Status::OK();
}
}
auto cfd = PickCompactionFromQueue(&task_token, log_buffer);
if (cfd == nullptr) {
// Can't find any executable task from the compaction queue.
// All tasks have been throttled by compaction thread limiter.
++unscheduled_compactions_;
return Status::Busy();
}
ColumnFamilyData* cfd = nullptr;
// We unreference here because the following code will take a Ref() on
// this cfd if it is going to use it (Compaction class holds a
// reference).
// This will all happen under a mutex so we don't have to be afraid of
// somebody else deleting it.
if (cfd->UnrefAndTryDelete()) {
// This was the last reference of the column family, so no need to
// compact.
return Status::OK();
if (!need_repick) {
cfd = PickCompactionFromQueue(&task_token, log_buffer);
if (cfd == nullptr) {
// Can't find any executable task from the compaction queue.
// All tasks have been throttled by compaction thread limiter.
++unscheduled_compactions_;
return Status::Busy();
}
// We unreference here because the following code will take a Ref() on
// this cfd if it is going to use it (Compaction class holds a
// reference).
// This will all happen under a mutex so we don't have to be afraid of
// somebody else deleting it.
if (cfd->UnrefAndTryDelete()) {
// This was the last reference of the column family, so no need to
// compact.
return Status::OK();
}
} else {
cfd = c->column_family_data();
assert(cfd);
ResetBottomPriCompactionIntent(cfd, c);
assert(c == nullptr);
}
// Pick up latest mutable CF Options and use it throughout the
@@ -3687,21 +3705,24 @@ 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);
InitSnapshotContext(job_context);
assert(is_snapshot_supported_ || snapshots_.empty());
}
c.reset(cfd->PickCompaction(mutable_cf_options, mutable_db_options_,
snapshot_seqs, snapshot_checker, log_buffer));
TEST_SYNC_POINT("DBImpl::BackgroundCompaction():AfterPickCompaction");
c.reset(cfd->PickCompaction(
mutable_cf_options, mutable_db_options_, job_context->snapshot_seqs,
job_context->snapshot_checker, log_buffer,
thread_pri == Env::Priority::BOTTOM /* require_max_output_level */));
if (thread_pri == Env::Priority::LOW) {
TEST_SYNC_POINT("DBImpl::BackgroundCompaction():AfterPickCompaction");
} else if (thread_pri == Env::Priority::BOTTOM) {
TEST_SYNC_POINT_CALLBACK(
"DBImpl::BackgroundCompaction():AfterPickCompactionBottomPri",
c.get());
}
if (c != nullptr) {
bool enough_room = EnoughRoomForCompaction(
@@ -3715,7 +3736,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
->storage_info()
->ComputeCompactionScore(c->immutable_options(),
c->mutable_cf_options());
AddToCompactionQueue(cfd);
EnqueuePendingCompaction(cfd);
c.reset();
// Don't need to sleep here, because BackgroundCallCompaction
@@ -3737,16 +3758,21 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
// options take effect.
// 3) When we Pick a new compaction, we "remove" those files being
// compacted from the calculation, which then influences compaction
// score. Here we check if we need the new compaction even without the
// files that are currently being compacted. If we need another
// compaction, we might be able to execute it in parallel, so we add
// it to the queue and schedule a new thread.
if (cfd->NeedsCompaction()) {
// Yes, we need more compactions!
AddToCompactionQueue(cfd);
MaybeScheduleFlushOrCompaction();
}
// score. Inside EnqueuePendingCompaction(), we check if we need
// the new compaction even without the files that are currently being
// compacted. If we need another compaction, we might be able to
// execute it in parallel, so we add it to the queue and schedule a
// new thread.
EnqueuePendingCompaction(cfd);
MaybeScheduleFlushOrCompaction();
}
} else if (is_prepicked) {
ROCKS_LOG_BUFFER(
log_buffer,
"[%s] Pre-picked compaction repicked files for compaction as "
"required, "
"but upon re-evaluation, no compaction was found necessary \n",
cfd->GetName().c_str());
}
}
}
@@ -4123,14 +4149,16 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
ThreadStatusUtil::ResetThreadStatus();
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:AfterCompaction",
c->column_family_data());
} else if (!is_prepicked && c->output_level() > 0 &&
c->output_level() ==
} else if (!is_prepicked &&
Compaction::OutputToNonZeroMaxOutputLevel(
c->output_level(),
c->column_family_data()
->current()
->storage_info()
->MaxOutputLevel(
immutable_db_options_.allow_ingest_behind) &&
immutable_db_options_.allow_ingest_behind)) &&
env_->GetBackgroundThreads(Env::Priority::BOTTOM) > 0) {
assert(thread_pri == Env::Priority::LOW);
// Forward compactions involving last level to the bottom pool if it exists,
// such that compactions unlikely to contribute to write stalls can be
// delayed or deprioritized.
@@ -4139,7 +4167,23 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
ca->db = this;
ca->compaction_pri_ = Env::Priority::BOTTOM;
ca->prepicked_compaction = new PrepickedCompaction;
ca->prepicked_compaction->compaction = c.release();
// If `universal_reduce_file_locking` is true, we only lock a limited set of
// input files by creating an intended compaction to forward to bottom
// priority pool and repicking files when bottom priority thread
// gets to execute this intended compaction
const bool need_repick =
c->mutable_cf_options()
.compaction_options_universal.reduce_file_locking;
if (need_repick) {
ca->prepicked_compaction->compaction =
CreateIntendedCompactionForwardedToBottomPriorityPool(c.get());
c.reset();
ca->prepicked_compaction->need_repick = true;
} else {
ca->prepicked_compaction->compaction = c.release();
ca->prepicked_compaction->need_repick = false;
}
ca->prepicked_compaction->manual_compaction_state = nullptr;
// Transfer requested token, so it doesn't need to do it again.
ca->prepicked_compaction->task_token = std::move(task_token);
@@ -4154,11 +4198,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
output_level = c->output_level();
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:NonTrivial",
&output_level);
std::vector<SequenceNumber> snapshot_seqs;
SequenceNumber earliest_write_conflict_snapshot;
SnapshotChecker* snapshot_checker;
GetSnapshotContext(job_context, &snapshot_seqs,
&earliest_write_conflict_snapshot, &snapshot_checker);
InitSnapshotContext(job_context);
assert(is_snapshot_supported_ || snapshots_.empty());
CompactionJob compaction_job(
@@ -4167,8 +4207,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
&shutting_down_, log_buffer, directories_.GetDbDir(),
GetDataDir(c->column_family_data(), c->output_path_id()),
GetDataDir(c->column_family_data(), 0), stats_, &mutex_,
&error_handler_, snapshot_seqs, earliest_write_conflict_snapshot,
snapshot_checker, job_context, table_cache_, &event_logger_,
&error_handler_, job_context, table_cache_, &event_logger_,
c->mutable_cf_options().paranoid_file_checks,
c->mutable_cf_options().report_bg_io_stats, dbname_,
&compaction_job_stats, thread_pri, io_tracer_,
@@ -4188,8 +4227,15 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
NotifyOnCompactionBegin(c->column_family_data(), c.get(), status,
compaction_job_stats, job_context->job_id);
mutex_.Unlock();
TEST_SYNC_POINT_CALLBACK(
"DBImpl::BackgroundCompaction:NonTrivial:BeforeRun", nullptr);
if (thread_pri == Env::Priority::LOW) {
TEST_SYNC_POINT_CALLBACK(
"DBImpl::BackgroundCompaction:NonTrivial:BeforeRun", nullptr);
} else {
assert(thread_pri == Env::Priority::BOTTOM);
TEST_SYNC_POINT(
"DBImpl::BackgroundCompaction:NonTrivial:BeforeRunBottomPri");
}
// Should handle error?
compaction_job.Run().PermitUncheckedError();
TEST_SYNC_POINT("DBImpl::BackgroundCompaction:NonTrivial:AfterRun");
@@ -4283,9 +4329,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
->storage_info()
->ComputeCompactionScore(c->immutable_options(),
c->mutable_cf_options());
if (!cfd->queued_for_compaction()) {
AddToCompactionQueue(cfd);
}
EnqueuePendingCompaction(cfd);
}
}
// this will unref its input_version and column_family_data
@@ -4330,6 +4374,71 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
return status;
}
// Create an intended compaction to forward based on the original picked
// compaction. It serves two purposes while it is waiting
// for a bottom-priority thread becomes available to run:
// - Prevent the last input file (or sorted run if non-L0) from
// being included in compaction score calculations unnecessarily since the
// intended compaction is already scheduled to compact it
// - Allow other input files to be picked by low-priority compactions that can
// run right away
//
// Once a bottom-priority available to run this intended compaction, it will
// repick files to consider the LSM updates that occurred during the waiting
// period.
Compaction* DBImpl::CreateIntendedCompactionForwardedToBottomPriorityPool(
Compaction* c) {
auto* cfd = c->column_family_data();
const auto& io = c->immutable_options();
const auto& mo = c->mutable_cf_options();
auto* vstorage = c->input_version()->storage_info();
std::vector<CompactionInputFiles> inputs(1);
const std::vector<FileMetaData*>* max_intput_level_files = nullptr;
int max_intput_level = 0;
for (size_t i = c->num_input_levels(); i >= 1; --i) {
size_t level = i - 1;
if (c->num_input_files(level) > 0) {
max_intput_level = static_cast<int>(level);
max_intput_level_files = c->inputs(level);
break;
}
}
assert(max_intput_level_files);
assert(!max_intput_level_files->empty());
inputs[0].level = max_intput_level;
if (max_intput_level == 0) {
// The last input file
inputs[0].files.push_back(
(*max_intput_level_files)[max_intput_level_files->size() - 1]);
} else {
// The last input sorted run
for (FileMetaData* f : (*max_intput_level_files)) {
inputs[0].files.push_back(f);
}
}
c->ReleaseCompactionFiles(Status::OK());
Compaction* intended_compaction = new Compaction(
vstorage, io, mo, mutable_db_options_, std::move(inputs),
c->output_level(), c->target_output_file_size(),
c->max_compaction_bytes(), c->output_path_id(), c->output_compression(),
c->output_compression_opts(), c->output_temperature(),
c->max_subcompactions(), c->grandparents(),
std::nullopt /* earliest_snapshot */, nullptr /* snapshot_checker */);
cfd->compaction_picker()->RegisterCompaction(intended_compaction);
vstorage->ComputeCompactionScore(io, mo);
intended_compaction->FinalizeInputInfo(cfd->current());
return intended_compaction;
}
bool DBImpl::HasPendingManualCompaction() {
return (!manual_compaction_dequeue_.empty());
}
@@ -4601,31 +4710,33 @@ void DBImpl::SetSnapshotChecker(SnapshotChecker* snapshot_checker) {
snapshot_checker_.reset(snapshot_checker);
}
void DBImpl::GetSnapshotContext(
JobContext* job_context, std::vector<SequenceNumber>* snapshot_seqs,
SequenceNumber* earliest_write_conflict_snapshot,
SnapshotChecker** snapshot_checker_ptr) {
void DBImpl::InitSnapshotContext(JobContext* job_context) {
mutex_.AssertHeld();
assert(job_context != nullptr);
assert(snapshot_seqs != nullptr);
assert(earliest_write_conflict_snapshot != nullptr);
assert(snapshot_checker_ptr != nullptr);
*snapshot_checker_ptr = snapshot_checker_.get();
if (use_custom_gc_ && *snapshot_checker_ptr == nullptr) {
*snapshot_checker_ptr = DisableGCSnapshotChecker::Instance();
if (job_context->snapshot_context_initialized) {
return;
}
if (*snapshot_checker_ptr != nullptr) {
SnapshotChecker* snapshot_checker = snapshot_checker_.get();
if (use_custom_gc_ && !snapshot_checker) {
snapshot_checker = DisableGCSnapshotChecker::Instance();
}
std::unique_ptr<ManagedSnapshot> managed_snapshot = nullptr;
if (snapshot_checker) {
// If snapshot_checker is used, that means the flush/compaction may
// contain values not visible to snapshot taken after
// flush/compaction job starts. Take a snapshot and it will appear
// in snapshot_seqs and force compaction iterator to consider such
// snapshots.
const Snapshot* job_snapshot =
GetSnapshotImpl(false /*write_conflict_boundary*/, false /*lock*/);
job_context->job_snapshot.reset(new ManagedSnapshot(this, job_snapshot));
const Snapshot* snapshot =
GetSnapshotImpl(/*is_write_conflict_boundary=*/false, /*lock=*/false);
managed_snapshot.reset(new ManagedSnapshot(this, snapshot));
}
*snapshot_seqs = snapshots_.GetAll(earliest_write_conflict_snapshot);
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber;
std::vector<SequenceNumber> snapshot_seqs =
snapshots_.GetAll(&earliest_write_conflict_snapshot);
job_context->InitSnapshotContext(
snapshot_checker, std::move(managed_snapshot),
earliest_write_conflict_snapshot, std::move(snapshot_seqs));
}
Status DBImpl::WaitForCompact(
@@ -4684,4 +4795,18 @@ Status DBImpl::WaitForCompact(
}
}
bool DBImpl::ShouldPickCompaction(
bool is_prepicked, const PrepickedCompaction* prepicked_compaction) {
return (!is_prepicked && !compaction_queue_.empty()) ||
(is_prepicked && prepicked_compaction->need_repick);
}
void DBImpl::ResetBottomPriCompactionIntent(ColumnFamilyData* cfd,
std::unique_ptr<Compaction>& c) {
c->ReleaseCompactionFiles(Status::OK());
cfd->current()->storage_info()->ComputeCompactionScore(
c->immutable_options(), c->mutable_cf_options());
c.reset();
}
} // namespace ROCKSDB_NAMESPACE
+5 -2
View File
@@ -933,7 +933,10 @@ Status DBImplSecondary::CompactWithoutInstallation(
immutable_db_options_.info_log.get());
const int job_id = next_job_id_.fetch_add(1);
JobContext job_context(0, true /*create_superversion*/);
std::vector<SequenceNumber> snapshots = input.snapshots;
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber,
std::move(snapshots));
// use primary host's db_id for running the compaction, but db_session_id is
// using the local one, which is to make sure the unique id is unique from
// the remote compactors. Because the id is generated from db_id,
@@ -944,7 +947,7 @@ Status DBImplSecondary::CompactWithoutInstallation(
job_id, c.get(), immutable_db_options_, mutable_db_options_,
file_options_for_compaction_, versions_.get(), &shutting_down_,
&log_buffer, output_dir.get(), stats_, &mutex_, &error_handler_,
input.snapshots, table_cache_, &event_logger_, dbname_, io_tracer_,
&job_context, table_cache_, &event_logger_, dbname_, io_tracer_,
options.canceled ? *options.canceled : kManualCompactionCanceledFalse_,
input.db_id, db_session_id_, secondary_path_, input, result);
+2
View File
@@ -7,6 +7,8 @@
// 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.
#include <iomanip>
#include "db/db_test_util.h"
#include "port/stack_trace.h"
#include "test_util/testutil.h"
+32 -19
View File
@@ -66,11 +66,11 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
timestamp_lb_(read_options.iter_start_ts),
timestamp_size_(timestamp_ub_ ? timestamp_ub_->size() : 0),
active_mem_(active_mem),
memtable_seqno_lb_((active_mem_ && !active_mem_->IsEmpty())
? active_mem_->GetFirstSequenceNumber()
: kMaxSequenceNumber),
memtable_op_scan_flush_trigger_(
mutable_cf_options.memtable_op_scan_flush_trigger),
memtable_seqno_lb_(kMaxSequenceNumber),
memtable_op_scan_flush_trigger_(0),
avg_op_scan_flush_trigger_(0),
iter_step_since_seek_(1),
mem_hidden_op_scanned_since_seek_(0),
direction_(kForward),
valid_(false),
current_entry_is_merged_(false),
@@ -98,6 +98,25 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
// prefix_seek_opt_in_only should force total_order_seek whereever the caller
// is duplicating the original ReadOptions
assert(!ioptions.prefix_seek_opt_in_only || read_options.total_order_seek);
if (active_mem_) {
// FIXME: GetEarliestSequenceNumber() may return a seqno that is one smaller
// than the smallest seqno in the memtable. This violates its comment and
// entries with that seqno may not be in the active memtable. Before it's
// fixed, we use GetFirstSequenceNumber() for more accurate result.
memtable_seqno_lb_ = active_mem_->IsEmpty()
? active_mem_->GetEarliestSequenceNumber()
: active_mem_->GetFirstSequenceNumber();
memtable_op_scan_flush_trigger_ =
mutable_cf_options.memtable_op_scan_flush_trigger;
if (memtable_op_scan_flush_trigger_) {
// avg_op_scan_flush_trigger_ requires memtable_op_scan_flush_trigger_ > 0
avg_op_scan_flush_trigger_ =
mutable_cf_options.memtable_avg_op_scan_flush_trigger;
}
} else {
// memtable_op_scan_flush_trigger_ and avg_op_scan_flush_trigger_ are
// initialized to 0(disabled) as default.
}
}
Status DBIter::GetProperty(std::string prop_name, std::string* prop) {
@@ -159,6 +178,7 @@ void DBIter::Next() {
local_stats_.skip_count_ += num_internal_keys_skipped_;
local_stats_.skip_count_--;
num_internal_keys_skipped_ = 0;
iter_step_since_seek_++;
bool ok = true;
if (direction_ == kReverse) {
is_key_seqnum_zero_ = false;
@@ -373,8 +393,7 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
// to one.
bool reseek_done = false;
uint64_t mem_ops_scanned = 0;
bool marked_for_flush = false;
uint64_t mem_hidden_op_scanned = 0;
do {
// Will update is_key_seqnum_zero_ as soon as we parsed the current key
// but we need to save the previous value to be used in the loop.
@@ -431,12 +450,7 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
CompareKeyForSkip(ikey_.user_key, saved_key_.GetUserKey()) <= 0) {
num_skipped++; // skip this entry
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
if (memtable_op_scan_flush_trigger_ && active_mem_ &&
ikey_.sequence >= memtable_seqno_lb_ && !marked_for_flush &&
++mem_ops_scanned >= memtable_op_scan_flush_trigger_) {
active_mem_->MarkForFlush();
marked_for_flush = true;
}
MarkMemtableForFlushForPerOpTrigger(mem_hidden_op_scanned);
} else {
assert(!skipping_saved_key ||
CompareKeyForSkip(ikey_.user_key, saved_key_.GetUserKey()) > 0);
@@ -458,12 +472,7 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
!iter_.iter()->IsKeyPinned() /* copy */);
skipping_saved_key = true;
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
if (memtable_op_scan_flush_trigger_ && active_mem_ &&
ikey_.sequence >= memtable_seqno_lb_ && !marked_for_flush &&
++mem_ops_scanned >= memtable_op_scan_flush_trigger_) {
active_mem_->MarkForFlush();
marked_for_flush = true;
}
MarkMemtableForFlushForPerOpTrigger(mem_hidden_op_scanned);
}
break;
case kTypeValue:
@@ -1588,6 +1597,7 @@ void DBIter::Seek(const Slice& target) {
ResetBlobData();
ResetValueAndColumns();
ResetInternalKeysSkippedCounter();
MarkMemtableForFlushForAvgTrigger();
// Seek the inner iterator based on the target key.
{
@@ -1664,6 +1674,7 @@ void DBIter::SeekForPrev(const Slice& target) {
ResetBlobData();
ResetValueAndColumns();
ResetInternalKeysSkippedCounter();
MarkMemtableForFlushForAvgTrigger();
// Seek the inner iterator based on the target key.
{
@@ -1725,6 +1736,7 @@ void DBIter::SeekToFirst() {
ResetBlobData();
ResetValueAndColumns();
ResetInternalKeysSkippedCounter();
MarkMemtableForFlushForAvgTrigger();
ClearSavedValue();
is_key_seqnum_zero_ = false;
@@ -1788,6 +1800,7 @@ void DBIter::SeekToLast() {
ResetBlobData();
ResetValueAndColumns();
ResetInternalKeysSkippedCounter();
MarkMemtableForFlushForAvgTrigger();
ClearSavedValue();
is_key_seqnum_zero_ = false;
+38 -4
View File
@@ -12,7 +12,6 @@
#include <string>
#include "db/db_impl/db_impl.h"
#include "db/range_del_aggregator.h"
#include "memory/arena.h"
#include "options/cf_options.h"
#include "rocksdb/db.h"
@@ -62,7 +61,8 @@ class DBIter final : public Iterator {
//
// @param active_mem Pointer to the active memtable that `internal_iter`
// is reading from. If not null, the memtable can be marked for flush
// according to option mutable_cf_options.memtable_op_scan_flush_trigger.
// according to options mutable_cf_options.memtable_op_scan_flush_trigger
// and mutable_cf_options.memtable_avg_op_scan_flush_trigger.
// @param arena_mode If true, the DBIter will be allocated from the arena.
static DBIter* NewIter(Env* env, const ReadOptions& read_options,
const ImmutableOptions& ioptions,
@@ -145,6 +145,7 @@ class DBIter final : public Iterator {
void operator=(const DBIter&) = delete;
~DBIter() override {
MarkMemtableForFlushForAvgTrigger();
ThreadStatus::OperationType cur_op_type =
ThreadStatusUtil::GetThreadOperation();
ThreadStatusUtil::SetThreadOperation(
@@ -417,6 +418,36 @@ class DBIter final : public Iterator {
return true;
}
void MarkMemtableForFlushForAvgTrigger() {
if (avg_op_scan_flush_trigger_ &&
mem_hidden_op_scanned_since_seek_ >= memtable_op_scan_flush_trigger_ &&
mem_hidden_op_scanned_since_seek_ >=
static_cast<uint64_t>(iter_step_since_seek_) *
avg_op_scan_flush_trigger_) {
assert(memtable_op_scan_flush_trigger_ > 0);
active_mem_->MarkForFlush();
avg_op_scan_flush_trigger_ = 0;
memtable_op_scan_flush_trigger_ = 0;
}
iter_step_since_seek_ = 1;
mem_hidden_op_scanned_since_seek_ = 0;
}
void MarkMemtableForFlushForPerOpTrigger(uint64_t& mem_hidden_op_scanned) {
if (memtable_op_scan_flush_trigger_ &&
ikey_.sequence >= memtable_seqno_lb_) {
if (++mem_hidden_op_scanned >= memtable_op_scan_flush_trigger_) {
active_mem_->MarkForFlush();
// Turn off the flush trigger checks.
memtable_op_scan_flush_trigger_ = 0;
avg_op_scan_flush_trigger_ = 0;
}
if (avg_op_scan_flush_trigger_) {
++mem_hidden_op_scanned_since_seek_;
}
}
}
const SliceTransform* prefix_extractor_;
Env* const env_;
SystemClock* clock_;
@@ -476,8 +507,11 @@ class DBIter final : public Iterator {
std::string saved_timestamp_;
std::optional<std::vector<ScanOptions>> scan_opts_;
ReadOnlyMemTable* const active_mem_;
const SequenceNumber memtable_seqno_lb_;
const uint32_t memtable_op_scan_flush_trigger_;
SequenceNumber memtable_seqno_lb_;
uint32_t memtable_op_scan_flush_trigger_;
uint32_t avg_op_scan_flush_trigger_;
uint32_t iter_step_since_seek_;
uint32_t mem_hidden_op_scanned_since_seek_;
Direction direction_;
bool valid_;
bool current_entry_is_merged_;
+155
View File
@@ -3890,6 +3890,7 @@ TEST_P(DBIteratorTest, MemtableOpsScanFlushTriggerWithSeek) {
db_->GetIntProperty("rocksdb.num-deletes-active-mem-table", &val));
ASSERT_EQ(0, val);
} else {
ASSERT_EQ(0, NumTableFilesAtLevel(0));
uint64_t val = 0;
ASSERT_TRUE(
db_->GetIntProperty("rocksdb.num-deletes-active-mem-table", &val));
@@ -3984,6 +3985,160 @@ TEST_P(DBIteratorTest, MemtableOpsScanFlushTriggerWithNext) {
}
}
}
TEST_P(DBIteratorTest, AverageMemtableOpsScanFlushTrigger) {
// Tests option memtable_avg_op_scan_flush_trigger with
// long tombstone sequences.
Random* r = Random::GetTLSInstance();
const int kAvgTrigger = 10;
const int kMaxTrigger = 500;
Options options;
options.create_if_missing = true;
options.memtable_op_scan_flush_trigger = kMaxTrigger;
options.memtable_avg_op_scan_flush_trigger = kAvgTrigger;
options.level_compaction_dynamic_level_bytes = true;
DestroyAndReopen(options);
const int kNumKeys = 1000;
// Base data that will be covered by a consecutive sequence of tombstones.
for (int i = 0; i < kNumKeys; ++i) {
ASSERT_OK(Put(Key(i), r->RandomString(50)));
}
ASSERT_OK(Flush());
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
ASSERT_EQ(1, NumTableFilesAtLevel(6));
for (int i = 0; i < kNumKeys; ++i) {
// We issue slightly more deletions than kAvgTrigger between visible keys
// to ensure avg skipped entries exceed kAvgTrigger.
if (i % (kAvgTrigger + 2) != 0) {
ASSERT_OK(SingleDelete(Key(i)));
}
}
// Each operation, except the first Seek, is expected to see kAvgTrigger + 1
// tombstones (from the active memtable) before it finds the next visible key.
SetPerfLevel(PerfLevel::kEnableCount);
get_perf_context()->Reset();
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
iter->Seek(Key(1));
ASSERT_EQ(get_perf_context()->next_on_memtable_count, kAvgTrigger + 1);
iter.reset();
// Should not flush since total entries skipped is below
// memtable_op_scan_flush_trigger
ASSERT_OK(Put(Key(0), "dummy write"));
ASSERT_OK(Put(Key(0), "dummy write"));
ASSERT_OK(db_->WaitForCompact({}));
ASSERT_EQ(0, NumTableFilesAtLevel(0));
get_perf_context()->Reset();
iter.reset(db_->NewIterator(ReadOptions()));
int num_ops = 1;
uint64_t num_skipped = 0;
iter->Seek(Key(0));
ASSERT_EQ(iter->key(), Key(0));
uint64_t last_memtable_next_count =
get_perf_context()->next_on_memtable_count;
iter->Next();
num_ops++;
while (iter->Valid()) {
ASSERT_OK(iter->status());
uint64_t num_skipped_in_op =
get_perf_context()->next_on_memtable_count - last_memtable_next_count;
ASSERT_GE(num_skipped_in_op, kAvgTrigger + 1);
last_memtable_next_count = get_perf_context()->next_on_memtable_count;
num_skipped += num_skipped_in_op;
iter->Next();
num_ops++;
}
// During iterator destruction we mark memtable for flush
iter.reset();
// avg trigger
ASSERT_GE(num_skipped, kAvgTrigger * num_ops);
// memtable_op_scan_flush_trigger
ASSERT_GE(num_skipped, kMaxTrigger);
// Average hidden entries scanned from memtable per operation is more than
// kAvgTrigger and the total skipped is more than
// memtable_op_scan_flush_trigger, the current memtable should be marked for
// flush. The following two writes will trigger the flush.
ASSERT_OK(Put(Key(0), "dummy write"));
// Before a write, we schedule memtables for flush if requested.
ASSERT_OK(Put(Key(0), "dummy write"));
ASSERT_OK(db_->WaitForCompact({}));
ASSERT_EQ(1, NumTableFilesAtLevel(0));
}
TEST_P(DBIteratorTest, AverageMemtableOpsScanFlushTriggerByOverwrites) {
// Tests option memtable_avg_op_scan_flush_trigger with overwrites to keys.
Random* r = Random::GetTLSInstance();
const int kAvgTrigger = 25;
Options options;
options.create_if_missing = true;
options.memtable_op_scan_flush_trigger = 250;
options.memtable_avg_op_scan_flush_trigger = kAvgTrigger;
options.level_compaction_dynamic_level_bytes = true;
DestroyAndReopen(options);
const int kNumKeys = 100;
// Base data that will be covered by a consecutive sequence of tombstones.
for (int i = 0; i < kNumKeys; ++i) {
ASSERT_OK(Put(Key(i), r->RandomString(50)));
}
ASSERT_OK(Flush());
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
ASSERT_EQ(1, NumTableFilesAtLevel(6));
// One visible key every 10 keys.
// Each non-visible user key has 3 non-visible entries in the active memtable.
for (int i = 0; i < kNumKeys; ++i) {
if (i % 10 != 0) {
ASSERT_OK(Put(Key(i), r->RandomString(50)));
ASSERT_OK(Put(Key(i), r->RandomString(50)));
ASSERT_OK(Delete(Key(i)));
}
}
SetPerfLevel(PerfLevel::kEnableCount);
get_perf_context()->Reset();
ReadOptions ro;
std::unique_ptr<Iterator> iter(db_->NewIterator(ro));
iter->Seek(Key(1));
ASSERT_GT(get_perf_context()->next_on_memtable_count, kAvgTrigger);
// Re-seek to trigger check for flush trigger
iter->Seek(Key(1));
// Should not flush since total entries skipped is below
// memtable_op_scan_flush_trigger
ASSERT_FALSE(static_cast<ColumnFamilyHandleImpl*>(db_->DefaultColumnFamily())
->cfd()
->mem()
->IsMarkedForFlush());
ASSERT_OK(Put(Key(0), "dummy write"));
ASSERT_OK(Put(Key(0), "dummy write"));
ASSERT_OK(db_->WaitForCompact({}));
ASSERT_EQ(0, NumTableFilesAtLevel(0));
get_perf_context()->Reset();
int num_ops = 1;
iter->Seek(Key(1));
while (iter->Valid()) {
num_ops++;
iter->Next();
}
ASSERT_GT(get_perf_context()->next_on_memtable_count, num_ops * kAvgTrigger);
// Re-seek should check conditions for marking memtable for flush
iter->Seek(Key(80));
// Average hidden entries scanned from memtable per operation is 2.
ASSERT_OK(Put(Key(0), "dummy write"));
// Before a write, we schedule memtables for flush if requested.
ASSERT_OK(Put(Key(0), "dummy write"));
ASSERT_OK(db_->WaitForCompact({}));
ASSERT_EQ(1, NumTableFilesAtLevel(0));
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+18
View File
@@ -151,6 +151,7 @@ TEST_F(DBTest, RequestIdPlumbingTest) {
options.env = env_;
// Create a mock environment to capture IODebugContext during reads
IODebugContext dbgCopy;
const std::string* captured_request_id_dbg;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
@@ -160,6 +161,8 @@ TEST_F(DBTest, RequestIdPlumbingTest) {
captured_request_id_dbg = nullptr;
} else {
captured_request_id_dbg = dbg->request_id;
// Test IODebugContext assignment operator
dbgCopy = *dbg;
}
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
@@ -178,6 +181,10 @@ TEST_F(DBTest, RequestIdPlumbingTest) {
// Verify the request_id was propagated to the file system
ASSERT_NE(captured_request_id_dbg, nullptr);
ASSERT_EQ(*captured_request_id_dbg, test_request_id);
ASSERT_NE(dbgCopy.request_id, nullptr);
ASSERT_NE(dbgCopy.request_id, captured_request_id_dbg);
ASSERT_EQ(*dbgCopy.request_id, test_request_id);
}
captured_request_id_dbg = nullptr;
@@ -197,6 +204,17 @@ TEST_F(DBTest, RequestIdPlumbingTest) {
// Verify the request_id was propagated to the file system
ASSERT_NE(captured_request_id_dbg, nullptr);
ASSERT_EQ(*captured_request_id_dbg, request_id);
ASSERT_NE(dbgCopy.request_id, nullptr);
ASSERT_NE(dbgCopy.request_id, captured_request_id_dbg);
ASSERT_EQ(*dbgCopy.request_id, request_id);
// Test IODebugContext copy constructor
IODebugContext dbgCopy2(dbgCopy);
ASSERT_NE(dbgCopy2.request_id, nullptr);
ASSERT_NE(dbgCopy2.request_id, captured_request_id_dbg);
ASSERT_NE(dbgCopy2.request_id, dbgCopy.request_id);
ASSERT_EQ(*dbgCopy2.request_id, request_id);
}
// test request_id plumbing during multiget
+319 -2
View File
@@ -10,7 +10,6 @@
#include <atomic>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include "db/db_test_util.h"
@@ -30,6 +29,7 @@
#include "test_util/testutil.h"
#include "util/defer.h"
#include "util/random.h"
#include "util/simple_mixed_compressor.h"
#include "utilities/fault_injection_env.h"
namespace ROCKSDB_NAMESPACE {
@@ -1883,6 +1883,225 @@ TEST_F(DBTest2, CompressionOptions) {
}
}
TEST_F(DBTest2, RoundRobinManager) {
if (ZSTD_Supported()) {
auto mgr = std::make_shared<RoundRobinManager>(
GetDefaultBuiltinCompressionManager());
for (CompressionType type : {kZSTD}) {
std::vector<std::string> values;
for (bool use_wrapper : {true}) {
SCOPED_TRACE("Compression type: " + std::to_string(type) +
(use_wrapper ? " with " : " no ") + "wrapper");
Options options = CurrentOptions();
options.compression = type;
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
options.statistics->set_stats_level(StatsLevel::kExceptTimeForMutex);
BlockBasedTableOptions bbto;
bbto.enable_index_compression = false;
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
options.compression_manager = use_wrapper ? mgr : nullptr;
DestroyAndReopen(options);
Random rnd(301);
constexpr int kCount = 13;
// Highly compressible blocks, except 1 non-compressible. Half of the
// compressible are morked for bypass and 1 marked for rejection. Values
// are large enough to ensure just 1 k-v per block.
for (int i = 0; i < kCount; ++i) {
std::string value;
if (i == 6) {
// One non-compressible block
value = rnd.RandomBinaryString(20000);
} else {
test::CompressibleString(&rnd, 0.1, 20000, &value);
}
values.push_back(value);
ASSERT_OK(Put(Key(i), value));
ASSERT_EQ(Get(Key(i)), value);
}
ASSERT_OK(Flush());
// Ensure well-formed for reads
for (int i = 0; i < kCount; ++i) {
ASSERT_NE(Get(Key(i)), "NOT_FOUND");
ASSERT_EQ(Get(Key(i)), values[i]);
}
ASSERT_EQ(Get(Key(kCount)), "NOT_FOUND");
}
}
}
}
TEST_F(DBTest2, RandomMixedCompressionManager) {
if (ZSTD_Supported()) {
auto mgr = std::make_shared<RandomMixedCompressionManager>(
GetDefaultBuiltinCompressionManager());
// Currently mixedmanager only supports with preffered compression manager
// zstd
for (CompressionType type : {kZSTD}) {
std::vector<std::string> values;
for (bool use_wrapper : {true}) {
SCOPED_TRACE("Compression type: " + std::to_string(type) +
(use_wrapper ? " with " : " no ") + "wrapper");
Options options = CurrentOptions();
options.compression = type;
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
options.statistics->set_stats_level(StatsLevel::kExceptTimeForMutex);
BlockBasedTableOptions bbto;
bbto.enable_index_compression = false;
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
options.compression_manager = use_wrapper ? mgr : nullptr;
DestroyAndReopen(options);
Random rnd(301);
constexpr int kCount = 13;
// Highly compressible blocks, except 1 non-compressible. Half of the
// compressible are morked for bypass and 1 marked for rejection. Values
// are large enough to ensure just 1 k-v per block.
for (int i = 0; i < kCount; ++i) {
std::string value;
if (i == 6) {
// One non-compressible block
value = rnd.RandomBinaryString(20000);
} else {
test::CompressibleString(&rnd, 0.1, 20000, &value);
}
values.push_back(value);
ASSERT_OK(Put(Key(i), value));
ASSERT_EQ(Get(Key(i)), value);
}
ASSERT_OK(Flush());
// Ensure well-formed for reads
for (int i = 0; i < kCount; ++i) {
ASSERT_NE(Get(Key(i)), "NOT_FOUND");
ASSERT_EQ(Get(Key(i)), values[i]);
}
ASSERT_EQ(Get(Key(kCount)), "NOT_FOUND");
}
}
}
}
TEST_F(DBTest2, CompressionManagerWrapper) {
// Test that we can use a custom CompressionManager to wrap the built-in
// CompressionManager, thus adopting a custom *strategy* based on existing
// algorithms. This will "mark" some blocks (in their contents) as "do not
// compress", i.e. no attempt to compress, and some blocks as "reject
// compression", i.e. compression attempted but rejected because of ratio
// or otherwise. These cases are distinguishable for statistics that
// approximate "wasted effort".
static std::string kDoNotCompress = "do_not_compress";
static std::string kRejectCompression = "reject_compression";
struct MyCompressor : public CompressorWrapper {
using CompressorWrapper::CompressorWrapper;
Status CompressBlock(Slice uncompressed_data,
std::string* compressed_output,
CompressionType* out_compression_type,
ManagedWorkingArea* working_area) override {
auto begin = uncompressed_data.data();
auto end = uncompressed_data.data() + uncompressed_data.size();
if (std::search(begin, end, kDoNotCompress.begin(),
kDoNotCompress.end()) != end) {
// Do not attempt compression
EXPECT_EQ(*out_compression_type, kNoCompression);
return Status::OK();
} else if (std::search(begin, end, kRejectCompression.begin(),
kRejectCompression.end()) != end) {
// Simulate attempted & rejected compression
*compressed_output = "blah";
EXPECT_EQ(*out_compression_type, kNoCompression);
return Status::OK();
} else {
return wrapped_->CompressBlock(uncompressed_data, compressed_output,
out_compression_type, working_area);
}
}
};
struct MyManager : public CompressionManagerWrapper {
using CompressionManagerWrapper::CompressionManagerWrapper;
const char* Name() const override { return wrapped_->Name(); }
std::unique_ptr<Compressor> GetCompressorForSST(
const FilterBuildingContext& context, const CompressionOptions& opts,
CompressionType preferred) override {
return std::make_unique<MyCompressor>(
wrapped_->GetCompressorForSST(context, opts, preferred));
}
};
auto mgr = std::make_shared<MyManager>(GetDefaultBuiltinCompressionManager());
for (CompressionType type : GetSupportedCompressions()) {
for (bool use_wrapper : {false, true}) {
if (type == kNoCompression) {
continue;
}
SCOPED_TRACE("Compression type: " + std::to_string(type) +
(use_wrapper ? " with " : " no ") + "wrapper");
Options options = CurrentOptions();
options.compression = type;
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
options.statistics->set_stats_level(StatsLevel::kExceptTimeForMutex);
BlockBasedTableOptions bbto;
bbto.enable_index_compression = false;
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
options.compression_manager = use_wrapper ? mgr : nullptr;
DestroyAndReopen(options);
auto PopStat = [&](Tickers t) -> uint64_t {
return options.statistics->getAndResetTickerCount(t);
};
Random rnd(301);
constexpr int kCount = 13;
// Highly compressible blocks, except 1 non-compressible. Half of the
// compressible are morked for bypass and 1 marked for rejection. Values
// are large enough to ensure just 1 k-v per block.
for (int i = 0; i < kCount; ++i) {
std::string value;
if (i == 6) {
// One non-compressible block
value = rnd.RandomBinaryString(20000);
} else {
test::CompressibleString(&rnd, 0.1, 20000, &value);
if ((i % 2) == 0) {
// Half for bypass
value += kDoNotCompress;
} else if (i == 7) {
// One for rejection
value += kRejectCompression;
}
}
ASSERT_OK(Put(Key(i), value));
}
ASSERT_OK(Flush());
if (use_wrapper) {
EXPECT_EQ(kCount / 2 - 1, PopStat(NUMBER_BLOCK_COMPRESSED));
EXPECT_EQ(kCount / 2, PopStat(NUMBER_BLOCK_COMPRESSION_BYPASSED));
EXPECT_EQ(1 + 1, PopStat(NUMBER_BLOCK_COMPRESSION_REJECTED));
} else {
EXPECT_EQ(kCount - 1, PopStat(NUMBER_BLOCK_COMPRESSED));
EXPECT_EQ(0, PopStat(NUMBER_BLOCK_COMPRESSION_BYPASSED));
EXPECT_EQ(1, PopStat(NUMBER_BLOCK_COMPRESSION_REJECTED));
}
// Ensure well-formed for reads
for (int i = 0; i < kCount; ++i) {
ASSERT_NE(Get(Key(i)), "NOT_FOUND");
}
ASSERT_EQ(Get(Key(kCount)), "NOT_FOUND");
}
}
}
class CompactionStallTestListener : public EventListener {
public:
CompactionStallTestListener()
@@ -3008,7 +3227,7 @@ TEST_F(DBTest2, PausingManualCompaction1) {
"TestCompactFiles:PausingManualCompaction:3", [&](void* arg) {
auto paused = static_cast<std::atomic<int>*>(arg);
// CompactFiles() relies on manual_compactions_paused to
// determine if thie compaction should be paused or not
// determine if this compaction should be paused or not
ASSERT_EQ(0, paused->load(std::memory_order_acquire));
paused->fetch_add(1, std::memory_order_release);
});
@@ -3120,6 +3339,7 @@ TEST_F(DBTest2, PausingManualCompaction3) {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
dbfull()->DisableManualCompaction();
ASSERT_TRUE(dbfull()
->CompactRange(compact_options, nullptr, nullptr)
.IsManualCompactionPaused());
@@ -5419,6 +5639,103 @@ TEST_F(DBTest2, TestCompactFiles) {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
}
TEST_F(DBTest2, TestCancelCompactFiles) {
SyncPoint::GetInstance()->EnableProcessing();
Options options;
options.env = env_;
options.num_levels = 2;
options.disable_auto_compactions = true;
Reopen(options);
auto* handle = db_->DefaultColumnFamily();
ASSERT_EQ(db_->NumberLevels(handle), 2);
ROCKSDB_NAMESPACE::SstFileWriter sst_file_writer{
ROCKSDB_NAMESPACE::EnvOptions(), options};
// ingest large SST files
std::vector<std::string> external_sst_file_names;
int key_counter = 0;
const int num_keys_per_file = 100000;
const int num_files = 10;
for (int i = 0; i < num_files; ++i) {
std::string file_name =
dbname_ + "/test_compact_files" + std::to_string(i) + ".sst_t";
external_sst_file_names.push_back(file_name);
ASSERT_OK(sst_file_writer.Open(file_name));
for (int j = 0; j < num_keys_per_file; ++j) {
ASSERT_OK(sst_file_writer.Put(Key(j + num_keys_per_file * key_counter),
std::to_string(j)));
}
key_counter += 1;
ASSERT_OK(sst_file_writer.Finish());
}
ASSERT_OK(db_->IngestExternalFile(handle, external_sst_file_names,
IngestExternalFileOptions()));
ASSERT_EQ(NumTableFilesAtLevel(1, 0), num_files);
std::vector<std::string> files;
GetSstFiles(env_, dbname_, &files);
ASSERT_EQ(files.size(), num_files);
// Test that 0 compactions happen - canceled is set to True initially
CompactionOptions compaction_options;
std::atomic<bool> canceled(true);
compaction_options.canceled = &canceled;
ASSERT_TRUE(db_->CompactFiles(compaction_options, handle, files, 1)
.IsManualCompactionPaused());
ASSERT_EQ(NumTableFilesAtLevel(1, 0), num_files);
// Test cancellation before the check to cancel compaction happens -
// compaction should not occur
bool disable_compaction = false;
compaction_options.canceled->store(false, std::memory_order_release);
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"TestCancelCompactFiles:SuccessfulCompaction", [&](void* arg) {
auto paused = static_cast<std::atomic<int>*>(arg);
if (disable_compaction) {
db_->DisableManualCompaction();
ASSERT_EQ(1, paused->load(std::memory_order_acquire));
} else {
compaction_options.canceled->store(true, std::memory_order_release);
ASSERT_EQ(0, paused->load(std::memory_order_acquire));
}
});
ASSERT_TRUE(db_->CompactFiles(compaction_options, handle, files, 1)
.IsManualCompactionPaused());
ASSERT_EQ(NumTableFilesAtLevel(1, 0), num_files);
// DisableManualCompaction() should successfully cancel compaction
disable_compaction = true;
compaction_options.canceled->store(false, std::memory_order_release);
ASSERT_TRUE(db_->CompactFiles(compaction_options, handle, files, 1)
.IsManualCompactionPaused());
ASSERT_EQ(NumTableFilesAtLevel(1, 0), num_files);
// unlike CompactRange, value of compaction_options.canceled will be
// unaffected by calling DisableManualCompactions()
ASSERT_FALSE(compaction_options.canceled->load());
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
db_->EnableManualCompaction();
// Test cancelation after the check to cancel compaction - compaction should
// occur, leaving only 1 file
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"CompactFilesImpl:0", [&](void* /*arg*/) {
compaction_options.canceled->store(true, std::memory_order_release);
});
compaction_options.canceled->store(false, std::memory_order_release);
ASSERT_OK(db_->CompactFiles(compaction_options, handle, files, 1));
ASSERT_EQ(NumTableFilesAtLevel(1, 0), 1);
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
}
TEST_F(DBTest2, MultiDBParallelOpenTest) {
const int kNumDbs = 2;
Options options = CurrentOptions();
+63 -43
View File
@@ -1672,55 +1672,75 @@ TEST_P(DBTestUniversalCompaction, ConcurrentBottomPriLowPriCompactions) {
}
const int kNumFilesTrigger = 3;
Env::Default()->SetBackgroundThreads(1, Env::Priority::BOTTOM);
Options options = CurrentOptions();
options.compaction_style = kCompactionStyleUniversal;
options.max_background_compactions = 2;
options.num_levels = num_levels_;
options.write_buffer_size = 100 << 10; // 100KB
options.target_file_size_base = 32 << 10; // 32KB
options.level0_file_num_compaction_trigger = kNumFilesTrigger;
// Trigger compaction if size amplification exceeds 110%
options.compaction_options_universal.max_size_amplification_percent = 110;
DestroyAndReopen(options);
// Need to get a token to enable compaction parallelism up to
// `max_background_compactions` jobs.
auto pressure_token =
dbfull()->TEST_write_controler().GetCompactionPressureToken();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
{// wait for the full compaction to be picked before adding files intended
// for the second one.
{"DBImpl::BackgroundCompaction:ForwardToBottomPriPool",
"DBTestUniversalCompaction:ConcurrentBottomPriLowPriCompactions:0"},
// the full (bottom-pri) compaction waits until a partial (low-pri)
// compaction has started to verify they can run in parallel.
{"DBImpl::BackgroundCompaction:NonTrivial",
"DBImpl::BGWorkBottomCompaction"}});
SyncPoint::GetInstance()->EnableProcessing();
for (bool universal_reduce_file_locking : {true, false}) {
Options options = CurrentOptions();
options.compaction_style = kCompactionStyleUniversal;
options.compaction_options_universal.reduce_file_locking =
universal_reduce_file_locking;
options.max_background_compactions = 2;
options.num_levels = num_levels_;
options.write_buffer_size = 100 << 10; // 100KB
options.target_file_size_base = 32 << 10; // 32KB
options.level0_file_num_compaction_trigger = kNumFilesTrigger;
// Trigger compaction if size amplification exceeds 110%
options.compaction_options_universal.max_size_amplification_percent = 110;
DestroyAndReopen(options);
Random rnd(301);
for (int i = 0; i < 2; ++i) {
for (int num = 0; num < kNumFilesTrigger; num++) {
int key_idx = 0;
GenerateNewFile(&rnd, &key_idx, true /* no_wait */);
// use no_wait above because that one waits for flush and compaction. We
// don't want to wait for compaction because the full compaction is
// intentionally blocked while more files are flushed.
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
// Need to get a token to enable compaction parallelism up to
// `max_background_compactions` jobs.
auto pressure_token =
dbfull()->TEST_write_controler().GetCompactionPressureToken();
if (universal_reduce_file_locking) {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
{// Wait for the full compaction to be repicked before adding files
// intended for the second compaction.
{"DBImpl::BackgroundCompaction():AfterPickCompactionBottomPri",
"DBTestUniversalCompaction:ConcurrentBottomPriLowPriCompactions:0"},
// Wait for the second compaction to run before running the full
// compaction to verify they can run in parallel
{"DBImpl::BackgroundCompaction:NonTrivial:BeforeRun",
"DBImpl::BackgroundCompaction:NonTrivial:BeforeRunBottomPri"}});
} else {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
{// Wait for the full compaction to be forwarded before adding files
// intended for the second compaction.
{"DBImpl::BackgroundCompaction:ForwardToBottomPriPool",
"DBTestUniversalCompaction:ConcurrentBottomPriLowPriCompactions:0"},
// Wait for the second compaction to run before running the full
// compaction to verify they can run in parallel
{"DBImpl::BackgroundCompaction:NonTrivial:BeforeRun",
"DBImpl::BackgroundCompaction:NonTrivial:BeforeRunBottomPri"}});
}
if (i == 0) {
TEST_SYNC_POINT(
"DBTestUniversalCompaction:ConcurrentBottomPriLowPriCompactions:0");
SyncPoint::GetInstance()->EnableProcessing();
Random rnd(301);
for (int i = 0; i < 2; ++i) {
for (int num = 0; num < kNumFilesTrigger; num++) {
int key_idx = 0;
GenerateNewFile(&rnd, &key_idx, true /* no_wait */);
// use no_wait above because that one waits for flush and compaction. We
// don't want to wait for compaction because the full compaction is
// intentionally blocked while more files are flushed.
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
}
if (i == 0) {
TEST_SYNC_POINT(
"DBTestUniversalCompaction:ConcurrentBottomPriLowPriCompactions:0");
}
}
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// First compaction should output to bottom level. Second should output to
// L0 since older L0 files pending compaction prevent it from being placed
// lower.
ASSERT_EQ(NumSortedRuns(), 2);
ASSERT_GT(NumTableFilesAtLevel(0), 0);
ASSERT_GT(NumTableFilesAtLevel(num_levels_ - 1), 0);
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
}
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// First compaction should output to bottom level. Second should output to L0
// since older L0 files pending compaction prevent it from being placed lower.
ASSERT_EQ(NumSortedRuns(), 2);
ASSERT_GT(NumTableFilesAtLevel(0), 0);
ASSERT_GT(NumTableFilesAtLevel(num_levels_ - 1), 0);
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
Env::Default()->SetBackgroundThreads(0, Env::Priority::BOTTOM);
}
+33 -15
View File
@@ -77,7 +77,12 @@ void EventHelpers::LogAndNotifyTableFileCreationFinished(
TableFileCreationReason reason, const Status& s,
const std::string& file_checksum,
const std::string& file_checksum_func_name) {
if (s.ok() && event_logger) {
if (!event_logger && listeners.empty()) {
s.PermitUncheckedError();
return;
}
if (event_logger) {
JSONWriter jwriter;
AppendCurrentTime(&jwriter);
jwriter << "cf_name" << cf_name << "job" << job_id << "event"
@@ -165,6 +170,8 @@ void EventHelpers::LogAndNotifyTableFileCreationFinished(
jwriter << "oldest_blob_file_number" << oldest_blob_file_number;
}
jwriter << "status" << s.ToString();
jwriter.EndObject();
event_logger->Log(jwriter);
@@ -195,18 +202,22 @@ void EventHelpers::LogAndNotifyTableFileDeletion(
const std::string& file_path, const Status& status,
const std::string& dbname,
const std::vector<std::shared_ptr<EventListener>>& listeners) {
JSONWriter jwriter;
AppendCurrentTime(&jwriter);
jwriter << "job" << job_id << "event" << "table_file_deletion"
<< "file_number" << file_number;
if (!status.ok()) {
jwriter << "status" << status.ToString();
if (!event_logger && listeners.empty()) {
status.PermitUncheckedError();
return;
}
jwriter.EndObject();
if (event_logger) {
JSONWriter jwriter;
AppendCurrentTime(&jwriter);
event_logger->Log(jwriter);
jwriter << "job" << job_id << "event" << "table_file_deletion"
<< "file_number" << file_number << "status" << status.ToString();
jwriter.EndObject();
event_logger->Log(jwriter);
}
if (listeners.empty()) {
return;
@@ -274,7 +285,12 @@ void EventHelpers::LogAndNotifyBlobFileCreationFinished(
const std::string& file_checksum,
const std::string& file_checksum_func_name, uint64_t total_blob_count,
uint64_t total_blob_bytes) {
if (s.ok() && event_logger) {
if (!event_logger && listeners.empty()) {
s.PermitUncheckedError();
return;
}
if (event_logger) {
JSONWriter jwriter;
AppendCurrentTime(&jwriter);
jwriter << "cf_name" << cf_name << "job" << job_id << "event"
@@ -305,15 +321,17 @@ void EventHelpers::LogAndNotifyBlobFileDeletion(
const std::vector<std::shared_ptr<EventListener>>& listeners, int job_id,
uint64_t file_number, const std::string& file_path, const Status& status,
const std::string& dbname) {
if (!event_logger && listeners.empty()) {
status.PermitUncheckedError();
return;
}
if (event_logger) {
JSONWriter jwriter;
AppendCurrentTime(&jwriter);
jwriter << "job" << job_id << "event" << "blob_file_deletion"
<< "file_number" << file_number;
if (!status.ok()) {
jwriter << "status" << status.ToString();
}
<< "file_number" << file_number << "status" << status.ToString();
jwriter.EndObject();
event_logger->Log(jwriter);
+27 -34
View File
@@ -92,12 +92,10 @@ FlushJob::FlushJob(
const MutableCFOptions& mutable_cf_options, uint64_t max_memtable_id,
const FileOptions& file_options, VersionSet* versions,
InstrumentedMutex* db_mutex, std::atomic<bool>* shutting_down,
std::vector<SequenceNumber> existing_snapshots,
SequenceNumber earliest_write_conflict_snapshot,
SnapshotChecker* snapshot_checker, JobContext* job_context,
FlushReason flush_reason, LogBuffer* log_buffer, FSDirectory* db_directory,
FSDirectory* output_file_directory, CompressionType output_compression,
Statistics* stats, EventLogger* event_logger, bool measure_io_stats,
JobContext* job_context, FlushReason flush_reason, LogBuffer* log_buffer,
FSDirectory* db_directory, FSDirectory* output_file_directory,
CompressionType output_compression, Statistics* stats,
EventLogger* event_logger, bool measure_io_stats,
const bool sync_output_directory, const bool write_manifest,
Env::Priority thread_pri, const std::shared_ptr<IOTracer>& io_tracer,
std::shared_ptr<const SeqnoToTimeMapping> seqno_to_time_mapping,
@@ -114,12 +112,7 @@ FlushJob::FlushJob(
versions_(versions),
db_mutex_(db_mutex),
shutting_down_(shutting_down),
existing_snapshots_(std::move(existing_snapshots)),
earliest_snapshot_(existing_snapshots_.empty()
? kMaxSequenceNumber
: existing_snapshots_.at(0)),
earliest_write_conflict_snapshot_(earliest_write_conflict_snapshot),
snapshot_checker_(snapshot_checker),
earliest_snapshot_(job_context->GetEarliestSnapshotSequence()),
job_context_(job_context),
flush_reason_(flush_reason),
log_buffer_(log_buffer),
@@ -140,6 +133,7 @@ FlushJob::FlushJob(
full_history_ts_low_(std::move(full_history_ts_low)),
blob_callback_(blob_callback),
seqno_to_time_mapping_(std::move(seqno_to_time_mapping)) {
assert(job_context->snapshot_context_initialized);
// Update the thread status to indicate flush.
ReportStartedFlush();
TEST_SYNC_POINT("FlushJob::FlushJob()");
@@ -456,7 +450,7 @@ Status FlushJob::MemPurge() {
const std::string* const full_history_ts_low = &(cfd_->GetFullHistoryTsLow());
std::unique_ptr<CompactionRangeDelAggregator> range_del_agg(
new CompactionRangeDelAggregator(&(cfd_->internal_comparator()),
existing_snapshots_,
job_context_->snapshot_seqs,
full_history_ts_low));
for (auto& rd_iter : range_del_iters) {
range_del_agg->AddTombstones(std::move(rd_iter));
@@ -495,19 +489,19 @@ Status FlushJob::MemPurge() {
Env* env = db_options_.env;
assert(env);
MergeHelper merge(
env, (cfd_->internal_comparator()).user_comparator(),
(ioptions.merge_operator).get(), compaction_filter.get(),
ioptions.logger, true /* internal key corruption is not ok */,
existing_snapshots_.empty() ? 0 : existing_snapshots_.back(),
snapshot_checker_);
MergeHelper merge(env, (cfd_->internal_comparator()).user_comparator(),
(ioptions.merge_operator).get(), compaction_filter.get(),
ioptions.logger,
true /* internal key corruption is not ok */,
job_context_->GetLatestSnapshotSequence(),
job_context_->snapshot_checker);
assert(job_context_);
SequenceNumber job_snapshot_seq = job_context_->GetJobSnapshotSequence();
const std::atomic<bool> kManualCompactionCanceledFalse{false};
CompactionIterator c_iter(
iter.get(), (cfd_->internal_comparator()).user_comparator(), &merge,
kMaxSequenceNumber, &existing_snapshots_, earliest_snapshot_,
earliest_write_conflict_snapshot_, job_snapshot_seq, snapshot_checker_,
kMaxSequenceNumber, &job_context_->snapshot_seqs, earliest_snapshot_,
job_context_->earliest_write_conflict_snapshot,
job_context_->GetJobSnapshotSequence(), job_context_->snapshot_checker,
env, ShouldReportDetailedTime(env, ioptions.stats),
true /* internal key corruption is not ok */, range_del_agg.get(),
nullptr, ioptions.allow_data_in_errors,
@@ -761,7 +755,7 @@ bool FlushJob::MemPurgeDecider(double threshold) {
// Pick the oldest existing snapshot that is more recent
// than the sequence number of the sampled entry.
min_seqno_snapshot = kMaxSequenceNumber;
for (SequenceNumber seq_num : existing_snapshots_) {
for (SequenceNumber seq_num : job_context_->snapshot_seqs) {
if (seq_num > res.sequence && seq_num < min_seqno_snapshot) {
min_seqno_snapshot = seq_num;
}
@@ -1000,20 +994,19 @@ Status FlushJob::WriteLevel0Table() {
preclude_last_level_min_seqno_ == kMaxSequenceNumber
? preclude_last_level_min_seqno_
: std::min(earliest_snapshot_, preclude_last_level_min_seqno_));
const SequenceNumber job_snapshot_seq =
job_context_->GetJobSnapshotSequence();
s = BuildTable(
dbname_, versions_, db_options_, tboptions, file_options_,
cfd_->table_cache(), iter.get(), std::move(range_del_iters), &meta_,
&blob_file_additions, existing_snapshots_, earliest_snapshot_,
earliest_write_conflict_snapshot_, job_snapshot_seq,
snapshot_checker_, mutable_cf_options_.paranoid_file_checks,
cfd_->internal_stats(), &io_s, io_tracer_,
BlobFileCreationReason::kFlush, seqno_to_time_mapping_.get(),
event_logger_, job_context_->job_id, &table_properties_, write_hint,
full_history_ts_low, blob_callback_, base_, &memtable_payload_bytes,
&memtable_garbage_bytes, &flush_stats);
&blob_file_additions, job_context_->snapshot_seqs, earliest_snapshot_,
job_context_->earliest_write_conflict_snapshot,
job_context_->GetJobSnapshotSequence(),
job_context_->snapshot_checker,
mutable_cf_options_.paranoid_file_checks, cfd_->internal_stats(),
&io_s, io_tracer_, BlobFileCreationReason::kFlush,
seqno_to_time_mapping_.get(), event_logger_, job_context_->job_id,
&table_properties_, write_hint, full_history_ts_low, blob_callback_,
base_, &memtable_payload_bytes, &memtable_garbage_bytes,
&flush_stats);
TEST_SYNC_POINT_CALLBACK("FlushJob::WriteLevel0Table:s", &s);
// TODO: Cleanup io_status in BuildTable and table builders
assert(!s.ok() || io_s.ok());
+3 -8
View File
@@ -63,11 +63,9 @@ class FlushJob {
const MutableCFOptions& mutable_cf_options, uint64_t max_memtable_id,
const FileOptions& file_options, VersionSet* versions,
InstrumentedMutex* db_mutex, std::atomic<bool>* shutting_down,
std::vector<SequenceNumber> existing_snapshots,
SequenceNumber earliest_write_conflict_snapshot,
SnapshotChecker* snapshot_checker, JobContext* job_context,
FlushReason flush_reason, LogBuffer* log_buffer,
FSDirectory* db_directory, FSDirectory* output_file_directory,
JobContext* job_context, FlushReason flush_reason,
LogBuffer* log_buffer, FSDirectory* db_directory,
FSDirectory* output_file_directory,
CompressionType output_compression, Statistics* stats,
EventLogger* event_logger, bool measure_io_stats,
const bool sync_output_directory, const bool write_manifest,
@@ -167,10 +165,7 @@ class FlushJob {
VersionSet* versions_;
InstrumentedMutex* db_mutex_;
std::atomic<bool>* shutting_down_;
std::vector<SequenceNumber> existing_snapshots_;
SequenceNumber earliest_snapshot_;
SequenceNumber earliest_write_conflict_snapshot_;
SnapshotChecker* snapshot_checker_;
JobContext* job_context_;
FlushReason flush_reason_;
LogBuffer* log_buffer_;
+65 -65
View File
@@ -186,16 +186,16 @@ TEST_F(FlushJobTest, Empty) {
JobContext job_context(0);
auto cfd = versions_->GetColumnFamilySet()->GetDefault();
EventLogger event_logger(db_options_.info_log.get());
SnapshotChecker* snapshot_checker = nullptr; // not relavant
FlushJob flush_job(
dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_,
cfd->GetLatestMutableCFOptions(),
std::numeric_limits<uint64_t>::max() /* memtable_id */, env_options_,
versions_.get(), &mutex_, &shutting_down_, {}, kMaxSequenceNumber,
snapshot_checker, &job_context, FlushReason::kTest, nullptr, nullptr,
nullptr, kNoCompression, nullptr, &event_logger, false,
true /* sync_output_directory */, true /* write_manifest */,
Env::Priority::USER, nullptr /*IOTracer*/, empty_seqno_to_time_mapping_);
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber, {});
FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(),
db_options_, cfd->GetLatestMutableCFOptions(),
std::numeric_limits<uint64_t>::max() /* memtable_id */,
env_options_, versions_.get(), &mutex_, &shutting_down_,
&job_context, FlushReason::kTest, nullptr, nullptr,
nullptr, kNoCompression, nullptr, &event_logger, false,
true /* sync_output_directory */,
true /* write_manifest */, Env::Priority::USER,
nullptr /*IOTracer*/, empty_seqno_to_time_mapping_);
{
InstrumentedMutexLock l(&mutex_);
flush_job.PickMemTable();
@@ -272,16 +272,16 @@ TEST_F(FlushJobTest, NonEmpty) {
}
EventLogger event_logger(db_options_.info_log.get());
SnapshotChecker* snapshot_checker = nullptr; // not relavant
FlushJob flush_job(
dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_,
cfd->GetLatestMutableCFOptions(),
std::numeric_limits<uint64_t>::max() /* memtable_id */, env_options_,
versions_.get(), &mutex_, &shutting_down_, {}, kMaxSequenceNumber,
snapshot_checker, &job_context, FlushReason::kTest, nullptr, nullptr,
nullptr, kNoCompression, db_options_.statistics.get(), &event_logger,
true, true /* sync_output_directory */, true /* write_manifest */,
Env::Priority::USER, nullptr /*IOTracer*/, empty_seqno_to_time_mapping_);
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber, {});
FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(),
db_options_, cfd->GetLatestMutableCFOptions(),
std::numeric_limits<uint64_t>::max() /* memtable_id */,
env_options_, versions_.get(), &mutex_, &shutting_down_,
&job_context, FlushReason::kTest, nullptr, nullptr,
nullptr, kNoCompression, db_options_.statistics.get(),
&event_logger, true, true /* sync_output_directory */,
true /* write_manifest */, Env::Priority::USER,
nullptr /*IOTracer*/, empty_seqno_to_time_mapping_);
HistogramData hist;
FileMetaData file_meta;
@@ -332,18 +332,18 @@ TEST_F(FlushJobTest, FlushMemTablesSingleColumnFamily) {
}
EventLogger event_logger(db_options_.info_log.get());
SnapshotChecker* snapshot_checker = nullptr; // not relavant
assert(memtable_ids.size() == num_mems);
uint64_t smallest_memtable_id = memtable_ids.front();
uint64_t flush_memtable_id = smallest_memtable_id + num_mems_to_flush - 1;
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber, {});
FlushJob flush_job(
dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_,
cfd->GetLatestMutableCFOptions(), flush_memtable_id, env_options_,
versions_.get(), &mutex_, &shutting_down_, {}, kMaxSequenceNumber,
snapshot_checker, &job_context, FlushReason::kTest, nullptr, nullptr,
nullptr, kNoCompression, db_options_.statistics.get(), &event_logger,
true, true /* sync_output_directory */, true /* write_manifest */,
versions_.get(), &mutex_, &shutting_down_, &job_context,
FlushReason::kTest, nullptr, nullptr, nullptr, kNoCompression,
db_options_.statistics.get(), &event_logger, true,
true /* sync_output_directory */, true /* write_manifest */,
Env::Priority::USER, nullptr /*IOTracer*/, empty_seqno_to_time_mapping_);
HistogramData hist;
FileMetaData file_meta;
@@ -405,18 +405,17 @@ TEST_F(FlushJobTest, FlushMemtablesMultipleColumnFamilies) {
}
EventLogger event_logger(db_options_.info_log.get());
SnapshotChecker* snapshot_checker = nullptr; // not relevant
std::vector<std::unique_ptr<FlushJob>> flush_jobs;
k = 0;
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber, {});
for (auto cfd : all_cfds) {
std::vector<SequenceNumber> snapshot_seqs;
flush_jobs.emplace_back(new FlushJob(
dbname_, cfd, db_options_, cfd->GetLatestMutableCFOptions(),
memtable_ids[k], env_options_, versions_.get(), &mutex_,
&shutting_down_, snapshot_seqs, kMaxSequenceNumber, snapshot_checker,
&job_context, FlushReason::kTest, nullptr, nullptr, nullptr,
kNoCompression, db_options_.statistics.get(), &event_logger, true,
false /* sync_output_directory */, false /* write_manifest */,
&shutting_down_, &job_context, FlushReason::kTest, nullptr, nullptr,
nullptr, kNoCompression, db_options_.statistics.get(), &event_logger,
true, false /* sync_output_directory */, false /* write_manifest */,
Env::Priority::USER, nullptr /*IOTracer*/,
empty_seqno_to_time_mapping_));
k++;
@@ -532,16 +531,17 @@ TEST_F(FlushJobTest, Snapshots) {
}
EventLogger event_logger(db_options_.info_log.get());
SnapshotChecker* snapshot_checker = nullptr; // not relavant
FlushJob flush_job(
dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_,
cfd->GetLatestMutableCFOptions(),
std::numeric_limits<uint64_t>::max() /* memtable_id */, env_options_,
versions_.get(), &mutex_, &shutting_down_, snapshots, kMaxSequenceNumber,
snapshot_checker, &job_context, FlushReason::kTest, nullptr, nullptr,
nullptr, kNoCompression, db_options_.statistics.get(), &event_logger,
true, true /* sync_output_directory */, true /* write_manifest */,
Env::Priority::USER, nullptr /*IOTracer*/, empty_seqno_to_time_mapping_);
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber,
std::move(snapshots));
FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(),
db_options_, cfd->GetLatestMutableCFOptions(),
std::numeric_limits<uint64_t>::max() /* memtable_id */,
env_options_, versions_.get(), &mutex_, &shutting_down_,
&job_context, FlushReason::kTest, nullptr, nullptr,
nullptr, kNoCompression, db_options_.statistics.get(),
&event_logger, true, true /* sync_output_directory */,
true /* write_manifest */, Env::Priority::USER,
nullptr /*IOTracer*/, empty_seqno_to_time_mapping_);
mutex_.Lock();
flush_job.PickMemTable();
ASSERT_OK(flush_job.Run());
@@ -585,18 +585,18 @@ TEST_F(FlushJobTest, GetRateLimiterPriorityForWrite) {
}
EventLogger event_logger(db_options_.info_log.get());
SnapshotChecker* snapshot_checker = nullptr; // not relavant
assert(memtable_ids.size() == num_mems);
uint64_t smallest_memtable_id = memtable_ids.front();
uint64_t flush_memtable_id = smallest_memtable_id + num_mems_to_flush - 1;
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber, {});
FlushJob flush_job(
dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_,
cfd->GetLatestMutableCFOptions(), flush_memtable_id, env_options_,
versions_.get(), &mutex_, &shutting_down_, {}, kMaxSequenceNumber,
snapshot_checker, &job_context, FlushReason::kTest, nullptr, nullptr,
nullptr, kNoCompression, db_options_.statistics.get(), &event_logger,
true, true /* sync_output_directory */, true /* write_manifest */,
versions_.get(), &mutex_, &shutting_down_, &job_context,
FlushReason::kTest, nullptr, nullptr, nullptr, kNoCompression,
db_options_.statistics.get(), &event_logger, true,
true /* sync_output_directory */, true /* write_manifest */,
Env::Priority::USER, nullptr /*IOTracer*/, empty_seqno_to_time_mapping_);
// When the state from WriteController is normal.
@@ -658,16 +658,16 @@ TEST_F(FlushJobTest, ReplaceTimedPutWriteTimeWithPreferredSeqno) {
}
EventLogger event_logger(db_options_.info_log.get());
SnapshotChecker* snapshot_checker = nullptr; // not relevant
FlushJob flush_job(
dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_,
cfd->GetLatestMutableCFOptions(),
std::numeric_limits<uint64_t>::max() /* memtable_id */, env_options_,
versions_.get(), &mutex_, &shutting_down_, {}, kMaxSequenceNumber,
snapshot_checker, &job_context, FlushReason::kTest, nullptr, nullptr,
nullptr, kNoCompression, db_options_.statistics.get(), &event_logger,
true, true /* sync_output_directory */, true /* write_manifest */,
Env::Priority::USER, nullptr /*IOTracer*/, seqno_to_time_mapping);
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber, {});
FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(),
db_options_, cfd->GetLatestMutableCFOptions(),
std::numeric_limits<uint64_t>::max() /* memtable_id */,
env_options_, versions_.get(), &mutex_, &shutting_down_,
&job_context, FlushReason::kTest, nullptr, nullptr,
nullptr, kNoCompression, db_options_.statistics.get(),
&event_logger, true, true /* sync_output_directory */,
true /* write_manifest */, Env::Priority::USER,
nullptr /*IOTracer*/, seqno_to_time_mapping);
FileMetaData file_meta;
mutex_.Lock();
@@ -761,19 +761,19 @@ TEST_P(FlushJobTimestampTest, AllKeysExpired) {
}
std::vector<SequenceNumber> snapshots;
constexpr SnapshotChecker* const snapshot_checker = nullptr;
JobContext job_context(0);
EventLogger event_logger(db_options_.info_log.get());
std::string full_history_ts_low;
PutFixed64(&full_history_ts_low, std::numeric_limits<uint64_t>::max());
cfd->SetFullHistoryTsLow(full_history_ts_low);
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber, {});
FlushJob flush_job(
dbname_, cfd, db_options_, cfd->GetLatestMutableCFOptions(),
std::numeric_limits<uint64_t>::max() /* memtable_id */, env_options_,
versions_.get(), &mutex_, &shutting_down_, snapshots, kMaxSequenceNumber,
snapshot_checker, &job_context, FlushReason::kTest, nullptr, nullptr,
nullptr, kNoCompression, db_options_.statistics.get(), &event_logger,
true, true /* sync_output_directory */, true /* write_manifest */,
versions_.get(), &mutex_, &shutting_down_, &job_context,
FlushReason::kTest, nullptr, nullptr, nullptr, kNoCompression,
db_options_.statistics.get(), &event_logger, true,
true /* sync_output_directory */, true /* write_manifest */,
Env::Priority::USER, nullptr /*IOTracer*/, empty_seqno_to_time_mapping_,
/*db_id=*/"",
/*db_session_id=*/"", full_history_ts_low);
@@ -823,8 +823,8 @@ TEST_P(FlushJobTimestampTest, NoKeyExpired) {
}
std::vector<SequenceNumber> snapshots;
SnapshotChecker* const snapshot_checker = nullptr;
JobContext job_context(0);
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber, {});
EventLogger event_logger(db_options_.info_log.get());
std::string full_history_ts_low;
PutFixed64(&full_history_ts_low, 0);
@@ -832,10 +832,10 @@ TEST_P(FlushJobTimestampTest, NoKeyExpired) {
FlushJob flush_job(
dbname_, cfd, db_options_, cfd->GetLatestMutableCFOptions(),
std::numeric_limits<uint64_t>::max() /* memtable_id */, env_options_,
versions_.get(), &mutex_, &shutting_down_, snapshots, kMaxSequenceNumber,
snapshot_checker, &job_context, FlushReason::kTest, nullptr, nullptr,
nullptr, kNoCompression, db_options_.statistics.get(), &event_logger,
true, true /* sync_output_directory */, true /* write_manifest */,
versions_.get(), &mutex_, &shutting_down_, &job_context,
FlushReason::kTest, nullptr, nullptr, nullptr, kNoCompression,
db_options_.statistics.get(), &event_logger, true,
true /* sync_output_directory */, true /* write_manifest */,
Env::Priority::USER, nullptr /*IOTracer*/, empty_seqno_to_time_mapping_,
/*db_id=*/"",
/*db_session_id=*/"", full_history_ts_low);
+49 -11
View File
@@ -135,6 +135,37 @@ struct JobContext {
return kMaxSequenceNumber;
}
SequenceNumber GetLatestSnapshotSequence() const {
assert(snapshot_context_initialized);
if (snapshot_seqs.empty()) {
return 0;
}
return snapshot_seqs.back();
}
SequenceNumber GetEarliestSnapshotSequence() const {
assert(snapshot_context_initialized);
if (snapshot_seqs.empty()) {
return kMaxSequenceNumber;
}
return snapshot_seqs.front();
}
void InitSnapshotContext(SnapshotChecker* checker,
std::unique_ptr<ManagedSnapshot> managed_snapshot,
SequenceNumber earliest_write_conflict,
std::vector<SequenceNumber>&& snapshots) {
if (snapshot_context_initialized) {
return;
}
snapshot_context_initialized = true;
snapshot_checker = checker;
assert(!job_snapshot);
job_snapshot = std::move(managed_snapshot);
earliest_write_conflict_snapshot = earliest_write_conflict;
snapshot_seqs = std::move(snapshots);
}
// Structure to store information for candidate files to delete.
struct CandidateFileInfo {
std::string file_name;
@@ -146,9 +177,6 @@ struct JobContext {
}
};
// Unique job id
int job_id;
// a list of all files that we'll consider deleting
// (every once in a while this is filled up with all files
// in the DB directory)
@@ -197,14 +225,14 @@ struct JobContext {
// the current manifest_file_number, log_number and prev_log_number
// that corresponds to the set of files in 'live'.
uint64_t manifest_file_number;
uint64_t pending_manifest_file_number;
uint64_t manifest_file_number = 0;
uint64_t pending_manifest_file_number = 0;
// 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;
uint64_t log_number = 0;
uint64_t prev_log_number = 0;
uint64_t min_pending_output = 0;
uint64_t prev_wals_total_size = 0;
@@ -213,17 +241,27 @@ struct JobContext {
// Snapshot taken before flush/compaction job.
std::unique_ptr<ManagedSnapshot> job_snapshot;
SnapshotChecker* snapshot_checker = nullptr;
std::vector<SequenceNumber> snapshot_seqs;
// This is the earliest snapshot that could be used for write-conflict
// checking by a transaction. For any user-key newer than this snapshot, we
// should make sure not to remove evidence that a write occurred.
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber;
// Unique job id
int job_id;
bool snapshot_context_initialized = false;
explicit JobContext(int _job_id, bool create_superversion = false) {
job_id = _job_id;
manifest_file_number = 0;
pending_manifest_file_number = 0;
log_number = 0;
prev_log_number = 0;
superversion_contexts.emplace_back(
SuperVersionContext(create_superversion));
}
// Delete the default constructor
JobContext() = delete;
// For non-empty JobContext Clean() has to be called at least once before
// before destruction (see asserts in ~JobContext()). Should be called with
// unlocked DB mutex. Destructor doesn't call Clean() to avoid accidentally
+41
View File
@@ -537,6 +537,47 @@ TEST_F(EventListenerTest, DisableBGCompaction) {
ASSERT_GE(listener->slowdown_count, kSlowdownTrigger * 9);
}
class TestNumInputFilesTotalInputBytesPouplatedInListener
: public EventListener {
public:
void OnCompactionCompleted(DB* /*db*/, const CompactionJobInfo& ci) override {
std::lock_guard<std::mutex> lock(mutex_);
num_input_files = ci.stats.num_input_files;
total_num_of_bytes = ci.stats.total_input_bytes;
}
size_t num_input_files = 0;
size_t total_num_of_bytes = 0;
std::mutex mutex_;
};
TEST_F(EventListenerTest, NumInputFilesTotalBytesPopulated) {
Options options;
options.level_compaction_dynamic_level_bytes = false;
options.env = CurrentOptions().env;
options.create_if_missing = true;
options.memtable_factory.reset(test::NewSpecialSkipListFactory(
DBTestBase::kNumKeysByGenerateNewRandomFile));
TestNumInputFilesTotalInputBytesPouplatedInListener* listener =
new TestNumInputFilesTotalInputBytesPouplatedInListener();
options.listeners.emplace_back(listener);
options.level0_file_num_compaction_trigger = 4;
options.compaction_style = kCompactionStyleLevel;
DestroyAndReopen(options);
Random rnd(301);
ASSERT_EQ(listener->num_input_files, 0);
ASSERT_EQ(listener->total_num_of_bytes, 0);
// Write 4 files in L0
for (int i = 0; i < 4; i++) {
GenerateNewRandomFile(&rnd);
}
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_EQ(listener->num_input_files, 4);
ASSERT_NE(listener->total_num_of_bytes, 0);
}
class TestCompactionReasonListener : public EventListener {
public:
void OnCompactionCompleted(DB* /*db*/, const CompactionJobInfo& ci) override {
+9
View File
@@ -1135,6 +1135,15 @@ void DumpManifestHandler::CheckIterationResult(const log::Reader& reader,
// Print out DebugStrings. Can include non-terminating null characters.
fwrite(cfd->current()->DebugString(hex_).data(), sizeof(char),
cfd->current()->DebugString(hex_).size(), stdout);
fprintf(stdout,
"By default, manifest file dump prints LSM trees as if %d levels "
"were configured, "
"which is not necessarily true for the column family (CF) this "
"manifest is associated with. "
"Please consult other DB files, such as the OPTIONS file, to "
"confirm.\n",
cfd->ioptions().num_levels);
}
fprintf(stdout,
"next_file_number %" PRIu64 " last_sequence %" PRIu64
+3
View File
@@ -398,6 +398,7 @@ DECLARE_bool(use_adaptive_mutex_lru);
DECLARE_uint32(compress_format_version);
DECLARE_uint64(manifest_preallocation_size);
DECLARE_bool(enable_checksum_handoff);
DECLARE_string(compression_manager);
DECLARE_uint64(max_total_wal_size);
DECLARE_double(high_pri_pool_ratio);
DECLARE_double(low_pri_pool_ratio);
@@ -424,7 +425,9 @@ DECLARE_bool(track_and_verify_wals);
DECLARE_bool(enable_remote_compaction);
DECLARE_bool(auto_refresh_iterator_with_snapshot);
DECLARE_uint32(memtable_op_scan_flush_trigger);
DECLARE_uint32(memtable_avg_op_scan_flush_trigger);
DECLARE_uint32(ingest_wbwi_one_in);
DECLARE_bool(universal_reduce_file_locking);
constexpr long KB = 1024;
constexpr int kRandomValueMaxFactor = 3;
+14 -1
View File
@@ -467,7 +467,9 @@ DEFINE_uint64(blob_file_size,
DEFINE_string(blob_compression_type, "none",
"[Integrated BlobDB] The compression algorithm to use for large "
"values stored in blob files.");
DEFINE_string(compression_manager, "mixed",
"Ability to change compression manager specified in "
"simple_mixed_manager.h (mixed -> roundRobin)");
DEFINE_bool(enable_blob_garbage_collection,
ROCKSDB_NAMESPACE::AdvancedColumnFamilyOptions()
.enable_blob_garbage_collection,
@@ -1482,4 +1484,15 @@ DEFINE_uint32(
ROCKSDB_NAMESPACE::ColumnFamilyOptions().memtable_op_scan_flush_trigger,
"Sets CF option memtable_op_scan_flush_trigger.");
DEFINE_uint32(
memtable_avg_op_scan_flush_trigger,
ROCKSDB_NAMESPACE::ColumnFamilyOptions().memtable_avg_op_scan_flush_trigger,
"Sets CF option memtable_avg_op_scan_flush_trigger.");
DEFINE_bool(
universal_reduce_file_locking,
ROCKSDB_NAMESPACE::ColumnFamilyOptions()
.compaction_options_universal.reduce_file_locking,
"Sets "
"ColumnFamilyOptions().compaciton_options_universal.reduce_file_locking.");
#endif // GFLAGS
+42 -1
View File
@@ -34,6 +34,7 @@
#include "rocksdb/utilities/write_batch_with_index.h"
#include "test_util/testutil.h"
#include "util/cast_util.h"
#include "util/simple_mixed_compressor.h"
#include "utilities/backup/backup_engine_impl.h"
#include "utilities/fault_injection_fs.h"
#include "utilities/fault_injection_secondary_cache.h"
@@ -849,9 +850,15 @@ Status StressTest::NewTxn(WriteOptions& write_opts, ThreadState* thread,
assert(FLAGS_user_timestamp_size == 0);
if (thread->rand.OneIn(2)) {
txn_options.commit_bypass_memtable = true;
} else {
}
if (thread->rand.OneIn(2)) {
txn_options.large_txn_commit_optimize_threshold = 1;
}
if (thread->rand.OneIn(2) ||
(!txn_options.commit_bypass_memtable &&
txn_options.large_txn_commit_optimize_threshold != 1)) {
txn_options.large_txn_commit_optimize_byte_threshold = 1;
}
if (commit_bypass_memtable) {
*commit_bypass_memtable = txn_options.commit_bypass_memtable;
}
@@ -3405,7 +3412,39 @@ void StressTest::Open(SharedState* shared, bool reopen) {
InitializeOptionsFromFlags(cache_, filter_policy_, options_);
}
InitializeOptionsGeneral(cache_, filter_policy_, sqfc_factory_, options_);
if (!strcasecmp(FLAGS_compression_manager.c_str(), "mixed") ||
!strcasecmp(FLAGS_compression_manager.c_str(), "randommixed")) {
// Currently limited to ZSTD compression. Table property compression_name
// needs to set to zstd for now even when there can be more than one
// algorithm in the table under your compressor.
if (!ZSTD_Supported()) {
fprintf(stderr,
"ZSTD compression not supported thus mixed compression cannot be "
"used\n");
exit(1);
}
if (!strcasecmp(FLAGS_compression_manager.c_str(), "mixed")) {
auto mgr = std::make_shared<RoundRobinManager>(
GetDefaultBuiltinCompressionManager());
options_.compression_manager = mgr;
} else if (!strcasecmp(FLAGS_compression_manager.c_str(), "randommixed")) {
auto mgr = std::make_shared<RandomMixedCompressionManager>(
GetDefaultBuiltinCompressionManager());
options_.compression_manager = mgr;
}
options_.compression = kZSTD;
options_.bottommost_compression = kZSTD;
} else if (!strcasecmp(FLAGS_compression_manager.c_str(), "autoskip")) {
options_.compression_manager =
CreateAutoSkipCompressionManager(GetDefaultBuiltinCompressionManager());
} else if (!strcasecmp(FLAGS_compression_manager.c_str(), "none")) {
// Nothing to do using default compression manager
} else {
fprintf(stderr, "Unknown compression manager: %s\n",
FLAGS_compression_manager.c_str());
exit(1);
}
if (FLAGS_prefix_size == 0 && FLAGS_rep_factory == kHashSkipList) {
fprintf(stderr,
"prefeix_size cannot be zero if memtablerep == prefix_hash\n");
@@ -4331,6 +4370,8 @@ void InitializeOptionsFromFlags(
}
options.memtable_op_scan_flush_trigger = FLAGS_memtable_op_scan_flush_trigger;
options.compaction_options_universal.reduce_file_locking =
FLAGS_universal_reduce_file_locking;
}
void InitializeOptionsGeneral(
+3 -3
View File
@@ -687,9 +687,9 @@ IOStatus WritableFileWriter::WriteBufferedWithChecksum(const IOOptions& opts,
if (rate_limiter_ != nullptr && rate_limiter_priority_used != Env::IO_TOTAL) {
while (data_size > 0) {
size_t tmp_size;
tmp_size = rate_limiter_->RequestToken(data_size, buf_.Alignment(),
rate_limiter_priority_used, stats_,
RateLimiter::OpType::kWrite);
tmp_size =
rate_limiter_->RequestToken(data_size, 0, rate_limiter_priority_used,
stats_, RateLimiter::OpType::kWrite);
data_size -= tmp_size;
}
}
+515
View File
@@ -0,0 +1,515 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
//
// APIs for customizing compression in RocksDB.
//
// ***********************************************************************
// EXPERIMENTAL - subject to change while under development
// ***********************************************************************
#pragma once
#include "rocksdb/cache.h"
#include "rocksdb/compression_type.h"
#include "rocksdb/data_structure.h"
namespace ROCKSDB_NAMESPACE {
// TODO: alias/adapt for compression
struct FilterBuildingContext;
// A Compressor represents a very specific but potentially adapting strategy for
// compressing blocks, including the relevant algorithm(s), options, dictionary,
// etc. as applicable--every input except the sequence of bytes to compress.
// Compressor is generally thread-safe so can be shared by multiple threads. (It
// could make sense to convert unique_ptr<Compressor> to
// shared_ptr<Compressor>.) A Compressor for data files is expected to be used
// for just one file, so that compression strategy can be explicitly
// reconsidered for each new file. However, a Compressor for in-memory use could
// live indefinitely.
//
// If a single thread is doing many compressions under the same strategy, it
// should request a WorkingArea that will in some cases make repeated
// compression in a single thread more efficient. Unlike the rest of Compressor,
// each WorkingArea can only be used by one thread at a time. WorkingAreas can
// have pre-allocated space and/or data structures, and/or thread-local
// statistics that are later incorporated into shared statistics objects.
//
// The Compressor marks each block with a CompressionType to guide
// decompression. However, the compression dictionary (or whether there is one
// associated) is determined at Compressor creation time, though the process of
// getting a Compressor with a dictionary starts with a Compressor without
// dictionary (which will often be relevant alongside); see relevant functions.
// If the Compressor wants to decide block-by-block whether to apply the
// configured dictionary, that would need to be encoded in CompressionType or
// the compressed output. (NOTE: this was historically NOT encoded in
// CompressionType and instead implied by BlockType and the presence of a
// dictionary block in the file. Some of the resulting awkwardness includes
// a number of built-in CompressionTypes that ignore any dictionary block in
// the file; therefore they cannot accommodate dictionary compression in the
// future without a schema change / extension.)
class Compressor {
public:
Compressor() = default;
virtual ~Compressor() = default;
// Returns the max total bytes of for all sampled blocks for creating the data
// dictionary, or zero indicating dictionary compression should not be
// used/configured. This will typically be called after
// CompressionManager::GetCompressor() to see if samples should be accumulated
// and passed to MaybeCloneSpecialized().
virtual size_t GetMaxSampleSizeIfWantDict(CacheEntryRole block_type) const {
// Default implementation: no dictionary
(void)block_type;
return 0;
}
// Returns the serialized form of the data dictionary associated with this
// Compressor. NOTE: empty dict is equivalent to no dict.
virtual Slice GetSerializedDict() const { return Slice(); }
// If there's a dominant compression type returned by this compressor as
// configured, return it. Otherwise, return kDisableCompressionOption.
virtual CompressionType GetPreferredCompressionType() const {
return CompressionType::kDisableCompressionOption;
}
// Utility struct for providing sample data for the compression dictionary.
// Potentially extensible by callers of Compressor (but not recommended)
struct DictSampleArgs {
// All the sample input blocks stored contiguously
std::string sample_data;
// The lengths of each of the sample blocks in `sample_data`
std::vector<size_t> sample_lens;
bool empty() { return sample_data.empty(); }
bool Verify() {
size_t total_len = 0;
for (auto len : sample_lens) {
total_len += len;
}
return total_len == sample_data.size();
}
};
// Create potential variants of the same Compressor that might be
// (a) optimized for a particular block type (does not affect correct
// decompression), and/or
// (b) configured to use a compression dictionary, based on the given
// samples (decompression must provide the dictionary from
// GetSerializedDict())
// Return of nullptr indicates no specialization exists or was attempted
// and the caller is best to use the current Compressor for the desired
// scenario. Using CacheEntryRole:kMisc for block_type generally means
// "unspecified", and both parameters are merely suggestions. The exact
// dictionary associated with a returned compressor must be read from
// GetSerializedDict().
virtual std::unique_ptr<Compressor> MaybeCloneSpecialized(
CacheEntryRole block_type, DictSampleArgs&& dict_samples) {
// Default implementation: no specialization
(void)block_type;
(void)dict_samples;
// Caller should have checked GetMaxSampleSizeIfWantDict before attempting
// to provide dictionary samples
assert(dict_samples.empty());
return nullptr;
}
// A WorkingArea is an optional structure (both for callers and
// implementations) that can enable optimizing repeated compressions by
// reusing working space or thread-local tracking of statistics or trends.
// This enables use of ZSTD context, for example.
//
// EXTENSIBLE or reinterpret_cast-able by custom Compressor implementations
struct WorkingArea {};
protected:
// To allow for flexible re-use / reclaimation, we have explicit Get and
// Release functions, and usually wrap in a special RAII smart pointer.
// For example, a WorkingArea could be saved/recycled in thread-local or
// core-local storage, or heap managed, etc., though an explicit WorkingArea
// is only advised for repeated compression (by a single thread).
virtual void ReleaseWorkingArea(WorkingArea*) {}
public:
using ManagedWorkingArea =
ManagedPtr<WorkingArea, Compressor, &Compressor::ReleaseWorkingArea>;
// See struct WorkingArea above
virtual ManagedWorkingArea ObtainWorkingArea() {
// Default implementation: no working area
return {};
}
// Compress `uncompressed_data` to `compressed_output`, which should be
// passed in empty. Note that the compressed output will be decompressed
// by the sequence Decompressor::ExtractUncompressedSize() followed by
// Decompressor::DecompressBlock(), which must also be provided the same
// CompressionType saved in `out_compression_type`. (In many configurations,
// `compressed_output` will have a prefix storing the uncompressed_data size
// before the compressed bytes returned by the underlying compression
// algorithm. And the compression type is usually stored adjacent to the
// compressed data, or in some cases assumed/asserted based on the particular
// Compressor.)
//
// If return status is not OK, then some fatal condition has arisen. On OK
// status, setting `*out_compression_type = kNoCompression` means compression
// is declined and the caller should use the original uncompressed_data and
// ignore any result in `compressed_output`. Otherwise, compression has
// happened with results in `compressed_output` and `out_compression_type`,
// which are allowed to vary from call to call.
//
// The working area is optional and used to optimize repeated compression by
// a single thread. ManagedWorkingArea is provided rather than just
// WorkingArea so that it can be used only if the `owner` matches expectation.
// This could be useful for a Compressor wrapping more than one alternative
// underlying Compressor.
//
// TODO: instead of string, consider a buffer only large enough for max
// tolerable compressed size. Does that work for all existing algorithms?
// * Looks like Snappy doesn't support that. :(
// * Except perhaps using the Sink interface
// * But looks like everything else should. :)
// Could save CPU by eliminating extra zero-ing and giving up quicker when
// ratio is insufficient.
virtual Status CompressBlock(Slice uncompressed_data,
std::string* compressed_output,
CompressionType* out_compression_type,
ManagedWorkingArea* working_area) = 0;
// TODO: something to populate table properties based on settings, after all
// or as WorkingAreas released. Maybe also update stats, or that could be in
// thread-specific WorkingArea.
};
// A Decompressor usually has a wide capability to decompress all kinds of
// compressed data in the scope of a CompressionManager (see that class below),
// except
// (a) it might be optimized for or limited to a particular compression type(s)
// (see GetDecompressor* functions for in CompressionManager),
// (b) distinct Decompressors are required to decompress with compression
// dictionaries. (Decompressors are generally associated with empty/no
// dictionary unless created with MaybeCloneForDict().)
//
// Similar to Compressor, Decompressor is generally thread safe except that each
// WorkingArea can only be used by a single thread at a time.
//
// Decompressors known to be associated with no dictionary are typically
// returned as shared_ptr, because they are broadly usable across threads.
// Because compression dictionaries are externally managed (see
// MaybeCloneForDict()), Decompressors associated with compression dictionaries
// are typically returned as unique_ptr, so that they are more easily
// guaranteed not to outlive their dictionaries (e.g. in block cache).
// Decompressors associated with compression dictionaries might include a
// processed or "digested" form of the raw dictionary for efficient repeated
// compressions.
//
// NOTE: Splitting the interface between ExtractUncompressedSize and
// DecompressBlock leaves to the caller details of (and flexibility in)
// allocating buffers for decompressing into. For example, the data could be
// decompressed into part of a single buffer allocated to hold a block's
// uncompressed contents along with an in-memory object representation of the
// block (to reduce fragmentation and other overheads of separate objects).
class Decompressor {
public:
Decompressor() = default;
virtual ~Decompressor() = default;
// A name for logging / debugging purposes
virtual const char* Name() const = 0;
// A WorkingArea is an optional structure (both for callers and
// implementations) that can enable optimizing repeated decompressions by
// reusing working space or thread-local tracking of statistics. This enables
// use of ZSTD context, for example.
//
// EXTENSIBLE or reinterpret_cast-able by custom Compressor implementations
struct WorkingArea {};
protected:
// To allow for flexible re-use / reclaimation, we have explicit Obtain and
// Release functions, which are typically wrapped in a special RAII smart
// pointer. For example, a WorkingArea could be saved/recycled in thread-local
// or core-local storage, or heap managed, etc., though an explicit
// WorkingArea is only advised for repeated decompression (by a single
// thread).
virtual void ReleaseWorkingArea(WorkingArea* wa) {
// Default implementation: no working area
(void)wa;
assert(wa == nullptr);
}
public:
using ManagedWorkingArea =
ManagedPtr<WorkingArea, Decompressor, &Decompressor::ReleaseWorkingArea>;
virtual ManagedWorkingArea ObtainWorkingArea(CompressionType /*preferred*/) {
// Default implementation: no working area
return {};
}
// If this Decompressor is associated with a (de)compression dictionary
// (created with MaybeCloneForDict()), this returns a pointer to those raw (or
// "serialized") bytes, which are externally managed (see
// MaybeCloneForDict()).
// Default: empty slice => no dictionary
virtual const Slice& GetSerializedDict() const;
// Create a variant of this Decompressor in `out` using the specified raw
// ("serialized") dictionary. This step is required for decompressing data
// compressed with the same dictionary. The new Decompressor references the
// given Slice through its lifetime so the data it points to must be managed
// by the caller along with (or beyond) the new Decompressor. If the
// dictionary is processed into a form reusable by repeated compressions in
// many threads, that happens within this call.
//
// Must return OK if storing a result in `out`. Otherwise, could return values
// like NotSupported - dictionary compression is not (yet) supported for this
// kind of Decompressor.
// Corruption - dictionary is malformed (though many implementations will
// accept any data as a dictionary)
virtual Status MaybeCloneForDict(const Slice& /*serialized_dict*/,
std::unique_ptr<Decompressor>* /*out*/) {
return Status::NotSupported(
"Dictionary compression not (yet) supported by " + std::string(Name()));
}
// Memory size of this object and others it owns. Does not include the
// serialized dictionary (when used) which is externally managed.
virtual size_t ApproximateOwnedMemoryUsage() const {
// Default: negligible
return 0;
}
// Potentially extensible by callers of Decompressor (but not recommended)
struct Args {
CompressionType compression_type = kNoCompression;
Slice compressed_data;
uint64_t uncompressed_size = 0;
ManagedWorkingArea* working_area = nullptr;
};
// For efficiency on the read path, RocksDB strongly prefers the uncompressed
// data size to be encoded in the compressed data in an easily accessible way,
// so that allocation of a potentially long-lived buffer can be ideally sized.
// This function determines the uncompressed size and potentially modifies
// `args.compressed_data` to strip off the size metadata, for providing both
// to DecompressBlock along with an appropriate buffer based on that size.
// Some implementations will leave `compressed_data` unmodified and let
// DecompressBlock call a library function that processes a format that
// includes size metadata (e.g. Snappy).
//
// Even for legacy cases without size metadata (e.g. some very old RocksDB
// formats), an exact size is required and could require decompressing the
// data (here and in DecompressBlock()).
//
// Return non-OK in case of corrupt data or some other unworkable limitation
// or failure.
//
// The default implementation uses a standard format for prepending
// uncompressed size to the compressed payload. (RocksDB
// compress_format_version=2 except Snappy)
virtual Status ExtractUncompressedSize(Args& args);
// Called to decompress a block of data after running ExtractUncompressedSize
// on it. `args.compressed_data` is what ExtractUncompressedSize left there
// after potentially stripping off the uncompressed size metadata. Returns OK
// iff uncompressed data of size `uncompressed_size` is written to
// `uncompressed_output`.
virtual Status DecompressBlock(const Args& args,
char* uncompressed_output) = 0;
};
// A CompressionManager represents
// * When/where/how to use different compressions
// * A schema (or set of schemas) and implementation for mapping
// <CompressionType, dictionary, compressed data>
// to uncompressed data (or error), which can expand over time (error in fewer
// cases) for a given CompatibilityName() but can never change that mapping
// (because that would break backward compatibility, potential quiet
// corruption)
// TODO: consider adding optional streaming compression support (low priority)
class CompressionManager
: public std::enable_shared_from_this<CompressionManager>,
public Customizable {
public:
CompressionManager() = default;
virtual ~CompressionManager() = default;
static const char* Type() { return "CompressionManager"; }
// *************** Creating various Compression Managers *************** //
// A name for the schema family of this CompressionManager. In short, if
// two CompressionManagers have functionally the same Decompressor(s), they
// should have the same CompatibilityName(), so that a compatible
// CompressionManager/Decompressor might be used if the original is
// unavailable. (Name() can be useful in addition to CompatibilityName() for
// understanding what compression strategy was used.)
virtual const char* CompatibilityName() const = 0;
// Default implementation checks the current compatibility name and returns
// this CompressionManager (via `out`) if appropriate, and otherwise defers
// to CreateFromString().
virtual Status FindCompatibleCompressionManager(
Slice compatibility_name, std::shared_ptr<CompressionManager>* out);
// Create a CompressionManager from a string, including built-in
// CompressionManager types.
// TODO: ObjectLibrary stuff
static Status CreateFromString(const ConfigOptions& config_options,
const std::string& id,
std::shared_ptr<CompressionManager>* result);
// ************************* Compressor creation *********************** //
// Returning nullptr means compression is entirely disabled for the file,
// which is valid at the discretion of the CompressionManager. Returning
// nullptr should normally be the result if preferred == kNoCompression.
//
// These functions must be thread-safe.
// Get a compressor for an SST file.
// SUBJECT TO CHANGE
// TODO: is it practical to get ColumnFamilyOptions plumbed into here?
virtual std::unique_ptr<Compressor> GetCompressorForSST(
const FilterBuildingContext&, const CompressionOptions& opts,
CompressionType preferred) {
return GetCompressor(opts, preferred);
}
// Get a compressor for a generic/unspecified purpose (e.g. in-memory
// compression).
virtual std::unique_ptr<Compressor> GetCompressor(
const CompressionOptions& opts, CompressionType type) = 0;
// **************************** Decompressors ************************** //
// Get a decompressor that is compatible with any blocks compressed by
// compressors returned by this CompressionManager (at least this code
// revision and earlier). (NOTE: recommended to return a shared_ptr alias of
// this shared_ptr to a field that is a Decompressor.)
// Justification for not making CompressionManager inherit Decompressor: this
// tends to run into the diamond inheritance problem in implementations and
// potential overheads of virtual inheritance.
virtual std::shared_ptr<Decompressor> GetDecompressor() = 0;
// Compatible with same as above, but potentially optimized for a certain
// expected CompressionType
virtual std::shared_ptr<Decompressor> GetDecompressorOptimizeFor(
CompressionType /*optimize_for_type*/) {
// Safe default implementation
return GetDecompressor();
}
// Get a decompressor that is allowed to have support only for the
// CompressionTypes in the given start-to-end array (unique, sorted by
// unsigned char)
virtual std::shared_ptr<Decompressor> GetDecompressorForTypes(
const CompressionType* /*types_begin*/,
const CompressionType* /*types_end*/) {
// Safe default implementation
return GetDecompressor();
}
};
// ************************* Utility wrappers etc. *********************** //
class CompressorWrapper : public Compressor {
public:
explicit CompressorWrapper(std::unique_ptr<Compressor> compressor)
: wrapped_(std::move(compressor)) {}
// No copies
CompressorWrapper(const CompressorWrapper&) = delete;
CompressorWrapper& operator=(const CompressorWrapper&) = delete;
size_t GetMaxSampleSizeIfWantDict(CacheEntryRole block_type) const override {
return wrapped_->GetMaxSampleSizeIfWantDict(block_type);
}
Slice GetSerializedDict() const override {
return wrapped_->GetSerializedDict();
}
CompressionType GetPreferredCompressionType() const override {
return wrapped_->GetPreferredCompressionType();
}
std::unique_ptr<Compressor> MaybeCloneSpecialized(
CacheEntryRole block_type, DictSampleArgs&& dict_samples) override {
return wrapped_->MaybeCloneSpecialized(block_type, std::move(dict_samples));
}
ManagedWorkingArea ObtainWorkingArea() override {
return wrapped_->ObtainWorkingArea();
}
Status CompressBlock(Slice uncompressed_data, std::string* compressed_output,
CompressionType* out_compression_type,
ManagedWorkingArea* working_area) override {
return wrapped_->CompressBlock(uncompressed_data, compressed_output,
out_compression_type, working_area);
}
protected:
std::unique_ptr<Compressor> wrapped_;
};
// TODO: CompressorBase, for custom compressions
class CompressionManagerWrapper : public CompressionManager {
public:
explicit CompressionManagerWrapper(
std::shared_ptr<CompressionManager> wrapped)
: wrapped_(std::move(wrapped)) {}
const char* CompatibilityName() const override {
return wrapped_->CompatibilityName();
}
Status FindCompatibleCompressionManager(
Slice compatibility_name,
std::shared_ptr<CompressionManager>* out) override {
return wrapped_->FindCompatibleCompressionManager(compatibility_name, out);
}
std::unique_ptr<Compressor> GetCompressorForSST(
const FilterBuildingContext& context, const CompressionOptions& opts,
CompressionType preferred) override {
return wrapped_->GetCompressorForSST(context, opts, preferred);
}
std::unique_ptr<Compressor> GetCompressor(const CompressionOptions& opts,
CompressionType type) override {
return wrapped_->GetCompressor(opts, type);
}
std::shared_ptr<Decompressor> GetDecompressor() override {
return wrapped_->GetDecompressor();
}
std::shared_ptr<Decompressor> GetDecompressorOptimizeFor(
CompressionType optimize_for_type) override {
return wrapped_->GetDecompressorOptimizeFor(optimize_for_type);
}
std::shared_ptr<Decompressor> GetDecompressorForTypes(
const CompressionType* types_begin,
const CompressionType* types_end) override {
return wrapped_->GetDecompressorForTypes(types_begin, types_end);
}
protected:
std::shared_ptr<CompressionManager> wrapped_;
};
// Compression manager that implements built-in compression strategy. The
// behavior of
// compression_manager=nullptr with this
const std::shared_ptr<CompressionManager>&
GetDefaultBuiltinCompressionManager();
// Gets CompressionManager designed for the automated compression strategy.
// This may include deciding to compress or not.
// In future should be able to select compression algorithm based on the CPU
// utilization and IO constraints.
std::shared_ptr<CompressionManagerWrapper> CreateAutoSkipCompressionManager(
std::shared_ptr<CompressionManager> wrapped);
} // namespace ROCKSDB_NAMESPACE
+21 -2
View File
@@ -863,7 +863,7 @@ struct AdvancedColumnFamilyOptions {
//
// Default: 0 (disable the feature)
//
// Not dynamically changeable, change it requires db restart.
// Dynamically changeable through the SetOptions() API
uint64_t preclude_last_level_data_seconds = 0;
// EXPERIMENTAL
@@ -886,7 +886,7 @@ struct AdvancedColumnFamilyOptions {
//
// Default: 0 (disable the feature)
//
// Not dynamically changeable, change it requires db restart.
// Dynamically changeable through the SetOptions() API
uint64_t preserve_internal_time_seconds = 0;
// When set, large values (blobs) are written to separate blob files, and
@@ -1127,6 +1127,25 @@ struct AdvancedColumnFamilyOptions {
// Dynamically changeable through the SetOptions() API.
uint32_t memtable_op_scan_flush_trigger = 0;
// Similar to `memtable_op_scan_flush_trigger`, but this option applies to
// Next() calls between Seeks or until iterator destruction. If the average
// of the number of invisible entries scanned from the active memtable, the
// memtable will be marked for flush.
// Note that to avoid the case where the window between Seeks is too small,
// the option only takes effect if the total number of hidden entries scanned
// within a window is at least `memtable_op_scan_flush_trigger`. So this
// option is only effective when `memtable_op_scan_flush_trigger` is set.
//
// This option should be set to a lower value than
// `memtable_op_scan_flush_trigger`. It covers the case where an iterator
// scans through an expensive key range with many invisible entries from the
// active memtable, but the number of invisible entries per operation does not
// exceed `memtable_op_scan_flush_trigger`.
//
// Default: 0 (disabled)
// Dynamically changeable through the SetOptions() API.
uint32_t memtable_avg_op_scan_flush_trigger = 0;
// Create ColumnFamilyOptions with default values for all fields
AdvancedColumnFamilyOptions();
// Create ColumnFamilyOptions from Options
+6
View File
@@ -1507,6 +1507,12 @@ rocksdb_options_set_memtable_op_scan_flush_trigger(rocksdb_options_t*,
extern ROCKSDB_LIBRARY_API uint32_t
rocksdb_options_get_memtable_op_scan_flush_trigger(rocksdb_options_t*);
extern ROCKSDB_LIBRARY_API void
rocksdb_options_set_memtable_avg_op_scan_flush_trigger(rocksdb_options_t*,
uint32_t);
extern ROCKSDB_LIBRARY_API uint32_t
rocksdb_options_get_memtable_avg_op_scan_flush_trigger(rocksdb_options_t*);
enum {
rocksdb_statistics_level_disable_all = 0,
rocksdb_statistics_level_except_tickers =
+3
View File
@@ -182,4 +182,7 @@ struct CompressionOptions {
#endif
};
// See advanced_compression.h
class CompressionManager;
} // namespace ROCKSDB_NAMESPACE
+132 -34
View File
@@ -7,9 +7,9 @@
#include <assert.h>
#include <array>
#include <cstddef>
#include <cstdint>
#include <vector>
#include "rocksdb/rocksdb_namespace.h"
@@ -17,24 +17,33 @@ namespace ROCKSDB_NAMESPACE {
namespace detail {
int CountTrailingZeroBitsForSmallEnumSet(uint64_t);
int BitsSetToOneForSmallEnumSet(uint64_t);
} // namespace detail
// Represents a set of values of some enum type with a small number of
// possible enumerators. For now, it supports enums where no enumerator
// exceeds 63 when converted to int.
// Represents a set of values of some enum type with a small number of possible
// enumerators. Assumes that any combination of enumerators with values 0
// through MAX_ENUMERATOR (inclusive) might be part of the set. NOTE: would like
// to use std::bitset, but it doesn't support constexpr (in C++17) operations
// and doesn't support efficient iteration over sparse "set to true" entries.
template <typename ENUM_TYPE, ENUM_TYPE MAX_ENUMERATOR>
class SmallEnumSet {
private:
using StateT = uint64_t;
static constexpr int kStateBits = sizeof(StateT) * 8;
static constexpr int kMaxMax = kStateBits - 1;
static constexpr int kMaxValue = static_cast<int>(MAX_ENUMERATOR);
static_assert(kMaxValue >= 0);
static_assert(kMaxValue <= kMaxMax);
static_assert(kMaxValue < 1024, "MAX_ENUMERATOR is suspiciously large");
using PieceT = uint64_t;
static constexpr int kPieceBits = 64;
static constexpr int kPieceMask = 63;
static constexpr int kPieceShift = 6;
static constexpr int kPieceCount = kMaxValue / kPieceBits + 1;
using StateT = std::array<PieceT, kPieceCount>;
static constexpr int kStateBits = kPieceBits * kPieceCount;
static_assert(kStateBits == sizeof(StateT) * 8);
static_assert(kMaxValue <= kStateBits - 1);
public:
// construct / create
SmallEnumSet() : state_(0) {}
// construct / create empty set
SmallEnumSet() : state_{} {}
template <class... TRest>
/*implicit*/ constexpr SmallEnumSet(const ENUM_TYPE e, TRest... rest) {
@@ -44,8 +53,16 @@ class SmallEnumSet {
// Return the set that includes all valid values, assuming the enum
// is "dense" (includes all values converting to 0 through kMaxValue)
static constexpr SmallEnumSet All() {
StateT tmp = StateT{1} << kMaxValue;
return SmallEnumSet(RawStateMarker(), tmp | (tmp - 1));
StateT tmp;
for (int i = 0; i < kPieceCount - 1; ++i) {
tmp[i] = ~PieceT{0};
}
if constexpr (((kMaxValue + 1) & kPieceMask) != 0) {
tmp[kPieceCount - 1] = (PieceT{1} << ((kMaxValue + 1) & kPieceMask)) - 1;
} else {
tmp[kPieceCount - 1] = ~PieceT{0};
}
return SmallEnumSet(RawStateMarker(), tmp);
}
// equality
@@ -60,11 +77,17 @@ class SmallEnumSet {
bool Contains(const ENUM_TYPE e) const {
int value = static_cast<int>(e);
assert(value >= 0 && value <= kMaxValue);
StateT tmp = 1;
return state_ & (tmp << value);
return GetPiece(value) & (PieceT{1} << (value & kPieceMask));
}
bool empty() const { return state_ == 0; }
bool empty() const {
for (int i = 0; i < kPieceCount; ++i) {
if (state_[i] != 0) {
return false;
}
}
return true;
}
// iterator
class const_iterator {
@@ -92,7 +115,7 @@ class SmallEnumSet {
if (pos_ < kMaxValue) {
pos_ = set_->SkipUnset(pos_ + 1);
} else {
pos_ = kStateBits;
pos_ = kMaxValue + 1;
}
return *this;
}
@@ -118,7 +141,15 @@ class SmallEnumSet {
const_iterator begin() const { return const_iterator(this, SkipUnset(0)); }
const_iterator end() const { return const_iterator(this, kStateBits); }
const_iterator end() const { return const_iterator(this, kMaxValue + 1); }
size_t count() const {
size_t rv = 0;
for (int i = 0; i < kPieceCount; ++i) {
rv += static_cast<size_t>(detail::BitsSetToOneForSmallEnumSet(state_[i]));
}
return rv;
}
// mutable ops
@@ -127,9 +158,10 @@ class SmallEnumSet {
bool Add(const ENUM_TYPE e) {
int value = static_cast<int>(e);
assert(value >= 0 && value <= kMaxValue);
StateT old_state = state_;
state_ |= (StateT{1} << value);
return old_state != state_;
PieceT& piece_ref = RefPiece(value);
PieceT old_piece = piece_ref;
piece_ref |= (PieceT{1} << (value & kPieceMask));
return old_piece != piece_ref;
}
// Modifies the set (if needed) not to include the given value. Returns true
@@ -137,18 +169,20 @@ class SmallEnumSet {
bool Remove(const ENUM_TYPE e) {
int value = static_cast<int>(e);
assert(value >= 0 && value <= kMaxValue);
StateT old_state = state_;
state_ &= ~(StateT{1} << value);
return old_state != state_;
PieceT& piece_ref = RefPiece(value);
PieceT old_piece = piece_ref;
piece_ref &= ~(PieceT{1} << (value & kPieceMask));
return old_piece != piece_ref;
}
// applicative ops
// Return a new set based on this one with the additional value(s) inserted
constexpr SmallEnumSet With(const ENUM_TYPE e) const {
int value = static_cast<int>(e);
assert(value >= 0 && value <= kMaxValue);
return SmallEnumSet(RawStateMarker(), state_ | (StateT{1} << value));
assert(static_cast<int>(e) >= 0 && static_cast<int>(e) <= kMaxValue);
SmallEnumSet rv(*this);
rv.Add(e);
return rv;
}
template <class... TRest>
constexpr SmallEnumSet With(const ENUM_TYPE e1, const ENUM_TYPE e2,
@@ -158,9 +192,10 @@ class SmallEnumSet {
// Return a new set based on this one excluding the given value(s)
constexpr SmallEnumSet Without(const ENUM_TYPE e) const {
int value = static_cast<int>(e);
assert(value >= 0 && value <= kMaxValue);
return SmallEnumSet(RawStateMarker(), state_ & ~(StateT{1} << value));
assert(static_cast<int>(e) >= 0 && static_cast<int>(e) <= kMaxValue);
SmallEnumSet rv(*this);
rv.Remove(e);
return rv;
}
template <class... TRest>
constexpr SmallEnumSet Without(const ENUM_TYPE e1, const ENUM_TYPE e2,
@@ -170,17 +205,80 @@ class SmallEnumSet {
private:
int SkipUnset(int pos) const {
StateT tmp = state_ >> pos;
if (tmp == 0) {
return kStateBits;
} else {
return pos + detail::CountTrailingZeroBitsForSmallEnumSet(tmp);
while (pos <= kMaxValue) {
PieceT remainder = GetPiece(pos) >> (pos & kPieceMask);
if (remainder != 0) {
return pos + detail::CountTrailingZeroBitsForSmallEnumSet(remainder);
}
pos = (pos + kPieceBits) & ~kPieceMask;
}
return kMaxValue + 1;
}
struct RawStateMarker {};
explicit SmallEnumSet(RawStateMarker, StateT state) : state_(state) {}
PieceT GetPiece(int pos) const {
if constexpr (kPieceCount == 1) {
return state_[0];
} else {
return state_[pos >> kPieceShift];
}
}
PieceT& RefPiece(int pos) {
if constexpr (kPieceCount == 1) {
return state_[0];
} else {
return state_[pos >> kPieceShift];
}
}
StateT state_;
};
// A smart pointer that tracks an object and an owner, using a statically
// determined function on those to reclaim the object, if both object and owner
// are non-null
template <typename T, class Owner, auto Fn>
class ManagedPtr {
public:
ManagedPtr() = default;
ManagedPtr(T* ptr, Owner* owner) : ptr_(ptr), owner_(owner) {}
~ManagedPtr() {
if (ptr_ && owner_) {
if constexpr (std::is_member_function_pointer_v<decltype(Fn)>) {
(owner_->*Fn)(ptr_);
} else {
Fn(owner_, ptr_);
}
}
}
// No copies
ManagedPtr(const ManagedPtr&) = delete;
ManagedPtr& operator=(const ManagedPtr&) = delete;
// Moves
ManagedPtr(ManagedPtr&& other) noexcept {
ptr_ = other.ptr_;
owner_ = other.owner_;
other.ptr_ = nullptr;
other.owner_ = nullptr;
}
ManagedPtr& operator=(ManagedPtr&& other) noexcept {
ptr_ = other.ptr_;
owner_ = other.owner_;
other.ptr_ = nullptr;
other.owner_ = nullptr;
return *this;
}
T* get() const { return ptr_; }
T* operator->() const { return ptr_; }
T& operator*() const { return *ptr_; }
operator bool() const { return ptr_ != nullptr; }
Owner* owner() const { return owner_; }
private:
T* ptr_ = nullptr;
Owner* owner_ = nullptr;
};
} // namespace ROCKSDB_NAMESPACE
+37 -2
View File
@@ -18,6 +18,7 @@
#include <stdint.h>
#include <any>
#include <chrono>
#include <cstdarg>
#include <functional>
@@ -250,16 +251,44 @@ struct IODebugContext {
};
uint64_t trace_data = 0;
// Arbitrary structure containing cost information about the IO request
std::any cost_info;
IODebugContext() {}
// Copy constructor
IODebugContext(const IODebugContext& other)
: file_path(other.file_path),
counters(other.counters),
msg(other.msg),
trace_data(other.trace_data),
cost_info(other.cost_info),
_request_id(other.request_id ? *other.request_id : "") {
request_id = other.request_id ? &_request_id : nullptr;
}
// Copy assignment operator
IODebugContext& operator=(const IODebugContext& other) {
if (this != &other) {
file_path = other.file_path;
counters = other.counters;
msg = other.msg;
trace_data = other.trace_data;
cost_info = other.cost_info;
_request_id = other.request_id ? *other.request_id : "";
request_id = other.request_id ? &_request_id : nullptr;
}
return *this;
}
void AddCounter(std::string& name, uint64_t value) {
counters.emplace(name, value);
}
// Called by underlying file system to set request_id and log request_id in
// IOTracing.
void SetRequestId(const std::string* _request_id) {
request_id = _request_id;
void SetRequestId(const std::string* updated_request_id) {
request_id = updated_request_id;
trace_data |= (1 << TraceData::kRequestID);
}
@@ -272,6 +301,12 @@ struct IODebugContext {
ss << msg;
return ss.str();
}
private:
// Private member that allows for safe copying of IODebugContext without any
// memory ownership issues. After copying, request_id can point directly to
// this field.
std::string _request_id;
};
// A function pointer type for custom destruction of void pointer passed to
+20 -2
View File
@@ -233,6 +233,14 @@ struct ColumnFamilyOptions : public AdvancedColumnFamilyOptions {
// different options for compression algorithms
CompressionOptions compression_opts;
// EXPERIMENTAL
// Customized compression through a callback interface. When non-nullptr,
// supersedes the above compression options, except that the above options are
// still processed as they historically would be and passed to
// CompressionManager::GetCompressorForSST as hints or suggestions. See
// advanced_compression.h
std::shared_ptr<CompressionManager> compression_manager;
// Number of files to trigger level-0 compaction. A value <0 means that
// level-0 compaction will not be triggered by number of files at all.
//
@@ -2214,10 +2222,20 @@ struct CompactionOptions {
// If > 0, it will replace the option in the DBOptions for this compaction.
uint32_t max_subcompactions;
// Allows cancellation of an in-progress manual compaction.
//
// Cancellation can be delayed waiting on automatic compactions when used
// together with `exclusive_manual_compaction == true`.
std::atomic<bool>* canceled;
// NOTE: Calling DisableManualCompaction() will not override the
// canceled variable in CompactionOptions, as it does for CompactRangeOptions
// - this is because ManualCompactionState is not used
CompactionOptions()
: compression(kDisableCompressionOption),
output_file_size_limit(std::numeric_limits<uint64_t>::max()),
max_subcompactions(0) {}
max_subcompactions(0),
canceled(nullptr) {}
};
// For level based compaction, we can configure if we want to skip/force
@@ -2284,7 +2302,7 @@ struct CompactRangeOptions {
// Cancellation can be delayed waiting on automatic compactions when used
// together with `exclusive_manual_compaction == true`.
std::atomic<bool>* canceled = nullptr;
// NOTE: Calling DisableManualCompaction() overwrites the uer-provided
// NOTE: Calling DisableManualCompaction() overwrites the user-provided
// canceled variable in CompactRangeOptions.
// Typically, when CompactRange is being called in one thread (t1) with
// canceled = false, and DisableManualCompaction is being called in the
+1
View File
@@ -5,6 +5,7 @@
#pragma once
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
+22 -1
View File
@@ -111,6 +111,26 @@ class CompactionOptionsUniversal {
// Default: false
bool incremental;
// EXPERIMENTAL
//
// If true, auto universal compaction picking will adjust to minimize locking
// of input files when bottom priority compactions are waiting to run. This
// can increase the likelihood of existing L0s being selected for compaction,
// thereby improving write stall and reducing read regression. It may increase
// the overrall write amplification and compaction load on low priority
// threads.
//
// Default: false (disabled)
//
// This options does not apply to manual compactions.
//
// This option is temporary in case turning on this feature causes problems
// and users need to undo it quickly. This option is planned for removal in
// the near future with default value set to true.
//
// Dynamically changeable through the SetOptions() API.
bool reduce_file_locking;
// Default set of parameters
CompactionOptionsUniversal()
: size_ratio(1),
@@ -121,7 +141,8 @@ class CompactionOptionsUniversal {
max_read_amp(-1),
stop_style(kCompactionStopStyleTotalSize),
allow_trivial_move(false),
incremental(false) {}
incremental(false),
reduce_file_locking(false) {}
#if __cplusplus >= 202002L
bool operator==(const CompactionOptionsUniversal& rhs) const = default;
+28 -3
View File
@@ -254,7 +254,7 @@ struct TransactionDBOptions {
// for more details.
std::vector<std::shared_ptr<SecondaryIndex>> secondary_indices;
// Deprecated, this option may be removed in the future.
// Deprecated, this option has no effect and may be removed in the future.
// Use TransactionOptions::large_txn_commit_optimize_threshold instead.
//
// This option is only valid for write committed. If the number of updates in
@@ -366,6 +366,22 @@ struct TransactionOptions {
// DeleteRange, SingleDelete.
bool write_batch_track_timestamp_size = false;
// The following three options enable optimizations for large transaction
// commit to bypass memtable write.
// - If any transaction's commit should bybass memtable write,
// set commit_bypass_memtable to true.
// - If only bypass memtable write for transactions with >= n operations,
// set commit_bypass_memtable to false,
// large_txn_commit_optimize_threshold to n, and
// large_txn_commit_optimize_byte_threshold to 0.
// Similarly for only optimize when a transaction's write batch size is >= n.
// - If bypass memtable write for transactions with >= n operations or >= x
// bytes,
// set commit_bypass_memtable to false,
// large_txn_commit_optimize_threshold to n, and
// large_txn_commit_optimize_byte_threshold to x.
//
//
// EXPERIMENTAL, SUBJECT TO CHANGE
// Only supports write-committed policy. If set to true, the transaction will
// skip memtable write and ingest into the DB directly during Commit(). This
@@ -394,8 +410,17 @@ struct TransactionOptions {
// When the number of updates in a transaction is at least this threshold,
// we will enable optimizations for commiting a large transaction. See
// comment for `commit_bypass_memtable` for more optimization detail.
uint32_t large_txn_commit_optimize_threshold =
std::numeric_limits<uint32_t>::max();
//
// Default: 0 (disabled).
uint32_t large_txn_commit_optimize_threshold = 0;
// EXPERIMENTAL, SUBJECT TO CHANGE
// When the size of a transaction's write batch is at least this threshold,
// we will enable optimizations for commiting a large transaction. See
// comment for `commit_bypass_memtable` for more optimization detail.
//
// Default: 0 (disabled).
uint64_t large_txn_commit_optimize_byte_threshold = 0;
};
// The per-write optimizations that do not involve transactions. TransactionDB
+1 -1
View File
@@ -12,7 +12,7 @@
// NOTE: in 'main' development branch, this should be the *next*
// minor or major version number planned for release.
#define ROCKSDB_MAJOR 10
#define ROCKSDB_MINOR 3
#define ROCKSDB_MINOR 4
#define ROCKSDB_PATCH 0
// Do not use these. We made the mistake of declaring macros starting with
+20
View File
@@ -79,6 +79,10 @@ class VectorRep : public MemTableRep {
// Advance to the first entry with a key >= target
void Seek(const Slice& user_key, const char* memtable_key) override;
// Seek and do some memory validation
Status SeekAndValidate(const Slice& internal_key, const char* memtable_key,
bool allow_data_in_errors) override;
// Advance to the first entry with a key <= target
void SeekForPrev(const Slice& user_key, const char* memtable_key) override;
@@ -221,6 +225,22 @@ void VectorRep::Iterator::Seek(const Slice& user_key,
.first;
}
Status VectorRep::Iterator::SeekAndValidate(const Slice& /* internal_key */,
const char* /* memtable_key */,
bool /* allow_data_in_errors */) {
if (vrep_) {
WriteLock l(&vrep_->rwlock_);
if (bucket_->begin() == bucket_->end()) {
// Memtable is empty
return Status::OK();
} else {
return Status::NotSupported("SeekAndValidate() not implemented");
}
} else {
return Status::NotSupported("SeekAndValidate() not implemented");
}
}
// Advance to the first entry with a key <= target
void VectorRep::Iterator::SeekForPrev(const Slice& /*user_key*/,
const char* /*memtable_key*/) {
+17 -1
View File
@@ -349,6 +349,10 @@ static std::unordered_map<std::string, OptionTypeInfo>
OptionTypeFlags::kMutable}},
{"allow_trivial_move",
{offsetof(class CompactionOptionsUniversal, allow_trivial_move),
OptionType::kBoolean, OptionVerificationType::kNormal,
OptionTypeFlags::kMutable}},
{"reduce_file_locking",
{offsetof(class CompactionOptionsUniversal, reduce_file_locking),
OptionType::kBoolean, OptionVerificationType::kNormal,
OptionTypeFlags::kMutable}}};
@@ -698,6 +702,11 @@ static std::unordered_map<std::string, OptionTypeInfo>
name, value, addr);
}
})},
{"compression_manager",
OptionTypeInfo::AsCustomSharedPtr<CompressionManager>(
offsetof(struct MutableCFOptions, compression_manager),
OptionVerificationType::kByNameAllowNull,
(OptionTypeFlags::kMutable | OptionTypeFlags::kAllowNull))},
// End special case properties
{"memtable_max_range_deletions",
{offsetof(struct MutableCFOptions, memtable_max_range_deletions),
@@ -707,6 +716,10 @@ static std::unordered_map<std::string, OptionTypeInfo>
{offsetof(struct MutableCFOptions, memtable_op_scan_flush_trigger),
OptionType::kUInt32T, OptionVerificationType::kNormal,
OptionTypeFlags::kMutable}},
{"memtable_avg_op_scan_flush_trigger",
{offsetof(struct MutableCFOptions, memtable_avg_op_scan_flush_trigger),
OptionType::kUInt32T, OptionVerificationType::kNormal,
OptionTypeFlags::kMutable}},
};
static std::unordered_map<std::string, OptionTypeInfo>
@@ -1190,7 +1203,8 @@ void MutableCFOptions::Dump(Logger* log) const {
uncache_aggressiveness);
ROCKS_LOG_INFO(log, " memtable_op_scan_flush_trigger: %" PRIu32,
memtable_op_scan_flush_trigger);
ROCKS_LOG_INFO(log, " memtable_avg_op_scan_flush_trigger: %" PRIu32,
memtable_avg_op_scan_flush_trigger);
// Universal Compaction Options
ROCKS_LOG_INFO(log, "compaction_options_universal.size_ratio : %d",
compaction_options_universal.size_ratio);
@@ -1213,6 +1227,8 @@ void MutableCFOptions::Dump(Logger* log) const {
static_cast<int>(compaction_options_universal.allow_trivial_move));
ROCKS_LOG_INFO(log, "compaction_options_universal.incremental : %d",
static_cast<int>(compaction_options_universal.incremental));
ROCKS_LOG_INFO(log, "compaction_options_universal.reduce_file_locking : %d",
compaction_options_universal.reduce_file_locking);
// FIFO Compaction Options
ROCKS_LOG_INFO(log, "compaction_options_fifo.max_table_files_size : %" PRIu64,
+8 -2
View File
@@ -161,6 +161,7 @@ struct MutableCFOptions {
bottommost_compression(options.bottommost_compression),
compression_opts(options.compression_opts),
bottommost_compression_opts(options.bottommost_compression_opts),
compression_manager(options.compression_manager),
last_level_temperature(options.last_level_temperature),
default_write_temperature(options.default_write_temperature),
memtable_protection_bytes_per_key(
@@ -174,7 +175,9 @@ struct MutableCFOptions {
bottommost_file_compaction_delay(
options.bottommost_file_compaction_delay),
uncache_aggressiveness(options.uncache_aggressiveness),
memtable_op_scan_flush_trigger(options.memtable_op_scan_flush_trigger) {
memtable_op_scan_flush_trigger(options.memtable_op_scan_flush_trigger),
memtable_avg_op_scan_flush_trigger(
options.memtable_avg_op_scan_flush_trigger) {
RefreshDerivedOptions(options.num_levels, options.compaction_style);
}
@@ -230,7 +233,8 @@ struct MutableCFOptions {
memtable_max_range_deletions(0),
bottommost_file_compaction_delay(0),
uncache_aggressiveness(0),
memtable_op_scan_flush_trigger(0) {}
memtable_op_scan_flush_trigger(0),
memtable_avg_op_scan_flush_trigger(0) {}
explicit MutableCFOptions(const Options& options);
@@ -327,6 +331,7 @@ struct MutableCFOptions {
CompressionType bottommost_compression;
CompressionOptions compression_opts;
CompressionOptions bottommost_compression_opts;
std::shared_ptr<CompressionManager> compression_manager;
Temperature last_level_temperature;
Temperature default_write_temperature;
uint32_t memtable_protection_bytes_per_key;
@@ -339,6 +344,7 @@ struct MutableCFOptions {
uint32_t bottommost_file_compaction_delay;
uint32_t uncache_aggressiveness;
uint32_t memtable_op_scan_flush_trigger;
uint32_t memtable_avg_op_scan_flush_trigger;
// Derived options
// Per-level target file size.
+12 -4
View File
@@ -112,7 +112,9 @@ AdvancedColumnFamilyOptions::AdvancedColumnFamilyOptions(const Options& options)
blob_cache(options.blob_cache),
prepopulate_blob_cache(options.prepopulate_blob_cache),
persist_user_defined_timestamps(options.persist_user_defined_timestamps),
memtable_op_scan_flush_trigger(options.memtable_op_scan_flush_trigger) {
memtable_op_scan_flush_trigger(options.memtable_op_scan_flush_trigger),
memtable_avg_op_scan_flush_trigger(
options.memtable_avg_op_scan_flush_trigger) {
assert(memtable_factory.get() != nullptr);
if (max_bytes_for_level_multiplier_additional.size() <
static_cast<unsigned int>(num_levels)) {
@@ -284,9 +286,12 @@ void ColumnFamilyOptions::Dump(Logger* log) const {
ROCKS_LOG_HEADER(log,
" Options.max_sequential_skip_in_iterations: %" PRIu64,
max_sequential_skip_in_iterations);
ROCKS_LOG_HEADER(
log, " Options.memtable_op_scan_flush_trigger: %" PRIu32,
memtable_op_scan_flush_trigger);
ROCKS_LOG_HEADER(log,
" Options.memtable_op_scan_flush_trigger: %" PRIu32,
memtable_op_scan_flush_trigger);
ROCKS_LOG_HEADER(log,
" Options.memtable_avg_op_scan_flush_trigger: %" PRIu32,
memtable_avg_op_scan_flush_trigger);
ROCKS_LOG_HEADER(log,
" Options.max_compaction_bytes: %" PRIu64,
max_compaction_bytes);
@@ -353,6 +358,9 @@ void ColumnFamilyOptions::Dump(Logger* log) const {
str_compaction_stop_style.c_str());
ROCKS_LOG_HEADER(log, "Options.compaction_options_universal.max_read_amp: %d",
compaction_options_universal.max_read_amp);
ROCKS_LOG_HEADER(
log, "Options.compaction_options_universal.reduce_file_locking: %d",
compaction_options_universal.reduce_file_locking);
ROCKS_LOG_HEADER(
log, "Options.compaction_options_fifo.max_table_files_size: %" PRIu64,
compaction_options_fifo.max_table_files_size);
+3 -1
View File
@@ -293,6 +293,7 @@ void UpdateColumnFamilyOptions(const MutableCFOptions& moptions,
cf_opts->compression_opts = moptions.compression_opts;
cf_opts->bottommost_compression = moptions.bottommost_compression;
cf_opts->bottommost_compression_opts = moptions.bottommost_compression_opts;
cf_opts->compression_manager = moptions.compression_manager;
cf_opts->sample_for_compression = moptions.sample_for_compression;
cf_opts->compression_per_level = moptions.compression_per_level;
cf_opts->last_level_temperature = moptions.last_level_temperature;
@@ -301,6 +302,8 @@ void UpdateColumnFamilyOptions(const MutableCFOptions& moptions,
cf_opts->uncache_aggressiveness = moptions.uncache_aggressiveness;
cf_opts->memtable_op_scan_flush_trigger =
moptions.memtable_op_scan_flush_trigger;
cf_opts->memtable_avg_op_scan_flush_trigger =
moptions.memtable_avg_op_scan_flush_trigger;
}
void UpdateColumnFamilyOptions(const ImmutableCFOptions& ioptions,
@@ -565,7 +568,6 @@ bool SerializeSingleOptionHelper(const void* opt_address,
return SerializeEnum<CompressionType>(
compression_type_string_map,
*(static_cast<const CompressionType*>(opt_address)), value);
break;
case OptionType::kChecksumType:
return SerializeEnum<ChecksumType>(
checksum_type_string_map,
+9 -1
View File
@@ -530,6 +530,8 @@ TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) {
sizeof(const CompactionFilter*)},
{offsetof(struct ColumnFamilyOptions, compaction_filter_factory),
sizeof(std::shared_ptr<CompactionFilterFactory>)},
{offsetof(struct ColumnFamilyOptions, compression_manager),
sizeof(std::shared_ptr<CompressionManager>)},
{offsetof(struct ColumnFamilyOptions, prefix_extractor),
sizeof(std::shared_ptr<const SliceTransform>)},
{offsetof(struct ColumnFamilyOptions, snap_refresh_nanos),
@@ -619,6 +621,7 @@ TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) {
"strategy=7;max_dict_bytes=8;level=9;window_bits=10;max_compressed_bytes_"
"per_kb=876;checksum=true};"
"bottommost_compression=kDisableCompressionOption;"
"compression_manager=BuiltinV2;"
"level0_stop_writes_trigger=33;"
"num_levels=99;"
"level0_slowdown_writes_trigger=22;"
@@ -675,7 +678,8 @@ TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) {
"bottommost_file_compaction_delay=7200;"
"uncache_aggressiveness=1234;"
"paranoid_memory_checks=1;"
"memtable_op_scan_flush_trigger=123;",
"memtable_op_scan_flush_trigger=123;"
"memtable_avg_op_scan_flush_trigger=12;",
new_options));
ASSERT_NE(new_options->blob_cache.get(), nullptr);
@@ -699,6 +703,8 @@ TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) {
new_options->compaction_options_fifo.file_temperature_age_thresholds[0]
.age,
12345);
ASSERT_EQ(new_options->compression_manager,
GetBuiltinCompressionManager(/*compression_format_version*/ 2));
ColumnFamilyOptions rnd_filled_options = *new_options;
@@ -718,6 +724,8 @@ TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) {
sizeof(std::vector<int>)},
{offsetof(struct MutableCFOptions, compaction_options_fifo),
sizeof(struct CompactionOptionsFIFO)},
{offsetof(struct MutableCFOptions, compression_manager),
sizeof(std::shared_ptr<CompressionManager>)},
{offsetof(struct MutableCFOptions, compression_per_level),
sizeof(std::vector<CompressionType>)},
{offsetof(struct MutableCFOptions, max_file_size),
+58
View File
@@ -202,11 +202,69 @@ char* Decompress(const char* input_data, size_t input_length,
return outputBuffer.release();
}
int64_t GetDecompressedSize(const char* input_data, size_t input_length) {
assert(input_data != nullptr);
if (input_length == 0) {
return 0;
}
COMPRESS_ALLOCATION_ROUTINES* allocRoutinesPtr = nullptr;
DECOMPRESSOR_HANDLE decompressor = NULL;
BOOL success =
CreateDecompressor(COMPRESS_ALGORITHM_XPRESS, // Compression Algorithm
allocRoutinesPtr, // Optional allocation routine
&decompressor); // Handle
if (!success) {
#ifdef _DEBUG
std::cerr << "XPRESS: Failed to create Decompressor LastError "
<< GetLastError() << std::endl;
#endif
return -1;
}
std::unique_ptr<void, decltype(CloseDecompressorFun)> decompressorGuard(
decompressor, CloseDecompressorFun);
SIZE_T decompressedBufferSize = 0;
success = ::Decompress(decompressor, // Compressor Handle
const_cast<char*>(input_data), // Compressed data
input_length, // Compressed data size
NULL, // Buffer set to NULL
0, // Buffer size set to 0
&decompressedBufferSize); // Decompressed Data size
assert(!success);
auto lastError = GetLastError();
if (lastError != ERROR_INSUFFICIENT_BUFFER) {
#ifdef _DEBUG
std::cerr
<< "XPRESS: Failed to estimate decompressed buffer size LastError "
<< lastError << std::endl;
#endif
return -1;
}
assert(decompressedBufferSize > 0);
return static_cast<int64_t>(decompressedBufferSize);
}
int64_t DecompressToBuffer(const char* input, size_t input_length, char* output,
size_t output_length) {
assert(input != nullptr);
assert(output != nullptr);
if (input_length == 0) {
return 0;
}
COMPRESS_ALLOCATION_ROUTINES* allocRoutinesPtr = nullptr;
DECOMPRESSOR_HANDLE decompressor = NULL;
BOOL success =
+2
View File
@@ -22,6 +22,8 @@ bool Compress(const char* input, size_t length, std::string* output);
char* Decompress(const char* input_data, size_t input_length,
size_t* uncompressed_size);
int64_t GetDecompressedSize(const char* input, size_t input_length);
int64_t DecompressToBuffer(const char* input, size_t input_length, char* output,
size_t output_length);
+3
View File
@@ -243,6 +243,7 @@ LIB_SOURCES = \
util/compaction_job_stats_impl.cc \
util/comparator.cc \
util/compression.cc \
util/auto_skip_compressor.cc \
util/compression_context_cache.cc \
util/concurrent_task_limiter_impl.cc \
util/crc32c.cc \
@@ -256,6 +257,7 @@ LIB_SOURCES = \
util/ribbon_config.cc \
util/slice.cc \
util/file_checksum_helper.cc \
util/simple_mixed_compressor.cc \
util/status.cc \
util/stderr_logger.cc \
util/string_util.cc \
@@ -591,6 +593,7 @@ TEST_MAIN_SOURCES = \
table/table_test.cc \
table/block_fetcher_test.cc \
test_util/testutil_test.cc \
util/compression_test.cc \
tools/block_cache_analyzer/block_cache_trace_analyzer_test.cc \
tools/io_tracer_parser_test.cc \
tools/ldb_cmd_test.cc \
+481 -478
View File
@@ -190,484 +190,6 @@ struct BlockBasedTableBuilder::WorkingAreaPair {
Decompressor::ManagedWorkingArea verify;
};
struct BlockBasedTableBuilder::Rep {
const ImmutableOptions ioptions;
// BEGIN from MutableCFOptions
std::shared_ptr<const SliceTransform> prefix_extractor;
// END from MutableCFOptions
const WriteOptions write_options;
const BlockBasedTableOptions table_options;
const InternalKeyComparator& internal_comparator;
// Size in bytes for the user-defined timestamps.
size_t ts_sz;
// When `ts_sz` > 0 and this flag is false, the user-defined timestamp in the
// user key will be stripped when creating the block based table. This
// stripping happens for all user keys, including the keys in data block,
// index block for data block, index block for index block (if index type is
// `kTwoLevelIndexSearch`), index for filter blocks (if using partitioned
// filters), the `first_internal_key` in `IndexValue`, the `end_key` for range
// deletion entries.
// As long as the user keys are sorted when added via `Add` API, their logic
// ordering won't change after timestamps are stripped. However, for each user
// key to be logically equivalent before and after timestamp is stripped, the
// user key should contain the minimum timestamp.
bool persist_user_defined_timestamps;
WritableFileWriter* file;
std::atomic<uint64_t> offset;
size_t alignment;
BlockBuilder data_block;
// Buffers uncompressed data blocks to replay later. Needed when
// compression dictionary is enabled so we can finalize the dictionary before
// compressing any data blocks.
std::vector<std::string> data_block_buffers;
BlockBuilder range_del_block;
InternalKeySliceTransform internal_prefix_transform;
std::unique_ptr<IndexBuilder> index_builder;
std::string index_separator_scratch;
PartitionedIndexBuilder* p_index_builder_ = nullptr;
std::string last_ikey; // Internal key or empty (unset)
const Slice* first_key_in_next_block = nullptr;
bool warm_cache = false;
uint64_t sample_for_compression;
std::atomic<uint64_t> compressible_input_data_bytes;
std::atomic<uint64_t> uncompressible_input_data_bytes;
std::atomic<uint64_t> sampled_input_data_bytes;
std::atomic<uint64_t> sampled_output_slow_data_bytes;
std::atomic<uint64_t> sampled_output_fast_data_bytes;
uint32_t compression_parallel_threads;
int max_compressed_bytes_per_kb;
size_t max_dict_sample_bytes = 0;
// *** Compressors & decompressors - Yes, it seems like a lot here but ***
// *** these are distinct fields to minimize extra conditionals and ***
// *** field reads on hot code paths. ***
// A compressor for blocks in general, without dictionary compression
std::unique_ptr<Compressor> basic_compressor;
// A compressor using dictionary compression (when applicable)
std::unique_ptr<Compressor> compressor_with_dict;
// Once configured/determined, points to one of the above Compressors to
// use on data blocks.
Compressor* data_block_compressor = nullptr;
// A decompressor corresponding to basic_compressor (when non-nullptr).
// Used for verification and cache warming.
std::shared_ptr<Decompressor> basic_decompressor;
// When needed, a decompressor for verifying compression using a
// dictionary sampled/trained from this file.
std::unique_ptr<Decompressor> verify_decompressor_with_dict;
// When non-nullptr, compression should be verified with this corresponding
// decompressor, except for data blocks. (Points to same as basic_decompressor
// when verify_compression is set.)
UnownedPtr<Decompressor> verify_decompressor;
// Once configured/determined, points to one of the above Decompressors to use
// in verifying data blocks.
UnownedPtr<Decompressor> data_block_verify_decompressor;
// Working area for basic_compressor when compression_parallel_threads==1
WorkingAreaPair basic_working_area;
// Working areas for data_block_compressor, for each of
// compression_parallel_threads
std::vector<WorkingAreaPair> data_block_working_areas;
size_t data_begin_offset = 0;
TableProperties props;
// States of the builder.
//
// - `kBuffered`: This is the initial state where zero or more data blocks are
// accumulated uncompressed in-memory. From this state, call
// `EnterUnbuffered()` to finalize the compression dictionary if enabled,
// compress/write out any buffered blocks, and proceed to the `kUnbuffered`
// state.
//
// - `kUnbuffered`: This is the state when compression dictionary is finalized
// either because it wasn't enabled in the first place or it's been created
// from sampling previously buffered data. In this state, blocks are simply
// compressed/written out as they fill up. From this state, call `Finish()`
// to complete the file (write meta-blocks, etc.), or `Abandon()` to delete
// the partially created file.
//
// - `kClosed`: This indicates either `Finish()` or `Abandon()` has been
// called, so the table builder is no longer usable. We must be in this
// state by the time the destructor runs.
enum class State {
kBuffered,
kUnbuffered,
kClosed,
};
State state = State::kUnbuffered;
// `kBuffered` state is allowed only as long as the buffering of uncompressed
// data blocks (see `data_block_buffers`) does not exceed `buffer_limit`.
uint64_t buffer_limit = 0;
std::shared_ptr<CacheReservationManager>
compression_dict_buffer_cache_res_mgr;
const bool use_delta_encoding_for_index_values;
std::unique_ptr<FilterBlockBuilder> filter_builder;
OffsetableCacheKey base_cache_key;
const TableFileCreationReason reason;
BlockHandle pending_handle; // Handle to add to index block
std::string single_threaded_compressed_output;
std::unique_ptr<FlushBlockPolicy> flush_block_policy;
std::vector<std::unique_ptr<InternalTblPropColl>> table_properties_collectors;
std::unique_ptr<ParallelCompressionRep> pc_rep;
BlockCreateContext create_context;
// The size of the "tail" part of a SST file. "Tail" refers to
// all blocks after data blocks till the end of the SST file.
uint64_t tail_size;
// The total size of all blocks in this file before they are compressed.
// This is used for logging compaction stats.
uint64_t pre_compression_size = 0;
// See class Footer
uint32_t base_context_checksum;
uint64_t get_offset() { return offset.load(std::memory_order_relaxed); }
void set_offset(uint64_t o) { offset.store(o, std::memory_order_relaxed); }
bool IsParallelCompressionEnabled() const {
return compression_parallel_threads > 1;
}
Status GetStatus() {
// We need to make modifications of status visible when status_ok is set
// to false, and this is ensured by status_mutex, so no special memory
// order for status_ok is required.
if (status_ok.load(std::memory_order_relaxed)) {
return Status::OK();
} else {
return CopyStatus();
}
}
Status CopyStatus() {
std::lock_guard<std::mutex> lock(status_mutex);
return status;
}
IOStatus GetIOStatus() {
// We need to make modifications of io_status visible when status_ok is set
// to false, and this is ensured by io_status_mutex, so no special memory
// order for io_status_ok is required.
if (io_status_ok.load(std::memory_order_relaxed)) {
#ifdef ROCKSDB_ASSERT_STATUS_CHECKED // Avoid unnecessary lock acquisition
auto ios = CopyIOStatus();
ios.PermitUncheckedError();
// Assume no races in unit tests
assert(ios.ok());
#endif // ROCKSDB_ASSERT_STATUS_CHECKED
return IOStatus::OK();
} else {
return CopyIOStatus();
}
}
IOStatus CopyIOStatus() {
std::lock_guard<std::mutex> lock(io_status_mutex);
return io_status;
}
// Never erase an existing status that is not OK.
void SetStatus(Status s) {
if (!s.ok() && status_ok.load(std::memory_order_relaxed)) {
// Locking is an overkill for non compression_parallel_threads
// case but since it's unlikely that s is not OK, we take this cost
// to be simplicity.
std::lock_guard<std::mutex> lock(status_mutex);
status = s;
status_ok.store(false, std::memory_order_relaxed);
}
}
// Never erase an existing I/O status that is not OK.
// Calling this will also SetStatus(ios)
void SetIOStatus(IOStatus ios) {
if (!ios.ok() && io_status_ok.load(std::memory_order_relaxed)) {
// Locking is an overkill for non compression_parallel_threads
// case but since it's unlikely that s is not OK, we take this cost
// to be simplicity.
std::lock_guard<std::mutex> lock(io_status_mutex);
io_status = ios;
io_status_ok.store(false, std::memory_order_relaxed);
}
SetStatus(ios);
}
Rep(const BlockBasedTableOptions& table_opt, const TableBuilderOptions& tbo,
WritableFileWriter* f)
: ioptions(tbo.ioptions),
prefix_extractor(tbo.moptions.prefix_extractor),
write_options(tbo.write_options),
table_options(table_opt),
internal_comparator(tbo.internal_comparator),
ts_sz(tbo.internal_comparator.user_comparator()->timestamp_size()),
persist_user_defined_timestamps(
tbo.ioptions.persist_user_defined_timestamps),
file(f),
offset(0),
alignment(table_options.block_align
? std::min(static_cast<size_t>(table_options.block_size),
kDefaultPageSize)
: 0),
data_block(table_options.block_restart_interval,
table_options.use_delta_encoding,
false /* use_value_delta_encoding */,
tbo.internal_comparator.user_comparator()
->CanKeysWithDifferentByteContentsBeEqual()
? BlockBasedTableOptions::kDataBlockBinarySearch
: table_options.data_block_index_type,
table_options.data_block_hash_table_util_ratio, ts_sz,
persist_user_defined_timestamps),
range_del_block(
1 /* block_restart_interval */, true /* use_delta_encoding */,
false /* use_value_delta_encoding */,
BlockBasedTableOptions::kDataBlockBinarySearch /* index_type */,
0.75 /* data_block_hash_table_util_ratio */, ts_sz,
persist_user_defined_timestamps),
internal_prefix_transform(prefix_extractor.get()),
sample_for_compression(tbo.moptions.sample_for_compression),
compressible_input_data_bytes(0),
uncompressible_input_data_bytes(0),
sampled_input_data_bytes(0),
sampled_output_slow_data_bytes(0),
sampled_output_fast_data_bytes(0),
compression_parallel_threads(tbo.compression_opts.parallel_threads),
max_compressed_bytes_per_kb(
tbo.compression_opts.max_compressed_bytes_per_kb),
data_block_working_areas(compression_parallel_threads),
use_delta_encoding_for_index_values(table_opt.format_version >= 4 &&
!table_opt.block_align),
reason(tbo.reason),
flush_block_policy(
table_options.flush_block_policy_factory->NewFlushBlockPolicy(
table_options, data_block)),
create_context(&table_options, &ioptions, ioptions.stats,
/*decompressor=*/nullptr,
tbo.moptions.block_protection_bytes_per_key,
tbo.internal_comparator.user_comparator(),
!use_delta_encoding_for_index_values,
table_opt.index_type ==
BlockBasedTableOptions::kBinarySearchWithFirstKey),
tail_size(0),
status_ok(true),
io_status_ok(true) {
FilterBuildingContext filter_context(table_options);
filter_context.info_log = ioptions.logger;
filter_context.column_family_name = tbo.column_family_name;
filter_context.reason = reason;
// Only populate other fields if known to be in LSM rather than
// generating external SST file
if (reason != TableFileCreationReason::kMisc) {
filter_context.compaction_style = ioptions.compaction_style;
filter_context.num_levels = ioptions.num_levels;
filter_context.level_at_creation = tbo.level_at_creation;
filter_context.is_bottommost = tbo.is_bottommost;
assert(filter_context.level_at_creation < filter_context.num_levels);
}
// TODO: get CompressionManager from options and sort out properties
auto mgr = GetBuiltinCompressionManager(
GetCompressFormatForVersion(table_opt.format_version));
props.compression_name = CompressionTypeToString(tbo.compression_type);
props.compression_options =
CompressionOptionsToString(tbo.compression_opts);
// Sanitize to only allowing compression when it saves space.
max_compressed_bytes_per_kb =
std::min(int{1023}, tbo.compression_opts.max_compressed_bytes_per_kb);
basic_compressor = mgr->GetCompressorForSST(
filter_context, tbo.compression_opts, tbo.compression_type);
if (basic_compressor) {
if (table_options.enable_index_compression) {
basic_working_area.compress = basic_compressor->ObtainWorkingArea();
}
max_dict_sample_bytes = basic_compressor->GetMaxSampleSizeIfWantDict(
CacheEntryRole::kDataBlock);
if (max_dict_sample_bytes > 0) {
state = State::kBuffered;
if (tbo.target_file_size == 0) {
buffer_limit = tbo.compression_opts.max_dict_buffer_bytes;
} else if (tbo.compression_opts.max_dict_buffer_bytes == 0) {
buffer_limit = tbo.target_file_size;
} else {
buffer_limit = std::min(tbo.target_file_size,
tbo.compression_opts.max_dict_buffer_bytes);
}
} else {
// No distinct data block compressor using dictionary
data_block_compressor = basic_compressor.get();
for (uint32_t i = 0; i < compression_parallel_threads; i++) {
data_block_working_areas[i].compress =
data_block_compressor->ObtainWorkingArea();
}
}
basic_decompressor =
mgr->GetDecompressorOptimizeFor(tbo.compression_type);
create_context.decompressor = basic_decompressor.get();
if (table_options.verify_compression) {
verify_decompressor = basic_decompressor.get();
if (table_options.enable_index_compression) {
basic_working_area.verify =
verify_decompressor->ObtainWorkingArea(tbo.compression_type);
}
if (state == State::kUnbuffered) {
for (uint32_t i = 0; i < compression_parallel_threads; i++) {
data_block_working_areas[i].verify =
verify_decompressor->ObtainWorkingArea(tbo.compression_type);
}
data_block_verify_decompressor = verify_decompressor.get();
}
}
}
switch (table_options.prepopulate_block_cache) {
case BlockBasedTableOptions::PrepopulateBlockCache::kFlushOnly:
warm_cache = (reason == TableFileCreationReason::kFlush);
break;
case BlockBasedTableOptions::PrepopulateBlockCache::kDisable:
warm_cache = false;
break;
default:
// missing case
assert(false);
warm_cache = false;
}
const auto compress_dict_build_buffer_charged =
table_options.cache_usage_options.options_overrides
.at(CacheEntryRole::kCompressionDictionaryBuildingBuffer)
.charged;
if (table_options.block_cache &&
(compress_dict_build_buffer_charged ==
CacheEntryRoleOptions::Decision::kEnabled ||
compress_dict_build_buffer_charged ==
CacheEntryRoleOptions::Decision::kFallback)) {
compression_dict_buffer_cache_res_mgr =
std::make_shared<CacheReservationManagerImpl<
CacheEntryRole::kCompressionDictionaryBuildingBuffer>>(
table_options.block_cache);
} else {
compression_dict_buffer_cache_res_mgr = nullptr;
}
if (table_options.index_type ==
BlockBasedTableOptions::kTwoLevelIndexSearch) {
p_index_builder_ = PartitionedIndexBuilder::CreateIndexBuilder(
&internal_comparator, use_delta_encoding_for_index_values,
table_options, ts_sz, persist_user_defined_timestamps);
index_builder.reset(p_index_builder_);
} else {
index_builder.reset(IndexBuilder::CreateIndexBuilder(
table_options.index_type, &internal_comparator,
&this->internal_prefix_transform, use_delta_encoding_for_index_values,
table_options, ts_sz, persist_user_defined_timestamps));
}
if (ioptions.optimize_filters_for_hits && tbo.is_bottommost) {
// Apply optimize_filters_for_hits setting here when applicable by
// skipping filter generation
filter_builder.reset();
} else if (tbo.skip_filters) {
// For SstFileWriter skip_filters
filter_builder.reset();
} else if (!table_options.filter_policy) {
// Null filter_policy -> no filter
filter_builder.reset();
} else {
filter_builder.reset(CreateFilterBlockBuilder(
ioptions, tbo.moptions, filter_context,
use_delta_encoding_for_index_values, p_index_builder_, ts_sz,
persist_user_defined_timestamps));
}
assert(tbo.internal_tbl_prop_coll_factories);
for (auto& factory : *tbo.internal_tbl_prop_coll_factories) {
assert(factory);
std::unique_ptr<InternalTblPropColl> collector{
factory->CreateInternalTblPropColl(
tbo.column_family_id, tbo.level_at_creation,
tbo.ioptions.num_levels,
tbo.last_level_inclusive_max_seqno_threshold)};
if (collector) {
table_properties_collectors.emplace_back(std::move(collector));
}
}
table_properties_collectors.emplace_back(
new BlockBasedTablePropertiesCollector(
table_options.index_type, table_options.whole_key_filtering,
prefix_extractor != nullptr,
table_options.decouple_partitioned_filters));
if (ts_sz > 0 && persist_user_defined_timestamps) {
table_properties_collectors.emplace_back(
new TimestampTablePropertiesCollector(
tbo.internal_comparator.user_comparator()));
}
// These are only needed for populating table properties
props.column_family_id = tbo.column_family_id;
props.column_family_name = tbo.column_family_name;
props.oldest_key_time = tbo.oldest_key_time;
props.newest_key_time = tbo.newest_key_time;
props.file_creation_time = tbo.file_creation_time;
props.orig_file_number = tbo.cur_file_num;
props.db_id = tbo.db_id;
props.db_session_id = tbo.db_session_id;
props.db_host_id = ioptions.db_host_id;
props.format_version = table_options.format_version;
if (!ReifyDbHostIdProperty(ioptions.env, &props.db_host_id).ok()) {
ROCKS_LOG_INFO(ioptions.logger, "db_host_id property will not be set");
}
// Default is UINT64_MAX for unknown. Setting it to 0 here
// to allow updating it by taking max in BlockBasedTableBuilder::Add().
props.key_largest_seqno = 0;
if (FormatVersionUsesContextChecksum(table_options.format_version)) {
// Must be non-zero and semi- or quasi-random
// TODO: ideally guaranteed different for related files (e.g. use file
// number and db_session, for benefit of SstFileWriter)
do {
base_context_checksum = Random::GetTLSInstance()->Next();
} while (UNLIKELY(base_context_checksum == 0));
} else {
base_context_checksum = 0;
}
if (alignment > 0 && basic_compressor) {
// With better sanitization in `CompactionPicker::CompactFiles()`, we
// would not need to handle this case here and could change it to an
// assertion instead.
SetStatus(Status::InvalidArgument(
"Enable block_align, but compression enabled"));
}
}
Rep(const Rep&) = delete;
Rep& operator=(const Rep&) = delete;
private:
// Synchronize status & io_status accesses across threads from main thread,
// compression thread and write thread in parallel compression.
std::mutex status_mutex;
std::atomic<bool> status_ok;
Status status;
std::mutex io_status_mutex;
std::atomic<bool> io_status_ok;
IOStatus io_status;
};
struct BlockBasedTableBuilder::ParallelCompressionRep {
// TODO: consider replacing with autovector or similar
// Keys is a wrapper of vector of strings avoiding
@@ -962,6 +484,487 @@ struct BlockBasedTableBuilder::ParallelCompressionRep {
}
};
struct BlockBasedTableBuilder::Rep {
const ImmutableOptions ioptions;
// BEGIN from MutableCFOptions
std::shared_ptr<const SliceTransform> prefix_extractor;
// END from MutableCFOptions
const WriteOptions write_options;
const BlockBasedTableOptions table_options;
const InternalKeyComparator& internal_comparator;
// Size in bytes for the user-defined timestamps.
size_t ts_sz;
// When `ts_sz` > 0 and this flag is false, the user-defined timestamp in the
// user key will be stripped when creating the block based table. This
// stripping happens for all user keys, including the keys in data block,
// index block for data block, index block for index block (if index type is
// `kTwoLevelIndexSearch`), index for filter blocks (if using partitioned
// filters), the `first_internal_key` in `IndexValue`, the `end_key` for range
// deletion entries.
// As long as the user keys are sorted when added via `Add` API, their logic
// ordering won't change after timestamps are stripped. However, for each user
// key to be logically equivalent before and after timestamp is stripped, the
// user key should contain the minimum timestamp.
bool persist_user_defined_timestamps;
WritableFileWriter* file;
std::atomic<uint64_t> offset;
size_t alignment;
BlockBuilder data_block;
// Buffers uncompressed data blocks to replay later. Needed when
// compression dictionary is enabled so we can finalize the dictionary before
// compressing any data blocks.
std::vector<std::string> data_block_buffers;
BlockBuilder range_del_block;
InternalKeySliceTransform internal_prefix_transform;
std::unique_ptr<IndexBuilder> index_builder;
std::string index_separator_scratch;
PartitionedIndexBuilder* p_index_builder_ = nullptr;
std::string last_ikey; // Internal key or empty (unset)
const Slice* first_key_in_next_block = nullptr;
bool warm_cache = false;
uint64_t sample_for_compression;
std::atomic<uint64_t> compressible_input_data_bytes;
std::atomic<uint64_t> uncompressible_input_data_bytes;
std::atomic<uint64_t> sampled_input_data_bytes;
std::atomic<uint64_t> sampled_output_slow_data_bytes;
std::atomic<uint64_t> sampled_output_fast_data_bytes;
uint32_t compression_parallel_threads;
int max_compressed_bytes_per_kb;
size_t max_dict_sample_bytes = 0;
// *** Compressors & decompressors - Yes, it seems like a lot here but ***
// *** these are distinct fields to minimize extra conditionals and ***
// *** field reads on hot code paths. ***
// A compressor for blocks in general, without dictionary compression
std::unique_ptr<Compressor> basic_compressor;
// A compressor using dictionary compression (when applicable)
std::unique_ptr<Compressor> compressor_with_dict;
// Once configured/determined, points to one of the above Compressors to
// use on data blocks.
Compressor* data_block_compressor = nullptr;
// A decompressor corresponding to basic_compressor (when non-nullptr).
// Used for verification and cache warming.
std::shared_ptr<Decompressor> basic_decompressor;
// When needed, a decompressor for verifying compression using a
// dictionary sampled/trained from this file.
std::unique_ptr<Decompressor> verify_decompressor_with_dict;
// When non-nullptr, compression should be verified with this corresponding
// decompressor, except for data blocks. (Points to same as basic_decompressor
// when verify_compression is set.)
UnownedPtr<Decompressor> verify_decompressor;
// Once configured/determined, points to one of the above Decompressors to use
// in verifying data blocks.
UnownedPtr<Decompressor> data_block_verify_decompressor;
// Working area for basic_compressor when compression_parallel_threads==1
WorkingAreaPair basic_working_area;
// Working areas for data_block_compressor, for each of
// compression_parallel_threads
std::vector<WorkingAreaPair> data_block_working_areas;
size_t data_begin_offset = 0;
TableProperties props;
// States of the builder.
//
// - `kBuffered`: This is the initial state where zero or more data blocks are
// accumulated uncompressed in-memory. From this state, call
// `EnterUnbuffered()` to finalize the compression dictionary if enabled,
// compress/write out any buffered blocks, and proceed to the `kUnbuffered`
// state.
//
// - `kUnbuffered`: This is the state when compression dictionary is finalized
// either because it wasn't enabled in the first place or it's been created
// from sampling previously buffered data. In this state, blocks are simply
// compressed/written out as they fill up. From this state, call `Finish()`
// to complete the file (write meta-blocks, etc.), or `Abandon()` to delete
// the partially created file.
//
// - `kClosed`: This indicates either `Finish()` or `Abandon()` has been
// called, so the table builder is no longer usable. We must be in this
// state by the time the destructor runs.
enum class State {
kBuffered,
kUnbuffered,
kClosed,
};
State state = State::kUnbuffered;
// `kBuffered` state is allowed only as long as the buffering of uncompressed
// data blocks (see `data_block_buffers`) does not exceed `buffer_limit`.
uint64_t buffer_limit = 0;
std::shared_ptr<CacheReservationManager>
compression_dict_buffer_cache_res_mgr;
const bool use_delta_encoding_for_index_values;
std::unique_ptr<FilterBlockBuilder> filter_builder;
OffsetableCacheKey base_cache_key;
const TableFileCreationReason reason;
BlockHandle pending_handle; // Handle to add to index block
std::string single_threaded_compressed_output;
std::unique_ptr<FlushBlockPolicy> flush_block_policy;
std::vector<std::unique_ptr<InternalTblPropColl>> table_properties_collectors;
std::unique_ptr<ParallelCompressionRep> pc_rep;
BlockCreateContext create_context;
// The size of the "tail" part of a SST file. "Tail" refers to
// all blocks after data blocks till the end of the SST file.
uint64_t tail_size;
// The total size of all blocks in this file before they are compressed.
// This is used for logging compaction stats.
uint64_t pre_compression_size = 0;
// See class Footer
uint32_t base_context_checksum;
uint64_t get_offset() { return offset.load(std::memory_order_relaxed); }
void set_offset(uint64_t o) { offset.store(o, std::memory_order_relaxed); }
bool IsParallelCompressionEnabled() const {
return compression_parallel_threads > 1;
}
Status GetStatus() {
// We need to make modifications of status visible when status_ok is set
// to false, and this is ensured by status_mutex, so no special memory
// order for status_ok is required.
if (status_ok.load(std::memory_order_relaxed)) {
return Status::OK();
} else {
return CopyStatus();
}
}
Status CopyStatus() {
std::lock_guard<std::mutex> lock(status_mutex);
return status;
}
IOStatus GetIOStatus() {
// We need to make modifications of io_status visible when status_ok is set
// to false, and this is ensured by io_status_mutex, so no special memory
// order for io_status_ok is required.
if (io_status_ok.load(std::memory_order_relaxed)) {
#ifdef ROCKSDB_ASSERT_STATUS_CHECKED // Avoid unnecessary lock acquisition
auto ios = CopyIOStatus();
ios.PermitUncheckedError();
// Assume no races in unit tests
assert(ios.ok());
#endif // ROCKSDB_ASSERT_STATUS_CHECKED
return IOStatus::OK();
} else {
return CopyIOStatus();
}
}
IOStatus CopyIOStatus() {
std::lock_guard<std::mutex> lock(io_status_mutex);
return io_status;
}
// Never erase an existing status that is not OK.
void SetStatus(Status s) {
if (!s.ok() && status_ok.load(std::memory_order_relaxed)) {
// Locking is an overkill for non compression_parallel_threads
// case but since it's unlikely that s is not OK, we take this cost
// to be simplicity.
std::lock_guard<std::mutex> lock(status_mutex);
status = s;
status_ok.store(false, std::memory_order_relaxed);
}
}
// Never erase an existing I/O status that is not OK.
// Calling this will also SetStatus(ios)
void SetIOStatus(IOStatus ios) {
if (!ios.ok() && io_status_ok.load(std::memory_order_relaxed)) {
// Locking is an overkill for non compression_parallel_threads
// case but since it's unlikely that s is not OK, we take this cost
// to be simplicity.
std::lock_guard<std::mutex> lock(io_status_mutex);
io_status = ios;
io_status_ok.store(false, std::memory_order_relaxed);
}
SetStatus(ios);
}
Rep(const BlockBasedTableOptions& table_opt, const TableBuilderOptions& tbo,
WritableFileWriter* f)
: ioptions(tbo.ioptions),
prefix_extractor(tbo.moptions.prefix_extractor),
write_options(tbo.write_options),
table_options(table_opt),
internal_comparator(tbo.internal_comparator),
ts_sz(tbo.internal_comparator.user_comparator()->timestamp_size()),
persist_user_defined_timestamps(
tbo.ioptions.persist_user_defined_timestamps),
file(f),
offset(0),
alignment(table_options.block_align
? std::min(static_cast<size_t>(table_options.block_size),
kDefaultPageSize)
: 0),
data_block(table_options.block_restart_interval,
table_options.use_delta_encoding,
false /* use_value_delta_encoding */,
tbo.internal_comparator.user_comparator()
->CanKeysWithDifferentByteContentsBeEqual()
? BlockBasedTableOptions::kDataBlockBinarySearch
: table_options.data_block_index_type,
table_options.data_block_hash_table_util_ratio, ts_sz,
persist_user_defined_timestamps),
range_del_block(
1 /* block_restart_interval */, true /* use_delta_encoding */,
false /* use_value_delta_encoding */,
BlockBasedTableOptions::kDataBlockBinarySearch /* index_type */,
0.75 /* data_block_hash_table_util_ratio */, ts_sz,
persist_user_defined_timestamps),
internal_prefix_transform(prefix_extractor.get()),
sample_for_compression(tbo.moptions.sample_for_compression),
compressible_input_data_bytes(0),
uncompressible_input_data_bytes(0),
sampled_input_data_bytes(0),
sampled_output_slow_data_bytes(0),
sampled_output_fast_data_bytes(0),
compression_parallel_threads(tbo.compression_opts.parallel_threads),
max_compressed_bytes_per_kb(
tbo.compression_opts.max_compressed_bytes_per_kb),
data_block_working_areas(compression_parallel_threads),
use_delta_encoding_for_index_values(table_opt.format_version >= 4 &&
!table_opt.block_align),
reason(tbo.reason),
flush_block_policy(
table_options.flush_block_policy_factory->NewFlushBlockPolicy(
table_options, data_block)),
create_context(&table_options, &ioptions, ioptions.stats,
/*decompressor=*/nullptr,
tbo.moptions.block_protection_bytes_per_key,
tbo.internal_comparator.user_comparator(),
!use_delta_encoding_for_index_values,
table_opt.index_type ==
BlockBasedTableOptions::kBinarySearchWithFirstKey),
tail_size(0),
status_ok(true),
io_status_ok(true) {
FilterBuildingContext filter_context(table_options);
filter_context.info_log = ioptions.logger;
filter_context.column_family_name = tbo.column_family_name;
filter_context.reason = reason;
// Only populate other fields if known to be in LSM rather than
// generating external SST file
if (reason != TableFileCreationReason::kMisc) {
filter_context.compaction_style = ioptions.compaction_style;
filter_context.num_levels = ioptions.num_levels;
filter_context.level_at_creation = tbo.level_at_creation;
filter_context.is_bottommost = tbo.is_bottommost;
assert(filter_context.level_at_creation < filter_context.num_levels);
}
// TODO: get CompressionManager from options and sort out properties
auto mgr = tbo.moptions.compression_manager;
if (mgr == nullptr) {
mgr = GetBuiltinCompressionManager(
GetCompressFormatForVersion(table_opt.format_version));
}
props.compression_name = CompressionTypeToString(tbo.compression_type);
props.compression_options =
CompressionOptionsToString(tbo.compression_opts);
// Sanitize to only allowing compression when it saves space.
max_compressed_bytes_per_kb =
std::min(int{1023}, tbo.compression_opts.max_compressed_bytes_per_kb);
basic_compressor = mgr->GetCompressorForSST(
filter_context, tbo.compression_opts, tbo.compression_type);
if (basic_compressor) {
if (table_options.enable_index_compression) {
basic_working_area.compress = basic_compressor->ObtainWorkingArea();
}
max_dict_sample_bytes = basic_compressor->GetMaxSampleSizeIfWantDict(
CacheEntryRole::kDataBlock);
if (max_dict_sample_bytes > 0) {
state = State::kBuffered;
if (tbo.target_file_size == 0) {
buffer_limit = tbo.compression_opts.max_dict_buffer_bytes;
} else if (tbo.compression_opts.max_dict_buffer_bytes == 0) {
buffer_limit = tbo.target_file_size;
} else {
buffer_limit = std::min(tbo.target_file_size,
tbo.compression_opts.max_dict_buffer_bytes);
}
} else {
// No distinct data block compressor using dictionary
data_block_compressor = basic_compressor.get();
for (uint32_t i = 0; i < compression_parallel_threads; i++) {
data_block_working_areas[i].compress =
data_block_compressor->ObtainWorkingArea();
}
}
basic_decompressor =
mgr->GetDecompressorOptimizeFor(tbo.compression_type);
create_context.decompressor = basic_decompressor.get();
if (table_options.verify_compression) {
verify_decompressor = basic_decompressor.get();
if (table_options.enable_index_compression) {
basic_working_area.verify =
verify_decompressor->ObtainWorkingArea(tbo.compression_type);
}
if (state == State::kUnbuffered) {
for (uint32_t i = 0; i < compression_parallel_threads; i++) {
data_block_working_areas[i].verify =
verify_decompressor->ObtainWorkingArea(tbo.compression_type);
}
data_block_verify_decompressor = verify_decompressor.get();
}
}
}
switch (table_options.prepopulate_block_cache) {
case BlockBasedTableOptions::PrepopulateBlockCache::kFlushOnly:
warm_cache = (reason == TableFileCreationReason::kFlush);
break;
case BlockBasedTableOptions::PrepopulateBlockCache::kDisable:
warm_cache = false;
break;
default:
// missing case
assert(false);
warm_cache = false;
}
const auto compress_dict_build_buffer_charged =
table_options.cache_usage_options.options_overrides
.at(CacheEntryRole::kCompressionDictionaryBuildingBuffer)
.charged;
if (table_options.block_cache &&
(compress_dict_build_buffer_charged ==
CacheEntryRoleOptions::Decision::kEnabled ||
compress_dict_build_buffer_charged ==
CacheEntryRoleOptions::Decision::kFallback)) {
compression_dict_buffer_cache_res_mgr =
std::make_shared<CacheReservationManagerImpl<
CacheEntryRole::kCompressionDictionaryBuildingBuffer>>(
table_options.block_cache);
} else {
compression_dict_buffer_cache_res_mgr = nullptr;
}
if (table_options.index_type ==
BlockBasedTableOptions::kTwoLevelIndexSearch) {
p_index_builder_ = PartitionedIndexBuilder::CreateIndexBuilder(
&internal_comparator, use_delta_encoding_for_index_values,
table_options, ts_sz, persist_user_defined_timestamps);
index_builder.reset(p_index_builder_);
} else {
index_builder.reset(IndexBuilder::CreateIndexBuilder(
table_options.index_type, &internal_comparator,
&this->internal_prefix_transform, use_delta_encoding_for_index_values,
table_options, ts_sz, persist_user_defined_timestamps));
}
if (ioptions.optimize_filters_for_hits && tbo.is_bottommost) {
// Apply optimize_filters_for_hits setting here when applicable by
// skipping filter generation
filter_builder.reset();
} else if (tbo.skip_filters) {
// For SstFileWriter skip_filters
filter_builder.reset();
} else if (!table_options.filter_policy) {
// Null filter_policy -> no filter
filter_builder.reset();
} else {
filter_builder.reset(CreateFilterBlockBuilder(
ioptions, tbo.moptions, filter_context,
use_delta_encoding_for_index_values, p_index_builder_, ts_sz,
persist_user_defined_timestamps));
}
assert(tbo.internal_tbl_prop_coll_factories);
for (auto& factory : *tbo.internal_tbl_prop_coll_factories) {
assert(factory);
std::unique_ptr<InternalTblPropColl> collector{
factory->CreateInternalTblPropColl(
tbo.column_family_id, tbo.level_at_creation,
tbo.ioptions.num_levels,
tbo.last_level_inclusive_max_seqno_threshold)};
if (collector) {
table_properties_collectors.emplace_back(std::move(collector));
}
}
table_properties_collectors.emplace_back(
new BlockBasedTablePropertiesCollector(
table_options.index_type, table_options.whole_key_filtering,
prefix_extractor != nullptr,
table_options.decouple_partitioned_filters));
if (ts_sz > 0 && persist_user_defined_timestamps) {
table_properties_collectors.emplace_back(
new TimestampTablePropertiesCollector(
tbo.internal_comparator.user_comparator()));
}
// These are only needed for populating table properties
props.column_family_id = tbo.column_family_id;
props.column_family_name = tbo.column_family_name;
props.oldest_key_time = tbo.oldest_key_time;
props.newest_key_time = tbo.newest_key_time;
props.file_creation_time = tbo.file_creation_time;
props.orig_file_number = tbo.cur_file_num;
props.db_id = tbo.db_id;
props.db_session_id = tbo.db_session_id;
props.db_host_id = ioptions.db_host_id;
props.format_version = table_options.format_version;
if (!ReifyDbHostIdProperty(ioptions.env, &props.db_host_id).ok()) {
ROCKS_LOG_INFO(ioptions.logger, "db_host_id property will not be set");
}
// Default is UINT64_MAX for unknown. Setting it to 0 here
// to allow updating it by taking max in BlockBasedTableBuilder::Add().
props.key_largest_seqno = 0;
if (FormatVersionUsesContextChecksum(table_options.format_version)) {
// Must be non-zero and semi- or quasi-random
// TODO: ideally guaranteed different for related files (e.g. use file
// number and db_session, for benefit of SstFileWriter)
do {
base_context_checksum = Random::GetTLSInstance()->Next();
} while (UNLIKELY(base_context_checksum == 0));
} else {
base_context_checksum = 0;
}
if (alignment > 0 && basic_compressor) {
// With better sanitization in `CompactionPicker::CompactFiles()`, we
// would not need to handle this case here and could change it to an
// assertion instead.
SetStatus(Status::InvalidArgument(
"Enable block_align, but compression enabled"));
}
}
Rep(const Rep&) = delete;
Rep& operator=(const Rep&) = delete;
private:
// Synchronize status & io_status accesses across threads from main thread,
// compression thread and write thread in parallel compression.
std::mutex status_mutex;
std::atomic<bool> status_ok;
Status status;
std::mutex io_status_mutex;
std::atomic<bool> io_status_ok;
IOStatus io_status;
};
BlockBasedTableBuilder::BlockBasedTableBuilder(
const BlockBasedTableOptions& table_options, const TableBuilderOptions& tbo,
WritableFileWriter* file) {
@@ -709,6 +709,13 @@ Status BlockBasedTable::Open(
rep->file = std::move(file);
rep->footer = footer;
// Some ancient versions (~2.5 - 2.7, format_version=1) could compress the
// metaindex block, so we need to allow for that
if (footer.format_version() < 2) {
auto mgr = GetBuiltinCompressionManager(/*compression_format_version=*/1);
rep->decompressor = mgr->GetDecompressor();
}
// For fully portable/stable cache keys, we need to read the properties
// block before setting up cache keys. TODO: consider setting up a bootstrap
// cache key for PersistentCache to use for metaindex and properties blocks.
@@ -743,6 +750,9 @@ Status BlockBasedTable::Open(
rep->table_properties ? rep->table_properties->compression_name
: std::string{});
if (saved_comp_type != kNoCompression) {
// Includes "unrecognized" or "unspecified" case, including some old files
// before the compression_name table property was introduced in
// version 4.9.0
// TODO: custom CompressionManager
auto mgr = GetBuiltinCompressionManager(
GetCompressFormatForVersion(footer.format_version()));
@@ -34,6 +34,7 @@
#include "table/two_level_iterator.h"
#include "trace_replay/block_cache_tracer.h"
#include "util/atomic.h"
#include "util/cast_util.h"
#include "util/coro_utils.h"
#include "util/hash_containers.h"
+1
View File
@@ -14,6 +14,7 @@
#include "table/block_based/block_type.h"
#include "table/format.h"
#include "table/persistent_cache_options.h"
#include "util/cast_util.h"
namespace ROCKSDB_NAMESPACE {
+12 -6
View File
@@ -14,6 +14,7 @@
#include <algorithm>
#include <cstddef>
#include <cstdio>
#include <iomanip>
#include <iostream>
#include <map>
#include <memory>
@@ -4728,13 +4729,18 @@ static void DoCompressionTest(CompressionType comp) {
const ImmutableOptions ioptions(options);
const MutableCFOptions moptions(options);
c.Finish(options, ioptions, moptions, table_options, ikc, &keys, &kvmap);
size_t file_size = c.TEST_GetSink()->contents().size();
EXPECT_EQ(c.ApproximateOffsetOf("abc"), 0);
EXPECT_EQ(c.ApproximateOffsetOf("k01"), 0);
EXPECT_EQ(c.ApproximateOffsetOf("k02"), 0);
EXPECT_NEAR2(c.ApproximateOffsetOf("k03"), file_size / 2, file_size / 10);
EXPECT_NEAR2(c.ApproximateOffsetOf("k04"), file_size / 2, file_size / 10);
EXPECT_NEAR2(c.ApproximateOffsetOf("xyz"), file_size, file_size / 10);
size_t data_blocks_size = c.GetTableReader()->GetTableProperties()->data_size;
// Near expected compressed size ~= (0.25 + 0.25) * 10000
EXPECT_NEAR2(data_blocks_size, 5000, 1500);
ASSERT_TRUE(Between(c.ApproximateOffsetOf("abc"), 0, 0));
ASSERT_TRUE(Between(c.ApproximateOffsetOf("k01"), 0, 0));
ASSERT_TRUE(Between(c.ApproximateOffsetOf("k02"), 0, 0));
ASSERT_TRUE(Between(c.ApproximateOffsetOf("k03"), 2000, 3555));
ASSERT_TRUE(Between(c.ApproximateOffsetOf("k04"), 2000, 3555));
ASSERT_TRUE(Between(c.ApproximateOffsetOf("xyz"), 4000, 7110));
c.ResetTableReader();
}
+2 -2
View File
@@ -91,9 +91,9 @@ bool ShouldPersistUDT(const UserDefinedTimestampTestMode& test_mode) {
return test_mode != UserDefinedTimestampTestMode::kStripUserDefinedTimestamp;
}
Slice CompressibleString(Random* rnd, double compressed_fraction, int len,
Slice CompressibleString(Random* rnd, double compressed_to_fraction, int len,
std::string* dst) {
int raw = static_cast<int>(len * compressed_fraction);
int raw = static_cast<int>(len * compressed_to_fraction);
if (raw < 1) {
raw = 1;
}
+32
View File
@@ -477,6 +477,38 @@ GTEST_DECLARE_bool_(death_test_use_fork);
namespace internal {
template <typename RawType>
AssertionResult CmpHelperFloatingPointEQ(const char* lhs_expression,
const char* rhs_expression,
RawType lhs_value, RawType rhs_value) {
const FloatingPoint<RawType> lhs(lhs_value), rhs(rhs_value);
if (lhs.AlmostEquals(rhs)) {
return AssertionSuccess();
}
::std::stringstream lhs_ss;
lhs_ss << std::setprecision(std::numeric_limits<RawType>::digits10 + 2)
<< lhs_value;
::std::stringstream rhs_ss;
rhs_ss << std::setprecision(std::numeric_limits<RawType>::digits10 + 2)
<< rhs_value;
return EqFailure(lhs_expression, rhs_expression,
StringStreamToString(&lhs_ss), StringStreamToString(&rhs_ss),
false);
}
template
AssertionResult CmpHelperFloatingPointEQ(const char* lhs_expression,
const char* rhs_expression,
float lhs_value, float rhs_value);
template
AssertionResult CmpHelperFloatingPointEQ(const char* lhs_expression,
const char* rhs_expression,
double lhs_value, double rhs_value);
// The value of GetTestTypeId() as seen from within the Google Test
// library. This is solely for testing GetTestTypeId().
GTEST_API_ extern const TypeId kTestTypeIdInGoogleTest;
+2 -22
View File
@@ -3973,7 +3973,7 @@ const char* StringFromGTestEnv(const char* flag, const char* default_val);
#include <ctype.h>
#include <float.h>
#include <string.h>
#include <iomanip>
// #include <iomanip> // Not included in newer versions of gtest
#include <limits>
#include <map>
#include <set>
@@ -21451,27 +21451,7 @@ template <typename RawType>
AssertionResult CmpHelperFloatingPointEQ(const char* lhs_expression,
const char* rhs_expression,
RawType lhs_value,
RawType rhs_value) {
const FloatingPoint<RawType> lhs(lhs_value), rhs(rhs_value);
if (lhs.AlmostEquals(rhs)) {
return AssertionSuccess();
}
::std::stringstream lhs_ss;
lhs_ss << std::setprecision(std::numeric_limits<RawType>::digits10 + 2)
<< lhs_value;
::std::stringstream rhs_ss;
rhs_ss << std::setprecision(std::numeric_limits<RawType>::digits10 + 2)
<< rhs_value;
return EqFailure(lhs_expression,
rhs_expression,
StringStreamToString(&lhs_ss),
StringStreamToString(&rhs_ss),
false);
}
RawType rhs_value);
// Helper function for implementing ASSERT_NEAR.
//
+24 -14
View File
@@ -11,6 +11,8 @@
# Return value 0 means all regression tests pass. 1 if not pass.
#
# Environment options:
# SANITY_CHECK=1 - Do a syntax check and git checkout test as a sanity check
# that the script hasn't been broken by e.g. adding a new release wrongly.
# SHORT_TEST=1 - Test only the oldest branch for each kind of test. This is
# a good choice for PR validation as it is relatively fast and will find
# most issues.
@@ -135,7 +137,7 @@ EOF
# To check for DB forward compatibility with loading options (old version
# reading data from new), as well as backward compatibility
declare -a db_forward_with_options_refs=("8.6.fb" "8.7.fb" "8.8.fb" "8.9.fb" "8.10.fb" "8.11.fb" "9.0.fb" "9.1.fb" "9.2.fb" "9.3.fb" "9.4.fb" "9.5.fb" "9.6.fb" "9.7.fb" "9.8.fb" "9.9.fb" "9.10.fb" "9.11.fb" "10.0.fb" "10.1.fb" "10.2.fb")
declare -a db_forward_with_options_refs=("8.6.fb" "8.7.fb" "8.8.fb" "8.9.fb" "8.10.fb" "8.11.fb" "9.0.fb" "9.1.fb" "9.2.fb" "9.3.fb" "9.4.fb" "9.5.fb" "9.6.fb" "9.7.fb" "9.8.fb" "9.9.fb" "9.10.fb" "9.11.fb" "10.0.fb" "10.1.fb" "10.2.fb" "10.3.fb")
# To check for DB forward compatibility without loading options (in addition
# to the "with loading options" set), as well as backward compatibility
declare -a db_forward_no_options_refs=() # N/A at the moment
@@ -195,10 +197,14 @@ if [ "$SHORT_TEST" == "" ]; then
done
fi
invoke_make()
{
[ "$SANITY_CHECK" ] || make "$@"
}
generate_db()
{
set +e
bash "$script_copy_dir"/generate_random_db.sh "$1" "$2"
[ "$SANITY_CHECK" ] || bash "$script_copy_dir"/generate_random_db.sh "$1" "$2"
if [ $? -ne 0 ]; then
echo ==== Error loading data from $2 to $1 ====
exit 1
@@ -209,7 +215,7 @@ generate_db()
compare_db()
{
set +e
bash "$script_copy_dir"/verify_random_db.sh "$1" "$2" "$3" "$4" "$5"
[ "$SANITY_CHECK" ] || bash "$script_copy_dir"/verify_random_db.sh "$1" "$2" "$3" "$4" "$5"
if [ $? -ne 0 ]; then
echo ==== Read different content from $1 and $2 or error happened. ====
exit 1
@@ -220,7 +226,7 @@ compare_db()
write_external_sst()
{
set +e
bash "$script_copy_dir"/write_external_sst.sh "$1" "$2" "$3"
[ "$SANITY_CHECK" ] || bash "$script_copy_dir"/write_external_sst.sh "$1" "$2" "$3"
if [ $? -ne 0 ]; then
echo ==== Error writing external SST file using data from $1 to $3 ====
exit 1
@@ -231,7 +237,7 @@ write_external_sst()
ingest_external_sst()
{
set +e
bash "$script_copy_dir"/ingest_external_sst.sh "$1" "$2"
[ "$SANITY_CHECK" ] || bash "$script_copy_dir"/ingest_external_sst.sh "$1" "$2"
if [ $? -ne 0 ]; then
echo ==== Error ingesting external SST in $2 to DB at $1 ====
exit 1
@@ -242,7 +248,7 @@ ingest_external_sst()
backup_db()
{
set +e
bash "$script_copy_dir"/backup_db.sh "$1" "$2"
[ "$SANITY_CHECK" ] || bash "$script_copy_dir"/backup_db.sh "$1" "$2"
if [ $? -ne 0 ]; then
echo ==== Error backing up DB $1 to $2 ====
exit 1
@@ -253,7 +259,7 @@ backup_db()
restore_db()
{
set +e
bash "$script_copy_dir"/restore_db.sh "$1" "$2"
[ "$SANITY_CHECK" ] || bash "$script_copy_dir"/restore_db.sh "$1" "$2"
if [ $? -ne 0 ]; then
echo ==== Error restoring from $1 to $2 ====
exit 1
@@ -297,8 +303,8 @@ current_checkout_name="$current_checkout_name ($current_checkout_hash)"
echo "== Building $current_checkout_name debug"
git checkout -B $tmp_branch $current_checkout_hash
force_no_fbcode
make clean
DISABLE_WARNING_AS_ERROR=1 make ldb -j$J
invoke_make clean
DISABLE_WARNING_AS_ERROR=1 invoke_make ldb -j$J
echo "== Using $current_checkout_name, generate DB with extern SST and ingest"
current_ext_test_dir=$ext_test_dir"/current"
@@ -318,8 +324,8 @@ do
echo "== Building $checkout_ref debug"
git reset --hard $tmp_origin/$checkout_ref
force_no_fbcode
make clean
DISABLE_WARNING_AS_ERROR=1 make ldb -j$J
invoke_make clean
DISABLE_WARNING_AS_ERROR=1 invoke_make ldb -j$J
# We currently assume DB backward compatibility for every branch listed
echo "== Use $checkout_ref to generate a DB ..."
@@ -376,8 +382,8 @@ done
echo "== Building $current_checkout_name debug (again, final)"
git reset --hard $current_checkout_hash
force_no_fbcode
make clean
DISABLE_WARNING_AS_ERROR=1 make ldb -j$J
invoke_make clean
DISABLE_WARNING_AS_ERROR=1 invoke_make ldb -j$J
for checkout_ref in "${checkout_refs[@]}"
do
@@ -404,4 +410,8 @@ do
fi
done
echo ==== Compatibility Test PASSED ====
if [ "$SANITY_CHECK" ]; then
echo "==== check_format_compatible.sh sanity check PASSED ===="
else
echo ==== Compatibility Test PASSED ====
fi
+67 -10
View File
@@ -83,6 +83,7 @@
#include "util/gflags_compat.h"
#include "util/mutexlock.h"
#include "util/random.h"
#include "util/simple_mixed_compressor.h"
#include "util/stderr_logger.h"
#include "util/string_util.h"
#include "util/xxhash.h"
@@ -92,7 +93,6 @@
#include "utilities/merge_operators/bytesxor.h"
#include "utilities/merge_operators/sortlist.h"
#include "utilities/persistent_cache/block_cache_tier.h"
#ifdef MEMKIND
#include "memory/memkind_kmem_allocator.h"
#endif
@@ -596,6 +596,9 @@ static enum ROCKSDB_NAMESPACE::CompressionType
FLAGS_compressed_secondary_cache_compression_type_e =
ROCKSDB_NAMESPACE::kLZ4Compression;
DEFINE_string(compression_manager, "none",
"Set the compression manager type to mixed(roundrobin) or other "
"type. None for BuilInCompressor");
DEFINE_int32(compressed_secondary_cache_compression_level,
ROCKSDB_NAMESPACE::CompressionOptions().level,
"Compression level. The meaning of this value is library-"
@@ -1811,6 +1814,15 @@ DEFINE_bool(track_and_verify_wals_in_manifest, false,
DEFINE_bool(track_and_verify_wals, false, "See Options.track_and_verify_wals");
DEFINE_int32(same_value_percentage, 0,
"Percentage of time value will be same i.e good for compression "
"of the block");
DEFINE_bool(universal_reduce_file_locking,
ROCKSDB_NAMESPACE::Options()
.compaction_options_universal.reduce_file_locking,
"See Options().compaction_options_universal.reduce_file_locking");
namespace ROCKSDB_NAMESPACE {
namespace {
static Status CreateMemTableRepFactory(
@@ -1931,9 +1943,10 @@ class RandomGenerator {
std::string data_;
unsigned int pos_;
std::unique_ptr<BaseDistribution> dist_;
Random rnd;
public:
RandomGenerator() {
RandomGenerator() : rnd(301) {
auto max_value_size = FLAGS_value_size_max;
switch (FLAGS_value_size_distribution_type_e) {
case kUniform:
@@ -1952,7 +1965,6 @@ class RandomGenerator {
// We use a limited amount of data over and over again and ensure
// that it is larger than the compression window (32KB), and also
// large enough to serve all typical value sizes we want to write.
Random rnd(301);
std::string piece;
while (data_.size() < (unsigned)std::max(1048576, max_value_size)) {
// Add a short fragment that is as compressible as specified
@@ -1965,11 +1977,15 @@ class RandomGenerator {
Slice Generate(unsigned int len) {
assert(len <= data_.size());
if (pos_ + len > data_.size()) {
pos_ = 0;
if (rnd.PercentTrue(FLAGS_same_value_percentage)) {
return Slice(data_.data(), len);
} else {
if (pos_ + len > data_.size()) {
pos_ = 0;
}
pos_ += len;
return Slice(data_.data() + pos_ - len, len);
}
pos_ += len;
return Slice(data_.data() + pos_ - len, len);
}
Slice Generate() {
@@ -2884,8 +2900,15 @@ class Benchmark {
}
#endif
}
auto compression = CompressionTypeToString(FLAGS_compression_type_e);
// mixed compression manager expect compression type to be expliciltiy
// configured through Options to be zstd
auto compression = std::string("zstd");
if (!strcasecmp(FLAGS_compression_manager.c_str(), "none")) {
compression = CompressionTypeToString(FLAGS_compression_type_e);
} else {
fprintf(stdout, "Compression manager: %s\n",
FLAGS_compression_manager.c_str());
}
fprintf(stdout, "Compression: %s\n", compression.c_str());
fprintf(stdout, "Compression sampling rate: %" PRId64 "\n",
FLAGS_sample_for_compression);
@@ -4610,7 +4633,39 @@ class Benchmark {
FLAGS_level0_file_num_compaction_trigger;
options.level0_slowdown_writes_trigger =
FLAGS_level0_slowdown_writes_trigger;
options.compression = FLAGS_compression_type_e;
if (!strcasecmp(FLAGS_compression_manager.c_str(), "none")) {
options.compression = FLAGS_compression_type_e;
} else {
std::shared_ptr<CompressionManagerWrapper> mgr;
if (!strcasecmp(FLAGS_compression_manager.c_str(), "mixed")) {
// Need to list zstd in the compression_name table property if it's
// potentially used by being in the mix (i.e., potentially at least one
// data block in the table is compressed by zstd). This ensures proper
// context and dictionary handling, and prevents crashes in older
// RocksDB versions.
options.compression = kZSTD;
options.bottommost_compression = kZSTD;
mgr = std::make_shared<RoundRobinManager>(
GetDefaultBuiltinCompressionManager());
} else if (!strcasecmp(FLAGS_compression_manager.c_str(), "autoskip")) {
options.compression = FLAGS_compression_type_e;
if (FLAGS_compression_type_e == kNoCompression) {
fprintf(stderr,
"Compression type must not be no Compression when using "
"autoskip");
ErrorExit();
}
mgr = CreateAutoSkipCompressionManager(
GetDefaultBuiltinCompressionManager());
} else {
// not defined -> exit with error
fprintf(stderr, "Requested compression manager not supported");
ErrorExit();
}
options.compression_manager = mgr;
}
if (FLAGS_simulate_hybrid_fs_file != "") {
options.last_level_temperature = Temperature::kWarm;
}
@@ -4756,6 +4811,8 @@ class Benchmark {
options.paranoid_memory_checks = FLAGS_paranoid_memory_checks;
options.memtable_op_scan_flush_trigger =
FLAGS_memtable_op_scan_flush_trigger;
options.compaction_options_universal.reduce_file_locking =
FLAGS_universal_reduce_file_locking;
}
void InitializeOptionsGeneral(Options* opts, ToolHooks& hooks) {
+35 -5
View File
@@ -107,7 +107,8 @@ default_params = {
"iterpercent": 10,
"lock_wal_one_in": lambda: random.choice([10000, 1000000]),
"mark_for_compaction_one_file_in": lambda: 10 * random.randint(0, 1),
"max_background_compactions": 20,
"max_background_compactions": lambda: random.choice([2, 20]),
"num_bottom_pri_threads": lambda: random.choice([0, 1, 20]),
"max_bytes_for_level_base": 10485760,
# max_key has to be the same across invocations for verification to work, hence no lambda
"max_key": random.choice([100000, 25000000]),
@@ -345,8 +346,14 @@ default_params = {
"enable_remote_compaction": lambda: random.choice([0, 1]),
"auto_refresh_iterator_with_snapshot": lambda: random.choice([0, 1]),
"memtable_op_scan_flush_trigger": lambda: random.choice([0, 10, 100, 1000]),
"memtable_avg_op_scan_flush_trigger": lambda: random.choice([0, 2, 20, 200]),
"ingest_wbwi_one_in": lambda: random.choice([0, 0, 100, 500]),
"universal_reduce_file_locking": lambda: random.randint(0, 1),
"compression_manager": lambda: random.choice(
["mixed"] * 1 + ["none"] * 2 + ["autoskip"] * 2 + ["randommixed"] * 2
),
}
_TEST_DIR_ENV_VAR = "TEST_TMPDIR"
# If TEST_TMPDIR_EXPECTED is not specified, default value will be TEST_TMPDIR
_TEST_EXPECTED_DIR_ENV_VAR = "TEST_TMPDIR_EXPECTED"
@@ -994,10 +1001,33 @@ def finalize_and_sanitize(src_params):
# have to disable metadata write fault injection to other file
dest_params["exclude_wal_from_write_fault_injection"] = 1
dest_params["metadata_write_fault_one_in"] = 0
# Enabling block_align with compression is not supported
if dest_params.get("block_align") == 1:
dest_params["compression_type"] = "none"
dest_params["bottommost_compression_type"] = "none"
# Disabling block align if mixed manager is neing used
if (
dest_params.get("compression_manager") == "mixed"
or dest_params.get("compression_manager") == "randommixed"
):
if dest_params.get("block_align") == 1:
dest_params["block_align"] = 0
dest_params["compression_type"] = "zstd"
dest_params["bottommost_compression_type"] = "zstd"
elif dest_params.get("compression_manager") == "autoskip":
# disabling compression parallel threads if auto skip manager is being used as the predictor is not thread safe
dest_params["compression_parallel_threads"] = 1
# esuring the compression is being used
if dest_params.get("compression_type") == "none":
dest_params["compression_type"] = random.choice(
["snappy", "zlib", "lz4", "lz4hc", "xpress", "zstd"]
)
if dest_params.get("bottommost_compression_type") == "none":
dest_params["bottommost_compression_type"] = random.choice(
["snappy", "zlib", "lz4", "lz4hc", "xpress", "zstd"]
)
dest_params["block_align"] = 0
else:
# Enabling block_align with compression is not supported
if dest_params.get("block_align") == 1:
dest_params["compression_type"] = "none"
dest_params["bottommost_compression_type"] = "none"
# If periodic_compaction_seconds is not set, daily_offpeak_time_utc doesn't do anything
if dest_params.get("periodic_compaction_seconds") == 0:
dest_params["daily_offpeak_time_utc"] = ""
+16 -7
View File
@@ -44,6 +44,7 @@
#include "util/cast_util.h"
#include "util/coding.h"
#include "util/file_checksum_helper.h"
#include "util/simple_mixed_compressor.h"
#include "util/stderr_logger.h"
#include "util/string_util.h"
#include "util/write_batch_util.h"
@@ -867,13 +868,21 @@ bool LDBCommand::ParseCompressionTypeOption(
"No compressions are supported in this build for \"mixed\".");
return false;
}
// A temporary hack to generate an SST file with a mix of compression
// types, as this has been *de facto* supported for a long time on the
// read side with no code to generate them on the write side. We can test
// that functionality, e.g. in check_format_compatible.sh, with this hack
g_hack_mixed_compression.StoreRelaxed(1);
// Need to list zstd in compression_name table property if it's
// potentially in the mix, for proper handling of context and dictionary.
options_.compression = kZSTD;
options_.bottommost_compression = kZSTD;
auto mgr = std::make_shared<RoundRobinManager>(
GetDefaultBuiltinCompressionManager());
options_.compression_manager = mgr;
// Need to list zstd in the compression_name table property if it's
// potentially used by being in the mix (i.e., potentially at least one
// data block in the table is compressed by zstd). This ensures proper
// context and dictionary handling, and prevents crashes in older RocksDB
// versions.
//
// To achieve this, set `value` (the compression_type in Options which
// will be used to set compression_name table property) to kZSTD, even if
// multiple compression types are used within a single table.
value = ZSTD_Supported() ? kZSTD : GetSupportedCompressions()[0];
return true;
#endif // !NDEBUG
+1
View File
@@ -6,6 +6,7 @@
#include "rocksdb/utilities/ldb_cmd.h"
#include <cinttypes>
#include <iomanip>
#include "db/db_test_util.h"
#include "db/version_edit.h"
@@ -0,0 +1 @@
* `TransactionOptions::large_txn_commit_optimize_threshold` now has default value 0 for disabled. `TransactionDBOptions::txn_commit_bypass_memtable_threshold` now has no effect on transactions.
@@ -0,0 +1 @@
* Fix a bug where CreateColumnFamilyWithImport() could miss the SST file for the memtable flush it triggered. The exported CF then may not contain the updates in the memtable when CreateColumnFamilyWithImport() is called.
-1
View File
@@ -1 +0,0 @@
* Fix a bug where transaction lock upgrade can incorrectly fail with a Deadlock status. This happens when a transaction has a non-zero timeout and tries to upgrade a shared lock that is also held by another transaction.
@@ -0,0 +1 @@
Fix iterator operations returning NotImplemented status if disallow_memtable_writes and paranoid_memory_checks CF options are both set.
@@ -1 +0,0 @@
Pass wrapped WritableFileWriter pointer to ExternalTableBuilder so that the file checksum can be correctly calculated and returned by SstFileWriter for external table files.
@@ -1 +0,0 @@
* Fix an infinite-loop bug in transaction locking. This can happen if a transaction reaches lock limit and its time out expires before it attempts to wait for it.
@@ -1 +0,0 @@
* Fixed a potential data race with `CompressionOptions::parallel_threads > 1` and a `TablePropertiesCollector` overriding `BlockAdd()`.
@@ -0,0 +1 @@
* Add a new CF option `memtable_avg_op_scan_flush_trigger` that supports triggering memtable flush when an iterator scans through an expensive range of keys, with the average number of skipped keys from the active memtable exceeding the threshold.
@@ -1 +0,0 @@
* Add new experimental `CompactionOptionsFIFO::allow_trivial_copy_when_change_temperature` along with `CompactionOptionsFIFO::trivial_copy_buffer_size` to allow optimizing FIFO compactions with tiering when kChangeTemperature to move files from source tier FileSystem to another tier FileSystem via trivial and direct copying raw sst file instead of reading thru the content of the SST file then rebuilding the table files.
@@ -0,0 +1 @@
* Add new experimental `TransactionOptions::large_txn_commit_optimize_byte_threshold` to enable optimizations for large transaction commit by transaction batch data size.
@@ -1 +0,0 @@
* Add a new field to Compaction Stats in LOG files for the pre-compression size written to each level.
@@ -1 +0,0 @@
* Add new experimental `TransactionOptions::large_txn_commit_optimize_threshold` to enable optimizations for large transaction commit with per transaction threshold. `TransactionDBOptions::txn_commit_bypass_memtable_threshold` is deprecated in favor of this transaction option.
@@ -1,2 +0,0 @@
[internal team use only]
allow an application-defined request_id to be passed to RocksDB and propagated to the filesystem via IODebugContext
@@ -0,0 +1 @@
Add a new option `CompactionOptionsUniversal::reduce_file_locking` and if it's true, auto universal compaction picking will adjust to minimize locking of input files when bottom priority compactions are waiting to run. This can increase the likelihood of existing L0s being selected for compaction, thereby improving write stall and reducing read regression.
+108
View File
@@ -0,0 +1,108 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
//
#include "util/auto_skip_compressor.h"
#include "options/options_helper.h"
#include "rocksdb/advanced_compression.h"
#include "test_util/sync_point.h"
#include "util/random.h"
namespace ROCKSDB_NAMESPACE {
int CompressionRejectionProbabilityPredictor::Predict() const {
return pred_rejection_prob_percentage_;
}
size_t CompressionRejectionProbabilityPredictor::attempted_compression_count()
const {
return rejected_count_ + compressed_count_;
}
bool CompressionRejectionProbabilityPredictor::Record(
Slice uncompressed_block_data, std::string* compressed_output,
const CompressionOptions& opts) {
if (compressed_output->size() >
(static_cast<uint64_t>(opts.max_compressed_bytes_per_kb) *
uncompressed_block_data.size()) >>
10) {
rejected_count_++;
} else {
compressed_count_++;
}
auto attempted = attempted_compression_count();
if (attempted >= window_size_) {
pred_rejection_prob_percentage_ =
static_cast<int>(rejected_count_ * 100 / attempted);
compressed_count_ = 0;
rejected_count_ = 0;
assert(attempted_compression_count() == 0);
}
return true;
}
AutoSkipCompressorWrapper::AutoSkipCompressorWrapper(
std::unique_ptr<Compressor> compressor, const CompressionOptions& opts)
: CompressorWrapper::CompressorWrapper(std::move(compressor)),
opts_(opts),
predictor_(
std::make_shared<CompressionRejectionProbabilityPredictor>(10)) {}
Status AutoSkipCompressorWrapper::CompressBlock(
Slice uncompressed_data, std::string* compressed_output,
CompressionType* out_compression_type, ManagedWorkingArea* wa) {
bool exploration =
Random::GetTLSInstance()->PercentTrue(kExplorationPercentage);
TEST_SYNC_POINT_CALLBACK(
"AutoSkipCompressorWrapper::CompressBlock::exploitOrExplore",
&exploration);
if (exploration) {
return CompressBlockAndRecord(uncompressed_data, compressed_output,
out_compression_type, wa);
} else {
auto prediction = predictor_->Predict();
if (prediction <= kProbabilityCutOff) {
// decide to compress
return CompressBlockAndRecord(uncompressed_data, compressed_output,
out_compression_type, wa);
} else {
// decide to bypass compression
*out_compression_type = kNoCompression;
return Status::OK();
}
}
return Status::OK();
}
Status AutoSkipCompressorWrapper::CompressBlockAndRecord(
Slice uncompressed_data, std::string* compressed_output,
CompressionType* out_compression_type, ManagedWorkingArea* wa) {
Status status = wrapped_->CompressBlock(uncompressed_data, compressed_output,
out_compression_type, wa);
// determine if it was rejected or compressed
predictor_->Record(uncompressed_data, compressed_output, opts_);
return status;
}
const char* AutoSkipCompressorManager::Name() const {
// should have returned "AutoSkipCompressorManager" but we currently have an
// error so for now returning name of the wrapped container
return wrapped_->Name();
}
std::unique_ptr<Compressor> AutoSkipCompressorManager::GetCompressorForSST(
const FilterBuildingContext& context, const CompressionOptions& opts,
CompressionType preferred) {
assert(GetSupportedCompressions().size() > 1);
assert(preferred != kNoCompression);
return std::make_unique<AutoSkipCompressorWrapper>(
wrapped_->GetCompressorForSST(context, opts, preferred), opts);
}
std::shared_ptr<CompressionManagerWrapper> CreateAutoSkipCompressionManager(
std::shared_ptr<CompressionManager> wrapped) {
return std::make_shared<AutoSkipCompressorManager>(
wrapped == nullptr ? GetDefaultBuiltinCompressionManager() : wrapped);
}
} // namespace ROCKSDB_NAMESPACE
+65
View File
@@ -0,0 +1,65 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
//
// Creates auto skip compressor wrapper which intelligently decides bypassing
// compression based on past data
#pragma once
#include <memory>
#include "rocksdb/advanced_compression.h"
#include "util/compression.h"
namespace ROCKSDB_NAMESPACE {
// Predict rejection probability using a moving window approach
// This class is not thread safe
class CompressionRejectionProbabilityPredictor {
public:
CompressionRejectionProbabilityPredictor(int window_size)
: pred_rejection_prob_percentage_(0),
rejected_count_(0),
compressed_count_(0),
window_size_(window_size) {}
int Predict() const;
bool Record(Slice uncompressed_block_data, std::string* compressed_output,
const CompressionOptions& opts);
size_t attempted_compression_count() const;
protected:
int pred_rejection_prob_percentage_;
size_t rejected_count_;
size_t compressed_count_;
size_t window_size_;
};
class AutoSkipCompressorWrapper : public CompressorWrapper {
public:
explicit AutoSkipCompressorWrapper(std::unique_ptr<Compressor> compressor,
const CompressionOptions& opts);
Status CompressBlock(Slice uncompressed_data, std::string* compressed_output,
CompressionType* out_compression_type,
ManagedWorkingArea* wa) override;
private:
Status CompressBlockAndRecord(Slice uncompressed_data,
std::string* compressed_output,
CompressionType* out_compression_type,
ManagedWorkingArea* wa);
static constexpr int kExplorationPercentage = 10;
static constexpr int kProbabilityCutOff = 50;
const CompressionOptions opts_;
std::shared_ptr<CompressionRejectionProbabilityPredictor> predictor_;
};
class AutoSkipCompressorManager : public CompressionManagerWrapper {
using CompressionManagerWrapper::CompressionManagerWrapper;
const char* Name() const override;
std::unique_ptr<Compressor> GetCompressorForSST(
const FilterBuildingContext& context, const CompressionOptions& opts,
CompressionType preferred) override;
};
} // namespace ROCKSDB_NAMESPACE
-47
View File
@@ -85,51 +85,4 @@ class UnownedPtr {
T* ptr_ = nullptr;
};
// A smart pointer that tracks an object and an owner, using a statically
// determined function on those to reclaim the object, if both object and owner
// are non-null
template <typename T, class Owner, auto Fn>
class ManagedPtr {
public:
ManagedPtr() = default;
ManagedPtr(T* ptr, Owner* owner) : ptr_(ptr), owner_(owner) {}
~ManagedPtr() {
if (ptr_ && owner_) {
if constexpr (std::is_member_function_pointer_v<decltype(Fn)>) {
(owner_->*Fn)(ptr_);
} else {
Fn(owner_, ptr_);
}
}
}
// No copies
ManagedPtr(const ManagedPtr&) = delete;
ManagedPtr& operator=(const ManagedPtr&) = delete;
// Moves
ManagedPtr(ManagedPtr&& other) noexcept {
ptr_ = other.ptr_;
owner_ = other.owner_;
other.ptr_ = nullptr;
other.owner_ = nullptr;
}
ManagedPtr& operator=(ManagedPtr&& other) noexcept {
ptr_ = other.ptr_;
owner_ = other.owner_;
other.ptr_ = nullptr;
other.owner_ = nullptr;
return *this;
}
T* get() const { return ptr_; }
T* operator->() const { return ptr_; }
T& operator*() const { return *ptr_; }
operator bool() const { return ptr_ != nullptr; }
Owner* owner() const { return owner_; }
private:
T* ptr_ = nullptr;
Owner* owner_ = nullptr;
};
} // namespace ROCKSDB_NAMESPACE
Loaded 100 of 112 files, more files were not shown because too many files have changed in this diff. Show more