Compare commits

..
25 Commits
Author SHA1 Message Date
prerit 3a00304c7b update point_lock_manager.cc 2025-03-27 10:47:48 -07:00
prerit 7d971cbd33 Check for yields while waiting for lock in a loop 2025-03-27 10:38:01 -07:00
Hui Xiao 9072f5db09 Update for 10.1 release (#13485)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/13485

Reviewed By: jaykorean, pdillinger

Differential Revision: D71787995

Pulled By: hx235

fbshipit-source-id: 59b6ff7c824adbdef34b6ae12d7dbcc3e0852961
2025-03-25 14:55:07 -07:00
Peter Dillinger 49b0cb64df Fix uninitialized use in WBWIMemTable::Get (#13486)
Summary:
Based on passing address of uninit variable in ReadOnlyMemTable::Get() in memtable.h. The contract and other implementations suggest it is a pure out parameter that is always overwritten, so we initialize it in the function before checking its value in a loop

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

Test Plan: watch build-linux-valgrind in CI

Reviewed By: cbi42

Differential Revision: D71819843

Pulled By: pdillinger

fbshipit-source-id: 1e06f3ee6998099791af27de5b2872eb476ceb7c
2025-03-25 10:56:25 -07:00
Peter Dillinger 82794e0a4f Deprecate RangePtr, favor new RangeOpt and OptSlice (#13481)
Summary:
The new API in https://github.com/facebook/rocksdb/issues/13453 is awkward and precarious because of using RangePtr, which encodes optional keys using raw pointers to Slice. We could use `std::optional<Slice>` instead but that is unsatisfyingly a larger object with an inefficient size (typically 17 bytes).

Here I introduce a custom optional Slice type, `OptSlice`, that is the same size as a Slice, and use it in a number of places to clean up code and make some public APIs easier to work with. This includes

* `atomic_replace_range` (not yet released, OK to change)
* `GetAllKeyVersions()` which gets a behavior change because of its unusual handling of empty keys.
* `DeleteFilesInRanges()`
* TODO in follow-up: `CompactRange()`

Most of the diff is associated updates and refactorings. Also

* Move some relevant things out of db.h to keep it as tidy as possible.

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

Test Plan: tests updated

Reviewed By: hx235

Differential Revision: D71747774

Pulled By: pdillinger

fbshipit-source-id: b4c8519608d119b8bceca9bb0fd778608f62a141
2025-03-24 17:08:17 -07:00
Yu Zhang 934cf2d40d Implement the DB::GetPropertiesOfTablesForLevels API (#13469)
Summary:
As titled. This API returns the table properties of files per level. It can be handy for use cases that needed file's leveling info while retrieving TableProperties. We will use this API to later aggregate per level data write time info.

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

Test Plan: Added unit tests

Reviewed By: pdillinger

Differential Revision: D71353096

Pulled By: jowlyzhang

fbshipit-source-id: dc1fbb2c97e4365fc8d7241f9a59c65fbf4fb766
2025-03-21 17:23:01 -07:00
Yu Zhang 0b815cf3b3 Add a CompactionJobStats.num_input_files_trivially_moved field (#13479)
Summary:
This PR adds a new field `CompactionJobStats.num_input_files_trivially_moved` representing the number of files this compaction trivially moved. It should either equal to the total number of input files, or being 0.

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

Test Plan: Added tests

Reviewed By: hx235

Differential Revision: D71638796

Pulled By: jowlyzhang

fbshipit-source-id: 794c085408a0dc95f11874ca60fca3e6b5b92cba
2025-03-21 17:17:03 -07:00
Peter Dillinger 7f3ee34cdf Experimental ingestion option atomic_replace_range (#13453)
Summary:
Adding a new option (argument) for file ingestion `atomic_replace_range` which is intended to support a couple forms of "atomic replacement of a key range":
* (Experimental implementation here) With snapshot_consistency=false, the feature acts like an atomic DeleteFilesInRange prior to the ingestion, though requires no existing files to partially overlap the range. (Consider using SstPartitioner.) This is especially useful for "always compacted" workloads, perhaps along with CF option `disallow_memtable_writes` and ingestion option `fail_if_not_bottommost_level`. If both bounds are nullptr, the whole CF is replaced.
* (To implement in follow-up) With snapshot_consistency=true (and perhaps in some fallback cases from above such as partial overlap), a "giant tombstone file" as in https://github.com/facebook/rocksdb/issues/13078 is generated and ingested at the beginning of the list.

Because I see this as a more elaborate DeleteRange, I would naturally expect the upper bound/limit key to be exclusive, but it has been challenging getting that to work. The inclusive/exclusive handling is currently a documented bug for the experimental feature to sort out in follow-up work. (I would love to take advantage of proposed SliceBound, but that would be ambitious to adapt to DeleteRange. Even getting the "replace whole CF" variant of the functionality might be difficult to get worthing with DeleteRange underneath. Nevertheless, I feel it's best to consolidate these two forms of "atomic replacement" under variants of the same API.)

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

Test Plan:
Unit tests added / updated.

db_stress integration left as follow-up work (experimental feature, will be challenging)

Reviewed By: anand1976

Differential Revision: D71584295

Pulled By: pdillinger

fbshipit-source-id: 307abff426e4b7d0a340008918ebcddc896ef747
2025-03-21 15:55:41 -07:00
Maciej Szeszko d0374a0a72 Control SST write lifetime hints based on compaction style (#13472)
Summary:
This PR is a followup to https://github.com/facebook/rocksdb/pull/13461. We're introducing an experimental option / killswitch to control SST write lifetime hint calculation based on the selected compaction style. By default (and mostly for backwards compatibility reasons), we'll calculate the SST hints only for level compactions. With this change users have an option to configure SST lifetime hint policy in their environments to enable the calculations in the universal compaction mode as well. It's important to underline that as currently implemented, SST write lifetime hints are calculated in a static way and solely based on the level, which might not be suitable for non-uniform workloads with dynamic / high-variance lifespan of data within the same level. In those cases (or when the performance is not satisfactory), it's recommended to disable the hints by setting the set to empty. Please see the comment in `options.h` for more.

**NOTE:** We deliberately decided to introduce a new option to ensure no impact to external users running their RocksDB instances on local flash with the default `PosixWritableFile` file implementation.

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

Reviewed By: pdillinger, anand1976

Differential Revision: D71445488

Pulled By: mszeszko-meta

fbshipit-source-id: 57dc5e56662fa0b0fd686e183c0ec7090ff12d66
2025-03-21 13:10:43 -07:00
Jay Huh 12829883d7 Fix CompactionStats when max_sub_compaction > 1 (#13470)
Summary:
## Issue

Thanks to PRs https://github.com/facebook/rocksdb/issues/13455 and https://github.com/facebook/rocksdb/issues/13464 , we were able to find another issue with compaction stats.

When there are multiple sub-compactions and they are processed remotely, some compaction stats are not collected correctly.

Here's an example of how `num_input_records` can be double-counted during a compaction with multiple sub-compactions executed remotely. Please note that this problem is not limited to `num_input_records`.

Input File: 1 SST file with 100 keys.

- Key 1~50 are in one sub compaction
- Key 51~100 in another sub compaction

`UpdateOutputLevelCompactionStats()` currently retrieves the total number of entries from the input files and sets `num_input_records` in the internal_stats to 100. In `CompactionJob::Run()`, this method is called once after all sub-compactions have finished. However, during remote compaction, `UpdateOutputLevelCompactionStats()` is called for each offloaded sub-compaction on the remote side and then aggregated on the primary host. The internal_stats for the first sub-compaction will have 100 `num_input_records`, and the second sub-compaction will have another 100 `num_input_records`. We end up having 200 `num_input_records` in the aggregated internal_stats.

There was another issue that `num_input_record` was not properly excluding `num_input_range_del` in `UpdateCompactionJobStats()`. `job_stats_->num_input_record` originally has correct value set by compaction iterator, but then later overwritten in `UpdateCompactionJobStats()`. `UpdateCompactionJobStats()` was called during `CompactionJob::Install()`, so not caught by `VerifyInputRecordCount()`.

## Refactor and other changes before the fixes
* Renamed `UpdateOutputLevelCompactionStats()` to `BuildStatsFromInputTableProperties()` to make the function more descriptive. `BuildStatsFromInputTableProperties()` builds input stats by scanning through entries from TableProperties in the Input Files and it's at the top compaction level, not at the sub-compaction level. (It also updates a couple of non-input stats, `bytes_read_blob` and `num_dropped_records`, but will be refactored in a later PR.)
* `UpdateCompactionJobStats()` was moved from `CompactionJob::Install()` to `CompactionJob::Run()` and separated into `UpdateCompactionJobInputStats()` and `UpdateCompactionJobOutputStats()`.

## Fixes
* Remote Compaction no longer updates the subcompaction-job-level input stats from InputTableProperties to avoid double-counted stats in case of multiple sub-compactions. Subcompaction-job-level input stats are aggregated to the compaction-job-level input stats in the primary host after all sub-compactions are finished.
* Remote Compaction now only calls `UpdateCompactionJobOutputStats()` to update the job-level output stats by copying from internal stats.
* `UpdateCompactionJobInputStats()` now takes `num_input_range_del` and properly subtracts it from the input record count. `VerifyInputRecordCount()` expected `job_stats.num_input_records` to be equal to `internal_stats_.output_level_stats.num_input_records - num_input_range_del`. However, when updating the job-level stats, we were taking the entire `internal_stats_.output_level_stats.num_input_records` after verification.

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

Test Plan:
Local Compaction
```
./db_compaction_test -- --gtest_filter="*DBCompactionTest.VerifyRecordCount*"
```
Remote Compaction
```
./compaction_service_test --gtest_filter="*CompactionServiceTest.VerifyInputRecordCount*"
```

Reviewed By: pdillinger

Differential Revision: D71566149

Pulled By: jaykorean

fbshipit-source-id: c8aafcde701dec8901fd5e5a9ec186e26b896c19
2025-03-20 13:18:48 -07:00
Hui Xiao 2e175124d8 Rename Env::IOActivity::kReadManifest (#13471)
Summary:
Context/Summary: as mentioned in the [comment](https://github.com/facebook/rocksdb/pull/13178?fbclid=IwZXh0bgNhZW0CMTAAAR1nvz-1Ifh6Pm8PwFZbGHAxhLtwfi4W_XaSe-BqnBx3ICJOq-9DTdqFvs0_aem_ITO_0B6cca0kTViRmsAA8g#issuecomment-2702510373) , we want to rename this public name to align with the naming convention.

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

Test Plan:
- Compilation
- Manually check for no internal usage of this name. Hopefully it's good for OSS as well as this field is relatively new and the whole IOActivity is marked "EXPERIMENTAL"

Reviewed By: mszeszko-meta

Differential Revision: D71485300

Pulled By: hx235

fbshipit-source-id: 318c8b6c2a4d874f2f831e3ca690aa2fb8974c0f
2025-03-19 12:08:06 -07:00
Jay Huh 0a43d8a261 Verify compaction output record count (#13455)
Summary:
Continuing cbi42 's work in 602cc0f9a4be89020fb870dba2816f11dd515d16.

In this PR, we are adding record count verification for each compaction by comparing number of entries summed from Table Properties with the number of output records from the compaction stats.

If the count does not match, `Status::Corruption(msg)` is returned with detailed message including the actual number (from table property) and the expected number (from compaction stats)

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

Test Plan:
New UT added
```
./db_compaction_test -- --gtest_filter="*Verify*"
```

The check had to be disabled for some of the existing tests using MockTable/MockTableFactory, because TableProperties aren't populated properly for the MockTables.

Reviewed By: hx235

Differential Revision: D71235790

Pulled By: jaykorean

fbshipit-source-id: 3a86a878d13e79d948409d6a9843d1c992d2c98e
2025-03-18 18:40:33 -07:00
Jay Huh cc487ba367 Fix Compaction Stats for Remote Compaction and Tiered Storage (#13464)
Summary:
## Background

Compaction statistics are collected at various levels across different classes and structs.

* `InternalStats::CompactionStats`: Per-level Compaction Stats within a job (can be at subcompaction level which later get aggregated to the compaction level)
* `InternalStats::CompactionStatsFull`: Contains two per-level compaction stats - `output_level_stats` for primary output level stats and `proximal_level_stats` for proximal level stats. Proximal level statistics are only relevant when using Tiered Storage with the per-key placement feature enabled.
* `InternalStats::CompactionOutputsStats`: Simplified version of `InternalStats::CompactionStats`. Only has a subset of fields from `InternalStats::CompactionStats`
* `CompactionJobStats`: Job-level Compaction Stats. (can be at subcompaction level which later get aggregated to the compaction level)

Please note that some fields in Job-level stats are not in Per-level stats and they don't map 1-to-1 today.

## Issues

* In non-remote compactions, proximal level compaction statistics were not being aggregated into job-level statistics. Job level statistics were missing stats for proximal level for tiered storage compactions with per-key-replacement feature enabled.
* During remote compactions, proximal level compaction statistics were pre-aggregated into job-level statistics on the remote side. However, per-level compaction statistics were not part of the serialized compaction result, so that primary host lost that information and weren't able to populate `per_key_placement_comp_stats_` and `internal_stats_.proximal_level_stats` properly during the installation.
* `TieredCompactionTest` was only checking if (expected stats > 0 && actual stats > 0) instead actual value comparison

## Fixes

* Renamed `compaction_stats_` to `internal_stats_` for `InternalStats::CompactionStatsFull` in `CompactionJob` for better readability
* Removed the usage of `InternalStats::CompactionOutputsStats` and consolidated them to `InternalStats::CompactionStats`.
* Remote Compactions now include the internal stats in the serialized `CompactionServiceResult`. `output_level_stats` and `proximal_level_stats` get later propagated in sub_compact output stats accordingly.
* `CompactionJob::UpdateCompactionJobStats()` now takes `CompactionStatsFull` and aggregates the `proximal_level_stats` as well
* `TieredCompactionTest` is now doing the actual value comparisons for input/output file counts and record counts. Follow up is needed to do the same for the bytes read / written.

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

Test Plan:
Unit Tests updated to verify stats

```
./compaction_service_test
```
```
./tiered_compaction_test
```

Reviewed By: pdillinger

Differential Revision: D71220393

Pulled By: jaykorean

fbshipit-source-id: ad70bffd9614ced683f90c7570a17def9b5c8f3f
2025-03-18 16:28:18 -07:00
Yu Zhang 17ac19f2c4 Add a check during recovery for proper seqno advancement (#13465)
Summary:
This PR adds a check for an invariant of sequence number during recovery, that it should not be set backward. This is inspired by a recent SEV that is caused by a software bug. It is a relatively cheap and straightforward check that RocksDB can do to avoid silently opening the DB in a corrupted state.

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

Test Plan:
Existing tests should cover the case when the invariant is met

The corrupted state is manually tested using aforementioned bug.

Reviewed By: hx235

Differential Revision: D71226513

Pulled By: jowlyzhang

fbshipit-source-id: cd8056fa6653d44ceeb9ba9b4693ab0660a53b4e
2025-03-17 12:49:10 -07:00
Hui Xiao 24952ff088 Expose number of L0 files in the CF right before the compaction starts in CompactionJobInfo (#13462)
Summary:
**Context/Summary:**
For users who are interested in knowing how efficient their compaction in reducing L0 files or how bad their long-running compaction in "locking" L0 files, they now have a reference point "L0 files in the CF pre compaction" for their input compaction files.
- Compared to the existing stats or exposing in some other way, exposing this info in CompactionJobInfo allows users to compare it with other compaction data (e.g, compaction input num, compaction reason) of within **one** compaction (of per-compaction granularity).
- If this number is high while their "short-running" compaction has little L0 files input, then those compaction may have a room for improvement. Similar for those long-running compaction. This PR is to add a new field `CompactionJobInfo::num_l0_files_pre_compaction` for that.

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

Test Plan: - Piggyback on an existing test

Reviewed By: jaykorean

Differential Revision: D71124938

Pulled By: hx235

fbshipit-source-id: aa47c9c86c62d9425771b320f5636e50671fd289
2025-03-17 11:11:44 -07:00
Maciej Szeszko 6ac13a5f0a Expose WriteLifeTimeHint at the FileOptions level (#13461)
Summary:
The original implementation of NVMe write lifetime hints (https://github.com/facebook/rocksdb/pull/3095) assumed a flexible interface which decouples file creation from the explicit act of setting write lifetime hint (see `PosixWritableFile` for more context). However, there are existing file systems implementations (ex. Warm Storage) that require all the options (including file write lifetime hints) to be specified once at the time of the actual `FSWritableFile` object instantiation. We're extending the `FileOptions` with `Env::WriteLifeTimeHint` and patch existing callsites accordingly to enable one-shot metadata setup for those more constraint implementations.

NOTE: Today `CalculateSSTWriteHint` only sets write lifetime hint for Level compactions. We'll fill that gap in following PRs and add calculation for Universal Compactions which would unblock Zippy's use case.

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

Reviewed By: anand1976

Differential Revision: D71144645

Pulled By: mszeszko-meta

fbshipit-source-id: 6c09b62a360d48bd6e4fb08a1265bce2a49f3f4a
2025-03-14 21:43:50 -07:00
Peter Dillinger 0cc943c067 format_version < 2 unsupported for write, deprecated for read (#13463)
Summary:
In hopes of eventually removing some ugly and awkard code for compress_format_version < 2, users can no longer write files in that format and its read support is marked deprecated. For continuing to test that read support, there is a back door to writing the files in unit tests.

If format_version < 2 is specified, it is quietly sanitized to 2. (This is similar to other BlockBasedTableOptions.)

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

Test Plan: unit tests updated.

Reviewed By: hx235

Differential Revision: D71152916

Pulled By: pdillinger

fbshipit-source-id: 95be55e86f93f09fd898223578b9381385c3ccd8
2025-03-14 10:50:05 -07:00
Jay Huh ca7367a003 Replace penultimate naming with proximal (#13460)
Summary:
With generalized age-based tiering (work-in-progress), the "warm tier" data will no longer necessarily be placed in the second-to-last level (also known as the "penultimate level").

Also, the cold tier may no longer necessarily be at the last level, so we need to rename options like `preclude_last_level_seconds` to `preclude_cold_tier_seconds`, but renaming options is trickier because it can be a breaking change for consuming applications. We will do this later as a follow up.

**Minor fix included**: Fixed one `use-after-move` in CompactionPicker

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

Test Plan: CI

Reviewed By: pdillinger

Differential Revision: D71059486

Pulled By: jaykorean

fbshipit-source-id: fd360cdf719e015bf9f9e3f6f1663438226566a4
2025-03-12 18:24:28 -07:00
Jay Huh c5921df3d7 Add PerKeyPlacement support (#13459)
Summary:
This PR adds support for PerKeyPlacement in Remote Compaction.

The `seqno_to_time_mapping` is already available from the table properties of the input files. `preserve_internal_time_seconds` and `preclude_last_level_data_seconds` are directly read from the OPTIONS file upon db open in the remote worker. The necessary changes include:

- Add `is_penultimate_level_output` and `file_temperature` to the `CompactionServiceOutputFile`
- When building the output for the remote compaction, get the outputs for penultimate level and last level separately, serialize them with the two additional information added in this PR.
- When deserializing the result from the primary, SubcompactionState's `GetOutputs()` now takes `is_penultimate_level`. This allows us to determine which level to place the output file.
- Include stats from `compaction_stats.penultimate_level_stats` in the remote compaction result

# To Follow up
- Stats to be fixed. Stats are not being populated correctly for PerKeyPlacement even for non-remote compactions.
- Clean up / Reconcile the "penultimate" naming by replacing with "proximal"

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

Test Plan:
Updated the unit test

```
./compaction_service_test
```

Reviewed By: pdillinger

Differential Revision: D71007211

Pulled By: jaykorean

fbshipit-source-id: f926e56df17239875d849d46b8b940f8cd5f1825
2025-03-12 11:46:02 -07:00
Maciej Szeszko 8e16f8fecf Reduce db stress noise (#13447)
Summary:
[Experiment]

This PR is a followup to https://github.com/facebook/rocksdb/pull/13408. Thick bandaid of ignoring all injected read errors in context of periodic iterator auto refreshes in db stress proved to be effective. We confirmed our theory that errors are not a really a consequence / defect related to this new feature but rather due to subtle ways in which downstream code paths handle their respective IO failures. In this change we're replacing a thick 'ignore all IO read errors' bandaid in `no_batched_ops_stress` with a much smaller, targeted patches in obsolete files purge / delete codepaths, table block cache reader, table cache lookup to make sure we don't miss signal and ensure there's a single mechanism for ignoring error injection in db stress tests.

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

Reviewed By: hx235

Differential Revision: D70794787

Pulled By: mszeszko-meta

fbshipit-source-id: c5fcd4780d82357c407f53bf0bb22fc38f7bd277
2025-03-12 01:13:40 -07:00
Jay Huh 22ca6e5e68 Additional debug logging for InputFileCheck Failure (#13452)
Summary:
Add debug logging when the Wait() does not return `kSuccess` so that we can compare the version state that was printed by the logging added in https://github.com/facebook/rocksdb/issues/13427 upon InputFileCheck failure.

# Test Plan

CI + Tested with Temporary Change in Meta Internal Infra

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

Reviewed By: hx235

Differential Revision: D70898963

Pulled By: jaykorean

fbshipit-source-id: d591b82f2df173b5e01f6552230844ce95155256
2025-03-10 13:37:47 -07:00
Richard Barnes 60c266658d Use nullptr in infra_asic_fpga/ip/mtia/athena/main/models/cmodel/util/jsonUtils.cpp
Summary:
`nullptr` is preferable to `0` or `NULL`. Let's use it everywhere so we can enable `-Wzero-as-null-pointer-constant`.

 - If you approve of this diff, please use the "Accept & Ship" button :-)

Reviewed By: dtolnay

Differential Revision: D70818166

fbshipit-source-id: 4658fb004676fe2686249fdd8ecb322dec8aa63d
2025-03-09 11:18:56 -07:00
Peter Dillinger b9c7481fc2 Fix some secondary/read-only DB logic (#13441)
Summary:
Primarily, fix an issue from https://github.com/facebook/rocksdb/issues/13316 with opening secondary DB with preserve/preclude option (crash test disable in https://github.com/facebook/rocksdb/issues/13439). The issue comes down to mixed-up interpretations of "read_only" which should now be resolved. I've introduced the stronger notion of "unchanging" which means the VersionSet never sees any changes to the LSM tree, and the weaker notion of "read_only" which means LSM tree changes are not written through this VersionSet/etc. but can pick up externally written changes. In particular, ManifestTailer should use read_only=true (along with unchanging=false) for proper handling of preserve/preclude options.

A new assertion in VersionSet::CreateColumnFamily to help ensure sane usage of the two boolean flags is incompatible with the known wart of allowing CreateColumnFamily on a read-only DB. So to keep that assertion, I have fixed that issue by disallowing it. And this in turn required downstream clean-up in ldb, where I cleaned up some call sites as well.

Also, rename SanitizeOptions for ColumnFamilyOptions to SanitizeCfOptions, for ease of search etc.

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

Test Plan:
* Added preserve option to a test in db_secondary_test, which reproduced the failure seen in the crash test.
* Revert https://github.com/facebook/rocksdb/issues/13439 to re-enable crash test functionality
* Update some tests to deal with disallowing CF creation on read-only DB
* Add some testing around read-only DBs and CreateColumnFamily(ies)
* Resurrect a nearby test for read-only DB to be sure it doesn't write to the DB dir. New EnforcedReadOnlyReopen should probably be used in more places but didn't want to attempt a big migration here and now. (Suggested follow-up.)

Reviewed By: jowlyzhang

Differential Revision: D70808033

Pulled By: pdillinger

fbshipit-source-id: 486b4e9f9c9045150a0ebb9cb302753d03932a3f
2025-03-07 14:56:45 -08:00
Peter Dillinger 5d1c0a8832 Reformat assertion in TEST_VerifyNoObsoleteFilesCached (#13446)
Summary:
... for better automatic failure grouping

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

Test Plan: no production code change

Reviewed By: hx235

Differential Revision: D70789464

Pulled By: pdillinger

fbshipit-source-id: 68263f6ed666349d65b5f493865973a213f35ec9
2025-03-07 11:25:44 -08:00
Jay Huh d033c6a849 set ignore_unknown_options when parsing options (#13443)
Summary:
In case the primary host has a new option added which isn't available in the remote worker yet, the remote compaction currently fails. In most cases, these new options are not relevant to the remote compaction and the worker should be able to move on by ignoring it.

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

Test Plan: Verified internally in Meta Infra.

Reviewed By: anand1976

Differential Revision: D70744359

Pulled By: jaykorean

fbshipit-source-id: eb6a388c2358a7f8089f2e35a378b7017b9e03f3
2025-03-06 17:26:37 -08:00
109 changed files with 2685 additions and 1124 deletions

No files matched your search

+22
View File
@@ -1,6 +1,28 @@
# Rocksdb Change Log
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
## 10.1.0 (03/24/2025)
### New Features
* Added a new `DBOptions.calculate_sst_write_lifetime_hint_set` setting that allows to customize which compaction styles SST write lifetime hint calculation is allowed on. Today RocksDB supports only two modes `kCompactionStyleLevel` and `kCompactionStyleUniversal`.
* Add a new field `num_l0_files` in `CompactionJobInfo` about the number of L0 files in the CF right before and after the compaction
* Added per-key-placement feature in Remote Compaction
* Implemented API DB::GetPropertiesOfTablesByLevel that retrieves table properties for files in each LSM tree level
### Public API Changes
* `GetAllKeyVersions()` now interprets empty slices literally, as valid keys, and uses new `OptSlice` type default value for extreme upper and lower range limits.
* `DeleteFilesInRanges()` now takes `RangeOpt` which is based on `OptSlice`. The overload taking `RangePtr` is deprecated.
* Add an unordered map of name/value pairs, ReadOptions::property_bag, to pass opaque options through to an external table when creating an Iterator.
* Introduced CompactionServiceJobStatus::kAborted to allow handling aborted scenario in Schedule(), Wait() or OnInstallation() APIs in Remote Compactions.
* format\_version < 2 in BlockBasedTableOptions is no longer supported for writing new files. Support for reading such files is deprecated and might be removed in the future. `CompressedSecondaryCacheOptions::compress_format_version == 1` is also deprecated.
### Behavior Changes
* `ldb` now returns an error if the specified `--compression_type` is not supported in the build.
* MultiGet with snapshot and ReadOptions::read_tier = kPersistedTier will now read a consistent view across CFs (instead of potentially reading some CF before and some CF after a flush).
* CreateColumnFamily() is no longer allowed on a read-only DB (OpenForReadOnly())
### Bug Fixes
* Fixed stats for Tiered Storage with preclude_last_level feature
## 10.0.0 (02/21/2025)
### New Features
* Introduced new `auto_refresh_iterator_with_snapshot` opt-in knob that (when enabled) will periodically release obsolete memory and storage resources for as long as the iterator is making progress and its supplied `read_options.snapshot` was initialized with non-nullptr value.
+7 -3
View File
@@ -188,10 +188,12 @@ Status BlobFileBuilder::OpenBlobFileIfNeeded() {
}
std::unique_ptr<FSWritableFile> file;
FileOptions fo_copy;
{
assert(file_options_);
Status s = NewWritableFile(fs_, blob_file_path, &file, *file_options_);
fo_copy = *file_options_;
fo_copy.write_hint = write_hint_;
Status s = NewWritableFile(fs_, blob_file_path, &file, fo_copy);
TEST_SYNC_POINT_CALLBACK(
"BlobFileBuilder::OpenBlobFileIfNeeded:NewWritableFile", &s);
@@ -209,7 +211,9 @@ Status BlobFileBuilder::OpenBlobFileIfNeeded() {
assert(file);
file->SetIOPriority(write_options_->rate_limiter_priority);
file->SetWriteLifeTimeHint(write_hint_);
// Subsequent attempts to override the hint via SetWriteLifeTimeHint
// with the very same value will be ignored by the fs.
file->SetWriteLifeTimeHint(fo_copy.write_hint);
FileTypeSet tmp_set = immutable_options_->checksum_handoff_file_types;
Statistics* const statistics = immutable_options_->stats;
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
+6 -2
View File
@@ -145,7 +145,9 @@ Status BuildTable(
bool use_direct_writes = file_options.use_direct_writes;
TEST_SYNC_POINT_CALLBACK("BuildTable:create_file", &use_direct_writes);
#endif // !NDEBUG
IOStatus io_s = NewWritableFile(fs, fname, &file, file_options);
FileOptions fo_copy = file_options;
fo_copy.write_hint = write_hint;
IOStatus io_s = NewWritableFile(fs, fname, &file, fo_copy);
assert(s.ok());
s = io_s;
if (io_status->ok()) {
@@ -163,7 +165,9 @@ Status BuildTable(
table_file_created = true;
FileTypeSet tmp_set = ioptions.checksum_handoff_file_types;
file->SetIOPriority(tboptions.write_options.rate_limiter_priority);
file->SetWriteLifeTimeHint(write_hint);
// Subsequent attempts to override the hint via SetWriteLifeTimeHint
// with the very same value will be ignored by the fs.
file->SetWriteLifeTimeHint(fo_copy.write_hint);
file_writer.reset(new WritableFileWriter(
std::move(file), fname, file_options, ioptions.clock, io_tracer,
ioptions.stats, Histograms::SST_WRITE_MICROS, ioptions.listeners,
+4 -4
View File
@@ -200,9 +200,9 @@ const uint64_t kDefaultTtl = 0xfffffffffffffffe;
const uint64_t kDefaultPeriodicCompSecs = 0xfffffffffffffffe;
} // anonymous namespace
ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
bool read_only,
const ColumnFamilyOptions& src) {
ColumnFamilyOptions SanitizeCfOptions(const ImmutableDBOptions& db_options,
bool read_only,
const ColumnFamilyOptions& src) {
ColumnFamilyOptions result = src;
size_t clamp_max = std::conditional<
sizeof(size_t) == 4, std::integral_constant<size_t, 0xffffffff>,
@@ -569,7 +569,7 @@ ColumnFamilyData::ColumnFamilyData(
dropped_(false),
flush_skip_reschedule_(false),
internal_comparator_(cf_options.comparator),
initial_cf_options_(SanitizeOptions(db_options, read_only, cf_options)),
initial_cf_options_(SanitizeCfOptions(db_options, read_only, cf_options)),
ioptions_(db_options, initial_cf_options_),
mutable_cf_options_(initial_cf_options_),
is_delete_range_supported_(
+3 -3
View File
@@ -281,9 +281,9 @@ Status CheckConcurrentWritesSupported(const ColumnFamilyOptions& cf_options);
Status CheckCFPathsSupported(const DBOptions& db_options,
const ColumnFamilyOptions& cf_options);
ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
bool read_only,
const ColumnFamilyOptions& src);
ColumnFamilyOptions SanitizeCfOptions(const ImmutableDBOptions& db_options,
bool read_only,
const ColumnFamilyOptions& src);
// Wrap user defined table properties collector factories `from cf_options`
// into internal ones in internal_tbl_prop_coll_factories. Add a system internal
// one too.
+4 -4
View File
@@ -271,8 +271,8 @@ class ColumnFamilyTestBase : public testing::Test {
// them.
ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(
ConfigOptions(), desc.options,
SanitizeOptions(dbfull()->immutable_db_options(), /*read_only*/ false,
current_cf_opt)));
SanitizeCfOptions(dbfull()->immutable_db_options(),
/*read_only*/ false, current_cf_opt)));
cfi++;
}
}
@@ -2233,7 +2233,7 @@ TEST_P(ColumnFamilyTest, CreateMissingColumnFamilies) {
ASSERT_EQ(my_fs->options_files_created.load(), 2);
}
TEST_P(ColumnFamilyTest, SanitizeOptions) {
TEST_P(ColumnFamilyTest, SanitizeCfOptions) {
DBOptions db_options;
for (int s = kCompactionStyleLevel; s <= kCompactionStyleUniversal; ++s) {
for (int l = 0; l <= 2; l++) {
@@ -2249,7 +2249,7 @@ TEST_P(ColumnFamilyTest, SanitizeOptions) {
original.write_buffer_size =
l * 4 * 1024 * 1024 + i * 1024 * 1024 + j * 1024 + k;
ColumnFamilyOptions result = SanitizeOptions(
ColumnFamilyOptions result = SanitizeCfOptions(
ImmutableDBOptions(db_options), /*read_only*/ false, original);
ASSERT_TRUE(result.level0_stop_writes_trigger >=
result.level0_slowdown_writes_trigger);
+50 -54
View File
@@ -338,16 +338,16 @@ Compaction::Compaction(
_blob_garbage_collection_age_cutoff > 1
? mutable_cf_options().blob_garbage_collection_age_cutoff
: _blob_garbage_collection_age_cutoff),
penultimate_level_(
// For simplicity, we don't support the concept of "penultimate level"
proximal_level_(
// For simplicity, we don't support the concept of "proximal level"
// with `CompactionReason::kExternalSstIngestion` and
// `CompactionReason::kRefitLevel`
_compaction_reason == CompactionReason::kExternalSstIngestion ||
_compaction_reason == CompactionReason::kRefitLevel
? Compaction::kInvalidLevel
: EvaluatePenultimateLevel(vstorage, mutable_cf_options_,
immutable_options_, start_level_,
output_level_)) {
: EvaluateProximalLevel(vstorage, mutable_cf_options_,
immutable_options_, start_level_,
output_level_)) {
MarkFilesBeingCompacted(true);
if (is_manual_compaction_) {
compaction_reason_ = CompactionReason::kManualCompaction;
@@ -405,10 +405,10 @@ Compaction::Compaction(
}
}
PopulatePenultimateLevelOutputRange();
PopulateProximalLevelOutputRange();
}
void Compaction::PopulatePenultimateLevelOutputRange() {
void Compaction::PopulateProximalLevelOutputRange() {
if (!SupportsPerKeyPlacement()) {
assert(keep_in_last_level_through_seqno_ == kMaxSequenceNumber);
return;
@@ -417,46 +417,42 @@ void Compaction::PopulatePenultimateLevelOutputRange() {
// exclude the last level, the range of all input levels is the safe range
// of keys that can be moved up.
int exclude_level = number_levels_ - 1;
penultimate_output_range_type_ = PenultimateOutputRangeType::kNonLastRange;
proximal_output_range_type_ = ProximalOutputRangeType::kNonLastRange;
// For universal compaction, the penultimate_output_range could be extended if
// all penultimate level files are included in the compaction (which includes
// the case that the penultimate level is empty).
// For universal compaction, the proximal_output_range could be extended if
// all proximal level files are included in the compaction (which includes
// the case that the proximal level is empty).
if (immutable_options_.compaction_style == kCompactionStyleUniversal) {
exclude_level = kInvalidLevel;
penultimate_output_range_type_ = PenultimateOutputRangeType::kFullRange;
std::set<uint64_t> penultimate_inputs;
proximal_output_range_type_ = ProximalOutputRangeType::kFullRange;
std::set<uint64_t> proximal_inputs;
for (const auto& input_lvl : inputs_) {
if (input_lvl.level == penultimate_level_) {
if (input_lvl.level == proximal_level_) {
for (const auto& file : input_lvl.files) {
penultimate_inputs.emplace(file->fd.GetNumber());
proximal_inputs.emplace(file->fd.GetNumber());
}
}
}
auto penultimate_files = input_vstorage_->LevelFiles(penultimate_level_);
for (const auto& file : penultimate_files) {
if (penultimate_inputs.find(file->fd.GetNumber()) ==
penultimate_inputs.end()) {
auto proximal_files = input_vstorage_->LevelFiles(proximal_level_);
for (const auto& file : proximal_files) {
if (proximal_inputs.find(file->fd.GetNumber()) == proximal_inputs.end()) {
exclude_level = number_levels_ - 1;
penultimate_output_range_type_ =
PenultimateOutputRangeType::kNonLastRange;
proximal_output_range_type_ = ProximalOutputRangeType::kNonLastRange;
break;
}
}
}
// FIXME: should make use of `penultimate_output_range_type_`.
// FIXME: should make use of `proximal_output_range_type_`.
// FIXME: when last level's input range does not overlap with
// penultimate level, and penultimate level input is empty,
// this call will not set penultimate_level_smallest_ or
// penultimate_level_largest_. No keys will be compacted up.
GetBoundaryInternalKeys(input_vstorage_, inputs_,
&penultimate_level_smallest_,
&penultimate_level_largest_, exclude_level);
// proximal level, and proximal level input is empty,
// this call will not set proximal_level_smallest_ or
// proximal_level_largest_. No keys will be compacted up.
GetBoundaryInternalKeys(input_vstorage_, inputs_, &proximal_level_smallest_,
&proximal_level_largest_, exclude_level);
if (penultimate_output_range_type_ !=
PenultimateOutputRangeType::kFullRange) {
// If not full range in penultimate level, must keep everything already
if (proximal_output_range_type_ != ProximalOutputRangeType::kFullRange) {
// If not full range in proximal level, must keep everything already
// in the last level there, because moving it back up might cause
// overlap/placement issues that are difficult to resolve properly in the
// presence of range deletes
@@ -486,23 +482,23 @@ Compaction::~Compaction() {
}
bool Compaction::SupportsPerKeyPlacement() const {
return penultimate_level_ != kInvalidLevel;
return proximal_level_ != kInvalidLevel;
}
int Compaction::GetPenultimateLevel() const { return penultimate_level_; }
int Compaction::GetProximalLevel() const { return proximal_level_; }
// smallest_key and largest_key include timestamps if user-defined timestamp is
// enabled.
bool Compaction::OverlapPenultimateLevelOutputRange(
bool Compaction::OverlapProximalLevelOutputRange(
const Slice& smallest_key, const Slice& largest_key) const {
if (!SupportsPerKeyPlacement()) {
return false;
}
// See FIXME in Compaction::PopulatePenultimateLevelOutputRange().
// See FIXME in Compaction::PopulateProximalLevelOutputRange().
// We do not compact any key up in this case.
if (penultimate_level_smallest_.size() == 0 ||
penultimate_level_largest_.size() == 0) {
if (proximal_level_smallest_.size() == 0 ||
proximal_level_largest_.size() == 0) {
return false;
}
@@ -510,13 +506,13 @@ bool Compaction::OverlapPenultimateLevelOutputRange(
input_vstorage_->InternalComparator()->user_comparator();
return ucmp->CompareWithoutTimestamp(
smallest_key, penultimate_level_largest_.user_key()) <= 0 &&
smallest_key, proximal_level_largest_.user_key()) <= 0 &&
ucmp->CompareWithoutTimestamp(
largest_key, penultimate_level_smallest_.user_key()) >= 0;
largest_key, proximal_level_smallest_.user_key()) >= 0;
}
// key includes timestamp if user-defined timestamp is enabled.
void Compaction::TEST_AssertWithinPenultimateLevelOutputRange(
void Compaction::TEST_AssertWithinProximalLevelOutputRange(
const Slice& user_key, bool expect_failure) const {
#ifdef NDEBUG
(void)user_key;
@@ -524,15 +520,15 @@ void Compaction::TEST_AssertWithinPenultimateLevelOutputRange(
#else
assert(SupportsPerKeyPlacement());
assert(penultimate_level_smallest_.size() > 0);
assert(penultimate_level_largest_.size() > 0);
assert(proximal_level_smallest_.size() > 0);
assert(proximal_level_largest_.size() > 0);
auto* cmp = input_vstorage_->user_comparator();
// op_type of a key can change during compaction, e.g. Merge -> Put.
if (!(cmp->Compare(user_key, penultimate_level_smallest_.user_key()) >= 0)) {
if (!(cmp->Compare(user_key, proximal_level_smallest_.user_key()) >= 0)) {
assert(expect_failure);
} else if (!(cmp->Compare(user_key, penultimate_level_largest_.user_key()) <=
} else if (!(cmp->Compare(user_key, proximal_level_largest_.user_key()) <=
0)) {
assert(expect_failure);
} else {
@@ -1018,7 +1014,7 @@ uint64_t Compaction::MinInputFileEpochNumber() const {
return min_epoch_number;
}
int Compaction::EvaluatePenultimateLevel(
int Compaction::EvaluateProximalLevel(
const VersionStorageInfo* vstorage,
const MutableCFOptions& mutable_cf_options,
const ImmutableOptions& immutable_options, const int start_level,
@@ -1033,21 +1029,21 @@ int Compaction::EvaluatePenultimateLevel(
return kInvalidLevel;
}
int penultimate_level = output_level - 1;
assert(penultimate_level < immutable_options.num_levels);
if (penultimate_level <= 0) {
int proximal_level = output_level - 1;
assert(proximal_level < immutable_options.num_levels);
if (proximal_level <= 0) {
return kInvalidLevel;
}
// If the penultimate level is not within input level -> output level range
// check if the penultimate output level is empty, if it's empty, it could
// also be locked for the penultimate output.
// If the proximal level is not within input level -> output level range
// check if the proximal output level is empty, if it's empty, it could
// also be locked for the proximal output.
// TODO: ideally, it only needs to check if there's a file within the
// compaction output key range. For simplicity, it just check if there's any
// file on the penultimate level.
// file on the proximal level.
if (start_level == immutable_options.num_levels - 1 &&
(immutable_options.compaction_style != kCompactionStyleUniversal ||
!vstorage->LevelFiles(penultimate_level).empty())) {
!vstorage->LevelFiles(proximal_level).empty())) {
return kInvalidLevel;
}
@@ -1061,7 +1057,7 @@ int Compaction::EvaluatePenultimateLevel(
return kInvalidLevel;
}
return penultimate_level;
return proximal_level;
}
void Compaction::FilterInputsForCompactionIterator() {
+43 -43
View File
@@ -102,13 +102,13 @@ class Compaction {
BlobGarbageCollectionPolicy::kUseDefault,
double blob_garbage_collection_age_cutoff = -1);
// The type of the penultimate level output range
enum class PenultimateOutputRangeType : int {
kNotSupported, // it cannot output to the penultimate level
kFullRange, // any data could be output to the penultimate level
// The type of the proximal level output range
enum class ProximalOutputRangeType : int {
kNotSupported, // it cannot output to the proximal level
kFullRange, // any data could be output to the proximal level
kNonLastRange, // only the keys within non_last_level compaction inputs can
// be outputted to the penultimate level
kDisabled, // no data can be outputted to the penultimate level
// be outputted to the proximal level
kDisabled, // no data can be outputted to the proximal level
};
// No copying allowed
@@ -370,29 +370,29 @@ class Compaction {
Slice GetLargestUserKey() const { return largest_user_key_; }
PenultimateOutputRangeType GetPenultimateOutputRangeType() const {
return penultimate_output_range_type_;
ProximalOutputRangeType GetProximalOutputRangeType() const {
return proximal_output_range_type_;
}
// Return true if the compaction supports per_key_placement
bool SupportsPerKeyPlacement() const;
// Get per_key_placement penultimate output level, which is `last_level - 1`
// Get per_key_placement proximal output level, which is `last_level - 1`
// if per_key_placement feature is supported. Otherwise, return -1.
int GetPenultimateLevel() const;
int GetProximalLevel() const;
// Return true if the given range is overlap with penultimate level output
// Return true if the given range is overlap with proximal level output
// range.
// Both smallest_key and largest_key include timestamps if user-defined
// timestamp is enabled.
bool OverlapPenultimateLevelOutputRange(const Slice& smallest_key,
const Slice& largest_key) const;
bool OverlapProximalLevelOutputRange(const Slice& smallest_key,
const Slice& largest_key) const;
// For testing purposes, check that a key is within penultimate level
// For testing purposes, check that a key is within proximal level
// output range for per_key_placement feature, which is safe to place the key
// to the penultimate level. Different compaction strategies have different
// to the proximal level. Different compaction strategies have different
// rules. `user_key` includes timestamp if user-defined timestamp is enabled.
void TEST_AssertWithinPenultimateLevelOutputRange(
void TEST_AssertWithinProximalLevelOutputRange(
const Slice& user_key, bool expect_failure = false) const;
CompactionReason compaction_reason() const { return compaction_reason_; }
@@ -441,20 +441,20 @@ class Compaction {
static constexpr int kInvalidLevel = -1;
// Evaluate penultimate output level. If the compaction supports
// per_key_placement feature, it returns the penultimate level number.
// Evaluate proximal output level. If the compaction supports
// per_key_placement feature, it returns the proximal level number.
// Otherwise, it's set to kInvalidLevel (-1), which means
// output_to_penultimate_level is not supported.
// Note: even the penultimate level output is supported (PenultimateLevel !=
// output_to_proximal_level is not supported.
// Note: even the proximal level output is supported (ProximalLevel !=
// kInvalidLevel), some key range maybe unsafe to be outputted to the
// penultimate level. The safe key range is populated by
// `PopulatePenultimateLevelOutputRange()`.
// Which could potentially disable all penultimate level output.
static int EvaluatePenultimateLevel(
const VersionStorageInfo* vstorage,
const MutableCFOptions& mutable_cf_options,
const ImmutableOptions& immutable_options, const int start_level,
const int output_level);
// proximal level. The safe key range is populated by
// `PopulateProximalLevelOutputRange()`.
// Which could potentially disable all proximal level output.
static int EvaluateProximalLevel(const VersionStorageInfo* vstorage,
const MutableCFOptions& mutable_cf_options,
const ImmutableOptions& immutable_options,
const int start_level,
const int output_level);
// If some data cannot be safely migrated "up" the LSM tree due to a change
// in the preclude_last_level_data_seconds setting, this indicates a sequence
@@ -482,10 +482,10 @@ class Compaction {
InternalKey* smallest_key, InternalKey* largest_key,
int exclude_level = -1);
// populate penultimate level output range, which will be used to determine if
// a key is safe to output to the penultimate level (details see
// `Compaction::WithinPenultimateLevelOutputRange()`.
void PopulatePenultimateLevelOutputRange();
// populate proximal level output range, which will be used to determine if
// a key is safe to output to the proximal level (details see
// `Compaction::WithinProximalLevelOutputRange()`.
void PopulateProximalLevelOutputRange();
// If oldest snapshot is specified at Compaction construction time, we have
// an opportunity to optimize inputs for compaction iterator for this case:
@@ -616,20 +616,20 @@ class Compaction {
// only set when per_key_placement feature is enabled, -1 (kInvalidLevel)
// means not supported.
const int penultimate_level_;
const int proximal_level_;
// Key range for penultimate level output
// Key range for proximal level output
// includes timestamp if user-defined timestamp is enabled.
// penultimate_output_range_type_ shows the range type
InternalKey penultimate_level_smallest_;
InternalKey penultimate_level_largest_;
PenultimateOutputRangeType penultimate_output_range_type_ =
PenultimateOutputRangeType::kNotSupported;
// proximal_output_range_type_ shows the range type
InternalKey proximal_level_smallest_;
InternalKey proximal_level_largest_;
ProximalOutputRangeType proximal_output_range_type_ =
ProximalOutputRangeType::kNotSupported;
};
#ifndef NDEBUG
// Helper struct only for tests, which contains the data to decide if a key
// should be output to the penultimate level.
// should be output to the proximal level.
// TODO: remove this when the public feature knob is available
struct PerKeyPlacementContext {
const int level;
@@ -637,16 +637,16 @@ struct PerKeyPlacementContext {
const Slice value;
const SequenceNumber seq_num;
bool& output_to_penultimate_level;
bool& output_to_proximal_level;
PerKeyPlacementContext(int _level, Slice _key, Slice _value,
SequenceNumber _seq_num,
bool& _output_to_penultimate_level)
bool& _output_to_proximal_level)
: level(_level),
key(_key),
value(_value),
seq_num(_seq_num),
output_to_penultimate_level(_output_to_penultimate_level) {}
output_to_proximal_level(_output_to_proximal_level) {}
};
#endif /* !NDEBUG */
+268 -158
View File
@@ -109,16 +109,16 @@ const char* GetCompactionReasonString(CompactionReason compaction_reason) {
}
}
const char* GetCompactionPenultimateOutputRangeTypeString(
Compaction::PenultimateOutputRangeType range_type) {
const char* GetCompactionProximalOutputRangeTypeString(
Compaction::ProximalOutputRangeType range_type) {
switch (range_type) {
case Compaction::PenultimateOutputRangeType::kNotSupported:
case Compaction::ProximalOutputRangeType::kNotSupported:
return "NotSupported";
case Compaction::PenultimateOutputRangeType::kFullRange:
case Compaction::ProximalOutputRangeType::kFullRange:
return "FullRange";
case Compaction::PenultimateOutputRangeType::kNonLastRange:
case Compaction::ProximalOutputRangeType::kNonLastRange:
return "NonLastRange";
case Compaction::PenultimateOutputRangeType::kDisabled:
case Compaction::ProximalOutputRangeType::kDisabled:
return "Disabled";
default:
assert(false);
@@ -147,7 +147,7 @@ CompactionJob::CompactionJob(
BlobFileCompletionCallback* blob_callback, int* bg_compaction_scheduled,
int* bg_bottom_compaction_scheduled)
: compact_(new CompactionState(compaction)),
compaction_stats_(compaction->compaction_reason(), 1),
internal_stats_(compaction->compaction_reason(), 1),
db_options_(db_options),
mutable_db_options_copy_(mutable_db_options),
log_buffer_(log_buffer),
@@ -155,7 +155,7 @@ CompactionJob::CompactionJob(
stats_(stats),
bottommost_level_(false),
write_hint_(Env::WLTH_NOT_SET),
compaction_job_stats_(compaction_job_stats),
job_stats_(compaction_job_stats),
job_id_(job_id),
dbname_(dbname),
db_id_(db_id),
@@ -191,7 +191,7 @@ CompactionJob::CompactionJob(
extra_num_subcompaction_threads_reserved_(0),
bg_compaction_scheduled_(bg_compaction_scheduled),
bg_bottom_compaction_scheduled_(bg_bottom_compaction_scheduled) {
assert(compaction_job_stats_ != nullptr);
assert(job_stats_ != nullptr);
assert(log_buffer_ != nullptr);
const auto* cfd = compact_->compaction->column_family_data();
@@ -240,9 +240,8 @@ void CompactionJob::ReportStartedCompaction(Compaction* compaction) {
// to ensure GetThreadList() can always show them all together.
ThreadStatusUtil::SetThreadOperation(ThreadStatus::OP_COMPACTION);
compaction_job_stats_->is_manual_compaction =
compaction->is_manual_compaction();
compaction_job_stats_->is_full_compaction = compaction->is_full_compaction();
job_stats_->is_manual_compaction = compaction->is_manual_compaction();
job_stats_->is_full_compaction = compaction->is_full_compaction();
}
void CompactionJob::Prepare(
@@ -260,7 +259,8 @@ void CompactionJob::Prepare(
assert(storage_info);
assert(storage_info->NumLevelFiles(compact_->compaction->level()) > 0);
write_hint_ = storage_info->CalculateSSTWriteHint(c->output_level());
write_hint_ = storage_info->CalculateSSTWriteHint(
c->output_level(), db_options_.calculate_sst_write_lifetime_hint_set);
bottommost_level_ = c->bottommost_level();
if (!known_single_subcompact.has_value() && c->ShouldFormSubcompactions()) {
@@ -378,8 +378,8 @@ void CompactionJob::Prepare(
}
// Now combine what we would like to preclude from last level with what we
// can safely support without dangerously moving data back up the LSM tree,
// to get the final seqno threshold for penultimate vs. last. In particular,
// when the reserved output key range for the penultimate level does not
// to get the final seqno threshold for proximal vs. last. In particular,
// when the reserved output key range for the proximal level does not
// include the entire last level input key range, we need to keep entries
// already in the last level there. (Even allowing within-range entries to
// move back up could cause problems with range tombstones. Perhaps it
@@ -388,8 +388,8 @@ void CompactionJob::Prepare(
// tracking and complexity to CompactionIterator that is probably not
// worthwhile overall. Correctness is also more clear when splitting by
// seqno threshold.)
penultimate_after_seqno_ = std::max(preclude_last_level_min_seqno,
c->GetKeepInLastLevelThroughSeqno());
proximal_after_seqno_ = std::max(preclude_last_level_min_seqno,
c->GetKeepInLastLevelThroughSeqno());
options_file_number_ = versions_->options_file_number();
}
@@ -695,17 +695,17 @@ Status CompactionJob::Run() {
thread.join();
}
compaction_stats_.SetMicros(db_options_.clock->NowMicros() - start_micros);
internal_stats_.SetMicros(db_options_.clock->NowMicros() - start_micros);
for (auto& state : compact_->sub_compact_states) {
compaction_stats_.AddCpuMicros(state.compaction_job_stats.cpu_micros);
internal_stats_.AddCpuMicros(state.compaction_job_stats.cpu_micros);
state.RemoveLastEmptyOutput();
}
RecordTimeToHistogram(stats_, COMPACTION_TIME,
compaction_stats_.stats.micros);
internal_stats_.output_level_stats.micros);
RecordTimeToHistogram(stats_, COMPACTION_CPU_TIME,
compaction_stats_.stats.cpu_micros);
internal_stats_.output_level_stats.cpu_micros);
TEST_SYNC_POINT("CompactionJob::Run:BeforeVerify");
@@ -840,7 +840,6 @@ Status CompactionJob::Run() {
TEST_SYNC_POINT("CompactionJob::ReleaseSubcompactionResources:0");
TEST_SYNC_POINT("CompactionJob::ReleaseSubcompactionResources:1");
TablePropertiesCollection tp;
for (const auto& state : compact_->sub_compact_states) {
for (const auto& output : state.GetOutputs()) {
auto fn =
@@ -855,46 +854,60 @@ Status CompactionJob::Run() {
// compaction_service is set. We now know whether each sub_compaction was
// done remotely or not. Reset is_remote_compaction back to false and allow
// AggregateCompactionStats() to set the right value.
compaction_job_stats_->is_remote_compaction = false;
job_stats_->is_remote_compaction = false;
// Finish up all bookkeeping to unify the subcompaction results.
compact_->AggregateCompactionStats(compaction_stats_, *compaction_job_stats_);
compact_->AggregateCompactionStats(internal_stats_, *job_stats_);
uint64_t num_input_range_del = 0;
bool ok = UpdateCompactionStats(&num_input_range_del);
// (Sub)compactions returned ok, do sanity check on the number of input keys.
if (status.ok() && ok && compaction_job_stats_->has_num_input_records) {
size_t ts_sz = compact_->compaction->column_family_data()
->user_comparator()
->timestamp_size();
// When trim_ts_ is non-empty, CompactionIterator takes
// HistoryTrimmingIterator as input iterator and sees a trimmed view of
// input keys. So the number of keys it processed is not suitable for
// verification here.
// TODO: support verification when trim_ts_ is non-empty.
if (!(ts_sz > 0 && !trim_ts_.empty())) {
assert(compaction_stats_.stats.num_input_records > 0);
// TODO: verify the number of range deletion entries.
uint64_t expected =
compaction_stats_.stats.num_input_records - num_input_range_del;
uint64_t actual = compaction_job_stats_->num_input_records;
if (expected != actual) {
char scratch[2345];
compact_->compaction->Summary(scratch, sizeof(scratch));
std::string msg =
"Compaction number of input keys does not match "
"number of keys processed. Expected " +
std::to_string(expected) + " but processed " +
std::to_string(actual) + ". Compaction summary: " + scratch;
bool ok = BuildStatsFromInputTableProperties(&num_input_range_del);
// (Sub)compactions returned ok, do sanity check on the number of input
// keys.
if (status.ok() && ok) {
if (job_stats_->has_num_input_records) {
status = VerifyInputRecordCount(num_input_range_del);
if (!status.ok()) {
ROCKS_LOG_WARN(
db_options_.info_log, "[%s] [JOB %d] Compaction with status: %s",
compact_->compaction->column_family_data()->GetName().c_str(),
job_context_->job_id, msg.c_str());
if (db_options_.compaction_verify_record_count) {
status = Status::Corruption(msg);
}
job_context_->job_id, status.ToString().c_str());
}
}
UpdateCompactionJobInputStats(internal_stats_, num_input_range_del);
}
UpdateCompactionJobOutputStats(internal_stats_);
// Verify number of output records
if (status.ok() && db_options_.compaction_verify_record_count) {
uint64_t total_output_num = 0;
for (const auto& state : compact_->sub_compact_states) {
for (const auto& output : state.GetOutputs()) {
total_output_num += output.table_properties->num_entries -
output.table_properties->num_range_deletions;
}
}
uint64_t expected = internal_stats_.output_level_stats.num_output_records;
if (internal_stats_.has_proximal_level_output) {
expected += internal_stats_.proximal_level_stats.num_output_records;
}
if (expected != total_output_num) {
char scratch[2345];
compact_->compaction->Summary(scratch, sizeof(scratch));
std::string msg =
"Number of keys in compaction output SST files does not match "
"number of keys added. Expected " +
std::to_string(expected) + " but there are " +
std::to_string(total_output_num) +
" in output SST files. Compaction summary: " + scratch;
ROCKS_LOG_WARN(
db_options_.info_log, "[%s] [JOB %d] Compaction with status: %s",
compact_->compaction->column_family_data()->GetName().c_str(),
job_context_->job_id, msg.c_str());
status = Status::Corruption(msg);
}
}
RecordCompactionIOStats();
LogFlush(db_options_.info_log);
TEST_SYNC_POINT("CompactionJob::Run():End");
@@ -916,7 +929,7 @@ Status CompactionJob::Install(bool* compaction_released) {
int output_level = compact_->compaction->output_level();
cfd->internal_stats()->AddCompactionStats(output_level, thread_pri_,
compaction_stats_);
internal_stats_);
if (status.ok()) {
status = InstallCompactionResults(compaction_released);
@@ -927,7 +940,7 @@ Status CompactionJob::Install(bool* compaction_released) {
VersionStorageInfo::LevelSummaryStorage tmp;
auto vstorage = cfd->current()->storage_info();
const auto& stats = compaction_stats_.stats;
const auto& stats = internal_stats_.output_level_stats;
double read_write_amp = 0.0;
double write_amp = 0.0;
@@ -993,19 +1006,20 @@ Status CompactionJob::Install(bool* compaction_released) {
blob_files.back()->GetBlobFileNumber());
}
if (compaction_stats_.has_penultimate_level_output) {
ROCKS_LOG_BUFFER(
log_buffer_,
"[%s] has Penultimate Level output: %" PRIu64
", level %d, number of files: %" PRIu64 ", number of records: %" PRIu64,
column_family_name.c_str(),
compaction_stats_.penultimate_level_stats.bytes_written,
compact_->compaction->GetPenultimateLevel(),
compaction_stats_.penultimate_level_stats.num_output_files,
compaction_stats_.penultimate_level_stats.num_output_records);
if (internal_stats_.has_proximal_level_output) {
ROCKS_LOG_BUFFER(log_buffer_,
"[%s] has Proximal Level output: %" PRIu64
", level %d, number of files: %" PRIu64
", number of records: %" PRIu64,
column_family_name.c_str(),
internal_stats_.proximal_level_stats.bytes_written,
compact_->compaction->GetProximalLevel(),
internal_stats_.proximal_level_stats.num_output_files,
internal_stats_.proximal_level_stats.num_output_records);
}
UpdateCompactionJobStats(stats);
TEST_SYNC_POINT_CALLBACK(
"CompactionJob::Install:AfterUpdateCompactionJobStats", job_stats_);
auto stream = event_logger_->LogToBuffer(log_buffer_, 8192);
stream << "job" << job_id_ << "event" << "compaction_finished"
@@ -1027,17 +1041,16 @@ Status CompactionJob::Install(bool* compaction_released) {
<< CompressionTypeToString(compact_->compaction->output_compression());
stream << "num_single_delete_mismatches"
<< compaction_job_stats_->num_single_del_mismatch;
<< job_stats_->num_single_del_mismatch;
stream << "num_single_delete_fallthrough"
<< compaction_job_stats_->num_single_del_fallthru;
<< job_stats_->num_single_del_fallthru;
if (measure_io_stats_) {
stream << "file_write_nanos" << compaction_job_stats_->file_write_nanos;
stream << "file_range_sync_nanos"
<< compaction_job_stats_->file_range_sync_nanos;
stream << "file_fsync_nanos" << compaction_job_stats_->file_fsync_nanos;
stream << "file_write_nanos" << job_stats_->file_write_nanos;
stream << "file_range_sync_nanos" << job_stats_->file_range_sync_nanos;
stream << "file_fsync_nanos" << job_stats_->file_fsync_nanos;
stream << "file_prepare_write_nanos"
<< compaction_job_stats_->file_prepare_write_nanos;
<< job_stats_->file_prepare_write_nanos;
}
stream << "lsm_state";
@@ -1055,16 +1068,16 @@ Status CompactionJob::Install(bool* compaction_released) {
stream << "blob_file_tail" << blob_files.back()->GetBlobFileNumber();
}
if (compaction_stats_.has_penultimate_level_output) {
if (internal_stats_.has_proximal_level_output) {
InternalStats::CompactionStats& pl_stats =
compaction_stats_.penultimate_level_stats;
stream << "penultimate_level_num_output_files" << pl_stats.num_output_files;
stream << "penultimate_level_bytes_written" << pl_stats.bytes_written;
stream << "penultimate_level_num_output_records"
internal_stats_.proximal_level_stats;
stream << "proximal_level_num_output_files" << pl_stats.num_output_files;
stream << "proximal_level_bytes_written" << pl_stats.bytes_written;
stream << "proximal_level_num_output_records"
<< pl_stats.num_output_records;
stream << "penultimate_level_num_output_files_blob"
stream << "proximal_level_num_output_files_blob"
<< pl_stats.num_output_files_blob;
stream << "penultimate_level_bytes_written_blob"
stream << "proximal_level_bytes_written_blob"
<< pl_stats.bytes_written_blob;
}
@@ -1312,7 +1325,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
std::vector<std::string> blob_file_paths;
// TODO: BlobDB to support output_to_penultimate_level compaction, which needs
// TODO: BlobDB to support output_to_proximal_level compaction, which needs
// 2 builders, so may need to move to `CompactionOutputs`
std::unique_ptr<BlobFileBuilder> blob_file_builder(
(mutable_cf_options.enable_blob_files &&
@@ -1397,30 +1410,30 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
}
const auto& ikey = c_iter->ikey();
bool use_penultimate_output = ikey.sequence > penultimate_after_seqno_;
bool use_proximal_output = ikey.sequence > proximal_after_seqno_;
#ifndef NDEBUG
if (sub_compact->compaction->SupportsPerKeyPlacement()) {
// Could be overridden by unittest
PerKeyPlacementContext context(sub_compact->compaction->output_level(),
ikey.user_key, c_iter->value(),
ikey.sequence, use_penultimate_output);
ikey.sequence, use_proximal_output);
TEST_SYNC_POINT_CALLBACK("CompactionIterator::PrepareOutput.context",
&context);
if (use_penultimate_output) {
// Verify that entries sent to the penultimate level are within the
if (use_proximal_output) {
// Verify that entries sent to the proximal level are within the
// allowed range (because the input key range of the last level could
// be larger than the allowed output key range of the penultimate
// be larger than the allowed output key range of the proximal
// level). This check uses user keys (ignores sequence numbers) because
// compaction boundaries are a "clean cut" between user keys (see
// CompactionPicker::ExpandInputsToCleanCut()), which is especially
// important when preferred sequence numbers has been swapped in for
// kTypeValuePreferredSeqno / TimedPut.
sub_compact->compaction->TEST_AssertWithinPenultimateLevelOutputRange(
sub_compact->compaction->TEST_AssertWithinProximalLevelOutputRange(
c_iter->user_key());
}
} else {
assert(penultimate_after_seqno_ == kMaxSequenceNumber);
assert(!use_penultimate_output);
assert(proximal_after_seqno_ == kMaxSequenceNumber);
assert(!use_proximal_output);
}
#endif // NDEBUG
@@ -1429,7 +1442,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
// and `close_file_func`.
// TODO: it would be better to have the compaction file open/close moved
// into `CompactionOutputs` which has the output file information.
status = sub_compact->AddToOutput(*c_iter, use_penultimate_output,
status = sub_compact->AddToOutput(*c_iter, use_proximal_output,
open_file_func, close_file_func);
if (!status.ok()) {
break;
@@ -1641,15 +1654,15 @@ Status CompactionJob::FinishCompactionOutputFile(
std::pair<SequenceNumber, SequenceNumber> keep_seqno_range{
0, kMaxSequenceNumber};
if (sub_compact->compaction->SupportsPerKeyPlacement()) {
if (outputs.IsPenultimateLevel()) {
keep_seqno_range.first = penultimate_after_seqno_;
if (outputs.IsProximalLevel()) {
keep_seqno_range.first = proximal_after_seqno_;
} else {
keep_seqno_range.second = penultimate_after_seqno_;
keep_seqno_range.second = proximal_after_seqno_;
}
}
CompactionIterationStats range_del_out_stats;
// NOTE1: Use `bottommost_level_ = true` for both bottommost and
// output_to_penultimate_level compaction here, as it's only used to decide
// output_to_proximal_level compaction here, as it's only used to decide
// if range dels could be dropped. (Logically, we are taking a single sorted
// run returned from CompactionIterator and physically splitting it between
// two output levels.)
@@ -1812,22 +1825,22 @@ Status CompactionJob::InstallCompactionResults(bool* compaction_released) {
{
Compaction::InputLevelSummaryBuffer inputs_summary;
if (compaction_stats_.has_penultimate_level_output) {
if (internal_stats_.has_proximal_level_output) {
ROCKS_LOG_BUFFER(
log_buffer_,
"[%s] [JOB %d] Compacted %s => output_to_penultimate_level: %" PRIu64
"[%s] [JOB %d] Compacted %s => output_to_proximal_level: %" PRIu64
" bytes + last: %" PRIu64 " bytes. Total: %" PRIu64 " bytes",
compaction->column_family_data()->GetName().c_str(), job_id_,
compaction->InputLevelSummary(&inputs_summary),
compaction_stats_.penultimate_level_stats.bytes_written,
compaction_stats_.stats.bytes_written,
compaction_stats_.TotalBytesWritten());
internal_stats_.proximal_level_stats.bytes_written,
internal_stats_.output_level_stats.bytes_written,
internal_stats_.TotalBytesWritten());
} else {
ROCKS_LOG_BUFFER(log_buffer_,
"[%s] [JOB %d] Compacted %s => %" PRIu64 " bytes",
compaction->column_family_data()->GetName().c_str(),
job_id_, compaction->InputLevelSummary(&inputs_summary),
compaction_stats_.TotalBytesWritten());
internal_stats_.TotalBytesWritten());
}
}
@@ -1946,11 +1959,11 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
// Here last_level_temperature supersedes default_write_temperature, when
// enabled and applicable
if (last_level_temp != Temperature::kUnknown &&
sub_compact->compaction->is_last_level() &&
!outputs.IsPenultimateLevel()) {
sub_compact->compaction->is_last_level() && !outputs.IsProximalLevel()) {
temperature = last_level_temp;
}
fo_copy.temperature = temperature;
fo_copy.write_hint = write_hint_;
Status s;
IOStatus io_s = NewWritableFile(fs_.get(), fname, &writable_file, fo_copy);
@@ -2036,7 +2049,9 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
}
writable_file->SetIOPriority(GetRateLimiterPriority());
writable_file->SetWriteLifeTimeHint(write_hint_);
// Subsequent attempts to override the hint via SetWriteLifeTimeHint
// with the very same value will be ignored by the fs.
writable_file->SetWriteLifeTimeHint(fo_copy.write_hint);
FileTypeSet tmp_set = db_options_.checksum_handoff_file_types;
writable_file->SetPreallocationBlockSize(static_cast<size_t>(
sub_compact->compaction->OutputFilePreallocationSize()));
@@ -2061,7 +2076,7 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
bottommost_level_, TableFileCreationReason::kCompaction,
0 /* oldest_key_time */, current_time, db_id_, db_session_id_,
sub_compact->compaction->max_output_file_size(), file_number,
penultimate_after_seqno_ /*last_level_inclusive_max_seqno_threshold*/);
proximal_after_seqno_ /*last_level_inclusive_max_seqno_threshold*/);
outputs.NewBuilder(tboptions);
@@ -2085,12 +2100,13 @@ void CopyPrefix(const Slice& src, size_t prefix_length, std::string* dst) {
}
} // namespace
bool CompactionJob::UpdateCompactionStats(uint64_t* num_input_range_del) {
bool CompactionJob::BuildStatsFromInputTableProperties(
uint64_t* num_input_range_del) {
assert(compact_);
Compaction* compaction = compact_->compaction;
compaction_stats_.stats.num_input_files_in_non_output_levels = 0;
compaction_stats_.stats.num_input_files_in_output_level = 0;
internal_stats_.output_level_stats.num_input_files_in_non_output_levels = 0;
internal_stats_.output_level_stats.num_input_files_in_output_level = 0;
bool has_error = false;
const ReadOptions read_options(Env::IOActivity::kCompaction);
@@ -2102,13 +2118,14 @@ bool CompactionJob::UpdateCompactionStats(uint64_t* num_input_range_del) {
size_t num_input_files = flevel->num_files;
uint64_t* bytes_read;
if (compaction->level(input_level) != compaction->output_level()) {
compaction_stats_.stats.num_input_files_in_non_output_levels +=
internal_stats_.output_level_stats.num_input_files_in_non_output_levels +=
static_cast<int>(num_input_files);
bytes_read = &compaction_stats_.stats.bytes_read_non_output_levels;
bytes_read =
&internal_stats_.output_level_stats.bytes_read_non_output_levels;
} else {
compaction_stats_.stats.num_input_files_in_output_level +=
internal_stats_.output_level_stats.num_input_files_in_output_level +=
static_cast<int>(num_input_files);
bytes_read = &compaction_stats_.stats.bytes_read_output_level;
bytes_read = &internal_stats_.output_level_stats.bytes_read_output_level;
}
for (size_t i = 0; i < num_input_files; ++i) {
const FileMetaData* file_meta = flevel->files[i].file_metadata;
@@ -2128,7 +2145,8 @@ bool CompactionJob::UpdateCompactionStats(uint64_t* num_input_range_del) {
has_error = true;
}
}
compaction_stats_.stats.num_input_records += file_input_entries;
internal_stats_.output_level_stats.num_input_records +=
file_input_entries;
if (num_input_range_del) {
*num_input_range_del += file_num_range_del;
}
@@ -2139,62 +2157,122 @@ bool CompactionJob::UpdateCompactionStats(uint64_t* num_input_range_del) {
size_t num_filtered_input_files = filtered_flevel.size();
uint64_t* bytes_skipped;
if (compaction->level(input_level) != compaction->output_level()) {
compaction_stats_.stats.num_filtered_input_files_in_non_output_levels +=
internal_stats_.output_level_stats
.num_filtered_input_files_in_non_output_levels +=
static_cast<int>(num_filtered_input_files);
bytes_skipped = &compaction_stats_.stats.bytes_skipped_non_output_levels;
bytes_skipped =
&internal_stats_.output_level_stats.bytes_skipped_non_output_levels;
} else {
compaction_stats_.stats.num_filtered_input_files_in_output_level +=
internal_stats_.output_level_stats
.num_filtered_input_files_in_output_level +=
static_cast<int>(num_filtered_input_files);
bytes_skipped = &compaction_stats_.stats.bytes_skipped_output_level;
bytes_skipped =
&internal_stats_.output_level_stats.bytes_skipped_output_level;
}
for (const FileMetaData* filtered_file_meta : filtered_flevel) {
*bytes_skipped += filtered_file_meta->fd.GetFileSize();
}
}
assert(compaction_job_stats_);
compaction_stats_.stats.bytes_read_blob =
compaction_job_stats_->total_blob_bytes_read;
compaction_stats_.stats.num_dropped_records =
compaction_stats_.DroppedRecords();
// TODO - find a better place to set these two
assert(job_stats_);
internal_stats_.output_level_stats.bytes_read_blob =
job_stats_->total_blob_bytes_read;
internal_stats_.output_level_stats.num_dropped_records =
internal_stats_.DroppedRecords();
return !has_error;
}
void CompactionJob::UpdateCompactionJobStats(
const InternalStats::CompactionStats& stats) const {
compaction_job_stats_->elapsed_micros = stats.micros;
void CompactionJob::UpdateCompactionJobInputStats(
const InternalStats::CompactionStatsFull& internal_stats,
uint64_t num_input_range_del) const {
assert(job_stats_);
// input information
compaction_job_stats_->total_input_bytes =
stats.bytes_read_non_output_levels + stats.bytes_read_output_level;
compaction_job_stats_->num_input_records = stats.num_input_records;
compaction_job_stats_->num_input_files =
stats.num_input_files_in_non_output_levels +
stats.num_input_files_in_output_level;
compaction_job_stats_->num_input_files_at_output_level =
stats.num_input_files_in_output_level;
compaction_job_stats_->num_filtered_input_files =
stats.num_filtered_input_files_in_non_output_levels +
stats.num_filtered_input_files_in_output_level;
compaction_job_stats_->num_filtered_input_files_at_output_level =
stats.num_filtered_input_files_in_output_level;
compaction_job_stats_->total_skipped_input_bytes =
stats.bytes_skipped_non_output_levels + stats.bytes_skipped_output_level;
job_stats_->total_input_bytes =
internal_stats.output_level_stats.bytes_read_non_output_levels +
internal_stats.output_level_stats.bytes_read_output_level;
job_stats_->num_input_records =
internal_stats.output_level_stats.num_input_records - num_input_range_del;
job_stats_->num_input_files =
internal_stats.output_level_stats.num_input_files_in_non_output_levels +
internal_stats.output_level_stats.num_input_files_in_output_level;
job_stats_->num_input_files_at_output_level =
internal_stats.output_level_stats.num_input_files_in_output_level;
job_stats_->num_filtered_input_files =
internal_stats.output_level_stats
.num_filtered_input_files_in_non_output_levels +
internal_stats.output_level_stats
.num_filtered_input_files_in_output_level;
job_stats_->num_filtered_input_files_at_output_level =
internal_stats.output_level_stats
.num_filtered_input_files_in_output_level;
job_stats_->total_skipped_input_bytes =
internal_stats.output_level_stats.bytes_skipped_non_output_levels +
internal_stats.output_level_stats.bytes_skipped_output_level;
if (internal_stats.has_proximal_level_output) {
job_stats_->total_input_bytes +=
internal_stats.proximal_level_stats.bytes_read_non_output_levels +
internal_stats.proximal_level_stats.bytes_read_output_level;
job_stats_->num_input_records +=
internal_stats.proximal_level_stats.num_input_records;
job_stats_->num_input_files +=
internal_stats.proximal_level_stats
.num_input_files_in_non_output_levels +
internal_stats.proximal_level_stats.num_input_files_in_output_level;
job_stats_->num_input_files_at_output_level +=
internal_stats.proximal_level_stats.num_input_files_in_output_level;
job_stats_->num_filtered_input_files +=
internal_stats.proximal_level_stats
.num_filtered_input_files_in_non_output_levels +
internal_stats.proximal_level_stats
.num_filtered_input_files_in_output_level;
job_stats_->num_filtered_input_files_at_output_level +=
internal_stats.proximal_level_stats
.num_filtered_input_files_in_output_level;
job_stats_->total_skipped_input_bytes +=
internal_stats.proximal_level_stats.bytes_skipped_non_output_levels +
internal_stats.proximal_level_stats.bytes_skipped_output_level;
}
}
void CompactionJob::UpdateCompactionJobOutputStats(
const InternalStats::CompactionStatsFull& internal_stats) const {
assert(job_stats_);
job_stats_->elapsed_micros = internal_stats.output_level_stats.micros;
job_stats_->cpu_micros = internal_stats.output_level_stats.cpu_micros;
// output information
compaction_job_stats_->total_output_bytes = stats.bytes_written;
compaction_job_stats_->total_output_bytes_blob = stats.bytes_written_blob;
compaction_job_stats_->num_output_records = stats.num_output_records;
compaction_job_stats_->num_output_files = stats.num_output_files;
compaction_job_stats_->num_output_files_blob = stats.num_output_files_blob;
job_stats_->total_output_bytes =
internal_stats.output_level_stats.bytes_written;
job_stats_->total_output_bytes_blob =
internal_stats.output_level_stats.bytes_written_blob;
job_stats_->num_output_records =
internal_stats.output_level_stats.num_output_records;
job_stats_->num_output_files =
internal_stats.output_level_stats.num_output_files;
job_stats_->num_output_files_blob =
internal_stats.output_level_stats.num_output_files_blob;
if (stats.num_output_files > 0) {
if (internal_stats.has_proximal_level_output) {
job_stats_->total_output_bytes +=
internal_stats.proximal_level_stats.bytes_written;
job_stats_->total_output_bytes_blob +=
internal_stats.proximal_level_stats.bytes_written_blob;
job_stats_->num_output_records +=
internal_stats.proximal_level_stats.num_output_records;
job_stats_->num_output_files +=
internal_stats.proximal_level_stats.num_output_files;
job_stats_->num_output_files_blob +=
internal_stats.proximal_level_stats.num_output_files_blob;
}
if (job_stats_->num_output_files > 0) {
CopyPrefix(compact_->SmallestUserKey(),
CompactionJobStats::kMaxPrefixLength,
&compaction_job_stats_->smallest_output_key_prefix);
&job_stats_->smallest_output_key_prefix);
CopyPrefix(compact_->LargestUserKey(), CompactionJobStats::kMaxPrefixLength,
&compaction_job_stats_->largest_output_key_prefix);
&job_stats_->largest_output_key_prefix);
}
}
@@ -2232,19 +2310,19 @@ void CompactionJob::LogCompaction() {
? int64_t{-1} // Use -1 for "none"
: static_cast<int64_t>(existing_snapshots_[0]));
if (compaction->SupportsPerKeyPlacement()) {
stream << "prenultimate_after_seqno" << penultimate_after_seqno_;
stream << "proximal_after_seqno" << proximal_after_seqno_;
stream << "preserve_seqno_after" << preserve_seqno_after_;
stream << "penultimate_output_level" << compaction->GetPenultimateLevel();
stream << "penultimate_output_range"
<< GetCompactionPenultimateOutputRangeTypeString(
compaction->GetPenultimateOutputRangeType());
stream << "proximal_output_level" << compaction->GetProximalLevel();
stream << "proximal_output_range"
<< GetCompactionProximalOutputRangeTypeString(
compaction->GetProximalOutputRangeType());
if (compaction->GetPenultimateOutputRangeType() ==
Compaction::PenultimateOutputRangeType::kDisabled) {
if (compaction->GetProximalOutputRangeType() ==
Compaction::ProximalOutputRangeType::kDisabled) {
ROCKS_LOG_WARN(
db_options_.info_log,
"[%s] [JOB %d] Penultimate level output is disabled, likely "
"because of the range conflict in the penultimate level",
"[%s] [JOB %d] Proximal level output is disabled, likely "
"because of the range conflict in the proximal level",
cfd->GetName().c_str(), job_id_);
}
}
@@ -2269,4 +2347,36 @@ Env::IOPriority CompactionJob::GetRateLimiterPriority() {
return Env::IO_LOW;
}
Status CompactionJob::VerifyInputRecordCount(
uint64_t num_input_range_del) const {
size_t ts_sz = compact_->compaction->column_family_data()
->user_comparator()
->timestamp_size();
// When trim_ts_ is non-empty, CompactionIterator takes
// HistoryTrimmingIterator as input iterator and sees a trimmed view of
// input keys. So the number of keys it processed is not suitable for
// verification here.
// TODO: support verification when trim_ts_ is non-empty.
if (!(ts_sz > 0 && !trim_ts_.empty())) {
assert(internal_stats_.output_level_stats.num_input_records > 0);
// TODO: verify the number of range deletion entries.
uint64_t expected = internal_stats_.output_level_stats.num_input_records -
num_input_range_del;
uint64_t actual = job_stats_->num_input_records;
if (expected != actual) {
char scratch[2345];
compact_->compaction->Summary(scratch, sizeof(scratch));
std::string msg =
"Compaction number of input keys does not match "
"number of keys processed. Expected " +
std::to_string(expected) + " but processed " +
std::to_string(actual) + ". Compaction summary: " + scratch;
if (db_options_.compaction_verify_record_count) {
return Status::Corruption(msg);
}
}
}
return Status::OK();
}
} // namespace ROCKSDB_NAMESPACE
+63 -39
View File
@@ -67,7 +67,7 @@ class SubcompactionState;
// if needed.
//
// CompactionJob has 2 main stats:
// 1. CompactionJobStats compaction_job_stats_
// 1. CompactionJobStats job_stats_
// CompactionJobStats is a public data structure which is part of Compaction
// event listener that rocksdb share the job stats with the user.
// Internally it's an aggregation of all the compaction_job_stats from each
@@ -81,7 +81,7 @@ class SubcompactionState;
// +------------------------+ |
// | CompactionJob | | +------------------------+
// | | | | SubcompactionState |
// | compaction_job_stats +-----+ | |
// | job_stats +-----+ | |
// | | +--------->| compaction_job_stats |
// | | | | |
// +------------------------+ | +------------------------+
@@ -98,16 +98,13 @@ class SubcompactionState;
// +--------->+ |
// +------------------------+
//
// 2. CompactionStatsFull compaction_stats_
// 2. CompactionStatsFull internal_stats_
// `CompactionStatsFull` is an internal stats about the compaction, which
// is eventually sent to `ColumnFamilyData::internal_stats_` and used for
// logging and public metrics.
// Internally, it's an aggregation of stats_ from each `SubcompactionState`.
// It has 2 parts, normal stats about the main compaction information and
// the penultimate level output stats.
// `SubcompactionState` maintains the CompactionOutputs for normal output and
// the penultimate level output if exists, the per_level stats is
// stored with the outputs.
// It has 2 parts, ordinary output level stats and the proximal level output
// stats.
// +---------------------------+
// | SubcompactionState |
// | |
@@ -119,15 +116,15 @@ class SubcompactionState;
// | | |
// | | +----------------------+ |
// +--------------------------------+ | | | CompactionOutputs | |
// | CompactionJob | | | | (penultimate_level) | |
// | CompactionJob | | | | (proximal_level) | |
// | | +--------->| stats_ | |
// | compaction_stats_ | | | | +----------------------+ |
// | internal_stats_ | | | | +----------------------+ |
// | +-------------------------+ | | | | |
// | |stats (normal) |------|----+ +---------------------------+
// | |output_level_stats |------|----+ +---------------------------+
// | +-------------------------+ | | |
// | | | |
// | +-------------------------+ | | | +---------------------------+
// | |penultimate_level_stats +------+ | | SubcompactionState |
// | |proximal_level_stats |------+ | | SubcompactionState |
// | +-------------------------+ | | | | |
// | | | | | +----------------------+ |
// | | | | | | CompactionOutputs | |
@@ -137,7 +134,7 @@ class SubcompactionState;
// | | |
// | | +----------------------+ |
// | | | CompactionOutputs | |
// | | | (penultimate_level) | |
// | | | (proximal_level) | |
// +--------->| stats_ | |
// | +----------------------+ |
// | |
@@ -199,23 +196,9 @@ class CompactionJob {
IOStatus io_status() const { return io_status_; }
protected:
// Update the following stats in compaction_stats_.stats
// - num_input_files_in_non_output_levels
// - num_input_files_in_output_level
// - bytes_read_non_output_levels
// - bytes_read_output_level
// - num_input_records
// - bytes_read_blob
// - num_dropped_records
//
// @param num_input_range_del if non-null, will be set to the number of range
// deletion entries in this compaction input.
//
// Returns true iff compaction_stats_.stats.num_input_records and
// num_input_range_del are calculated successfully.
bool UpdateCompactionStats(uint64_t* num_input_range_del = nullptr);
virtual void UpdateCompactionJobStats(
const InternalStats::CompactionStats& stats) const;
void UpdateCompactionJobOutputStats(
const InternalStats::CompactionStatsFull& internal_stats) const;
void LogCompaction();
virtual void RecordCompactionIOStats();
void CleanupCompaction();
@@ -224,7 +207,7 @@ class CompactionJob {
void ProcessKeyValueCompaction(SubcompactionState* sub_compact);
CompactionState* compact_;
InternalStats::CompactionStatsFull compaction_stats_;
InternalStats::CompactionStatsFull internal_stats_;
const ImmutableDBOptions& db_options_;
const MutableDBOptions mutable_db_options_copy_;
LogBuffer* log_buffer_;
@@ -237,11 +220,37 @@ class CompactionJob {
IOStatus io_status_;
CompactionJobStats* compaction_job_stats_;
CompactionJobStats* job_stats_;
private:
friend class CompactionJobTestBase;
// Collect the following stats from Input Table Properties
// - num_input_files_in_non_output_levels
// - num_input_files_in_output_level
// - bytes_read_non_output_levels
// - bytes_read_output_level
// - num_input_records
// - bytes_read_blob
// - num_dropped_records
// and set them in internal_stats_.output_level_stats
//
// @param num_input_range_del if non-null, will be set to the number of range
// deletion entries in this compaction input.
//
// Returns true iff internal_stats_.output_level_stats.num_input_records and
// num_input_range_del are calculated successfully.
//
// This should be called only once for compactions (not per subcompaction)
bool BuildStatsFromInputTableProperties(
uint64_t* num_input_range_del = nullptr);
void UpdateCompactionJobInputStats(
const InternalStats::CompactionStatsFull& internal_stats,
uint64_t num_input_range_del) const;
Status VerifyInputRecordCount(uint64_t num_input_range_del) const;
// Generates a histogram representing potential divisions of key ranges from
// the input. It adds the starting and/or ending keys of certain input files
// to the working set and then finds the approximate size of data in between
@@ -363,8 +372,8 @@ class CompactionJob {
// Minimal sequence number to preclude the data from the last level. If the
// key has bigger (newer) sequence number than this, it will be precluded from
// the last level (output to penultimate level).
SequenceNumber penultimate_after_seqno_ = kMaxSequenceNumber;
// the last level (output to proximal level).
SequenceNumber proximal_after_seqno_ = kMaxSequenceNumber;
// Options File Number used for Remote Compaction
// Setting this requires DBMutex.
@@ -431,6 +440,8 @@ struct CompactionServiceOutputFile {
bool marked_for_compaction;
UniqueId64x2 unique_id{};
TableProperties table_properties;
bool is_proximal_level_output;
Temperature file_temperature;
CompactionServiceOutputFile() = default;
CompactionServiceOutputFile(
@@ -440,7 +451,8 @@ struct CompactionServiceOutputFile {
uint64_t _epoch_number, const std::string& _file_checksum,
const std::string& _file_checksum_func_name, uint64_t _paranoid_hash,
bool _marked_for_compaction, UniqueId64x2 _unique_id,
const TableProperties& _table_properties)
const TableProperties& _table_properties, bool _is_proximal_level_output,
Temperature _file_temperature)
: file_name(name),
smallest_seqno(smallest),
largest_seqno(largest),
@@ -454,7 +466,9 @@ struct CompactionServiceOutputFile {
paranoid_hash(_paranoid_hash),
marked_for_compaction(_marked_for_compaction),
unique_id(std::move(_unique_id)),
table_properties(_table_properties) {}
table_properties(_table_properties),
is_proximal_level_output(_is_proximal_level_output),
file_temperature(_file_temperature) {}
};
// CompactionServiceResult contains the compaction result from a different db
@@ -470,8 +484,21 @@ struct CompactionServiceResult {
uint64_t bytes_read = 0;
uint64_t bytes_written = 0;
// Job-level Compaction Stats.
//
// NOTE: Job level stats cannot be rebuilt from scratch by simply aggregating
// per-level stats due to some fields populated directly during compaction
// (e.g. RecordDroppedKeys()). This is why we need both job-level stats and
// per-level in the serialized result. If rebuilding job-level stats from
// per-level stats become possible in the future, consider deprecating this
// field.
CompactionJobStats stats;
// Per-level Compaction Stats for both output_level_stats and
// proximal_level_stats
InternalStats::CompactionStatsFull internal_stats;
// serialization interface to read and write the object
static Status Read(const std::string& data_str, CompactionServiceResult* obj);
Status Write(std::string* output);
@@ -517,9 +544,6 @@ class CompactionServiceCompactionJob : private CompactionJob {
protected:
void RecordCompactionIOStats() override;
void UpdateCompactionJobStats(
const InternalStats::CompactionStats& stats) const override;
private:
// Get table file name in output_path
std::string GetTableFileName(uint64_t file_number) override;
+13 -6
View File
@@ -232,6 +232,11 @@ class CompactionJobTestBase : public testing::Test {
// set default for the tests
mutable_cf_options_.target_file_size_base = 1024 * 1024;
mutable_cf_options_.max_compaction_bytes = 10 * 1024 * 1024;
// Turn off compaction_verify_record_count MockTables
if (table_type == TableTypeForTest::kMockTable) {
db_options_.compaction_verify_record_count = false;
}
}
void SetUp() override {
@@ -1474,7 +1479,7 @@ TEST_F(CompactionJobTest, OldestBlobFileNumber) {
/* expected_oldest_blob_file_numbers */ {19});
}
TEST_F(CompactionJobTest, VerifyPenultimateLevelOutput) {
TEST_F(CompactionJobTest, VerifyProximalLevelOutput) {
cf_options_.last_level_temperature = Temperature::kCold;
SyncPoint::GetInstance()->SetCallBack(
"Compaction::SupportsPerKeyPlacement:Enabled", [&](void* arg) {
@@ -1487,8 +1492,7 @@ TEST_F(CompactionJobTest, VerifyPenultimateLevelOutput) {
SyncPoint::GetInstance()->SetCallBack(
"CompactionIterator::PrepareOutput.context", [&](void* arg) {
auto context = static_cast<PerKeyPlacementContext*>(arg);
context->output_to_penultimate_level =
context->seq_num > latest_cold_seq;
context->output_to_proximal_level = context->seq_num > latest_cold_seq;
});
SyncPoint::GetInstance()->EnableProcessing();
@@ -1534,11 +1538,11 @@ TEST_F(CompactionJobTest, VerifyPenultimateLevelOutput) {
/*verify_func=*/[&](Compaction& comp) {
for (char c = 'a'; c <= 'z'; c++) {
if (c == 'a') {
comp.TEST_AssertWithinPenultimateLevelOutputRange(
comp.TEST_AssertWithinProximalLevelOutputRange(
"a", true /*expect_failure*/);
} else {
std::string c_str{c};
comp.TEST_AssertWithinPenultimateLevelOutputRange(c_str);
comp.TEST_AssertWithinProximalLevelOutputRange(c_str);
}
}
});
@@ -1682,7 +1686,8 @@ TEST_F(CompactionJobTest, ResultSerialization) {
file_checksum /* file_checksum */,
file_checksum_func_name /* file_checksum_func_name */,
rnd64.Uniform(UINT64_MAX) /* paranoid_hash */,
rnd.OneIn(2) /* marked_for_compaction */, id /* unique_id */, tp);
rnd.OneIn(2) /* marked_for_compaction */, id /* unique_id */, tp,
false /* is_proximal_level_output */, Temperature::kHot);
}
result.output_level = rnd.Uniform(10);
result.output_path = rnd.RandomString(rnd.Uniform(kStrMaxLen));
@@ -1736,6 +1741,8 @@ TEST_F(CompactionJobTest, ResultSerialization) {
ASSERT_EQ(deserialized_tmp.output_files[0].file_checksum, file_checksum);
ASSERT_EQ(deserialized_tmp.output_files[0].file_checksum_func_name,
file_checksum_func_name);
ASSERT_EQ(deserialized_tmp.output_files[0].file_temperature,
Temperature::kHot);
}
// Test unknown field
+3 -3
View File
@@ -54,7 +54,7 @@ Status CompactionOutputs::Finish(
}
current_output().finished = true;
stats_.bytes_written += current_bytes;
stats_.num_output_files = outputs_.size();
stats_.num_output_files = static_cast<int>(outputs_.size());
return s;
}
@@ -792,8 +792,8 @@ void CompactionOutputs::FillFilesToCutForTtl() {
}
CompactionOutputs::CompactionOutputs(const Compaction* compaction,
const bool is_penultimate_level)
: compaction_(compaction), is_penultimate_level_(is_penultimate_level) {
const bool is_proximal_level)
: compaction_(compaction), is_proximal_level_(is_proximal_level) {
partitioner_ = compaction->output_level() == 0
? nullptr
: compaction->CreateSstPartitioner();
+20 -21
View File
@@ -30,29 +30,32 @@ class CompactionOutputs {
// compaction output file
struct Output {
Output(FileMetaData&& _meta, const InternalKeyComparator& _icmp,
bool _enable_hash, bool _finished, uint64_t precalculated_hash)
bool _enable_hash, bool _finished, uint64_t precalculated_hash,
bool _is_proximal_level)
: meta(std::move(_meta)),
validator(_icmp, _enable_hash, precalculated_hash),
finished(_finished) {}
finished(_finished),
is_proximal_level(_is_proximal_level) {}
FileMetaData meta;
OutputValidator validator;
bool finished;
bool is_proximal_level;
std::shared_ptr<const TableProperties> table_properties;
};
CompactionOutputs() = delete;
explicit CompactionOutputs(const Compaction* compaction,
const bool is_penultimate_level);
const bool is_proximal_level);
bool IsPenultimateLevel() const { return is_penultimate_level_; }
bool IsProximalLevel() const { return is_proximal_level_; }
// Add generated output to the list
void AddOutput(FileMetaData&& meta, const InternalKeyComparator& icmp,
bool enable_hash, bool finished = false,
uint64_t precalculated_hash = 0) {
outputs_.emplace_back(std::move(meta), icmp, enable_hash, finished,
precalculated_hash);
precalculated_hash, is_proximal_level_);
}
// Set new table builder for the current output
@@ -63,34 +66,29 @@ class CompactionOutputs {
file_writer_.reset(writer);
}
// TODO: Remove it when remote compaction support tiered compaction
void AddBytesWritten(uint64_t bytes) { stats_.bytes_written += bytes; }
void SetNumOutputRecords(uint64_t num) { stats_.num_output_records = num; }
void SetNumOutputFiles(uint64_t num) { stats_.num_output_files = num; }
// TODO: Move the BlobDB builder into CompactionOutputs
const std::vector<BlobFileAddition>& GetBlobFileAdditions() const {
if (is_penultimate_level_) {
if (is_proximal_level_) {
assert(blob_file_additions_.empty());
}
return blob_file_additions_;
}
std::vector<BlobFileAddition>* GetBlobFileAdditionsPtr() {
assert(!is_penultimate_level_);
assert(!is_proximal_level_);
return &blob_file_additions_;
}
bool HasBlobFileAdditions() const { return !blob_file_additions_.empty(); }
BlobGarbageMeter* CreateBlobGarbageMeter() {
assert(!is_penultimate_level_);
assert(!is_proximal_level_);
blob_garbage_meter_ = std::make_unique<BlobGarbageMeter>();
return blob_garbage_meter_.get();
}
BlobGarbageMeter* GetBlobGarbageMeter() const {
if (is_penultimate_level_) {
if (is_proximal_level_) {
// blobdb doesn't support per_key_placement yet
assert(blob_garbage_meter_ == nullptr);
return nullptr;
@@ -99,8 +97,9 @@ class CompactionOutputs {
}
void UpdateBlobStats() {
assert(!is_penultimate_level_);
stats_.num_output_files_blob = blob_file_additions_.size();
assert(!is_proximal_level_);
stats_.num_output_files_blob =
static_cast<int>(blob_file_additions_.size());
for (const auto& blob : blob_file_additions_) {
stats_.bytes_written_blob += blob.GetTotalBlobBytes();
}
@@ -304,12 +303,12 @@ class CompactionOutputs {
std::vector<BlobFileAddition> blob_file_additions_;
std::unique_ptr<BlobGarbageMeter> blob_garbage_meter_;
// Basic compaction output stats for this level's outputs
InternalStats::CompactionOutputsStats stats_;
// Per level's output stat
InternalStats::CompactionStats stats_;
// indicate if this CompactionOutputs obj for penultimate_level, should always
// indicate if this CompactionOutputs obj for proximal_level, should always
// be false if per_key_placement feature is not enabled.
const bool is_penultimate_level_;
const bool is_proximal_level_;
// partitioner information
std::string last_key_for_partitioner_;
@@ -363,7 +362,7 @@ class CompactionOutputs {
std::vector<size_t> level_ptrs_;
};
// helper struct to concatenate the last level and penultimate level outputs
// helper struct to concatenate the last level and proximal level outputs
// which could be replaced by std::ranges::join_view() in c++20
struct OutputIterator {
public:
+19 -19
View File
@@ -272,8 +272,8 @@ bool CompactionPicker::RangeOverlapWithCompaction(
return true;
}
if (c->SupportsPerKeyPlacement()) {
if (c->OverlapPenultimateLevelOutputRange(smallest_user_key,
largest_user_key)) {
if (c->OverlapProximalLevelOutputRange(smallest_user_key,
largest_user_key)) {
return true;
}
}
@@ -284,7 +284,7 @@ bool CompactionPicker::RangeOverlapWithCompaction(
bool CompactionPicker::FilesRangeOverlapWithCompaction(
const std::vector<CompactionInputFiles>& inputs, int level,
int penultimate_level) const {
int proximal_level) const {
bool is_empty = true;
for (auto& in : inputs) {
if (!in.empty()) {
@@ -301,18 +301,18 @@ bool CompactionPicker::FilesRangeOverlapWithCompaction(
// files cannot be overlapped in the order of L0 files.
InternalKey smallest, largest;
GetRange(inputs, &smallest, &largest, Compaction::kInvalidLevel);
if (penultimate_level != Compaction::kInvalidLevel) {
if (proximal_level != Compaction::kInvalidLevel) {
if (ioptions_.compaction_style == kCompactionStyleUniversal) {
if (RangeOverlapWithCompaction(smallest.user_key(), largest.user_key(),
penultimate_level)) {
proximal_level)) {
return true;
}
} else {
InternalKey penultimate_smallest, penultimate_largest;
GetRange(inputs, &penultimate_smallest, &penultimate_largest, level);
if (RangeOverlapWithCompaction(penultimate_smallest.user_key(),
penultimate_largest.user_key(),
penultimate_level)) {
InternalKey proximal_smallest, proximal_largest;
GetRange(inputs, &proximal_smallest, &proximal_largest, level);
if (RangeOverlapWithCompaction(proximal_smallest.user_key(),
proximal_largest.user_key(),
proximal_level)) {
return true;
}
}
@@ -353,7 +353,7 @@ Compaction* CompactionPicker::CompactFiles(
}
assert(output_level == 0 || !FilesRangeOverlapWithCompaction(
input_files, output_level,
Compaction::EvaluatePenultimateLevel(
Compaction::EvaluateProximalLevel(
vstorage, mutable_cf_options, ioptions_,
start_level, output_level)));
#endif /* !NDEBUG */
@@ -659,9 +659,9 @@ Compaction* CompactionPicker::CompactRange(
// overlaping outputs in the same level.
if (FilesRangeOverlapWithCompaction(
inputs, output_level,
Compaction::EvaluatePenultimateLevel(vstorage, mutable_cf_options,
ioptions_, start_level,
output_level))) {
Compaction::EvaluateProximalLevel(vstorage, mutable_cf_options,
ioptions_, start_level,
output_level))) {
// This compaction output could potentially conflict with the output
// of a currently running compaction, we cannot run it.
*manual_conflict = true;
@@ -848,9 +848,9 @@ Compaction* CompactionPicker::CompactRange(
// overlaping outputs in the same level.
if (FilesRangeOverlapWithCompaction(
compaction_inputs, output_level,
Compaction::EvaluatePenultimateLevel(vstorage, mutable_cf_options,
ioptions_, input_level,
output_level))) {
Compaction::EvaluateProximalLevel(vstorage, mutable_cf_options,
ioptions_, input_level,
output_level))) {
// This compaction output could potentially conflict with the output
// of a currently running compaction, we cannot run it.
*manual_conflict = true;
@@ -1137,7 +1137,7 @@ Status CompactionPicker::SanitizeAndConvertCompactionInputFiles(
if (output_level != 0 &&
FilesRangeOverlapWithCompaction(
*converted_input_files, output_level,
Compaction::EvaluatePenultimateLevel(
Compaction::EvaluateProximalLevel(
version->storage_info(), version->GetMutableCFOptions(),
ioptions_, (*converted_input_files)[0].level, output_level))) {
return Status::Aborted(
@@ -1154,7 +1154,7 @@ void CompactionPicker::RegisterCompaction(Compaction* c) {
assert(ioptions_.compaction_style != kCompactionStyleLevel ||
c->output_level() == 0 ||
!FilesRangeOverlapWithCompaction(*c->inputs(), c->output_level(),
c->GetPenultimateLevel()));
c->GetProximalLevel()));
// CompactionReason::kExternalSstIngestion's start level is just a placeholder
// number without actual meaning as file ingestion technically does not have
// an input level like other compactions
+7 -1
View File
@@ -138,6 +138,12 @@ class CompactionPicker {
return !level0_compactions_in_progress_.empty();
}
// Is any compaction in progress
bool IsCompactionInProgress() const {
return !(level0_compactions_in_progress_.empty() &&
compactions_in_progress_.empty());
}
// Return true if the passed key range overlap with a compaction output
// that is currently running.
bool RangeOverlapWithCompaction(const Slice& smallest_user_key,
@@ -190,7 +196,7 @@ class CompactionPicker {
// key range of a currently running compaction.
bool FilesRangeOverlapWithCompaction(
const std::vector<CompactionInputFiles>& inputs, int level,
int penultimate_level) const;
int proximal_level) const;
bool SetupOtherInputs(const std::string& cf_name,
const MutableCFOptions& mutable_cf_options,
+9 -9
View File
@@ -414,9 +414,9 @@ void LevelCompactionBuilder::SetupOtherFilesWithRoundRobinExpansion() {
&tmp_start_level_inputs) ||
compaction_picker_->FilesRangeOverlapWithCompaction(
{tmp_start_level_inputs}, output_level_,
Compaction::EvaluatePenultimateLevel(vstorage_, mutable_cf_options_,
ioptions_, start_level_,
output_level_))) {
Compaction::EvaluateProximalLevel(vstorage_, mutable_cf_options_,
ioptions_, start_level_,
output_level_))) {
// Constraint 1a
tmp_start_level_inputs.clear();
return;
@@ -490,9 +490,9 @@ bool LevelCompactionBuilder::SetupOtherInputsIfNeeded() {
// We need to disallow this from happening.
if (compaction_picker_->FilesRangeOverlapWithCompaction(
compaction_inputs_, output_level_,
Compaction::EvaluatePenultimateLevel(vstorage_, mutable_cf_options_,
ioptions_, start_level_,
output_level_))) {
Compaction::EvaluateProximalLevel(vstorage_, mutable_cf_options_,
ioptions_, start_level_,
output_level_))) {
// This compaction output could potentially conflict with the output
// of a currently running compaction, we cannot run it.
return false;
@@ -846,9 +846,9 @@ bool LevelCompactionBuilder::PickFileToCompact() {
&start_level_inputs_) ||
compaction_picker_->FilesRangeOverlapWithCompaction(
{start_level_inputs_}, output_level_,
Compaction::EvaluatePenultimateLevel(vstorage_, mutable_cf_options_,
ioptions_, start_level_,
output_level_))) {
Compaction::EvaluateProximalLevel(vstorage_, mutable_cf_options_,
ioptions_, start_level_,
output_level_))) {
// A locked (pending compaction) input-level file was pulled in due to
// user-key overlap.
start_level_inputs_.clear();
+29 -29
View File
@@ -3677,7 +3677,7 @@ TEST_F(CompactionPickerTest, UniversalSizeRatioTierCompactionLastLevel) {
const uint64_t kFileSize = 100000;
const int kNumLevels = 7;
const int kLastLevel = kNumLevels - 1;
const int kPenultimateLevel = kLastLevel - 1;
const int kProximalLevel = kLastLevel - 1;
ioptions_.compaction_style = kCompactionStyleUniversal;
mutable_cf_options_.preclude_last_level_data_seconds = 1000;
@@ -3702,14 +3702,14 @@ TEST_F(CompactionPickerTest, UniversalSizeRatioTierCompactionLastLevel) {
// Here to make sure it's size ratio compaction instead of size amp
ASSERT_EQ(compaction->compaction_reason(),
CompactionReason::kUniversalSizeRatio);
ASSERT_EQ(compaction->output_level(), kPenultimateLevel - 1);
ASSERT_EQ(compaction->output_level(), kProximalLevel - 1);
ASSERT_EQ(compaction->input_levels(0)->num_files, 2);
ASSERT_EQ(compaction->input_levels(5)->num_files, 0);
ASSERT_EQ(compaction->input_levels(6)->num_files, 0);
}
TEST_F(CompactionPickerTest, UniversalSizeAmpTierCompactionNotSuport) {
// Tiered compaction only support level_num > 2 (otherwise the penultimate
// Tiered compaction only support level_num > 2 (otherwise the proximal
// level is going to be level 0, which may make thing more complicated), so
// when there's only 2 level, still treating level 1 as the last level for
// size amp compaction
@@ -3753,7 +3753,7 @@ TEST_F(CompactionPickerTest, UniversalSizeAmpTierCompactionLastLevel) {
const uint64_t kFileSize = 100000;
const int kNumLevels = 7;
const int kLastLevel = kNumLevels - 1;
const int kPenultimateLevel = kLastLevel - 1;
const int kProximalLevel = kLastLevel - 1;
ioptions_.compaction_style = kCompactionStyleUniversal;
mutable_cf_options_.preclude_last_level_data_seconds = 1000;
@@ -3775,10 +3775,10 @@ TEST_F(CompactionPickerTest, UniversalSizeAmpTierCompactionLastLevel) {
vstorage_.get(), &log_buffer_));
// It's a Size Amp compaction, but doesn't include the last level file and
// output to the penultimate level.
// output to the proximal level.
ASSERT_EQ(compaction->compaction_reason(),
CompactionReason::kUniversalSizeAmplification);
ASSERT_EQ(compaction->output_level(), kPenultimateLevel);
ASSERT_EQ(compaction->output_level(), kProximalLevel);
ASSERT_EQ(compaction->input_levels(0)->num_files, 2);
ASSERT_EQ(compaction->input_levels(5)->num_files, 1);
ASSERT_EQ(compaction->input_levels(6)->num_files, 0);
@@ -3940,7 +3940,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest, OverlapWithNormalCompaction) {
ASSERT_EQ(enable_per_key_placement_,
level_compaction_picker.FilesRangeOverlapWithCompaction(
input_files, 6,
Compaction::EvaluatePenultimateLevel(
Compaction::EvaluateProximalLevel(
vstorage_.get(), mutable_cf_options_, ioptions_, 0, 6)));
}
@@ -4028,7 +4028,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
ASSERT_EQ(enable_per_key_placement_,
universal_compaction_picker.FilesRangeOverlapWithCompaction(
input_files, 6,
Compaction::EvaluatePenultimateLevel(
Compaction::EvaluateProximalLevel(
vstorage_.get(), mutable_cf_options_, ioptions_, 0, 6)));
}
@@ -4076,9 +4076,9 @@ TEST_P(PerKeyPlacementCompactionPickerTest, NormalCompactionOverlapUniversal) {
input_files, 5, Compaction::kInvalidLevel));
}
TEST_P(PerKeyPlacementCompactionPickerTest, PenultimateOverlapUniversal) {
TEST_P(PerKeyPlacementCompactionPickerTest, ProximalOverlapUniversal) {
// This test is make sure the Tiered compaction would lock whole range of
// both output level and penultimate level
// both output level and proximal level
if (enable_per_key_placement_) {
mutable_cf_options_.preclude_last_level_data_seconds = 10000;
}
@@ -4098,7 +4098,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest, PenultimateOverlapUniversal) {
UpdateVersionStorageInfo();
// the existing compaction is the 1st L4 file + L6 file
// then compaction of the 2nd L4 file to L5 (penultimate level) is overlapped
// then compaction of the 2nd L4 file to L5 (proximal level) is overlapped
// when the tiered compaction feature is on.
CompactionOptions comp_options;
std::unordered_set<uint64_t> input_set;
@@ -4187,9 +4187,9 @@ TEST_P(PerKeyPlacementCompactionPickerTest, LastLevelOnlyOverlapUniversal) {
}
TEST_P(PerKeyPlacementCompactionPickerTest,
LastLevelOnlyFailPenultimateUniversal) {
LastLevelOnlyFailProximalUniversal) {
// This is to test last_level only compaction still unable to do the
// penultimate level compaction if there's already a file in the penultimate
// proximal level compaction if there's already a file in the proximal
// level.
// This should rarely happen in universal compaction, as the non-empty L5
// should be included in the compaction.
@@ -4222,9 +4222,9 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
mutable_db_options_, 0));
ASSERT_TRUE(comp1);
ASSERT_EQ(comp1->GetPenultimateLevel(), Compaction::kInvalidLevel);
ASSERT_EQ(comp1->GetProximalLevel(), Compaction::kInvalidLevel);
// As comp1 cannot be output to the penultimate level, compacting file 40 to
// As comp1 cannot be output to the proximal level, compacting file 40 to
// L5 is always safe.
input_set.clear();
input_files.clear();
@@ -4239,14 +4239,14 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
comp_options, input_files, 5, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
ASSERT_TRUE(comp2);
ASSERT_EQ(Compaction::kInvalidLevel, comp2->GetPenultimateLevel());
ASSERT_EQ(Compaction::kInvalidLevel, comp2->GetProximalLevel());
}
TEST_P(PerKeyPlacementCompactionPickerTest,
LastLevelOnlyConflictWithOngoingUniversal) {
// This is to test last_level only compaction still unable to do the
// penultimate level compaction if there's already an ongoing compaction to
// the penultimate level
// proximal level compaction if there's already an ongoing compaction to
// the proximal level
if (enable_per_key_placement_) {
mutable_cf_options_.preclude_last_level_data_seconds = 10000;
}
@@ -4265,7 +4265,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
Add(6, 60U, "101", "351", 60000000U);
UpdateVersionStorageInfo();
// create an ongoing compaction to L5 (penultimate level)
// create an ongoing compaction to L5 (proximal level)
CompactionOptions comp_options;
std::unordered_set<uint64_t> input_set;
input_set.insert(40);
@@ -4278,7 +4278,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
mutable_db_options_, 0));
ASSERT_TRUE(comp1);
ASSERT_EQ(comp1->GetPenultimateLevel(), Compaction::kInvalidLevel);
ASSERT_EQ(comp1->GetProximalLevel(), Compaction::kInvalidLevel);
input_set.clear();
input_files.clear();
@@ -4289,7 +4289,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
ASSERT_EQ(enable_per_key_placement_,
universal_compaction_picker.FilesRangeOverlapWithCompaction(
input_files, 6,
Compaction::EvaluatePenultimateLevel(
Compaction::EvaluateProximalLevel(
vstorage_.get(), mutable_cf_options_, ioptions_, 6, 6)));
if (!enable_per_key_placement_) {
@@ -4297,7 +4297,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
mutable_db_options_, 0));
ASSERT_TRUE(comp2);
ASSERT_EQ(Compaction::kInvalidLevel, comp2->GetPenultimateLevel());
ASSERT_EQ(Compaction::kInvalidLevel, comp2->GetProximalLevel());
}
}
@@ -4306,7 +4306,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
// This is similar to `LastLevelOnlyConflictWithOngoingUniversal`, the only
// change is the ongoing compaction to L5 has no overlap with the last level
// compaction, so it's safe to move data from the last level to the
// penultimate level.
// proximal level.
if (enable_per_key_placement_) {
mutable_cf_options_.preclude_last_level_data_seconds = 10000;
}
@@ -4325,7 +4325,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
Add(6, 60U, "101", "351", 60000000U);
UpdateVersionStorageInfo();
// create an ongoing compaction to L5 (penultimate level)
// create an ongoing compaction to L5 (proximal level)
CompactionOptions comp_options;
std::unordered_set<uint64_t> input_set;
input_set.insert(42);
@@ -4338,7 +4338,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
mutable_db_options_, 0));
ASSERT_TRUE(comp1);
ASSERT_EQ(comp1->GetPenultimateLevel(), Compaction::kInvalidLevel);
ASSERT_EQ(comp1->GetProximalLevel(), Compaction::kInvalidLevel);
input_set.clear();
input_files.clear();
@@ -4349,8 +4349,8 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
// always safe to move data up
ASSERT_FALSE(universal_compaction_picker.FilesRangeOverlapWithCompaction(
input_files, 6,
Compaction::EvaluatePenultimateLevel(vstorage_.get(), mutable_cf_options_,
ioptions_, 6, 6)));
Compaction::EvaluateProximalLevel(vstorage_.get(), mutable_cf_options_,
ioptions_, 6, 6)));
// 2 compactions can be run in parallel
std::unique_ptr<Compaction> comp2(universal_compaction_picker.CompactFiles(
@@ -4358,9 +4358,9 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
mutable_db_options_, 0));
ASSERT_TRUE(comp2);
if (enable_per_key_placement_) {
ASSERT_NE(Compaction::kInvalidLevel, comp2->GetPenultimateLevel());
ASSERT_NE(Compaction::kInvalidLevel, comp2->GetProximalLevel());
} else {
ASSERT_EQ(Compaction::kInvalidLevel, comp2->GetPenultimateLevel());
ASSERT_EQ(Compaction::kInvalidLevel, comp2->GetProximalLevel());
}
}
+14 -27
View File
@@ -288,7 +288,9 @@ class UniversalCompactionBuilder {
// and the index of the file in that level
struct InputFileInfo {
InputFileInfo() : f(nullptr), level(0), index(0) {}
InputFileInfo() : InputFileInfo(nullptr, 0, 0) {}
InputFileInfo(FileMetaData* file_meta, size_t l, size_t i)
: f(file_meta), level(l), index(i) {}
FileMetaData* f;
size_t level;
@@ -321,22 +323,14 @@ SmallestKeyHeap create_level_heap(Compaction* c, const Comparator* ucmp) {
SmallestKeyHeap smallest_key_priority_q =
SmallestKeyHeap(SmallestKeyHeapComparator(ucmp));
InputFileInfo input_file;
for (size_t l = 0; l < c->num_input_levels(); l++) {
if (c->num_input_files(l) != 0) {
if (l == 0 && c->start_level() == 0) {
for (size_t i = 0; i < c->num_input_files(0); i++) {
input_file.f = c->input(0, i);
input_file.level = 0;
input_file.index = i;
smallest_key_priority_q.push(std::move(input_file));
smallest_key_priority_q.emplace(c->input(0, i), 0, i);
}
} else {
input_file.f = c->input(l, 0);
input_file.level = l;
input_file.index = 0;
smallest_key_priority_q.push(std::move(input_file));
smallest_key_priority_q.emplace(c->input(l, 0), l, 0);
}
}
}
@@ -374,7 +368,7 @@ bool UniversalCompactionBuilder::IsInputFilesNonOverlapping(Compaction* c) {
auto comparator = icmp_->user_comparator();
int first_iter = 1;
InputFileInfo prev, curr, next;
InputFileInfo prev, curr;
SmallestKeyHeap smallest_key_priority_q =
create_level_heap(c, icmp_->user_comparator());
@@ -397,17 +391,10 @@ bool UniversalCompactionBuilder::IsInputFilesNonOverlapping(Compaction* c) {
prev = curr;
}
next.f = nullptr;
if (c->level(curr.level) != 0 &&
curr.index < c->num_input_files(curr.level) - 1) {
next.f = c->input(curr.level, curr.index + 1);
next.level = curr.level;
next.index = curr.index + 1;
}
if (next.f) {
smallest_key_priority_q.push(std::move(next));
smallest_key_priority_q.emplace(c->input(curr.level, curr.index + 1),
curr.level, curr.index + 1);
}
}
return true;
@@ -996,7 +983,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
if (output_level != 0 && picker_->FilesRangeOverlapWithCompaction(
inputs, output_level,
Compaction::EvaluatePenultimateLevel(
Compaction::EvaluateProximalLevel(
vstorage_, mutable_cf_options_, ioptions_,
start_level, output_level))) {
return nullptr;
@@ -1345,7 +1332,7 @@ Compaction* UniversalCompactionBuilder::PickIncrementalForReduceSizeAmp(
// intra L0 compactions outputs could have overlap
if (output_level != 0 && picker_->FilesRangeOverlapWithCompaction(
inputs, output_level,
Compaction::EvaluatePenultimateLevel(
Compaction::EvaluateProximalLevel(
vstorage_, mutable_cf_options_, ioptions_,
start_level, output_level))) {
return nullptr;
@@ -1486,9 +1473,9 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
}
if (picker_->FilesRangeOverlapWithCompaction(
inputs, output_level,
Compaction::EvaluatePenultimateLevel(
vstorage_, mutable_cf_options_, ioptions_, start_level,
output_level))) {
Compaction::EvaluateProximalLevel(vstorage_, mutable_cf_options_,
ioptions_, start_level,
output_level))) {
return nullptr;
}
@@ -1590,7 +1577,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionWithSortedRunRange(
// intra L0 compactions outputs could have overlap
if (output_level != 0 && picker_->FilesRangeOverlapWithCompaction(
inputs, output_level,
Compaction::EvaluatePenultimateLevel(
Compaction::EvaluateProximalLevel(
vstorage_, mutable_cf_options_, ioptions_,
start_level, output_level))) {
return nullptr;
+195 -40
View File
@@ -110,6 +110,9 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
break;
}
std::string debug_str_before_wait =
compaction->input_version()->DebugString();
ROCKS_LOG_INFO(db_options_.info_log,
"[%s] [JOB %d] Waiting for remote compaction...",
compaction->column_family_data()->GetName().c_str(), job_id_);
@@ -118,6 +121,16 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
db_options_.compaction_service->Wait(response.scheduled_job_id,
&compaction_result_binary);
if (compaction_status != CompactionServiceJobStatus::kSuccess) {
ROCKS_LOG_ERROR(db_options_.info_log,
"[%s] [JOB %d] Wait() status is not kSuccess. "
"\nDebugString Before Wait():\n%s"
"\nDebugString After Wait():\n%s",
compaction->column_family_data()->GetName().c_str(),
job_id_, debug_str_before_wait.c_str(),
compaction->input_version()->DebugString().c_str());
}
if (compaction_status == CompactionServiceJobStatus::kUseLocal) {
ROCKS_LOG_INFO(
db_options_.info_log,
@@ -226,19 +239,34 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
meta.file_checksum_func_name = file.file_checksum_func_name;
meta.marked_for_compaction = file.marked_for_compaction;
meta.unique_id = file.unique_id;
meta.temperature = file.file_temperature;
auto cfd = compaction->column_family_data();
sub_compact->Current().AddOutput(std::move(meta),
cfd->internal_comparator(), false, true,
file.paranoid_hash);
sub_compact->Current().UpdateTableProperties(file.table_properties);
CompactionOutputs* compaction_outputs =
sub_compact->Outputs(file.is_proximal_level_output);
assert(compaction_outputs);
compaction_outputs->AddOutput(std::move(meta), cfd->internal_comparator(),
false, true, file.paranoid_hash);
compaction_outputs->UpdateTableProperties(file.table_properties);
}
// Set per-level stats
auto compaction_output_stats =
sub_compact->OutputStats(false /* is_proximal_level */);
assert(compaction_output_stats);
compaction_output_stats->Add(
compaction_result.internal_stats.output_level_stats);
if (compaction->SupportsPerKeyPlacement()) {
compaction_output_stats =
sub_compact->OutputStats(true /* is_proximal_level */);
assert(compaction_output_stats);
compaction_output_stats->Add(
compaction_result.internal_stats.proximal_level_stats);
}
// Set job stats
sub_compact->compaction_job_stats = compaction_result.stats;
sub_compact->Current().SetNumOutputRecords(
compaction_result.stats.num_output_records);
sub_compact->Current().SetNumOutputFiles(
compaction_result.stats.num_output_files);
sub_compact->Current().AddBytesWritten(compaction_result.bytes_written);
RecordTick(stats_, REMOTE_COMPACT_READ_BYTES, compaction_result.bytes_read);
RecordTick(stats_, REMOTE_COMPACT_WRITE_BYTES,
compaction_result.bytes_written);
@@ -258,18 +286,6 @@ void CompactionServiceCompactionJob::RecordCompactionIOStats() {
CompactionJob::RecordCompactionIOStats();
}
void CompactionServiceCompactionJob::UpdateCompactionJobStats(
const InternalStats::CompactionStats& stats) const {
compaction_job_stats_->elapsed_micros = stats.micros;
// output information only in remote compaction
compaction_job_stats_->total_output_bytes = stats.bytes_written;
compaction_job_stats_->total_output_bytes_blob = stats.bytes_written_blob;
compaction_job_stats_->num_output_records = stats.num_output_records;
compaction_job_stats_->num_output_files = stats.num_output_files;
compaction_job_stats_->num_output_files_blob = stats.num_output_files_blob;
}
CompactionServiceCompactionJob::CompactionServiceCompactionJob(
int job_id, Compaction* compaction, const ImmutableDBOptions& db_options,
const MutableDBOptions& mutable_db_options, const FileOptions& file_options,
@@ -331,15 +347,14 @@ Status CompactionServiceCompactionJob::Run() {
ProcessKeyValueCompaction(sub_compact);
compaction_stats_.stats.micros =
db_options_.clock->NowMicros() - start_micros;
compaction_stats_.stats.cpu_micros =
sub_compact->compaction_job_stats.cpu_micros;
uint64_t elapsed_micros = db_options_.clock->NowMicros() - start_micros;
internal_stats_.SetMicros(elapsed_micros);
internal_stats_.AddCpuMicros(elapsed_micros);
RecordTimeToHistogram(stats_, COMPACTION_TIME,
compaction_stats_.stats.micros);
internal_stats_.output_level_stats.micros);
RecordTimeToHistogram(stats_, COMPACTION_CPU_TIME,
compaction_stats_.stats.cpu_micros);
internal_stats_.output_level_stats.cpu_micros);
Status status = sub_compact->status;
IOStatus io_s = sub_compact->io_status;
@@ -369,25 +384,31 @@ Status CompactionServiceCompactionJob::Run() {
// Build Compaction Job Stats
// 1. Aggregate CompactionOutputStats into Internal Compaction Stats
// (compaction_stats_) and aggregate Compaction Job Stats
// (compaction_job_stats_) from the sub compactions
compact_->AggregateCompactionStats(compaction_stats_, *compaction_job_stats_);
// 1. Aggregate internal stats and job stats for all subcompactions
// internal stats: sub_compact.proximal_level_outputs_.stats and
// sub_compact.compaction_outputs_.stats into
// internal_stats_.output_level_stats and
// internal_stats_.proximal_level_stats
// job-level stats: sub_compact.compaction_job_stats into compact.job_stats_
//
// For remote compaction, there's only one subcompaction.
compact_->AggregateCompactionStats(internal_stats_, *job_stats_);
// 2. Update the Output information in the Compaction Job Stats with
// aggregated Internal Compaction Stats.
UpdateCompactionJobStats(compaction_stats_.stats);
// 3. Set fields that are not propagated as part of aggregations above
// 2. Update job-level output stats with the aggregated internal_stats_
// Please note that input stats will be updated by primary host when all
// subcompactions are finished
UpdateCompactionJobOutputStats(internal_stats_);
// and set fields that are not propagated as part of the update
compaction_result_->stats.is_manual_compaction = c->is_manual_compaction();
compaction_result_->stats.is_full_compaction = c->is_full_compaction();
compaction_result_->stats.is_remote_compaction = true;
// 4. Update IO Stats that are not part of the aggregations above (bytes_read,
// bytes_written)
// 3. Update IO Stats that are not part of the the update above
// (bytes_read, bytes_written)
RecordCompactionIOStats();
// Build Output
compaction_result_->internal_stats = internal_stats_;
compaction_result_->output_level = compact_->compaction->output_level();
compaction_result_->output_path = output_path_;
if (status.ok()) {
@@ -400,7 +421,8 @@ Status CompactionServiceCompactionJob::Run() {
meta.file_creation_time, meta.epoch_number, meta.file_checksum,
meta.file_checksum_func_name, output_file.validator.GetHash(),
meta.marked_for_compaction, meta.unique_id,
*output_file.table_properties);
*output_file.table_properties, output_file.is_proximal_level,
meta.temperature);
}
}
@@ -572,7 +594,16 @@ static std::unordered_map<std::string, OptionTypeInfo>
const auto this_one = static_cast<const TableProperties*>(addr1);
const auto that_one = static_cast<const TableProperties*>(addr2);
return this_one->AreEqual(opts, that_one, mismatch);
}}}};
}}},
{"is_proximal_level_output",
{offsetof(struct CompactionServiceOutputFile,
is_proximal_level_output),
OptionType::kBoolean, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"file_temperature",
{offsetof(struct CompactionServiceOutputFile, file_temperature),
OptionType::kTemperature, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}}};
static std::unordered_map<std::string, OptionTypeInfo>
compaction_job_stats_type_info = {
@@ -697,6 +728,125 @@ static std::unordered_map<std::string, OptionTypeInfo>
OptionTypeFlags::kNone}},
};
static std::unordered_map<std::string, OptionTypeInfo>
compaction_stats_type_info = {
{"micros",
{offsetof(struct InternalStats::CompactionStats, micros),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"cpu_micros",
{offsetof(struct InternalStats::CompactionStats, cpu_micros),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"bytes_read_non_output_levels",
{offsetof(struct InternalStats::CompactionStats,
bytes_read_non_output_levels),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"bytes_read_output_level",
{offsetof(struct InternalStats::CompactionStats,
bytes_read_output_level),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"bytes_skipped_non_output_levels",
{offsetof(struct InternalStats::CompactionStats,
bytes_skipped_non_output_levels),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"bytes_skipped_output_level",
{offsetof(struct InternalStats::CompactionStats,
bytes_skipped_output_level),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"bytes_read_blob",
{offsetof(struct InternalStats::CompactionStats, bytes_read_blob),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"bytes_written",
{offsetof(struct InternalStats::CompactionStats, bytes_written),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"bytes_written_blob",
{offsetof(struct InternalStats::CompactionStats, bytes_written_blob),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"bytes_moved",
{offsetof(struct InternalStats::CompactionStats, bytes_moved),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"num_input_files_in_non_output_levels",
{offsetof(struct InternalStats::CompactionStats,
num_input_files_in_non_output_levels),
OptionType::kInt, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"num_input_files_in_output_level",
{offsetof(struct InternalStats::CompactionStats,
num_input_files_in_output_level),
OptionType::kInt, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"num_filtered_input_files_in_non_output_levels",
{offsetof(struct InternalStats::CompactionStats,
num_filtered_input_files_in_non_output_levels),
OptionType::kInt, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"num_filtered_input_files_in_output_level",
{offsetof(struct InternalStats::CompactionStats,
num_filtered_input_files_in_output_level),
OptionType::kInt, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"num_output_files",
{offsetof(struct InternalStats::CompactionStats, num_output_files),
OptionType::kInt, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"num_output_files_blob",
{offsetof(struct InternalStats::CompactionStats,
num_output_files_blob),
OptionType::kInt, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"num_input_records",
{offsetof(struct InternalStats::CompactionStats, num_input_records),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"num_dropped_records",
{offsetof(struct InternalStats::CompactionStats, num_dropped_records),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"num_output_records",
{offsetof(struct InternalStats::CompactionStats, num_output_records),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"count",
{offsetof(struct InternalStats::CompactionStats, count),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"counts", OptionTypeInfo::Array<
int, static_cast<int>(CompactionReason::kNumOfReasons)>(
offsetof(struct InternalStats::CompactionStats, counts),
OptionVerificationType::kNormal, OptionTypeFlags::kNone,
{0, OptionType::kInt})},
};
static std::unordered_map<std::string, OptionTypeInfo>
compaction_internal_stats_type_info = {
{"output_level_stats",
OptionTypeInfo::Struct(
"output_level_stats", &compaction_stats_type_info,
offsetof(struct InternalStats::CompactionStatsFull,
output_level_stats),
OptionVerificationType::kNormal, OptionTypeFlags::kNone)},
{"has_proximal_level_output",
{offsetof(struct InternalStats::CompactionStatsFull,
has_proximal_level_output),
OptionType::kBoolean, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"proximal_level_stats",
OptionTypeInfo::Struct(
"proximal_level_stats", &compaction_stats_type_info,
offsetof(struct InternalStats::CompactionStatsFull,
proximal_level_stats),
OptionVerificationType::kNormal, OptionTypeFlags::kNone)},
};
namespace {
// this is a helper struct to serialize and deserialize class Status, because
// Status's members are not public.
@@ -803,6 +953,11 @@ static std::unordered_map<std::string, OptionTypeInfo> cs_result_type_info = {
"stats", &compaction_job_stats_type_info,
offsetof(struct CompactionServiceResult, stats),
OptionVerificationType::kNormal, OptionTypeFlags::kNone)},
{"internal_stats",
OptionTypeInfo::Struct(
"internal_stats", &compaction_internal_stats_type_info,
offsetof(struct CompactionServiceResult, internal_stats),
OptionVerificationType::kNormal, OptionTypeFlags::kNone)},
};
Status CompactionServiceInput::Read(const std::string& data_str,
+66 -21
View File
@@ -357,11 +357,12 @@ TEST_F(CompactionServiceTest, BasicCompactions) {
} else {
ASSERT_OK(result.status);
}
ASSERT_GE(result.stats.elapsed_micros, 1);
ASSERT_GE(result.stats.cpu_micros, 1);
ASSERT_GE(result.internal_stats.output_level_stats.micros, 1);
ASSERT_GE(result.internal_stats.output_level_stats.cpu_micros, 1);
ASSERT_EQ(20, result.stats.num_output_records);
ASSERT_EQ(result.output_files.size(), result.stats.num_output_files);
ASSERT_EQ(20, result.internal_stats.output_level_stats.num_output_records);
ASSERT_EQ(result.output_files.size(),
result.internal_stats.output_level_stats.num_output_files);
uint64_t total_size = 0;
for (auto output_file : result.output_files) {
@@ -372,7 +373,7 @@ TEST_F(CompactionServiceTest, BasicCompactions) {
ASSERT_GT(file_size, 0);
total_size += file_size;
}
ASSERT_EQ(total_size, result.stats.total_output_bytes);
ASSERT_EQ(total_size, result.internal_stats.TotalBytesWritten());
ASSERT_TRUE(result.stats.is_remote_compaction);
ASSERT_TRUE(result.stats.is_manual_compaction);
@@ -716,6 +717,46 @@ TEST_F(CompactionServiceTest, VerifyStatsLocalFallback) {
VerifyTestData();
}
TEST_F(CompactionServiceTest, VerifyInputRecordCount) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
ReopenWithCompactionService(&options);
GenerateTestData();
auto my_cs = GetCompactionService();
std::string start_str = Key(15);
std::string end_str = Key(45);
Slice start(start_str);
Slice end(end_str);
uint64_t comp_num = my_cs->GetCompactionNum();
// Only iterator through 10 keys and force compaction to finish.
int num_iter = 0;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"CompactionJob::ProcessKeyValueCompaction()::stop", [&](void* stop_ptr) {
num_iter++;
if (num_iter == 10) {
*(bool*)stop_ptr = true;
}
});
SyncPoint::GetInstance()->EnableProcessing();
// CompactRange() should fail
Status s = db_->CompactRange(CompactRangeOptions(), &start, &end);
ASSERT_NOK(s);
ASSERT_TRUE(s.IsCorruption());
const char* expected_message =
"Compaction number of input keys does not match number of keys "
"processed.";
ASSERT_TRUE(std::strstr(s.getState(), expected_message));
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
}
TEST_F(CompactionServiceTest, CorruptedOutput) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
@@ -1188,34 +1229,38 @@ TEST_F(CompactionServiceTest, PrecludeLastLevel) {
for (int i = 0; i < kNumTrigger; i++) {
for (int j = 0; j < kNumKeys; j++) {
// FIXME: need to assign outputs to levels to allow overlapping ranges:
// ASSERT_OK(Put(Key(j * kNumTrigger + i), "v" + std::to_string(i)));
// instead of this (too easy):
ASSERT_OK(Put(Key(i * kNumKeys + j), "v" + std::to_string(i)));
ASSERT_OK(Put(Key(j * kNumTrigger + i), "v" + std::to_string(i)));
}
ASSERT_OK(Flush());
}
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// Data split between penultimate (kUnknown) and last (kCold) levels
// FIXME: need to assign outputs to levels to get this:
// ASSERT_EQ("0,0,0,0,0,1,1", FilesPerLevel());
// ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
// ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
// instead of this (WRONG but currently expected):
ASSERT_EQ("0,0,0,0,0,0,2", FilesPerLevel());
// Check manifest temperatures
// Data split between proximal (kUnknown) and last (kCold) levels
ASSERT_EQ("0,0,0,0,0,1,1", FilesPerLevel());
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
// TODO: Check FileSystem temperatures with FileTemperatureTestFS
for (int i = 0; i < kNumTrigger; i++) {
for (int j = 0; j < kNumKeys; j++) {
// FIXME
// ASSERT_EQ(Get(Key(j * kNumTrigger + i)), "v" + std::to_string(i));
ASSERT_EQ(Get(Key(i * kNumKeys + j)), "v" + std::to_string(i));
ASSERT_EQ(Get(Key(j * kNumTrigger + i)), "v" + std::to_string(i));
}
}
// Verify Output Stats
auto my_cs = GetCompactionService();
CompactionServiceResult result;
my_cs->GetResult(&result);
ASSERT_OK(result.status);
ASSERT_GT(result.internal_stats.output_level_stats.cpu_micros, 0);
ASSERT_GT(result.internal_stats.output_level_stats.micros, 0);
ASSERT_EQ(result.internal_stats.output_level_stats.num_output_records +
result.internal_stats.proximal_level_stats.num_output_records,
kNumTrigger * kNumKeys);
ASSERT_EQ(result.internal_stats.output_level_stats.num_output_files +
result.internal_stats.proximal_level_stats.num_output_files,
2);
}
TEST_F(CompactionServiceTest, ConcurrentCompaction) {
+4 -4
View File
@@ -36,11 +36,11 @@ Slice CompactionState::LargestUserKey() {
}
void CompactionState::AggregateCompactionStats(
InternalStats::CompactionStatsFull& compaction_stats,
CompactionJobStats& compaction_job_stats) {
InternalStats::CompactionStatsFull& internal_stats,
CompactionJobStats& job_stats) {
for (const auto& sc : sub_compact_states) {
sc.AggregateCompactionOutputStats(compaction_stats);
compaction_job_stats.Add(sc.compaction_job_stats);
sc.AggregateCompactionOutputStats(internal_stats);
job_stats.Add(sc.compaction_job_stats);
}
}
} // namespace ROCKSDB_NAMESPACE
+2 -2
View File
@@ -29,8 +29,8 @@ class CompactionState {
Status status;
void AggregateCompactionStats(
InternalStats::CompactionStatsFull& compaction_stats,
CompactionJobStats& compaction_job_stats);
InternalStats::CompactionStatsFull& internal_stats,
CompactionJobStats& job_stats);
explicit CompactionState(Compaction* c) : compaction(c) {}
+17 -18
View File
@@ -14,33 +14,32 @@
namespace ROCKSDB_NAMESPACE {
void SubcompactionState::AggregateCompactionOutputStats(
InternalStats::CompactionStatsFull& compaction_stats) const {
InternalStats::CompactionStatsFull& internal_stats) const {
// Outputs should be closed. By extension, any files created just for
// range deletes have already been written also.
assert(compaction_outputs_.HasBuilder() == false);
assert(penultimate_level_outputs_.HasBuilder() == false);
assert(proximal_level_outputs_.HasBuilder() == false);
// FIXME: These stats currently include abandonned output files
// assert(compaction_outputs_.stats_.num_output_files ==
// compaction_outputs_.outputs_.size());
// assert(penultimate_level_outputs_.stats_.num_output_files ==
// penultimate_level_outputs_.outputs_.size());
// assert(proximal_level_outputs_.stats_.num_output_files ==
// proximal_level_outputs_.outputs_.size());
compaction_stats.stats.Add(compaction_outputs_.stats_);
if (penultimate_level_outputs_.HasOutput()) {
compaction_stats.has_penultimate_level_output = true;
compaction_stats.penultimate_level_stats.Add(
penultimate_level_outputs_.stats_);
internal_stats.output_level_stats.Add(compaction_outputs_.stats_);
if (proximal_level_outputs_.HasOutput()) {
internal_stats.has_proximal_level_output = true;
internal_stats.proximal_level_stats.Add(proximal_level_outputs_.stats_);
}
}
OutputIterator SubcompactionState::GetOutputs() const {
return OutputIterator(penultimate_level_outputs_.outputs_,
return OutputIterator(proximal_level_outputs_.outputs_,
compaction_outputs_.outputs_);
}
void SubcompactionState::Cleanup(Cache* cache) {
penultimate_level_outputs_.Cleanup();
proximal_level_outputs_.Cleanup();
compaction_outputs_.Cleanup();
if (!status.ok()) {
@@ -63,9 +62,9 @@ void SubcompactionState::Cleanup(Cache* cache) {
}
Slice SubcompactionState::SmallestUserKey() const {
if (penultimate_level_outputs_.HasOutput()) {
if (proximal_level_outputs_.HasOutput()) {
Slice a = compaction_outputs_.SmallestUserKey();
Slice b = penultimate_level_outputs_.SmallestUserKey();
Slice b = proximal_level_outputs_.SmallestUserKey();
if (a.empty()) {
return b;
}
@@ -85,9 +84,9 @@ Slice SubcompactionState::SmallestUserKey() const {
}
Slice SubcompactionState::LargestUserKey() const {
if (penultimate_level_outputs_.HasOutput()) {
if (proximal_level_outputs_.HasOutput()) {
Slice a = compaction_outputs_.LargestUserKey();
Slice b = penultimate_level_outputs_.LargestUserKey();
Slice b = proximal_level_outputs_.LargestUserKey();
if (a.empty()) {
return b;
}
@@ -107,12 +106,12 @@ Slice SubcompactionState::LargestUserKey() const {
}
Status SubcompactionState::AddToOutput(
const CompactionIterator& iter, bool use_penultimate_output,
const CompactionIterator& iter, bool use_proximal_output,
const CompactionFileOpenFunc& open_file_func,
const CompactionFileCloseFunc& close_file_func) {
// update target output
current_outputs_ = use_penultimate_output ? &penultimate_level_outputs_
: &compaction_outputs_;
current_outputs_ =
use_proximal_output ? &proximal_level_outputs_ : &compaction_outputs_;
return current_outputs_->AddToOutput(iter, open_file_func, close_file_func);
}
+43 -26
View File
@@ -26,13 +26,13 @@ namespace ROCKSDB_NAMESPACE {
// Maintains state and outputs for each sub-compaction
// It contains 2 `CompactionOutputs`:
// 1. one for the normal output files
// 2. another for the penultimate level outputs
// 2. another for the proximal level outputs
// a `current` pointer maintains the current output group, when calling
// `AddToOutput()`, it checks the output of the current compaction_iterator key
// and point `current` to the target output group. By default, it just points to
// normal compaction_outputs, if the compaction_iterator key should be placed on
// the penultimate level, `current` is changed to point to
// `penultimate_level_outputs`.
// the proximal level, `current` is changed to point to
// `proximal_level_outputs`.
// The later operations uses `Current()` to get the target group.
//
// +----------+ +-----------------------------+ +---------+
@@ -43,7 +43,7 @@ namespace ROCKSDB_NAMESPACE {
// | | ... |
// |
// | +-----------------------------+ +---------+
// +-------------> | penultimate_level_outputs |----->| output |
// +-------------> | proximal_level_outputs |----->| output |
// +-----------------------------+ +---------+
// | ... |
@@ -78,7 +78,7 @@ class SubcompactionState {
Slice LargestUserKey() const;
// Get all outputs from the subcompaction. For per_key_placement compaction,
// it returns both the last level outputs and penultimate level outputs.
// it returns both the last level outputs and proximal level outputs.
OutputIterator GetOutputs() const;
// Assign range dels aggregator. The various tombstones will potentially
@@ -92,7 +92,7 @@ class SubcompactionState {
void RemoveLastEmptyOutput() {
compaction_outputs_.RemoveLastEmptyOutput();
penultimate_level_outputs_.RemoveLastEmptyOutput();
proximal_level_outputs_.RemoveLastEmptyOutput();
}
void BuildSubcompactionJobInfo(
@@ -119,14 +119,14 @@ class SubcompactionState {
start(_start),
end(_end),
sub_job_id(_sub_job_id),
compaction_outputs_(c, /*is_penultimate_level=*/false),
penultimate_level_outputs_(c, /*is_penultimate_level=*/true) {
compaction_outputs_(c, /*is_proximal_level=*/false),
proximal_level_outputs_(c, /*is_proximal_level=*/true) {
assert(compaction != nullptr);
// Set output split key (used for RoundRobin feature) only for normal
// compaction_outputs, output to penultimate_level feature doesn't support
// compaction_outputs, output to proximal_level feature doesn't support
// RoundRobin feature (and may never going to be supported, because for
// RoundRobin, the data time is mostly naturally sorted, no need to have
// per-key placement with output_to_penultimate_level).
// per-key placement with output_to_proximal_level).
compaction_outputs_.SetOutputSlitKey(start, end);
}
@@ -141,18 +141,17 @@ class SubcompactionState {
compaction_job_stats(std::move(state.compaction_job_stats)),
sub_job_id(state.sub_job_id),
compaction_outputs_(std::move(state.compaction_outputs_)),
penultimate_level_outputs_(std::move(state.penultimate_level_outputs_)),
proximal_level_outputs_(std::move(state.proximal_level_outputs_)),
range_del_agg_(std::move(state.range_del_agg_)) {
current_outputs_ =
state.current_outputs_ == &state.penultimate_level_outputs_
? &penultimate_level_outputs_
: &compaction_outputs_;
current_outputs_ = state.current_outputs_ == &state.proximal_level_outputs_
? &proximal_level_outputs_
: &compaction_outputs_;
}
// Add all the new files from this compaction to version_edit
void AddOutputsEdit(VersionEdit* out_edit) const {
for (const auto& file : penultimate_level_outputs_.outputs_) {
out_edit->AddFile(compaction->GetPenultimateLevel(), file.meta);
for (const auto& file : proximal_level_outputs_.outputs_) {
out_edit->AddFile(compaction->GetProximalLevel(), file.meta);
}
for (const auto& file : compaction_outputs_.outputs_) {
out_edit->AddFile(compaction->output_level(), file.meta);
@@ -162,13 +161,32 @@ class SubcompactionState {
void Cleanup(Cache* cache);
void AggregateCompactionOutputStats(
InternalStats::CompactionStatsFull& compaction_stats) const;
InternalStats::CompactionStatsFull& internal_stats) const;
CompactionOutputs& Current() const {
assert(current_outputs_);
return *current_outputs_;
}
CompactionOutputs* Outputs(bool is_proximal_level) {
assert(compaction);
if (is_proximal_level) {
assert(compaction->SupportsPerKeyPlacement());
return &proximal_level_outputs_;
}
return &compaction_outputs_;
}
// Per-level stats for the output
InternalStats::CompactionStats* OutputStats(bool is_proximal_level) {
assert(compaction);
if (is_proximal_level) {
assert(compaction->SupportsPerKeyPlacement());
return &proximal_level_outputs_.stats_;
}
return &compaction_outputs_.stats_;
}
CompactionRangeDelAggregator* RangeDelAgg() const {
return range_del_agg_.get();
}
@@ -179,12 +197,11 @@ class SubcompactionState {
}
// Add compaction_iterator key/value to the `Current` output group.
Status AddToOutput(const CompactionIterator& iter,
bool use_penultimate_output,
Status AddToOutput(const CompactionIterator& iter, bool use_proximal_output,
const CompactionFileOpenFunc& open_file_func,
const CompactionFileCloseFunc& close_file_func);
// Close all compaction output files, both output_to_penultimate_level outputs
// Close all compaction output files, both output_to_proximal_level outputs
// and normal outputs.
Status CloseCompactionFiles(const Status& curr_status,
const CompactionFileOpenFunc& open_file_func,
@@ -195,11 +212,11 @@ class SubcompactionState {
// CloseOutput() may open new compaction output files.
Status s = curr_status;
if (per_key) {
s = penultimate_level_outputs_.CloseOutput(
s, range_del_agg_.get(), open_file_func, close_file_func);
s = proximal_level_outputs_.CloseOutput(s, range_del_agg_.get(),
open_file_func, close_file_func);
} else {
assert(penultimate_level_outputs_.HasBuilder() == false);
assert(penultimate_level_outputs_.HasOutput() == false);
assert(proximal_level_outputs_.HasBuilder() == false);
assert(proximal_level_outputs_.HasOutput() == false);
}
s = compaction_outputs_.CloseOutput(s, range_del_agg_.get(), open_file_func,
close_file_func);
@@ -209,7 +226,7 @@ class SubcompactionState {
private:
// State kept for output being generated
CompactionOutputs compaction_outputs_;
CompactionOutputs penultimate_level_outputs_;
CompactionOutputs proximal_level_outputs_;
CompactionOutputs* current_outputs_ = &compaction_outputs_;
std::unique_ptr<CompactionRangeDelAggregator> range_del_agg_;
};
+411 -189
View File
@@ -33,42 +33,13 @@ ConfigOptions GetStrictConfigOptions() {
class TieredCompactionTest : public DBTestBase {
public:
TieredCompactionTest()
: DBTestBase("tiered_compaction_test", /*env_do_fsync=*/true),
kBasicCompStats(CompactionReason::kUniversalSizeAmplification, 1),
kBasicPerKeyPlacementCompStats(
CompactionReason::kUniversalSizeAmplification, 1),
kBasicFlushStats(CompactionReason::kFlush, 1) {
kBasicCompStats.micros = kHasValue;
kBasicCompStats.cpu_micros = kHasValue;
kBasicCompStats.bytes_read_non_output_levels = kHasValue;
kBasicCompStats.num_input_files_in_non_output_levels = kHasValue;
kBasicCompStats.num_input_records = kHasValue;
kBasicCompStats.num_dropped_records = kHasValue;
kBasicPerLevelStats.num_output_records = kHasValue;
kBasicPerLevelStats.bytes_written = kHasValue;
kBasicPerLevelStats.num_output_files = kHasValue;
kBasicPerKeyPlacementCompStats.micros = kHasValue;
kBasicPerKeyPlacementCompStats.cpu_micros = kHasValue;
kBasicPerKeyPlacementCompStats.Add(kBasicPerLevelStats);
kBasicFlushStats.micros = kHasValue;
kBasicFlushStats.cpu_micros = kHasValue;
kBasicFlushStats.bytes_written = kHasValue;
kBasicFlushStats.num_output_files = kHasValue;
}
: DBTestBase("tiered_compaction_test", /*env_do_fsync=*/true) {}
protected:
static constexpr uint8_t kHasValue = 1;
InternalStats::CompactionStats kBasicCompStats;
InternalStats::CompactionStats kBasicPerKeyPlacementCompStats;
InternalStats::CompactionOutputsStats kBasicPerLevelStats;
InternalStats::CompactionStats kBasicFlushStats;
std::atomic_bool enable_per_key_placement = true;
CompactionJobStats job_stats;
void SetUp() override {
SyncPoint::GetInstance()->SetCallBack(
"Compaction::SupportsPerKeyPlacement:Enabled", [&](void* arg) {
@@ -108,21 +79,36 @@ class TieredCompactionTest : public DBTestBase {
// Verify the compaction stats, the stats are roughly compared
void VerifyCompactionStats(
const std::vector<InternalStats::CompactionStats>& expect_stats,
const InternalStats::CompactionStats& expect_pl_stats) {
const std::vector<InternalStats::CompactionStats>& expected_stats,
const InternalStats::CompactionStats& expected_pl_stats,
size_t output_level, uint64_t num_input_range_del = 0) {
const std::vector<InternalStats::CompactionStats>& stats =
GetCompactionStats();
const size_t kLevels = expect_stats.size();
const size_t kLevels = expected_stats.size();
ASSERT_EQ(kLevels, stats.size());
ASSERT_TRUE(output_level < kLevels);
for (auto it = stats.begin(), expect = expect_stats.begin();
it != stats.end(); it++, expect++) {
VerifyCompactionStats(*it, *expect);
for (size_t level = 0; level < kLevels; level++) {
VerifyCompactionStats(stats[level], expected_stats[level]);
}
const InternalStats::CompactionStats& pl_stats =
GetPerKeyPlacementCompactionStats();
VerifyCompactionStats(pl_stats, expect_pl_stats);
VerifyCompactionStats(pl_stats, expected_pl_stats);
const auto& output_level_stats = stats[output_level];
CompactionJobStats expected_job_stats;
expected_job_stats.cpu_micros = output_level_stats.cpu_micros;
expected_job_stats.num_input_files =
output_level_stats.num_input_files_in_output_level +
output_level_stats.num_input_files_in_non_output_levels;
expected_job_stats.num_input_records =
output_level_stats.num_input_records - num_input_range_del;
expected_job_stats.num_output_files =
output_level_stats.num_output_files + pl_stats.num_output_files;
expected_job_stats.num_output_records =
output_level_stats.num_output_records + pl_stats.num_output_records;
VerifyCompactionJobStats(job_stats, expected_job_stats);
}
void ResetAllStats(std::vector<InternalStats::CompactionStats>& stats,
@@ -139,42 +125,52 @@ class TieredCompactionTest : public DBTestBase {
}
private:
void CompareStats(uint64_t val, uint64_t expect) {
if (expect > 0) {
ASSERT_TRUE(val > 0);
} else {
ASSERT_EQ(val, 0);
}
}
void VerifyCompactionStats(
const InternalStats::CompactionStats& stats,
const InternalStats::CompactionStats& expect_stats) {
CompareStats(stats.micros, expect_stats.micros);
CompareStats(stats.cpu_micros, expect_stats.cpu_micros);
CompareStats(stats.bytes_read_non_output_levels,
expect_stats.bytes_read_non_output_levels);
CompareStats(stats.bytes_read_output_level,
expect_stats.bytes_read_output_level);
CompareStats(stats.bytes_read_blob, expect_stats.bytes_read_blob);
CompareStats(stats.bytes_written, expect_stats.bytes_written);
CompareStats(stats.bytes_moved, expect_stats.bytes_moved);
CompareStats(stats.num_input_files_in_non_output_levels,
expect_stats.num_input_files_in_non_output_levels);
CompareStats(stats.num_input_files_in_output_level,
expect_stats.num_input_files_in_output_level);
CompareStats(stats.num_output_files, expect_stats.num_output_files);
CompareStats(stats.num_output_files_blob,
expect_stats.num_output_files_blob);
CompareStats(stats.num_input_records, expect_stats.num_input_records);
CompareStats(stats.num_dropped_records, expect_stats.num_dropped_records);
CompareStats(stats.num_output_records, expect_stats.num_output_records);
ASSERT_EQ(stats.micros > 0, expect_stats.micros > 0);
ASSERT_EQ(stats.cpu_micros > 0, expect_stats.cpu_micros > 0);
// Hard to get consistent byte sizes of SST files.
// Use ASSERT_NEAR for comparison
ASSERT_NEAR(stats.bytes_read_non_output_levels * 1.0f,
expect_stats.bytes_read_non_output_levels * 1.0f,
stats.bytes_read_non_output_levels * 0.5f);
ASSERT_NEAR(stats.bytes_read_output_level * 1.0f,
expect_stats.bytes_read_output_level * 1.0f,
stats.bytes_read_output_level * 0.5f);
ASSERT_NEAR(stats.bytes_read_blob * 1.0f,
expect_stats.bytes_read_blob * 1.0f,
stats.bytes_read_blob * 0.5f);
ASSERT_NEAR(stats.bytes_written * 1.0f, expect_stats.bytes_written * 1.0f,
stats.bytes_written * 0.5f);
ASSERT_EQ(stats.bytes_moved, expect_stats.bytes_moved);
ASSERT_EQ(stats.num_input_files_in_non_output_levels,
expect_stats.num_input_files_in_non_output_levels);
ASSERT_EQ(stats.num_input_files_in_output_level,
expect_stats.num_input_files_in_output_level);
ASSERT_EQ(stats.num_output_files, expect_stats.num_output_files);
ASSERT_EQ(stats.num_output_files_blob, expect_stats.num_output_files_blob);
ASSERT_EQ(stats.num_input_records, expect_stats.num_input_records);
ASSERT_EQ(stats.num_dropped_records, expect_stats.num_dropped_records);
ASSERT_EQ(stats.num_output_records, expect_stats.num_output_records);
ASSERT_EQ(stats.count, expect_stats.count);
for (int i = 0; i < static_cast<int>(CompactionReason::kNumOfReasons);
i++) {
ASSERT_EQ(stats.counts[i], expect_stats.counts[i]);
}
}
void VerifyCompactionJobStats(const CompactionJobStats& stats,
const CompactionJobStats& expected_stats) {
ASSERT_EQ(stats.cpu_micros, expected_stats.cpu_micros);
ASSERT_EQ(stats.num_input_files, expected_stats.num_input_files);
ASSERT_EQ(stats.num_input_records, expected_stats.num_input_records);
ASSERT_EQ(job_stats.num_output_files, expected_stats.num_output_files);
ASSERT_EQ(job_stats.num_output_records, expected_stats.num_output_records);
}
};
TEST_F(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
@@ -199,19 +195,37 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
[&](void* arg) {
*static_cast<SequenceNumber*>(arg) = latest_cold_seq.load();
});
SyncPoint::GetInstance()->SetCallBack(
"CompactionJob::Install:AfterUpdateCompactionJobStats", [&](void* arg) {
job_stats.Reset();
job_stats.Add(*(static_cast<CompactionJobStats*>(arg)));
});
SyncPoint::GetInstance()->EnableProcessing();
std::vector<InternalStats::CompactionStats> expect_stats(kNumLevels);
InternalStats::CompactionStats& last_stats = expect_stats[kLastLevel];
InternalStats::CompactionStats expect_pl_stats;
// Put keys in the following way to create overlaps
// First file from 0 ~ 99
// Second file from 10 ~ 109
// ...
size_t bytes_per_file = 1952;
uint64_t total_input_key_count = kNumTrigger * kNumKeys;
uint64_t total_output_key_count = 130; // 0 ~ 129
for (int i = 0; i < kNumTrigger; i++) {
for (int j = 0; j < kNumKeys; j++) {
ASSERT_OK(Put(Key(i * 10 + j), "value" + std::to_string(i)));
}
ASSERT_OK(Flush());
seq_history.emplace_back(dbfull()->GetLatestSequenceNumber());
expect_stats[0].Add(kBasicFlushStats);
InternalStats::CompactionStats flush_stats(CompactionReason::kFlush, 1);
flush_stats.cpu_micros = 1;
flush_stats.micros = 1;
flush_stats.bytes_written = bytes_per_file;
flush_stats.num_output_files = 1;
expect_stats[0].Add(flush_stats);
}
ASSERT_OK(dbfull()->TEST_WaitForCompact());
@@ -221,32 +235,97 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
// basic compaction stats are still counted to the last level
expect_stats[kLastLevel].Add(kBasicCompStats);
expect_pl_stats.Add(kBasicPerKeyPlacementCompStats);
uint64_t bytes_written_penultimate_level =
GetPerKeyPlacementCompactionStats().bytes_written;
VerifyCompactionStats(expect_stats, expect_pl_stats);
// TODO - Use designated initializer when c++20 support is required
{
InternalStats::CompactionStats last_level_compaction_stats(
CompactionReason::kUniversalSizeAmplification, 1);
last_level_compaction_stats.cpu_micros = 1;
last_level_compaction_stats.micros = 1;
last_level_compaction_stats.bytes_written = 0;
last_level_compaction_stats.bytes_read_non_output_levels =
bytes_per_file * kNumTrigger;
last_level_compaction_stats.num_input_files_in_non_output_levels =
kNumTrigger;
last_level_compaction_stats.num_input_records = total_input_key_count;
last_level_compaction_stats.num_dropped_records =
total_input_key_count - total_output_key_count;
last_level_compaction_stats.num_output_records = 0;
last_level_compaction_stats.num_output_files = 0;
expect_stats[kLastLevel].Add(last_level_compaction_stats);
}
{
InternalStats::CompactionStats penultimate_level_compaction_stats(
CompactionReason::kUniversalSizeAmplification, 1);
penultimate_level_compaction_stats.cpu_micros = 1;
penultimate_level_compaction_stats.micros = 1;
penultimate_level_compaction_stats.bytes_written =
bytes_written_penultimate_level;
penultimate_level_compaction_stats.num_output_files = 1;
penultimate_level_compaction_stats.num_output_records =
total_output_key_count;
expect_pl_stats.Add(penultimate_level_compaction_stats);
}
VerifyCompactionStats(expect_stats, expect_pl_stats, kLastLevel);
ResetAllStats(expect_stats, expect_pl_stats);
// move forward the cold_seq to split the file into 2 levels, so should have
// both the last level stats and the output_to_penultimate_level stats
// both the last level stats and the penultimate level stats
latest_cold_seq = seq_history[0];
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_EQ("0,0,0,0,0,1,1", FilesPerLevel());
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
last_stats.Add(kBasicCompStats);
last_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
last_stats.Add(kBasicPerLevelStats);
last_stats.num_dropped_records = 0;
expect_pl_stats.Add(kBasicPerKeyPlacementCompStats);
expect_pl_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
VerifyCompactionStats(expect_stats, expect_pl_stats);
// Now update the input count to be the total count from the previous
total_input_key_count = total_output_key_count;
uint64_t moved_to_last_level_key_count = 10;
// delete all cold data, so all data will be on penultimate level
// bytes read in non output = bytes written in penultimate level from previous
uint64_t bytes_read_in_non_output_level = bytes_written_penultimate_level;
uint64_t bytes_written_output_level =
GetCompactionStats()[kLastLevel].bytes_written;
// Now get the new bytes written in penultimate level
bytes_written_penultimate_level =
GetPerKeyPlacementCompactionStats().bytes_written;
{
InternalStats::CompactionStats last_level_compaction_stats(
CompactionReason::kManualCompaction, 1);
last_level_compaction_stats.cpu_micros = 1;
last_level_compaction_stats.micros = 1;
last_level_compaction_stats.bytes_written = bytes_written_output_level;
last_level_compaction_stats.bytes_read_non_output_levels =
bytes_read_in_non_output_level;
last_level_compaction_stats.num_input_files_in_non_output_levels = 1;
last_level_compaction_stats.num_input_records = total_input_key_count;
last_level_compaction_stats.num_dropped_records =
total_input_key_count - total_output_key_count;
last_level_compaction_stats.num_output_records =
moved_to_last_level_key_count;
last_level_compaction_stats.num_output_files = 1;
expect_stats[kLastLevel].Add(last_level_compaction_stats);
}
{
InternalStats::CompactionStats penultimate_level_compaction_stats(
CompactionReason::kManualCompaction, 1);
penultimate_level_compaction_stats.cpu_micros = 1;
penultimate_level_compaction_stats.micros = 1;
penultimate_level_compaction_stats.bytes_written =
bytes_written_penultimate_level;
penultimate_level_compaction_stats.num_output_files = 1;
penultimate_level_compaction_stats.num_output_records =
total_output_key_count - moved_to_last_level_key_count;
expect_pl_stats.Add(penultimate_level_compaction_stats);
}
VerifyCompactionStats(expect_stats, expect_pl_stats, kLastLevel);
// delete all cold data, so all data will be on proximal level
for (int i = 0; i < 10; i++) {
ASSERT_OK(Delete(Key(i)));
}
@@ -255,17 +334,54 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
ResetAllStats(expect_stats, expect_pl_stats);
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
last_stats.Add(kBasicCompStats);
last_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
last_stats.bytes_read_output_level = kHasValue;
last_stats.num_input_files_in_output_level = kHasValue;
expect_pl_stats.Add(kBasicPerKeyPlacementCompStats);
expect_pl_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
VerifyCompactionStats(expect_stats, expect_pl_stats);
// 10 tombstones added
total_input_key_count = total_input_key_count + 10;
total_output_key_count = total_output_key_count - 10;
auto last_level_stats = GetCompactionStats()[kLastLevel];
bytes_written_penultimate_level =
GetPerKeyPlacementCompactionStats().bytes_written;
ASSERT_LT(bytes_written_penultimate_level,
last_level_stats.bytes_read_non_output_levels +
last_level_stats.bytes_read_output_level);
{
InternalStats::CompactionStats last_level_compaction_stats(
CompactionReason::kManualCompaction, 1);
last_level_compaction_stats.cpu_micros = 1;
last_level_compaction_stats.micros = 1;
last_level_compaction_stats.bytes_written = 0;
last_level_compaction_stats.bytes_read_non_output_levels =
last_level_stats.bytes_read_non_output_levels;
last_level_compaction_stats.bytes_read_output_level =
last_level_stats.bytes_read_output_level;
last_level_compaction_stats.num_input_files_in_non_output_levels = 2;
last_level_compaction_stats.num_input_files_in_output_level = 1;
last_level_compaction_stats.num_input_records = total_input_key_count;
last_level_compaction_stats.num_dropped_records =
total_input_key_count - total_output_key_count;
last_level_compaction_stats.num_output_records = 0;
last_level_compaction_stats.num_output_files = 0;
expect_stats[kLastLevel].Add(last_level_compaction_stats);
}
{
InternalStats::CompactionStats penultimate_level_compaction_stats(
CompactionReason::kManualCompaction, 1);
penultimate_level_compaction_stats.cpu_micros = 1;
penultimate_level_compaction_stats.micros = 1;
penultimate_level_compaction_stats.bytes_written =
bytes_written_penultimate_level;
penultimate_level_compaction_stats.num_output_files = 1;
penultimate_level_compaction_stats.num_output_records =
total_output_key_count;
expect_pl_stats.Add(penultimate_level_compaction_stats);
}
VerifyCompactionStats(expect_stats, expect_pl_stats, kLastLevel);
// move forward the cold_seq again with range delete, take a snapshot to keep
// the range dels in both cold and hot SSTs
@@ -275,6 +391,7 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
ASSERT_OK(
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), start, end));
ASSERT_OK(Flush());
uint64_t num_input_range_del = 1;
ResetAllStats(expect_stats, expect_pl_stats);
@@ -283,12 +400,49 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
last_stats.Add(kBasicCompStats);
last_stats.Add(kBasicPerLevelStats);
last_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
expect_pl_stats.Add(kBasicPerKeyPlacementCompStats);
expect_pl_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
VerifyCompactionStats(expect_stats, expect_pl_stats);
// Previous output + one delete range
total_input_key_count = total_output_key_count + num_input_range_del;
moved_to_last_level_key_count = 20;
last_level_stats = GetCompactionStats()[kLastLevel];
bytes_written_penultimate_level =
GetPerKeyPlacementCompactionStats().bytes_written;
// Expected to write more in last level
ASSERT_GT(bytes_written_penultimate_level, last_level_stats.bytes_written);
{
InternalStats::CompactionStats last_level_compaction_stats(
CompactionReason::kManualCompaction, 1);
last_level_compaction_stats.cpu_micros = 1;
last_level_compaction_stats.micros = 1;
last_level_compaction_stats.bytes_written = last_level_stats.bytes_written;
last_level_compaction_stats.bytes_read_non_output_levels =
last_level_stats.bytes_read_non_output_levels;
last_level_compaction_stats.bytes_read_output_level = 0;
last_level_compaction_stats.num_input_files_in_non_output_levels = 2;
last_level_compaction_stats.num_input_files_in_output_level = 0;
last_level_compaction_stats.num_input_records = total_input_key_count;
last_level_compaction_stats.num_dropped_records =
num_input_range_del; // delete range tombstone
last_level_compaction_stats.num_output_records =
moved_to_last_level_key_count;
last_level_compaction_stats.num_output_files = 1;
expect_stats[kLastLevel].Add(last_level_compaction_stats);
}
{
InternalStats::CompactionStats penultimate_level_compaction_stats(
CompactionReason::kManualCompaction, 1);
penultimate_level_compaction_stats.cpu_micros = 1;
penultimate_level_compaction_stats.micros = 1;
penultimate_level_compaction_stats.bytes_written =
bytes_written_penultimate_level;
penultimate_level_compaction_stats.num_output_files = 1;
penultimate_level_compaction_stats.num_output_records =
total_input_key_count - moved_to_last_level_key_count -
num_input_range_del;
expect_pl_stats.Add(penultimate_level_compaction_stats);
}
VerifyCompactionStats(expect_stats, expect_pl_stats, kLastLevel,
num_input_range_del);
// verify data
std::string value;
@@ -341,11 +495,11 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
// This test was essentially for a hacked-up version on future functionality.
// It can be resurrected if/when a form of range-based tiering is properly
// implemented.
// TODO - Add stats verification when adding this test back
TEST_F(TieredCompactionTest, DISABLED_RangeBasedTieredStorageUniversal) {
const int kNumTrigger = 4;
const int kNumLevels = 7;
const int kNumKeys = 100;
const int kLastLevel = kNumLevels - 1;
auto options = CurrentOptions();
options.compaction_style = kCompactionStyleUniversal;
@@ -364,14 +518,13 @@ TEST_F(TieredCompactionTest, DISABLED_RangeBasedTieredStorageUniversal) {
"CompactionIterator::PrepareOutput.context", [&](void* arg) {
auto context = static_cast<PerKeyPlacementContext*>(arg);
MutexLock l(&mutex);
context->output_to_penultimate_level =
context->output_to_proximal_level =
cmp->Compare(context->key, hot_start) >= 0 &&
cmp->Compare(context->key, hot_end) < 0;
});
SyncPoint::GetInstance()->EnableProcessing();
std::vector<InternalStats::CompactionStats> expect_stats(kNumLevels);
InternalStats::CompactionStats& last_stats = expect_stats[kLastLevel];
InternalStats::CompactionStats expect_pl_stats;
for (int i = 0; i < kNumTrigger; i++) {
@@ -379,21 +532,15 @@ TEST_F(TieredCompactionTest, DISABLED_RangeBasedTieredStorageUniversal) {
ASSERT_OK(Put(Key(j), "value" + std::to_string(j)));
}
ASSERT_OK(Flush());
expect_stats[0].Add(kBasicFlushStats);
}
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_EQ("0,0,0,0,0,1,1", FilesPerLevel());
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
last_stats.Add(kBasicCompStats);
last_stats.Add(kBasicPerLevelStats);
expect_pl_stats.Add(kBasicPerKeyPlacementCompStats);
VerifyCompactionStats(expect_stats, expect_pl_stats);
ResetAllStats(expect_stats, expect_pl_stats);
// change to all cold, no output_to_penultimate_level output
// change to all cold, no output_to_proximal_level output
{
MutexLock l(&mutex);
hot_start = Key(100);
@@ -404,14 +551,6 @@ TEST_F(TieredCompactionTest, DISABLED_RangeBasedTieredStorageUniversal) {
ASSERT_EQ(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
last_stats.Add(kBasicCompStats);
last_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
last_stats.Add(kBasicPerLevelStats);
last_stats.num_dropped_records = 0;
last_stats.bytes_read_output_level = kHasValue;
last_stats.num_input_files_in_output_level = kHasValue;
VerifyCompactionStats(expect_stats, expect_pl_stats);
// change to all hot, universal compaction support moving data to up level if
// it's within compaction level range.
{
@@ -421,7 +560,7 @@ TEST_F(TieredCompactionTest, DISABLED_RangeBasedTieredStorageUniversal) {
}
// No data is moved from cold tier to hot tier because no input files from L5
// or higher, it's not safe to move data to output_to_penultimate_level level.
// or higher, it's not safe to move data to output_to_proximal_level level.
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
@@ -567,7 +706,7 @@ TEST_F(TieredCompactionTest, LevelColdRangeDelete) {
// 20->30 will be marked as cold data, but it cannot be placed to cold tier
// (bottommost) otherwise, it will be "deleted" by the range del in
// output_to_penultimate_level level verify that these data will be able to
// output_to_proximal_level level verify that these data will be able to
// queried
for (int i = 20; i < 30; i++) {
ASSERT_OK(Put(Key(i), "value" + std::to_string(i)));
@@ -677,17 +816,17 @@ TEST_F(TieredCompactionTest, LevelOutofBoundaryRangeDelete) {
std::vector<std::vector<FileMetaData>> level_to_files;
dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(),
&level_to_files);
// range tombstone is in the penultimate level
const int penultimate_level = kNumLevels - 2;
ASSERT_EQ(level_to_files[penultimate_level].size(), 1);
ASSERT_EQ(level_to_files[penultimate_level][0].num_entries, 1);
ASSERT_EQ(level_to_files[penultimate_level][0].num_deletions, 1);
ASSERT_EQ(level_to_files[penultimate_level][0].temperature,
// range tombstone is in the proximal level
const int proximal_level = kNumLevels - 2;
ASSERT_EQ(level_to_files[proximal_level].size(), 1);
ASSERT_EQ(level_to_files[proximal_level][0].num_entries, 1);
ASSERT_EQ(level_to_files[proximal_level][0].num_deletions, 1);
ASSERT_EQ(level_to_files[proximal_level][0].temperature,
Temperature::kUnknown);
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
ASSERT_EQ("0,1,10",
FilesPerLevel()); // one file is at the penultimate level which
FilesPerLevel()); // one file is at the proximal level which
// only contains a range delete
// Add 2 hot keys, each is a new SST, they will be placed in the same level as
@@ -701,7 +840,7 @@ TEST_F(TieredCompactionTest, LevelOutofBoundaryRangeDelete) {
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
ASSERT_EQ("0,2,10",
FilesPerLevel()); // one file is at the penultimate level
FilesPerLevel()); // one file is at the proximal level
// which only contains a range delete
std::vector<LiveFileMetaData> live_file_meta;
db_->GetLiveFilesMetaData(&live_file_meta);
@@ -711,7 +850,7 @@ TEST_F(TieredCompactionTest, LevelOutofBoundaryRangeDelete) {
if (meta.num_deletions > 0) {
// found SST with del, which has 2 entries, one for data one for range del
ASSERT_EQ(meta.level,
kNumLevels - 2); // output to penultimate level
kNumLevels - 2); // output to proximal level
ASSERT_EQ(meta.num_entries, 2);
ASSERT_EQ(meta.num_deletions, 1);
found_sst_with_del = true;
@@ -722,7 +861,7 @@ TEST_F(TieredCompactionTest, LevelOutofBoundaryRangeDelete) {
// release the first snapshot and compact, which should compact the range del
// but new inserted key `0` and `6` are still hot data which will be placed on
// the penultimate level
// the proximal level
db_->ReleaseSnapshot(snap);
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
ASSERT_EQ("0,2,7", FilesPerLevel());
@@ -738,7 +877,7 @@ TEST_F(TieredCompactionTest, LevelOutofBoundaryRangeDelete) {
ASSERT_FALSE(found_sst_with_del);
// Now make all data cold, key 0 will be moved to the last level, but key 6 is
// still in snap2, so it will be kept at the penultimate level
// still in snap2, so it will be kept at the proximal level
latest_cold_seq = dbfull()->GetLatestSequenceNumber();
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
ASSERT_EQ("0,1,8", FilesPerLevel());
@@ -783,7 +922,7 @@ TEST_F(TieredCompactionTest, UniversalRangeDelete) {
}
ASSERT_OK(Flush());
// compact to the penultimate level with 10 files
// compact to the proximal level with 10 files
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
@@ -810,7 +949,7 @@ TEST_F(TieredCompactionTest, UniversalRangeDelete) {
ASSERT_EQ("0,0,0,0,0,0,8", FilesPerLevel());
// range del with snapshot should be preserved in the penultimate level
// range del with snapshot should be preserved in the proximal level
auto snap = db_->GetSnapshot();
start = Key(6);
@@ -841,7 +980,7 @@ TEST_F(TieredCompactionTest, UniversalRangeDelete) {
if (meta.num_deletions > 0) {
// found SST with del, which has 2 entries, one for data one for range del
ASSERT_EQ(meta.level,
kNumLevels - 2); // output_to_penultimate_level level
kNumLevels - 2); // output_to_proximal_level level
ASSERT_EQ(meta.num_entries, 2);
ASSERT_EQ(meta.num_deletions, 1);
found_sst_with_del = true;
@@ -890,6 +1029,8 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageLevel) {
const int kNumKeys = 100;
const int kLastLevel = kNumLevels - 1;
int output_level = 0;
auto options = CurrentOptions();
SetColdTemperature(options);
options.level0_file_num_compaction_trigger = kNumTrigger;
@@ -906,18 +1047,40 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageLevel) {
[&](void* arg) {
*static_cast<SequenceNumber*>(arg) = latest_cold_seq.load();
});
SyncPoint::GetInstance()->SetCallBack(
"CompactionJob::Install:AfterUpdateCompactionJobStats", [&](void* arg) {
job_stats.Reset();
job_stats.Add(*(static_cast<CompactionJobStats*>(arg)));
});
SyncPoint::GetInstance()->SetCallBack(
"CompactionJob::ProcessKeyValueCompaction()::Processing", [&](void* arg) {
auto compaction = static_cast<Compaction*>(arg);
output_level = compaction->output_level();
});
SyncPoint::GetInstance()->EnableProcessing();
std::vector<InternalStats::CompactionStats> expect_stats(kNumLevels);
InternalStats::CompactionStats& last_stats = expect_stats[kLastLevel];
InternalStats::CompactionStats expect_pl_stats;
// Put keys in the following way to create overlaps
// First file from 0 ~ 99
// Second file from 10 ~ 109
// ...
size_t bytes_per_file = 1952;
uint64_t total_input_key_count = kNumTrigger * kNumKeys;
uint64_t total_output_key_count = 130; // 0 ~ 129
for (int i = 0; i < kNumTrigger; i++) {
for (int j = 0; j < kNumKeys; j++) {
ASSERT_OK(Put(Key(i * 10 + j), "value" + std::to_string(i)));
}
ASSERT_OK(Flush());
expect_stats[0].Add(kBasicFlushStats);
InternalStats::CompactionStats flush_stats(CompactionReason::kFlush, 1);
flush_stats.cpu_micros = 1;
flush_stats.micros = 1;
flush_stats.bytes_written = bytes_per_file;
flush_stats.num_output_files = 1;
expect_stats[0].Add(flush_stats);
}
ASSERT_OK(dbfull()->TEST_WaitForCompact());
@@ -926,10 +1089,30 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageLevel) {
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
expect_stats[1].Add(kBasicCompStats);
expect_stats[1].Add(kBasicPerLevelStats);
expect_stats[1].ResetCompactionReason(CompactionReason::kLevelL0FilesNum);
VerifyCompactionStats(expect_stats, expect_pl_stats);
uint64_t bytes_written_output_level =
GetCompactionStats()[output_level].bytes_written;
ASSERT_GT(bytes_written_output_level, 0);
{
InternalStats::CompactionStats output_level_compaction_stats(
CompactionReason::kLevelL0FilesNum, 1);
output_level_compaction_stats.cpu_micros = 1;
output_level_compaction_stats.micros = 1;
output_level_compaction_stats.bytes_written = bytes_written_output_level;
output_level_compaction_stats.bytes_read_non_output_levels =
bytes_per_file * kNumTrigger;
output_level_compaction_stats.bytes_read_output_level = 0;
output_level_compaction_stats.num_input_files_in_non_output_levels =
kNumTrigger;
output_level_compaction_stats.num_input_files_in_output_level = 0;
output_level_compaction_stats.num_input_records = total_input_key_count;
output_level_compaction_stats.num_dropped_records =
total_input_key_count - total_output_key_count;
output_level_compaction_stats.num_output_records = total_output_key_count;
output_level_compaction_stats.num_output_files = 1;
expect_stats[output_level].Add(output_level_compaction_stats);
}
VerifyCompactionStats(expect_stats, expect_pl_stats, output_level);
// move all data to the last level
MoveFilesToLevel(kLastLevel);
@@ -944,15 +1127,26 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageLevel) {
ASSERT_EQ(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
last_stats.Add(kBasicCompStats);
last_stats.Add(kBasicPerLevelStats);
last_stats.num_dropped_records = 0;
last_stats.bytes_read_non_output_levels = 0;
last_stats.num_input_files_in_non_output_levels = 0;
last_stats.bytes_read_output_level = kHasValue;
last_stats.num_input_files_in_output_level = kHasValue;
last_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
VerifyCompactionStats(expect_stats, expect_pl_stats);
total_input_key_count = total_output_key_count;
{
InternalStats::CompactionStats output_level_compaction_stats(
CompactionReason::kManualCompaction, 1);
output_level_compaction_stats.cpu_micros = 1;
output_level_compaction_stats.micros = 1;
output_level_compaction_stats.bytes_written = bytes_written_output_level;
output_level_compaction_stats.bytes_read_non_output_levels = 0;
output_level_compaction_stats.bytes_read_output_level =
bytes_written_output_level;
output_level_compaction_stats.num_input_files_in_non_output_levels = 0;
output_level_compaction_stats.num_input_files_in_output_level = 1;
output_level_compaction_stats.num_input_records = total_input_key_count;
output_level_compaction_stats.num_dropped_records =
total_input_key_count - total_output_key_count;
output_level_compaction_stats.num_output_records = total_output_key_count;
output_level_compaction_stats.num_output_files = 1;
expect_stats[output_level].Add(output_level_compaction_stats);
}
VerifyCompactionStats(expect_stats, expect_pl_stats, output_level);
// Add new data, which is all hot and overriding all existing data
latest_cold_seq = dbfull()->GetLatestSequenceNumber();
@@ -976,17 +1170,47 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageLevel) {
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
uint64_t bytes_written_in_proximal_level =
GetPerKeyPlacementCompactionStats().bytes_written;
for (int level = 2; level < kNumLevels - 1; level++) {
expect_stats[level].bytes_moved = kHasValue;
expect_stats[level].bytes_moved = bytes_written_in_proximal_level;
}
last_stats.Add(kBasicCompStats);
last_stats.bytes_read_output_level = kHasValue;
last_stats.num_input_files_in_output_level = kHasValue;
last_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
expect_pl_stats.Add(kBasicPerKeyPlacementCompStats);
expect_pl_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
VerifyCompactionStats(expect_stats, expect_pl_stats);
// Another set of 130 keys + from the previous
total_input_key_count = total_output_key_count + 130;
// Merged into 130
total_output_key_count = 130;
{
InternalStats::CompactionStats output_level_compaction_stats(
CompactionReason::kManualCompaction, 1);
output_level_compaction_stats.cpu_micros = 1;
output_level_compaction_stats.micros = 1;
output_level_compaction_stats.bytes_written = 0;
output_level_compaction_stats.bytes_read_non_output_levels =
bytes_written_in_proximal_level;
output_level_compaction_stats.bytes_read_output_level =
bytes_written_output_level;
output_level_compaction_stats.num_input_files_in_non_output_levels = 1;
output_level_compaction_stats.num_input_files_in_output_level = 1;
output_level_compaction_stats.num_input_records = total_input_key_count;
output_level_compaction_stats.num_dropped_records =
total_input_key_count - total_output_key_count;
output_level_compaction_stats.num_output_records = 0;
output_level_compaction_stats.num_output_files = 0;
expect_stats[output_level].Add(output_level_compaction_stats);
}
{
InternalStats::CompactionStats proximal_level_compaction_stats(
CompactionReason::kManualCompaction, 1);
expect_pl_stats.cpu_micros = 1;
expect_pl_stats.micros = 1;
expect_pl_stats.bytes_written = bytes_written_in_proximal_level;
expect_pl_stats.num_output_files = 1;
expect_pl_stats.num_output_records = total_output_key_count;
expect_pl_stats.Add(proximal_level_compaction_stats);
}
VerifyCompactionStats(expect_stats, expect_pl_stats, output_level);
// move forward the cold_seq, try to split the data into cold and hot, but in
// this case it's unsafe to split the data
@@ -1138,7 +1362,7 @@ TEST_F(TieredCompactionTest, DISABLED_RangeBasedTieredStorageLevel) {
"CompactionIterator::PrepareOutput.context", [&](void* arg) {
auto context = static_cast<PerKeyPlacementContext*>(arg);
MutexLock l(&mutex);
context->output_to_penultimate_level =
context->output_to_proximal_level =
cmp->Compare(context->key, hot_start) >= 0 &&
cmp->Compare(context->key, hot_end) < 0;
});
@@ -1221,10 +1445,10 @@ TEST_F(TieredCompactionTest, DISABLED_RangeBasedTieredStorageLevel) {
options.statistics->getTickerCount(COMPACTION_RANGE_DEL_DROP_OBSOLETE),
1);
// Tests that we only compact keys up to penultimate level
// that are within penultimate level input's internal key range.
// UPDATE: this functionality has changed. With penultimate-enabled
// compaction, the expanded potential output range in the penultimate
// Tests that we only compact keys up to proximal level
// that are within proximal level input's internal key range.
// UPDATE: this functionality has changed. With proximal-enabled
// compaction, the expanded potential output range in the proximal
// level is reserved so should be safe to use.
{
MutexLock l(&mutex);
@@ -1376,7 +1600,7 @@ TEST_P(PrecludeLastLevelTest, MigrationFromPreserveTimeManualCompaction) {
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
// all data is moved up to the penultimate level
// all data is moved up to the proximal level
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
@@ -1448,7 +1672,7 @@ TEST_P(PrecludeLastLevelTest, MigrationFromPreserveTimeAutoCompaction) {
ASSERT_OK(dbfull()->TEST_WaitForCompact());
}
// all data is moved up to the penultimate level
// all data is moved up to the proximal level
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
@@ -1489,8 +1713,7 @@ TEST_P(PrecludeLastLevelTest, MigrationFromPreserveTimePartial) {
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
std::vector<KeyVersion> key_versions;
ASSERT_OK(GetAllKeyVersions(db_, Slice(), Slice(),
std::numeric_limits<size_t>::max(),
ASSERT_OK(GetAllKeyVersions(db_, {}, {}, std::numeric_limits<size_t>::max(),
&key_versions));
// make sure there're more than 300 keys and first 100 keys are having seqno
@@ -1579,9 +1802,9 @@ TEST_P(PrecludeLastLevelTest, SmallPrecludeTime) {
}
TEST_P(PrecludeLastLevelTest, CheckInternalKeyRange) {
// When compacting keys from the last level to penultimate level,
// output to penultimate level should be within internal key range
// of input files from penultimate level.
// When compacting keys from the last level to proximal level,
// output to proximal level should be within internal key range
// of input files from proximal level.
// Set up:
// L5:
// File 1: DeleteRange[1, 3)@4, File 2: [3@5, 100@6]
@@ -1719,8 +1942,8 @@ TEST_P(PrecludeWithCompactStyleTest, RangeTombstoneSnapshotMigrateFromLast) {
ApplyConfigChange(&options, {{"preclude_last_level_data_seconds", "10000"}});
// To exercise the WithinPenultimateLevelOutputRange feature, we want files
// around the middle file to be compacted on the penultimate level
// To exercise the WithinProximalLevelOutputRange feature, we want files
// around the middle file to be compacted on the proximal level
ASSERT_OK(Put(Key(0), "val0"));
ASSERT_OK(Flush());
ASSERT_OK(Put(Key(3), "val3"));
@@ -1777,9 +2000,9 @@ TEST_P(PrecludeWithCompactStyleTest, RangeTombstoneSnapshotMigrateFromLast) {
EXPECT_EQ("0,0,0,0,0,3,1", FilesPerLevel());
VerifyLogicalState(__LINE__);
// Compact everything, but some data still goes to both penultimate and last
// Compact everything, but some data still goes to both proximal and last
// levels. A full-range compaction should be safe to "migrate" data from the
// last level to penultimate (because of preclude setting change).
// last level to proximal (because of preclude setting change).
ASSERT_OK(CompactRange({}, {}, {}));
EXPECT_EQ("0,0,0,0,0,1,1", FilesPerLevel());
VerifyLogicalState(__LINE__);
@@ -1898,7 +2121,7 @@ TEST_P(TimedPutPrecludeLastLevelTest, InterleavedTimedPutAndPut) {
Close();
}
TEST_P(TimedPutPrecludeLastLevelTest, PreserveTimedPutOnPenultimateLevel) {
TEST_P(TimedPutPrecludeLastLevelTest, PreserveTimedPutOnProximalLevel) {
Options options = CurrentOptions();
options.compaction_style = kCompactionStyleUniversal;
options.disable_auto_compactions = true;
@@ -1924,14 +2147,14 @@ TEST_P(TimedPutPrecludeLastLevelTest, PreserveTimedPutOnPenultimateLevel) {
ASSERT_OK(TimedPut(0, Key(2), "v2", kMockStartTime - 1 * 24 * 60 * 60, wo));
ASSERT_OK(Flush());
// Should still be in penultimate level.
// Should still be in proximal level.
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
ASSERT_GT(GetSstSizeHelper(Temperature::kHot), 0);
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
// Wait one more day and release snapshot. Data's preferred seqno should be
// swapped in, but data should still stay in penultimate level. SST file's
// swapped in, but data should still stay in proximal level. SST file's
// seqno to time mapping should continue to cover preferred seqno after
// compaction.
db_->ReleaseSnapshot(snap1);
@@ -2079,8 +2302,7 @@ TEST_P(PrecludeLastLevelTest, LastLevelOnlyCompactionPartial) {
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
std::vector<KeyVersion> key_versions;
ASSERT_OK(GetAllKeyVersions(db_, Slice(), Slice(),
std::numeric_limits<size_t>::max(),
ASSERT_OK(GetAllKeyVersions(db_, {}, {}, std::numeric_limits<size_t>::max(),
&key_versions));
// make sure there're more than 300 keys and first 100 keys are having seqno
@@ -2253,13 +2475,13 @@ TEST_P(PrecludeLastLevelOptionalTest, LastLevelOnlyCompactionNoPreclude) {
Close();
}
TEST_P(PrecludeLastLevelOptionalTest, PeriodicCompactionToPenultimateLevel) {
TEST_P(PrecludeLastLevelOptionalTest, PeriodicCompactionToProximalLevel) {
// Test the last level only periodic compaction should also be blocked by an
// ongoing compaction in penultimate level if tiered compaction is enabled
// ongoing compaction in proximal level if tiered compaction is enabled
// otherwise, the periodic compaction should just run for the last level.
const int kNumTrigger = 4;
const int kNumLevels = 7;
const int kPenultimateLevel = kNumLevels - 2;
const int kProximalLevel = kNumLevels - 2;
const int kKeyPerSec = 1;
const int kNumKeys = 100;
@@ -2301,13 +2523,13 @@ TEST_P(PrecludeLastLevelOptionalTest, PeriodicCompactionToPenultimateLevel) {
SyncPoint::GetInstance()->SetCallBack(
"CompactionJob::ProcessKeyValueCompaction()::Processing", [&](void* arg) {
auto compaction = static_cast<Compaction*>(arg);
if (compaction->output_level() == kPenultimateLevel) {
if (compaction->output_level() == kProximalLevel) {
is_size_ratio_compaction_running = true;
TEST_SYNC_POINT(
"PrecludeLastLevelTest::PeriodicCompactionToPenultimateLevel:"
"PrecludeLastLevelTest::PeriodicCompactionToProximalLevel:"
"SizeRatioCompaction1");
TEST_SYNC_POINT(
"PrecludeLastLevelTest::PeriodicCompactionToPenultimateLevel:"
"PrecludeLastLevelTest::PeriodicCompactionToProximalLevel:"
"SizeRatioCompaction2");
is_size_ratio_compaction_running = false;
}
@@ -2329,17 +2551,17 @@ TEST_P(PrecludeLastLevelOptionalTest, PeriodicCompactionToPenultimateLevel) {
verified_last_level_compaction = true;
}
TEST_SYNC_POINT(
"PrecludeLastLevelTest::PeriodicCompactionToPenultimateLevel:"
"PrecludeLastLevelTest::PeriodicCompactionToProximalLevel:"
"AutoCompactionPicked");
});
SyncPoint::GetInstance()->LoadDependency({
{"PrecludeLastLevelTest::PeriodicCompactionToPenultimateLevel:"
{"PrecludeLastLevelTest::PeriodicCompactionToProximalLevel:"
"SizeRatioCompaction1",
"PrecludeLastLevelTest::PeriodicCompactionToPenultimateLevel:DoneWrite"},
{"PrecludeLastLevelTest::PeriodicCompactionToPenultimateLevel:"
"PrecludeLastLevelTest::PeriodicCompactionToProximalLevel:DoneWrite"},
{"PrecludeLastLevelTest::PeriodicCompactionToProximalLevel:"
"AutoCompactionPicked",
"PrecludeLastLevelTest::PeriodicCompactionToPenultimateLevel:"
"PrecludeLastLevelTest::PeriodicCompactionToProximalLevel:"
"SizeRatioCompaction2"},
});
@@ -2356,11 +2578,11 @@ TEST_P(PrecludeLastLevelOptionalTest, PeriodicCompactionToPenultimateLevel) {
}
TEST_SYNC_POINT(
"PrecludeLastLevelTest::PeriodicCompactionToPenultimateLevel:DoneWrite");
"PrecludeLastLevelTest::PeriodicCompactionToProximalLevel:DoneWrite");
// wait for periodic compaction time and flush to trigger the periodic
// compaction, which should be blocked by ongoing compaction in the
// penultimate level
// proximal level
mock_clock_->MockSleepForSeconds(10000);
for (int i = 0; i < 3 * kNumKeys; i++) {
ASSERT_OK(Put(Key(i), rnd.RandomString(10)));
@@ -2423,7 +2645,7 @@ class ThreeRangesPartitionerFactory : public SstPartitionerFactory {
}
};
TEST_P(PrecludeLastLevelTest, PartialPenultimateLevelCompaction) {
TEST_P(PrecludeLastLevelTest, PartialProximalLevelCompaction) {
const int kNumTrigger = 4;
const int kNumLevels = 7;
const int kKeyPerSec = 10;
@@ -2593,8 +2815,8 @@ TEST_P(PrecludeLastLevelTest, RangeDelsCauseFileEndpointsToOverlap) {
"UniversalCompactionBuilder::PickCompaction:Return", [&](void* arg) {
auto compaction = static_cast<Compaction*>(arg);
if (compaction->SupportsPerKeyPlacement()) {
ASSERT_EQ(compaction->GetPenultimateOutputRangeType(),
Compaction::PenultimateOutputRangeType::kNonLastRange);
ASSERT_EQ(compaction->GetProximalOutputRangeType(),
Compaction::ProximalOutputRangeType::kNonLastRange);
per_key_comp_num++;
}
});
@@ -2650,7 +2872,7 @@ TEST_P(PrecludeLastLevelTest, RangeDelsCauseFileEndpointsToOverlap) {
ASSERT_EQ(3, per_key_comp_num);
verify_db();
// Finish off the penultimate level.
// Finish off the proximal level.
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
ASSERT_EQ("0,0,0,0,0,0,3", FilesPerLevel());
verify_db();
+11
View File
@@ -26,6 +26,17 @@ Status DeleteFilesInRange(DB* db, ColumnFamilyHandle* column_family,
Status DeleteFilesInRanges(DB* db, ColumnFamilyHandle* column_family,
const RangePtr* ranges, size_t n, bool include_end) {
std::vector<RangeOpt> range_opts(n);
for (size_t i = 0; i < n; ++i) {
range_opts[i] = {OptSlice::CopyFromPtr(ranges[i].start),
OptSlice::CopyFromPtr(ranges[i].limit)};
}
return DeleteFilesInRanges(db, column_family, range_opts.data(), n,
include_end);
}
Status DeleteFilesInRanges(DB* db, ColumnFamilyHandle* column_family,
const RangeOpt* ranges, size_t n, bool include_end) {
return (static_cast_with_check<DBImpl>(db->GetRootDB()))
->DeleteFilesInRanges(column_family, ranges, n, include_end);
}
+3 -1
View File
@@ -851,6 +851,9 @@ TEST_F(CorruptionTest, ParanoidFileChecksOnCompact) {
options.env = env_.get();
options.paranoid_file_checks = true;
options.create_if_missing = true;
// Skip verifying record count against TableProperties for
// MockTables
options.compaction_verify_record_count = false;
Status s;
for (const auto& mode : corruption_modes) {
delete db_;
@@ -863,7 +866,6 @@ TEST_F(CorruptionTest, ParanoidFileChecksOnCompact) {
ASSERT_OK(DB::Open(options, dbname_, &db_));
assert(db_ != nullptr); // suppress false clang-analyze report
Build(100, 2);
// ASSERT_OK(db_->Flush(FlushOptions()));
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
ASSERT_OK(dbi->TEST_FlushMemTable());
mock->SetCorruptionMode(mode);
+35 -14
View File
@@ -161,6 +161,7 @@ TEST_F(DBBasicTest, UniqueSession) {
ASSERT_EQ(sid2, sid3);
DestroyAndReopen(options);
CreateAndReopenWithCF({"goku"}, options);
ASSERT_OK(db_->GetDbSessionId(sid1));
ASSERT_OK(Put("bar", "e1"));
@@ -179,6 +180,7 @@ TEST_F(DBBasicTest, UniqueSession) {
TEST_F(DBBasicTest, ReadOnlyDB) {
ASSERT_OK(Put("foo", "v1"));
ASSERT_OK(Put("bar", "v2"));
ASSERT_OK(Flush());
ASSERT_OK(Put("foo", "v3"));
Close();
@@ -208,10 +210,11 @@ TEST_F(DBBasicTest, ReadOnlyDB) {
auto options = CurrentOptions();
assert(options.env == env_);
ASSERT_OK(ReadOnlyReopen(options));
ASSERT_OK(EnforcedReadOnlyReopen(options));
ASSERT_EQ("v3", Get("foo"));
ASSERT_EQ("v2", Get("bar"));
verify_all_iters();
ASSERT_EQ(Flush().code(), Status::Code::kNotSupported);
Close();
// Reopen and flush memtable.
@@ -219,26 +222,38 @@ TEST_F(DBBasicTest, ReadOnlyDB) {
ASSERT_OK(Flush());
Close();
// Now check keys in read only mode.
ASSERT_OK(ReadOnlyReopen(options));
ASSERT_OK(EnforcedReadOnlyReopen(options));
ASSERT_EQ("v3", Get("foo"));
ASSERT_EQ("v2", Get("bar"));
verify_all_iters();
ASSERT_TRUE(db_->SyncWAL().IsNotSupported());
ASSERT_EQ(db_->SyncWAL().code(), Status::Code::kNotSupported);
// More ops that should fail
std::vector<ColumnFamilyHandle*> cfhs{{}};
ASSERT_EQ(db_->CreateColumnFamily(options, "blah", &cfhs[0]).code(),
Status::Code::kNotSupported);
ASSERT_EQ(db_->CreateColumnFamilies(options, {"blah"}, &cfhs).code(),
Status::Code::kNotSupported);
std::vector<ColumnFamilyDescriptor> cfds;
cfds.push_back({"blah", options});
ASSERT_EQ(db_->CreateColumnFamilies(cfds, &cfhs).code(),
Status::Code::kNotSupported);
}
// TODO akanksha: Update the test to check that combination
// does not actually write to FS (use open read-only with
// CompositeEnvWrapper+ReadOnlyFileSystem).
TEST_F(DBBasicTest, DISABLED_ReadOnlyDBWithWriteDBIdToManifestSet) {
TEST_F(DBBasicTest, ReadOnlyDBWithWriteDBIdToManifestSet) {
auto options = CurrentOptions();
options.write_dbid_to_manifest = false;
DestroyAndReopen(options);
ASSERT_OK(Put("foo", "v1"));
ASSERT_OK(Put("bar", "v2"));
ASSERT_OK(Put("foo", "v3"));
Close();
auto options = CurrentOptions();
options.write_dbid_to_manifest = true;
assert(options.env == env_);
ASSERT_OK(ReadOnlyReopen(options));
ASSERT_OK(EnforcedReadOnlyReopen(options));
std::string db_id1;
ASSERT_OK(db_->GetDbIdentity(db_id1));
ASSERT_EQ("v3", Get("foo"));
@@ -258,7 +273,7 @@ TEST_F(DBBasicTest, DISABLED_ReadOnlyDBWithWriteDBIdToManifestSet) {
ASSERT_OK(Flush());
Close();
// Now check keys in read only mode.
ASSERT_OK(ReadOnlyReopen(options));
ASSERT_OK(EnforcedReadOnlyReopen(options));
ASSERT_EQ("v3", Get("foo"));
ASSERT_EQ("v2", Get("bar"));
ASSERT_TRUE(db_->SyncWAL().IsNotSupported());
@@ -3273,8 +3288,7 @@ TEST_F(DBBasicTest, GetAllKeyVersions) {
ASSERT_OK(Delete(std::to_string(i)));
}
std::vector<KeyVersion> key_versions;
ASSERT_OK(GetAllKeyVersions(db_, Slice(), Slice(),
std::numeric_limits<size_t>::max(),
ASSERT_OK(GetAllKeyVersions(db_, {}, {}, std::numeric_limits<size_t>::max(),
&key_versions));
ASSERT_EQ(kNumInserts + kNumDeletes + kNumUpdates, key_versions.size());
for (size_t i = 0; i < kNumInserts + kNumDeletes + kNumUpdates; i++) {
@@ -3284,7 +3298,7 @@ TEST_F(DBBasicTest, GetAllKeyVersions) {
ASSERT_EQ(key_versions[i].GetTypeName(), "TypeValue");
}
}
ASSERT_OK(GetAllKeyVersions(db_, handles_[0], Slice(), Slice(),
ASSERT_OK(GetAllKeyVersions(db_, handles_[0], {}, {},
std::numeric_limits<size_t>::max(),
&key_versions));
ASSERT_EQ(kNumInserts + kNumDeletes + kNumUpdates, key_versions.size());
@@ -3299,10 +3313,17 @@ TEST_F(DBBasicTest, GetAllKeyVersions) {
for (size_t i = 0; i + 1 != kNumDeletes; ++i) {
ASSERT_OK(Delete(1, std::to_string(i)));
}
ASSERT_OK(GetAllKeyVersions(db_, handles_[1], Slice(), Slice(),
ASSERT_OK(GetAllKeyVersions(db_, handles_[1], {}, {},
std::numeric_limits<size_t>::max(),
&key_versions));
ASSERT_EQ(kNumInserts + kNumDeletes + kNumUpdates - 3, key_versions.size());
// Change from historical behavior: empty key is now interpreted literally as
// a legal key (rather than as a "not present" key)
ASSERT_OK(GetAllKeyVersions(db_, handles_[1], Slice(), Slice(),
std::numeric_limits<size_t>::max(),
&key_versions));
ASSERT_EQ(key_versions.size(), 0);
}
TEST_F(DBBasicTest, ValueTypeString) {
+164 -9
View File
@@ -127,6 +127,19 @@ class DBCompactionTestWithParam
exclusive_manual_compaction_ = std::get<1>(GetParam());
}
class TrivialMoveEventListener : public EventListener {
public:
explicit TrivialMoveEventListener(size_t expected_trivially_moved_files)
: expected_trivially_moved_files_(expected_trivially_moved_files) {}
void OnCompactionBegin(DB* /*db*/, const CompactionJobInfo& ci) override {
ASSERT_EQ(ci.stats.num_input_files_trivially_moved,
expected_trivially_moved_files_);
}
private:
size_t expected_trivially_moved_files_ = 0;
};
// Required if inheriting from testing::WithParamInterface<>
static void SetUpTestCase() {}
static void TearDownTestCase() {}
@@ -1301,6 +1314,9 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveOneFile) {
Options options = CurrentOptions();
options.write_buffer_size = 100000000;
TrivialMoveEventListener* trivial_move_listener =
new TrivialMoveEventListener(1 /*expected_trivially_moved_files*/);
options.listeners.emplace_back(trivial_move_listener);
options.max_subcompactions = max_subcompactions_;
DestroyAndReopen(options);
@@ -1361,6 +1377,10 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveNonOverlappingFiles) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
// 8 is number of `ranges` that each is a non overlapping file.
TrivialMoveEventListener* trivial_move_listener =
new TrivialMoveEventListener(8 /*expected_trivially_moved_files*/);
options.listeners.emplace_back(trivial_move_listener);
options.write_buffer_size = 10 * 1024 * 1024;
options.max_subcompactions = max_subcompactions_;
@@ -1408,6 +1428,11 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveNonOverlappingFiles) {
trivial_move = 0;
non_trivial_move = 0;
values.clear();
options.listeners.clear();
// Same ranges of files, but now overlapping, trivial move not applicable.
TrivialMoveEventListener* trivial_move_listener2 =
new TrivialMoveEventListener(0 /*expected_trivially_moved_files*/);
options.listeners.emplace_back(trivial_move_listener2);
DestroyAndReopen(options);
// Same ranges as above but overlapping
ranges = {
@@ -1455,6 +1480,11 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveTargetLevel) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
// Two non overlapping files in L0 trivialy moved:
// file 1 [0 => 300], file 2 [600 => 700]
TrivialMoveEventListener* trivial_move_listener1 =
new TrivialMoveEventListener(2 /*expected_trivially_moved_files*/);
options.listeners.emplace_back(trivial_move_listener1);
options.write_buffer_size = 10 * 1024 * 1024;
options.num_levels = 7;
options.max_subcompactions = max_subcompactions_;
@@ -2087,13 +2117,10 @@ TEST_P(DBDeleteFileRangeTest, DeleteFilesInRanges) {
auto begin_str1 = Key(0), end_str1 = Key(100);
auto begin_str2 = Key(100), end_str2 = Key(200);
auto begin_str3 = Key(200), end_str3 = Key(299);
Slice begin1(begin_str1), end1(end_str1);
Slice begin2(begin_str2), end2(end_str2);
Slice begin3(begin_str3), end3(end_str3);
std::vector<RangePtr> ranges;
ranges.emplace_back(&begin1, &end1);
ranges.emplace_back(&begin2, &end2);
ranges.emplace_back(&begin3, &end3);
std::vector<RangeOpt> ranges;
ranges.emplace_back(begin_str1, end_str1);
ranges.emplace_back(begin_str2, end_str2);
ranges.emplace_back(begin_str3, end_str3);
ASSERT_OK(DeleteFilesInRanges(db_, db_->DefaultColumnFamily(),
ranges.data(), ranges.size()));
ASSERT_EQ("0,3,7", FilesPerLevel(0));
@@ -2141,7 +2168,7 @@ TEST_P(DBDeleteFileRangeTest, DeleteFilesInRanges) {
// Delete all files.
{
RangePtr range;
RangeOpt range;
ASSERT_OK(DeleteFilesInRanges(db_, db_->DefaultColumnFamily(), &range, 1));
ASSERT_EQ("", FilesPerLevel(0));
@@ -5912,6 +5939,9 @@ TEST_F(DBCompactionTest, SubcompactionEvent) {
ASSERT_EQ(running_compactions_.find(ci.job_id),
running_compactions_.end());
running_compactions_.emplace(ci.job_id, std::unordered_set<int>());
if (expected_num_l0_files_pre_compaction_ != -1) {
ASSERT_EQ(expected_num_l0_files_pre_compaction_, ci.num_l0_files);
}
}
void OnCompactionCompleted(DB* /*db*/,
@@ -5921,6 +5951,9 @@ TEST_F(DBCompactionTest, SubcompactionEvent) {
ASSERT_NE(it, running_compactions_.end());
ASSERT_EQ(it->second.size(), 0);
running_compactions_.erase(it);
if (expected_num_l0_files_post_compaction_ != -1) {
ASSERT_EQ(expected_num_l0_files_post_compaction_, ci.num_l0_files);
}
}
void OnSubcompactionBegin(const SubcompactionJobInfo& si) override {
@@ -5950,10 +5983,25 @@ TEST_F(DBCompactionTest, SubcompactionEvent) {
return total_subcompaction_cnt_;
}
void SetExpectedNumL0FilesPreCompaction(int num) {
expected_num_l0_files_pre_compaction_ = num;
}
void SetExpectedNumL0FilesPostCompaction(int num) {
expected_num_l0_files_post_compaction_ = num;
}
void ResetExpectedNumL0Files() {
SetExpectedNumL0FilesPreCompaction(-1);
SetExpectedNumL0FilesPostCompaction(-1);
}
private:
InstrumentedMutex mutex_;
std::unordered_map<int, std::unordered_set<int>> running_compactions_;
size_t total_subcompaction_cnt_ = 0;
int expected_num_l0_files_pre_compaction_ = -1;
int expected_num_l0_files_post_compaction_ = -1;
};
Options options = CurrentOptions();
@@ -5973,6 +6021,7 @@ TEST_F(DBCompactionTest, SubcompactionEvent) {
ASSERT_OK(Flush());
}
MoveFilesToLevel(2);
ASSERT_EQ(FilesPerLevel(), "0,0,4");
// generate 2 files @ L1 which overlaps with L2 files
for (int i = 0; i < 2; i++) {
@@ -5982,11 +6031,18 @@ TEST_F(DBCompactionTest, SubcompactionEvent) {
}
ASSERT_OK(Flush());
}
listener->SetExpectedNumL0FilesPreCompaction(2 /* num */);
listener->SetExpectedNumL0FilesPostCompaction(0 /* num */);
MoveFilesToLevel(1);
ASSERT_EQ(FilesPerLevel(), "0,2,4");
listener->ResetExpectedNumL0Files();
CompactRangeOptions comp_opts;
comp_opts.max_subcompactions = 4;
listener->SetExpectedNumL0FilesPreCompaction(0 /* num */);
Status s = dbfull()->CompactRange(comp_opts, nullptr, nullptr);
ASSERT_OK(s);
ASSERT_OK(dbfull()->TEST_WaitForCompact());
@@ -5994,6 +6050,8 @@ TEST_F(DBCompactionTest, SubcompactionEvent) {
ASSERT_EQ(listener->GetRunningCompactionCount(), 0);
// and sub compaction is triggered
ASSERT_GT(listener->GetTotalSubcompactionCount(), 0);
listener->ResetExpectedNumL0Files();
}
TEST_F(DBCompactionTest, CompactFilesOutputRangeConflict) {
@@ -10472,7 +10530,7 @@ TEST_F(DBCompactionTest, NumberOfSubcompactions) {
}
}
TEST_F(DBCompactionTest, VerifyRecordCount) {
TEST_F(DBCompactionTest, VerifyInputRecordCount) {
Options options = CurrentOptions();
options.compaction_style = kCompactionStyleLevel;
options.level0_file_num_compaction_trigger = 3;
@@ -10510,6 +10568,103 @@ TEST_F(DBCompactionTest, VerifyRecordCount) {
ASSERT_TRUE(std::strstr(s.getState(), expect));
}
TEST_F(DBCompactionTest, VerifyOutputRecordCountBlockBasedTable) {
Options options = CurrentOptions();
options.compaction_style = kCompactionStyleLevel;
options.level0_file_num_compaction_trigger = 3;
options.compaction_verify_record_count = true;
DestroyAndReopen(options);
Random rnd(301);
// Create 2 overlapping L0 files
for (int i = 1; i < 20; i += 2) {
ASSERT_OK(Put(Key(i), rnd.RandomString(100)));
}
ASSERT_OK(Flush());
ASSERT_OK(db_->DeleteRange(WriteOptions(), Key(10), Key(15)));
for (int i = 0; i < 20; i += 2) {
ASSERT_OK(Put(Key(i), rnd.RandomString(100)));
}
ASSERT_OK(Flush());
// Skip adding every 7th key in the output table
int num_iter = 0;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"BlockBasedTableBuilder::Add::skip", [&](void* skip) {
num_iter++;
if (num_iter % 7 == 0) {
*(bool*)skip = true;
}
});
SyncPoint::GetInstance()->EnableProcessing();
Status s = db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
ASSERT_TRUE(s.IsCorruption());
const char* expect =
"Number of keys in compaction output SST files does not match number of "
"keys added.";
ASSERT_TRUE(std::strstr(s.getState(), expect));
}
TEST_F(DBCompactionTest, VerifyOutputRecordCountPlainTable) {
Options options = CurrentOptions();
options.compaction_style = kCompactionStyleLevel;
options.level0_file_num_compaction_trigger = 3;
options.compaction_verify_record_count = true;
PlainTableOptions plain_table_options;
plain_table_options.user_key_len = 0;
plain_table_options.bloom_bits_per_key = 2;
plain_table_options.hash_table_ratio = 0.8;
plain_table_options.index_sparseness = 3;
plain_table_options.huge_page_tlb_size = 0;
plain_table_options.encoding_type = kPrefix;
plain_table_options.full_scan_mode = false;
plain_table_options.store_index_in_file = false;
options.table_factory.reset(NewPlainTableFactory(plain_table_options));
options.memtable_factory.reset(NewHashLinkListRepFactory(4, 0, 3, true));
options.prefix_extractor.reset(NewFixedPrefixTransform(8));
options.allow_mmap_reads = false;
options.allow_concurrent_memtable_write = false;
options.unordered_write = false;
DestroyAndReopen(options);
Random rnd(301);
// Create 2 overlapping L0 files
for (int i = 1; i < 20; i += 2) {
ASSERT_OK(Put(Key(i), rnd.RandomString(100)));
}
ASSERT_OK(Flush());
for (int i = 0; i < 20; i += 2) {
ASSERT_OK(Put(Key(i), rnd.RandomString(100)));
}
ASSERT_OK(Flush());
// Skip adding every 7th key in the output table
int num_iter = 0;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"PlainTableBuilder::Add::skip", [&](void* skip) {
num_iter++;
if (num_iter % 7 == 0) {
*(bool*)skip = true;
}
});
SyncPoint::GetInstance()->EnableProcessing();
Status s = db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
ASSERT_TRUE(s.IsCorruption());
const char* expect =
"Number of keys in compaction output SST files does not match number of "
"keys added.";
ASSERT_TRUE(std::strstr(s.getState(), expect));
}
TEST_F(DBCompactionTest, ErrorWhenReadFileHead) {
// This is to test a bug that is fixed in
// https://github.com/facebook/rocksdb/pull/11782.
+60 -16
View File
@@ -80,6 +80,7 @@
#include "port/port.h"
#include "rocksdb/cache.h"
#include "rocksdb/compaction_filter.h"
#include "rocksdb/convenience.h"
#include "rocksdb/db.h"
#include "rocksdb/env.h"
#include "rocksdb/merge_operator.h"
@@ -4407,7 +4408,7 @@ Status DBImpl::GetPropertiesOfTablesInRange(ColumnFamilyHandle* column_family,
// Add timestamp if needed
for (size_t i = 0; i < n; i++) {
auto [start, limit] = MaybeAddTimestampsToRange(
&range[i].start, &range[i].limit, ts_sz, &keys.emplace_back(),
range[i].start, range[i].limit, ts_sz, &keys.emplace_back(),
&keys.emplace_back(), /*exclusive_end=*/false);
assert(start.has_value());
assert(limit.has_value());
@@ -4424,6 +4425,29 @@ Status DBImpl::GetPropertiesOfTablesInRange(ColumnFamilyHandle* column_family,
return s;
}
Status DBImpl::GetPropertiesOfTablesByLevel(
ColumnFamilyHandle* column_family,
std::vector<std::unique_ptr<TablePropertiesCollection>>* props_by_level) {
auto cfh = static_cast_with_check<ColumnFamilyHandleImpl>(column_family);
auto cfd = cfh->cfd();
// Increment the ref count
mutex_.Lock();
auto version = cfd->current();
version->Ref();
mutex_.Unlock();
const ReadOptions read_options;
auto s = version->GetPropertiesOfTablesByLevel(read_options, props_by_level);
// Decrement the ref count
mutex_.Lock();
version->Unref();
mutex_.Unlock();
return s;
}
const std::string& DBImpl::GetName() const { return dbname_; }
Env* DBImpl::GetEnv() const { return env_; }
@@ -4724,7 +4748,7 @@ void DBImpl::GetApproximateMemTableStats(ColumnFamilyHandle* column_family,
// Add timestamp if needed
std::string start_with_ts, limit_with_ts;
auto [start, limit] = MaybeAddTimestampsToRange(
&range.start, &range.limit, ts_sz, &start_with_ts, &limit_with_ts);
range.start, range.limit, ts_sz, &start_with_ts, &limit_with_ts);
assert(start.has_value());
assert(limit.has_value());
// Convert user_key into a corresponding internal key.
@@ -4762,9 +4786,8 @@ Status DBImpl::GetApproximateSizes(const SizeApproximationOptions& options,
for (int i = 0; i < n; i++) {
// Add timestamp if needed
std::string start_with_ts, limit_with_ts;
auto [start, limit] =
MaybeAddTimestampsToRange(&range[i].start, &range[i].limit, ts_sz,
&start_with_ts, &limit_with_ts);
auto [start, limit] = MaybeAddTimestampsToRange(
range[i].start, range[i].limit, ts_sz, &start_with_ts, &limit_with_ts);
assert(start.has_value());
assert(limit.has_value());
// Convert user_key into a corresponding internal key.
@@ -4840,7 +4863,7 @@ Status DBImpl::GetUpdatesSince(
}
Status DBImpl::DeleteFilesInRanges(ColumnFamilyHandle* column_family,
const RangePtr* ranges, size_t n,
const RangeOpt* ranges, size_t n,
bool include_end) {
// TODO: plumb Env::IOActivity, Env::IOPriority
const ReadOptions read_options;
@@ -4852,7 +4875,7 @@ Status DBImpl::DeleteFilesInRanges(ColumnFamilyHandle* column_family,
const Comparator* ucmp = cfd->user_comparator();
assert(ucmp);
const size_t ts_sz = ucmp->timestamp_size();
autovector<UserKeyRangePtr> ukey_ranges;
autovector<UserKeyRangeOpt> ukey_ranges;
std::vector<std::string> keys;
std::vector<Slice> key_slices;
ukey_ranges.reserve(n);
@@ -4862,8 +4885,8 @@ Status DBImpl::DeleteFilesInRanges(ColumnFamilyHandle* column_family,
auto [start, limit] = MaybeAddTimestampsToRange(
ranges[i].start, ranges[i].limit, ts_sz, &keys.emplace_back(),
&keys.emplace_back(), !include_end);
assert((ranges[i].start != nullptr) == start.has_value());
assert((ranges[i].limit != nullptr) == limit.has_value());
assert(ranges[i].start.has_value() == start.has_value());
assert(ranges[i].limit.has_value() == limit.has_value());
ukey_ranges.emplace_back(start, limit);
}
@@ -5799,6 +5822,27 @@ Status DBImpl::IngestExternalFiles(
"timestamps enabled doesn't support ingest behind.");
}
}
if (arg.atomic_replace_range.has_value()) {
if (ingest_opts.ingest_behind) {
return Status::InvalidArgument(
"Can't combine atomic_replace_range with ingest_behind.");
}
if (ingest_opts.snapshot_consistency) {
// TODO: support generating and ingesting a big tombstone file, which
// might depend on non-nullptr start and limit
return Status::NotSupported(
"atomic_replace_range not yet supported with "
"snapshot_consistency.");
} else {
if (arg.atomic_replace_range->start.has_value() ^
arg.atomic_replace_range->limit.has_value()) {
return Status::NotSupported(
"Only one of atomic_replace_range.{start,limit}.has_value() is "
"not supported.");
}
}
}
if (ingest_opts.allow_db_generated_files) {
if (ingest_opts.write_global_seqno) {
return Status::NotSupported(
@@ -5847,8 +5891,8 @@ Status DBImpl::IngestExternalFiles(
this);
Status es = ingestion_jobs[i].Prepare(
args[i].external_files, args[i].files_checksums,
args[i].files_checksum_func_names, args[i].file_temperature,
start_file_number, super_version);
args[i].files_checksum_func_names, args[i].atomic_replace_range,
args[i].file_temperature, start_file_number, super_version);
// capture first error only
if (!es.ok() && status.ok()) {
status = es;
@@ -5863,8 +5907,8 @@ Status DBImpl::IngestExternalFiles(
this);
Status es = ingestion_jobs[0].Prepare(
args[0].external_files, args[0].files_checksums,
args[0].files_checksum_func_names, args[0].file_temperature,
next_file_number, super_version);
args[0].files_checksum_func_names, args[0].atomic_replace_range,
args[0].file_temperature, next_file_number, super_version);
if (!es.ok()) {
status = es;
}
@@ -6255,9 +6299,9 @@ Status DBImpl::ClipColumnFamily(ColumnFamilyHandle* column_family,
if (status.ok()) {
// DeleteFilesInRanges non-overlap files except L0
std::vector<RangePtr> ranges;
ranges.emplace_back(nullptr, &begin_key);
ranges.emplace_back(&end_key, nullptr);
std::vector<RangeOpt> ranges;
ranges.emplace_back(OptSlice{}, begin_key);
ranges.emplace_back(end_key, OptSlice{});
status = DeleteFilesInRanges(column_family, ranges.data(), ranges.size());
}
+11 -1
View File
@@ -542,7 +542,7 @@ class DBImpl : public DB {
const TransactionLogIterator::ReadOptions& read_options =
TransactionLogIterator::ReadOptions()) override;
Status DeleteFilesInRanges(ColumnFamilyHandle* column_family,
const RangePtr* ranges, size_t n,
const RangeOpt* ranges, size_t n,
bool include_end = true);
void GetLiveFilesMetaData(std::vector<LiveFileMetaData>* metadata) override;
@@ -651,6 +651,11 @@ class DBImpl : public DB {
ColumnFamilyHandle* column_family, const Range* range, std::size_t n,
TablePropertiesCollection* props) override;
Status GetPropertiesOfTablesByLevel(
ColumnFamilyHandle* column_family,
std::vector<std::unique_ptr<TablePropertiesCollection>>* props_by_level)
override;
// ---- End of implementations of the DB interface ----
SystemClock* GetSystemClock() const;
@@ -2129,6 +2134,11 @@ class DBImpl : public DB {
bool flushed, std::unordered_map<int, VersionEdit>* version_edits,
RecoveryContext* recovery_ctx);
// Check that DB sequence number is not set back during recovery between
// replaying of WAL files and between replaying of WriteBatches.
Status CheckSeqnoNotSetBackDuringRecovery(SequenceNumber prev_next_seqno,
SequenceNumber current_next_seqno);
void FinishLogFilesRecovery(int job_id, const Status& status);
// The following two methods are used to flush a memtable to
// storage. The first one is used at database RecoveryTime (when the
+8 -1
View File
@@ -981,7 +981,8 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
std::string begin_str, end_str;
auto [begin, end] =
MaybeAddTimestampsToRange(begin_without_ts, end_without_ts, ts_sz,
MaybeAddTimestampsToRange(OptSlice::CopyFromPtr(begin_without_ts),
OptSlice::CopyFromPtr(end_without_ts), ts_sz,
&begin_str, &end_str, false /*exclusive_end*/);
return CompactRangeInternal(
@@ -1695,11 +1696,13 @@ void DBImpl::NotifyOnCompactionBegin(ColumnFamilyData* cfd, Compaction* c,
}
c->SetNotifyOnCompactionCompleted();
int num_l0_files = c->input_version()->storage_info()->NumLevelFiles(0);
// release lock while notifying events
mutex_.Unlock();
TEST_SYNC_POINT("DBImpl::NotifyOnCompactionBegin::UnlockMutex");
{
CompactionJobInfo info{};
info.num_l0_files = num_l0_files;
BuildCompactionJobInfo(cfd, c, st, job_stats, job_id, &info);
for (const auto& listener : immutable_db_options_.listeners) {
listener->OnCompactionBegin(this, info);
@@ -1724,11 +1727,13 @@ void DBImpl::NotifyOnCompactionCompleted(
return;
}
int num_l0_files = cfd->current()->storage_info()->NumLevelFiles(0);
// release lock while notifying events
mutex_.Unlock();
TEST_SYNC_POINT("DBImpl::NotifyOnCompactionCompleted::UnlockMutex");
{
CompactionJobInfo info{};
info.num_l0_files = num_l0_files;
BuildCompactionJobInfo(cfd, c, st, compaction_job_stats, job_id, &info);
for (const auto& listener : immutable_db_options_.listeners) {
listener->OnCompactionCompleted(this, info);
@@ -3798,6 +3803,8 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
compaction_job_stats.num_input_files = c->num_input_files(0);
// Trivial moves do not get compacted remotely
compaction_job_stats.is_remote_compaction = false;
compaction_job_stats.num_input_files_trivially_moved =
compaction_job_stats.num_input_files;
NotifyOnCompactionBegin(c->column_family_data(), c.get(), status,
compaction_job_stats, job_context->job_id);
+5 -2
View File
@@ -379,10 +379,13 @@ void DBImpl::TEST_VerifyNoObsoleteFilesCached(
uint64_t file_number;
GetUnaligned(reinterpret_cast<const uint64_t*>(key.data()), &file_number);
// Assert file is in live/quarantined set
if (live_and_quar_files.find(file_number) == live_and_quar_files.end()) {
bool cached_file_is_live_or_quar =
live_and_quar_files.find(file_number) != live_and_quar_files.end();
if (!cached_file_is_live_or_quar) {
// Fail with useful info
std::cerr << "File " << file_number << " is not live nor quarantined"
<< std::endl;
assert(false);
assert(cached_file_is_live_or_quar);
}
};
table_cache_->ApplyToAllEntries(fn, {});
+3
View File
@@ -368,6 +368,7 @@ void DBImpl::DeleteObsoleteFileImpl(int job_id, const std::string& fname,
FileType type, uint64_t number) {
TEST_SYNC_POINT_CALLBACK("DBImpl::DeleteObsoleteFileImpl::BeforeDeletion",
const_cast<std::string*>(&fname));
IGNORE_STATUS_IF_ERROR(Status::IOError());
Status file_deletion_status;
if (type == kTableFile || type == kBlobFile || type == kWalFile) {
@@ -423,6 +424,8 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
// FindObsoleteFiles() should've populated this so nonzero
assert(state.manifest_file_number != 0);
IGNORE_STATUS_IF_ERROR(Status::IOError());
// Now, convert lists to unordered sets, WITHOUT mutex held; set is slow.
std::unordered_set<uint64_t> sst_live_set(state.sst_live.begin(),
state.sst_live.end());
+41 -5
View File
@@ -35,8 +35,8 @@ Options SanitizeOptions(const std::string& dbname, const Options& src,
auto db_options =
SanitizeOptions(dbname, DBOptions(src), read_only, logger_creation_s);
ImmutableDBOptions immutable_db_options(db_options);
auto cf_options = SanitizeOptions(immutable_db_options, read_only,
ColumnFamilyOptions(src));
auto cf_options = SanitizeCfOptions(immutable_db_options, read_only,
ColumnFamilyOptions(src));
return Options(db_options, cf_options);
}
@@ -1203,6 +1203,13 @@ Status DBImpl::ProcessLogFiles(
PredecessorWALInfo predecessor_wal_info;
for (auto wal_number : wal_numbers) {
// Detecting early break on the next iteration after `wal_number` has been
// advanced since this `wal_number` doesn't affect follow-up handling after
// breaking out of the for loop.
if (!status.ok()) {
break;
}
SequenceNumber prev_next_sequence = *next_sequence;
if (status.ok()) {
status = ProcessLogFile(
wal_number, min_wal_number, is_retry, read_only, job_id,
@@ -1210,6 +1217,10 @@ Status DBImpl::ProcessLogFiles(
&stop_replay_by_wal_filter, &corrupted_wal_number,
corrupted_wal_found, version_edits, &flushed, predecessor_wal_info);
}
if (status.ok()) {
status = CheckSeqnoNotSetBackDuringRecovery(prev_next_sequence,
*next_sequence);
}
}
if (status.ok()) {
@@ -1317,6 +1328,7 @@ Status DBImpl::ProcessLogFile(
}
// FIXME(hx235): consolidate `process_status` and `status`
SequenceNumber prev_next_sequence = *next_sequence;
Status process_status = ProcessLogRecord(
record, reader, running_ts_sz, wal_number, fname, read_only, job_id,
logFileDropped, &reporter, &record_checksum, &last_seqno_observed,
@@ -1325,6 +1337,12 @@ Status DBImpl::ProcessLogFile(
if (!process_status.ok()) {
return process_status;
} else if (Status seqno_check_status = CheckSeqnoNotSetBackDuringRecovery(
prev_next_sequence, *next_sequence);
!seqno_check_status.ok()) {
// Sequence number being set back indicates a serious software bug, the DB
// should not be opened in this case.
return seqno_check_status;
} else if (*stop_replay_for_corruption) {
break;
}
@@ -1863,6 +1881,20 @@ Status DBImpl::MaybeFlushFinalMemtableOrRestoreActiveLogFiles(
return status;
}
Status DBImpl::CheckSeqnoNotSetBackDuringRecovery(
SequenceNumber prev_next_seqno, SequenceNumber current_next_seqno) {
if (prev_next_seqno == kMaxSequenceNumber ||
prev_next_seqno <= current_next_seqno) {
return Status::OK();
}
std::string msg =
"Sequence number is being set backwards during recovery, this is likely "
"a software bug or a data corruption. Prev next seqno: " +
std::to_string(prev_next_seqno) +
" , current next seqno: " + std::to_string(current_next_seqno);
return Status::Corruption(msg);
}
void DBImpl::FinishLogFilesRecovery(int job_id, const Status& status) {
event_logger_.Log() << "job" << job_id << "event"
<< (status.ok() ? "recovery_finished" : "recovery_failed")
@@ -1995,8 +2027,9 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
meta.oldest_ancester_time = current_time;
meta.epoch_number = cfd->NewEpochNumber();
{
auto write_hint =
cfd->current()->storage_info()->CalculateSSTWriteHint(/*level=*/0);
auto write_hint = cfd->current()->storage_info()->CalculateSSTWriteHint(
/*level=*/0,
immutable_db_options_.calculate_sst_write_lifetime_hint_set);
mutex_.Unlock();
SequenceNumber earliest_write_conflict_snapshot;
@@ -2264,6 +2297,7 @@ IOStatus DBImpl::CreateWAL(const WriteOptions& write_options,
BuildDBOptions(immutable_db_options_, mutable_db_options_);
FileOptions opt_file_options =
fs_->OptimizeForLogWrite(file_options_, db_options);
opt_file_options.write_hint = CalculateWALWriteHint();
// DB option takes precedence when not kUnknown
if (immutable_db_options_.wal_write_temperature != Temperature::kUnknown) {
opt_file_options.temperature = immutable_db_options_.wal_write_temperature;
@@ -2285,7 +2319,9 @@ IOStatus DBImpl::CreateWAL(const WriteOptions& write_options,
}
if (io_s.ok()) {
lfile->SetWriteLifeTimeHint(CalculateWALWriteHint());
// Subsequent attempts to override the hint via SetWriteLifeTimeHint
// with the very same value will be ignored by the fs.
lfile->SetWriteLifeTimeHint(opt_file_options.write_hint);
lfile->SetPreallocationBlockSize(preallocate_block_size);
const auto& listeners = immutable_db_options_.listeners;
+23
View File
@@ -155,6 +155,29 @@ class DBImplReadOnly : public DBImpl {
return Status::NotSupported("Not supported operation in read only mode.");
}
using DB::CreateColumnFamily;
using DBImpl::CreateColumnFamily;
Status CreateColumnFamily(const ColumnFamilyOptions& /*cf_options*/,
const std::string& /*column_family*/,
ColumnFamilyHandle** /*handle*/) override {
return Status::NotSupported("Not supported operation in read only mode.");
}
using DB::CreateColumnFamilies;
using DBImpl::CreateColumnFamilies;
Status CreateColumnFamilies(
const ColumnFamilyOptions& /*cf_options*/,
const std::vector<std::string>& /*column_family_names*/,
std::vector<ColumnFamilyHandle*>* /*handles*/) override {
return Status::NotSupported("Not supported operation in read only mode.");
}
Status CreateColumnFamilies(
const std::vector<ColumnFamilyDescriptor>& /*column_families*/,
std::vector<ColumnFamilyHandle*>* /*handles*/) override {
return Status::NotSupported("Not supported operation in read only mode.");
}
// FIXME: some missing overrides for more "write" functions
protected:
+1
View File
@@ -994,6 +994,7 @@ Status DB::OpenAndCompact(
DBOptions db_options;
ConfigOptions config_options;
config_options.env = override_options.env;
config_options.ignore_unknown_options = true;
std::vector<ColumnFamilyDescriptor> all_column_families;
TEST_SYNC_POINT_CALLBACK(
+3 -3
View File
@@ -971,7 +971,7 @@ TEST_F(DBMergeOperatorTest, MaxSuccessiveMergesBaseValues) {
// No base value
{
constexpr char key[] = "key1";
const std::string key = "key1";
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), key, foo));
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), key, bar));
@@ -994,7 +994,7 @@ TEST_F(DBMergeOperatorTest, MaxSuccessiveMergesBaseValues) {
// Plain base value
{
constexpr char key[] = "key2";
const std::string key = "key2";
ASSERT_OK(db_->Put(WriteOptions(), db_->DefaultColumnFamily(), key, foo));
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), key, bar));
@@ -1019,7 +1019,7 @@ TEST_F(DBMergeOperatorTest, MaxSuccessiveMergesBaseValues) {
// Wide-column base value
{
constexpr char key[] = "key3";
const std::string key = "key3";
const WideColumns columns{{kDefaultWideColumnName, foo}, {bar, baz}};
ASSERT_OK(db_->PutEntity(WriteOptions(), db_->DefaultColumnFamily(), key,
+1 -1
View File
@@ -71,7 +71,7 @@ class DBOptionsTest : public DBTestBase {
ImmutableDBOptions db_options(options);
test::RandomInitCFOptions(&options, options, rnd);
auto sanitized_options =
SanitizeOptions(db_options, /*read_only*/ false, options);
SanitizeCfOptions(db_options, /*read_only*/ false, options);
auto opt_map = GetMutableCFOptionsMap(sanitized_options);
delete options.compaction_filter;
return opt_map;
+1
View File
@@ -160,6 +160,7 @@ TEST_F(DBSecondaryTest, NonExistingDb) {
TEST_F(DBSecondaryTest, ReopenAsSecondary) {
Options options;
options.env = env_;
options.preserve_internal_time_seconds = 300;
Reopen(options);
ASSERT_OK(Put("foo", "foo_value"));
ASSERT_OK(Put("bar", "bar_value"));
+51 -1
View File
@@ -229,6 +229,56 @@ TEST_F(DBTablePropertiesTest, CreateOnDeletionCollectorFactory) {
ASSERT_EQ(0.5, del_factory->GetDeletionRatio());
}
TEST_F(DBTablePropertiesTest, GetPropertiesOfTablesByLevelTest) {
Random rnd(202);
Options options;
options.level_compaction_dynamic_level_bytes = false;
options.create_if_missing = true;
options.write_buffer_size = 4096;
options.max_write_buffer_number = 2;
options.level0_file_num_compaction_trigger = 2;
options.level0_slowdown_writes_trigger = 2;
options.level0_stop_writes_trigger = 2;
options.target_file_size_base = 2048;
options.max_bytes_for_level_base = 40960;
options.max_bytes_for_level_multiplier = 4;
options.hard_pending_compaction_bytes_limit = 16 * 1024;
options.num_levels = 8;
options.env = env_;
DestroyAndReopen(options);
// build a decent LSM
for (int i = 0; i < 10000; i++) {
EXPECT_OK(Put(test::RandomKey(&rnd, 5), rnd.RandomString(102)));
}
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact());
if (NumTableFilesAtLevel(0) == 0) {
EXPECT_OK(Put(test::RandomKey(&rnd, 5), rnd.RandomString(102)));
ASSERT_OK(Flush());
}
ASSERT_OK(db_->PauseBackgroundWork());
// Ensure that we have at least L0, L1 and L2
ASSERT_GT(NumTableFilesAtLevel(0), 0);
ASSERT_GT(NumTableFilesAtLevel(1), 0);
ASSERT_GT(NumTableFilesAtLevel(2), 0);
ColumnFamilyMetaData cf_meta;
db_->GetColumnFamilyMetaData(&cf_meta);
std::vector<std::unique_ptr<TablePropertiesCollection>> levels_props;
ASSERT_OK(db_->GetPropertiesOfTablesByLevel(db_->DefaultColumnFamily(),
&levels_props));
for (int i = 0; i < 8; i++) {
const std::unique_ptr<TablePropertiesCollection>& level_props =
levels_props[i];
ASSERT_EQ(level_props->size(), cf_meta.levels[i].files.size());
}
Close();
}
// Test params:
// 1) whether to enable user-defined timestamps
class DBTablePropertiesInRangeTest : public DBTestBase,
@@ -292,7 +342,7 @@ class DBTablePropertiesInRangeTest : public DBTestBase,
keys.reserve(range_size * 2);
for (auto& r : ranges) {
auto [start, limit] = MaybeAddTimestampsToRange(
&r.start, &r.limit, ts_sz, &keys.emplace_back(), &keys.emplace_back(),
r.start, r.limit, ts_sz, &keys.emplace_back(), &keys.emplace_back(),
/*exclusive_end=*/false);
EXPECT_TRUE(start.has_value());
EXPECT_TRUE(limit.has_value());
+32 -2
View File
@@ -59,11 +59,13 @@
#include "rocksdb/utilities/checkpoint.h"
#include "rocksdb/utilities/optimistic_transaction_db.h"
#include "rocksdb/utilities/write_batch_with_index.h"
#include "table/block_based/block_based_table_factory.h"
#include "table/mock_table.h"
#include "test_util/sync_point.h"
#include "test_util/testharness.h"
#include "test_util/testutil.h"
#include "util/compression.h"
#include "util/defer.h"
#include "util/mutexlock.h"
#include "util/random.h"
#include "util/rate_limiter_impl.h"
@@ -3180,6 +3182,15 @@ class ModelDB : public DB {
return Status();
}
using DB::GetPropertiesOfTablesByLevel;
Status GetPropertiesOfTablesByLevel(
ColumnFamilyHandle* /* column_family */,
std::vector<
std::unique_ptr<TablePropertiesCollection>>* /* props_by_level */)
override {
return Status();
}
using DB::KeyMayExist;
bool KeyMayExist(const ReadOptions& /*options*/,
ColumnFamilyHandle* /*column_family*/, const Slice& /*key*/,
@@ -5400,8 +5411,7 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
for (const auto& file : cf_meta.levels[4].files) {
listener->SetExpectedFileName(dbname_ + file.name);
Slice start(file.smallestkey), limit(file.largestkey);
const RangePtr ranges(&start, &limit);
const RangeOpt ranges(file.smallestkey, file.largestkey);
// Given verification from above, we're guaranteed that by deleting all the
// files in [<smallestkey>, <largestkey>] range, we're effectively deleting
// that very single file and nothing more.
@@ -5453,6 +5463,7 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel2) {
options.max_bytes_for_level_multiplier = 8;
options.max_background_compactions = 1;
options.num_levels = 5;
options.compaction_verify_record_count = false;
std::shared_ptr<mock::MockTableFactory> mtf(new mock::MockTableFactory);
options.table_factory = mtf;
@@ -6084,6 +6095,11 @@ TEST_F(DBTest, L0L1L2AndUpHitCounter) {
}
TEST_F(DBTest, EncodeDecompressedBlockSizeTest) {
bool& allow_unsupported_fv =
BlockBasedTableFactory::AllowUnsupportedFormatVersion();
SaveAndRestore guard(&allow_unsupported_fv);
ASSERT_FALSE(allow_unsupported_fv);
// iter 0 -- zlib
// iter 1 -- bzip2
// iter 2 -- lz4
@@ -6106,7 +6122,16 @@ TEST_F(DBTest, EncodeDecompressedBlockSizeTest) {
table_options.format_version = first_table_version;
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
Options options = CurrentOptions();
// Hack to generate old files (checked in factory construction)
allow_unsupported_fv = true;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
ASSERT_EQ(options.table_factory->GetOptions<BlockBasedTableOptions>()
->format_version,
first_table_version);
// Able to read old files without the hack
allow_unsupported_fv = false;
options.create_if_missing = true;
options.compression = comp;
DestroyAndReopen(options);
@@ -6118,9 +6143,14 @@ TEST_F(DBTest, EncodeDecompressedBlockSizeTest) {
// compressible string
ASSERT_OK(Put(Key(i), rnd.RandomString(128) + std::string(128, 'a')));
}
ASSERT_OK(Flush());
table_options.format_version = first_table_version == 1 ? 2 : 1;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
// format_version (for writing) is sanitized to minimum supported
ASSERT_EQ(options.table_factory->GetOptions<BlockBasedTableOptions>()
->format_version,
BlockBasedTableFactory::kMinSupportedFormatVersion);
Reopen(options);
for (int i = 0; i < kNumKeysWritten; ++i) {
auto r = Get(Key(i));
+12
View File
@@ -11,6 +11,7 @@
#include "cache/cache_reservation_manager.h"
#include "db/forward_iterator.h"
#include "env/fs_readonly.h"
#include "env/mock_env.h"
#include "port/lang.h"
#include "rocksdb/cache.h"
@@ -716,6 +717,17 @@ Status DBTestBase::ReadOnlyReopen(const Options& options) {
return DB::OpenForReadOnly(options, dbname_, &db_);
}
Status DBTestBase::EnforcedReadOnlyReopen(const Options& options) {
Close();
Options options_copy = options;
MaybeInstallTimeElapseOnlySleep(options_copy);
auto fs_read_only =
std::make_shared<ReadOnlyFileSystem>(env_->GetFileSystem());
env_read_only_ = std::make_shared<CompositeEnvWrapper>(env_, fs_read_only);
options_copy.env = env_read_only_.get();
return DB::OpenForReadOnly(options_copy, dbname_, &db_);
}
Status DBTestBase::TryReopen(const Options& options) {
Close();
last_options_.table_factory.reset();
+4
View File
@@ -1062,6 +1062,7 @@ class DBTestBase : public testing::Test {
MockEnv* mem_env_;
Env* encrypted_env_;
SpecialEnv* env_;
std::shared_ptr<Env> env_read_only_;
std::shared_ptr<Env> env_guard_;
DB* db_;
std::vector<ColumnFamilyHandle*> handles_;
@@ -1178,6 +1179,9 @@ class DBTestBase : public testing::Test {
Status ReadOnlyReopen(const Options& options);
// With a filesystem wrapper that fails on attempted write
Status EnforcedReadOnlyReopen(const Options& options);
Status TryReopen(const Options& options);
bool IsDirectIOSupported();
+8 -6
View File
@@ -83,6 +83,8 @@ extern const ValueType kValueTypeForSeekForPrev;
// A range of user keys used internally by RocksDB. Also see `Range` used by
// public APIs.
// TODO: merge with Range in pubic API, but this is generally inclusive limit
// and it is maybe exclusive limit
struct UserKeyRange {
// In case of user_defined timestamp, if enabled, `start` and `limit` should
// include user_defined timestamps.
@@ -93,18 +95,17 @@ struct UserKeyRange {
UserKeyRange(const Slice& s, const Slice& l) : start(s), limit(l) {}
};
// A range of user keys used internally by RocksDB. Also see `RangePtr` used by
// A range of user keys used internally by RocksDB. Also see `RangeOpt` used by
// public APIs.
struct UserKeyRangePtr {
struct UserKeyRangeOpt {
// In case of user_defined timestamp, if enabled, `start` and `limit` should
// point to key with timestamp part.
// An optional range start, if missing, indicating a start before all keys.
std::optional<Slice> start;
OptSlice start;
// An optional range end, if missing, indicating an end after all keys.
std::optional<Slice> limit;
OptSlice limit;
UserKeyRangePtr(const std::optional<Slice>& s, const std::optional<Slice>& l)
: start(s), limit(l) {}
UserKeyRangeOpt(const OptSlice& s, const OptSlice& l) : start(s), limit(l) {}
};
// Checks whether a type is an inline value type
@@ -469,6 +470,7 @@ class InternalKey {
Slice user_key() const { return ExtractUserKey(rep_); }
size_t size() const { return rep_.size(); }
bool unset() const { return rep_.empty(); }
void Set(const Slice& _user_key, SequenceNumber s, ValueType t) {
SetFrom(ParsedInternalKey(_user_key, s, t));
+2 -1
View File
@@ -57,7 +57,8 @@ Status GetFileChecksumsFromCurrentManifest(FileSystem* fs,
}
assert(checksum_list);
const ReadOptions read_options(Env::IOActivity::kReadManifest);
const ReadOptions read_options(
Env::IOActivity::kGetFileChecksumsFromCurrentManifest);
checksum_list->reset();
std::unique_ptr<SequentialFileReader> file_reader;
+233 -9
View File
@@ -1954,21 +1954,44 @@ TEST_F(ExternalSSTFileBasicTest, OverlappingFiles) {
SstFileWriter sst_file_writer(EnvOptions(), options);
std::string file3 = sst_files_dir_ + "file3.sst";
ASSERT_OK(sst_file_writer.Open(file3));
ASSERT_OK(sst_file_writer.Put("j", "j1"));
ASSERT_OK(sst_file_writer.Put("k", "k1"));
ASSERT_OK(sst_file_writer.Put("m", "m1"));
ExternalSstFileInfo file3_info;
ASSERT_OK(sst_file_writer.Finish(&file3_info));
files.push_back(std::move(file3));
}
// This could be ingested to the same level as file3 and file4, but the
// greedy/simple overlap check relegates it to a later level
{
SstFileWriter sst_file_writer(EnvOptions(), options);
std::string file4 = sst_files_dir_ + "file4.sst";
ASSERT_OK(sst_file_writer.Open(file4));
ASSERT_OK(sst_file_writer.Put("j", "j1"));
ExternalSstFileInfo file4_info;
ASSERT_OK(sst_file_writer.Finish(&file4_info));
files.push_back(std::move(file4));
}
{
SstFileWriter sst_file_writer(EnvOptions(), options);
std::string file5 = sst_files_dir_ + "file5.sst";
ASSERT_OK(sst_file_writer.Open(file5));
ASSERT_OK(sst_file_writer.Put("i", "i3"));
ExternalSstFileInfo file5_info;
ASSERT_OK(sst_file_writer.Finish(&file5_info));
files.push_back(std::move(file5));
}
IngestExternalFileOptions ifo;
ifo.allow_global_seqno = false;
ASSERT_NOK(db_->IngestExternalFile(files, ifo));
ifo.allow_global_seqno = true;
ASSERT_OK(db_->IngestExternalFile(files, ifo));
ASSERT_EQ(Get("a"), "a1");
ASSERT_EQ(Get("i"), "i2");
ASSERT_EQ(Get("i"), "i3");
ASSERT_EQ(Get("j"), "j1");
ASSERT_EQ(Get("k"), "k1");
ASSERT_EQ(Get("m"), "m1");
int total_keys = 0;
@@ -1979,10 +2002,11 @@ TEST_F(ExternalSSTFileBasicTest, OverlappingFiles) {
}
ASSERT_OK(iter->status());
delete iter;
ASSERT_EQ(total_keys, 4);
ASSERT_EQ(total_keys, 5);
ASSERT_EQ(1, NumTableFilesAtLevel(6));
ASSERT_EQ(2, NumTableFilesAtLevel(5));
ASSERT_EQ(2, NumTableFilesAtLevel(4));
}
class CompactionJobStatsCheckerForFilteredFiles : public EventListener {
@@ -2686,10 +2710,11 @@ TEST_F(ExternalSSTFileBasicTest, FailIfNotBottommostLevelAndDisallowMemtable) {
// Ingest with snapshot consistency
std::string file_path = sst_files_dir_ + std::to_string(1);
std::string file_path2 = sst_files_dir_ + std::to_string(2);
SstFileWriter sfw(EnvOptions(), options);
ASSERT_OK(sfw.Open(file_path));
ASSERT_OK(sfw.Put("b", "dontcare"));
ASSERT_OK(sfw.Put("b", "0"));
ASSERT_OK(sfw.Finish());
{
@@ -2700,6 +2725,7 @@ TEST_F(ExternalSSTFileBasicTest, FailIfNotBottommostLevelAndDisallowMemtable) {
ifo.snapshot_consistency = true;
ASSERT_OK(db_->IngestExternalFile(handles_[0], {file_path}, ifo));
}
ASSERT_EQ(Get(0, "b"), "0");
// Test level compaction
options.compaction_style = CompactionStyle::kCompactionStyleLevel;
@@ -2721,8 +2747,6 @@ TEST_F(ExternalSSTFileBasicTest, FailIfNotBottommostLevelAndDisallowMemtable) {
EXPECT_EQ(Put(1, "a", "1").code(), Status::Code::kInvalidArgument);
// Use ingestion to get to the same state as above
std::string file_path2 = sst_files_dir_ + std::to_string(2);
ASSERT_OK(sfw.Open(file_path2));
ASSERT_OK(sfw.Put("a", "1"));
ASSERT_OK(sfw.Put("c", "3"));
@@ -2735,16 +2759,216 @@ TEST_F(ExternalSSTFileBasicTest, FailIfNotBottommostLevelAndDisallowMemtable) {
ASSERT_OK(sfw.Finish());
ASSERT_OK(db_->IngestExternalFile(handles_[1], {file_path2}, {}));
}
ASSERT_EQ(Get(1, "a"), "1");
ASSERT_EQ(Get(1, "b"), "2");
ASSERT_EQ(Get(1, "c"), "3");
ASSERT_EQ(Get(1, "d"), "4");
{
// Test fail_if_not_bottommost_level, which fails if there's any overlap
// anywhere, even with snapshot_consistency=false
IngestExternalFileOptions ifo;
ASSERT_FALSE(ifo.fail_if_not_bottommost_level);
ifo.fail_if_not_bottommost_level = true;
ifo.snapshot_consistency = false;
// Fails with overlap on earlier level
Status s = db_->IngestExternalFile(handles_[1], {file_path}, ifo);
ASSERT_EQ(s.code(), Status::Code::kTryAgain);
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(db_->CompactRange(cro, handles_[1], nullptr, nullptr));
// Fails with overlap on last level
s = db_->IngestExternalFile(handles_[1], {file_path}, ifo);
ASSERT_EQ(s.code(), Status::Code::kTryAgain);
// No change to data
ASSERT_EQ(Get(1, "a"), "1");
ASSERT_EQ(Get(1, "b"), "2");
ASSERT_EQ(Get(1, "c"), "3");
ASSERT_EQ(Get(1, "d"), "4");
}
if (!disallow_memtable) {
// Test allow_blocking_flush=false (fail because of memtable overlap)
IngestExternalFileOptions ifo;
ifo.fail_if_not_bottommost_level = true;
const Status s = db_->IngestExternalFile(handles_[1], {file_path}, ifo);
ASSERT_TRUE(s.IsTryAgain());
ASSERT_TRUE(ifo.allow_blocking_flush);
ifo.allow_blocking_flush = false;
ASSERT_OK(Put(1, "b", "42"));
Status s = db_->IngestExternalFile(handles_[1], {file_path}, ifo);
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
ASSERT_EQ(Get(1, "a"), "1");
ASSERT_EQ(Get(1, "b"), "42");
ASSERT_EQ(Get(1, "c"), "3");
ASSERT_EQ(Get(1, "d"), "4");
// Revert state
ASSERT_OK(Put(1, "b", "2"));
ASSERT_OK(Flush(1));
}
{
// Test atomic_replace_range
IngestExternalFileArg arg;
arg.column_family = handles_[1];
arg.external_files = {file_path};
arg.atomic_replace_range = {{"a", "zzz"}};
// start with some failure cases
// TODO: support snapshot consistency with tombstone file
ASSERT_TRUE(arg.options.snapshot_consistency);
Status s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kNotSupported);
ASSERT_EQ(Get(1, "a"), "1");
ASSERT_EQ(Get(1, "b"), "2");
ASSERT_EQ(Get(1, "c"), "3");
ASSERT_EQ(Get(1, "d"), "4");
arg.options.snapshot_consistency = false;
// Can usually be used with atomic_replace_range and
// snapshot_consistency=false, except it requires no input overlap
arg.options.fail_if_not_bottommost_level = true;
// one-sided ranges not yet supported
arg.atomic_replace_range = {{{}, "zzz"}};
s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kNotSupported);
arg.atomic_replace_range = {{"a", {}}};
s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kNotSupported);
// rejected because doesn't cover ingested file
arg.atomic_replace_range = {{"x", "z"}};
s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
// rejected because of partial file overlap
arg.atomic_replace_range = {{"a", "c"}};
s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
if (!disallow_memtable) {
// memtable overlap with replace range
ASSERT_OK(Put(1, "e", "5"));
arg.options.allow_blocking_flush = false;
// rejected because of memtable overlap
arg.atomic_replace_range = {{"a", "z"}};
s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
// rejected because of memtable overlap
arg.atomic_replace_range = {{nullptr, nullptr}};
s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
// FIXME: upper bound should be exclusive (DeleteRange semantics).
// currently rejected because of documented bug
arg.atomic_replace_range = {{"a", "e"}};
s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
// work-around ensuring no memtable overlap
arg.atomic_replace_range = {{"a", "d2"}};
ASSERT_OK(db_->IngestExternalFiles({arg}));
ASSERT_EQ(Get(1, "e"), "5");
} else {
// rejected because of partial file overlap
arg.atomic_replace_range = {{"b", "z"}};
s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
// no memtable complications
arg.atomic_replace_range = {{"a", "z"}};
ASSERT_OK(db_->IngestExternalFiles({arg}));
ASSERT_EQ(Get(1, "e"), "NOT_FOUND");
}
ASSERT_EQ(Get(1, "a"), "NOT_FOUND");
ASSERT_EQ(Get(1, "b"), "0");
ASSERT_EQ(Get(1, "c"), "NOT_FOUND");
ASSERT_EQ(Get(1, "d"), "NOT_FOUND");
// The single ingested file replaced everything (except perhaps memtable)
std::vector<LiveFileMetaData> live_files;
db_->GetLiveFilesMetaData(&live_files);
// One file in each CF
ASSERT_EQ(live_files.size(), 2);
ASSERT_OK(sfw.Open(file_path));
ASSERT_OK(sfw.Put("f", "6"));
ASSERT_OK(sfw.Finish());
// Another file
ASSERT_OK(sfw.Open(file_path2));
ASSERT_OK(sfw.Put("f", "7"));
ASSERT_OK(sfw.Put("g", "8"));
ASSERT_OK(sfw.Finish());
if (!disallow_memtable) {
// rejected because of memtable overlap with range
arg.atomic_replace_range = {{"e", "z"}};
s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
// allow blocking flush of "e" (which is then replaced), and the file
// with just "b" is not replaced
arg.options.allow_blocking_flush = true;
ASSERT_OK(db_->IngestExternalFiles({arg}));
ASSERT_EQ(Get(1, "b"), "0");
ASSERT_EQ(Get(1, "e"), "NOT_FOUND");
ASSERT_EQ(Get(1, "f"), "6");
ASSERT_EQ(Get(1, "g"), "NOT_FOUND");
// memtable overlap with replace range
ASSERT_OK(Put(1, "e", "5"));
arg.options.allow_blocking_flush = false;
arg.external_files = {file_path2};
// rejected because of memtable overlap
arg.atomic_replace_range = {{nullptr, nullptr}};
s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
// Replace everything, including with memtable flush
arg.options.allow_blocking_flush = true;
ASSERT_OK(db_->IngestExternalFiles({arg}));
ASSERT_EQ(Get(1, "b"), "NOT_FOUND");
ASSERT_EQ(Get(1, "e"), "NOT_FOUND");
ASSERT_EQ(Get(1, "f"), "7");
ASSERT_EQ(Get(1, "g"), "8");
} else {
arg.external_files = {file_path2, file_path};
// rejected because of overlap in files to ingest with fail_if_ = true
arg.atomic_replace_range = {{"e", "z"}};
s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kTryAgain);
arg.options.fail_if_not_bottommost_level = false;
// rejected because range doesn't cover ingested files
// FIXME: upper bound should be exclusive "g" instead
arg.atomic_replace_range = {{"e", "f2"}};
s = db_->IngestExternalFiles({arg});
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
// Loaded into different levels, and the file with just "b" is not
// replaced
arg.atomic_replace_range = {{"e", "z"}};
ASSERT_OK(db_->IngestExternalFiles({arg}));
ASSERT_EQ(Get(1, "b"), "0");
ASSERT_EQ(Get(1, "f"), "6"); // earlier file listed later to ingest
ASSERT_EQ(Get(1, "g"), "8");
}
}
}
}
+112 -14
View File
@@ -29,6 +29,7 @@ Status ExternalSstFileIngestionJob::Prepare(
const std::vector<std::string>& external_files_paths,
const std::vector<std::string>& files_checksums,
const std::vector<std::string>& files_checksum_func_names,
const std::optional<RangeOpt>& atomic_replace_range,
const Temperature& file_temperature, uint64_t next_file_number,
SuperVersion* sv) {
Status status;
@@ -80,15 +81,47 @@ Status ExternalSstFileIngestionJob::Prepare(
std::sort(sorted_files.begin(), sorted_files.end(), file_range_checker_);
for (size_t i = 0; i + 1 < num_files; i++) {
if (file_range_checker_.OverlapsWithPrev(sorted_files[i],
sorted_files[i + 1],
/* ranges_sorted= */ true)) {
if (file_range_checker_.Overlaps(*sorted_files[i], *sorted_files[i + 1],
/* known_sorted= */ true)) {
files_overlap_ = true;
break;
}
}
}
if (atomic_replace_range.has_value()) {
atomic_replace_range_.emplace();
if (atomic_replace_range->start && atomic_replace_range->limit) {
// User keys to internal keys (with timestamps)
const size_t ts_sz = ucmp_->timestamp_size();
std::string start_with_ts, limit_with_ts;
auto [start, limit] = MaybeAddTimestampsToRange(
atomic_replace_range->start, atomic_replace_range->limit, ts_sz,
&start_with_ts, &limit_with_ts);
assert(start.has_value());
assert(limit.has_value());
atomic_replace_range_->smallest_internal_key.Set(
*start, kMaxSequenceNumber, kValueTypeForSeek);
atomic_replace_range_->largest_internal_key.Set(
*limit, kMaxSequenceNumber, kValueTypeForSeek);
// Check files to ingest against replace range
for (size_t i = 0; i < num_files; i++) {
if (!file_range_checker_.Contains(*atomic_replace_range_,
files_to_ingest_[i])) {
return Status::InvalidArgument(
"Atomic replace range does not contain all files");
}
}
} else {
// Currently if either bound is not present, both must be
assert(atomic_replace_range->start.has_value() == false);
assert(atomic_replace_range->limit.has_value() == false);
assert(atomic_replace_range_->smallest_internal_key.unset());
assert(atomic_replace_range_->largest_internal_key.unset());
}
}
if (ingestion_options_.ingest_behind && files_overlap_) {
return Status::NotSupported(
"Files with overlapping ranges cannot be ingested with ingestion "
@@ -359,9 +392,9 @@ void ExternalSstFileIngestionJob::DivideInputFilesIntoBatches() {
file_batches_to_ingest_.emplace_back(/* _track_batch_range= */ true);
for (auto& file : files_to_ingest_) {
if (file_range_checker_.OverlapsWithPrev(&file_batches_to_ingest_.back(),
&file,
/* ranges_sorted= */ false)) {
if (!file_batches_to_ingest_.back().unset() &&
file_range_checker_.Overlaps(file_batches_to_ingest_.back(), file,
/* known_sorted= */ false)) {
file_batches_to_ingest_.emplace_back(/* _track_batch_range= */ true);
}
file_batches_to_ingest_.back().AddFile(&file, file_range_checker_);
@@ -370,14 +403,32 @@ void ExternalSstFileIngestionJob::DivideInputFilesIntoBatches() {
Status ExternalSstFileIngestionJob::NeedsFlush(bool* flush_needed,
SuperVersion* super_version) {
size_t n = files_to_ingest_.size();
autovector<UserKeyRange> ranges;
ranges.reserve(n);
for (const IngestedFileInfo& file_to_ingest : files_to_ingest_) {
ranges.emplace_back(file_to_ingest.start_ukey, file_to_ingest.limit_ukey);
Status status;
if (atomic_replace_range_.has_value() && atomic_replace_range_->unset()) {
// For replacing whole CF, we can simply check whether memtable is empty
*flush_needed = !super_version->mem->IsEmpty();
} else {
autovector<UserKeyRange> ranges;
if (atomic_replace_range_.has_value()) {
assert(!atomic_replace_range_->smallest_internal_key.unset());
assert(!atomic_replace_range_->largest_internal_key.unset());
// NOTE: we already checked in Prepare() that the atomic_replace_range
// covers all the files_to_ingest
// FIXME: need to make upper bound key exclusive (not easy here because
// the existing internal APIs deal in inclusive upper bound user keys)
ranges.emplace_back(
atomic_replace_range_->smallest_internal_key.user_key(),
atomic_replace_range_->largest_internal_key.user_key());
} else {
ranges.reserve(files_to_ingest_.size());
for (const IngestedFileInfo& file_to_ingest : files_to_ingest_) {
ranges.emplace_back(file_to_ingest.start_ukey,
file_to_ingest.limit_ukey);
}
}
status = cfd_->RangesOverlapWithMemtables(
ranges, super_version, db_options_.allow_data_in_errors, flush_needed);
}
Status status = cfd_->RangesOverlapWithMemtables(
ranges, super_version, db_options_.allow_data_in_errors, flush_needed);
if (status.ok() && *flush_needed) {
if (!ingestion_options_.allow_blocking_flush) {
status = Status::InvalidArgument("External file requires flush");
@@ -430,8 +481,49 @@ Status ExternalSstFileIngestionJob::Run() {
// the only active writer, and hence they are equal
SequenceNumber last_seqno = versions_->LastSequence();
edit_.SetColumnFamily(cfd_->GetID());
// The levels that the files will be ingested into
if (atomic_replace_range_.has_value()) {
auto* vstorage = super_version->current->storage_info();
if (atomic_replace_range_->unset()) {
if (cfd_->compaction_picker()->IsCompactionInProgress()) {
return Status::InvalidArgument(
"Atomic replace range (full) overlaps with pending compaction");
}
for (int lvl = 0; lvl < cfd_->NumberLevels(); lvl++) {
for (auto file : vstorage->LevelFiles(lvl)) {
// Set up to delete file to be replaced
edit_.DeleteFile(lvl, file->fd.GetNumber());
}
}
} else {
assert(!atomic_replace_range_->smallest_internal_key.unset());
assert(!atomic_replace_range_->largest_internal_key.unset());
for (int lvl = 0; lvl < cfd_->NumberLevels(); lvl++) {
if (cfd_->RangeOverlapWithCompaction(
atomic_replace_range_->smallest_internal_key.user_key(),
atomic_replace_range_->largest_internal_key.user_key(), lvl)) {
return Status::InvalidArgument(
"Atomic replace range overlaps with pending compaction");
}
for (auto file : vstorage->LevelFiles(lvl)) {
if (file_range_checker_.Overlaps(*atomic_replace_range_,
file->smallest, file->largest)) {
if (file_range_checker_.Contains(*atomic_replace_range_,
file->smallest, file->largest)) {
// Set up to delete file to be replaced
edit_.DeleteFile(lvl, file->fd.GetNumber());
} else {
// TODO: generate and ingest a tombstone file also
return Status::InvalidArgument(
"Atomic replace range partially overlaps with existing file");
}
}
}
}
}
}
// Find levels to ingest into
std::optional<int> prev_batch_uppermost_level;
for (auto& batch : file_batches_to_ingest_) {
int batch_uppermost_level = 0;
@@ -1104,6 +1196,10 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
if (lvl > 0 && lvl < vstorage->base_level()) {
continue;
}
if (atomic_replace_range_.has_value()) {
target_level = lvl;
continue;
}
if (cfd_->RangeOverlapWithCompaction(file_to_ingest->start_ukey,
file_to_ingest->limit_ukey, lvl)) {
// We must use L0 or any level higher than `lvl` to be able to overwrite
@@ -1172,6 +1268,8 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
Status ExternalSstFileIngestionJob::CheckLevelForIngestedBehindFile(
IngestedFileInfo* file_to_ingest) {
assert(!atomic_replace_range_.has_value());
auto* vstorage = cfd_->current()->storage_info();
// First, check if new files fit in the last level
int last_lvl = cfd_->NumberLevels() - 1;
+54 -25
View File
@@ -27,50 +27,77 @@ class SystemClock;
struct KeyRangeInfo {
// Smallest internal key in an external file or for a batch of external files.
// unset() could be either invalid or "before all keys"
InternalKey smallest_internal_key;
// Largest internal key in an external file or for a batch of external files.
// unset() could be either invalid or "after all keys"
InternalKey largest_internal_key;
bool empty() const {
return smallest_internal_key.size() == 0 &&
largest_internal_key.size() == 0;
bool unset() const {
// Legal internal keys are at least 8 bytes.
return smallest_internal_key.unset() || largest_internal_key.unset();
}
};
// Helper class to apply SST file key range checks to the external files.
// XXX: using sstableKeyCompare with user comparator on internal keys is
// very broken
class ExternalFileRangeChecker {
public:
explicit ExternalFileRangeChecker(const Comparator* ucmp) : ucmp_(ucmp) {}
// Operator used for sorting ranges.
bool operator()(const KeyRangeInfo* prev_range,
const KeyRangeInfo* range) const {
assert(prev_range);
assert(range);
return sstableKeyCompare(ucmp_, prev_range->smallest_internal_key,
range->smallest_internal_key) < 0;
bool operator()(const KeyRangeInfo* range1,
const KeyRangeInfo* range2) const {
assert(range1);
assert(range2);
assert(!range1->unset());
assert(!range2->unset());
return sstableKeyCompare(ucmp_, range1->smallest_internal_key,
range2->smallest_internal_key) < 0;
}
// Check whether `range` overlaps with `prev_range`. `ranges_sorted` can be
// set to true when the inputs are already sorted based on the sorting logic
// provided by this checker's operator(), which can help simplify the check.
bool OverlapsWithPrev(const KeyRangeInfo* prev_range,
const KeyRangeInfo* range,
bool ranges_sorted = false) const {
assert(prev_range);
assert(range);
if (prev_range->empty() || range->empty()) {
bool Overlaps(const KeyRangeInfo& range1, const KeyRangeInfo& range2,
bool known_sorted = false) const {
return Overlaps(range1, range2.smallest_internal_key,
range2.largest_internal_key, known_sorted);
}
bool Overlaps(const KeyRangeInfo& range1, const InternalKey& range2_smallest,
const InternalKey& range2_largest,
bool known_sorted = false) const {
bool any_unset =
range1.unset() || range2_smallest.unset() || range2_largest.unset();
if (any_unset) {
assert(!any_unset);
return false;
}
if (ranges_sorted) {
return sstableKeyCompare(ucmp_, prev_range->largest_internal_key,
range->smallest_internal_key) >= 0;
if (known_sorted) {
return sstableKeyCompare(ucmp_, range1.largest_internal_key,
range2_smallest) >= 0;
}
return sstableKeyCompare(ucmp_, prev_range->largest_internal_key,
range->smallest_internal_key) >= 0 &&
sstableKeyCompare(ucmp_, prev_range->smallest_internal_key,
range->largest_internal_key) <= 0;
return sstableKeyCompare(ucmp_, range1.largest_internal_key,
range2_smallest) >= 0 &&
sstableKeyCompare(ucmp_, range1.smallest_internal_key,
range2_largest) <= 0;
}
bool Contains(const KeyRangeInfo& range1, const KeyRangeInfo& range2) {
return Contains(range1, range2.smallest_internal_key,
range2.largest_internal_key);
}
bool Contains(const KeyRangeInfo& range1, const InternalKey& range2_smallest,
const InternalKey& range2_largest) {
bool any_unset =
range1.unset() || range2_smallest.unset() || range2_largest.unset();
if (any_unset) {
assert(!any_unset);
return false;
}
return sstableKeyCompare(ucmp_, range1.smallest_internal_key,
range2_smallest) <= 0 &&
sstableKeyCompare(ucmp_, range1.largest_internal_key,
range2_largest) >= 0;
}
void MaybeUpdateRange(const InternalKey& start_key,
@@ -218,6 +245,7 @@ class ExternalSstFileIngestionJob {
Status Prepare(const std::vector<std::string>& external_files_paths,
const std::vector<std::string>& files_checksums,
const std::vector<std::string>& files_checksum_func_names,
const std::optional<RangeOpt>& atomic_replace_range,
const Temperature& file_temperature, uint64_t next_file_number,
SuperVersion* sv);
@@ -362,6 +390,7 @@ class ExternalSstFileIngestionJob {
autovector<IngestedFileInfo> files_to_ingest_;
std::vector<FileBatchInfo> file_batches_to_ingest_;
const IngestExternalFileOptions& ingestion_options_;
std::optional<KeyRangeInfo> atomic_replace_range_;
Directories* directories_;
EventLogger* event_logger_;
VersionEdit edit_;
+1 -3
View File
@@ -3915,9 +3915,7 @@ TEST_P(IngestDBGeneratedFileTest, FailureCase) {
s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
ASSERT_TRUE(s.ToString().find(err) != std::string::npos);
ASSERT_NOK(s);
if (options.compaction_style != kCompactionStyleUniversal) {
// FIXME: after fixing ingestion with universal compaction, currently
// will always ingest into L0.
if (options.num_levels > 1) {
ingest_opts.fail_if_not_bottommost_level = true;
s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
ASSERT_NOK(s);
+2 -1
View File
@@ -870,7 +870,8 @@ Status FlushJob::WriteLevel0Table() {
std::vector<BlobFileAddition> blob_file_additions;
{
auto write_hint = base_->storage_info()->CalculateSSTWriteHint(/*level=*/0);
auto write_hint = base_->storage_info()->CalculateSSTWriteHint(
/*level=*/0, db_options_.calculate_sst_write_lifetime_hint_set);
Env::IOPriority io_priority = GetRateLimiterPriority();
db_mutex_->Unlock();
if (log_buffer_) {
+1 -1
View File
@@ -234,7 +234,7 @@ class FlushJob {
// The current minimum seqno that compaction jobs will preclude the data from
// the last level. Data with seqnos larger than this or larger than
// `earliest_snapshot_` will be output to the penultimate level had it gone
// `earliest_snapshot_` will be output to the proximal level had it gone
// through a compaction to the last level.
SequenceNumber preclude_last_level_min_seqno_ = kMaxSequenceNumber;
};
+26 -51
View File
@@ -153,23 +153,6 @@ class InternalStats {
InternalStats(int num_levels, SystemClock* clock, ColumnFamilyData* cfd);
// Per level compaction stats
struct CompactionOutputsStats {
uint64_t num_output_records = 0;
uint64_t bytes_written = 0;
uint64_t bytes_written_blob = 0;
uint64_t num_output_files = 0;
uint64_t num_output_files_blob = 0;
void Add(const CompactionOutputsStats& stats) {
this->num_output_records += stats.num_output_records;
this->bytes_written += stats.bytes_written;
this->bytes_written_blob += stats.bytes_written_blob;
this->num_output_files += stats.num_output_files;
this->num_output_files_blob += stats.num_output_files_blob;
}
};
// Per level compaction stats. comp_stats_[level] stores the stats for
// compactions that produced data for the specified "level".
struct CompactionStats {
@@ -420,15 +403,6 @@ class InternalStats {
}
}
void Add(const CompactionOutputsStats& stats) {
this->num_output_files += static_cast<int>(stats.num_output_files);
this->num_output_records += stats.num_output_records;
this->bytes_written += stats.bytes_written;
this->bytes_written_blob += stats.bytes_written_blob;
this->num_output_files_blob +=
static_cast<int>(stats.num_output_files_blob);
}
void Subtract(const CompactionStats& c) {
this->micros -= c.micros;
this->cpu_micros -= c.cpu_micros;
@@ -473,49 +447,51 @@ class InternalStats {
}
};
// Compaction stats, for per_key_placement compaction, it includes 2 levels
// stats: the last level and the penultimate level.
// Compaction internal stats, for per_key_placement compaction, it includes 2
// output level stats: the last level and the proximal level.
struct CompactionStatsFull {
// the stats for the target primary output level
CompactionStats stats;
CompactionStats output_level_stats;
// stats for penultimate level output if exist
bool has_penultimate_level_output = false;
CompactionStats penultimate_level_stats;
// stats for proximal level output if exist
bool has_proximal_level_output = false;
CompactionStats proximal_level_stats;
explicit CompactionStatsFull() : stats(), penultimate_level_stats() {}
explicit CompactionStatsFull()
: output_level_stats(), proximal_level_stats() {}
explicit CompactionStatsFull(CompactionReason reason, int c)
: stats(reason, c), penultimate_level_stats(reason, c) {}
: output_level_stats(reason, c), proximal_level_stats(reason, c) {}
uint64_t TotalBytesWritten() const {
uint64_t bytes_written = stats.bytes_written + stats.bytes_written_blob;
if (has_penultimate_level_output) {
bytes_written += penultimate_level_stats.bytes_written +
penultimate_level_stats.bytes_written_blob;
uint64_t bytes_written = output_level_stats.bytes_written +
output_level_stats.bytes_written_blob;
if (has_proximal_level_output) {
bytes_written += proximal_level_stats.bytes_written +
proximal_level_stats.bytes_written_blob;
}
return bytes_written;
}
uint64_t DroppedRecords() {
uint64_t output_records = stats.num_output_records;
if (has_penultimate_level_output) {
output_records += penultimate_level_stats.num_output_records;
uint64_t output_records = output_level_stats.num_output_records;
if (has_proximal_level_output) {
output_records += proximal_level_stats.num_output_records;
}
if (stats.num_input_records > output_records) {
return stats.num_input_records - output_records;
if (output_level_stats.num_input_records > output_records) {
return output_level_stats.num_input_records - output_records;
}
return 0;
}
void SetMicros(uint64_t val) {
stats.micros = val;
penultimate_level_stats.micros = val;
output_level_stats.micros = val;
proximal_level_stats.micros = val;
}
void AddCpuMicros(uint64_t val) {
stats.cpu_micros += val;
penultimate_level_stats.cpu_micros += val;
output_level_stats.cpu_micros += val;
proximal_level_stats.cpu_micros += val;
}
};
@@ -587,10 +563,9 @@ class InternalStats {
void AddCompactionStats(int level, Env::Priority thread_pri,
const CompactionStatsFull& comp_stats_full) {
AddCompactionStats(level, thread_pri, comp_stats_full.stats);
if (comp_stats_full.has_penultimate_level_output) {
per_key_placement_comp_stats_.Add(
comp_stats_full.penultimate_level_stats);
AddCompactionStats(level, thread_pri, comp_stats_full.output_level_stats);
if (comp_stats_full.has_proximal_level_output) {
per_key_placement_comp_stats_.Add(comp_stats_full.proximal_level_stats);
}
}
+9 -6
View File
@@ -100,13 +100,15 @@ class Repairer {
db_options_(SanitizeOptions(dbname_, db_options)),
immutable_db_options_(ImmutableDBOptions(db_options_)),
icmp_(default_cf_opts.comparator),
default_cf_opts_(SanitizeOptions(immutable_db_options_,
/*read_only*/ false, default_cf_opts)),
default_cf_opts_(SanitizeCfOptions(immutable_db_options_,
/*read_only*/ false,
default_cf_opts)),
default_iopts_(
ImmutableOptions(immutable_db_options_, default_cf_opts_)),
default_mopts_(MutableCFOptions(default_cf_opts_)),
unknown_cf_opts_(SanitizeOptions(immutable_db_options_,
/*read_only*/ false, unknown_cf_opts)),
unknown_cf_opts_(SanitizeCfOptions(immutable_db_options_,
/*read_only*/ false,
unknown_cf_opts)),
create_unknown_cfs_(create_unknown_cfs),
raw_table_cache_(
// TableCache can be small since we expect each table to be opened
@@ -456,8 +458,9 @@ class Repairer {
meta.file_creation_time = current_time;
SnapshotChecker* snapshot_checker = DisableGCSnapshotChecker::Instance();
auto write_hint =
cfd->current()->storage_info()->CalculateSSTWriteHint(/*level=*/0);
auto write_hint = cfd->current()->storage_info()->CalculateSSTWriteHint(
/*level=*/0, db_options_.calculate_sst_write_lifetime_hint_set);
std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
range_del_iters;
auto range_del_iter = mem->NewRangeTombstoneIterator(
+5 -6
View File
@@ -96,7 +96,7 @@ TEST_F(SeqnoTimeTest, TemperatureBasicUniversal) {
}
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// All data is hot, only output to penultimate level
// All data is hot, only output to proximal level
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
@@ -185,7 +185,7 @@ TEST_F(SeqnoTimeTest, TemperatureBasicLevel) {
options.num_levels = kNumLevels;
options.level_compaction_dynamic_level_bytes = true;
// TODO(zjay): for level compaction, auto-compaction may stuck in deadloop, if
// the penultimate level score > 1, but the hot is not cold enough to compact
// the proximal level score > 1, but the hot is not cold enough to compact
// to last level, which will keep triggering compaction.
options.disable_auto_compactions = true;
DestroyAndReopen(options);
@@ -205,7 +205,7 @@ TEST_F(SeqnoTimeTest, TemperatureBasicLevel) {
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
// All data is hot, only output to penultimate level
// All data is hot, only output to proximal level
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
@@ -753,7 +753,7 @@ TEST_P(SeqnoTimeTablePropTest, SeqnoToTimeMappingUniversal) {
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
// make sure the data is all compacted to penultimate level if the feature is
// make sure the data is all compacted to proximal level if the feature is
// on, otherwise, compacted to the last level.
if (options.preclude_last_level_data_seconds > 0) {
ASSERT_GT(NumTableFilesAtLevel(5), 0);
@@ -792,8 +792,7 @@ TEST_P(SeqnoTimeTablePropTest, SeqnoToTimeMappingUniversal) {
}
ASSERT_GT(num_seqno_zeroing, 0);
std::vector<KeyVersion> key_versions;
ASSERT_OK(GetAllKeyVersions(db_, Slice(), Slice(),
std::numeric_limits<size_t>::max(),
ASSERT_OK(GetAllKeyVersions(db_, {}, {}, std::numeric_limits<size_t>::max(),
&key_versions));
// make sure there're more than 300 keys and first 100 keys are having seqno
// zeroed out, the last 100 key seqno not zeroed out
+1
View File
@@ -205,6 +205,7 @@ Status TableCache::FindTable(
RecordTick(ioptions_.stats, NO_FILE_ERRORS);
// We do not cache error results so that if the error is transient,
// or somebody repairs the file, we recover automatically.
IGNORE_STATUS_IF_ERROR(s);
} else {
s = cache_.Insert(key, table_reader.get(), 1, handle);
if (s.ok()) {
+1 -1
View File
@@ -408,7 +408,7 @@ void VersionEditHandler::CheckIterationResult(const log::Reader& reader,
if (cfd->IsDropped()) {
continue;
}
if (read_only_) {
if (version_set_->unchanging()) {
cfd->table_cache()->SetTablesAreImmortal();
}
*s = LoadTables(cfd, /*prefetch_index_and_filter_in_cache=*/false,
+7 -5
View File
@@ -198,7 +198,9 @@ class VersionEditHandler : public VersionEditHandlerBase {
bool prefetch_index_and_filter_in_cache,
bool is_initial_load);
virtual bool MustOpenAllColumnFamilies() const { return !read_only_; }
virtual bool MustOpenAllColumnFamilies() const {
return !version_set_->unchanging();
}
const bool read_only_;
std::vector<ColumnFamilyDescriptor> column_families_;
@@ -334,10 +336,10 @@ class ManifestTailer : public VersionEditHandlerPointInTime {
const ReadOptions& read_options,
EpochNumberRequirement epoch_number_requirement =
EpochNumberRequirement::kMustPresent)
: VersionEditHandlerPointInTime(/*read_only=*/false, column_families,
version_set, io_tracer, read_options,
/*allow_incomplete_valid_version=*/false,
epoch_number_requirement),
: VersionEditHandlerPointInTime(
/*read_only=*/true, column_families, version_set, io_tracer,
read_options,
/*allow_incomplete_valid_version=*/false, epoch_number_requirement),
mode_(Mode::kRecovery) {}
Status VerifyFile(ColumnFamilyData* cfd, const std::string& fpath, int level,
+56 -22
View File
@@ -1627,8 +1627,8 @@ Status Version::GetTableProperties(const ReadOptions& read_options,
return s;
}
Status Version::GetPropertiesOfAllTables(const ReadOptions& read_options,
TablePropertiesCollection* props) {
Status Version::GetPropertiesOfAllTables(
const ReadOptions& read_options, TablePropertiesCollection* props) const {
Status s;
for (int level = 0; level < storage_info_.num_levels_; level++) {
s = GetPropertiesOfAllTables(read_options, props, level);
@@ -1699,7 +1699,7 @@ Status Version::TablesRangeTombstoneSummary(int max_entries_to_print,
Status Version::GetPropertiesOfAllTables(const ReadOptions& read_options,
TablePropertiesCollection* props,
int level) {
int level) const {
for (const auto& file_meta : storage_info_.files_[level]) {
auto fname =
TableFileName(cfd_->ioptions().cf_paths, file_meta->fd.GetNumber(),
@@ -1753,6 +1753,24 @@ Status Version::GetPropertiesOfTablesInRange(
return Status::OK();
}
Status Version::GetPropertiesOfTablesByLevel(
const ReadOptions& read_options,
std::vector<std::unique_ptr<TablePropertiesCollection>>* props_by_level)
const {
Status s;
props_by_level->reserve(storage_info_.num_levels_);
for (int level = 0; level < storage_info_.num_levels_; level++) {
props_by_level->push_back(std::make_unique<TablePropertiesCollection>());
s = GetPropertiesOfAllTables(read_options, props_by_level->back().get(),
level);
if (!s.ok()) {
return s;
}
}
return Status::OK();
}
Status Version::GetAggregatedTableProperties(
const ReadOptions& read_options, std::shared_ptr<const TableProperties>* tp,
int level) {
@@ -4761,7 +4779,7 @@ void VersionStorageInfo::CalculateBaseBytes(const ImmutableOptions& ioptions,
cur_level_size <= base_bytes_min &&
(options.preclude_last_level_data_seconds == 0 ||
i < num_levels_ - 2)) {
// When per_key_placement is enabled, the penultimate level is
// When per_key_placement is enabled, the proximal level is
// necessary.
lowest_unnecessary_level_ = i;
}
@@ -4903,24 +4921,38 @@ bool VersionStorageInfo::RangeMightExistAfterSortedRun(
}
Env::WriteLifeTimeHint VersionStorageInfo::CalculateSSTWriteHint(
int level) const {
if (compaction_style_ != kCompactionStyleLevel) {
int level, CompactionStyleSet compaction_style_set) const {
if (!compaction_style_set.Contains(compaction_style_)) {
return Env::WLTH_NOT_SET;
}
if (level == 0) {
return Env::WLTH_MEDIUM;
}
// L1: medium, L2: long, ...
if (level - base_level_ >= 2) {
return Env::WLTH_EXTREME;
} else if (level < base_level_) {
// There is no restriction which prevents level passed in to be smaller
// than base_level.
return Env::WLTH_MEDIUM;
switch (compaction_style_) {
case kCompactionStyleLevel:
if (level == 0) {
return Env::WLTH_MEDIUM;
}
// L1: medium, L2: long, ...
if (level - base_level_ >= 2) {
return Env::WLTH_EXTREME;
} else if (level < base_level_) {
// There is no restriction which prevents level passed in to be smaller
// than base_level.
return Env::WLTH_MEDIUM;
}
return static_cast<Env::WriteLifeTimeHint>(
level - base_level_ + static_cast<int>(Env::WLTH_MEDIUM));
case kCompactionStyleUniversal:
if (level == 0) {
return Env::WLTH_SHORT;
}
if (level == 1) {
return Env::WLTH_MEDIUM;
}
return Env::WLTH_LONG;
default:
return Env::WLTH_NOT_SET;
}
return static_cast<Env::WriteLifeTimeHint>(
level - base_level_ + static_cast<int>(Env::WLTH_MEDIUM));
}
void Version::AddLiveFiles(std::vector<uint64_t>* live_table_files,
@@ -5114,7 +5146,7 @@ VersionSet::VersionSet(
BlockCacheTracer* const block_cache_tracer,
const std::shared_ptr<IOTracer>& io_tracer, const std::string& db_id,
const std::string& db_session_id, const std::string& daily_offpeak_time_utc,
ErrorHandler* const error_handler, const bool read_only)
ErrorHandler* error_handler, bool unchanging)
: column_family_set_(new ColumnFamilySet(
dbname, _db_options, storage_options, table_cache,
write_buffer_manager, write_controller, block_cache_tracer, io_tracer,
@@ -5143,12 +5175,12 @@ VersionSet::VersionSet(
db_session_id_(db_session_id),
offpeak_time_option_(OffpeakTimeOption(daily_offpeak_time_utc)),
error_handler_(error_handler),
read_only_(read_only),
unchanging_(unchanging),
closed_(false) {}
Status VersionSet::Close(FSDirectory* db_dir, InstrumentedMutex* mu) {
Status s;
if (closed_ || read_only_ || !manifest_file_number_ || !descriptor_log_) {
if (closed_ || unchanging_ || !manifest_file_number_ || !descriptor_log_) {
return s;
}
@@ -7297,6 +7329,8 @@ ColumnFamilyData* VersionSet::CreateColumnFamily(
const ColumnFamilyOptions& cf_options, const ReadOptions& read_options,
const VersionEdit* edit, bool read_only) {
assert(edit->IsColumnFamilyAdd());
// Unchanging LSM tree implies no writes to the CF
assert(!unchanging_ || read_only);
MutableCFOptions dummy_cf_options;
Version* dummy_versions =
@@ -7430,7 +7464,7 @@ ReactiveVersionSet::ReactiveVersionSet(
write_buffer_manager, write_controller,
/*block_cache_tracer=*/nullptr, io_tracer, /*db_id*/ "",
/*db_session_id*/ "", /*daily_offpeak_time_utc*/ "",
/*error_handler=*/nullptr, /*read_only=*/true) {}
/*error_handler=*/nullptr, /*unchanging=*/false) {}
ReactiveVersionSet::~ReactiveVersionSet() = default;
+23 -8
View File
@@ -630,7 +630,8 @@ class VersionStorageInfo {
const Slice& largest_user_key,
int last_level, int last_l0_idx);
Env::WriteLifeTimeHint CalculateSSTWriteHint(int level) const;
Env::WriteLifeTimeHint CalculateSSTWriteHint(
int level, CompactionStyleSet compaction_style_set) const;
const Comparator* user_comparator() const { return user_comparator_; }
@@ -993,17 +994,21 @@ class Version {
const FileMetaData* file_meta,
const std::string* fname = nullptr) const;
// REQUIRES: lock is held
// On success, *props will be populated with all SSTables' table properties.
// The keys of `props` are the sst file name, the values of `props` are the
// tables' properties, represented as std::shared_ptr.
Status GetPropertiesOfAllTables(const ReadOptions& read_options,
TablePropertiesCollection* props);
TablePropertiesCollection* props) const;
Status GetPropertiesOfAllTables(const ReadOptions& read_options,
TablePropertiesCollection* props, int level);
TablePropertiesCollection* props,
int level) const;
Status GetPropertiesOfTablesInRange(const ReadOptions& read_options,
const autovector<UserKeyRange>& ranges,
TablePropertiesCollection* props) const;
Status GetPropertiesOfTablesByLevel(
const ReadOptions& read_options,
std::vector<std::unique_ptr<TablePropertiesCollection>>* props_by_level)
const;
// Print summary of range delete tombstones in SST files into out_str,
// with maximum max_entries_to_print entries printed out.
@@ -1174,6 +1179,9 @@ class AtomicGroupReadBuffer {
// VersionSet is the collection of versions of all the column families of the
// database. Each database owns one VersionSet. A VersionSet has access to all
// column families via ColumnFamilySet, i.e. set of the column families.
// `unchanging` means the LSM tree structure of the column families will not
// change during the lifetime of this VersionSet (true for read-only instance,
// but false for secondary instance or writable DB).
class VersionSet {
public:
VersionSet(const std::string& dbname, const ImmutableDBOptions* db_options,
@@ -1184,7 +1192,7 @@ class VersionSet {
const std::shared_ptr<IOTracer>& io_tracer,
const std::string& db_id, const std::string& db_session_id,
const std::string& daily_offpeak_time_utc,
ErrorHandler* const error_handler, const bool read_only);
ErrorHandler* error_handler, bool unchanging);
// No copying allowed
VersionSet(const VersionSet&) = delete;
void operator=(const VersionSet&) = delete;
@@ -1263,8 +1271,11 @@ class VersionSet {
void WakeUpWaitingManifestWriters();
// Recover the last saved descriptor (MANIFEST) from persistent storage.
// If read_only == true, Recover() will not complain if some column families
// are not opened
// Unlike `unchanging` on the VersionSet, `read_only` here and in other
// functions below refers to the CF receiving no writes or modifications
// through this VersionSet, but could through external manifest updates
// etc. Thus, `read_only=true` for secondary instances as well as read-only
// instances.
Status Recover(const std::vector<ColumnFamilyDescriptor>& column_families,
bool read_only = false, std::string* db_id = nullptr,
bool no_error_if_files_missing = false, bool is_retry = false,
@@ -1342,6 +1353,8 @@ class VersionSet {
return min_log_number_to_keep_.load();
}
bool unchanging() const { return unchanging_; }
// Allocate and return a new file number
uint64_t NewFileNumber() { return next_file_number_.fetch_add(1); }
@@ -1573,6 +1586,8 @@ class VersionSet {
AppendVersion(cfd, version);
}
bool& TEST_unchanging() { return const_cast<bool&>(unchanging_); }
protected:
struct ManifestWriter;
@@ -1722,7 +1737,7 @@ class VersionSet {
VersionEdit* edit, SequenceNumber* max_last_sequence,
InstrumentedMutex* mu);
const bool read_only_;
const bool unchanging_;
bool closed_;
};
+11 -8
View File
@@ -26,6 +26,7 @@
#include "test_util/mock_time_env.h"
#include "test_util/testharness.h"
#include "test_util/testutil.h"
#include "util/defer.h"
#include "util/string_util.h"
namespace ROCKSDB_NAMESPACE {
@@ -1905,7 +1906,7 @@ TEST_F(VersionSetTest, WalAddition) {
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*read_only=*/false));
/*error_handler=*/nullptr, /*unchanging=*/false));
ASSERT_OK(new_versions->Recover(column_families_, /*read_only=*/false));
const auto& wals = new_versions->GetWalSet().GetWals();
ASSERT_EQ(wals.size(), 1);
@@ -1973,7 +1974,7 @@ TEST_F(VersionSetTest, WalCloseWithoutSync) {
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*read_only=*/false));
/*error_handler=*/nullptr, /*unchanging=*/false));
ASSERT_OK(new_versions->Recover(column_families_, false));
const auto& wals = new_versions->GetWalSet().GetWals();
ASSERT_EQ(wals.size(), 2);
@@ -2027,7 +2028,7 @@ TEST_F(VersionSetTest, WalDeletion) {
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*read_only=*/false));
/*error_handler=*/nullptr, /*unchanging=*/false));
ASSERT_OK(new_versions->Recover(column_families_, false));
const auto& wals = new_versions->GetWalSet().GetWals();
ASSERT_EQ(wals.size(), 1);
@@ -2066,7 +2067,7 @@ TEST_F(VersionSetTest, WalDeletion) {
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*read_only=*/false));
/*error_handler=*/nullptr, /*unchanging=*/false));
ASSERT_OK(new_versions->Recover(column_families_, false));
const auto& wals = new_versions->GetWalSet().GetWals();
ASSERT_EQ(wals.size(), 1);
@@ -2187,7 +2188,7 @@ TEST_F(VersionSetTest, DeleteWalsBeforeNonExistingWalNumber) {
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*read_only=*/false));
/*error_handler=*/nullptr, /*unchanging=*/false));
ASSERT_OK(new_versions->Recover(column_families_, false));
const auto& wals = new_versions->GetWalSet().GetWals();
ASSERT_EQ(wals.size(), 1);
@@ -2224,7 +2225,7 @@ TEST_F(VersionSetTest, DeleteAllWals) {
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*read_only=*/false));
/*error_handler=*/nullptr, /*unchanging=*/false));
ASSERT_OK(new_versions->Recover(column_families_, false));
const auto& wals = new_versions->GetWalSet().GetWals();
ASSERT_EQ(wals.size(), 0);
@@ -2267,7 +2268,7 @@ TEST_F(VersionSetTest, AtomicGroupWithWalEdits) {
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*read_only=*/false));
/*error_handler=*/nullptr, /*unchanging=*/false));
std::string db_id;
ASSERT_OK(
new_versions->Recover(column_families_, /*read_only=*/false, &db_id));
@@ -2447,7 +2448,7 @@ class VersionSetWithTimestampTest : public VersionSetTest {
&write_buffer_manager_, &write_controller_,
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
/*error_handler=*/nullptr, /*read_only=*/false));
/*error_handler=*/nullptr, /*unchanging=*/false));
ASSERT_OK(vset->Recover(column_families_, /*read_only=*/false,
/*db_id=*/nullptr));
for (auto* cfd : *(vset->GetColumnFamilySet())) {
@@ -3749,6 +3750,8 @@ TEST_P(VersionSetTestEmptyDb, OpenCompleteManifest) {
}
std::string db_id;
bool has_missing_table_file = false;
SaveAndRestore<bool> override_unchanging(&versions_->TEST_unchanging(),
read_only);
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families,
read_only, &db_id,
&has_missing_table_file);
+1
View File
@@ -283,6 +283,7 @@ void WalManager::ArchiveWALFile(const std::string& fname, uint64_t number) {
// The sync point below is used in (DBTest,TransactionLogIteratorRace)
TEST_SYNC_POINT("WalManager::PurgeObsoleteFiles:1");
Status s = env_->RenameFile(fname, archived_log_name);
IGNORE_STATUS_IF_ERROR(s);
// The sync point below is used in (DBTest,TransactionLogIteratorRace)
TEST_SYNC_POINT("WalManager::PurgeObsoleteFiles:2");
// The sync point below is used in
+1 -1
View File
@@ -137,7 +137,7 @@ class SharedState {
for (int i = 0; i < FLAGS_column_families; ++i) {
key_locks_[i].reset(new port::Mutex[num_locks]);
}
if (FLAGS_read_fault_one_in) {
if (FLAGS_read_fault_one_in || FLAGS_metadata_read_fault_one_in) {
#ifdef NDEBUG
// Unsupported in release mode because it relies on
// `IGNORE_STATUS_IF_ERROR` to distinguish faults not expected to lead to
+1 -8
View File
@@ -1600,12 +1600,6 @@ class NonBatchedOpsStressTest : public StressTest {
Slice ub_slice;
ReadOptions ro_copy = read_opts;
// There is a narrow window in iterator auto refresh run where injected read
// errors are simply untraceable, ex. failure to delete file as a part of
// superversion cleanup callback invoked by the DBIter destructor.
bool ignore_injected_read_error_in_iter =
ro_copy.auto_refresh_iterator_with_snapshot;
// Randomly test with `iterate_upper_bound` and `prefix_same_as_start`
//
// Get the next prefix first and then see if we want to set it to be the
@@ -1698,8 +1692,7 @@ class NonBatchedOpsStressTest : public StressTest {
FaultInjectionIOType::kRead),
fault_fs_guard->GetAndResetInjectedThreadLocalErrorCount(
FaultInjectionIOType::kMetadataRead));
if (!ignore_injected_read_error_in_iter &&
!SharedState::ignore_read_error && injected_error_count > 0 &&
if (!SharedState::ignore_read_error && injected_error_count > 0 &&
s.ok()) {
// Grab mutex so multiple thread don't try to print the
// stack trace at the same time
Vendored
+2 -2
View File
@@ -754,8 +754,8 @@ std::string Env::IOActivityToString(IOActivity activity) {
return "GetEntity";
case Env::IOActivity::kMultiGetEntity:
return "MultiGetEntity";
case Env::IOActivity::kReadManifest:
return "ReadManifest";
case Env::IOActivity::kGetFileChecksumsFromCurrentManifest:
return "GetFileChecksumsFromCurrentManifest";
case Env::IOActivity::kUnknown:
return "Unknown";
};
+2
View File
@@ -130,6 +130,7 @@ Status DeleteScheduler::AddFileToDeletionQueue(const std::string& file_path,
s.ToString().c_str());
if (!s.ok()) {
IGNORE_STATUS_IF_ERROR(s);
ROCKS_LOG_ERROR(info_log_, "Failed to mark %s as trash -- %s",
file_path.c_str(), s.ToString().c_str());
s = fs_->DeleteFile(file_path, IOOptions(), nullptr);
@@ -151,6 +152,7 @@ Status DeleteScheduler::AddFileToDeletionQueue(const std::string& file_path,
if (io_s.ok()) {
total_trash_size_.fetch_add(trash_file_size);
}
IGNORE_STATUS_IF_ERROR(s);
}
//**TODO: What should we do if we failed to
// get the file size?
+1 -1
View File
@@ -300,7 +300,7 @@ struct CompressedSecondaryCacheOptions : LRUCacheOptions {
// compress_format_version can have two values:
// compress_format_version == 1 -- decompressed size is not included in the
// block header.
// block header. DEPRECATED
// compress_format_version == 2 -- decompressed size is included in the block
// header in varint32 format.
uint32_t compress_format_version = 2;
+3 -1
View File
@@ -33,6 +33,8 @@ struct CompactionJobStats {
uint64_t num_blobs_read = 0;
// the number of compaction input files (table files)
size_t num_input_files = 0;
// The number of input files that get trivially moved.
size_t num_input_files_trivially_moved = 0;
// the number of compaction input files at the output level (table files)
size_t num_input_files_at_output_level = 0;
// the number of compaction input files that are filtered out by compaction
@@ -118,6 +120,6 @@ struct CompactionJobStats {
// number of single-deletes which meet something other than a put
uint64_t num_single_del_mismatch = 0;
// TODO: Add output_to_penultimate_level output information
// TODO: Add output_to_proximal_level output information
};
} // namespace ROCKSDB_NAMESPACE
+16
View File
@@ -450,6 +450,22 @@ Status DeleteFilesInRange(DB* db, ColumnFamilyHandle* column_family,
// Delete files in multiple ranges at once
// Delete files in a lot of ranges one at a time can be slow, use this API for
// better performance in that case.
Status DeleteFilesInRanges(DB* db, ColumnFamilyHandle* column_family,
const RangeOpt* ranges, size_t n,
bool include_end = true);
// DEPRECATED
struct RangePtr {
// In case of user_defined timestamp, if enabled, `start` and `limit` should
// point to key without timestamp part.
const Slice* start;
const Slice* limit;
RangePtr() : start(nullptr), limit(nullptr) {}
RangePtr(const Slice* s, const Slice* l) : start(s), limit(l) {}
};
// DEPRECATED
Status DeleteFilesInRanges(DB* db, ColumnFamilyHandle* column_family,
const RangePtr* ranges, size_t n,
bool include_end = true);
+5 -45
View File
@@ -96,43 +96,6 @@ class ColumnFamilyHandle {
static const int kMajorVersion = __ROCKSDB_MAJOR__;
static const int kMinorVersion = __ROCKSDB_MINOR__;
// A range of keys
struct Range {
// In case of user_defined timestamp, if enabled, `start` and `limit` should
// point to key without timestamp part.
Slice start;
Slice limit;
Range() {}
Range(const Slice& s, const Slice& l) : start(s), limit(l) {}
};
struct RangePtr {
// In case of user_defined timestamp, if enabled, `start` and `limit` should
// point to key without timestamp part.
const Slice* start;
const Slice* limit;
RangePtr() : start(nullptr), limit(nullptr) {}
RangePtr(const Slice* s, const Slice* l) : start(s), limit(l) {}
};
// It is valid that files_checksums and files_checksum_func_names are both
// empty (no checksum information is provided for ingestion). Otherwise,
// their sizes should be the same as external_files. The file order should
// be the same in three vectors and guaranteed by the caller.
// Note that, we assume the temperatures of this batch of files to be
// ingested are the same.
struct IngestExternalFileArg {
ColumnFamilyHandle* column_family = nullptr;
std::vector<std::string> external_files;
IngestExternalFileOptions options;
std::vector<std::string> files_checksums;
std::vector<std::string> files_checksum_func_names;
// A hint as to the temperature for *reading* the files to be ingested.
Temperature file_temperature = Temperature::kUnknown;
};
struct GetMergeOperandsOptions {
using ContinueCallback = std::function<bool(Slice)>;
@@ -2081,14 +2044,11 @@ class DB {
ColumnFamilyHandle* column_family, const Range* range, std::size_t n,
TablePropertiesCollection* props) = 0;
// Get the table properties of files per level.
virtual Status GetPropertiesOfTablesForLevels(
ColumnFamilyHandle* /* column_family */,
std::vector<
std::unique_ptr<TablePropertiesCollection>>* /* levels_props */) {
return Status::NotSupported(
"GetPropertiesOfTablesForLevels() is not implemented.");
}
// Get the table properties of files by level.
virtual Status GetPropertiesOfTablesByLevel(
ColumnFamilyHandle* column_family,
std::vector<std::unique_ptr<TablePropertiesCollection>>*
props_by_level) = 0;
virtual Status SuggestCompactRange(ColumnFamilyHandle* /*column_family*/,
const Slice* /*begin*/,
+1 -1
View File
@@ -455,7 +455,7 @@ class Env : public Customizable {
kVerifyFileChecksums = 7,
kGetEntity = 8,
kMultiGetEntity = 9,
kReadManifest = 10,
kGetFileChecksumsFromCurrentManifest = 10,
kUnknown, // Keep last for easy array of non-unknowns
};
+8 -1
View File
@@ -192,6 +192,12 @@ struct FileOptions : EnvOptions {
// handoff during file writes.
ChecksumType handoff_checksum_type;
// Expose write lifetime hint on the FileOptions level to provide more
// flexibility in setting the hint in downstream, custom implementations
// that might be able to process the hint only at the time of the actual
// FSWritableFile object creation.
Env::WriteLifeTimeHint write_hint = Env::WLTH_NOT_SET;
FileOptions() : EnvOptions(), handoff_checksum_type(ChecksumType::kCRC32c) {}
FileOptions(const DBOptions& opts)
@@ -206,7 +212,8 @@ struct FileOptions : EnvOptions {
: EnvOptions(opts),
io_options(opts.io_options),
temperature(opts.temperature),
handoff_checksum_type(opts.handoff_checksum_type) {}
handoff_checksum_type(opts.handoff_checksum_type),
write_hint(opts.write_hint) {}
FileOptions& operator=(const FileOptions&) = default;
};
+3
View File
@@ -439,6 +439,9 @@ struct CompactionJobInfo {
// the job id, which is unique in the same thread.
int job_id;
// the number of L0 files in the CF right before and after the compaction
int num_l0_files;
// the smallest input level of the compaction.
int base_input_level;
// the output level of the compaction.
+86 -2
View File
@@ -62,6 +62,8 @@ struct Options;
struct DbPath;
using FileTypeSet = SmallEnumSet<FileType, FileType::kBlobFile>;
using CompactionStyleSet =
SmallEnumSet<CompactionStyle, CompactionStyle::kCompactionStyleNone>;
struct ColumnFamilyOptions : public AdvancedColumnFamilyOptions {
// The function recovers options to a previous version. Only 4.6 or later
@@ -1620,6 +1622,24 @@ struct DBOptions {
// `kUnknown`, this overrides any temperature set by OptimizeForLogWrite
// functions.
Temperature wal_write_temperature = Temperature::kUnknown;
// Enum set indicative of which compaction styles SST write lifetime hint
// calculation is allowed on. Today, RocksDB provides native support for
// kCompactionStyleLevel and kCompactionStyleUniversal (experimental version).
// Other compaction styles, even when enabled in the set, won't have any
// effect in the default PosixWritableFile file implementation. There are
// numerous benefits coming from employing the hints including reduction in
// write amplification caused by OS file movement during garbage collection,
// and reduction in wear-leveling (SSDs). However, as currently implemented,
// SST write lifetime hints are calculated in a static way and solely based on
// the level, which might not be suitable for non-uniform workloads with
// dynamic / high-variance lifespan of data within the same level. In those
// cases (or when the performance is not satisfactory), it's recommended to
// disable the hints by assigning the setting to the empty set (= {});
//
// Default: Enabled in kCompactionStyleLevel mode.
CompactionStyleSet calculate_sst_write_lifetime_hint_set = {
CompactionStyle::kCompactionStyleLevel};
// End EXPERIMENTAL
};
@@ -2225,6 +2245,29 @@ struct CompactRangeOptions {
double blob_garbage_collection_age_cutoff = -1;
};
// A range of keys. In case of user_defined timestamp, if enabled, `start` and
// `limit` should point to key without timestamp part.
struct Range {
Slice start;
Slice limit;
Range() {}
Range(const Slice& s, const Slice& l) : start(s), limit(l) {}
};
// A key range with optional endpoints. In case of user_defined timestamp, if
// enabled, `start` and `limit` should point to key without timestamp part.
struct RangeOpt {
// When start.has_value() == false, refers to starting before every key
OptSlice start;
// When limit.has_value() == false, refers to ending after every key
OptSlice limit;
RangeOpt() {}
RangeOpt(const OptSlice& s, const OptSlice& l) : start(s), limit(l) {}
// RangeOpt(const Slice& s, const Slice& l) : start(s), limit(l) {}
};
// IngestExternalFileOptions is used by IngestExternalFile()
struct IngestExternalFileOptions {
// Can be set to true to move the files instead of copying them.
@@ -2247,8 +2290,11 @@ struct IngestExternalFileOptions {
// during file ingestion in the DB (the conditions under which a global_seqno
// must be assigned to the ingested file).
bool allow_global_seqno = true;
// If set to false and the file key range overlaps with the memtable key range
// (memtable flush required), IngestExternalFile will fail.
// Normally (true), IngestExternalFile() will trigger and block for flushing
// memtable(s) if there is overlap between ingested files and memtable(s). If
// allow_blocking_flush is set to false, IngestExternalFile() will fail if the
// file key range overlaps with the memtable key range (memtable flush
// required).
bool allow_blocking_flush = true;
// Set to true if you would like duplicate keys in the file being ingested
// to be skipped rather than overwriting existing data under that key.
@@ -2331,6 +2377,44 @@ struct IngestExternalFileOptions {
bool fill_cache = true;
};
// It is valid that files_checksums and files_checksum_func_names are both
// empty (no checksum information is provided for ingestion). Otherwise,
// their sizes should be the same as external_files. The file order should
// be the same in three vectors and guaranteed by the caller.
// Note that, we assume the temperatures of this batch of files to be
// ingested are the same.
struct IngestExternalFileArg {
ColumnFamilyHandle* column_family = nullptr;
std::vector<std::string> external_files;
IngestExternalFileOptions options;
std::vector<std::string> files_checksums;
std::vector<std::string> files_checksum_func_names;
// A hint as to the temperature for *reading* the files to be ingested.
Temperature file_temperature = Temperature::kUnknown;
// EXPERIMENTAL: When specified, existing keys in the given range will be
// cleared atomically as part of the ingestion, where the ingested files are
// logically applied on top of the cleared key range.
// * If both `start` and `limit` are nullptr, the entire column family is
// cleared; however, setting just one bound to nullptr is not yet supported.
// * When a range is specified, all the external files in this batch must
// be contained in that key range.
// * Checks for memtable overlap and possible blocking flush will apply
// to this range (not just the file ranges).
// * Not compatible with ingest_behind=true.
// * When options.snapshot_consistency = false, the range is cleared
// similarly to DeleteFilesInRange, but fails if any files overlap the range
// only partially.
// * It is recommended to use fail_if_not_bottommost_level=true to ensure
// data in the key range is ingested to a single compacted level (the
// last level). (fail_if_not_bottommost_level=false allows overlap between
// the ingested files.)
// * options.snapshot_consistency = true is not yet supported.
// BUG: the upper bound of the range may be interpreted as inclusive or
// exclusive, so it is best not to depend on one or the other until it is
// sorted out.
std::optional<RangeOpt> atomic_replace_range;
};
enum TraceFilterType : uint64_t {
// Trace all the operations
kTraceFilterNone = 0x0,
+41
View File
@@ -20,6 +20,7 @@
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <string>
@@ -129,6 +130,46 @@ class Slice {
// Intentionally copyable
};
// A likely more efficient alternative to std::optional<Slice>. For example,
// an empty key might be distinct from "not specified" (and Slice* as an
// optional is more troublesome to deal with).
class OptSlice {
public:
OptSlice() : slice_(nullptr, SIZE_MAX) {}
/*implicit*/ OptSlice(const Slice& s) : slice_(s) {}
/*implicit*/ OptSlice(const std::string& s) : slice_(s) {}
/*implicit*/ OptSlice(const std::string_view& sv) : slice_(sv) {}
/*implicit*/ OptSlice(const char* c_str) : slice_(c_str) {}
// For easier migrating from APIs uing Slice* as an optional type.
// CAUTION: OptSlice{nullptr} is "no value" while Slice{nullptr} is "empty"
/*implicit*/ OptSlice(std::nullptr_t) : OptSlice() {}
bool has_value() const noexcept { return slice_.size() != SIZE_MAX; }
explicit operator bool() const noexcept { return has_value(); }
const Slice& value() const noexcept {
assert(has_value());
return slice_;
}
const Slice& operator*() const noexcept { return value(); }
const Slice* operator->() const noexcept { return &value(); }
const Slice* AsPtr() const noexcept {
return has_value() ? &slice_ : nullptr;
}
// Populate from an optional pointer. This is a very explicit conversion
// to minimize risk of bugs as in
// Slice start, limit;
// RangeOpt rng = {&start, &limit};
// start = ...; // BUG: would not affect rng
static OptSlice CopyFromPtr(const Slice* ptr) {
return ptr ? OptSlice{*ptr} : OptSlice{};
}
protected:
Slice slice_;
};
/**
* A Slice that can be pinned with some cleanup tasks, which will be run upon
* ::Reset() or object destruction, whichever is invoked first. This can be used
+16 -8
View File
@@ -126,7 +126,15 @@ struct CacheUsageOptions {
};
// Configures how SST files using the block-based table format (standard)
// are written and read.
// are written and read. With few exceptions, each option only affects either
// (a) how new SST files are written, or (b) how SST files are read. If an
// option seems to affect how the SST file is constructed, e.g. format_version,
// that option *ONLY* has an effect at construction time. Contrast this with
// options like the various `cache` and `pin` options, that only affect
// in-memory and IO behavior at read time. In general, any version of RocksDB
// able to read the full key-value and indexing data in the SST file will read
// it as written regardless of current options for writing new files. See
// filter_policy regarding filters.
//
// Except as specifically noted, all options here are "mutable" using
// SetOptions(), with the caveat that only new table builders and new table
@@ -480,6 +488,10 @@ struct BlockBasedTableOptions {
// If non-nullptr, use the specified filter policy to reduce disk reads.
// Many applications will benefit from passing the result of
// NewBloomFilterPolicy() here.
//
// Because filters only impact performance and are not data-critical, an
// SST file can be opened and used without filters if (a) the filter
// policy name or schema is unrecognized, or (b) filter_policy is nullptr.
std::shared_ptr<const FilterPolicy> filter_policy = nullptr;
// If true, place whole keys in the filter (not just prefixes).
@@ -524,13 +536,9 @@ struct BlockBasedTableOptions {
// Default: 0 (disabled)
uint32_t read_amp_bytes_per_bit = 0;
// We currently have these versions:
// 0 -- This version can be read by really old RocksDB's. Doesn't support
// changing checksum type (default is CRC32).
// 1 -- Can be read by RocksDB's versions since 3.0. Supports non-default
// checksum, like xxHash. It is written by RocksDB when
// BlockBasedTableOptions::checksum is something other than kCRC32c. (version
// 0 is silently upconverted)
// We currently have these format versions:
// 0 - 1 -- Unsupported for writing new files and quietly sanitized to 2.
// Read support is deprecated and could be removed in the future.
// 2 -- Can be read by RocksDB's versions since 3.10. Changes the way we
// encode compressed blocks with LZ4, BZip2 and Zlib compression. If you
// don't plan to run RocksDB before version 3.10, you should probably use
+1 -1
View File
@@ -64,7 +64,7 @@ struct ThreadStatus {
OP_VERIFY_FILE_CHECKSUMS,
OP_GETENTITY,
OP_MULTIGETENTITY,
OP_READ_MANIFEST,
OP_GET_FILE_CHECKSUMS_FROM_CURRENT_MANIFEST,
NUM_OP_TYPES
};
+3 -3
View File
@@ -33,12 +33,12 @@ struct KeyVersion {
// copied to memory, if the range covers too many keys, the memory usage
// may be huge. `max_num_ikeys` can be used to cap the memory usage.
// The result is inserted into the provided vector, `key_versions`.
Status GetAllKeyVersions(DB* db, Slice begin_key, Slice end_key,
Status GetAllKeyVersions(DB* db, OptSlice begin_key, OptSlice end_key,
size_t max_num_ikeys,
std::vector<KeyVersion>* key_versions);
Status GetAllKeyVersions(DB* db, ColumnFamilyHandle* cfh, Slice begin_key,
Slice end_key, size_t max_num_ikeys,
Status GetAllKeyVersions(DB* db, ColumnFamilyHandle* cfh, OptSlice begin_key,
OptSlice end_key, size_t max_num_ikeys,
std::vector<KeyVersion>* key_versions);
} // namespace ROCKSDB_NAMESPACE
+8
View File
@@ -554,6 +554,14 @@ class StackableDB : public DB {
return db_->GetPropertiesOfTablesInRange(column_family, range, n, props);
}
using DB::GetPropertiesOfTablesByLevel;
Status GetPropertiesOfTablesByLevel(
ColumnFamilyHandle* column_family,
std::vector<std::unique_ptr<TablePropertiesCollection>>* props_by_level)
override {
return db_->GetPropertiesOfTablesByLevel(column_family, props_by_level);
}
Status GetUpdatesSince(
SequenceNumber seq_number, std::unique_ptr<TransactionLogIterator>* iter,
const TransactionLogIterator::ReadOptions& read_options) override {
+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 1
#define ROCKSDB_MINOR 2
#define ROCKSDB_PATCH 0
// Do not use these. We made the mistake of declaring macros starting with
+9 -9
View File
@@ -3637,7 +3637,7 @@ void Java_org_rocksdb_RocksDB_destroyDB(JNIEnv* env, jclass, jstring jdb_path,
}
bool get_slice_helper(JNIEnv* env, jobjectArray ranges, jsize index,
std::unique_ptr<ROCKSDB_NAMESPACE::Slice>& slice,
ROCKSDB_NAMESPACE::OptSlice& opt_slice,
std::vector<std::unique_ptr<jbyte[]>>& ranges_to_free) {
jobject jArray = env->GetObjectArrayElement(ranges, index);
if (env->ExceptionCheck()) {
@@ -3659,8 +3659,8 @@ bool get_slice_helper(JNIEnv* env, jobjectArray ranges, jsize index,
return false;
}
env->DeleteLocalRef(jArray);
slice.reset(new ROCKSDB_NAMESPACE::Slice(
reinterpret_cast<char*>(ranges_to_free.back().get()), len_ba));
opt_slice = ROCKSDB_NAMESPACE::Slice(
reinterpret_cast<char*>(ranges_to_free.back().get()), len_ba);
return true;
}
/*
@@ -3675,24 +3675,24 @@ void Java_org_rocksdb_RocksDB_deleteFilesInRanges(JNIEnv* env, jclass /*jdb*/,
jboolean include_end) {
jsize length = env->GetArrayLength(ranges);
std::vector<ROCKSDB_NAMESPACE::RangePtr> rangesVector;
std::vector<std::unique_ptr<ROCKSDB_NAMESPACE::Slice>> slices;
std::vector<ROCKSDB_NAMESPACE::RangeOpt> rangesVector;
std::vector<ROCKSDB_NAMESPACE::OptSlice> slices;
std::vector<std::unique_ptr<jbyte[]>> ranges_to_free;
for (jsize i = 0; (i + 1) < length; i += 2) {
slices.push_back(std::unique_ptr<ROCKSDB_NAMESPACE::Slice>());
slices.emplace_back();
if (!get_slice_helper(env, ranges, i, slices.back(), ranges_to_free)) {
// exception thrown
return;
}
slices.push_back(std::unique_ptr<ROCKSDB_NAMESPACE::Slice>());
slices.emplace_back();
if (!get_slice_helper(env, ranges, i + 1, slices.back(), ranges_to_free)) {
// exception thrown
return;
}
rangesVector.push_back(ROCKSDB_NAMESPACE::RangePtr(
slices[slices.size() - 2].get(), slices[slices.size() - 1].get()));
rangesVector.push_back(ROCKSDB_NAMESPACE::RangeOpt(
slices[slices.size() - 2], slices[slices.size() - 1]));
}
auto* db = reinterpret_cast<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
+1 -1
View File
@@ -438,7 +438,7 @@ SkipList<Key, Comparator>::SkipList(const Comparator cmp, Allocator* allocator,
kScaledInverseBranching_((Random::kMaxNext + 1) / kBranching_),
compare_(cmp),
allocator_(allocator),
head_(NewNode(0 /* any key will do */, max_height)),
head_(NewNode({} /* any key will do */, max_height)),
max_height_(1),
prev_height_(1) {
assert(max_height > 0 && kMaxHeight_ == static_cast<uint32_t>(max_height));
+1
View File
@@ -61,6 +61,7 @@ bool WBWIMemTable::Get(const LookupKey& key, std::string* value,
assert(!wbwi_->GetWriteBatch()->HasDeleteRange());
assert(merge_context);
*out_seq = kMaxSequenceNumber;
[[maybe_unused]] SequenceNumber read_seq =
GetInternalKeySeqno(key.internal_key());
// This is memtable is a single write batch, no snapshot can be taken within
+3 -2
View File
@@ -50,8 +50,9 @@ Env::IOActivity ThreadStatusUtil::TEST_GetExpectedIOActivity(
return Env::IOActivity::kGetEntity;
case ThreadStatus::OperationType::OP_MULTIGETENTITY:
return Env::IOActivity::kMultiGetEntity;
case ThreadStatus::OperationType::OP_READ_MANIFEST:
return Env::IOActivity::kReadManifest;
case ThreadStatus::OperationType::
OP_GET_FILE_CHECKSUMS_FROM_CURRENT_MANIFEST:
return Env::IOActivity::kGetFileChecksumsFromCurrentManifest;
default:
return Env::IOActivity::kUnknown;
}
+4 -1
View File
@@ -141,6 +141,7 @@ static std::unordered_map<std::string, OptionTypeInfo>
std::shared_ptr<Statistics> statistics;
std::vector<DbPath> db_paths;
FileTypeSet checksum_handoff_file_types;
CompactionStyleSet calculate_sst_write_lifetime_hint_set;
*/
{"advise_random_on_open",
{offsetof(struct ImmutableDBOptions, advise_random_on_open),
@@ -801,7 +802,9 @@ ImmutableDBOptions::ImmutableDBOptions(const DBOptions& options)
follower_catchup_retry_count(options.follower_catchup_retry_count),
follower_catchup_retry_wait_ms(options.follower_catchup_retry_wait_ms),
metadata_write_temperature(options.metadata_write_temperature),
wal_write_temperature(options.wal_write_temperature) {
wal_write_temperature(options.wal_write_temperature),
calculate_sst_write_lifetime_hint_set(
options.calculate_sst_write_lifetime_hint_set) {
fs = env->GetFileSystem();
clock = env->GetSystemClock().get();
logger = info_log.get();
+1
View File
@@ -107,6 +107,7 @@ struct ImmutableDBOptions {
uint64_t follower_catchup_retry_wait_ms;
Temperature metadata_write_temperature;
Temperature wal_write_temperature;
CompactionStyleSet calculate_sst_write_lifetime_hint_set;
// Beginning convenience/helper objects that are not part of the base
// DBOptions
+2
View File
@@ -199,6 +199,8 @@ void BuildDBOptions(const ImmutableDBOptions& immutable_db_options,
immutable_db_options.metadata_write_temperature;
options.wal_write_temperature = immutable_db_options.wal_write_temperature;
options.compaction_service = immutable_db_options.compaction_service;
options.calculate_sst_write_lifetime_hint_set =
immutable_db_options.calculate_sst_write_lifetime_hint_set;
}
ColumnFamilyOptions BuildColumnFamilyOptions(
+2
View File
@@ -342,6 +342,8 @@ TEST_F(OptionsSettableTest, DBOptionsAllFieldsSettable) {
{offsetof(struct DBOptions, compaction_service),
sizeof(std::shared_ptr<CompactionService>)},
{offsetof(struct DBOptions, daily_offpeak_time_utc), sizeof(std::string)},
{offsetof(struct DBOptions, calculate_sst_write_lifetime_hint_set),
sizeof(CompactionStyleSet)},
};
char* options_ptr = new char[sizeof(DBOptions)];
+5 -10
View File
@@ -980,16 +980,6 @@ BlockBasedTableBuilder::BlockBasedTableBuilder(
const BlockBasedTableOptions& table_options, const TableBuilderOptions& tbo,
WritableFileWriter* file) {
BlockBasedTableOptions sanitized_table_options(table_options);
if (sanitized_table_options.format_version == 0 &&
sanitized_table_options.checksum != kCRC32c) {
ROCKS_LOG_WARN(
tbo.ioptions.logger,
"Silently converting format_version to 1 because checksum is "
"non-default");
// silently convert format_version to 1 to keep consistent with current
// behavior
sanitized_table_options.format_version = 1;
}
auto ucmp = tbo.internal_comparator.user_comparator();
assert(ucmp);
(void)ucmp; // avoids unused variable error.
@@ -1028,6 +1018,11 @@ void BlockBasedTableBuilder::Add(const Slice& ikey, const Slice& value) {
if (r->props.num_entries > r->props.num_range_deletions) {
assert(r->internal_comparator.Compare(ikey, Slice(r->last_ikey)) > 0);
}
bool skip = false;
TEST_SYNC_POINT_CALLBACK("BlockBasedTableBuilder::Add::skip", (void*)&skip);
if (skip) {
return;
}
#endif // !NDEBUG
auto should_flush = r->flush_block_policy->Update(ikey, value);
@@ -467,6 +467,20 @@ void BlockBasedTableFactory::InitializeOptions() {
options_overrides_iter->second.charged = options.charged;
}
}
if (table_options_.format_version < kMinSupportedFormatVersion) {
if (AllowUnsupportedFormatVersion()) {
// Allow old format version for testing.
// And relevant old sanitization.
if (table_options_.format_version == 0 &&
table_options_.checksum != kCRC32c) {
// silently convert format_version to 1 to support non-CRC32c checksum
table_options_.format_version = 1;
}
} else {
table_options_.format_version = kMinSupportedFormatVersion;
}
}
}
Status BlockBasedTableFactory::PrepareOptions(const ConfigOptions& opts) {
@@ -910,6 +924,11 @@ 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,
@@ -87,6 +87,12 @@ class BlockBasedTableFactory : public TableFactory {
return &shared_state_->tail_prefetch_stats;
}
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,
+1
View File
@@ -320,6 +320,7 @@ void BlockFetcher::ReadBlock(bool retry) {
}
PERF_COUNTER_ADD(block_read_byte, block_size_with_trailer_);
IGNORE_STATUS_IF_ERROR(io_status_);
if (io_status_.ok()) {
if (use_fs_scratch_ && !read_req.status.ok()) {
io_status_ = read_req.status;
+8
View File
@@ -151,6 +151,14 @@ void PlainTableBuilder::Add(const Slice& key, const Slice& value) {
return;
}
#ifndef NDEBUG
bool skip = false;
TEST_SYNC_POINT_CALLBACK("PlainTableBuilder::Add::skip", (void*)&skip);
if (skip) {
return;
}
#endif // !NDEBUG
// Store key hash
if (store_index_in_file_) {
if (moptions_.prefix_extractor == nullptr) {
+4 -5
View File
@@ -474,12 +474,11 @@ Status SstFileDumper::ReadSequential(bool print_kv, uint64_t read_num_limit,
const Comparator* ucmp = internal_comparator_.user_comparator();
size_t ts_sz = ucmp->timestamp_size();
Slice from_slice = from_key;
Slice to_slice = to_key;
OptSlice from_opt = has_from ? from_key : OptSlice{};
OptSlice to_opt = has_to ? to_key : OptSlice{};
std::string from_key_buf, to_key_buf;
auto [from, to] = MaybeAddTimestampsToRange(
has_from ? &from_slice : nullptr, has_to ? &to_slice : nullptr, ts_sz,
&from_key_buf, &to_key_buf);
auto [from, to] = MaybeAddTimestampsToRange(from_opt, to_opt, ts_sz,
&from_key_buf, &to_key_buf);
uint64_t i = 0;
if (from.has_value()) {
InternalKey ikey;
+4
View File
@@ -6868,6 +6868,10 @@ TEST_F(ExternalTableReaderTest, SstReaderTest) {
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
// Opt-in this whole test file
ROCKSDB_NAMESPACE::BlockBasedTableFactory::AllowUnsupportedFormatVersion() =
true;
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
+1 -1
View File
@@ -135,7 +135,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")
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")
# 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
-3
View File
@@ -1030,9 +1030,6 @@ def finalize_and_sanitize(src_params):
# Continuous verification fails with secondaries inside NonBatchedOpsStressTest
if dest_params.get("test_secondary") == 1:
dest_params["continuous_verification_interval"] = 0
# FIXME: temporarily broken combination
dest_params["preserve_internal_time_seconds"] = 0
dest_params["preclude_last_level_data_seconds"] = 0
return dest_params
+39 -33
View File
@@ -1421,7 +1421,7 @@ CompactorCommand::CompactorCommand(
const std::vector<std::string>& /*params*/,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, false,
: LDBCommand(options, flags, false /* is_read_only */,
BuildCmdLineOptions({ARG_FROM, ARG_TO, ARG_HEX, ARG_KEY_HEX,
ARG_VALUE_HEX, ARG_TTL})),
null_from_(true),
@@ -1496,7 +1496,7 @@ DBLoaderCommand::DBLoaderCommand(
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(
options, flags, false,
options, flags, false /* is_read_only */,
BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX, ARG_FROM,
ARG_TO, ARG_CREATE_IF_MISSING, ARG_DISABLE_WAL,
ARG_BULK_LOAD, ARG_COMPACT})),
@@ -1628,7 +1628,7 @@ ManifestDumpCommand::ManifestDumpCommand(
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(
options, flags, false,
options, flags, true /* is_read_only */,
BuildCmdLineOptions({ARG_VERBOSE, ARG_PATH, ARG_HEX, ARG_JSON})),
verbose_(false),
json_(false) {
@@ -1776,7 +1776,7 @@ FileChecksumDumpCommand::FileChecksumDumpCommand(
const std::vector<std::string>& /*params*/,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, false,
: LDBCommand(options, flags, true /* is_read_only */,
BuildCmdLineOptions({ARG_PATH, ARG_HEX})) {
auto itr = options.find(ARG_PATH);
if (itr != options.end()) {
@@ -1840,7 +1840,8 @@ GetPropertyCommand::GetPropertyCommand(
const std::vector<std::string>& params,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, true, BuildCmdLineOptions({})) {
: LDBCommand(options, flags, true /* is_read_only */,
BuildCmdLineOptions({})) {
if (params.size() != 1) {
exec_state_ =
LDBCommandExecuteResult::Failed("property name must be specified");
@@ -1891,7 +1892,8 @@ ListColumnFamiliesCommand::ListColumnFamiliesCommand(
const std::vector<std::string>& /*params*/,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, false, BuildCmdLineOptions({})) {}
: LDBCommand(options, flags, true /* is_read_only */,
BuildCmdLineOptions({})) {}
void ListColumnFamiliesCommand::DoCommand() {
PrepareOptions();
@@ -1925,7 +1927,7 @@ CreateColumnFamilyCommand::CreateColumnFamilyCommand(
const std::vector<std::string>& params,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, true, {ARG_DB}) {
: LDBCommand(options, flags, false /* is_read_only */, {ARG_DB}) {
if (params.size() != 1) {
exec_state_ = LDBCommandExecuteResult::Failed(
"new column family name must be specified");
@@ -1962,7 +1964,7 @@ DropColumnFamilyCommand::DropColumnFamilyCommand(
const std::vector<std::string>& params,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, true, {ARG_DB}) {
: LDBCommand(options, flags, false /* is_read_only */, {ARG_DB}) {
if (params.size() != 1) {
exec_state_ = LDBCommandExecuteResult::Failed(
"The name of column family to drop must be specified");
@@ -2038,7 +2040,7 @@ InternalDumpCommand::InternalDumpCommand(
const std::vector<std::string>& /*params*/,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, true,
: LDBCommand(options, flags, true /* is_read_only */,
BuildCmdLineOptions(
{ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX, ARG_FROM, ARG_TO,
ARG_MAX_KEYS, ARG_COUNT_ONLY, ARG_COUNT_DELIM, ARG_STATS,
@@ -2111,8 +2113,9 @@ void InternalDumpCommand::DoCommand() {
// Cast as DBImpl to get internal iterator
std::vector<KeyVersion> key_versions;
Status st = GetAllKeyVersions(db_, GetCfHandle(), from_, to_, max_keys_,
&key_versions);
Status st =
GetAllKeyVersions(db_, GetCfHandle(), has_from_ ? from_ : OptSlice{},
has_to_ ? to_ : OptSlice{}, max_keys_, &key_versions);
if (!st.ok()) {
exec_state_ = LDBCommandExecuteResult::Failed(st.ToString());
return;
@@ -2219,7 +2222,7 @@ DBDumperCommand::DBDumperCommand(
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(
options, flags, true,
options, flags, true /* is_read_only */,
BuildCmdLineOptions(
{ARG_TTL, ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX, ARG_FROM, ARG_TO,
ARG_MAX_KEYS, ARG_COUNT_ONLY, ARG_COUNT_DELIM, ARG_STATS,
@@ -2539,7 +2542,7 @@ ReduceDBLevelsCommand::ReduceDBLevelsCommand(
const std::vector<std::string>& /*params*/,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, false,
: LDBCommand(options, flags, false /* is_read_only */,
BuildCmdLineOptions({ARG_NEW_LEVELS, ARG_PRINT_OLD_LEVELS})),
old_levels_(1 << 7),
new_levels_(-1),
@@ -2596,7 +2599,7 @@ Status ReduceDBLevelsCommand::GetOldNumOfLevels(Options& opt, int* levels) {
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
/*db_id=*/"", /*db_session_id=*/"",
opt.daily_offpeak_time_utc,
/*error_handler=*/nullptr, /*read_only=*/true);
/*error_handler=*/nullptr, /*unchanging=*/false);
std::vector<ColumnFamilyDescriptor> dummy;
ColumnFamilyDescriptor dummy_descriptor(kDefaultColumnFamilyName,
ColumnFamilyOptions(opt));
@@ -2678,7 +2681,7 @@ ChangeCompactionStyleCommand::ChangeCompactionStyleCommand(
const std::vector<std::string>& /*params*/,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, false,
: LDBCommand(options, flags, false /* is_read_only */,
BuildCmdLineOptions(
{ARG_OLD_COMPACTION_STYLE, ARG_NEW_COMPACTION_STYLE})),
old_compaction_style_(-1),
@@ -3224,7 +3227,7 @@ WALDumperCommand::WALDumperCommand(
const std::vector<std::string>& /*params*/,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, true,
: LDBCommand(options, flags, true /* is_read_only */,
BuildCmdLineOptions({ARG_WAL_FILE, ARG_DB, ARG_WRITE_COMMITTED,
ARG_PRINT_HEADER, ARG_PRINT_VALUE,
ARG_ONLY_PRINT_SEQNO_GAPS})),
@@ -3280,7 +3283,7 @@ void WALDumperCommand::DoCommand() {
GetCommand::GetCommand(const std::vector<std::string>& params,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, true,
: LDBCommand(options, flags, true /* is_read_only */,
BuildCmdLineOptions({ARG_TTL, ARG_HEX, ARG_KEY_HEX,
ARG_VALUE_HEX, ARG_READ_TIMESTAMP})) {
if (params.size() != 1) {
@@ -3339,7 +3342,7 @@ MultiGetCommand::MultiGetCommand(
const std::vector<std::string>& params,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, true,
: LDBCommand(options, flags, true /* is_read_only */,
BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX,
ARG_READ_TIMESTAMP})) {
if (params.size() < 1) {
@@ -3414,7 +3417,7 @@ GetEntityCommand::GetEntityCommand(
const std::vector<std::string>& params,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, true,
: LDBCommand(options, flags, true /* is_read_only */,
BuildCmdLineOptions({ARG_TTL, ARG_HEX, ARG_KEY_HEX,
ARG_VALUE_HEX, ARG_READ_TIMESTAMP})) {
if (params.size() != 1) {
@@ -3552,7 +3555,7 @@ ApproxSizeCommand::ApproxSizeCommand(
const std::vector<std::string>& /*params*/,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, true,
: LDBCommand(options, flags, true /* is_read_only */,
BuildCmdLineOptions(
{ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX, ARG_FROM, ARG_TO})) {
if (options.find(ARG_FROM) != options.end()) {
@@ -3608,7 +3611,7 @@ BatchPutCommand::BatchPutCommand(
const std::vector<std::string>& params,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, false,
: LDBCommand(options, flags, false /* is_read_only */,
BuildCmdLineOptions({ARG_TTL, ARG_HEX, ARG_KEY_HEX,
ARG_VALUE_HEX, ARG_CREATE_IF_MISSING})) {
if (params.size() < 2) {
@@ -3680,7 +3683,7 @@ ScanCommand::ScanCommand(const std::vector<std::string>& /*params*/,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(
options, flags, true,
options, flags, true /* is_read_only */,
BuildCmdLineOptions({ARG_TTL, ARG_NO_VALUE, ARG_HEX, ARG_KEY_HEX,
ARG_TO, ARG_VALUE_HEX, ARG_FROM, ARG_TIMESTAMP,
ARG_MAX_KEYS, ARG_TTL_START, ARG_TTL_END,
@@ -3857,7 +3860,7 @@ void ScanCommand::DoCommand() {
DeleteCommand::DeleteCommand(const std::vector<std::string>& params,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, false,
: LDBCommand(options, flags, false /* is_read_only */,
BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX})) {
if (params.size() != 1) {
exec_state_ = LDBCommandExecuteResult::Failed(
@@ -3893,7 +3896,7 @@ SingleDeleteCommand::SingleDeleteCommand(
const std::vector<std::string>& params,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, false,
: LDBCommand(options, flags, false /* is_read_only */,
BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX})) {
if (params.size() != 1) {
exec_state_ = LDBCommandExecuteResult::Failed(
@@ -3929,7 +3932,7 @@ DeleteRangeCommand::DeleteRangeCommand(
const std::vector<std::string>& params,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, false,
: LDBCommand(options, flags, false /* is_read_only */,
BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX})) {
if (params.size() != 2) {
exec_state_ = LDBCommandExecuteResult::Failed(
@@ -3967,7 +3970,7 @@ void DeleteRangeCommand::DoCommand() {
PutCommand::PutCommand(const std::vector<std::string>& params,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, false,
: LDBCommand(options, flags, false /* is_read_only */,
BuildCmdLineOptions({ARG_TTL, ARG_HEX, ARG_KEY_HEX,
ARG_VALUE_HEX, ARG_CREATE_IF_MISSING})) {
if (params.size() != 2) {
@@ -4021,7 +4024,7 @@ PutEntityCommand::PutEntityCommand(
const std::vector<std::string>& params,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, false,
: LDBCommand(options, flags, false /* is_read_only */,
BuildCmdLineOptions({ARG_TTL, ARG_HEX, ARG_KEY_HEX,
ARG_VALUE_HEX, ARG_CREATE_IF_MISSING})) {
if (params.size() < 2) {
@@ -4103,7 +4106,7 @@ DBQuerierCommand::DBQuerierCommand(
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(
options, flags, false,
options, flags, false /* is_read_only */,
BuildCmdLineOptions({ARG_TTL, ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX})) {
}
@@ -4339,7 +4342,8 @@ CheckConsistencyCommand::CheckConsistencyCommand(
const std::vector<std::string>& /*params*/,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, true, BuildCmdLineOptions({})) {}
: LDBCommand(options, flags, true /* is_read_only */,
BuildCmdLineOptions({})) {}
void CheckConsistencyCommand::Help(std::string& ret) {
ret.append(" ");
@@ -4402,7 +4406,8 @@ const std::string RepairCommand::ARG_VERBOSE = "verbose";
RepairCommand::RepairCommand(const std::vector<std::string>& /*params*/,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, false, BuildCmdLineOptions({ARG_VERBOSE})) {
: LDBCommand(options, flags, false /* is_read_only */,
BuildCmdLineOptions({ARG_VERBOSE})) {
verbose_ = IsFlagPresent(flags, ARG_VERBOSE);
}
@@ -4683,7 +4688,7 @@ DBFileDumperCommand::DBFileDumperCommand(
const std::vector<std::string>& /*params*/,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, true,
: LDBCommand(options, flags, true /* is_read_only */,
BuildCmdLineOptions(
{ARG_DECODE_BLOB_INDEX, ARG_DUMP_UNCOMPRESSED_BLOBS})),
decode_blob_index_(IsFlagPresent(flags, ARG_DECODE_BLOB_INDEX)),
@@ -4804,7 +4809,7 @@ DBLiveFilesMetadataDumperCommand::DBLiveFilesMetadataDumperCommand(
const std::vector<std::string>& /*params*/,
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, true,
: LDBCommand(options, flags, true /* is_read_only */,
BuildCmdLineOptions({ARG_SORT_BY_FILENAME})) {
sort_by_filename_ = IsFlagPresent(flags, ARG_SORT_BY_FILENAME);
}
@@ -5119,7 +5124,8 @@ void IngestExternalSstFilesCommand::OverrideBaseOptions() {
ListFileRangeDeletesCommand::ListFileRangeDeletesCommand(
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, true, BuildCmdLineOptions({ARG_MAX_KEYS})) {
: LDBCommand(options, flags, true /* is_read_only */,
BuildCmdLineOptions({ARG_MAX_KEYS})) {
auto itr = options.find(ARG_MAX_KEYS);
if (itr != options.end()) {
try {
@@ -1 +0,0 @@
* `ldb` now returns an error if the specified `--compression_type` is not supported in the build.
@@ -1 +0,0 @@
* MultiGet with snapshot and ReadOptions::read_tier = kPersistedTier will now read a consistent view across CFs (instead of potentially reading some CF before and some CF after a flush).
Loaded 100 of 109 files, more files were not shown because too many files have changed in this diff. Show more