Compare commits

...
Author SHA1 Message Date
Maciej Szeszko 9ac0ac1f2f Start 10.5 development 2025-06-20 16:38:34 -07:00
Maciej Szeszko 190bb0bd24 Disable AutoSkipCompressionManager test (#13715)
Summary:
Auto skip compression manager code is currently running only in context of test / db bench. Disable failing test to unblock monthly minor release.

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

Test Plan: Disable test.

Reviewed By: hx235

Differential Revision: D77039218

Pulled By: mszeszko-meta

fbshipit-source-id: f9eeec8d5ca4efeaf1f490c5f091b3aff7861a4a
2025-06-20 12:38:32 -07:00
Peter Dillinger fdc2970d37 Connect custom compression to crash test and ObjectLibrary (#13710)
Summary:
Some pieces of follow-up to https://github.com/facebook/rocksdb/issues/13659.
_Recommend hiding whitespace for review_
* Add support for instantiating CompressionManagers through CreateFromString/ObjectLibrary.
* Pull CompressorCustomAlg and DecompressorCustomAlg out of db_test2, refactor/improvement them a bit, and put them in testutil.h for sharing with db_stress. Switched it from being built on snappy to being built on lz4 so that it can properly test dictionary compression.
* Add a custom compression manager for db_stress that uses these, and add to crash test. This depends on the ObjectLibrary stuff because some invocations of db_stress will not be configured with the custom compression manager but will need to access it to read some existing SST files.
* Remove some pieces where the concern of setting compression=kZSTD for compatibility purposes had leaked into configuring some tests and compression managers. After https://github.com/facebook/rocksdb/issues/13659 this compatibility concern is contained in the SST building code.
* Fix BuiltinDecompressorV2SnappyOnly hiding the (ignored) compression dictionary. SST read logic expects the serialized dictionary to be returned by the decompressor even if it's effectively ignored. Updated DBBlockCacheTest.CacheCompressionDict to cover this case.

For follow-up:
* Combine custom compression and mixed compression types in a file (not clean/easy without duplicating or majorly refactoring the mixed/random compressor)

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

Test Plan: unit tests updated

Reviewed By: hx235

Differential Revision: D76928974

Pulled By: pdillinger

fbshipit-source-id: 772cf9cb048d737699b0e2887c624fb64a68aa8c
2025-06-19 12:54:15 -07:00
Changyu Bi d55655a423 Add an optional min file size requirement for deletion triggered compaction (#13707)
Summary:
add the `min_file_size` parameter to CompactOnDeletionCollector. A file must be at least this size for it to qualify for DTC. This is useful when a user wants to specific a min file size requirement that is larger than the size constraint imposed by the sliding window's `deletion_trigger` requirement.

Added some comment explaining that the file_size provided to table property collector only includes data blocks and may not be up-to-date. This PR also updates DTC to consider SingleDelete and DeletionWithTimestamp as tombstones.

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

Test Plan:
- new unit test for when min_file_size is specified.
- existing unit test for when min_file_size is not specified.

Reviewed By: hx235, pdillinger

Differential Revision: D76837231

Pulled By: cbi42

fbshipit-source-id: 0782144e75aef9961bf03da2a2c4b3c613ce5db3
2025-06-19 11:04:35 -07:00
Changyu Bi c8aafdba33 Support concurrent write for vector memtable (#13675)
Summary:
Some usage of vector memtable is bottlenecked in the memtable insertion path when using multiple writers. This PR adds support for concurrent writes for the vector memtable. The updates from each concurrent writer are buffered in a thread local vector. When a writer is done, MemTable::BatchPostProcess() is called to flush the thread local updates to the main vector. TSAN test and function comment suggest that ApproximateMemoryUsage() needs to be thread-safe, so its implementation is updated to provide thread-safe access.

Together with unordered_write, benchmark shows much improved insertion throughput.

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

Test Plan:
- new unit test
- enabled some coverage of vector memtable in stress test
- Performance benchmark: benchmarked memtable insertion performance with by running fillrandom 20 times
  - Compare branch and main performance with one thread and write batch size 100:
    - main: 4896888.950 ops/sec
    - branch: 4923366.350 ops/sec
  - Benchmark this branch by configuring different threads, allow_concurrent_memtable_write, and unordered_write. Performance ratio is computed as current ops/sec divided by ops/sec at 1 thread with the same options.

allow_concurrent | unordered_write | Threads | ops/sec | Performance Ratio
-- | -- | -- | -- | --
0 | 0 | 1 | 4923367 | 1.0
0 | 0 | 2 | 5215640 | 1.1
0 | 0 | 4 | 5588510 | 1.1
0 | 0 | 8 | 6077525 | 1.2
1 | 0 | 1 | 4919060 | 1.0
1 | 0 | 2 | 5821922 | 1.2
1 | 0 | 4 | 7850395 | 1.6
1 | 0 | 8 | 10516600 | 2.1
1 | 1 | 1 | 5050004 | 1.0
1 | 1 | 2 | 8489834 | 1.7
1 | 1 | 4 | 14439513 | 2.9
1 | 1 | 8 | 21538098 | 4.3

```
mkdir -p /tmp/bench_$1
export TEST_TMPDIR=/tmp/bench_$1

memtablerep_value=${6:-vector}

(for I in $(seq 1 $2)
do
	/data/users/changyubi/vscode-root/rocksdb/$1 --benchmarks=fillrandom --seed=1722808058 --write_buffer_size=67108864 --min_write_buffer_number_to_merge=1000 --max_write_buffer_number=1000 --enable_pipelined_write=0 --memtablerep=$memtablerep_value --disable_auto_compactions=1 --disable_wal=1 --avoid_flush_during_shutdown=1 --allow_concurrent_memtable_write=${5:-0} --unordered_write=$4 --batch_size=1 --threads=$3 2>&1 | grep "fillrandom"
done;) | awk '{ t += $5; c++; print } END { printf ("%9.3f\n", 1.0 * t / c) }';
```

Reviewed By: pdillinger

Differential Revision: D76641755

Pulled By: cbi42

fbshipit-source-id: c107ba42749855ad4fd1f52491eb93900757542e
2025-06-18 17:32:59 -07:00
Peter Dillinger 1601da4049 Improve file checksum handling for ingestion (#13708)
Summary:
* Improve debugability with better error messages (including the returned status, not just log messages)
* Tolerate user providing file checksums recognized by the factory but not the same function as currently, generally provided by the factory. This makes it practical to transition from one type of checksum to another without major hiccups in ingestion workflows.

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

Test Plan: updated unit test, manually inspect LOG file from the unit test

Reviewed By: cbi42

Differential Revision: D76837804

Pulled By: pdillinger

fbshipit-source-id: 45b744829b3a125e9d0ee6874bd37ce534c2e13c
2025-06-17 21:34:34 -07:00
Sujit Maharjan 34d8f03af4 Moving predictor to WorkingArea to make it thread safe (#13706)
Summary:
**Summary:**

We need to move the Predictor to WorkingArea so that it is local to each thread and thus is thread safe.

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

Test Plan: It should pass the test case written in ./compression_test.

Reviewed By: pdillinger

Differential Revision: D76836846

Pulled By: shubhajeet

fbshipit-source-id: 0d0170baf65f4bb95ba107fec77151e66b8a4449
2025-06-17 19:17:25 -07:00
Sujit Maharjan 05996cd497 crash test and other refactoring (#13704)
Summary:
**Summary:**
AddressSanitizer failed complaining stack-use-after-return while executing AutoSkipCompressorWrapper::CompressBlock. This was caused because the AutoSkipCompressorWrapper was storing const reference pointer to Compression Options. It seems like the life time of the Compression Options can be shorter than the AutoSkipCompressorWrapper thus we need to copy the Compression Options and store it in AutoSkipCompressorWrapper.

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

Test Plan:
Run the crashtest again to verify that we don’t encounter the issue again.
```bash
make clean
COMPILE_WITH_ASAN=1 make -j80 dbg
mkdir -p /dev/shm/rocksdb_test/rocksdb_crashtest_blackbox
mkdir -p dev/shm/rocksdb_test/rocksdb_crashtest_expected
./db_stress --WAL_size_limit_MB=0 --WAL_ttl_seconds=0 --acquire_snapshot_one_in=100 --adaptive_readahead=0 --adm_policy=2 --advise_random_on_open=1 --allow_data_in_errors=True --allow_fallocate=1 --allow_setting_blob_options_dynamically=1 --allow_unprepared_value=1 --async_io=0 --auto_readahead_size=1 --auto_refresh_iterator_with_snapshot=0 --avoid_flush_during_recovery=0 --avoid_flush_during_shutdown=1 --avoid_unnecessary_blocking_io=1 --backup_max_size=104857600 --backup_one_in=100000 --batch_protection_bytes_per_key=0 --bgerror_resume_retry_interval=1000000 --blob_cache_size=2097152 --blob_compaction_readahead_size=0 --blob_compression_type=snappy --blob_file_size=16777216 --blob_file_starting_level=2 --blob_garbage_collection_age_cutoff=0.5 --blob_garbage_collection_force_threshold=0.5 --block_align=0 --block_protection_bytes_per_key=2 --block_size=16384 --bloom_before_level=2147483646 --bloom_bits=9.703060295811829 --bottommost_compression_type=disable --bottommost_file_compaction_delay=0 --bytes_per_sync=262144 --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=0 --charge_file_metadata=1 --charge_filter_construction=1 --charge_table_reader=0 --check_multiget_consistency=0 --check_multiget_entity_consistency=0 --checkpoint_one_in=1000000 --checksum_type=kXXH3 --clear_column_family_one_in=0 --compact_files_one_in=1000 --compact_range_one_in=1000000 --compaction_pri=1 --compaction_readahead_size=1048576 --compaction_style=0 --compaction_ttl=0 --compress_format_version=1 --compressed_secondary_cache_size=16777216 --compression_checksum=0 --compression_manager=autoskip --compression_max_dict_buffer_bytes=0 --compression_max_dict_bytes=0 --compression_parallel_threads=1 --compression_type=zlib --compression_use_zstd_dict_trainer=1 --compression_zstd_max_train_bytes=0 --continuous_verification_interval=0 --daily_offpeak_time_utc= --data_block_index_type=1 --db=/dev/shm/rocksdb_test/rocksdb_crashtest_blackbox --db_write_buffer_size=1048576 --decouple_partitioned_filters=1 --default_temperature=kUnknown --default_write_temperature=kWarm --delete_obsolete_files_period_micros=30000000 --delpercent=4 --delrangepercent=1 --destroy_db_initially=0 --detect_filter_construct_corruption=0 --disable_file_deletions_one_in=10000 --disable_manual_compaction_one_in=1000000 --disable_wal=0 --dump_malloc_stats=0 --enable_blob_files=1 --enable_blob_garbage_collection=1 --enable_checksum_handoff=1 --enable_compaction_filter=0 --enable_custom_split_merge=0 --enable_do_not_compress_roles=0 --enable_index_compression=1 --enable_memtable_insert_with_hint_prefix_extractor=0 --enable_pipelined_write=0 --enable_remote_compaction=0 --enable_sst_partitioner_factory=0 --enable_thread_tracking=1 --enable_write_thread_adaptive_yield=0 --error_recovery_with_no_fault_injection=0 --exclude_wal_from_write_fault_injection=0 --expected_values_dir=/dev/shm/rocksdb_test/rocksdb_crashtest_expected --fifo_allow_compaction=1 --file_checksum_impl=xxh64 --file_temperature_age_thresholds= --fill_cache=1 --flush_one_in=1000 --format_version=2 --get_all_column_family_metadata_one_in=10000 --get_current_wal_file_one_in=0 --get_live_files_apis_one_in=1000000 --get_properties_of_all_tables_one_in=100000 --get_property_one_in=100000 --get_sorted_wal_files_one_in=0 --hard_pending_compaction_bytes_limit=274877906944 --high_pri_pool_ratio=0 --index_block_restart_interval=15 --index_shortening=2 --index_type=2 --ingest_external_file_one_in=1000 --ingest_wbwi_one_in=0 --initial_auto_readahead_size=0 --inplace_update_support=0 --iterpercent=10 --key_len_percent_dist=1,30,69 --key_may_exist_one_in=100000 --last_level_temperature=kHot --level_compaction_dynamic_level_bytes=0 --lock_wal_one_in=0 --log_file_time_to_roll=0 --log_readahead_size=16777216 --long_running_snapshots=1 --low_pri_pool_ratio=0 --lowest_used_cache_tier=2 --manifest_preallocation_size=0 --manual_wal_flush_one_in=0 --mark_for_compaction_one_file_in=10 --max_auto_readahead_size=524288 --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=16 --max_write_buffer_number=10 --max_write_buffer_size_to_maintain=1048576 --memtable_avg_op_scan_flush_trigger=0 --memtable_insert_hint_per_batch=1 --memtable_max_range_deletions=0 --memtable_op_scan_flush_trigger=1000 --memtable_prefix_bloom_size_ratio=0.01 --memtable_protection_bytes_per_key=4 --memtable_whole_key_filtering=1 --memtablerep=skip_list --metadata_charge_policy=0 --metadata_read_fault_one_in=0 --metadata_write_fault_one_in=1000 --min_blob_size=16 --min_write_buffer_number_to_merge=2 --mmap_read=1 --mock_direct_io=False --nooverwritepercent=1 --num_bottom_pri_threads=1 --num_file_reads_for_auto_readahead=1 --open_files=100 --open_metadata_read_fault_one_in=0 --open_metadata_write_fault_one_in=0 --open_read_fault_one_in=0 --open_write_fault_one_in=16 --ops_per_thread=100000000 --optimize_filters_for_hits=0 --optimize_filters_for_memory=0 --optimize_multiget_for_io=1 --paranoid_file_checks=0 --paranoid_memory_checks=0 --partition_filters=1 --partition_pinning=3 --pause_background_one_in=1000000 --periodic_compaction_seconds=0 --prefix_size=7 --prefixpercent=5 --prepopulate_blob_cache=0 --prepopulate_block_cache=1 --preserve_internal_time_seconds=0 --progress_reports=0 --promote_l0_one_in=0 --read_amp_bytes_per_bit=0 --read_fault_one_in=0 --readahead_size=16384 --readpercent=45 --recycle_log_file_num=0 --reopen=0 --report_bg_io_stats=0 --reset_stats_one_in=10000 --sample_for_compression=0 --secondary_cache_fault_one_in=32 --secondary_cache_uri= --set_options_one_in=1000 --skip_stats_update_on_db_open=0 --snapshot_hold_ops=100000 --soft_pending_compaction_bytes_limit=1048576 --sqfc_name=bar --sqfc_version=2 --sst_file_manager_bytes_per_sec=0 --sst_file_manager_bytes_per_truncate=0 --stats_dump_period_sec=0 --stats_history_buffer_size=1048576 --strict_bytes_per_sync=0 --subcompactions=3 --sync=0 --sync_fault_injection=0 --table_cache_numshardbits=-1 --target_file_size_base=524288 --target_file_size_multiplier=2 --test_batches_snapshots=0 --test_ingest_standalone_range_deletion_one_in=10 --top_level_index_pinning=2 --uncache_aggressiveness=5100 --universal_max_read_amp=0 --universal_reduce_file_locking=1 --unpartitioned_pinning=2 --use_adaptive_mutex=1 --use_adaptive_mutex_lru=0 --use_attribute_group=1 --use_blob_cache=1 --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=0 --use_multi_cf_iterator=1 --use_multi_get_entity=0 --use_multiget=1 --use_put_entity_one_in=10 --use_shared_block_and_blob_cache=0 --use_sqfc_for_range_queries=1 --use_timed_put_one_in=0 --use_write_buffer_manager=1 --user_timestamp_size=0 --value_size_mult=32 --verification_only=0 --verify_checksum=1 --verify_checksum_one_in=1000 --verify_compression=0 --verify_db_one_in=100000 --verify_file_checksums_one_in=1000000 --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=1048576 --write_dbid_to_manifest=1 --write_fault_one_in=128 --write_identity_file=0 --writepercent=35
```

Reviewed By: hx235

Differential Revision: D76826904

Pulled By: shubhajeet

fbshipit-source-id: c4a1522d3fed37bdd3e711f4c99c16d7bd1d794f
2025-06-17 11:28:33 -07:00
anand76 25837eeee5 Change NewExternalTableFactory to return unique_ptr (#13705)
Summary:
Change NewExternalTableFactory API and remove the just added NewExternalTableFactoryAsUniquePtr.

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

Reviewed By: jaykorean

Differential Revision: D76827580

Pulled By: anand1976

fbshipit-source-id: 251ad0e498b62059b8417ff967ca74146de43e2f
2025-06-17 10:50:33 -07:00
Sujit Maharjan c6cfbc2919 clang tidy warning lsh == rhs. lhs or rhs not initialized. (#13703)
Summary:
**Summary**:

Clang tidy was throwing error that the gtest assertion EXPECT_EQ was being carried out variables that was not initialized.

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

Test Plan:
Ran the clang-tidy operations to make sure the same error does not appear.
```bash
  CC=clang-10 CXX=clang++-10 ROCKSDB_DISABLE_ALIGNED_NEW=1 CLANG_ANALYZER="/usr/bin/clang++-10" CLANG_SCAN_BUILD=scan-build-10 USE_CLANG=1 make V=1 -j32 analyze
```

Reviewed By: hx235, pdillinger

Differential Revision: D76777988

Pulled By: shubhajeet

fbshipit-source-id: b9bfe26a2264d4c21224ab53a0b0307596d7f49d
2025-06-17 09:47:47 -07:00
anand76 d27b47f439 Add NewExternalTableFactoryAsUniquePtr API (#13694)
Summary:
The Object registry requires object to be allocated as std::unique_ptr. Hence we provide a new API for external table plugins to allocate and return a unique_ptr ExternalTableFactory wrapper.

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

Reviewed By: jaykorean

Differential Revision: D76767974

Pulled By: anand1976

fbshipit-source-id: ac59c523a11679ca7c9f0b280325c7873c6b4c07
2025-06-16 17:02:40 -07:00
Peter Dillinger 9d490593d0 Preliminary support for custom compression algorithms (#13659)
Summary:
This change builds on https://github.com/facebook/rocksdb/issues/13540 and https://github.com/facebook/rocksdb/issues/13626 in allowing a CompressionManager / Compressor / Decompressor to use a custom compression algorithm, with a distinct CompressionType. For background, review the API comments on CompressionManager and its CompatibilityName() function.

Highlights:
* Reserve and name 127 new CompressionTypes that can be used for custom compression algorithms / schemas. In many or most cases I expect the enumerators such as `kCustomCompression8F` to be used in user code rather than casting between integers and CompressionTypes, as I expect the supported custom compression algorithms to be identifiable / enumerable at compile time.
* When using these custom compression types, a CompressionManager must use a CompatibilityName() other than the built-in one AND new format_version=7 (see below).
* When building new SST files, track the full set of CompressionTypes actually used (usually just one aside from kNoCompression), using our efficient bitset SmallEnumSet, which supports fast iteration over the bits set to 1. Ideally, to support mixed or non-mixed compression algorithms in a file as efficiently as possible, we would know the set of CompressionTypes as SST file open time.
* New schema for `TableProperties::compression_name` in format_version=7 to represent the CompressionManager's CompatibilityName(), the set of CompressionTypes used, and potentially more in the future, while keeping the data relatively human-readable.
  * It would be possible to do this without a new format_version, but then the only way to ensure incompatible versions fail is with an unsupported CompressionType tag, not with a compression_name property. Therefore, (a) I prefer not to put something misleading in the `compression_name` property (a built-in compression name) when there is nuance because of a CompressionManager, and (b) I prefer better, more consistent error messages that refer to either format_version or the CompressionManager's CompatibilityName(), rather than an unrecognized custom CompressionType value (which could have come from various CompressionManagers).
* The current configured CompressionManager is passed in to TableReaders so that it (or one it knows about) can be used if it matches the CompatibilityName() used for compression in the SST file. Until the connection with ObjectRegistry is implemented, the only way to read files generated with a particular CompressionManager using custom compression algorithms is to configure it (or a known relative; see FindCompatibleCompressionManager()) in the ColumnFamilyOptions.
* Optimized snappy compression with BuiltinDecompressorV2SnappyOnly, to offset some small added overheads with the new tracking. This is essentially an early part of the planned refactoring that will get rid of the old internal compression APIs.
* Another small optimization in eliminating an unnecessary key copy in flush (builder.cc).
* Fix some handling of named CompressionManagers in CompressionManager::CreateFromString() (problem seen in https://github.com/facebook/rocksdb/issues/13647)

Smaller things:
* Adds Name() and GetId() functions to Compressor for debugging/logging purposes. (Compressor and Decompressor are not expected to be Customizable because they are only instantiated by a CompressionManager.)
* When using an explicit compression_manager, the GetId() of the CompressionManager and the Compressor used to build the file are stored as bonus entries in the compression_options table property. This table property is not parsed anywhere, so it is currently for human reading, but still could be parsed with the new underscore-prefixed bonus entries. IMHO, this is preferable to additional table properties, which would increase memory fragmentation in the TableProperties objects and likely take slightly more CPU on SST open and slightly more storage.
* ReleaseWorkingArea() function from protected to public to make wrappers work, because of a quirk in C++ (vs. Java) in which you cannot access protected members of another instance of the same class (sigh)
* Added `CompressionManager:: SupportsCompressionType()` for early options sanity checking.

Follow-up before release:
* Make format_version=7 official / supported
* Stress test coverage

Sooner than later:
* Update tests for RoundRobinManager and SimpleMixedCompressionManager to take advantage of e.g. set of compression types in compression_name property
* ObjectRegistry stuff
* Refactor away old internal compression APIs

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

Test Plan:
Basic unit test added.

## Performance

### SST write performance
```
SUFFIX=`tty | sed 's|/|_|g'`; for ARGS in "-compression_type=none" "-compression_type=snappy" "-compression_type=zstd" "-compression_type=snappy -verify_compression=1" "-compression_type=zstd -verify_compression=1" "-compression_type=zstd -compression_max_dict_bytes=8180"; do echo $ARGS; (for I in `seq 1 20`; do BIN=/dev/shm/dbbench${SUFFIX}.bin; rm -f $BIN; cp db_bench $BIN; $BIN -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 -format_version=7 $ARGS 2>&1 | grep micros/op; done) | awk '{n++; sum += $5;} END { print int(sum / n); }'; done
```

Ops/sec, Before -> After, both fv=6:
-compression_type=none
1894386 -> 1858403 (-2.0%)
-compression_type=snappy
1859131 -> 1807469 (-2.8%)
-compression_type=zstd
1191428 -> 1214374 (+1.9%)
-compression_type=snappy -verify_compression=1
1861819 -> 1858342 (+0.2%)
-compression_type=zstd -verify_compression=1
979435 -> 995870 (+1.6%)
-compression_type=zstd -compression_max_dict_bytes=8180
905349 -> 940563 (+3.9%)

Ops/sec, Before fv=6 -> After fv=7:
-compression_type=none
1879365 -> 1836159 (-2.3%)
-compression_type=snappy
1865460 -> 1830916 (-1.9%)
-compression_type=zstd
1191428 -> 1210260 (+1.6%)
-compression_type=snappy -verify_compression=1
1866756 -> 1818989 (-2.6%)
-compression_type=zstd -verify_compression=1
982640 -> 997129 (+1.5%)
-compression_type=zstd -compression_max_dict_bytes=8180
912608 -> 937248 (+2.7%)

### SST read performance
Create DBs
```
for COMP in none snappy zstd; do echo $ARGS; ./db_bench -db=/dev/shm/dbbench-7-$COMP --benchmarks=fillseq,flush -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=$COMP -format_version=7; done
```
And test
```
for COMP in none
snappy zstd none; do echo $COMP; (for I in `seq 1 8`; do ./db_bench -readonly -db=/dev/shm/dbbench
-7-$COMP --benchmarks=readrandom -num=10000000 -duration=20 -threads=8 2>&1 | grep micros/op; done
) | awk '{n++; sum += $5;} END { print int(sum / n); }'; done
```

Ops/sec, Before -> After (both fv=6)
none
1491732 -> 1500209 (+0.6%)
snappy
1157216 -> 1169202 (+1.0%)
zstd
695414 -> 703719 (+1.2%)
none (again)
1491787 -> 1528789 (+2.4%)

Ops/sec, Before fv=6 -> After fv=7:
none
1492278 -> 1508668 (+1.1%)
snappy
1140769 -> 1152613 (+1.0%)
zstd
696437 -> 696511 (+0.0%)
none (again)
1500585 -> 1512037 (+0.7%)

Overall, I think we can take the read CPU improvement in exchange for the hit (in some cases) on background write CPU

Reviewed By: hx235

Differential Revision: D76520739

Pulled By: pdillinger

fbshipit-source-id: e73bd72502ff85c8779cba313f26f7d1fd50be3a
2025-06-16 14:19:03 -07:00
71 changed files with 2172 additions and 540 deletions

No files matched your search

+2 -2
View File
@@ -874,13 +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/auto_skip_compressor.cc
util/compression_context_cache.cc
util/concurrent_task_limiter_impl.cc
util/crc32c.cc
@@ -1447,7 +1447,6 @@ if(WITH_TESTS)
table/table_test.cc
table/block_fetcher_test.cc
test_util/testutil_test.cc
util/compression_test.cc
trace_replay/block_cache_tracer_test.cc
trace_replay/io_tracer_test.cc
tools/block_cache_analyzer/block_cache_trace_analyzer_test.cc
@@ -1459,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
+19
View File
@@ -1,6 +1,25 @@
# Rocksdb Change Log
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
## 10.4.0 (06/20/2025)
### New Features
* 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.
* Vector based memtable now supports concurrent writers (DBOptions::allow_concurrent_memtable_write) #13675.
* Add new experimental `TransactionOptions::large_txn_commit_optimize_byte_threshold` to enable optimizations for large transaction commit by transaction batch data size.
* 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.
### Public API Changes
* Change NewExternalTableFactory to return a unique_ptr instead of shared_ptr.
* Add an optional min file size requirement for deletion triggered compaction. It can be specified when creating `CompactOnDeletionCollectorFactory`.
### Behavior Changes
* `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.
### Bug Fixes
* 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.
* Fix iterator operations returning NotImplemented status if disallow_memtable_writes and paranoid_memory_checks CF options are both set.
* Fixed handling of file checksums in IngestExternalFile() to allow providing checksums using recognized but not necessarily the DB's preferred checksum function, to ease migration between checksum functions.
## 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.
+2 -2
View File
@@ -218,8 +218,7 @@ Status BuildTable(
const Slice& key = c_iter.key();
const Slice& value = c_iter.value();
ParsedInternalKey ikey = c_iter.ikey();
key_after_flush_buf.assign(key.data(), key.size());
Slice key_after_flush = key_after_flush_buf;
Slice key_after_flush = key;
Slice value_after_flush = value;
if (ikey.type == kTypeValuePreferredSeqno) {
@@ -237,6 +236,7 @@ Status BuildTable(
std::min(smallest_preferred_seqno, preferred_seqno);
} else {
// Cannot get a useful preferred seqno, convert it to a kTypeValue.
key_after_flush_buf.assign(key.data(), key.size());
UpdateInternalKey(&key_after_flush_buf, ikey.sequence, kTypeValue);
ikey = ParsedInternalKey(ikey.user_key, ikey.sequence, kTypeValue);
key_after_flush = key_after_flush_buf;
+11 -2
View File
@@ -110,11 +110,19 @@ void GetInternalTblPropCollFactory(
}
}
bool CompressionSupportedWithManager(CompressionType type,
UnownedPtr<CompressionManager> mgr) {
return mgr ? mgr->SupportsCompressionType(type)
: CompressionTypeSupported(type);
}
Status CheckCompressionSupported(const ColumnFamilyOptions& cf_options) {
if (!cf_options.compression_per_level.empty()) {
for (size_t level = 0; level < cf_options.compression_per_level.size();
++level) {
if (!CompressionTypeSupported(cf_options.compression_per_level[level])) {
if (!CompressionSupportedWithManager(
cf_options.compression_per_level[level],
cf_options.compression_manager.get())) {
return Status::InvalidArgument(
"Compression type " +
CompressionTypeToString(cf_options.compression_per_level[level]) +
@@ -122,7 +130,8 @@ Status CheckCompressionSupported(const ColumnFamilyOptions& cf_options) {
}
}
} else {
if (!CompressionTypeSupported(cf_options.compression)) {
if (!CompressionSupportedWithManager(
cf_options.compression, cf_options.compression_manager.get())) {
return Status::InvalidArgument(
"Compression type " +
CompressionTypeToString(cf_options.compression) +
+3 -2
View File
@@ -458,9 +458,10 @@ class CompactionJobTestBase : public testing::Test {
ReadOptions read_opts;
Status s = cf_options_.table_factory->NewTableReader(
read_opts,
TableReaderOptions(cfd->ioptions(), nullptr, FileOptions(),
TableReaderOptions(cfd->ioptions(), /*prefix_extractor=*/nullptr,
/*compression_manager=*/nullptr, FileOptions(),
cfd_->internal_comparator(),
0 /* block_protection_bytes_per_key */),
/*block_protection_bytes_per_key=*/0),
std::move(freader), file_size, &table_reader, false);
ASSERT_OK(s);
assert(table_reader);
+4 -3
View File
@@ -93,9 +93,10 @@ Status VerifySstFileChecksumInternal(const Options& options,
nullptr /* file_read_hist */, ioptions.rate_limiter.get()));
const bool kImmortal = true;
auto reader_options = TableReaderOptions(
ioptions, options.prefix_extractor, env_options, internal_comparator,
options.block_protection_bytes_per_key, false /* skip_filters */,
!kImmortal, false /* force_direct_prefetch */, -1 /* level */);
ioptions, options.prefix_extractor, options.compression_manager.get(),
env_options, internal_comparator, options.block_protection_bytes_per_key,
false /* skip_filters */, !kImmortal, false /* force_direct_prefetch */,
-1 /* level */);
reader_options.largest_seqno = largest_seqno;
s = options.table_factory->NewTableReader(
read_options, reader_options, std::move(file_reader), file_size,
+68 -57
View File
@@ -829,68 +829,79 @@ TEST_F(DBBlockCacheTest, CacheCompressionDict) {
const int kNumEntriesPerFile = 128;
const int kNumBytesPerEntry = 1024;
// Try all the available libraries that support dictionary compression
std::vector<CompressionType> compression_types;
if (Zlib_Supported()) {
compression_types.push_back(kZlibCompression);
}
if (LZ4_Supported()) {
compression_types.push_back(kLZ4Compression);
compression_types.push_back(kLZ4HCCompression);
}
if (ZSTD_Supported()) {
compression_types.push_back(kZSTD);
}
std::vector<CompressionType> dict_compressions =
GetSupportedDictCompressions();
Random rnd(301);
for (auto compression_type : compression_types) {
Options options = CurrentOptions();
options.bottommost_compression = compression_type;
options.bottommost_compression_opts.max_dict_bytes = 4096;
options.bottommost_compression_opts.enabled = true;
options.create_if_missing = true;
options.num_levels = 2;
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
options.target_file_size_base = kNumEntriesPerFile * kNumBytesPerEntry;
BlockBasedTableOptions table_options;
table_options.cache_index_and_filter_blocks = true;
table_options.block_cache.reset(new MockCache());
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
DestroyAndReopen(options);
// Format version before and after compression handling changes
TEST_AllowUnsupportedFormatVersion() = true;
for (int format_version : {6, 7}) {
// Test all supported compression types because (at least historically)
// dictionary compression could be enabled and a dictionary block saved
// but ignored by some compression types. Ensure we at least don't crash
// or return corruption for those.
for (auto compression_type : GetSupportedCompressions()) {
// Extra handling checks only for types actually supporting dictionary
// compression.
bool dict_supported =
std::count(dict_compressions.begin(), dict_compressions.end(),
compression_type) > 0;
RecordCacheCountersForCompressionDict(options);
Options options = CurrentOptions();
options.bottommost_compression = compression_type;
options.bottommost_compression_opts.max_dict_bytes = 4096;
options.bottommost_compression_opts.enabled = true;
options.create_if_missing = true;
options.num_levels = 2;
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
options.target_file_size_base = kNumEntriesPerFile * kNumBytesPerEntry;
BlockBasedTableOptions table_options;
table_options.cache_index_and_filter_blocks = true;
table_options.block_cache.reset(new MockCache());
table_options.format_version = format_version;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
DestroyAndReopen(options);
for (int i = 0; i < kNumFiles; ++i) {
ASSERT_EQ(i, NumTableFilesAtLevel(0, 0));
for (int j = 0; j < kNumEntriesPerFile; ++j) {
std::string value = rnd.RandomString(kNumBytesPerEntry);
ASSERT_OK(Put(Key(j * kNumFiles + i), value.c_str()));
RecordCacheCountersForCompressionDict(options);
for (int i = 0; i < kNumFiles; ++i) {
ASSERT_EQ(i, NumTableFilesAtLevel(0, 0));
for (int j = 0; j < kNumEntriesPerFile; ++j) {
std::string value = rnd.RandomString(kNumBytesPerEntry);
ASSERT_OK(Put(Key(j * kNumFiles + i), value.c_str()));
}
ASSERT_OK(Flush());
}
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_EQ(0, NumTableFilesAtLevel(0));
ASSERT_EQ(kNumFiles, NumTableFilesAtLevel(1));
if (dict_supported) {
// Compression dictionary blocks are preloaded.
CheckCacheCountersForCompressionDict(
options, kNumFiles /* expected_compression_dict_misses */,
0 /* expected_compression_dict_hits */,
kNumFiles /* expected_compression_dict_inserts */);
}
// Seek to a key in a file. It should cause the SST's dictionary
// meta-block to be read.
RecordCacheCounters(options);
RecordCacheCountersForCompressionDict(options);
ReadOptions read_options;
ASSERT_NE("NOT_FOUND", Get(Key(kNumFiles * kNumEntriesPerFile - 1)));
if (dict_supported) {
// Two block hits: index and dictionary since they are prefetched
// One block missed/added: data block
CheckCacheCounters(options, 1 /* expected_misses */,
2 /* expected_hits */, 1 /* expected_inserts */,
0 /* expected_failures */);
CheckCacheCountersForCompressionDict(
options, 0 /* expected_compression_dict_misses */,
1 /* expected_compression_dict_hits */,
0 /* expected_compression_dict_inserts */);
}
ASSERT_OK(Flush());
}
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_EQ(0, NumTableFilesAtLevel(0));
ASSERT_EQ(kNumFiles, NumTableFilesAtLevel(1));
// Compression dictionary blocks are preloaded.
CheckCacheCountersForCompressionDict(
options, kNumFiles /* expected_compression_dict_misses */,
0 /* expected_compression_dict_hits */,
kNumFiles /* expected_compression_dict_inserts */);
// Seek to a key in a file. It should cause the SST's dictionary meta-block
// to be read.
RecordCacheCounters(options);
RecordCacheCountersForCompressionDict(options);
ReadOptions read_options;
ASSERT_NE("NOT_FOUND", Get(Key(kNumFiles * kNumEntriesPerFile - 1)));
// Two block hits: index and dictionary since they are prefetched
// One block missed/added: data block
CheckCacheCounters(options, 1 /* expected_misses */, 2 /* expected_hits */,
1 /* expected_inserts */, 0 /* expected_failures */);
CheckCacheCountersForCompressionDict(
options, 0 /* expected_compression_dict_misses */,
1 /* expected_compression_dict_hits */,
0 /* expected_compression_dict_inserts */);
}
}
+2 -3
View File
@@ -856,8 +856,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
write_group, current_sequence, column_family_memtables_.get(),
&flush_scheduler_, &trim_history_scheduler_,
write_options.ignore_missing_column_families,
0 /*recovery_log_number*/, this, parallel, seq_per_batch_,
batch_per_txn_);
0 /*recovery_log_number*/, this, seq_per_batch_, batch_per_txn_);
} else {
write_group.last_sequence = last_sequence;
write_thread_.LaunchParallelMemTableWriters(&write_group);
@@ -1115,7 +1114,7 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
memtable_write_group, w.sequence, column_family_memtables_.get(),
&flush_scheduler_, &trim_history_scheduler_,
write_options.ignore_missing_column_families, 0 /*log_number*/, this,
false /*concurrent_memtable_writes*/, seq_per_batch_, batch_per_txn_);
seq_per_batch_, batch_per_txn_);
if (memtable_write_group.status
.ok()) { // Don't publish a partial batch write
versions_->SetLastSequence(memtable_write_group.last_sequence);
+90
View File
@@ -424,6 +424,96 @@ TEST_F(DBMemTableTest, IntegrityChecks) {
ASSERT_FALSE(iter->Valid());
}
}
TEST_F(DBMemTableTest, VectorConcurrentInsert) {
Options options;
options.create_if_missing = true;
options.create_missing_column_families = true;
options.allow_concurrent_memtable_write = true;
options.memtable_factory.reset(new VectorRepFactory());
DestroyAndReopen(options);
CreateAndReopenWithCF({"cf1"}, options);
// Multi-threaded writes
{
WriteOptions write_options;
std::vector<port::Thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back([&, i]() {
int start = i * 100;
int end = start + 100;
WriteBatch batch;
for (int j = start; j < end; ++j) {
ASSERT_OK(
batch.Put(handles_[0], Key(j), "value" + std::to_string(j)));
}
ASSERT_OK(db_->Write(write_options, &batch));
});
}
for (auto& t : threads) {
t.join();
}
std::unique_ptr<Iterator> iter(
db_->NewIterator(ReadOptions(), handles_[0]));
iter->SeekToFirst();
for (int i = 0; i < 1000; ++i) {
ASSERT_TRUE(iter->Valid());
ASSERT_EQ(iter->key().ToString(), Key(i));
ASSERT_EQ(iter->value().ToString(), "value" + std::to_string(i));
iter->Next();
}
ASSERT_FALSE(iter->Valid());
ASSERT_OK(iter->status());
}
// Multi-threaded writes, multi CF
{
WriteOptions write_options;
std::vector<port::Thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back([&, i]() {
int start = i * 100;
int end = start + 100;
WriteBatch batch;
for (int j = start; j < end; ++j) {
ASSERT_OK(batch.Put(handles_[0], Key(j), "CF0" + std::to_string(j)));
ASSERT_OK(batch.Put(handles_[1], Key(j), "CF1" + std::to_string(j)));
}
ASSERT_OK(db_->Write(write_options, &batch));
});
}
for (auto& t : threads) {
t.join();
}
std::unique_ptr<Iterator> iter0(
db_->NewIterator(ReadOptions(), handles_[0]));
std::unique_ptr<Iterator> iter1(
db_->NewIterator(ReadOptions(), handles_[1]));
iter0->SeekToFirst();
iter1->SeekToFirst();
for (int i = 0; i < 1000; ++i) {
ASSERT_TRUE(iter0->Valid());
ASSERT_EQ(iter0->key().ToString(), Key(i));
ASSERT_EQ(iter0->value().ToString(), "CF0" + std::to_string(i));
iter0->Next();
ASSERT_TRUE(iter1->Valid());
ASSERT_EQ(iter1->key().ToString(), Key(i));
ASSERT_EQ(iter1->value().ToString(), "CF1" + std::to_string(i));
iter1->Next();
}
ASSERT_FALSE(iter0->Valid());
ASSERT_OK(iter0->status());
ASSERT_FALSE(iter1->Valid());
ASSERT_OK(iter1->status());
}
ASSERT_OK(Flush(0));
ASSERT_OK(Flush(1));
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+1 -2
View File
@@ -6209,8 +6209,7 @@ TEST_F(DBTest, L0L1L2AndUpHitCounter) {
}
TEST_F(DBTest, EncodeDecompressedBlockSizeTest) {
bool& allow_unsupported_fv =
BlockBasedTableFactory::AllowUnsupportedFormatVersion();
bool& allow_unsupported_fv = TEST_AllowUnsupportedFormatVersion();
SaveAndRestore guard(&allow_unsupported_fv);
ASSERT_FALSE(allow_unsupported_fv);
+349 -79
View File
@@ -1888,49 +1888,45 @@ TEST_F(DBTest2, RoundRobinManager) {
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");
std::vector<std::string> values;
for (bool use_wrapper : {true}) {
SCOPED_TRACE((use_wrapper ? "With " : "No ") + std::string("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);
Options options = CurrentOptions();
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;
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);
// 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);
}
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");
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");
}
}
}
@@ -1939,54 +1935,49 @@ 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");
std::vector<std::string> values;
for (bool use_wrapper : {true}) {
SCOPED_TRACE((use_wrapper ? "With " : "No ") + std::string("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);
Options options = CurrentOptions();
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;
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);
// 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);
}
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");
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
@@ -2000,6 +1991,7 @@ TEST_F(DBTest2, CompressionManagerWrapper) {
struct MyCompressor : public CompressorWrapper {
using CompressorWrapper::CompressorWrapper;
const char* Name() const override { return "MyCompressor"; }
Status CompressBlock(Slice uncompressed_data,
std::string* compressed_output,
@@ -2026,7 +2018,7 @@ TEST_F(DBTest2, CompressionManagerWrapper) {
};
struct MyManager : public CompressionManagerWrapper {
using CompressionManagerWrapper::CompressionManagerWrapper;
const char* Name() const override { return wrapped_->Name(); }
const char* Name() const override { return "MyManager"; }
std::unique_ptr<Compressor> GetCompressorForSST(
const FilterBuildingContext& context, const CompressionOptions& opts,
CompressionType preferred) override {
@@ -2102,6 +2094,284 @@ TEST_F(DBTest2, CompressionManagerWrapper) {
}
}
TEST_F(DBTest2, CompressionManagerCustomCompression) {
// Test that we can use a custom CompressionManager to implement custom
// compression algorithms, and that there are appropriate schema guard rails
// to ensure data is not processed by the wrong algorithm.
using Compressor8A = test::CompressorCustomAlg<kCustomCompression8A>;
using Compressor8B = test::CompressorCustomAlg<kCustomCompression8B>;
using Compressor8C = test::CompressorCustomAlg<kCustomCompression8C>;
if (!Compressor8A::Supported() || !LZ4_Supported()) {
fprintf(stderr,
"Prerequisite compression library not supported. Skipping\n");
return;
}
class MyManager : public CompressionManager {
public:
explicit MyManager(const char* compat_name) : compat_name_(compat_name) {}
const char* Name() const override { return name_.c_str(); }
const char* CompatibilityName() const override { return compat_name_; }
bool SupportsCompressionType(CompressionType type) const override {
return type == kCustomCompression8A || type == kCustomCompression8B ||
type == kCustomCompression8C ||
GetDefaultBuiltinCompressionManager()->SupportsCompressionType(
type);
}
int used_compressor8A_count_ = 0;
int used_compressor8B_count_ = 0;
int used_compressor8C_count_ = 0;
std::unique_ptr<Compressor> GetCompressor(const CompressionOptions& opts,
CompressionType type) override {
switch (static_cast<unsigned char>(type)) {
case kCustomCompression8A:
used_compressor8A_count_++;
return std::make_unique<Compressor8A>();
case kCustomCompression8B:
used_compressor8B_count_++;
return std::make_unique<Compressor8B>();
case kCustomCompression8C:
used_compressor8C_count_++;
return std::make_unique<Compressor8C>();
// Also support built-in compression algorithms
default:
return GetDefaultBuiltinCompressionManager()->GetCompressor(opts,
type);
}
}
std::shared_ptr<Decompressor> GetDecompressor() override {
return std::make_shared<test::DecompressorCustomAlg>();
}
CompressionType last_specific_decompressor_type_ = kNoCompression;
std::shared_ptr<Decompressor> GetDecompressorForTypes(
const CompressionType* types_begin,
const CompressionType* types_end) override {
assert(types_end > types_begin);
last_specific_decompressor_type_ = *types_begin;
auto decomp = std::make_shared<test::DecompressorCustomAlg>();
decomp->SetAllowedTypes(types_begin, types_end);
return decomp;
}
void AddFriend(const std::shared_ptr<CompressionManager>& mgr) {
friends_[mgr->CompatibilityName()] = mgr;
}
std::shared_ptr<CompressionManager> FindCompatibleCompressionManager(
Slice compatibility_name) override {
std::shared_ptr<CompressionManager> rv =
CompressionManager::FindCompatibleCompressionManager(
compatibility_name);
if (!rv) {
auto it = friends_.find(compatibility_name.ToString());
if (it != friends_.end()) {
return it->second.lock();
}
}
return rv;
}
private:
const char* compat_name_;
std::string name_;
// weak_ptr to avoid cycles
std::map<std::string, std::weak_ptr<CompressionManager>> friends_;
};
for (bool use_dict : {false, true}) {
SCOPED_TRACE(use_dict ? "With dict" : "No dict");
// Although these compression managers are actually compatible, we must
// respect their distinct compatibility names and treat them as incompatible
// (or else risk processing data incorrectly)
// NOTE: these are not registered in ObjectRegistry to test what happens
// when the original CompressionManager might not be available.
auto mgr_foo = std::make_shared<MyManager>("Foo");
auto mgr_bar = std::make_shared<MyManager>("Bar");
// And this one claims to be fully compatible with the built-in compression
// manager when it's not fully compatible (for custom CompressionTypes)
auto mgr_claim_compatible = std::make_shared<MyManager>("BuiltinV2");
constexpr uint16_t kValueSize = 10000;
Options options = CurrentOptions();
options.level0_file_num_compaction_trigger = 20;
BlockBasedTableOptions bbto;
bbto.enable_index_compression = false;
bbto.format_version = 6; // Before custom compression alg support
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
// Claims not to use custom compression (and doesn't unless setting a custom
// CompressionType)
options.compression_manager = mgr_claim_compatible;
// Use a built-in compression type with dictionary support
options.compression = kLZ4Compression;
options.compression_opts.max_dict_bytes = kValueSize / 2;
DestroyAndReopen(options);
Random rnd(404);
std::string value;
ASSERT_OK(
Put("a", test::CompressibleString(&rnd, 0.1, kValueSize, &value)));
ASSERT_OK(Flush());
// That data should be readable without access to the original compression
// manager, because it used the built-in CompatibilityName and a built-in
// CompressionType
options.compression_manager = nullptr;
Reopen(options);
ASSERT_EQ(Get("a"), value);
// Verify it was compressed
Range r = {"a", "a0"};
TablePropertiesCollection tables_properties;
ASSERT_OK(db_->GetPropertiesOfTablesInRange(db_->DefaultColumnFamily(), &r,
1, &tables_properties));
ASSERT_EQ(tables_properties.size(), 1U);
EXPECT_LT(tables_properties.begin()->second->data_size, kValueSize / 2);
EXPECT_EQ(tables_properties.begin()->second->compression_name, "LZ4");
// Disallow setting a custom CompressionType with a CompressionManager
// claiming to be built-in compatible.
options.compression_manager = mgr_claim_compatible;
options.compression = kCustomCompression8A;
ASSERT_EQ(TryReopen(options).code(), Status::Code::kInvalidArgument);
options.compression_manager = nullptr;
options.compression = kCustomCompressionFE;
ASSERT_EQ(TryReopen(options).code(), Status::Code::kInvalidArgument);
options.compression =
static_cast<CompressionType>(kLastBuiltinCompression + 1);
ASSERT_EQ(TryReopen(options).code(), Status::Code::kInvalidArgument);
// Custom compression schema (different CompatibilityName) not supported
// before format_version=7
options.compression_manager = mgr_foo;
options.compression = kLZ4Compression;
ASSERT_EQ(TryReopen(options).code(), Status::Code::kInvalidArgument);
// TODO: eliminate this hack when format_version=7 is published
SaveAndRestore guard(&TEST_AllowUnsupportedFormatVersion(), true);
// Set new format version
bbto.format_version = 7;
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
// Custom compression type not supported with built-in schema name, even
// with format_version=7
options.compression_manager = mgr_claim_compatible;
options.compression = kCustomCompression8B;
ASSERT_EQ(TryReopen(options).code(), Status::Code::kInvalidArgument);
// Using a built-in compression type with fv=7 but named custom schema
options.compression_manager = mgr_foo;
options.compression = kLZ4Compression;
Reopen(options);
ASSERT_OK(
Put("b", test::CompressibleString(&rnd, 0.1, kValueSize, &value)));
ASSERT_OK(Flush());
ASSERT_EQ(NumTableFilesAtLevel(0), 2);
ASSERT_EQ(Get("b"), value);
// Verify it was compressed with snappy
r = {"b", "b0"};
tables_properties.clear();
ASSERT_OK(db_->GetPropertiesOfTablesInRange(db_->DefaultColumnFamily(), &r,
1, &tables_properties));
ASSERT_EQ(tables_properties.size(), 1U);
EXPECT_LT(tables_properties.begin()->second->data_size, kValueSize / 2);
// Uses new format for "compression_name" property
EXPECT_EQ(tables_properties.begin()->second->compression_name, "Foo;04;");
EXPECT_EQ(mgr_foo->last_specific_decompressor_type_, kLZ4Compression);
// Custom compression type
options.compression = kCustomCompression8A;
Reopen(options);
ASSERT_OK(
Put("c", test::CompressibleString(&rnd, 0.1, kValueSize, &value)));
EXPECT_EQ(mgr_foo->used_compressor8A_count_, 0);
ASSERT_OK(Flush());
ASSERT_EQ(NumTableFilesAtLevel(0), 3);
ASSERT_EQ(Get("c"), value);
EXPECT_EQ(mgr_foo->used_compressor8A_count_, 1);
// Verify it was compressed with custom format
r = {"c", "c0"};
tables_properties.clear();
ASSERT_OK(db_->GetPropertiesOfTablesInRange(db_->DefaultColumnFamily(), &r,
1, &tables_properties));
ASSERT_EQ(tables_properties.size(), 1U);
EXPECT_LT(tables_properties.begin()->second->data_size, kValueSize / 2);
EXPECT_EQ(tables_properties.begin()->second->compression_name, "Foo;8A;");
EXPECT_EQ(mgr_foo->last_specific_decompressor_type_, kCustomCompression8A);
// Also dynamically changeable, because the compression manager will respect
// the current setting as reported under the legacy logic
ASSERT_OK(dbfull()->SetOptions({{"compression", "kLZ4Compression"}}));
ASSERT_OK(
Put("d", test::CompressibleString(&rnd, 0.1, kValueSize, &value)));
ASSERT_OK(Flush());
ASSERT_EQ(NumTableFilesAtLevel(0), 4);
ASSERT_EQ(Get("d"), value);
// Verify it was compressed with snappy
r = {"d", "d0"};
tables_properties.clear();
ASSERT_OK(db_->GetPropertiesOfTablesInRange(db_->DefaultColumnFamily(), &r,
1, &tables_properties));
ASSERT_EQ(tables_properties.size(), 1U);
EXPECT_LT(tables_properties.begin()->second->data_size, kValueSize / 2);
EXPECT_EQ(tables_properties.begin()->second->compression_name, "Foo;04;");
EXPECT_EQ(mgr_foo->last_specific_decompressor_type_, kLZ4Compression);
// Dynamically changeable to custom compressions also
ASSERT_OK(dbfull()->SetOptions({{"compression", "kCustomCompression8B"}}));
ASSERT_OK(
Put("e", test::CompressibleString(&rnd, 0.1, kValueSize, &value)));
ASSERT_OK(Flush());
ASSERT_EQ(NumTableFilesAtLevel(0), 5);
ASSERT_EQ(Get("e"), value);
// Verify it was compressed with custom format
r = {"e", "e0"};
tables_properties.clear();
ASSERT_OK(db_->GetPropertiesOfTablesInRange(db_->DefaultColumnFamily(), &r,
1, &tables_properties));
ASSERT_EQ(tables_properties.size(), 1U);
EXPECT_LT(tables_properties.begin()->second->data_size, kValueSize / 2);
EXPECT_EQ(tables_properties.begin()->second->compression_name, "Foo;8B;");
EXPECT_EQ(mgr_foo->last_specific_decompressor_type_, kCustomCompression8B);
// Fails to re-open with incompatible compression manager (can't find
// compression manager Foo because it's not registered nor known by Bar)
options.compression_manager = mgr_bar;
options.compression = kLZ4Compression;
ASSERT_EQ(TryReopen(options).code(), Status::Code::kNotSupported);
// But should re-open if we make Bar aware of the Foo compression manager
mgr_bar->AddFriend(mgr_foo);
Reopen(options);
// Can still read everything
ASSERT_EQ(Get("a").size(), kValueSize);
ASSERT_EQ(Get("b").size(), kValueSize);
ASSERT_EQ(Get("c").size(), kValueSize);
ASSERT_EQ(Get("d").size(), kValueSize);
ASSERT_EQ(Get("e").size(), kValueSize);
// TODO: mix of compatibility names in same DB
// TODO: test old version of a compression manager unable to read a
// compression type
// TODO: test getting compression manager from object registry
}
}
class CompactionStallTestListener : public EventListener {
public:
CompactionStallTestListener()
+93 -29
View File
@@ -16,6 +16,7 @@
#include "test_util/testharness.h"
#include "test_util/testutil.h"
#include "util/defer.h"
#include "util/file_checksum_helper.h"
#include "util/random.h"
#include "utilities/fault_injection_env.h"
@@ -346,7 +347,8 @@ class ChecksumVerifyHelper {
Status GetSingleFileChecksumAndFuncName(
const std::string& file_path, std::string* file_checksum,
std::string* file_checksum_func_name) {
std::string* file_checksum_func_name,
const std::string& requested_func_name = {}) {
Status s;
EnvOptions soptions;
std::unique_ptr<SequentialFile> file_reader;
@@ -364,6 +366,8 @@ class ChecksumVerifyHelper {
return Status::OK();
} else {
FileChecksumGenContext gen_context;
gen_context.file_name = file_path;
gen_context.requested_checksum_func_name = requested_func_name;
std::unique_ptr<FileChecksumGenerator> file_checksum_gen =
file_checksum_gen_factory->CreateFileChecksumGenerator(gen_context);
*file_checksum_func_name = file_checksum_gen->Name();
@@ -439,10 +443,50 @@ TEST_F(ExternalSSTFileBasicTest, BasicWithFileChecksumCrc32c) {
DestroyAndRecreateExternalSSTFilesDir();
}
namespace {
class VariousFileChecksumGenerator : public FileChecksumGenCrc32c {
public:
explicit VariousFileChecksumGenerator(const std::string& name)
: FileChecksumGenCrc32c({}), name_(name) {}
const char* Name() const override { return name_.c_str(); }
std::string GetChecksum() const override {
return FileChecksumGenCrc32c::GetChecksum() + "_" + name_;
}
private:
const std::string name_;
};
class VariousFileChecksumGenFactory : public FileChecksumGenFactory {
public:
std::unique_ptr<FileChecksumGenerator> CreateFileChecksumGenerator(
const FileChecksumGenContext& context) override {
static RelaxedAtomic<int> counter{0};
if (Slice(context.requested_checksum_func_name).starts_with("Various")) {
return std::make_unique<VariousFileChecksumGenerator>(
context.requested_checksum_func_name);
} else if (context.requested_checksum_func_name.empty()) {
// Lacking a specific request, use a different function name for each
// result.
return std::make_unique<VariousFileChecksumGenerator>(
"Various" + std::to_string(counter.FetchAddRelaxed(1)));
} else {
return nullptr;
}
}
static const char* kClassName() { return "VariousFileChecksumGenFactory"; }
const char* Name() const override { return kClassName(); }
};
} // namespace
TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
Options old_options = CurrentOptions();
Options options = CurrentOptions();
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
options.file_checksum_gen_factory =
std::make_shared<VariousFileChecksumGenFactory>();
const ImmutableCFOptions ioptions(options);
ChecksumVerifyHelper checksum_helper(options);
@@ -463,7 +507,8 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
ASSERT_EQ(file1_info.largest_key, Key(1099));
std::string file_checksum1, file_checksum_func_name1;
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
file1, &file_checksum1, &file_checksum_func_name1));
file1, &file_checksum1, &file_checksum_func_name1,
file1_info.file_checksum_func_name));
ASSERT_EQ(file1_info.file_checksum, file_checksum1);
ASSERT_EQ(file1_info.file_checksum_func_name, file_checksum_func_name1);
@@ -482,7 +527,8 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
ASSERT_EQ(file2_info.largest_key, Key(1299));
std::string file_checksum2, file_checksum_func_name2;
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
file2, &file_checksum2, &file_checksum_func_name2));
file2, &file_checksum2, &file_checksum_func_name2,
file2_info.file_checksum_func_name));
ASSERT_EQ(file2_info.file_checksum, file_checksum2);
ASSERT_EQ(file2_info.file_checksum_func_name, file_checksum_func_name2);
@@ -501,7 +547,8 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
ASSERT_EQ(file3_info.largest_key, Key(1499));
std::string file_checksum3, file_checksum_func_name3;
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
file3, &file_checksum3, &file_checksum_func_name3));
file3, &file_checksum3, &file_checksum_func_name3,
file3_info.file_checksum_func_name));
ASSERT_EQ(file3_info.file_checksum, file_checksum3);
ASSERT_EQ(file3_info.file_checksum_func_name, file_checksum_func_name3);
@@ -520,7 +567,8 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
ASSERT_EQ(file4_info.largest_key, Key(1799));
std::string file_checksum4, file_checksum_func_name4;
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
file4, &file_checksum4, &file_checksum_func_name4));
file4, &file_checksum4, &file_checksum_func_name4,
file4_info.file_checksum_func_name));
ASSERT_EQ(file4_info.file_checksum, file_checksum4);
ASSERT_EQ(file4_info.file_checksum_func_name, file_checksum_func_name4);
@@ -539,7 +587,8 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
ASSERT_EQ(file5_info.largest_key, Key(1999));
std::string file_checksum5, file_checksum_func_name5;
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
file5, &file_checksum5, &file_checksum_func_name5));
file5, &file_checksum5, &file_checksum_func_name5,
file5_info.file_checksum_func_name));
ASSERT_EQ(file5_info.file_checksum, file_checksum5);
ASSERT_EQ(file5_info.file_checksum_func_name, file_checksum_func_name5);
@@ -558,7 +607,8 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
ASSERT_EQ(file6_info.largest_key, Key(2199));
std::string file_checksum6, file_checksum_func_name6;
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
file6, &file_checksum6, &file_checksum_func_name6));
file6, &file_checksum6, &file_checksum_func_name6,
file6_info.file_checksum_func_name));
ASSERT_EQ(file6_info.file_checksum, file_checksum6);
ASSERT_EQ(file6_info.file_checksum_func_name, file_checksum_func_name6);
@@ -628,18 +678,23 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
}
ASSERT_OK(env_->FileExists(file2));
// Enable verify_file_checksum option
// No checksum information is provided, generate it when ingesting
std::vector<std::string> checksum, checksum_func;
s = AddFileWithFileChecksum({file3}, checksum, checksum_func, true, false,
false, false);
// Enable verify_file_checksum option. No checksum information is provided,
// so it is generated when ingesting. The configured checksum factory will
// use a different function than before.
s = AddFileWithFileChecksum({file3}, {}, {}, true, false, false, false);
ASSERT_OK(s) << s.ToString();
std::vector<LiveFileMetaData> live_files2;
dbfull()->GetLiveFilesMetaData(&live_files2);
for (const auto& f : live_files2) {
if (set1.find(f.name) == set1.end()) {
ASSERT_EQ(f.file_checksum, file_checksum3);
ASSERT_EQ(f.file_checksum_func_name, file_checksum_func_name3);
// Recomputed checksum, different function
EXPECT_NE(f.file_checksum_func_name, file_checksum_func_name3);
std::string cur_checksum3, cur_checksum_func_name3;
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
dbname_ + f.name, &cur_checksum3, &cur_checksum_func_name3,
f.file_checksum_func_name));
EXPECT_EQ(f.file_checksum, cur_checksum3);
EXPECT_EQ(f.file_checksum_func_name, cur_checksum_func_name3);
set1.insert(f.name);
}
}
@@ -653,8 +708,9 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
ASSERT_NOK(s) << s.ToString();
// Does not enable verify_file_checksum options
// Checksum function name matches, store the checksum being ingested.
s = AddFileWithFileChecksum({file4}, {"asd"}, {file_checksum_func_name4},
// Checksum function name is recognized, so store the checksum being ingested.
std::string file_checksum_func_name4alt = "VariousABCD";
s = AddFileWithFileChecksum({file4}, {"asd"}, {file_checksum_func_name4alt},
false, false, false, false);
ASSERT_OK(s) << s.ToString();
std::vector<LiveFileMetaData> live_files3;
@@ -663,7 +719,7 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
if (set1.find(f.name) == set1.end()) {
ASSERT_FALSE(f.file_checksum == file_checksum4);
ASSERT_EQ(f.file_checksum, "asd");
ASSERT_EQ(f.file_checksum_func_name, file_checksum_func_name4);
ASSERT_EQ(f.file_checksum_func_name, file_checksum_func_name4alt);
set1.insert(f.name);
}
}
@@ -672,7 +728,8 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
// enable verify_file_checksum options, DB enable checksum, and enable
// write_global_seq. So the checksum stored is different from the one
// ingested due to the sequence number changes.
// ingested due to the sequence number changes. The checksum function name
// may also change since the checksum is recomputed.
s = AddFileWithFileChecksum({file5}, {file_checksum5},
{file_checksum_func_name5}, true, false, false,
true);
@@ -681,11 +738,14 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
dbfull()->GetLiveFilesMetaData(&live_files4);
for (const auto& f : live_files4) {
if (set1.find(f.name) == set1.end()) {
// Recomputed checksum, different function
EXPECT_NE(f.file_checksum_func_name, file_checksum_func_name5);
std::string cur_checksum5, cur_checksum_func_name5;
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
dbname_ + f.name, &cur_checksum5, &cur_checksum_func_name5));
ASSERT_EQ(f.file_checksum, cur_checksum5);
ASSERT_EQ(f.file_checksum_func_name, file_checksum_func_name5);
dbname_ + f.name, &cur_checksum5, &cur_checksum_func_name5,
f.file_checksum_func_name));
EXPECT_EQ(f.file_checksum, cur_checksum5);
EXPECT_EQ(f.file_checksum_func_name, cur_checksum_func_name5);
set1.insert(f.name);
}
}
@@ -693,18 +753,22 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
ASSERT_OK(env_->FileExists(file5));
// Does not enable verify_file_checksum options and also the ingested file
// checksum information is empty. DB will generate and store the checksum
// in Manifest.
std::vector<std::string> files_c6, files_name6;
s = AddFileWithFileChecksum({file6}, files_c6, files_name6, false, false,
false, false);
// checksum information is empty. DB will generate and store file checksum
// in Manifest, which could be different from the previous invocation.
s = AddFileWithFileChecksum({file6}, {}, {}, false, false, false, false);
ASSERT_OK(s) << s.ToString();
std::vector<LiveFileMetaData> live_files6;
dbfull()->GetLiveFilesMetaData(&live_files6);
for (const auto& f : live_files6) {
if (set1.find(f.name) == set1.end()) {
ASSERT_EQ(f.file_checksum, file_checksum6);
ASSERT_EQ(f.file_checksum_func_name, file_checksum_func_name6);
// Recomputed checksum, different function
EXPECT_NE(f.file_checksum_func_name, file_checksum_func_name6);
std::string cur_checksum6, cur_checksum_func_name6;
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
dbname_ + f.name, &cur_checksum6, &cur_checksum_func_name6,
f.file_checksum_func_name));
EXPECT_EQ(f.file_checksum, cur_checksum6);
EXPECT_EQ(f.file_checksum_func_name, cur_checksum_func_name6);
set1.insert(f.name);
}
}
+44 -36
View File
@@ -260,10 +260,6 @@ Status ExternalSstFileIngestionJob::Prepare(
} else {
need_generate_file_checksum_ = true;
}
FileChecksumGenContext gen_context;
std::unique_ptr<FileChecksumGenerator> file_checksum_gen =
db_options_.file_checksum_gen_factory->CreateFileChecksumGenerator(
gen_context);
std::vector<std::string> generated_checksums;
std::vector<std::string> generated_checksum_func_names;
// Step 1: generate the checksum for ingested sst file.
@@ -271,7 +267,9 @@ Status ExternalSstFileIngestionJob::Prepare(
for (size_t i = 0; i < files_to_ingest_.size(); i++) {
std::string generated_checksum;
std::string generated_checksum_func_name;
std::string requested_checksum_func_name;
std::string requested_checksum_func_name =
i < files_checksum_func_names.size() ? files_checksum_func_names[i]
: "";
// TODO: rate limit file reads for checksum calculation during file
// ingestion.
// TODO: plumb Env::IOActivity
@@ -314,40 +312,50 @@ Status ExternalSstFileIngestionJob::Prepare(
if (files_checksum_func_names[i] !=
generated_checksum_func_names[i]) {
status = Status::InvalidArgument(
"Checksum function name does not match with the checksum "
"function name of this DB");
ROCKS_LOG_WARN(
db_options_.info_log,
"Sst file checksum verification of file: %s failed: %s",
external_files_paths[i].c_str(), status.ToString().c_str());
"DB file checksum gen factory " +
std::string(db_options_.file_checksum_gen_factory->Name()) +
" generated checksum function name " +
generated_checksum_func_names[i] + " for file " +
external_files_paths[i] +
" which does not match requested/provided " +
files_checksum_func_names[i]);
break;
}
if (files_checksums[i] != generated_checksums[i]) {
status = Status::Corruption(
"Ingested checksum does not match with the generated "
"checksum");
ROCKS_LOG_WARN(
db_options_.info_log,
"Sst file checksum verification of file: %s failed: %s",
files_to_ingest_[i].internal_file_path.c_str(),
status.ToString().c_str());
"Checksum verification mismatch for ingestion file " +
external_files_paths[i] + " using function " +
generated_checksum_func_names[i] + ". Expected: " +
Slice(files_checksums[i]).ToString(/*hex=*/true) +
" Computed: " +
Slice(generated_checksums[i]).ToString(/*hex=*/true));
break;
}
}
} else {
// If verify_file_checksum is not enabled, we only verify the
// checksum function name. If it does not match, fail the ingestion.
// If matches, we trust the ingested checksum information and store
// in the Manifest.
// If verify_file_checksum is not enabled, we only verify the factory
// recognizes the checksum function name. If it does not match, fail
// the ingestion. If matches, we trust the ingested checksum
// information and store in the Manifest.
for (size_t i = 0; i < files_to_ingest_.size(); i++) {
if (files_checksum_func_names[i] != file_checksum_gen->Name()) {
FileChecksumGenContext gen_context;
gen_context.file_name = files_to_ingest_[i].internal_file_path;
gen_context.requested_checksum_func_name =
files_checksum_func_names[i];
auto file_checksum_gen =
db_options_.file_checksum_gen_factory
->CreateFileChecksumGenerator(gen_context);
if (file_checksum_gen == nullptr ||
files_checksum_func_names[i] != file_checksum_gen->Name()) {
status = Status::InvalidArgument(
"Checksum function name does not match with the checksum "
"function name of this DB");
ROCKS_LOG_WARN(
db_options_.info_log,
"Sst file checksum verification of file: %s failed: %s",
external_files_paths[i].c_str(), status.ToString().c_str());
"Checksum function name " + files_checksum_func_names[i] +
" for file " + external_files_paths[i] +
" not recognized by DB checksum gen factory" +
db_options_.file_checksum_gen_factory->Name() +
(file_checksum_gen ? (" Returned function " +
std::string(file_checksum_gen->Name()))
: ""));
break;
}
files_to_ingest_[i].file_checksum = files_checksums[i];
@@ -362,12 +370,11 @@ Status ExternalSstFileIngestionJob::Prepare(
status = Status::InvalidArgument(
"The checksum information of ingested sst files are nonempty and "
"the size of checksums or the size of the checksum function "
"names "
"does not match with the number of ingested sst files");
ROCKS_LOG_WARN(
db_options_.info_log,
"The ingested sst files checksum information is incomplete: %s",
status.ToString().c_str());
"names does not match with the number of ingested sst files");
}
if (!status.ok()) {
ROCKS_LOG_WARN(db_options_.info_log, "Ingestion failed: %s",
status.ToString().c_str());
}
}
}
@@ -834,7 +841,8 @@ Status ExternalSstFileIngestionJob::ResetTableReader(
ro,
TableReaderOptions(
cfd_->ioptions(), sv->mutable_cf_options.prefix_extractor,
env_options_, cfd_->internal_comparator(),
sv->mutable_cf_options.compression_manager.get(), env_options_,
cfd_->internal_comparator(),
sv->mutable_cf_options.block_protection_bytes_per_key,
/*skip_filters*/ false, /*immortal*/ false,
/*force_direct_prefetch*/ false, /*level*/ -1,
+2 -1
View File
@@ -324,7 +324,8 @@ Status ImportColumnFamilyJob::GetIngestedFileInfo(
status = sv->mutable_cf_options.table_factory->NewTableReader(
TableReaderOptions(
cfd_->ioptions(), sv->mutable_cf_options.prefix_extractor,
env_options_, cfd_->internal_comparator(),
sv->mutable_cf_options.compression_manager.get(), env_options_,
cfd_->internal_comparator(),
sv->mutable_cf_options.block_protection_bytes_per_key,
/*skip_filters*/ false, /*immortal*/ false,
/*force_direct_prefetch*/ false, /*level*/ -1,
+2 -1
View File
@@ -196,10 +196,11 @@ bool MemTable::ShouldFlushNow() {
// allocate one more block.
const double kAllowOverAllocationRatio = 0.6;
// range deletion use skip list which allocates all memeory through `arena_`
assert(range_del_table_->ApproximateMemoryUsage() == 0);
// If arena still have room for new block allocation, we can safely say it
// shouldn't flush.
auto allocated_memory = table_->ApproximateMemoryUsage() +
range_del_table_->ApproximateMemoryUsage() +
arena_.MemoryAllocatedBytes();
approximate_memory_usage_.store(allocated_memory, std::memory_order_relaxed);
+5
View File
@@ -688,6 +688,7 @@ class MemTable final : public ReadOnlyMemTable {
// Update counters and flush status after inserting a whole write batch
// Used in concurrent memtable inserts.
void BatchPostProcess(const MemTablePostProcessInfo& update_counters) {
table_->BatchPostProcess();
num_entries_.fetch_add(update_counters.num_entries,
std::memory_order_relaxed);
data_size_.fetch_add(update_counters.data_size, std::memory_order_relaxed);
@@ -698,6 +699,10 @@ class MemTable final : public ReadOnlyMemTable {
if (update_counters.num_range_deletes > 0) {
num_range_deletes_.fetch_add(update_counters.num_range_deletes,
std::memory_order_relaxed);
// noop for skip-list memtable
// Besides correctness test in stress test, memtable flush record count
// check will catch this if it were not noop.
// range_del_table_->BatchPostProcess();
}
UpdateFlushState();
}
+2 -1
View File
@@ -146,7 +146,8 @@ Status TableCache::GetTableReader(
s = mutable_cf_options.table_factory->NewTableReader(
ro,
TableReaderOptions(
ioptions_, mutable_cf_options.prefix_extractor, file_options,
ioptions_, mutable_cf_options.prefix_extractor,
mutable_cf_options.compression_manager.get(), file_options,
internal_comparator,
mutable_cf_options.block_protection_bytes_per_key, skip_filters,
immortal_tables_, false /* force_direct_prefetch */, level,
+2 -6
View File
@@ -551,9 +551,6 @@ Status WriteBatchInternal::Iterate(const WriteBatch* wb,
if (LIKELY(!s.IsTryAgain())) {
last_was_try_again = false;
tag = 0;
column_family = 0; // default
s = ReadRecordFromWriteBatch(&input, &tag, &column_family, &key, &value,
&blob, &xid, &write_unix_time);
if (!s.ok()) {
@@ -1897,7 +1894,6 @@ Status WriteBatch::VerifyChecksum() const {
// ReadRecordFromWriteBatch
key.clear();
value.clear();
column_family = 0;
s = ReadRecordFromWriteBatch(&input, &tag, &column_family, &key, &value,
&blob, &xid, /*write_unix_time=*/nullptr);
if (!s.ok()) {
@@ -3214,11 +3210,11 @@ Status WriteBatchInternal::InsertInto(
ColumnFamilyMemTables* memtables, FlushScheduler* flush_scheduler,
TrimHistoryScheduler* trim_history_scheduler,
bool ignore_missing_column_families, uint64_t recovery_log_number, DB* db,
bool concurrent_memtable_writes, bool seq_per_batch, bool batch_per_txn) {
bool seq_per_batch, bool batch_per_txn) {
MemTableInserter inserter(
sequence, memtables, flush_scheduler, trim_history_scheduler,
ignore_missing_column_families, recovery_log_number, db,
concurrent_memtable_writes, nullptr /* prot_info */,
/*concurrent_memtable_writes=*/false, nullptr /* prot_info */,
nullptr /*has_valid_writes*/, seq_per_batch, batch_per_txn);
for (auto w : write_group) {
if (w->CallbackFailed()) {
+5 -4
View File
@@ -185,18 +185,19 @@ class WriteBatchInternal {
// If flush_scheduler is non-null, it will be invoked if the memtable
// should be flushed.
//
// Under concurrent use, the caller is responsible for making sure that
// the memtables object itself is thread-local.
// This overload is for non-concurrent insertion only.
static Status InsertInto(
WriteThread::WriteGroup& write_group, SequenceNumber sequence,
ColumnFamilyMemTables* memtables, FlushScheduler* flush_scheduler,
TrimHistoryScheduler* trim_history_scheduler,
bool ignore_missing_column_families = false, uint64_t log_number = 0,
DB* db = nullptr, bool concurrent_memtable_writes = false,
bool seq_per_batch = false, bool batch_per_txn = true);
DB* db = nullptr, bool seq_per_batch = false, bool batch_per_txn = true);
// Convenience form of InsertInto when you have only one batch
// next_seq returns the seq after last sequence number used in MemTable insert
//
// Under concurrent use, the caller is responsible for making sure that
// the memtables object itself is thread-local.
static Status InsertInto(
const WriteBatch* batch, ColumnFamilyMemTables* memtables,
FlushScheduler* flush_scheduler,
@@ -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).
#pragma once
#include "test_util/testutil.h"
namespace ROCKSDB_NAMESPACE {
class DbStressCustomCompressionManager : public CompressionManager {
public:
const char* Name() const override {
return "DbStressCustomCompressionManager";
}
const char* CompatibilityName() const override { return "DbStressCustom1"; }
bool SupportsCompressionType(CompressionType type) const override {
return default_->SupportsCompressionType(type) ||
type == kCustomCompressionAA || type == kCustomCompressionAB ||
type == kCustomCompressionAC;
}
std::unique_ptr<Compressor> GetCompressor(const CompressionOptions& opts,
CompressionType type) override {
// db_stress never specifies a custom type, so we randomly use them anyway
// when this compression manager is used.
std::array<CompressionType, 4> choices = {
type, kCustomCompressionAA, kCustomCompressionAB, kCustomCompressionAC};
type = choices[Random::GetTLSInstance()->Uniform(4)];
switch (static_cast<unsigned char>(type)) {
case kCustomCompressionAA:
return std::make_unique<
test::CompressorCustomAlg<kCustomCompressionAA>>();
case kCustomCompressionAB:
return std::make_unique<
test::CompressorCustomAlg<kCustomCompressionAB>>();
case kCustomCompressionAC:
return std::make_unique<
test::CompressorCustomAlg<kCustomCompressionAC>>();
// Also support built-in compression algorithms
default:
return GetDefaultBuiltinCompressionManager()->GetCompressor(opts, type);
}
}
std::shared_ptr<Decompressor> GetDecompressor() override {
return std::make_shared<test::DecompressorCustomAlg>();
}
std::shared_ptr<Decompressor> GetDecompressorForTypes(
const CompressionType* types_begin,
const CompressionType* types_end) override {
auto decomp = std::make_shared<test::DecompressorCustomAlg>();
decomp->SetAllowedTypes(types_begin, types_end);
return decomp;
}
protected:
std::shared_ptr<CompressionManager> default_ =
GetDefaultBuiltinCompressionManager();
};
} // namespace ROCKSDB_NAMESPACE
+29 -23
View File
@@ -11,6 +11,7 @@
#include <ios>
#include <thread>
#include "db_stress_tool/db_stress_compression_manager.h"
#include "db_stress_tool/db_stress_listener.h"
#include "rocksdb/io_status.h"
#include "rocksdb/options.h"
@@ -3412,29 +3413,34 @@ 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;
{
// We must register any compression managers with a custom
// CompatibilityName() so that if it was used in a past invocation but not
// the current invocation, we can still read the SST files requiring it.
static std::once_flag loaded;
std::call_once(loaded, [&]() {
TEST_AllowUnsupportedFormatVersion() = true;
auto& library = *ObjectLibrary::Default();
library.AddFactory<CompressionManager>(
DbStressCustomCompressionManager().CompatibilityName(),
[](const std::string& /*uri*/,
std::unique_ptr<CompressionManager>* guard,
std::string* /*errmsg*/) {
*guard = std::make_unique<DbStressCustomCompressionManager>();
return guard->get();
});
});
}
if (!strcasecmp(FLAGS_compression_manager.c_str(), "custom")) {
options_.compression_manager =
std::make_shared<DbStressCustomCompressionManager>();
} else if (!strcasecmp(FLAGS_compression_manager.c_str(), "mixed")) {
options_.compression_manager = std::make_shared<RoundRobinManager>(
GetDefaultBuiltinCompressionManager());
} else if (!strcasecmp(FLAGS_compression_manager.c_str(), "randommixed")) {
options_.compression_manager =
std::make_shared<RandomMixedCompressionManager>(
GetDefaultBuiltinCompressionManager());
} else if (!strcasecmp(FLAGS_compression_manager.c_str(), "autoskip")) {
options_.compression_manager =
CreateAutoSkipCompressionManager(GetDefaultBuiltinCompressionManager());
+2 -1
View File
@@ -91,7 +91,8 @@ TableReader* NewTableReader(const std::string& sst_file_path,
}
if (s.ok()) {
ImmutableOptions iopts(options, cf_ioptions);
TableReaderOptions t_opt(iopts, /*prefix_extractor=*/nullptr, env_options,
TableReaderOptions t_opt(iopts, /*prefix_extractor=*/nullptr,
/*compression_manager=*/nullptr, env_options,
cf_ioptions.internal_comparator,
0 /* block_protection_bytes_per_key */);
t_opt.largest_seqno = kMaxSequenceNumber;
+96 -20
View File
@@ -55,6 +55,15 @@ class Compressor {
Compressor() = default;
virtual ~Compressor() = default;
// Class name for logging / debugging purposes
virtual const char* Name() const = 0;
// Potentially more elaborate identifier for logging / debugging purposes
virtual std::string GetId() const {
std::string id = Name();
return id;
}
// 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
@@ -228,21 +237,19 @@ class Decompressor {
// 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).
// thread). ReleaseWorkingArea() in not intended to be called directly, but
// used by ManagedWorkingArea.
virtual void ReleaseWorkingArea(WorkingArea* wa) {
// Default implementation: no working area
(void)wa;
assert(wa == nullptr);
}
public:
using ManagedWorkingArea =
ManagedPtr<WorkingArea, Decompressor, &Decompressor::ReleaseWorkingArea>;
@@ -266,11 +273,11 @@ class Decompressor {
// 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)
// Must return OK if and only 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(
@@ -346,22 +353,30 @@ class CompressionManager
// 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.)
// understanding what compression strategy was used.) This name should be
// limited to legal variable names in C++ (alphanumeric and underscores).
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);
// to CreateFromString(). Failure should simply be a matter of "not found" in
// which case nullptr is returned.
virtual std::shared_ptr<CompressionManager> FindCompatibleCompressionManager(
Slice compatibility_name);
// Create a CompressionManager from a string, including built-in
// Create or find 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);
// Will this compression type be used if requested in calling
// GetCompressor/GetCompressorForSST?
virtual bool SupportsCompressionType(CompressionType type) const = 0;
// TODO: function to check compatibility with or sanitize CompressionOptions
// ************************* Compressor creation *********************** //
// Returning nullptr means compression is entirely disabled for the file,
// which is valid at the discretion of the CompressionManager. Returning
@@ -410,6 +425,14 @@ class CompressionManager
// Safe default implementation
return GetDecompressor();
}
// Get a decompressor that is allowed to have support only for the
// CompressionTypes used by the given Compressor.
virtual std::shared_ptr<Decompressor> GetDecompressorForCompressor(
const Compressor& compressor) {
// Reasonable default implementation
return GetDecompressorOptimizeFor(compressor.GetPreferredCompressionType());
}
};
// ************************* Utility wrappers etc. *********************** //
@@ -453,6 +476,51 @@ class CompressorWrapper : public Compressor {
std::unique_ptr<Compressor> wrapped_;
};
class DecompressorWrapper : public Decompressor {
public:
explicit DecompressorWrapper(std::shared_ptr<Decompressor> decompressor)
: wrapped_(std::move(decompressor)) {}
// No copies
DecompressorWrapper(const DecompressorWrapper&) = delete;
DecompressorWrapper& operator=(const DecompressorWrapper&) = delete;
const char* Name() const override { return wrapped_->Name(); }
void ReleaseWorkingArea(WorkingArea* wa) override {
wrapped_->ReleaseWorkingArea(wa);
}
ManagedWorkingArea ObtainWorkingArea(CompressionType preferred) override {
return wrapped_->ObtainWorkingArea(preferred);
}
const Slice& GetSerializedDict() const override {
return wrapped_->GetSerializedDict();
}
Status MaybeCloneForDict(const Slice& serialized_dict,
std::unique_ptr<Decompressor>* out) override {
// NOTE: derived class probably needs to override this to ensure a
// derived wrapper around the new Decompressor
return wrapped_->MaybeCloneForDict(serialized_dict, out);
}
size_t ApproximateOwnedMemoryUsage() const override {
return wrapped_->ApproximateOwnedMemoryUsage();
}
Status ExtractUncompressedSize(Args& args) override {
return wrapped_->ExtractUncompressedSize(args);
}
Status DecompressBlock(const Args& args, char* uncompressed_output) override {
return wrapped_->DecompressBlock(args, uncompressed_output);
}
protected:
std::shared_ptr<Decompressor> wrapped_;
};
// TODO: CompressorBase, for custom compressions
class CompressionManagerWrapper : public CompressionManager {
@@ -465,10 +533,13 @@ class CompressionManagerWrapper : public CompressionManager {
return wrapped_->CompatibilityName();
}
Status FindCompatibleCompressionManager(
Slice compatibility_name,
std::shared_ptr<CompressionManager>* out) override {
return wrapped_->FindCompatibleCompressionManager(compatibility_name, out);
std::shared_ptr<CompressionManager> FindCompatibleCompressionManager(
Slice compatibility_name) override {
return wrapped_->FindCompatibleCompressionManager(compatibility_name);
}
bool SupportsCompressionType(CompressionType type) const override {
return wrapped_->SupportsCompressionType(type);
}
std::unique_ptr<Compressor> GetCompressorForSST(
@@ -497,13 +568,18 @@ class CompressionManagerWrapper : public CompressionManager {
return wrapped_->GetDecompressorForTypes(types_begin, types_end);
}
std::shared_ptr<Decompressor> GetDecompressorForCompressor(
const Compressor& compressor) override {
return wrapped_->GetDecompressorForCompressor(compressor);
}
protected:
std::shared_ptr<CompressionManager> wrapped_;
};
// Compression manager that implements built-in compression strategy. The
// behavior of
// compression_manager=nullptr with this
// behavior of compression_manager=nullptr is essentially equivalent to
// using this compression manager.
const std::shared_ptr<CompressionManager>&
GetDefaultBuiltinCompressionManager();
// Gets CompressionManager designed for the automated compression strategy.
+139 -9
View File
@@ -18,16 +18,146 @@ namespace ROCKSDB_NAMESPACE {
enum CompressionType : unsigned char {
// NOTE: do not change the values of existing entries, as these are
// part of the persistent format on disk.
kNoCompression = 0x0,
kSnappyCompression = 0x1,
kZlibCompression = 0x2,
kBZip2Compression = 0x3,
kLZ4Compression = 0x4,
kLZ4HCCompression = 0x5,
kXpressCompression = 0x6,
kZSTD = 0x7,
kNoCompression = 0x00,
kSnappyCompression = 0x01,
kZlibCompression = 0x02,
kBZip2Compression = 0x03,
kLZ4Compression = 0x04,
kLZ4HCCompression = 0x05,
kXpressCompression = 0x06,
kZSTD = 0x07,
kLastBuiltinCompression = kZSTD,
// TODO: add enum values for user custom compression types
// Reserved for future use: up to 0x7F
// For use by user custom CompressionManagers
kCustomCompression80 = 0x80,
kCustomCompression81 = 0x81,
kCustomCompression82 = 0x82,
kCustomCompression83 = 0x83,
kCustomCompression84 = 0x84,
kCustomCompression85 = 0x85,
kCustomCompression86 = 0x86,
kCustomCompression87 = 0x87,
kCustomCompression88 = 0x88,
kCustomCompression89 = 0x89,
kCustomCompression8A = 0x8A,
kCustomCompression8B = 0x8B,
kCustomCompression8C = 0x8C,
kCustomCompression8D = 0x8D,
kCustomCompression8E = 0x8E,
kCustomCompression8F = 0x8F,
kCustomCompression90 = 0x90,
kCustomCompression91 = 0x91,
kCustomCompression92 = 0x92,
kCustomCompression93 = 0x93,
kCustomCompression94 = 0x94,
kCustomCompression95 = 0x95,
kCustomCompression96 = 0x96,
kCustomCompression97 = 0x97,
kCustomCompression98 = 0x98,
kCustomCompression99 = 0x99,
kCustomCompression9A = 0x9A,
kCustomCompression9B = 0x9B,
kCustomCompression9C = 0x9C,
kCustomCompression9D = 0x9D,
kCustomCompression9E = 0x9E,
kCustomCompression9F = 0x9F,
kCustomCompressionA0 = 0xA0,
kCustomCompressionA1 = 0xA1,
kCustomCompressionA2 = 0xA2,
kCustomCompressionA3 = 0xA3,
kCustomCompressionA4 = 0xA4,
kCustomCompressionA5 = 0xA5,
kCustomCompressionA6 = 0xA6,
kCustomCompressionA7 = 0xA7,
kCustomCompressionA8 = 0xA8,
kCustomCompressionA9 = 0xA9,
kCustomCompressionAA = 0xAA,
kCustomCompressionAB = 0xAB,
kCustomCompressionAC = 0xAC,
kCustomCompressionAD = 0xAD,
kCustomCompressionAE = 0xAE,
kCustomCompressionAF = 0xAF,
kCustomCompressionB0 = 0xB0,
kCustomCompressionB1 = 0xB1,
kCustomCompressionB2 = 0xB2,
kCustomCompressionB3 = 0xB3,
kCustomCompressionB4 = 0xB4,
kCustomCompressionB5 = 0xB5,
kCustomCompressionB6 = 0xB6,
kCustomCompressionB7 = 0xB7,
kCustomCompressionB8 = 0xB8,
kCustomCompressionB9 = 0xB9,
kCustomCompressionBA = 0xBA,
kCustomCompressionBB = 0xBB,
kCustomCompressionBC = 0xBC,
kCustomCompressionBD = 0xBD,
kCustomCompressionBE = 0xBE,
kCustomCompressionBF = 0xBF,
kCustomCompressionC0 = 0xC0,
kCustomCompressionC1 = 0xC1,
kCustomCompressionC2 = 0xC2,
kCustomCompressionC3 = 0xC3,
kCustomCompressionC4 = 0xC4,
kCustomCompressionC5 = 0xC5,
kCustomCompressionC6 = 0xC6,
kCustomCompressionC7 = 0xC7,
kCustomCompressionC8 = 0xC8,
kCustomCompressionC9 = 0xC9,
kCustomCompressionCA = 0xCA,
kCustomCompressionCB = 0xCB,
kCustomCompressionCC = 0xCC,
kCustomCompressionCD = 0xCD,
kCustomCompressionCE = 0xCE,
kCustomCompressionCF = 0xCF,
kCustomCompressionD0 = 0xD0,
kCustomCompressionD1 = 0xD1,
kCustomCompressionD2 = 0xD2,
kCustomCompressionD3 = 0xD3,
kCustomCompressionD4 = 0xD4,
kCustomCompressionD5 = 0xD5,
kCustomCompressionD6 = 0xD6,
kCustomCompressionD7 = 0xD7,
kCustomCompressionD8 = 0xD8,
kCustomCompressionD9 = 0xD9,
kCustomCompressionDA = 0xDA,
kCustomCompressionDB = 0xDB,
kCustomCompressionDC = 0xDC,
kCustomCompressionDD = 0xDD,
kCustomCompressionDE = 0xDE,
kCustomCompressionDF = 0xDF,
kCustomCompressionE0 = 0xE0,
kCustomCompressionE1 = 0xE1,
kCustomCompressionE2 = 0xE2,
kCustomCompressionE3 = 0xE3,
kCustomCompressionE4 = 0xE4,
kCustomCompressionE5 = 0xE5,
kCustomCompressionE6 = 0xE6,
kCustomCompressionE7 = 0xE7,
kCustomCompressionE8 = 0xE8,
kCustomCompressionE9 = 0xE9,
kCustomCompressionEA = 0xEA,
kCustomCompressionEB = 0xEB,
kCustomCompressionEC = 0xEC,
kCustomCompressionED = 0xED,
kCustomCompressionEE = 0xEE,
kCustomCompressionEF = 0xEF,
kCustomCompressionF0 = 0xF0,
kCustomCompressionF1 = 0xF1,
kCustomCompressionF2 = 0xF2,
kCustomCompressionF3 = 0xF3,
kCustomCompressionF4 = 0xF4,
kCustomCompressionF5 = 0xF5,
kCustomCompressionF6 = 0xF6,
kCustomCompressionF7 = 0xF7,
kCustomCompressionF8 = 0xF8,
kCustomCompressionF9 = 0xF9,
kCustomCompressionFA = 0xFA,
kCustomCompressionFB = 0xFB,
kCustomCompressionFC = 0xFC,
kCustomCompressionFD = 0xFD,
kCustomCompressionFE = 0xFE,
// kDisableCompressionOption is used to disable some compression options.
kDisableCompressionOption = 0xff,
+1 -1
View File
@@ -269,7 +269,7 @@ class ExternalTableFactory : public Customizable {
// Allocate a TableFactory that wraps around an ExternalTableFactory. Use this
// to allocate and set in ColumnFamilyOptions::table_factory.
std::shared_ptr<TableFactory> NewExternalTableFactory(
std::unique_ptr<TableFactory> NewExternalTableFactory(
std::shared_ptr<ExternalTableFactory> inner_factory);
} // namespace ROCKSDB_NAMESPACE
+2 -1
View File
@@ -80,7 +80,8 @@ class FileChecksumGenFactory : public Customizable {
const ConfigOptions& options, const std::string& value,
std::shared_ptr<FileChecksumGenFactory>* result);
// Create a new FileChecksumGenerator.
// Create a new FileChecksumGenerator. Recommended to return nullptr if the
// requested function name is not recognized.
virtual std::unique_ptr<FileChecksumGenerator> CreateFileChecksumGenerator(
const FileChecksumGenContext& context) = 0;
+13
View File
@@ -162,6 +162,12 @@ class MemTableRep {
return true;
}
// Only used after concurrent memtable inserts.
// This function will be called by each writer after all writes are done
// through InsertConcurrently().
// This is used by VectorRep to do batched writes for concurrent inserts.
virtual void BatchPostProcess() {}
// Returns true iff an entry that compares equal to key is in the collection.
virtual bool Contains(const char* key) const = 0;
@@ -397,6 +403,11 @@ class SkipListFactory : public MemTableRepFactory {
// the vector is sorted. This is useful for workloads where iteration is very
// rare and writes are generally not issued after reads begin.
//
// Concurrent inserts are supported by buffering writes in thread-local vectors
// for each write batch. To optimize performance for concurrent inserts, it is
// recommended to perform batched writes, and enable unordered_write (refer to
// the option comment for its impact on read consistency).
//
// Parameters:
// count: Passed to the constructor of the underlying std::vector of each
// VectorRep. On initialization, the underlying array will be at least count
@@ -418,6 +429,8 @@ class VectorRepFactory : public MemTableRepFactory {
MemTableRep* CreateMemTableRep(const MemTableRep::KeyComparator&, Allocator*,
const SliceTransform*,
Logger* logger) override;
bool IsInsertConcurrentlySupported() const override { return true; }
};
// This class contains a fixed array of buckets, each
+13 -2
View File
@@ -109,6 +109,10 @@ class TablePropertiesCollector {
// table.
// @params key the user key that is inserted into the table.
// @params value the value that is inserted into the table.
// @params file_size the current file size. For BlockBasedTable, this
// includes all the data blocks written so far, upto but not including
// the current block being built. With parallel compression, data
// blocks are written async so it depends on the compression progress.
virtual Status AddUserKey(const Slice& key, const Slice& value,
EntryType /*type*/, SequenceNumber /*seq*/,
uint64_t /*file_size*/) {
@@ -143,7 +147,7 @@ class TablePropertiesCollector {
// The name of the properties collector can be used for debugging purpose.
virtual const char* Name() const = 0;
// EXPERIMENTAL Return whether the output file should be further compacted
// Return whether the output file should be further compacted
virtual bool NeedCompact() const { return false; }
// For internal use only.
@@ -344,7 +348,14 @@ struct TableProperties {
// {collector_name[1]},{collector_name[2]},{collector_name[3]} ..
std::string property_collectors_names;
// The compression algo used to compress the SST files.
// Identifies the compression algorithm or schema used in the file.
// Specifically:
// * For format_version < 7, it is one of several names for built-in
// compression types. Because of how some previous versions of RocksDB
// behave, this must be set to "ZSTD" if any blocks are compressed
// with zstd and must NOT be set to "NoCompression" if any blocks are
// compressed.
// * For format_version >= 7, it is ...
std::string compression_name;
// Compression options used to compress the SST files.
@@ -23,15 +23,20 @@ class CompactOnDeletionCollectorFactory
// A factory of a table property collector that marks a SST
// file as need-compaction when it observe at least "D" deletion
// entries in any "N" consecutive entries, or the ratio of tombstone
// entries >= deletion_ratio.
// entries >= deletion_ratio for the entire file.
//
// @param sliding_window_size "N"
// @param deletion_trigger "D"
// @param deletion_ratio, if <= 0 or > 1, disable triggering compaction
// based on deletion ratio.
// @param min_file_size, a file needs to be at least this size to be marked
// for compaction. See comments above
// TablePropertiesCollector::AddUserKey() for limitations/inaccuracies on
// the file size.
CompactOnDeletionCollectorFactory(size_t sliding_window_size,
size_t deletion_trigger,
double deletion_ratio);
double deletion_ratio,
uint64_t min_file_size = 0);
~CompactOnDeletionCollectorFactory() override {}
@@ -59,6 +64,12 @@ class CompactOnDeletionCollectorFactory
}
double GetDeletionRatio() const { return deletion_ratio_.load(); }
uint64_t GetMinFileSize() const { return min_file_size_.load(); }
void SetMinFileSize(uint64_t min_file_size) {
min_file_size_.store(min_file_size);
}
static const char* kClassName() { return "CompactOnDeletionCollector"; }
const char* Name() const override { return kClassName(); }
@@ -68,6 +79,7 @@ class CompactOnDeletionCollectorFactory
std::atomic<size_t> sliding_window_size_;
std::atomic<size_t> deletion_trigger_;
std::atomic<double> deletion_ratio_;
std::atomic<uint64_t> min_file_size_;
};
// Creates a factory of a table property collector that marks a SST
@@ -85,7 +97,8 @@ class CompactOnDeletionCollectorFactory
std::shared_ptr<CompactOnDeletionCollectorFactory>
NewCompactOnDeletionCollectorFactory(size_t sliding_window_size,
size_t deletion_trigger,
double deletion_ratio = 0);
double deletion_ratio = 0,
uint64_t min_file_size = 0);
// A factory of a table property collector that marks a SST file as
// need-compaction when for the tiering use case, it observes, among all the
+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 4
#define ROCKSDB_MINOR 5
#define ROCKSDB_PATCH 0
// Do not use these. We made the mistake of declaring macros starting with
+49 -8
View File
@@ -30,6 +30,8 @@ class VectorRep : public MemTableRep {
// collection.
void Insert(KeyHandle handle) override;
void InsertConcurrently(KeyHandle handle) override;
// Returns true iff an entry that compares equal to key is in the collection.
bool Contains(const char* key) const override;
@@ -40,6 +42,8 @@ class VectorRep : public MemTableRep {
void Get(const LookupKey& k, void* callback_args,
bool (*callback_func)(void* arg, const char* entry)) override;
void BatchPostProcess() override;
~VectorRep() override = default;
class Iterator : public MemTableRep::Iterator {
@@ -100,19 +104,40 @@ class VectorRep : public MemTableRep {
private:
friend class Iterator;
ALIGN_AS(CACHE_LINE_SIZE) RelaxedAtomic<size_t> bucket_size_;
using Bucket = std::vector<const char*>;
std::shared_ptr<Bucket> bucket_;
mutable port::RWMutex rwlock_;
bool immutable_;
bool sorted_;
const KeyComparator& compare_;
// Thread-local vector to buffer concurrent writes.
using TlBucket = std::vector<const char*>;
ThreadLocalPtr tl_writes_;
static void DeleteTlBucket(void* ptr) {
auto* v = static_cast<TlBucket*>(ptr);
delete v;
}
};
void VectorRep::Insert(KeyHandle handle) {
auto* key = static_cast<char*>(handle);
WriteLock l(&rwlock_);
assert(!immutable_);
bucket_->push_back(key);
{
WriteLock l(&rwlock_);
assert(!immutable_);
bucket_->push_back(key);
}
bucket_size_.FetchAddRelaxed(1);
}
void VectorRep::InsertConcurrently(KeyHandle handle) {
auto* v = static_cast<TlBucket*>(tl_writes_.Get());
if (!v) {
v = new TlBucket();
tl_writes_.Reset(v);
}
v->push_back(static_cast<char*>(handle));
}
// Returns true iff an entry that compares equal to key is in the collection.
@@ -127,19 +152,35 @@ void VectorRep::MarkReadOnly() {
}
size_t VectorRep::ApproximateMemoryUsage() {
return sizeof(bucket_) + sizeof(*bucket_) +
bucket_->size() *
sizeof(
std::remove_reference<decltype(*bucket_)>::type::value_type);
return bucket_size_.LoadRelaxed() *
sizeof(std::remove_reference<decltype(*bucket_)>::type::value_type);
}
void VectorRep::BatchPostProcess() {
auto* v = static_cast<TlBucket*>(tl_writes_.Get());
if (v) {
{
WriteLock l(&rwlock_);
assert(!immutable_);
for (auto& key : *v) {
bucket_->push_back(key);
}
}
bucket_size_.FetchAddRelaxed(v->size());
delete v;
tl_writes_.Reset(nullptr);
}
}
VectorRep::VectorRep(const KeyComparator& compare, Allocator* allocator,
size_t count)
: MemTableRep(allocator),
bucket_size_(0),
bucket_(new Bucket()),
immutable_(false),
sorted_(false),
compare_(compare) {
compare_(compare),
tl_writes_(DeleteTlBucket) {
bucket_.get()->reserve(count);
}
+4 -3
View File
@@ -272,7 +272,8 @@ Status ConfigurableHelper::ConfigureOptions(
if (config_options.ignore_unknown_options) {
s = Status::OK();
} else if (s.ok() && unused == nullptr && !remaining.empty()) {
s = Status::NotFound("Could not find option: ", remaining.begin()->first);
s = Status::NotFound("Extra option not recognized",
remaining.begin()->first);
}
return s;
}
@@ -369,7 +370,7 @@ Status ConfigurableHelper::ConfigureSingleOption(
const auto opt_info =
FindOption(configurable, opt_name, &elem_name, &opt_ptr);
if (opt_info == nullptr) {
return Status::NotFound("Could not find option: ", name);
return Status::NotFound("Could not find option", name);
} else {
return ConfigureOption(config_options, configurable, *opt_info, opt_name,
elem_name, value, opt_ptr);
@@ -465,7 +466,7 @@ Status ConfigurableHelper::ConfigureOption(
return configurable.ParseOption(config_options, opt_info, name, value,
opt_ptr);
} else {
return Status::NotFound("Could not find option: ", name);
return Status::NotFound("Unknown how to configure option", name);
}
}
+128 -1
View File
@@ -386,6 +386,133 @@ std::unordered_map<std::string, CompressionType>
{"kLZ4HCCompression", kLZ4HCCompression},
{"kXpressCompression", kXpressCompression},
{"kZSTD", kZSTD},
{"kCustomCompression80", kCustomCompression80},
{"kCustomCompression81", kCustomCompression81},
{"kCustomCompression82", kCustomCompression82},
{"kCustomCompression83", kCustomCompression83},
{"kCustomCompression84", kCustomCompression84},
{"kCustomCompression85", kCustomCompression85},
{"kCustomCompression86", kCustomCompression86},
{"kCustomCompression87", kCustomCompression87},
{"kCustomCompression88", kCustomCompression88},
{"kCustomCompression89", kCustomCompression89},
{"kCustomCompression8A", kCustomCompression8A},
{"kCustomCompression8B", kCustomCompression8B},
{"kCustomCompression8C", kCustomCompression8C},
{"kCustomCompression8D", kCustomCompression8D},
{"kCustomCompression8E", kCustomCompression8E},
{"kCustomCompression8F", kCustomCompression8F},
{"kCustomCompression90", kCustomCompression90},
{"kCustomCompression91", kCustomCompression91},
{"kCustomCompression92", kCustomCompression92},
{"kCustomCompression93", kCustomCompression93},
{"kCustomCompression94", kCustomCompression94},
{"kCustomCompression95", kCustomCompression95},
{"kCustomCompression96", kCustomCompression96},
{"kCustomCompression97", kCustomCompression97},
{"kCustomCompression98", kCustomCompression98},
{"kCustomCompression99", kCustomCompression99},
{"kCustomCompression9A", kCustomCompression9A},
{"kCustomCompression9B", kCustomCompression9B},
{"kCustomCompression9C", kCustomCompression9C},
{"kCustomCompression9D", kCustomCompression9D},
{"kCustomCompression9E", kCustomCompression9E},
{"kCustomCompression9F", kCustomCompression9F},
{"kCustomCompressionA0", kCustomCompressionA0},
{"kCustomCompressionA1", kCustomCompressionA1},
{"kCustomCompressionA2", kCustomCompressionA2},
{"kCustomCompressionA3", kCustomCompressionA3},
{"kCustomCompressionA4", kCustomCompressionA4},
{"kCustomCompressionA5", kCustomCompressionA5},
{"kCustomCompressionA6", kCustomCompressionA6},
{"kCustomCompressionA7", kCustomCompressionA7},
{"kCustomCompressionA8", kCustomCompressionA8},
{"kCustomCompressionA9", kCustomCompressionA9},
{"kCustomCompressionAA", kCustomCompressionAA},
{"kCustomCompressionAB", kCustomCompressionAB},
{"kCustomCompressionAC", kCustomCompressionAC},
{"kCustomCompressionAD", kCustomCompressionAD},
{"kCustomCompressionAE", kCustomCompressionAE},
{"kCustomCompressionAF", kCustomCompressionAF},
{"kCustomCompressionB0", kCustomCompressionB0},
{"kCustomCompressionB1", kCustomCompressionB1},
{"kCustomCompressionB2", kCustomCompressionB2},
{"kCustomCompressionB3", kCustomCompressionB3},
{"kCustomCompressionB4", kCustomCompressionB4},
{"kCustomCompressionB5", kCustomCompressionB5},
{"kCustomCompressionB6", kCustomCompressionB6},
{"kCustomCompressionB7", kCustomCompressionB7},
{"kCustomCompressionB8", kCustomCompressionB8},
{"kCustomCompressionB9", kCustomCompressionB9},
{"kCustomCompressionBA", kCustomCompressionBA},
{"kCustomCompressionBB", kCustomCompressionBB},
{"kCustomCompressionBC", kCustomCompressionBC},
{"kCustomCompressionBD", kCustomCompressionBD},
{"kCustomCompressionBE", kCustomCompressionBE},
{"kCustomCompressionBF", kCustomCompressionBF},
{"kCustomCompressionC0", kCustomCompressionC0},
{"kCustomCompressionC1", kCustomCompressionC1},
{"kCustomCompressionC2", kCustomCompressionC2},
{"kCustomCompressionC3", kCustomCompressionC3},
{"kCustomCompressionC4", kCustomCompressionC4},
{"kCustomCompressionC5", kCustomCompressionC5},
{"kCustomCompressionC6", kCustomCompressionC6},
{"kCustomCompressionC7", kCustomCompressionC7},
{"kCustomCompressionC8", kCustomCompressionC8},
{"kCustomCompressionC9", kCustomCompressionC9},
{"kCustomCompressionCA", kCustomCompressionCA},
{"kCustomCompressionCB", kCustomCompressionCB},
{"kCustomCompressionCC", kCustomCompressionCC},
{"kCustomCompressionCD", kCustomCompressionCD},
{"kCustomCompressionCE", kCustomCompressionCE},
{"kCustomCompressionCF", kCustomCompressionCF},
{"kCustomCompressionD0", kCustomCompressionD0},
{"kCustomCompressionD1", kCustomCompressionD1},
{"kCustomCompressionD2", kCustomCompressionD2},
{"kCustomCompressionD3", kCustomCompressionD3},
{"kCustomCompressionD4", kCustomCompressionD4},
{"kCustomCompressionD5", kCustomCompressionD5},
{"kCustomCompressionD6", kCustomCompressionD6},
{"kCustomCompressionD7", kCustomCompressionD7},
{"kCustomCompressionD8", kCustomCompressionD8},
{"kCustomCompressionD9", kCustomCompressionD9},
{"kCustomCompressionDA", kCustomCompressionDA},
{"kCustomCompressionDB", kCustomCompressionDB},
{"kCustomCompressionDC", kCustomCompressionDC},
{"kCustomCompressionDD", kCustomCompressionDD},
{"kCustomCompressionDE", kCustomCompressionDE},
{"kCustomCompressionDF", kCustomCompressionDF},
{"kCustomCompressionE0", kCustomCompressionE0},
{"kCustomCompressionE1", kCustomCompressionE1},
{"kCustomCompressionE2", kCustomCompressionE2},
{"kCustomCompressionE3", kCustomCompressionE3},
{"kCustomCompressionE4", kCustomCompressionE4},
{"kCustomCompressionE5", kCustomCompressionE5},
{"kCustomCompressionE6", kCustomCompressionE6},
{"kCustomCompressionE7", kCustomCompressionE7},
{"kCustomCompressionE8", kCustomCompressionE8},
{"kCustomCompressionE9", kCustomCompressionE9},
{"kCustomCompressionEA", kCustomCompressionEA},
{"kCustomCompressionEB", kCustomCompressionEB},
{"kCustomCompressionEC", kCustomCompressionEC},
{"kCustomCompressionED", kCustomCompressionED},
{"kCustomCompressionEE", kCustomCompressionEE},
{"kCustomCompressionEF", kCustomCompressionEF},
{"kCustomCompressionF0", kCustomCompressionF0},
{"kCustomCompressionF1", kCustomCompressionF1},
{"kCustomCompressionF2", kCustomCompressionF2},
{"kCustomCompressionF3", kCustomCompressionF3},
{"kCustomCompressionF4", kCustomCompressionF4},
{"kCustomCompressionF5", kCustomCompressionF5},
{"kCustomCompressionF6", kCustomCompressionF6},
{"kCustomCompressionF7", kCustomCompressionF7},
{"kCustomCompressionF8", kCustomCompressionF8},
{"kCustomCompressionF9", kCustomCompressionF9},
{"kCustomCompressionFA", kCustomCompressionFA},
{"kCustomCompressionFB", kCustomCompressionFB},
{"kCustomCompressionFC", kCustomCompressionFC},
{"kCustomCompressionFD", kCustomCompressionFD},
{"kCustomCompressionFE", kCustomCompressionFE},
{"kDisableCompressionOption", kDisableCompressionOption}};
const std::vector<CompressionType>& GetSupportedCompressions() {
@@ -910,7 +1037,7 @@ Status OptionTypeInfo::Parse(const ConfigOptions& config_options,
: value;
if (opt_ptr == nullptr) {
return Status::NotFound("Could not find option", opt_name);
return Status::NotFound("Nullptr option", opt_name);
} else if (parse_func_ != nullptr) {
ConfigOptions copy = config_options;
copy.invoke_prepare_options = false;
+6 -2
View File
@@ -703,8 +703,12 @@ 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));
// TODO: try to enhance ObjectLibrary to support singletons
// ASSERT_EQ(new_options->compression_manager,
// GetBuiltinCompressionManager(/*compression_format_version*/ 2));
ASSERT_STREQ(
new_options->compression_manager->Name(),
GetBuiltinCompressionManager(/*compression_format_version*/ 2)->Name());
ColumnFamilyOptions rnd_filled_options = *new_options;
+1 -1
View File
@@ -2031,7 +2031,7 @@ TEST_F(OptionsTest, GetStringFromCompressionType) {
ASSERT_EQ(res, "kZlibCompression");
ASSERT_NOK(
GetStringFromCompressionType(&res, static_cast<CompressionType>(-10)));
GetStringFromCompressionType(&res, static_cast<CompressionType>(0x7F)));
}
TEST_F(OptionsTest, OnlyMutableDBOptions) {
+125 -13
View File
@@ -524,6 +524,7 @@ struct BlockBasedTableBuilder::Rep {
std::string last_ikey; // Internal key or empty (unset)
const Slice* first_key_in_next_block = nullptr;
bool warm_cache = false;
bool uses_explicit_compression_manager = false;
uint64_t sample_for_compression;
std::atomic<uint64_t> compressible_input_data_bytes;
@@ -560,6 +561,11 @@ struct BlockBasedTableBuilder::Rep {
// in verifying data blocks.
UnownedPtr<Decompressor> data_block_verify_decompressor;
// Set of compression types used for blocks in this file (mixing compression
// algorithms in a single file is allowed, using a CompressionManager)
SmallEnumSet<CompressionType, kDisableCompressionOption>
compression_types_used;
// Working area for basic_compressor when compression_parallel_threads==1
WorkingAreaPair basic_working_area;
// Working areas for data_block_compressor, for each of
@@ -770,16 +776,26 @@ struct BlockBasedTableBuilder::Rep {
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);
auto* mgr = tbo.moptions.compression_manager.get();
if (mgr == nullptr) {
uses_explicit_compression_manager = false;
mgr = GetBuiltinCompressionManager(
GetCompressFormatForVersion(
static_cast<uint32_t>(table_opt.format_version)))
.get();
} else {
uses_explicit_compression_manager = true;
// Stuff some extra debugging info as extra pseudo-options. Using
// underscore prefix to indicate they are special.
props.compression_options.append("_compression_manager=");
props.compression_options.append(mgr->GetId());
props.compression_options.append("; ");
}
// 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);
@@ -810,22 +826,23 @@ struct BlockBasedTableBuilder::Rep {
data_block_compressor->ObtainWorkingArea();
}
}
basic_decompressor =
mgr->GetDecompressorOptimizeFor(tbo.compression_type);
basic_decompressor = mgr->GetDecompressorForCompressor(*basic_compressor);
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);
basic_working_area.verify = verify_decompressor->ObtainWorkingArea(
basic_compressor->GetPreferredCompressionType());
}
if (state == State::kUnbuffered) {
assert(data_block_compressor);
data_block_verify_decompressor = verify_decompressor.get();
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->ObtainWorkingArea(
data_block_compressor->GetPreferredCompressionType());
}
data_block_verify_decompressor = verify_decompressor.get();
}
}
}
@@ -930,6 +947,7 @@ struct BlockBasedTableBuilder::Rep {
// 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;
PrePopulateCompressionProperties(mgr);
if (FormatVersionUsesContextChecksum(table_options.format_version)) {
// Must be non-zero and semi- or quasi-random
@@ -954,6 +972,92 @@ struct BlockBasedTableBuilder::Rep {
Rep(const Rep&) = delete;
Rep& operator=(const Rep&) = delete;
void PrePopulateCompressionProperties(UnownedPtr<CompressionManager> mgr) {
if (FormatVersionUsesCompressionManagerName(table_options.format_version)) {
assert(mgr);
// Use newer compression_name property
props.compression_name.reserve(32);
props.compression_name.append(mgr->CompatibilityName());
props.compression_name.push_back(';');
// Rest of property to be filled out at the end of building the file
} else {
// Use legacy compression_name property, populated at the end of building
// the file. Not compatible with compression managers using custom
// algorithms / compression types.
assert(Slice(mgr->CompatibilityName())
.compare(GetBuiltinCompressionManager(
GetCompressFormatForVersion(
static_cast<uint32_t>(props.format_version)))
->CompatibilityName()) == 0);
}
}
void PostPopulateCompressionProperties() {
// Do not include "no compression" in the set. It's not really useful
// information whether there are any uncompressed blocks. Some kinds of
// blocks are never compressed anyway.
compression_types_used.Remove(kNoCompression);
size_t ctype_count = compression_types_used.count();
if (uses_explicit_compression_manager) {
// Stuff some extra debugging info as extra pseudo-options. Using
// underscore prefix to indicate they are special.
std::string& compression_options = props.compression_options;
compression_options.append("_compressor=");
compression_options.append(data_block_compressor
? data_block_compressor->GetId()
: std::string{});
compression_options.append("; ");
} else {
// No explicit compression manager
assert(compression_types_used.count() <= 1);
}
std::string& compression_name = props.compression_name;
if (FormatVersionUsesCompressionManagerName(table_options.format_version)) {
// Fill in extended field of "compression name" property, which is the set
// of compression types used, sorted by unsigned byte and then hex
// encoded with two digits each (so that table properties are human
// readable).
assert(*compression_name.rbegin() == ';');
size_t pos = compression_name.size();
// Make space for the field contents
compression_name.append(ctype_count * 2, '\0');
char* ptr = compression_name.data() + pos;
// Populate the field contents
for (CompressionType t : compression_types_used) {
PutBaseChars<16>(&ptr, /*digits=*/2, static_cast<unsigned char>(t),
/*uppercase=*/true);
}
assert(ptr == compression_name.data() + pos + ctype_count * 2);
// Allow additional fields in the future
compression_name.push_back(';');
} else {
// Use legacy compression naming. To adhere to requirements described in
// TableProperties::compression_name, we might have to replace the name
// based on the legacy configured compression type.
assert(compression_name.empty());
if (ctype_count == 0) {
// We could get a slight performance boost in the reader by marking the
// file as "no compression" if compression is configured but
// consistently rejected, but that would give misleading info for
// debugging purposes. So instead we record the configured compression
// type, matching the historical behavior.
if (data_block_compressor) {
compression_name = CompressionTypeToString(
data_block_compressor->GetPreferredCompressionType());
} else {
assert(basic_compressor == nullptr);
compression_name = CompressionTypeToString(kNoCompression);
}
} else if (compression_types_used.Contains(kZSTD)) {
compression_name = CompressionTypeToString(kZSTD);
} else {
compression_name =
CompressionTypeToString(*compression_types_used.begin());
}
}
}
private:
// Synchronize status & io_status accesses across threads from main thread,
// compression thread and write thread in parallel compression.
@@ -1432,6 +1536,7 @@ void BlockBasedTableBuilder::WriteMaybeCompressedBlock(
}
}
r->compression_types_used.Add(comp_type);
std::array<char, kBlockTrailerSize> trailer;
trailer[0] = comp_type;
uint32_t checksum = ComputeBuiltinChecksumWithLastByte(
@@ -1783,6 +1888,9 @@ void BlockBasedTableBuilder::WritePropertiesBlock(
}
property_collectors_names += "]";
rep_->props.property_collectors_names = property_collectors_names;
rep_->PostPopulateCompressionProperties();
if (rep_->table_options.index_type ==
BlockBasedTableOptions::kTwoLevelIndexSearch) {
assert(rep_->p_index_builder_ != nullptr);
@@ -1930,6 +2038,8 @@ void BlockBasedTableBuilder::EnterUnbuffered() {
if (kNumBlocksBuffered == 0) {
// The below code is neither safe nor necessary for handling zero data
// blocks.
// For PostPopulateCompressionProperties()
r->data_block_compressor = r->basic_compressor.get();
return;
}
@@ -1967,6 +2077,8 @@ void BlockBasedTableBuilder::EnterUnbuffered() {
}
}
assert(samples.sample_data.size() > 0);
// final sample data block flushed, now we can generate dictionary
r->compressor_with_dict = r->basic_compressor->MaybeCloneSpecialized(
CacheEntryRole::kDataBlock, std::move(samples));
+61 -25
View File
@@ -469,7 +469,7 @@ void BlockBasedTableFactory::InitializeOptions() {
}
if (table_options_.format_version < kMinSupportedFormatVersion) {
if (AllowUnsupportedFormatVersion()) {
if (TEST_AllowUnsupportedFormatVersion()) {
// Allow old format version for testing.
// And relevant old sanitization.
if (table_options_.format_version == 0 &&
@@ -569,9 +569,11 @@ Status BlockBasedTableFactory::NewTableReader(
file_size, table_reader_options.block_protection_bytes_per_key,
table_reader, table_reader_options.tail_size,
shared_state_->table_reader_cache_res_mgr,
table_reader_options.prefix_extractor, prefetch_index_and_filter_in_cache,
table_reader_options.skip_filters, table_reader_options.level,
table_reader_options.immortal, table_reader_options.largest_seqno,
table_reader_options.prefix_extractor,
table_reader_options.compression_manager,
prefetch_index_and_filter_in_cache, table_reader_options.skip_filters,
table_reader_options.level, table_reader_options.immortal,
table_reader_options.largest_seqno,
table_reader_options.force_direct_prefetch,
&shared_state_->tail_prefetch_stats,
table_reader_options.block_cache_tracer,
@@ -608,28 +610,67 @@ Status BlockBasedTableFactory::ValidateOptions(
"Enable pin_l0_filter_and_index_blocks_in_cache, "
", but block cache is disabled");
}
if (!IsSupportedFormatVersion(table_options_.format_version)) {
if (!IsSupportedFormatVersion(table_options_.format_version) &&
!TEST_AllowUnsupportedFormatVersion()) {
return Status::InvalidArgument(
"Unsupported BlockBasedTable format_version. Please check "
"include/rocksdb/table.h for more info");
}
if (table_options_.block_align && (cf_opts.compression != kNoCompression)) {
return Status::InvalidArgument(
"Enable block_align, but compression "
"enabled");
bool using_builtin_compatible_compression = true;
if (cf_opts.compression_manager &&
strcmp(cf_opts.compression_manager->CompatibilityName(),
GetBuiltinCompressionManager(
GetCompressFormatForVersion(table_options_.format_version))
->CompatibilityName()) != 0) {
if (FormatVersionUsesCompressionManagerName(
table_options_.format_version)) {
using_builtin_compatible_compression = false;
} else {
return Status::InvalidArgument(
"Using a CompressionManager incompatible with built-in (custom "
"CompatibilityName()) is not supported for format_version < 7");
}
}
if (table_options_.block_align &&
cf_opts.bottommost_compression != kDisableCompressionOption &&
cf_opts.bottommost_compression != kNoCompression) {
return Status::InvalidArgument(
"Enable block_align, but bottommost_compression enabled");
}
if (table_options_.block_align) {
for (auto level_compression : cf_opts.compression_per_level) {
if (level_compression != kDisableCompressionOption &&
level_compression != kNoCompression) {
auto validate_compression_type_fn = [&](CompressionType ctype,
const char* context) {
if (ctype == kNoCompression) {
return Status::OK();
}
if (ctype == kDisableCompressionOption) {
if (strcmp(context, "compression") == 0) {
return Status::InvalidArgument(
"Enable block_align, but compression_per_level enabled");
"kDisableCompressionOption not permitted for option: "
"compression");
} else {
return Status::OK();
}
}
if (table_options_.block_align) {
return Status::InvalidArgument("Enable block_align, but " +
std::string(context) + " enabled");
}
if (ctype > kLastBuiltinCompression &&
using_builtin_compatible_compression) {
return Status::InvalidArgument(
"Using a CompressionType other than built-in ..."); // TODO
}
// Otherwise
return Status::OK();
};
{
Status s = validate_compression_type_fn(cf_opts.compression, "compression");
if (!s.ok()) {
return s;
}
s = validate_compression_type_fn(cf_opts.bottommost_compression,
"bottommost_compression");
if (!s.ok()) {
return s;
}
for (auto ctype : cf_opts.compression_per_level) {
s = validate_compression_type_fn(ctype, "compression_per_level");
if (!s.ok()) {
return s;
}
}
}
@@ -924,11 +965,6 @@ Status BlockBasedTableFactory::ParseOption(const ConfigOptions& config_options,
return status;
}
bool& BlockBasedTableFactory::AllowUnsupportedFormatVersion() {
static bool allow = false;
return allow;
}
Status GetBlockBasedTableOptionsFromString(
const ConfigOptions& config_options,
const BlockBasedTableOptions& table_options, const std::string& opts_str,
@@ -89,10 +89,6 @@ class BlockBasedTableFactory : public TableFactory {
static constexpr int kMinSupportedFormatVersion = 2;
// Set to true to allow unit testing of writing unsupported block-based table
// format versions (to test read side)
static bool& AllowUnsupportedFormatVersion();
protected:
const void* GetOptionsPtr(const std::string& name) const override;
Status ParseOption(const ConfigOptions& config_options,
+108 -11
View File
@@ -562,6 +562,105 @@ Status GetGlobalSequenceNumber(const TableProperties& table_properties,
return Status::OK();
}
Status GetDecompressor(const std::string& compression_name,
UnownedPtr<CompressionManager> compression_manager,
uint32_t table_format_version,
std::shared_ptr<Decompressor>* out_decompressor) {
if (compression_name.empty()) {
// Very old file (before RocksDB 4.9.0) that might contain compressed
// blocks. Get a general decompressor for the format version.
auto mgr_to_use = GetBuiltinCompressionManager(
GetCompressFormatForVersion(table_format_version));
*out_decompressor = mgr_to_use->GetDecompressor();
return Status::OK();
}
if (FormatVersionUsesCompressionManagerName(table_format_version)) {
constexpr char kFieldSep = ';';
size_t separator_pos = compression_name.find_first_of(kFieldSep);
if (separator_pos == std::string::npos) {
return Status::Corruption(
"Missing separator in compression_name property");
}
// Built with explicit CompressionManager and schema support for
// identifying its compatibility name, which is the first field here.
Slice compatibility_name(compression_name.data(), separator_pos);
std::shared_ptr<CompressionManager> mgr_to_use;
if (compression_manager) {
// First attempt to go through the compression manager configured for
// writing new files, for efficiency (usually correct) and not forcing
// use of ObjectLibrary registration (dependency injection).
mgr_to_use = compression_manager->FindCompatibleCompressionManager(
compatibility_name);
}
if (mgr_to_use == nullptr) {
ConfigOptions strict;
strict.ignore_unknown_options = false;
strict.ignore_unsupported_options = false;
Status s = CompressionManager::CreateFromString(
strict, compatibility_name.ToString(), &mgr_to_use);
// Even though we might be able to recover from "not found" if only
// built-in compression types are used (would be checked below), it
// would provide misleading or unreliable success to allow that to
// succeed.
if (!s.ok()) {
return s;
}
assert(mgr_to_use);
}
// Second field is set of compression types actually used in the file
size_t start_pos = separator_pos + 1;
separator_pos = compression_name.find_first_of(kFieldSep, start_pos);
if (UNLIKELY(separator_pos == std::string::npos)) {
return Status::Corruption("Missing second field from compression_name");
}
if (UNLIKELY((separator_pos - start_pos) & 1)) {
return Status::Corruption(
"Second field of compression_name has odd size");
}
size_t count = (separator_pos - start_pos) / 2;
auto ctypes = std::make_unique<CompressionType[]>(count);
const char* ptr = compression_name.data() + start_pos;
for (size_t i = 0; i < count; ++i) {
uint64_t val = 0;
bool success = ParseBaseChars<16>(&ptr, 2, &val);
if (UNLIKELY(!success || val == kNoCompression ||
val >= kDisableCompressionOption)) {
return Status::Corruption(
"Error parsing second field of compression_name");
}
ctypes[i] = static_cast<CompressionType>(val);
}
*out_decompressor =
mgr_to_use->GetDecompressorForTypes(ctypes.get(), ctypes.get() + count);
assert(*out_decompressor || count == 0);
// Can ignore possible additional future fields
} else {
// No explicit CompressionManager, e.g. legacy file support where
// decompressing with built-in CompressionManager works.
CompressionType saved_comp_type =
CompressionTypeFromString(compression_name);
if (saved_comp_type == kDisableCompressionOption) {
// Unrecognized. For RocksDB versions able to read format_version=7,
// this is considered an error so that we can continue to evolve the
// schema of the compression_name property and report good error
// messages.
return Status::Corruption("Unrecognized compression_name: " +
compression_name);
} else if (saved_comp_type != kNoCompression) {
// Use built-in compression manager
auto mgr_to_use = GetBuiltinCompressionManager(
GetCompressFormatForVersion(table_format_version));
*out_decompressor =
mgr_to_use->GetDecompressorOptimizeFor(saved_comp_type);
} else {
// No compression -> decompressor not needed
*out_decompressor = nullptr;
}
}
return Status::OK();
}
} // namespace
void BlockBasedTable::SetupBaseCacheKey(const TableProperties* properties,
@@ -629,6 +728,7 @@ Status BlockBasedTable::Open(
std::unique_ptr<TableReader>* table_reader, uint64_t tail_size,
std::shared_ptr<CacheReservationManager> table_reader_cache_res_mgr,
const std::shared_ptr<const SliceTransform>& prefix_extractor,
UnownedPtr<CompressionManager> compression_manager,
const bool prefetch_index_and_filter_in_cache, const bool skip_filters,
const int level, const bool immortal_table,
const SequenceNumber largest_seqno, const bool force_direct_prefetch,
@@ -696,7 +796,8 @@ Status BlockBasedTable::Open(
}
return s;
}
if (!IsSupportedFormatVersion(footer.format_version())) {
if (!IsSupportedFormatVersion(footer.format_version()) &&
!TEST_AllowUnsupportedFormatVersion()) {
return Status::Corruption(
"Unknown Footer version. Maybe this file was created with newer "
"version of RocksDB?");
@@ -746,17 +847,13 @@ Status BlockBasedTable::Open(
return s;
}
CompressionType saved_comp_type = CompressionTypeFromString(
// Read compression metadata and configure decompressor
s = GetDecompressor(
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()));
rep->decompressor = mgr->GetDecompressorOptimizeFor(saved_comp_type);
: std::string{},
compression_manager, footer.format_version(), &rep->decompressor);
if (!s.ok()) {
return s;
}
// Populate BlockCreateContext
@@ -106,6 +106,7 @@ class BlockBasedTable : public TableReader {
std::shared_ptr<CacheReservationManager> table_reader_cache_res_mgr =
nullptr,
const std::shared_ptr<const SliceTransform>& prefix_extractor = nullptr,
UnownedPtr<CompressionManager> compression_manager = nullptr,
bool prefetch_index_and_filter_in_cache = true, bool skip_filters = false,
int level = -1, const bool immortal_table = false,
const SequenceNumber largest_seqno = 0,
@@ -163,10 +163,11 @@ class BlockBasedTableReaderBaseTest : public testing::Test {
bool user_defined_timestamps_persisted = true) {
const MutableCFOptions moptions(options_);
TableReaderOptions table_reader_options = TableReaderOptions(
ioptions, moptions.prefix_extractor, foptions, comparator,
0 /* block_protection_bytes_per_key */, false /* _skip_filters */,
false /* _immortal */, false /* _force_direct_prefetch */,
-1 /* _level */, nullptr /* _block_cache_tracer */,
ioptions, moptions.prefix_extractor, moptions.compression_manager.get(),
foptions, comparator, 0 /* block_protection_bytes_per_key */,
false /* _skip_filters */, false /* _immortal */,
false /* _force_direct_prefetch */, -1 /* _level */,
nullptr /* _block_cache_tracer */,
0 /* _max_file_size_for_l0_meta_pin */, "" /* _cur_db_session_id */,
0 /* _cur_file_num */, {} /* _unique_id */, 0 /* _largest_seqno */,
0 /* _tail_size */, user_defined_timestamps_persisted);
@@ -582,7 +582,8 @@ void TestBoundary(InternalKey& ik1, std::string& v1, InternalKey& ik2,
const bool kSkipFilters = true;
const bool kImmortal = true;
ASSERT_OK(moptions.table_factory->NewTableReader(
TableReaderOptions(ioptions, moptions.prefix_extractor, soptions,
TableReaderOptions(ioptions, moptions.prefix_extractor,
nullptr /* compression_manager */, soptions,
internal_comparator,
0 /* block_protection_bytes_per_key */, !kSkipFilters,
!kImmortal, level_),
+3 -3
View File
@@ -473,10 +473,10 @@ class ExternalTableFactoryAdapter : public TableFactory {
} // namespace
std::shared_ptr<TableFactory> NewExternalTableFactory(
std::unique_ptr<TableFactory> NewExternalTableFactory(
std::shared_ptr<ExternalTableFactory> inner_factory) {
std::shared_ptr<TableFactory> res;
res.reset(new ExternalTableFactoryAdapter(std::move(inner_factory)));
std::unique_ptr<TableFactory> res;
res = std::make_unique<ExternalTableFactoryAdapter>(std::move(inner_factory));
return res;
}
+9 -2
View File
@@ -229,7 +229,8 @@ Status FooterBuilder::Build(uint64_t magic_number, uint32_t format_version,
const BlockHandle& index_handle,
uint32_t base_context_checksum) {
assert(magic_number != Footer::kNullTableMagicNumber);
assert(IsSupportedFormatVersion(format_version));
assert(IsSupportedFormatVersion(format_version) ||
TEST_AllowUnsupportedFormatVersion());
char* part2;
char* part3;
@@ -362,7 +363,8 @@ Status Footer::DecodeFrom(Slice input, uint64_t input_offset,
} else {
part3_ptr = magic_ptr - 4;
format_version_ = DecodeFixed32(part3_ptr);
if (UNLIKELY(!IsSupportedFormatVersion(format_version_))) {
if (UNLIKELY(!IsSupportedFormatVersion(format_version_) &&
!TEST_AllowUnsupportedFormatVersion())) {
return Status::Corruption("Corrupt or unsupported format_version: " +
std::to_string(format_version_));
}
@@ -475,6 +477,11 @@ std::string Footer::ToString() const {
return result;
}
bool& TEST_AllowUnsupportedFormatVersion() {
static bool allow = false;
return allow;
}
static Status ReadFooterFromFileInternal(const IOOptions& opts,
RandomAccessFileReader* file,
FileSystem& fs,
+8
View File
@@ -175,6 +175,10 @@ inline bool FormatVersionUsesIndexHandleInFooter(uint32_t version) {
return version < 6;
}
inline bool FormatVersionUsesCompressionManagerName(uint32_t version) {
return version >= 7;
}
// Footer encapsulates the fixed information stored at the tail end of every
// SST file. In general, it should only include things that cannot go
// elsewhere under the metaindex block. For example, checksum_type is
@@ -308,6 +312,10 @@ class FooterBuilder {
std::array<char, Footer::kMaxEncodedLength> data_;
};
// Set to true to allow unit testing of writing unsupported block-based table
// format versions (to test read side)
bool& TEST_AllowUnsupportedFormatVersion();
// Read the footer from file
// If enforce_table_magic_number != 0, ReadFooterFromFile() will return
// corruption if table_magic number is not equal to enforce_table_magic_number
+2 -1
View File
@@ -172,7 +172,8 @@ Status SstFileDumper::NewTableReader(
const InternalKeyComparator& /*internal_comparator*/, uint64_t file_size,
std::unique_ptr<TableReader>* /*table_reader*/) {
auto t_opt = TableReaderOptions(
ioptions_, moptions_.prefix_extractor, soptions_, internal_comparator_,
ioptions_, moptions_.prefix_extractor,
moptions_.compression_manager.get(), soptions_, internal_comparator_,
0 /* block_protection_bytes_per_key */, false /* skip_filters */,
false /* immortal */, true /* force_direct_prefetch */, -1 /* level */,
nullptr /* block_cache_tracer */, 0 /* max_file_size_for_l0_meta_pin */,
+2 -1
View File
@@ -62,7 +62,8 @@ Status SstFileReader::Open(const std::string& file_path) {
}
if (s.ok()) {
TableReaderOptions t_opt(
r->ioptions, r->moptions.prefix_extractor, r->soptions,
r->ioptions, r->moptions.prefix_extractor,
r->moptions.compression_manager.get(), r->soptions,
r->ioptions.internal_comparator,
r->moptions.block_protection_bytes_per_key,
/*skip_filters*/ false, /*immortal*/ false,
+6
View File
@@ -24,6 +24,7 @@
#include "rocksdb/table_properties.h"
#include "table/unique_id_impl.h"
#include "trace_replay/block_cache_tracer.h"
#include "util/cast_util.h"
namespace ROCKSDB_NAMESPACE {
@@ -35,6 +36,7 @@ struct TableReaderOptions {
TableReaderOptions(
const ImmutableOptions& _ioptions,
const std::shared_ptr<const SliceTransform>& _prefix_extractor,
UnownedPtr<CompressionManager> _compression_manager,
const EnvOptions& _env_options,
const InternalKeyComparator& _internal_comparator,
uint8_t _block_protection_bytes_per_key, bool _skip_filters = false,
@@ -46,6 +48,7 @@ struct TableReaderOptions {
uint64_t _tail_size = 0, bool _user_defined_timestamps_persisted = true)
: ioptions(_ioptions),
prefix_extractor(_prefix_extractor),
compression_manager(_compression_manager),
env_options(_env_options),
internal_comparator(_internal_comparator),
skip_filters(_skip_filters),
@@ -64,6 +67,9 @@ struct TableReaderOptions {
const ImmutableOptions& ioptions;
const std::shared_ptr<const SliceTransform>& prefix_extractor;
// NOTE: the compression manager is not saved, just potentially a decompressor
// from it, so we don't need a shared_ptr copy
UnownedPtr<CompressionManager> compression_manager;
const EnvOptions& env_options;
const InternalKeyComparator& internal_comparator;
// This is only used for BlockBasedTable (reader)
+3 -2
View File
@@ -145,8 +145,9 @@ void TableReaderBenchmark(Options& opts, EnvOptions& env_options,
std::unique_ptr<RandomAccessFileReader> file_reader(
new RandomAccessFileReader(std::move(raf), file_name));
s = opts.table_factory->NewTableReader(
TableReaderOptions(ioptions, moptions.prefix_extractor, env_options,
ikc, 0 /* block_protection_bytes_per_key */),
TableReaderOptions(ioptions, moptions.prefix_extractor,
moptions.compression_manager.get(), env_options, ikc,
0 /* block_protection_bytes_per_key */),
std::move(file_reader), file_size, &table_reader);
if (!s.ok()) {
fprintf(stderr, "Open Table Error: %s\n", s.ToString().c_str());
+7 -5
View File
@@ -444,7 +444,8 @@ class TableConstructor : public Constructor {
file_reader_.reset(new RandomAccessFileReader(std::move(source), "test"));
return moptions.table_factory->NewTableReader(
TableReaderOptions(ioptions, moptions.prefix_extractor, soptions,
TableReaderOptions(ioptions, moptions.prefix_extractor,
moptions.compression_manager.get(), soptions,
*last_internal_comparator_,
0 /* block_protection_bytes_per_key */,
/*skip_filters*/ false,
@@ -5332,7 +5333,8 @@ TEST_P(BlockBasedTableTest, DISABLED_TableWithGlobalSeqno) {
new RandomAccessFileReader(std::move(source), ""));
options.table_factory->NewTableReader(
TableReaderOptions(ioptions, moptions.prefix_extractor, EnvOptions(),
TableReaderOptions(ioptions, moptions.prefix_extractor,
moptions.compression_manager.get(), EnvOptions(),
ikc, 0 /* block_protection_bytes_per_key */),
std::move(file_reader), ss_rw.contents().size(), &table_reader);
@@ -5507,7 +5509,8 @@ TEST_P(BlockBasedTableTest, BlockAlignTest) {
const MutableCFOptions moptions2(options2);
ASSERT_OK(moptions.table_factory->NewTableReader(
TableReaderOptions(ioptions2, moptions2.prefix_extractor, EnvOptions(),
TableReaderOptions(ioptions2, moptions2.prefix_extractor,
moptions2.compression_manager.get(), EnvOptions(),
GetPlainInternalComparator(options2.comparator),
0 /* block_protection_bytes_per_key */),
std::move(file_reader), sink->contents().size(), &table_reader));
@@ -7386,8 +7389,7 @@ TEST_F(ExternalTableTest, IngestionTest) {
int main(int argc, char** argv) {
// Opt-in this whole test file
ROCKSDB_NAMESPACE::BlockBasedTableFactory::AllowUnsupportedFormatVersion() =
true;
ROCKSDB_NAMESPACE::TEST_AllowUnsupportedFormatVersion() = true;
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
::testing::InitGoogleTest(&argc, argv);
+127 -1
View File
@@ -23,6 +23,7 @@
#include "rocksdb/slice.h"
#include "rocksdb/table.h"
#include "table/internal_iterator.h"
#include "util/defer.h"
#include "util/mutexlock.h"
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
@@ -71,7 +72,7 @@ bool ShouldPersistUDT(const UserDefinedTimestampTestMode& test_mode);
// Store in *dst a string of length "len" that will compress to
// "N*compressed_fraction" bytes and return a Slice that references
// the generated data.
Slice CompressibleString(Random* rnd, double compressed_fraction, int len,
Slice CompressibleString(Random* rnd, double compressed_to_fraction, int len,
std::string* dst);
#ifndef NDEBUG
@@ -731,6 +732,131 @@ class StringFS : public FileSystemWrapper {
std::unordered_map<std::string, std::string> files_;
};
// A compressor that essentially implements a custom compression algorithm
// by leveraging an existing compression algorithm and putting a custom header
// on it to detect any attempts to decompress it with the wrong compression
// type or dictionary.
template <CompressionType kCompression>
struct CompressorCustomAlg : public CompressorWrapper {
static bool Supported() { return LZ4_Supported(); }
explicit CompressorCustomAlg(std::unique_ptr<Compressor> wrapped =
GetDefaultBuiltinCompressionManager()
->GetCompressor({}, kLZ4Compression))
: CompressorWrapper(std::move(wrapped)),
dictionary_hash_(GetSliceHash(wrapped_->GetSerializedDict())) {
static_assert(kCompression > kLastBuiltinCompression);
}
const char* Name() const override { return "CompressorCustomAlg"; }
Status CompressBlock(Slice uncompressed_data, std::string* compressed_output,
CompressionType* out_compression_type,
ManagedWorkingArea* working_area) override {
Status s = wrapped_->CompressBlock(uncompressed_data, compressed_output,
out_compression_type, working_area);
if (*out_compression_type != kNoCompression) {
assert(*out_compression_type == kLZ4Compression);
std::string header(/*size=*/5, 0);
header[0] = lossless_cast<char>(kCompression);
EncodeFixed32(&header[1], dictionary_hash_);
compressed_output->insert(0, header);
*out_compression_type = kCompression;
}
return s;
}
std::unique_ptr<Compressor> MaybeCloneSpecialized(
CacheEntryRole block_type, DictSampleArgs&& dict_samples) override {
auto clone =
wrapped_->MaybeCloneSpecialized(block_type, std::move(dict_samples));
return std::make_unique<CompressorCustomAlg>(std::move(clone));
}
protected:
uint32_t dictionary_hash_;
};
// A decompressor suitable for all the instantiable CompressorCustomAlg
// implementations. Can be configured to check that it is only used to
// decompress certain types using SetAllowedTypes().
struct DecompressorCustomAlg : public DecompressorWrapper {
using TypeSet = SmallEnumSet<CompressionType, kDisableCompressionOption>;
DecompressorCustomAlg(
std::shared_ptr<Decompressor> wrapped =
GetDefaultBuiltinCompressionManager()->GetDecompressor())
: DecompressorWrapper(std::move(wrapped)),
dictionary_hash_(GetSliceHash(wrapped_->GetSerializedDict())),
allowed_types_(TypeSet::All()) {}
const char* Name() const override { return "DecompressorCustomAlg"; }
Status MaybeCloneForDict(const Slice& serialized_dict,
std::unique_ptr<Decompressor>* out) override {
Status s = wrapped_->MaybeCloneForDict(serialized_dict, out);
if (s.ok()) {
assert(*out != nullptr);
auto clone = std::make_unique<DecompressorCustomAlg>(std::move(*out));
clone->SetAllowedTypes(allowed_types_);
*out = std::move(clone);
assert(out->get()->GetSerializedDict() == serialized_dict);
} else {
assert(*out == nullptr);
}
return s;
}
Status ExtractUncompressedSize(Args& args) override {
if (args.compression_type > kLastBuiltinCompression) {
assert(args.compressed_data.size() > 0);
assert(args.compressed_data[0] ==
lossless_cast<char>(args.compression_type));
assert(DecodeFixed32(args.compressed_data.data() + 1) ==
dictionary_hash_);
// Strip off our header because ExtractUncompressedSize() is also going
// to strip off the uncompressed size data.
args.compressed_data.remove_prefix(5);
// It's ok to modify other parts of args if we restore to original
SaveAndRestore<CompressionType> save_compression_type(
&args.compression_type, kLZ4Compression);
return wrapped_->ExtractUncompressedSize(args);
} else {
// Also support built-in compressions
return wrapped_->ExtractUncompressedSize(args);
}
}
Status DecompressBlock(const Args& args, char* uncompressed_output) override {
if (args.compression_type > kLastBuiltinCompression) {
// Also allowed to copy args and modify
Args modified_args = args;
modified_args.compression_type = kLZ4Compression;
return wrapped_->DecompressBlock(modified_args, uncompressed_output);
} else {
// Also support built-in compressions
return wrapped_->DecompressBlock(args, uncompressed_output);
}
}
void SetAllowedTypes(const CompressionType* types_begin,
const CompressionType* types_end) {
TypeSet allowed_types;
for (auto type = types_begin; type != types_end; ++type) {
allowed_types.Add(*type);
}
allowed_types_ = std::move(allowed_types);
}
void SetAllowedTypes(TypeSet allowed_types) {
allowed_types_ = std::move(allowed_types);
}
protected:
uint32_t dictionary_hash_;
SmallEnumSet<CompressionType, kDisableCompressionOption> allowed_types_;
};
// Randomly initialize the given DBOptions
void RandomInitDBOptions(DBOptions* db_opt, Random* rnd);
+1 -1
View File
@@ -137,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" "10.3.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" "10.4.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
+6
View File
@@ -1730,6 +1730,10 @@ DEFINE_uint64(stats_history_buffer_size,
DEFINE_bool(avoid_flush_during_recovery,
ROCKSDB_NAMESPACE::Options().avoid_flush_during_recovery,
"If true, avoids flushing the recovered WAL data where possible.");
DEFINE_bool(avoid_flush_during_shutdown,
ROCKSDB_NAMESPACE::Options().avoid_flush_during_shutdown,
"If true, avoids flushing the recovered WAL data where possible.");
DEFINE_int64(multiread_stride, 0,
"Stride length for the keys in a MultiGet batch");
DEFINE_bool(multiread_batched, false, "Use the new MultiGet API");
@@ -4264,6 +4268,7 @@ class Benchmark {
options.stats_history_buffer_size =
static_cast<size_t>(FLAGS_stats_history_buffer_size);
options.avoid_flush_during_recovery = FLAGS_avoid_flush_during_recovery;
options.avoid_flush_during_shutdown = FLAGS_avoid_flush_during_shutdown;
options.compression_opts.level = FLAGS_compression_level;
options.compression_opts.max_dict_bytes = FLAGS_compression_max_dict_bytes;
@@ -4495,6 +4500,7 @@ class Benchmark {
block_based_options.block_restart_interval = FLAGS_block_restart_interval;
block_based_options.index_block_restart_interval =
FLAGS_index_block_restart_interval;
TEST_AllowUnsupportedFormatVersion() = true;
block_based_options.format_version =
static_cast<uint32_t>(FLAGS_format_version);
block_based_options.read_amp_bytes_per_bit = FLAGS_read_amp_bytes_per_bit;
+20 -13
View File
@@ -178,7 +178,7 @@ default_params = {
"verify_checksum": 1,
"write_buffer_size": lambda: random.choice([1024 * 1024, 4 * 1024 * 1024]),
"writepercent": 35,
"format_version": lambda: random.choice([2, 3, 4, 5, 6, 6]),
"format_version": lambda: random.choice([2, 3, 4, 5, 6, 7, 7]),
"index_block_restart_interval": lambda: random.choice(range(1, 16)),
"use_multiget": lambda: random.randint(0, 1),
"use_get_entity": lambda: random.choice([0] * 7 + [1]),
@@ -350,7 +350,11 @@ default_params = {
"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
["mixed"] * 1
+ ["none"] * 2
+ ["autoskip"] * 2
+ ["randommixed"] * 2
+ ["custom"] * 3
),
}
@@ -520,6 +524,7 @@ cf_consistency_params = {
"ingest_external_file_one_in": 0,
# `CfConsistencyStressTest::TestIterateAgainstExpected()` is not implemented.
"verify_iterator_with_expected_state_one_in": 0,
"memtablerep": random.choice(["skip_list"] * 9 + ["vector"]),
}
# For pessimistic transaction db
@@ -722,6 +727,10 @@ def finalize_and_sanitize(src_params):
else:
dest_params["mock_direct_io"] = True
if dest_params.get("memtablerep") == "vector":
dest_params["inplace_update_support"] = 0
dest_params["paranoid_memory_checks"] = 0
if dest_params["test_batches_snapshots"] == 1:
dest_params["enable_compaction_filter"] = 0
dest_params["inplace_update_support"] = 0
@@ -949,8 +958,6 @@ def finalize_and_sanitize(src_params):
# disable atomic flush.
if dest_params["test_best_efforts_recovery"] == 0:
dest_params["disable_wal"] = 0
if dest_params.get("allow_concurrent_memtable_write", 1) == 1:
dest_params["memtablerep"] = "skip_list"
if (
dest_params.get("enable_compaction_filter", 0) == 1
or dest_params.get("inplace_update_support", 0) == 1
@@ -1001,19 +1008,19 @@ 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
# Disabling block align if mixed manager is neing used
if (
# Disabling block align if mixed manager is being used
if dest_params.get("compression_manager") == "custom":
if dest_params.get("block_align") == 1:
dest_params["block_align"] = 0
if dest_params["format_version"] < 7:
dest_params["format_version"] = 7
elif (
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"
dest_params["block_align"] = 0
elif dest_params.get("compression_manager") == "autoskip":
# disabling compression parallel threads if mixed manager is being used as the predictor is not thread safe
dest_params["compression_parallel_threads"] = 1
# esuring the compression is being used
# ensuring the compression is being used
if dest_params.get("compression_type") == "none":
dest_params["compression_type"] = random.choice(
["snappy", "zlib", "lz4", "lz4hc", "xpress", "zstd"]
+2
View File
@@ -197,6 +197,8 @@ int SSTDumpTool::Run(int argc, char const* const* argv, Options options) {
int64_t tmp_val;
TEST_AllowUnsupportedFormatVersion() = true;
for (int i = 1; i < argc; i++) {
if (strncmp(argv[i], "--env_uri=", 10) == 0) {
env_uri = argv[i] + 10;
@@ -1 +0,0 @@
* `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.
@@ -1 +0,0 @@
* 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 +0,0 @@
Fix iterator operations returning NotImplemented status if disallow_memtable_writes and paranoid_memory_checks CF options are both set.
@@ -1 +0,0 @@
* 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 `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 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.
+37 -17
View File
@@ -8,6 +8,7 @@
#include "options/options_helper.h"
#include "rocksdb/advanced_compression.h"
#include "test_util/sync_point.h"
#include "util/random.h"
namespace ROCKSDB_NAMESPACE {
@@ -31,9 +32,10 @@ bool CompressionRejectionProbabilityPredictor::Record(
} else {
compressed_count_++;
}
if (attempted_compression_count() >= window_size_) {
pred_rejection_prob_percentage_ = static_cast<int>(
rejected_count_ * 100 / (compressed_count_ + rejected_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);
@@ -41,34 +43,43 @@ bool CompressionRejectionProbabilityPredictor::Record(
return true;
}
AutoSkipCompressorWrapper::AutoSkipCompressorWrapper(
std::unique_ptr<Compressor> compressor, const CompressionOptions& opts,
const CompressionType type)
std::unique_ptr<Compressor> compressor, const CompressionOptions& opts)
: CompressorWrapper::CompressorWrapper(std::move(compressor)),
opts_(opts),
type_(type),
kOpts(opts),
predictor_(
std::make_shared<CompressionRejectionProbabilityPredictor>(10)) {
(void)type_;
(void)opts_;
std::make_shared<CompressionRejectionProbabilityPredictor>(10)) {}
const char* AutoSkipCompressorWrapper::Name() const {
return "AutoSkipCompressorWrapper";
}
Status AutoSkipCompressorWrapper::CompressBlock(
Slice uncompressed_data, std::string* compressed_output,
CompressionType* out_compression_type, ManagedWorkingArea* wa) {
// Check if the managed working area is provided or owned by this object.
// If not, bypass auto-skip logic since the working area lacks a predictor to
// record or make necessary decisions to compress or bypass compression of the
// block
if (wa == nullptr || wa->owner() != this) {
return wrapped_->CompressBlock(uncompressed_data, compressed_output,
out_compression_type, wa);
}
bool exploration =
Random::GetTLSInstance()->PercentTrue(kExplorationPercentage);
TEST_SYNC_POINT_CALLBACK(
"AutoSkipCompressorWrapper::CompressBlock::exploitOrExplore",
&exploration);
auto autoskip_wa = static_cast<AutoSkipWorkingArea*>(wa->get());
if (exploration) {
return CompressBlockAndRecord(uncompressed_data, compressed_output,
out_compression_type, wa);
out_compression_type, autoskip_wa);
} else {
auto prediction = predictor_->Predict();
auto predictor_ptr = autoskip_wa->predictor;
auto prediction = predictor_ptr->Predict();
if (prediction <= kProbabilityCutOff) {
// decide to compress
return CompressBlockAndRecord(uncompressed_data, compressed_output,
out_compression_type, wa);
out_compression_type, autoskip_wa);
} else {
// decide to bypass compression
*out_compression_type = kNoCompression;
@@ -78,13 +89,22 @@ Status AutoSkipCompressorWrapper::CompressBlock(
return Status::OK();
}
Compressor::ManagedWorkingArea AutoSkipCompressorWrapper::ObtainWorkingArea() {
auto wrap_wa = wrapped_->ObtainWorkingArea();
return ManagedWorkingArea(new AutoSkipWorkingArea(std::move(wrap_wa)), this);
}
void AutoSkipCompressorWrapper::ReleaseWorkingArea(WorkingArea* wa) {
delete static_cast<AutoSkipWorkingArea*>(wa);
}
Status AutoSkipCompressorWrapper::CompressBlockAndRecord(
Slice uncompressed_data, std::string* compressed_output,
CompressionType* out_compression_type, ManagedWorkingArea* wa) {
CompressionType* out_compression_type, AutoSkipWorkingArea* wa) {
Status status = wrapped_->CompressBlock(uncompressed_data, compressed_output,
out_compression_type, wa);
out_compression_type, &(wa->wrapped));
// determine if it was rejected or compressed
predictor_->Record(uncompressed_data, compressed_output, opts_);
auto predictor_ptr = wa->predictor;
predictor_ptr->Record(uncompressed_data, compressed_output, kOpts);
return status;
}
@@ -100,7 +120,7 @@ std::unique_ptr<Compressor> AutoSkipCompressorManager::GetCompressorForSST(
assert(GetSupportedCompressions().size() > 1);
assert(preferred != kNoCompression);
return std::make_unique<AutoSkipCompressorWrapper>(
wrapped_->GetCompressorForSST(context, opts, preferred), opts, preferred);
wrapped_->GetCompressorForSST(context, opts, preferred), opts);
}
std::shared_ptr<CompressionManagerWrapper> CreateAutoSkipCompressionManager(
+30 -7
View File
@@ -10,11 +10,9 @@
#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)
@@ -34,25 +32,50 @@ class CompressionRejectionProbabilityPredictor {
size_t window_size_;
};
class AutoSkipWorkingArea : public Compressor::WorkingArea {
public:
explicit AutoSkipWorkingArea(Compressor::ManagedWorkingArea&& wa)
: wrapped(std::move(wa)),
predictor(
std::make_shared<CompressionRejectionProbabilityPredictor>(10)) {}
~AutoSkipWorkingArea() {}
AutoSkipWorkingArea(const AutoSkipWorkingArea&) = delete;
AutoSkipWorkingArea& operator=(const AutoSkipWorkingArea&) = delete;
AutoSkipWorkingArea(AutoSkipWorkingArea&& other) noexcept
: wrapped(std::move(other.wrapped)),
predictor(std::move(other.predictor)) {}
AutoSkipWorkingArea& operator=(AutoSkipWorkingArea&& other) noexcept {
if (this != &other) {
wrapped = std::move(other.wrapped);
predictor = std::move(other.predictor);
}
return *this;
}
Compressor::ManagedWorkingArea wrapped;
std::shared_ptr<CompressionRejectionProbabilityPredictor> predictor;
};
class AutoSkipCompressorWrapper : public CompressorWrapper {
public:
const char* Name() const override;
explicit AutoSkipCompressorWrapper(std::unique_ptr<Compressor> compressor,
const CompressionOptions& opts,
const CompressionType type);
const CompressionOptions& opts);
Status CompressBlock(Slice uncompressed_data, std::string* compressed_output,
CompressionType* out_compression_type,
ManagedWorkingArea* wa) override;
ManagedWorkingArea ObtainWorkingArea() override;
void ReleaseWorkingArea(WorkingArea* wa) override;
private:
Status CompressBlockAndRecord(Slice uncompressed_data,
std::string* compressed_output,
CompressionType* out_compression_type,
ManagedWorkingArea* wa);
AutoSkipWorkingArea* wa);
static constexpr int kExplorationPercentage = 10;
static constexpr int kProbabilityCutOff = 50;
const CompressionOptions& opts_;
const CompressionType type_;
const CompressionOptions kOpts;
std::shared_ptr<CompressionRejectionProbabilityPredictor> predictor_;
};
+138 -27
View File
@@ -7,6 +7,7 @@
#include "options/options_helper.h"
#include "rocksdb/convenience.h"
#include "rocksdb/utilities/object_registry.h"
namespace ROCKSDB_NAMESPACE {
@@ -132,8 +133,8 @@ Status Decompressor::ExtractUncompressedSize(Args& args) {
// payload. (RocksDB compress_format_version=2 except Snappy)
//
// This is historically a varint32, but it is preliminarily generalized
// to varint64. (TODO: support that on the write side, at least for some
// codecs, in BBT format_version=7)
// to varint64, in case that is supported on the write side for some
// algorithms.
if (LIKELY(GetVarint64(&args.compressed_data, &args.uncompressed_size))) {
if (LIKELY(args.uncompressed_size <= SIZE_MAX)) {
return Status::OK();
@@ -155,6 +156,8 @@ namespace {
class BuiltinCompressorV1 : public Compressor {
public:
const char* Name() const override { return "BuiltinCompressorV1"; }
explicit BuiltinCompressorV1(const CompressionOptions& opts,
CompressionType type)
: opts_(opts), type_(type) {
@@ -192,6 +195,8 @@ class BuiltinCompressorV1 : public Compressor {
class BuiltinCompressorV2 : public Compressor {
public:
const char* Name() const override { return "BuiltinCompressorV2"; }
explicit BuiltinCompressorV2(const CompressionOptions& opts,
CompressionType type,
CompressionDict&& dict = {})
@@ -248,8 +253,7 @@ class BuiltinCompressorV2 : public Compressor {
// TODO: use ZSTD_CCtx directly
ManagedWorkingArea ObtainWorkingArea() override {
return ManagedWorkingArea(
static_cast<WorkingArea*>(new CompressionContext(type_, opts_)), this);
return ManagedWorkingArea(new CompressionContext(type_, opts_), this);
}
void ReleaseWorkingArea(WorkingArea* wa) override {
delete static_cast<CompressionContext*>(wa);
@@ -348,6 +352,8 @@ class BuiltinCompressionManagerV1 : public CompressionManager {
std::unique_ptr<Compressor> GetCompressor(const CompressionOptions& opts,
CompressionType type) override {
// At the time of deprecating the writing of new format_version=1 files,
// ZSTD was the last supported built-in compression type.
if (type > kZSTD) {
// Unrecognized; fall back on default compression
type = ColumnFamilyOptions{}.compression;
@@ -363,6 +369,10 @@ class BuiltinCompressionManagerV1 : public CompressionManager {
return std::shared_ptr<Decompressor>(shared_from_this(), &decompressor_);
}
bool SupportsCompressionType(CompressionType type) const override {
return CompressionTypeSupported(type);
}
protected:
BuiltinDecompressorV1 decompressor_;
};
@@ -665,6 +675,34 @@ class BuiltinDecompressorV2 : public Decompressor {
}
};
class BuiltinDecompressorV2SnappyOnly : public BuiltinDecompressorV2 {
public:
const char* Name() const override {
return "BuiltinDecompressorV2SnappyOnly";
}
Status ExtractUncompressedSize(Args& args) override {
assert(args.compression_type == kSnappyCompression);
#ifdef SNAPPY
size_t uncompressed_length = 0;
if (!snappy::GetUncompressedLength(args.compressed_data.data(),
args.compressed_data.size(),
&uncompressed_length)) {
return Status::Corruption("Error reading snappy compressed length");
}
args.uncompressed_size = uncompressed_length;
return Status::OK();
#else
return Status::NotSupported("Snappy not supported in this build");
#endif
}
Status DecompressBlock(const Args& args, char* uncompressed_output) override {
assert(args.compression_type == kSnappyCompression);
return Snappy_DecompressBlock(args, uncompressed_output);
}
};
class BuiltinDecompressorV2WithDict : public BuiltinDecompressorV2 {
public:
explicit BuiltinDecompressorV2WithDict(const Slice& dict) : dict_(dict) {}
@@ -708,6 +746,17 @@ class BuiltinDecompressorV2WithDict : public BuiltinDecompressorV2 {
Status BuiltinDecompressorV2::MaybeCloneForDict(
const Slice& dict, std::unique_ptr<Decompressor>* out) {
// Because of unfortunate decisions in handling built-in compression types,
// all the compression types before ZSTD that do not actually support
// dictionary compression pretend to support it. Specifically, we have to be
// able to read files with a compression dictionary block using those
// compression types even though the compression dictionary is ignored by
// the compression algorithm. And the Decompressor has to return the
// configured dictionary from GetSerializedDict() even if it is ignored. This
// unfortunately means that a new schema version (BuiltinV3?) would be needed
// toactually support dictionary compression in the future for these
// algorithms (if the libraries add support).
// TODO: can we make this a better/cleaner experience?
*out = std::make_unique<BuiltinDecompressorV2WithDict>(dict);
return Status::OK();
}
@@ -824,7 +873,7 @@ class BuiltinCompressionManagerV2 : public CompressionManager {
// No acceptable compression ratio => no compression
return nullptr;
}
if (type > kZSTD) {
if (type > kLastBuiltinCompression) {
// Unrecognized; fall back on default compression
type = ColumnFamilyOptions{}.compression;
}
@@ -851,16 +900,40 @@ class BuiltinCompressionManagerV2 : public CompressionManager {
std::shared_ptr<Decompressor> GetDecompressorForTypes(
const CompressionType* types_begin,
const CompressionType* types_end) override {
if (std::find(types_begin, types_end, kZSTD)) {
if (types_begin == types_end) {
return nullptr;
} else if (types_begin + 1 == types_end &&
*types_begin == kSnappyCompression) {
return GetSnappyDecompressor();
} else if (std::find(types_begin, types_end, kZSTD)) {
return GetZstdDecompressor();
} else {
return GetGeneralDecompressor();
}
}
std::shared_ptr<Decompressor> GetDecompressorForCompressor(
const Compressor& compressor) override {
#ifdef ROCKSDB_USE_RTTI
// To be extra safe, only optimize here if we are certain we are not
// looking at a wrapped compressor, so that we are sure it only uses that
// one compression type.
if (dynamic_cast<const BuiltinCompressorV2*>(&compressor)) {
CompressionType type = compressor.GetPreferredCompressionType();
return GetDecompressorForTypes(&type, &type + 1);
}
#endif
// Fallback
return CompressionManager::GetDecompressorForCompressor(compressor);
}
bool SupportsCompressionType(CompressionType type) const override {
return CompressionTypeSupported(type);
}
protected:
BuiltinDecompressorV2 decompressor_;
BuiltinDecompressorV2OptimizeZstd zstd_decompressor_;
BuiltinDecompressorV2SnappyOnly snappy_decompressor_;
inline std::shared_ptr<Decompressor> GetGeneralDecompressor() {
return std::shared_ptr<Decompressor>(shared_from_this(), &decompressor_);
@@ -870,6 +943,11 @@ class BuiltinCompressionManagerV2 : public CompressionManager {
return std::shared_ptr<Decompressor>(shared_from_this(),
&zstd_decompressor_);
}
inline std::shared_ptr<Decompressor> GetSnappyDecompressor() {
return std::shared_ptr<Decompressor>(shared_from_this(),
&snappy_decompressor_);
}
};
const std::shared_ptr<BuiltinCompressionManagerV1>
@@ -882,35 +960,68 @@ const std::shared_ptr<BuiltinCompressionManagerV2>
} // namespace
Status CompressionManager::CreateFromString(
const ConfigOptions& /*config_options*/, const std::string& id,
const ConfigOptions& config_options, const std::string& value,
std::shared_ptr<CompressionManager>* result) {
if (id == kNullptrString || id.empty()) {
if (value == kNullptrString || value.empty()) {
result->reset();
return Status::OK();
} else if (id.compare(kBuiltinCompressionManagerV1->CompatibilityName()) ==
0 ||
id.compare(kBuiltinCompressionManagerV1->Name()) == 0) {
*result = kBuiltinCompressionManagerV1;
return Status::OK();
} else if (id.compare(kBuiltinCompressionManagerV2->CompatibilityName()) ==
0 ||
id.compare(kBuiltinCompressionManagerV2->Name()) == 0) {
*result = kBuiltinCompressionManagerV2;
return Status::OK();
} else {
return Status::NotFound("Compatible compression manager for \"" + id +
"\"");
}
static std::once_flag loaded;
std::call_once(loaded, [&]() {
auto& library = *ObjectLibrary::Default();
// TODO: try to enhance ObjectLibrary to support singletons
library.AddFactory<CompressionManager>(
kBuiltinCompressionManagerV1->CompatibilityName(),
[](const std::string& /*uri*/,
std::unique_ptr<CompressionManager>* guard,
std::string* /*errmsg*/) {
*guard = std::make_unique<BuiltinCompressionManagerV1>();
return guard->get();
});
library.AddFactory<CompressionManager>(
kBuiltinCompressionManagerV2->CompatibilityName(),
[](const std::string& /*uri*/,
std::unique_ptr<CompressionManager>* guard,
std::string* /*errmsg*/) {
*guard = std::make_unique<BuiltinCompressionManagerV2>();
return guard->get();
});
});
std::string id;
std::unordered_map<std::string, std::string> opt_map;
Status status = Customizable::GetOptionsMap(config_options, result->get(),
value, &id, &opt_map);
if (!status.ok()) { // GetOptionsMap failed
return status;
} else if (id.empty()) { // We have no Id but have options. Not good
return Status::NotSupported("Cannot reset object ", id);
} else {
status = config_options.registry->NewSharedObject(id, result);
}
if (config_options.ignore_unsupported_options && status.IsNotSupported()) {
return Status::OK();
} else if (status.ok()) {
status = Customizable::ConfigureNewObject(config_options, result->get(),
opt_map);
}
return status;
}
Status CompressionManager::FindCompatibleCompressionManager(
Slice compatibility_name, std::shared_ptr<CompressionManager>* out) {
std::shared_ptr<CompressionManager>
CompressionManager::FindCompatibleCompressionManager(Slice compatibility_name) {
if (compatibility_name.compare(CompatibilityName()) == 0) {
*out = shared_from_this();
return Status::OK();
return shared_from_this();
} else {
return CreateFromString(ConfigOptions(), compatibility_name.ToString(),
out);
std::shared_ptr<CompressionManager> out;
Status s =
CreateFromString(ConfigOptions(), compatibility_name.ToString(), &out);
if (s.ok()) {
return out;
} else {
return nullptr;
}
}
}
+8 -7
View File
@@ -28,6 +28,7 @@
#include "table/block_based/block_type.h"
#include "test_util/sync_point.h"
#include "util/atomic.h"
#include "util/cast_util.h"
#include "util/coding.h"
#include "util/compression_context_cache.h"
#include "util/string_util.h"
@@ -247,6 +248,7 @@ struct DecompressorDict {
decompressor_ = std::make_unique<FailureDecompressor>(std::move(s));
} else {
assert(s.ok());
assert(decompressor_->GetSerializedDict() == dict);
}
memory_usage_ = sizeof(struct DecompressorDict);
@@ -697,8 +699,7 @@ inline bool CompressionTypeSupported(CompressionType compression_type) {
return XPRESS_Supported();
case kZSTD:
return ZSTD_Supported();
default:
assert(false);
default: // Including custom compression types
return false;
}
}
@@ -726,8 +727,7 @@ inline bool DictCompressionTypeSupported(CompressionType compression_type) {
// NB: dictionary supported since 0.5.0. See ZSTD_VERSION_NUMBER check
// above.
return ZSTD_Supported();
default:
assert(false);
default: // Including custom compression types
return false;
}
}
@@ -753,9 +753,10 @@ inline std::string CompressionTypeToString(CompressionType compression_type) {
return "ZSTD";
case kDisableCompressionOption:
return "DisableOption";
default:
assert(false);
return "";
default: {
char c = lossless_cast<char>(compression_type);
return "Custom" + Slice(&c, 1).ToString(/*hex=*/true);
}
}
}
+48 -35
View File
@@ -32,7 +32,7 @@ class AutoSkipTestFlushBlockPolicy : public FlushBlockPolicy {
statistics_(statistics) {}
bool Update(const Slice& /*key*/, const Slice& /*value*/) override {
auto multiple_of_10 = num_keys_ / window_;
auto nth_window = num_keys_ / window_;
if (data_block_builder_.empty()) {
// First key in this block
return false;
@@ -49,9 +49,10 @@ class AutoSkipTestFlushBlockPolicy : public FlushBlockPolicy {
};
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
// We force exploration to set the predicted rejection ratio and then test
// that the prediction is exploited
if (multiple_of_10 % 2 == 0) {
// We force exploration to set the predicted rejection ratio for odd
// window and then test that the prediction is exploited in the even
// window
if (nth_window % 2 == 0) {
SyncPoint::GetInstance()->SetCallBack(
"AutoSkipCompressorWrapper::CompressBlock::exploitOrExplore",
set_exploration);
@@ -72,36 +73,43 @@ class AutoSkipTestFlushBlockPolicy : public FlushBlockPolicy {
bypassed_percentage = static_cast<int>(bypassed_count * 100 / total);
compressed_percentage =
static_cast<int>(compressed_count * 100 / total);
}
// use mulitple of 10 to get correct assertion
switch (multiple_of_10) {
case 1:
// This is exploration stage in which we set the rejection ratio to
// 0.6
EXPECT_EQ(rejection_percentage, 60);
EXPECT_EQ(bypassed_percentage, 0);
EXPECT_EQ(compressed_percentage, 40);
break;
case 2:
// With the rejection ratio set to 0.6 all the blocks should be
// bypassed in next window
EXPECT_EQ(rejection_percentage, 0);
EXPECT_EQ(bypassed_percentage, 100);
EXPECT_EQ(compressed_percentage, 0);
break;
case 3:
// This is exploration stage in which we set the rejection ratio to
// 0.4
EXPECT_EQ(rejection_percentage, 40);
EXPECT_EQ(bypassed_percentage, 0);
EXPECT_EQ(compressed_percentage, 60);
break;
case 4:
// With the rejection ratio set to 0.4 all the blocks should be
// attempted to be compressed
EXPECT_EQ(rejection_percentage, 60);
EXPECT_EQ(bypassed_percentage, 0);
EXPECT_EQ(compressed_percentage, 40);
// use nth window to detect test cases and set the expected
switch (nth_window) {
case 1:
// In first window we only explore and thus here we verify that the
// correct prediction has been made by the end of the window
// Since 6 of 10 blocks are compression unfriendly, the predicted
// rejection ratio should be 60%
EXPECT_EQ(rejection_percentage, 60);
EXPECT_EQ(bypassed_percentage, 0);
EXPECT_EQ(compressed_percentage, 40);
break;
case 2:
// With the rejection ratio set to 0.6 all the blocks should be
// bypassed in next window
EXPECT_EQ(rejection_percentage, 0);
EXPECT_EQ(bypassed_percentage, 100);
EXPECT_EQ(compressed_percentage, 0);
break;
case 3:
// In third window we only explore and verify that the correct
// prediction has been made by the end of the window
// since 4 of 10 blocks are compression ufriendly, the predicted
// rejection ratio should be 40%
EXPECT_EQ(rejection_percentage, 40);
EXPECT_EQ(bypassed_percentage, 0);
EXPECT_EQ(compressed_percentage, 60);
break;
case 4:
// With the rejection ratio set to 0.4 all the blocks should be
// attempted to be compressed
// 6 of 10 blocks are compression unfriendly and thus should be
// rejected 4 of 10 blocks are compression friendly and thus should
// be compressed
EXPECT_EQ(rejection_percentage, 60);
EXPECT_EQ(bypassed_percentage, 0);
EXPECT_EQ(compressed_percentage, 40);
}
}
}
num_keys_++;
@@ -182,7 +190,12 @@ class DBAutoSkip : public DBTestBase {
}
};
TEST_F(DBAutoSkip, AutoSkipCompressionManager) {
// FIXME: the test is failing the assertion in auto_skip_compressor.cc
// when run on nightly build in build-linux-arm-test-full mode [1].
//
// [1]
// auto_skip_compressor.cc:101: Assertion `preferred != kNoCompression' failed.
TEST_F(DBAutoSkip, DISABLED_AutoSkipCompressionManager) {
if (GetSupportedCompressions().size() > 1) {
const int kValueSize = 20000;
// This will set the rejection ratio to 60%
+23 -24
View File
@@ -16,20 +16,16 @@ namespace ROCKSDB_NAMESPACE {
// MultiCompressorWrapper implementation
MultiCompressorWrapper::MultiCompressorWrapper(const CompressionOptions& opts,
CompressionType type,
CompressionDict&& dict) {
assert(type != kNoCompression);
assert(type == kZSTD);
auto builtInManager = GetDefaultBuiltinCompressionManager();
const auto& compressions = GetSupportedCompressions();
for (auto type_ : compressions) {
if (type_ == kNoCompression) {
for (auto type : compressions) {
if (type == kNoCompression) {
continue;
}
compressors_.push_back(builtInManager->GetCompressor(opts, type_));
compressors_.push_back(builtInManager->GetCompressor(opts, type));
}
(void)dict;
(void)type;
}
size_t MultiCompressorWrapper::GetMaxSampleSizeIfWantDict(
@@ -42,7 +38,7 @@ Slice MultiCompressorWrapper::GetSerializedDict() const {
}
CompressionType MultiCompressorWrapper::GetPreferredCompressionType() const {
return kZSTD;
return compressors_.back()->GetPreferredCompressionType();
}
Compressor::ManagedWorkingArea MultiCompressorWrapper::ObtainWorkingArea() {
@@ -51,10 +47,17 @@ Compressor::ManagedWorkingArea MultiCompressorWrapper::ObtainWorkingArea() {
std::unique_ptr<Compressor> MultiCompressorWrapper::MaybeCloneSpecialized(
CacheEntryRole block_type, DictSampleArgs&& dict_samples) {
// TODO: full dictionary compression support. Currently this just falls
// back on a non-multi compressor when asked to use a dictionary.
return compressors_.back()->MaybeCloneSpecialized(block_type,
std::move(dict_samples));
}
// RandomMixedCompressor implementation
const char* RandomMixedCompressor::Name() const {
return "RandomMixedCompressor";
}
Status RandomMixedCompressor::CompressBlock(
Slice uncompressed_data, std::string* compressed_output,
CompressionType* out_compression_type, ManagedWorkingArea* wa) {
@@ -66,19 +69,20 @@ Status RandomMixedCompressor::CompressBlock(
}
const char* RandomMixedCompressionManager::Name() const {
return wrapped_->Name();
// return "RandomMixedCompressionManager";
return "RandomMixedCompressionManager";
}
std::unique_ptr<Compressor> RandomMixedCompressionManager::GetCompressorForSST(
const FilterBuildingContext& context, const CompressionOptions& opts,
CompressionType preferred) {
assert(preferred == kZSTD);
(void)context;
return std::make_unique<RandomMixedCompressor>(opts, preferred);
const FilterBuildingContext& /*context*/, const CompressionOptions& opts,
CompressionType /*preferred*/) {
return std::make_unique<RandomMixedCompressor>(opts);
}
// RoundRobinCompressor implementation
const char* RoundRobinCompressor::Name() const {
return "RoundRobinCompressor";
}
Status RoundRobinCompressor::CompressBlock(
Slice uncompressed_data, std::string* compressed_output,
CompressionType* out_compression_type, ManagedWorkingArea* wa) {
@@ -92,17 +96,12 @@ Status RoundRobinCompressor::CompressBlock(
RelaxedAtomic<uint64_t> RoundRobinCompressor::block_counter{0};
// RoundRobinManager implementation
const char* RoundRobinManager::Name() const {
// return "RoundRobinManager";
return wrapped_->Name();
}
const char* RoundRobinManager::Name() const { return "RoundRobinManager"; }
std::unique_ptr<Compressor> RoundRobinManager::GetCompressorForSST(
const FilterBuildingContext& context, const CompressionOptions& opts,
CompressionType preferred) {
assert(preferred == kZSTD);
(void)context;
return std::make_unique<RoundRobinCompressor>(opts, preferred);
const FilterBuildingContext& /*context*/, const CompressionOptions& opts,
CompressionType /*preferred*/) {
return std::make_unique<RoundRobinCompressor>(opts);
}
} // namespace ROCKSDB_NAMESPACE
+2 -2
View File
@@ -8,7 +8,6 @@
#pragma once
#include <memory>
#include <mutex>
#include <vector>
#include "compression.h"
@@ -19,7 +18,6 @@ namespace ROCKSDB_NAMESPACE {
class MultiCompressorWrapper : public Compressor {
public:
explicit MultiCompressorWrapper(const CompressionOptions& opts,
CompressionType type,
CompressionDict&& dict = {});
size_t GetMaxSampleSizeIfWantDict(CacheEntryRole block_type) const override;
@@ -35,6 +33,7 @@ class MultiCompressorWrapper : public Compressor {
struct RandomMixedCompressor : public MultiCompressorWrapper {
using MultiCompressorWrapper::MultiCompressorWrapper;
const char* Name() const override;
Status CompressBlock(Slice uncompressed_data, std::string* compressed_output,
CompressionType* out_compression_type,
ManagedWorkingArea* wa) override;
@@ -50,6 +49,7 @@ class RandomMixedCompressionManager : public CompressionManagerWrapper {
struct RoundRobinCompressor : public MultiCompressorWrapper {
using MultiCompressorWrapper::MultiCompressorWrapper;
const char* Name() const override;
Status CompressBlock(Slice uncompressed_data, std::string* compressed_output,
CompressionType* out_compression_type,
ManagedWorkingArea* wa) override;
@@ -17,16 +17,19 @@
namespace ROCKSDB_NAMESPACE {
CompactOnDeletionCollector::CompactOnDeletionCollector(
size_t sliding_window_size, size_t deletion_trigger, double deletion_ratio)
size_t sliding_window_size, size_t deletion_trigger, double deletion_ratio,
uint64_t min_file_size)
: bucket_size_((sliding_window_size + kNumBuckets - 1) / kNumBuckets),
current_bucket_(0),
num_keys_in_current_bucket_(0),
num_deletions_in_observation_window_(0),
deletion_trigger_(deletion_trigger),
deletion_ratio_(deletion_ratio),
min_file_size_(min_file_size),
cur_file_size_(0),
max_deletion_in_window_(0),
deletion_ratio_enabled_(deletion_ratio > 0 && deletion_ratio <= 1),
need_compaction_(false),
finished_(false) {
need_compaction_(false) {
memset(num_deletions_in_buckets_, 0, sizeof(size_t) * kNumBuckets);
}
@@ -39,7 +42,7 @@ Status CompactOnDeletionCollector::AddUserKey(const Slice& /*key*/,
const Slice& /*value*/,
EntryType type,
SequenceNumber /*seq*/,
uint64_t /*file_size*/) {
uint64_t file_size) {
assert(!finished_);
if (!bucket_size_ && !deletion_ratio_enabled_) {
// This collector is effectively disabled
@@ -51,11 +54,14 @@ Status CompactOnDeletionCollector::AddUserKey(const Slice& /*key*/,
return Status::OK();
}
const bool is_delete = (type == kEntryDelete || type == kEntrySingleDelete ||
type == kEntryDeleteWithTimestamp);
if (deletion_ratio_enabled_) {
total_entries_++;
if (type == kEntryDelete) {
if (is_delete) {
deletion_entries_++;
}
cur_file_size_ = file_size;
}
if (bucket_size_) {
@@ -76,13 +82,20 @@ Status CompactOnDeletionCollector::AddUserKey(const Slice& /*key*/,
}
num_keys_in_current_bucket_++;
if (type == kEntryDelete) {
if (is_delete) {
num_deletions_in_observation_window_++;
num_deletions_in_buckets_[current_bucket_]++;
if (num_deletions_in_observation_window_ >= deletion_trigger_) {
need_compaction_ = true;
if (num_deletions_in_observation_window_ >= max_deletion_in_window_) {
max_deletion_in_window_ = num_deletions_in_observation_window_;
}
}
// The file may qualify for compaction based on file size constraints,
// even if max_deletion_in_window_ is not updated.
if (max_deletion_in_window_ >= deletion_trigger_ &&
file_size >= min_file_size_) {
need_compaction_ = true;
}
}
return Status::OK();
@@ -90,7 +103,8 @@ Status CompactOnDeletionCollector::AddUserKey(const Slice& /*key*/,
Status CompactOnDeletionCollector::Finish(
UserCollectedProperties* /*properties*/) {
if (!need_compaction_ && deletion_ratio_enabled_ && total_entries_ > 0) {
if (!need_compaction_ && deletion_ratio_enabled_ && total_entries_ > 0 &&
cur_file_size_ >= min_file_size_) {
double ratio = static_cast<double>(deletion_entries_) / total_entries_;
need_compaction_ = ratio >= deletion_ratio_;
}
@@ -153,23 +167,43 @@ static std::unordered_map<std::string, OptionTypeInfo>
return Status::OK();
},
nullptr}},
{"min_file_size",
{0, OptionType::kUnknown, OptionVerificationType::kNormal,
OptionTypeFlags::kCompareNever | OptionTypeFlags::kMutable,
[](const ConfigOptions&, const std::string&, const std::string& value,
void* addr) {
auto* factory =
static_cast<CompactOnDeletionCollectorFactory*>(addr);
factory->SetMinFileSize(ParseUint64(value));
return Status::OK();
},
[](const ConfigOptions&, const std::string&, const void* addr,
std::string* value) {
const auto* factory =
static_cast<const CompactOnDeletionCollectorFactory*>(addr);
*value = std::to_string(factory->GetMinFileSize());
return Status::OK();
},
nullptr}},
};
CompactOnDeletionCollectorFactory::CompactOnDeletionCollectorFactory(
size_t sliding_window_size, size_t deletion_trigger, double deletion_ratio)
size_t sliding_window_size, size_t deletion_trigger, double deletion_ratio,
uint64_t min_file_size)
: sliding_window_size_(sliding_window_size),
deletion_trigger_(deletion_trigger),
deletion_ratio_(deletion_ratio) {
deletion_ratio_(deletion_ratio),
min_file_size_(min_file_size) {
RegisterOptions("", this, &on_deletion_collector_type_info);
}
TablePropertiesCollector*
CompactOnDeletionCollectorFactory::CreateTablePropertiesCollector(
TablePropertiesCollectorFactory::Context /*context*/) {
return new CompactOnDeletionCollector(sliding_window_size_.load(),
deletion_trigger_.load(),
deletion_ratio_.load());
return new CompactOnDeletionCollector(
sliding_window_size_.load(), deletion_trigger_.load(),
deletion_ratio_.load(), min_file_size_.load());
}
std::string CompactOnDeletionCollectorFactory::ToString() const {
@@ -183,10 +217,12 @@ std::string CompactOnDeletionCollectorFactory::ToString() const {
std::shared_ptr<CompactOnDeletionCollectorFactory>
NewCompactOnDeletionCollectorFactory(size_t sliding_window_size,
size_t deletion_trigger,
double deletion_ratio) {
double deletion_ratio,
uint64_t min_file_size) {
return std::shared_ptr<CompactOnDeletionCollectorFactory>(
new CompactOnDeletionCollectorFactory(sliding_window_size,
deletion_trigger, deletion_ratio));
deletion_trigger, deletion_ratio,
min_file_size));
}
namespace {
@@ -11,7 +11,8 @@ namespace ROCKSDB_NAMESPACE {
class CompactOnDeletionCollector : public TablePropertiesCollector {
public:
CompactOnDeletionCollector(size_t sliding_window_size,
size_t deletion_trigger, double deletion_raatio);
size_t deletion_trigger, double deletion_ratio,
uint64_t min_file_size);
// AddUserKey() will be called when a new key/value pair is inserted into the
// table.
@@ -36,7 +37,7 @@ class CompactOnDeletionCollector : public TablePropertiesCollector {
// The name of the properties collector can be used for debugging purpose.
const char* Name() const override { return "CompactOnDeletionCollector"; }
// EXPERIMENTAL Return whether the output file should be further compacted
// Return whether the output file should be further compacted
bool NeedCompact() const override { return need_compaction_; }
static const int kNumBuckets = 128;
@@ -48,18 +49,21 @@ class CompactOnDeletionCollector : public TablePropertiesCollector {
// "bucket_size_" keys.
size_t num_deletions_in_buckets_[kNumBuckets];
// the number of keys in a bucket
size_t bucket_size_;
const size_t bucket_size_;
size_t current_bucket_;
size_t num_keys_in_current_bucket_;
size_t num_deletions_in_observation_window_;
size_t deletion_trigger_;
const size_t deletion_trigger_;
const double deletion_ratio_;
const bool deletion_ratio_enabled_;
size_t total_entries_ = 0;
size_t deletion_entries_ = 0;
const size_t min_file_size_;
size_t cur_file_size_;
size_t max_deletion_in_window_;
const bool deletion_ratio_enabled_;
// true if the current SST file needs to be compacted.
bool need_compaction_;
bool finished_;
bool finished_ = false;
};
} // namespace ROCKSDB_NAMESPACE
@@ -232,6 +232,61 @@ TEST(CompactOnDeletionCollector, SlidingWindow) {
}
}
TEST(CompactOnDeletionCollector, MinFileSize) {
TablePropertiesCollectorFactory::Context context;
context.column_family_id =
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily;
context.last_level_inclusive_max_seqno_threshold = kMaxSequenceNumber;
const size_t kWindowSize = 1000;
const size_t kDeletionTrigger = 800;
const double kDeletionRatio = 0.9;
const uint64_t kMinFileSize = 1 << 20;
for (uint64_t file_size : {(uint64_t)0, kMinFileSize - 1, kMinFileSize}) {
{
auto factory = NewCompactOnDeletionCollectorFactory(
kWindowSize, kDeletionTrigger, 0, kMinFileSize);
std::unique_ptr<TablePropertiesCollector> collector(
factory->CreateTablePropertiesCollector(context));
// Add enough deletions to meet the sliding window triggers
for (size_t i = 0; i < kWindowSize; i++) {
if (i < kDeletionTrigger) {
ASSERT_OK(collector->AddUserKey("key", "value", kEntryDelete, 0,
file_size));
} else {
ASSERT_OK(
collector->AddUserKey("key", "value", kEntryPut, 0, file_size));
}
}
ASSERT_OK(collector->Finish(nullptr));
ASSERT_EQ(collector->NeedCompact(), file_size >= kMinFileSize);
}
{
auto factory = NewCompactOnDeletionCollectorFactory(
kWindowSize, kDeletionTrigger, kDeletionRatio, kMinFileSize);
std::unique_ptr<TablePropertiesCollector> collector(
factory->CreateTablePropertiesCollector(context));
const size_t kTotalEntries = 100;
// Add all deletions to maximize tombstone ratio
for (size_t i = 0; i < kTotalEntries - 1; i++) {
ASSERT_OK(
collector->AddUserKey("key", "value", kEntrySingleDelete, 0, 0));
}
// Give update file size
ASSERT_OK(collector->AddUserKey("key", "value", kEntrySingleDelete, 0,
file_size));
ASSERT_OK(collector->Finish(nullptr));
ASSERT_EQ(collector->NeedCompact(), file_size >= kMinFileSize);
}
}
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {