Compare commits

..
53 Commits
Author SHA1 Message Date
anand76 01393464ea Update HISTORY for 9.3.2 2024-08-05 10:40:16 -07:00
anand76 97a152b841 Make transaction name conflict check more robust (#12895)
Summary:
The `PessimisticTransaction::SetName()` code checks for an existing txn of the given name before registering the new txn. However, this is not atomic, which could result in a race condition if two txns try to register with the same name. Both might succeed and lead to unpredictable behavior. This PR makes the test and set atomic.

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

Reviewed By: pdillinger

Differential Revision: D60460482

Pulled By: anand1976

fbshipit-source-id: e8afeb2356e1b8f4e8df785cb73532739f82579d
2024-08-05 10:37:59 -07:00
Yu Zhang 537ccd2fbe update HISTORY.md and version.h for 9.3.2 2024-08-05 09:25:52 -07:00
Yu Zhang 86e421fba9 Fix file deletions in DestroyDB not rate limited (#12891)
Summary:
Make `DestroyDB` slowly delete files if it's configured and enabled via `SstFileManager`.

It's currently not available mainly because of DeleteScheduler's logic related to tracked total_size_ and total_trash_size_. These accounting and logic should not be applied to `DestroyDB`. This PR adds a `DeleteUnaccountedDBFile` util for this purpose which deletes files without accounting it.  This util also supports assigning a file to a specified trash bucket so that user can later wait for a specific trash bucket to be empty. For `DestroyDB`, files with more than 1 hard links will be deleted immediately.

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

Test Plan: Added unit tests, existing tests.

Reviewed By: anand1976

Differential Revision: D60300220

Pulled By: jowlyzhang

fbshipit-source-id: 8b18109a177a3a9532f6dc2e40e08310c08ca3c7
2024-08-05 09:24:20 -07:00
Andrew Kryczka c5201abc4a update HISTORY.md and version.h for 9.3.1 2024-06-04 11:43:02 -07:00
Fan Zhang(DevX) 4eda1b79fa add export_file to rockdb TARGETS generator and re-gen
Summary:
we are converting the implicit loads to explicit loads, then remove the hidden loads in fbcode macroes.
details see https://fb.workplace.com/groups/devx.build.bffs/permalink/7481848805183560/

Reviewed By: JakobDegen

Differential Revision: D57800976

fbshipit-source-id: a893aa2aa9237704ba9eb998cba210222c95dd2f
2024-06-04 11:42:26 -07:00
Andrew Kryczka 030f7c4a7b include bug fixes in 9.3.0 from after the branch cut until now 2024-06-02 16:52:07 -07:00
anand76 6c60c8f3f3 Fix stale memory access with FSBuffer and tiered sec cache (#12712)
Summary:
A `BlockBasedTable` with `TieredSecondaryCache` containing a NVM cache inserts blocks  into the compressed cache and the corresponding compressed block into the NVM cache.  The `BlockFetcher` is used to get the uncompressed and compressed blocks by calling `ReadBlockContents()` and `GetUncompressedBlock()` respectively. If the file system supports FSBuffer (i.e returning a FS allocated buffer rather than caller provided), that buffer gets freed between the two calls. This PR fixes it by making the FSBuffer unique pointer a member rather than local variable.

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

Test Plan:
1. Add a unit test
2. Release validation stress test

Reviewed By: jaykorean

Differential Revision: D57974026

Pulled By: anand1976

fbshipit-source-id: cfa895914e74b4f628413b40e6e39d8d8e5286bd
2024-06-02 16:50:41 -07:00
Andrew Kryczka 2660754dbc Fix recycled WAL detection when wal_compression is enabled (#12643)
Summary:
I think the point of the `if (end_of_buffer_offset_ - buffer_.size() == 0)` was to only set `recycled_` when the first record was read. However, the condition was false when reading the first record when the WAL began with a  `kSetCompressionType` record because we had already dropped the `kSetCompressionType` record from `buffer_`. To fix this, I used `first_record_read_` instead.

Also, it was pretty confusing to treat the WAL as non-recycled when a recyclable record first appeared in a non-first record. I changed it to return an error if that happens.

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

Reviewed By: hx235

Differential Revision: D57238099

Pulled By: ajkr

fbshipit-source-id: e20a2a0c9cf0c9510a7b6af463650a05d559239e
2024-06-02 16:50:29 -07:00
Peter Dillinger 94a867c845 Disallow memtable flush and sst ingest while WAL is locked (#12652)
Summary:
We recently noticed that some memtable flushed and file
ingestions could proceed during LockWAL, in violation of its stated
contract. (Note: we aren't 100% sure its actually needed by MySQL, but
we want it to be in a clean state nonetheless.)

Despite earlier skepticism that this could be done safely (https://github.com/facebook/rocksdb/issues/12666), I
found a place to wait to wait for LockWAL to be cleared before allowing
these operations to proceed: WaitForPendingWrites()

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

Test Plan:
Added to unit tests. Extended how db_stress validates LockWAL
and re-enabled combination of ingestion and LockWAL in crash test, in
follow-up to https://github.com/facebook/rocksdb/issues/12642

Ran blackbox_crash_test for a long while with relevant features
amplified.

Suggested follow-up: fix FaultInjectionTestFS to report file sizes
consistent with what the user has requested to be flushed.

Reviewed By: jowlyzhang

Differential Revision: D57622142

Pulled By: pdillinger

fbshipit-source-id: aef265fce69465618974b4ec47f4636257c676ce
2024-06-02 16:50:15 -07:00
Hui Xiao 1b5474c8e1 Fix unreleased bug fix .md name (#12672)
Summary:
Context/Summary: as above

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

Test Plan: no code change

Reviewed By: ajkr

Differential Revision: D57505136

Pulled By: hx235

fbshipit-source-id: 0e216dc5974e9be10027b444eb6b4034f679dfd8
2024-06-02 16:49:53 -07:00
Andrew Kryczka b135fa7a8b 9.3.0 release notes 2024-06-02 16:21:22 -07:00
anand76 0ed93552f4 Implement obsolete file deletion (GC) in follower (#12657)
Summary:
This PR implements deletion of obsolete files in a follower RocksDB instance. The follower tails the leader's MANIFEST and creates links to newly added SST files. These links need to be deleted once those files become obsolete in order to reclaim space. There are three cases to be considered -
1. New files added and links created, but the Version could not be installed due to some missing files. Those links need to be preserved so a subsequent catch up attempt can succeed. We insert the next file number in the `VersionSet` to `pending_outputs_` to prevent their deletion.
2. Files deleted from the previous successfully installed `Version`. These are deleted as usual in `PurgeObsoleteFiles`.
3. New files added by a `VersionEdit` and deleted by a subsequent `VersionEdit`, both processed in the same catchup attempt. Links will be created for the new files when verifying a candidate `Version`. Those need to be deleted explicitly as they're never added to `VersionStorageInfo`, and thus not deleted by `PurgeObsoleteFiles`.

Test plan -
New unit tests in `db_follower_test`.

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

Reviewed By: jowlyzhang

Differential Revision: D57462697

Pulled By: anand1976

fbshipit-source-id: 898f15570638dd4930f839ffd31c560f9cb73916
2024-05-17 19:13:33 -07:00
Changyu Bi ffd7930312 Add more debug print to DBTestWithParam.ThreadStatusSingleCompaction (#12661)
Summary:
This test is flaky and a recent failure prints the following:
```
[ RUN      ] DBTestWithParam/DBTestWithParam.ThreadStatusSingleCompaction/0
thread id: 1842811, thread status:
thread id: 1842803, thread status:
db/db_test.cc:4697: Failure
Expected equality of these values:
  op_count
    Which is: 0
  expected_count
    Which is: 1
[  FAILED  ] DBTestWithParam/DBTestWithParam.ThreadStatusSingleCompaction/0, where GetParam() = (1, false) (307 ms)
```
Empty thread status implies that operation_type of the threads are all OP_UNKNOWN. From https://github.com/facebook/rocksdb/blob/3ed46e0668f840bea490e29beeac7777c50ae8fb/monitoring/thread_status_updater.cc#L197, this can be due to thread_data->operation_type being OP_UNKNOWN or that thread_data->cf_key it not in `cf_info_map_`, potentially due to how cf_key_ is accessed with relaxed memory order. This PR adds some debug print to print the cf_name to check this.

This PR also prints num_running_compaction and lsm state to check if a compaction is indeed running, and removes some not needed options and ensures that exactly 4 L0 files are created.

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

Test Plan:
- Cannot repro the failure locally: `gtest-parallel --repeat=10000 --workers=200 ./db_test --gtest_filter="*ThreadStatusSingleCompaction*"`
- New failure message will look like:
```
[ RUN      ] DBTestWithParam/DBTestWithParam.ThreadStatusSingleCompaction/0
op_count: 1, expected_count 2
thread id: 6104100864, thread status: , cf_name
thread id: 6103527424, thread status: Compaction, cf_name default
running compaction: 1 lsm state: 4
db/db_test.cc:4885: Failure
Value of: match
  Actual: false
Expected: true
[  FAILED  ] DBTestWithParam/DBTestWithParam.ThreadStatusSingleCompaction/0, where GetParam() = (1, false) (115 ms)
```

Reviewed By: hx235

Differential Revision: D57422755

Pulled By: cbi42

fbshipit-source-id: 635663f26052b20e485dfa06a7c0f1f318ac1099
2024-05-16 17:23:56 -07:00
Peter Dillinger 131c8ccfcd Add EntryType for TimedPut (#12669)
Summary:
Represent internal kTypeValuePreferredSeqno in the public API as kEntryTimedPut (because it is created by TimedPut, until the entry can be safely converted to a regular value entry in compaction)

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

Test Plan: for follow-up work actually using it. But putting this in place in the public API gives us more flexibility in rolling out that follow-up work (e.g. as a user extension or patch if needed).

Reviewed By: jowlyzhang

Differential Revision: D57459637

Pulled By: pdillinger

fbshipit-source-id: 160ccf7c4e524ee479558846b2a207d51b8b3d9c
2024-05-16 15:18:12 -07:00
Changyu Bi 2eb404de13 Print non-ok status for multi_ops_txns_stress test (#12660)
Summary:
Currently `assert(s.ok())` does not offer much information to debug.

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

Test Plan:
revert https://github.com/facebook/rocksdb/issues/12639 and run `python3 ./tools/db_crashtest.py blackbox --write_policy write_prepared --recycle_log_file_num=1 --threads=1 --test_multiops_txn`

before this PR:
```
Exit Before Killing
stdout:

stderr:
 db_stress: db_stress_tool/multi_ops_txns_stress.cc:1529: void rocksdb::MultiOpsTxnsStressTest::PreloadDb(rocksdb::SharedState*, int, uint32_t, uint32_t, uint32_t, uint32_t): Assertion `s.ok()' failed.
```

after this PR:
```
Exit Before Killing
stdout:

stderr:
 Verification failed: PreloadDB failed: Invalid argument: WriteOptions::disableWAL option is not supported if DBOptions::recycle_log_file_num > 0
db_stress: db_stress_tool/db_stress_test_base.cc:517: void rocksdb::StressTest::ProcessStatus(rocksdb::SharedState*, std::string, rocksdb::Status, bool) const: Assertion `false' failed.
Received signal 6 (Aborted)
```

Reviewed By: ajkr

Differential Revision: D57410819

Pulled By: cbi42

fbshipit-source-id: a03c2202c3fd666eb2f58bae24e0c9e3e6ed4265
2024-05-15 21:14:41 -07:00
Andrew Kryczka 4eaf628120 Add Iterator property "rocksdb.iterator.is-value-pinned" (#12659)
Summary:
`ReadOptions::pin_data` already has the effect of pinning the `Slice` returned by `Iterator::value()` when the value is stored inline (e.g., `kTypeValue`). This PR adds a bit of visibility into that via a new `Iterator` property, "rocksdb.iterator.is-value-pinned", as well as some documentation and tests.

See also: https://github.com/facebook/rocksdb/issues/12658

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

Reviewed By: cbi42

Differential Revision: D57391200

Pulled By: ajkr

fbshipit-source-id: 0caa8db27ca1aba86ee2addc3dfd6f0e003d32e2
2024-05-15 19:11:52 -07:00
Peter Dillinger 3ed46e0668 Handle early exit in DBErrorHandlingFSTests (#12655)
Summary:
To avoid use-after-free on custom env on ASSERT_WHATEVER failure.

This is motivated by a rare crash seen in DBErrorHandlingFSTest.WALWriteError (VersionSet::GetObsoleteFiles in a SstFileManagerImpl::ClearError thread) and wanting to rule out this being related to that.

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

Test Plan: manually seeing ASSERT_WHATEVER failures, especially under ASAN

Reviewed By: cbi42

Differential Revision: D57358202

Pulled By: pdillinger

fbshipit-source-id: 4da2a0d73a54380b257e5cc1ab6c666e26b83973
2024-05-14 16:44:32 -07:00
Jay Huh b4c6956a59 Add MultiGetEntity AttributeGroup API to stress test (#12640)
Summary:
Continuing from https://github.com/facebook/rocksdb/pull/12605, adding AttributeGroup `MultiGetEntity` API to stress tests.

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

Test Plan:
**AttributeGroup Tests**

NonBatchOps
```
python3 tools/db_crashtest.py blackbox --simple --max_key=25000000 --write_buffer_size=4194304 --use_attribute_group=1 --use_put_entity_one_in=1 --use_multi_get=1
```

BatchOps
```
python3 tools/db_crashtest.py blackbox  --test_batches_snapshots=1 --max_key=25000000 --write_buffer_size=4194304 --use_attribute_group=1 --use_put_entity_one_in=1 --use_multi_get=1
```

CfConsistency Test
```
python3 tools/db_crashtest.py blackbox --cf_consistency --max_key=25000000 --write_buffer_size=4194304 --use_attribute_group=1 --use_put_entity_one_in=1 --use_multi_get=1
```

**Non-AttributeGroup Tests**

NonBatchOps
```
python3 tools/db_crashtest.py blackbox --simple --max_key=25000000 --write_buffer_size=4194304 --use_attribute_group=0 --use_put_entity_one_in=1 --use_multi_get=1
```

BatchOps
```
python3 tools/db_crashtest.py blackbox  --test_batches_snapshots=1 --max_key=25000000 --write_buffer_size=4194304 --use_attribute_group=0 --use_put_entity_one_in=1 --use_multi_get=1
```

CfConsistency Test
```
python3 tools/db_crashtest.py blackbox --cf_consistency --max_key=25000000 --write_buffer_size=4194304 --use_attribute_group=0 --use_put_entity_one_in=1 --use_multi_get=1
```

Reviewed By: ltamasi

Differential Revision: D57233931

Pulled By: jaykorean

fbshipit-source-id: 8cea771ac2e5749050bf5319360c6c5aa476d7d5
2024-05-14 16:33:44 -07:00
Andrii Lysenko b9fc13db69 Add padding before timestamp size record if it doesn't fit into a WAL block. (#12614)
Summary:
If timestamp size record doesn't fit into a block, without padding `Writer::EmitPhysicalRecord` fails on assert (either `assert(block_offset_ + kHeaderSize + n <= kBlockSize);` or `assert(block_offset_ + kRecyclableHeaderSize + n <= kBlockSize)`, depending on whether recycling log files is enabled)  in debug build. In release, current block grows beyond 32K, `block_offset_` gets reset on next `AddRecord` and all the subsequent blocks are no longer aligned by block size.

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

Reviewed By: ltamasi

Differential Revision: D57302140

Pulled By: jowlyzhang

fbshipit-source-id: cacb5cefb7586885e52a8137ae23a95e1aefca2d
2024-05-14 15:54:02 -07:00
Yu Zhang 1567d50a27 Temporarily disable file ingestion if LockWAL is tested (#12642)
Summary:
As titled. A proper fix should probably be failing file ingestion if the DB is in a lock wal state as it promises to "Freezes the logical state of the DB".

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

Reviewed By: pdillinger

Differential Revision: D57235869

Pulled By: jowlyzhang

fbshipit-source-id: c70031463842220f865621eb6f53424df27d29e9
2024-05-14 09:27:48 -07:00
Yu Zhang c110091d36 Support read timestamp in ldb (#12641)
Summary:
As titled. Also updated sst_dump to print out user-defined timestamp separately and in human readable format.

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

Test Plan:
manually tested:
Example success run:
./ldb --db=$TEST_DB multi_get_entity 0x00000000000000950000000000000120 --key_hex --column_family=7  --read_timestamp=1115613683797942 --value_hex
0x00000000000000950000000000000120 ==> :0x0E0000000A0B080906070405020300011E1F1C1D1A1B181916171415121310112E2F2C2D2A2B282926272425222320213E3F3C3D3A3B383936373435323330314E4F4C4D4A4B484946474445424340415E5F5C5D5A5B58595657545552535051
Example failed run:
Failed: GetEntity failed: Invalid argument: column family enables user-defined timestamp while --read_timestamp is not a valid uint64 value.

sst_dump print out:
'000000000000015D000000000000012B000000000000013B|timestamp:1113554493256671' seq:2330405, type:1 => 010000000504070609080B0A0D0C0F0E111013121514171619181B1A1D1C1F1E212023222524272629282B2A2D2C2F2E313033323534373639383B3A3D3C3F3E

Reviewed By: ltamasi

Differential Revision: D57297006

Pulled By: jowlyzhang

fbshipit-source-id: 8486d91468e4f6c0d42dca3c9629f1c45a92bf5a
2024-05-13 15:43:12 -07:00
Hui Xiao 20213d01a3 Fix crash in CompactFiles() of conflict range under preclude_last_level_data_seconds > 0 (#12628)
Summary:
**Context/Summary:**

Previously `CompactFiles()` used `RangeOverlapWithCompaction()` to check for conflict when sanitizing input files while later used `FilesRangeOverlapWithCompaction()` to assert for no conflict. The latter function checks for more conflict scenarios than the former does, particularly the ones arising from `preclude_last_level_data_seconds > 0` (i.e, compaction can output to second-to-the-last level). So we ran into assertion violation in `CompactFiles()` like below
```
 Assertion `output_level == 0 || !FilesRangeOverlapWithCompaction( input_files, output_level, Compaction::EvaluatePenultimateLevel(vstorage, ioptions_, start_level, output_level))' failed.
```

This PR make `CompactFiles()` used `FilesRangeOverlapWithCompaction()` and return Aborted status upon range conflict instead of crashing (during debug build) or proceed incorrectly (during non-debug build). To do so cleanly, I included a refactoring to make `FilesRangeOverlapWithCompaction()` part of `SanitizeAndConvertCompactionInputFiles()`, replacing `RangeOverlapWithCompaction()`.

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

Test Plan: New UT crashed before the fix and return correct status after the fix.

Reviewed By: cbi42

Differential Revision: D57123536

Pulled By: hx235

fbshipit-source-id: f963a2c9e7ba1a9927a67fcc87f0dce126d3a430
2024-05-13 13:12:06 -07:00
Peter Dillinger 7747abdc15 Disable PromoteL0 in crash test (#12651)
Summary:
Seeing way too many errors likely related to PromoteL0 from https://github.com/facebook/rocksdb/issues/12617, containing
```
Cannot delete table file #N from level 0 since it is on level X
```

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

Test Plan: watch crash test results

Reviewed By: hx235

Differential Revision: D57286208

Pulled By: pdillinger

fbshipit-source-id: f7f0560cc0804ca297373c8d20ebc34986cc19d0
2024-05-13 12:42:01 -07:00
Peter Dillinger b75438f986 Allow disableWAL+recycle with WritePreparedTxnDB internals (#12639)
Summary:
Follow-up from https://github.com/facebook/rocksdb/issues/12403

The crash test was periodically failing with the
"disableWAL option is not supported if recycle_log_file_num > 0" failure, despite not setting the disableWAL from the user side.

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

Test Plan: db_stress reproducer now passes. Added WAL recycling to txn DB unit tests, which is generally more difficult for correctness. Many tests now cover this change and pass.

Reviewed By: anand1976

Differential Revision: D57227617

Pulled By: pdillinger

fbshipit-source-id: db9abefeb505bce624b45bc64009694d2a5baed9
2024-05-10 17:56:40 -07:00
Yu Zhang 7d9642d876 Add logging for read timestamp during VerifyDB (#12638)
Summary:
As titled. To help debug some verification failures.

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

Test Plan: manually tested

Reviewed By: ajkr

Differential Revision: D57219549

Pulled By: jowlyzhang

fbshipit-source-id: 59c05ac85fb1c24449e7394ea04172c855d86420
2024-05-10 12:34:53 -07:00
Levi Tamasi b92d874c8b Support MultiGetEntity in optimistic and WriteCommitted pessimistic transactions (#12634)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/12634

The patch implements support for the `MultiGetEntity` API in optimistic transactions and pessimistic transactions with the WriteCommitted policy. Similarly to the other wide-column transaction APIs, the implementation leverages the `WriteBatchWithIndex` layer.

Reviewed By: jaykorean

Differential Revision: D57177638

fbshipit-source-id: 2d9f9f287fc97e7c126830b48d21457c7c35db3f
2024-05-09 16:49:38 -07:00
Jay Huh 1f2715d1d2 AttributeGroup APIs in stress test - PutEntity and GetEntity (#12605)
Summary:
Adding AttributeGroup APIs in stress test. This contains the following changes only. More PRs to follow.

- Introduce `use_attribute_group` flag
- AttributeGroup `PutEntity()` and `GetEntity()` are now used per `use_attribute_group` flag in BatchOps, NonBatchOps and CfConsistency tests

In the next PRs I plan to add
- AttributeGroup `MultiGetEntity()` in Stress Test
- AttributeGroupIterator in Stress Test (along with CoalescingIterator)

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

Test Plan:
NonBatchOps
```
python3 tools/db_crashtest.py blackbox --simple --max_key=25000000 --write_buffer_size=4194304 --use_attribute_group=1 --use_put_entity_one_in=1
```

BatchOps
```
python3 tools/db_crashtest.py blackbox --test_batches_snapshots=1 --max_key=25000000 --write_buffer_size=4194304 --use_attribute_group=1 --use_put_entity_one_in=1
```

CfConsistency Test
```
python3 tools/db_crashtest.py blackbox --cf_consistency --max_key=25000000 --write_buffer_size=4194304 --use_attribute_group=1 --use_put_entity_one_in=1
```

Reviewed By: ltamasi

Differential Revision: D56916768

Pulled By: jaykorean

fbshipit-source-id: 8555d9e0d05927740a10e4e8301e44beec59a6f5
2024-05-09 16:40:22 -07:00
Hui Xiao 9bddac0dcf Add more public APIs to crash test (#12617)
Summary:
**Context/Summary:**
As titled. Bonus: found that PromoteL0 called with other concurrent PromoteL0 will return non-okay error so clarify the API.

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

Test Plan: CI

Reviewed By: ajkr

Differential Revision: D56954428

Pulled By: hx235

fbshipit-source-id: 0e056153c515003fd241ffec59b0d8a27529db4c
2024-05-09 15:37:38 -07:00
Levi Tamasi 97e70906fa Improve the sanity checks in (Multi)GetEntity and friends (#12630)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/12630

The patch cleans up, improves, and brings into sync (to the extent possible without API signature changes) the sanity checks around the `GetEntity` / `MultiGetEntity` family of APIs, including the read-your-own-writes (`WriteBatchWithIndex`) and transaction layers. The checks are centralized in two main sets of entry points, namely in `DB(Impl)` and the "main" `GetEntityFromBatchAndDB` / `MultiGetEntityFromBatchAndDB` overloads in `WriteBatchWithIndex`. This eliminates the need to duplicate the checks in the transaction classes.

Reviewed By: jaykorean

Differential Revision: D57125741

fbshipit-source-id: 4dd059ef644a9b173fbba767538943397e4cc6cd
2024-05-09 12:25:19 -07:00
Wei Liu 1a3357648f Error log update to db_impl_compaction_flush.cc (#12608)
Summary:
Make the error looks better.

Some inconsistency here and [here](https://github.com/WweiL/rocksdb/blob/e46ab9d4f0a0e63bfc668421e2994efa918d6570/db/db_impl/db_impl_compaction_flush.cc#L2701-L2702)

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

Reviewed By: ajkr

Differential Revision: D57134933

Pulled By: cbi42

fbshipit-source-id: 2f19f077f388d196652a4e3afd2526f18bf75b2d
2024-05-09 11:36:24 -07:00
Yu Zhang 9dc171e3bb Fix issue that cause false alarm corruption report (#12626)
Summary:
The state of `saved_seq_for_penul_check_` is not correctly maintained with the current flow. It's supposed to store the original sequence number for a `kTypeValuePreferredSeqno` entry for use in the `DecideOutputLevel` function. However, it's not always properly cleared.

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

Test Plan:
Added unit test that would fail before the fix
./tiered_compaction_test --gtest_filter="*InterleavedTimedPutAndPut*"

Reviewed By: pdillinger

Differential Revision: D57123469

Pulled By: jowlyzhang

fbshipit-source-id: 8d73214b3b6dc152daf19b6bd6ee9063581dc277
2024-05-08 15:51:38 -07:00
Peter Dillinger eeda54fe63 Fix db_crashtest.py for prefixpercent and enable_compaction_filter (#12627)
Summary:
After https://github.com/facebook/rocksdb/issues/12624 seeing db_stress failures due to db_crashtest.py calling it with --prefixpercent=5 --enable_compaction_filter=1

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

Test Plan: watch crash test

Reviewed By: ajkr

Differential Revision: D57121592

Pulled By: pdillinger

fbshipit-source-id: 55727355a7662e67efcd22d7e353153e78e24f59
2024-05-08 13:16:44 -07:00
Andrew Kryczka 933ac0e05c Fix locking for ColumnFamilyOptions::inplace_update_support (#12624)
Summary:
In `SaveValue()`, the read lock needs to be obtained before `VerifyEntryChecksum()` because the KV checksum verification reads the entire value metadata+data, which is all mutable when `ColumnFamilyOptions::inplace_update_support == true`.

In `MemTable::Update()`, the write lock needs to be obtained before mutating the value metadata (changing the value size) because it can be read concurrently.

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

Test Plan:
```
$ make COMPILE_WITH_TSAN=1 -j56 db_stress
...
$ python3 tools/db_crashtest.py blackbox --simple --max_key=10 --inplace_update_support=1 --interval=10 --allow_concurrent_memtable_write=0
```

Reviewed By: cbi42

Differential Revision: D57034571

Pulled By: ajkr

fbshipit-source-id: 3dddf881ad87923143acdf6bfec12ce47bb13a48
2024-05-08 08:30:12 -07:00
Hans Holmberg b8400c9faf Make linux file write life time hinting work (#12595)
Summary:
The life time hint fcntl takes a 64-bit unsigned int, so make sure to pass a uint64_t when doing the syscall.

See:

https://man7.org/linux/man-pages/man2/fcntl.2.html
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c75b1d9421f80f4143e389d2d50ddfc8a28c8c35

This is one of those "How did this ever work?", as Env::WriteLifeTimeHint hint is definitely not the same as an 64-bit unsigned int.
What's surprising is that SetWriteLifeTimeHint does pass a valid hint from time to time.

Thanks,
Hans

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

Reviewed By: cbi42

Differential Revision: D56901280

Pulled By: ajkr

fbshipit-source-id: f276348863cbc29a537bed9450b16b0cc513ea78
2024-05-07 17:54:50 -07:00
Changyu Bi 5bf2c00a35 Clarify manual compaction and file ingestion behavior with FIFO compaction (#12618)
Summary:
For manual compaction, FIFO compaction will always skip key range overlapping checking with SST files. If CompactRange() is called with CompactionRangeOptions::change_level=true, a CF with FIFO compaction will now return Status::NotSupported.

For file ingestion, we will always ingest into L0. Previously, it's possible to ingest files into non-L0 levels with FIFO compaction.

These changes also help to fix [this](https://github.com/facebook/rocksdb/blob/a178d15bafae1c9ea51f19691b2d1fb9dd3b6a3f/db/db_impl/db_impl_compaction_flush.cc#L1269) assertion failure in crash tests.

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

Test Plan: added unit tests to verify the new behavior.

Reviewed By: hx235

Differential Revision: D56962401

Pulled By: cbi42

fbshipit-source-id: 19812a1509650b4162b379ca5bee02f2e9d9569d
2024-05-07 12:00:15 -07:00
Levi Tamasi 83d051a8d9 Add release note for GetEntity transaction support (#12625)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/12625

Reviewed By: jaykorean

Differential Revision: D57059775

fbshipit-source-id: 80b3ddb51d538c6c21b69cd589f4ee8dd13596c9
2024-05-07 11:38:04 -07:00
Levi Tamasi eaa3226ef7 Add support for GetEntity in optimistic and WriteCommitted pessimistic transactions (#12623)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/12623

The PR adds support for the `GetEntity` API to optimistic and WriteCommitted pessimistic transactions. `MultiGetEntity` support and the `ForUpdate` variants of these read APIs will be implemented in subsequent PRs.

Reviewed By: jaykorean

Differential Revision: D57030879

fbshipit-source-id: 1f0aed6418782975fe537b6b3d437fad31fcbd43
2024-05-07 10:20:26 -07:00
Zaidoon Abd Al Hadi 36ab251c07 Expose block based metadata cache options via C API (#12611)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/12611

Reviewed By: jaykorean

Differential Revision: D56961823

Pulled By: ajkr

fbshipit-source-id: aa062cdb49a0bb2c1148a81d4c882a4733c7790e
2024-05-06 16:49:11 -07:00
Levi Tamasi 45c290660a Add PutEntity support for optimistic and WritePrepared pessimistic transactions (#12606)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/12606

The patch extends optimistic transactions and WriteCommitted pessimistic transactions with support for the `PutEntity` API. Similarly to the other APIs, `PutEntity` is available via both the `Transaction` and `TransactionDB` interfaces, where using the latter executes the write in a single-operation transaction as usual. Support for read APIs and other write policies (WritePrepared, WriteUnprepared) will be added in separate PRs.

Reviewed By: jaykorean

Differential Revision: D56911242

fbshipit-source-id: 57cf8bb6c6b1b40ba4a8a652831c13a617644289
2024-05-06 14:41:00 -07:00
Andrew Kryczka 0fef690bd5 Sync non-latest WALs during flush for 2PC, single-CF DBs (#12622)
Summary:
Previously we skipped syncing the non-latest WALs during memtable flush when the DB had only one column family. Normally that is fine because those non-latest WALs would not be read by recovery. However, in case of `DBOptions::allow_2pc == true`, there could be unmatched prepare records in those WALs making them needed by recovery. As a result, the missing sync could have resulted in the recovered WAL state falling behind the recovered SST state. When we detect that case, we return a `Status::Corruption` saying "SST file is ahead of WALs".

This PR proposes syncing the WAL in case of `DBOptions::allow_2pc`. This introduces the sync in some scenarios where it isn't needed (e.g., non-recent WALs contain no prepares) but I suspect the simplicity is worth it.

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

Reviewed By: cbi42

Differential Revision: D56987303

Pulled By: ajkr

fbshipit-source-id: 7fe9395458018a18d77e907a3b5429065c0e2e48
2024-05-06 11:56:16 -07:00
Changyu Bi 6fdc4c5282 Fix a corruption bug in CreateColumnFamilyWithImport() (#12602)
Summary:
when importing files from multiple CFs into a new CF, we were reusing the epoch numbers assigned by the original CFs. This means L0 files in the new CF can have the same epoch number (assigned originally by different CFs). While CreateColumnFamilyWithImport() requires each original CF to have disjoint key range, after an intra-l0 compaction, we still can end up with L0 files with the same epoch number but overlapping key range. This PR attempt to fix this by reassigning epoch numbers when importing multiple CFs.

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

Test Plan:
a new repro unit test. Before this PR, it fails with
```
[ RUN      ] ImportColumnFamilyTest.AssignEpochNumberToMultipleCF
db/import_column_family_test.cc:1048: Failure
db_->WaitForCompact(o)
Corruption: force_consistency_checks(DEBUG): VersionBuilder: L0 files of same epoch number but overlapping range https://github.com/facebook/rocksdb/issues/44 , smallest key: '6B6579303030303030' seq:511, type:1 , largest key: '6B6579303031303239' seq:510, type:1 , epoch number: 3 vs. file https://github.com/facebook/rocksdb/issues/36 , smallest key: '6B6579303030313030' seq:401, type:1 , largest key: '6B6579303030313939' seq:500, type:1 , epoch number: 3
```

Reviewed By: hx235

Differential Revision: D56851808

Pulled By: cbi42

fbshipit-source-id: 01b8c790c9f1f2a168047ead670e73633f705b84
2024-05-06 11:01:38 -07:00
Patrik Valo 3fdc7243f3 Fix truncating last character in the StderrLogger (#12620)
Summary:
This PR fixes a bug in the StderrLogger that truncated the last character in the logline. The problem was that we provided an incorrect max size parameter into the vsnprintf function. The size didn't take into account the null byte that the function automatically adds.

Before fix
```
** File Read Latency Histogram By Level [default] **
2024/05/04-18:50:24.209304 4788 [/db_impl/db_impl.cc:498] Shutdown: canceling all background wor
2024/05/04-18:50:24.209598 4788 [/db_impl/db_impl.cc:692] Shutdown complet
```

After fix
```
** File Read Latency Histogram By Level [default] **

2024/05/04-18:51:19.814584 4d4d [/db_impl/db_impl.cc:498] Shutdown: canceling all background work
2024/05/04-18:51:19.815528 4d4d [/db_impl/db_impl.cc:692] Shutdown complete
```

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

Test Plan:
tested on examples/simple_example.cc with StderrLogger
Fixes: https://github.com/facebook/rocksdb/issues/12576

Reviewed By: jaykorean

Differential Revision: D56972332

Pulled By: ajkr

fbshipit-source-id: 70405e8231ae6e90d24fe0b351bc8e749176bd15
2024-05-06 08:53:06 -07:00
Andrew Kryczka 7bf6d4c9d5 Lazily construct BlockBasedTableIterator::block_handles_ (#12616)
Summary:
Our external benchmark attributed a CPU regression to https://github.com/facebook/rocksdb/issues/11860. Based on the CPU profile the new overhead is from `std::deque`. The deque is always empty for these scans so we do not need to construct it. This PR lazily constructs it only when it is needed.

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

Test Plan:
- Command: `TEST_TMPDIR=/dev/shm ./db_bench -benchmarks=filluniquerandom,seekrandom[-X10] -compression_type=none -disable_auto_compactions=true -write_buffer_size=524288 -value_size=1024 -num=10000 -reads=100000`
- Results
  - Before this PR: `seekrandom [AVG    10 runs] : 47811 (± 431) ops/sec`
  - After this PR: `seekrandom [AVG 10 runs] : 51013 (± 632) ops/sec`

Reviewed By: jaykorean

Differential Revision: D56954136

Pulled By: ajkr

fbshipit-source-id: b4d34c9b6c6c2e83d4fff06deacb9f0df2ad042f
2024-05-03 17:18:13 -07:00
Peter Dillinger a178d15baf More checks around num_entries vs. num_deletions (#12600)
Summary:
We've seen an internal crash test+sanitizer failure seemingly caused by underflow on `current_num_non_deletions_` which would happen if num_entries < num_deletions. (T186407810)

This change adds an additional check (fail earlier?) and coerces read table properties to satisfy the invariant that is supposed to be provided by https://github.com/facebook/rocksdb/pull/4841 but could be violated by older files, due to
https://github.com/facebook/rocksdb/pull/4016.

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

Test Plan: existing tests

Reviewed By: ajkr

Differential Revision: D56796191

Pulled By: pdillinger

fbshipit-source-id: 6d22cc40eb74974c42b311293ee2775c6af95afc
2024-05-03 16:40:07 -07:00
Hui Xiao b312dbe920 Remove duplicate inplace_update_support crash/stress test flag (#12610)
Summary:
**Context/Summary:**
As titled. There were two flags serving the same purpose so removed one of them.

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

Test Plan: CI

Reviewed By: jaykorean, ajkr

Differential Revision: D56916119

Pulled By: hx235

fbshipit-source-id: 011140a7945782cc613ca86d4b542db0cf7fb444
2024-05-03 11:33:33 -07:00
Changyu Bi 174b8294c8 Do not change compaction style for tiered_storage crash tests (#12609)
Summary:
tiered_storage crash tests are intended to run with universal compaction only: https://github.com/facebook/rocksdb/blob/e2ef349f56f99ae83d2ded1de23ff0684c66e1bb/tools/db_crashtest.py#L562 Update the test script to not change compaction style for tiered_storage crash tests.

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

Test Plan: `python3 tools/db_crashtest.py whitebox --test_tiered_storage  --ops_per_thread=10 --max_key=100000 --duration=300 --reopen=1`

Reviewed By: ajkr

Differential Revision: D56917965

Pulled By: cbi42

fbshipit-source-id: 5ab693d7153832bae884788ff1f1762b8ec8262d
2024-05-03 10:34:56 -07:00
anand76 58627eff2e Fix build error due to virtual Iterator destructor (#12612)
Summary:
Fix build error due to virtual Iterator destructor not marked as override.

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

Reviewed By: jaykorean

Differential Revision: D56939155

Pulled By: anand1976

fbshipit-source-id: 2921d6facc296c69215de45151b08e279a1a98a2
2024-05-03 10:34:05 -07:00
Zaidoon Abd Al Hadi ed01babd07 Expose compaction pri through C API (#12604)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/12604

Reviewed By: cbi42

Differential Revision: D56914066

Pulled By: ajkr

fbshipit-source-id: 64b51ab2b7b5ec0b5fde5a5f61d076bac1c3a8ad
2024-05-02 18:39:24 -07:00
Changyu Bi e2ef349f56 Deflake unit test DBCompactionTest.CompactionLimiter (#12596)
Summary:
The test has been flaky for a long time. A recent [failure](https://github.com/facebook/rocksdb/actions/runs/8820808355/job/24215219590?pr=12578) shows that there is still flush running when the assertion fails. I think this is because `WaitForFlushMemTable()` may return before the a flush schedules the next compaction.

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

Test Plan: I could not repro the failure locally: `gtest-parallel --repeat=8000 --workers=100 ./db_compaction_test --gtest_filter="*CompactionLimiter*"`

Reviewed By: ajkr

Differential Revision: D56715874

Pulled By: cbi42

fbshipit-source-id: f5f64eb30fff7e115c19beedad2dc22afa06258d
2024-05-02 17:10:06 -07:00
Jaepil Jeong 2cd4346df6 Fix compile error in Clang (#12588)
Summary:
This PR fixes the following compile errors with Clang:

```
.../rocksdb/env/fs_on_demand.cc:184:5: error: no member named 'for_each' in namespace 'std'; did you mean 'std::ranges::for_each'?
  184 |     std::for_each(rchildren.begin(), rchildren.end(), [&](std::string& name) {
      |     ^~~~~~~~~~~~~
      |     std::ranges::for_each
/opt/homebrew/opt/llvm@17/bin/../include/c++/v1/__algorithm/ranges_for_each.h:68:23: note: 'std::ranges::for_each' declared here
   68 | inline constexpr auto for_each = __for_each::__fn{};
      |                       ^
.../rocksdb/env/fs_on_demand.cc:188:10: error: no member named 'sort' in namespace 'std'
  188 |     std::sort(result->begin(), result->end());
      |     ~~~~~^
.../rocksdb/env/fs_on_demand.cc:189:10: error: no member named 'sort' in namespace 'std'
  189 |     std::sort(rchildren.begin(), rchildren.end());
      |     ~~~~~^
.../rocksdb/env/fs_on_demand.cc:193:10: error: no member named 'set_union' in namespace 'std'
  193 |     std::set_union(result->begin(), result->end(), rchildren.begin(),
      |     ~~~~~^
.../rocksdb/env/fs_on_demand.cc:221:5: error: no member named 'for_each' in namespace 'std'; did you mean 'std::ranges::for_each'?
  221 |     std::for_each(
      |     ^~~~~~~~~~~~~
      |     std::ranges::for_each
/opt/homebrew/opt/llvm@17/bin/../include/c++/v1/__algorithm/ranges_for_each.h:68:23: note: 'std::ranges::for_each' declared here
   68 | inline constexpr auto for_each = __for_each::__fn{};
      |                       ^
.../rocksdb/env/fs_on_demand.cc:226:10: error: no member named 'sort' in namespace 'std'
  226 |     std::sort(result->begin(), result->end(), file_attr_sorter);
      |     ~~~~~^
.../rocksdb/env/fs_on_demand.cc:227:10: error: no member named 'sort' in namespace 'std'
  227 |     std::sort(rchildren.begin(), rchildren.end(), file_attr_sorter);
      |     ~~~~~^
.../rocksdb/env/fs_on_demand.cc:231:10: error: no member named 'set_union' in namespace 'std'
  231 |     std::set_union(rchildren.begin(), rchildren.end(), result->begin(),
      |     ~~~~~^
8 errors generated.
```

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

Reviewed By: jaykorean

Differential Revision: D56656222

Pulled By: ajkr

fbshipit-source-id: 7e94b6250fc9edfe597a61b7622f09d6b6cd9cbd
2024-05-02 16:54:21 -07:00
anand76 6cc7ad15b6 Implement secondary cache admission policy to allow all evicted blocks (#12599)
Summary:
Add a secondary cache admission policy to admit all blocks evicted from the block cache.

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

Reviewed By: pdillinger

Differential Revision: D56891760

Pulled By: anand1976

fbshipit-source-id: 193c98c055aa3477f4e3a78e5d3daef27a5eacf4
2024-05-02 11:23:35 -07:00
anand76 6349da612b Update HISTORY.md and version to 9.3.0 (#12601)
Summary:
Update HISTORY.md for 9.2 and version to 9.3.

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

Reviewed By: jaykorean, jowlyzhang

Differential Revision: D56845901

Pulled By: anand1976

fbshipit-source-id: 0d1137a6568e4712be2f8b705f4f7b438217dbed
2024-05-01 16:33:04 -07:00
94 changed files with 4792 additions and 885 deletions

No files matched your search

+29 -3
View File
@@ -1,14 +1,40 @@
# Rocksdb Change Log
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
## 9.2.2 (05/31/2024)
## 9.3.2 (08/02/2024)
### Bug Fixes
* Fixed a bug causing stale memory access when using the TieredSecondaryCache with an NVM secondary cache, and a file system that supports return an FS allocated buffer for MultiRead (FSSupportedOps::kFSBuffer is set).
* *Make DestroyDB supports slow deletion when it's configured in `SstFileManager`. The slow deletion is subject to the configured `rate_bytes_per_sec`, but not subject to the `max_trash_db_ratio`.
* Fix a race condition in pessimistic transactions that could allow multiple transactions with the same name to be registered simultaneously, resulting in a crash or other unpredictable behavior.
## 9.3.1 (05/25/2024)
### Bug Fixes
* [internal only] Build script improvement
## 9.3.0 (05/17/2024)
### New Features
* Optimistic transactions and pessimistic transactions with the WriteCommitted policy now support the `GetEntity` API.
* Added new `Iterator` property, "rocksdb.iterator.is-value-pinned", for checking whether the `Slice` returned by `Iterator::value()` can be used until the `Iterator` is destroyed.
* Optimistic transactions and WriteCommitted pessimistic transactions now support the `MultiGetEntity` API.
* Optimistic transactions and pessimistic transactions with the WriteCommitted policy now support the `PutEntity` API. Support for read APIs and other write policies (WritePrepared, WriteUnprepared) will be added later.
## 9.2.1 (05/03/2024)
### Public API Changes
* Exposed block based metadata cache options via C API
* Exposed compaction pri via c api.
* Add a kAdmPolicyAllowAll option to TieredAdmissionPolicy that admits all blocks evicted from the primary block cache into the compressed secondary cache.
### Behavior Changes
* CompactRange() with change_level=true on a CF with FIFO compaction will return Status::NotSupported().
* External file ingestion with FIFO compaction will always ingest to L0.
### Bug Fixes
* Fixed a bug for databases using `DBOptions::allow_2pc == true` (all `TransactionDB`s except `OptimisticTransactionDB`) that have exactly one column family. Due to a missing WAL sync, attempting to open the DB could have returned a `Status::Corruption` with a message like "SST file is ahead of WALs".
* Fix a bug in CreateColumnFamilyWithImport() where if multiple CFs are imported, we were not resetting files' epoch number and L0 files can have overlapping key range but the same epoch number.
* Fixed race conditions when `ColumnFamilyOptions::inplace_update_support == true` between user overwrites and reads on the same key.
* Fix a bug where `CompactFiles()` can compact files of range conflict with other ongoing compactions' when `preclude_last_level_data_seconds > 0` is used
* Fixed a false positive `Status::Corruption` reported when reopening a DB that used `DBOptions::recycle_log_file_num > 0` and `DBOptions::wal_compression != kNoCompression`.
* While WAL is locked with LockWAL(), some operations like Flush() and IngestExternalFile() are now blocked as they should have been.
* Fixed a bug causing stale memory access when using the TieredSecondaryCache with an NVM secondary cache, and a file system that supports return an FS allocated buffer for MultiRead (FSSupportedOps::kFSBuffer is set).
## 9.2.0 (05/01/2024)
### New Features
* Added two options `deadline` and `max_size_bytes` for CacheDumper to exit early
+1
View File
@@ -4,6 +4,7 @@
# This file is a Facebook-specific integration for buck builds, so can
# only be validated by Facebook employees.
load("//rocks/buckifier:defs.bzl", "cpp_library_wrapper","rocks_cpp_library_wrapper","cpp_binary_wrapper","cpp_unittest_wrapper","fancy_bench_wrapper","add_c_test_wrapper")
load("@fbcode_macros//build_defs:export_files.bzl", "export_file")
cpp_library_wrapper(name="rocksdb_lib", srcs=[
+1
View File
@@ -7,6 +7,7 @@ rocksdb_target_header_template = """# This file \100generated by:
# This file is a Facebook-specific integration for buck builds, so can
# only be validated by Facebook employees.
load("//rocks/buckifier:defs.bzl", "cpp_library_wrapper","rocks_cpp_library_wrapper","cpp_binary_wrapper","cpp_unittest_wrapper","fancy_bench_wrapper","add_c_test_wrapper")
load("@fbcode_macros//build_defs:export_files.bzl", "export_file")
"""
+26
View File
@@ -2745,6 +2745,24 @@ void rocksdb_options_set_block_based_table_factory(
}
}
void rocksdb_block_based_options_set_top_level_index_pinning_tier(
rocksdb_block_based_table_options_t* options, int v) {
options->rep.metadata_cache_options.top_level_index_pinning =
static_cast<ROCKSDB_NAMESPACE::PinningTier>(v);
}
void rocksdb_block_based_options_set_partition_pinning_tier(
rocksdb_block_based_table_options_t* options, int v) {
options->rep.metadata_cache_options.partition_pinning =
static_cast<ROCKSDB_NAMESPACE::PinningTier>(v);
}
void rocksdb_block_based_options_set_unpartitioned_pinning_tier(
rocksdb_block_based_table_options_t* options, int v) {
options->rep.metadata_cache_options.unpartitioned_pinning =
static_cast<ROCKSDB_NAMESPACE::PinningTier>(v);
}
rocksdb_cuckoo_table_options_t* rocksdb_cuckoo_options_create() {
return new rocksdb_cuckoo_table_options_t;
}
@@ -3930,6 +3948,14 @@ void rocksdb_options_set_fifo_compaction_options(
opt->rep.compaction_options_fifo = fifo->rep;
}
void rocksdb_options_set_compaction_pri(rocksdb_options_t* opt, int pri) {
opt->rep.compaction_pri = static_cast<ROCKSDB_NAMESPACE::CompactionPri>(pri);
}
int rocksdb_options_get_compaction_pri(rocksdb_options_t* opt) {
return opt->rep.compaction_pri;
}
char* rocksdb_options_statistics_get_string(rocksdb_options_t* opt) {
ROCKSDB_NAMESPACE::Statistics* statistics = opt->rep.statistics.get();
if (statistics) {
+11 -1
View File
@@ -704,6 +704,10 @@ int main(int argc, char** argv) {
rocksdb_block_based_options_set_block_cache(table_options, cache);
rocksdb_block_based_options_set_data_block_index_type(table_options, 1);
rocksdb_block_based_options_set_data_block_hash_ratio(table_options, 0.75);
rocksdb_block_based_options_set_top_level_index_pinning_tier(table_options,
1);
rocksdb_block_based_options_set_partition_pinning_tier(table_options, 2);
rocksdb_block_based_options_set_unpartitioned_pinning_tier(table_options, 3);
rocksdb_options_set_block_based_table_factory(options, table_options);
rocksdb_options_set_compression(options, rocksdb_no_compression);
@@ -2041,7 +2045,6 @@ int main(int argc, char** argv) {
rocksdb_options_set_advise_random_on_open(o, 1);
CheckCondition(1 == rocksdb_options_get_advise_random_on_open(o));
rocksdb_options_set_use_adaptive_mutex(o, 1);
CheckCondition(1 == rocksdb_options_get_use_adaptive_mutex(o));
@@ -2112,6 +2115,9 @@ int main(int argc, char** argv) {
rocksdb_options_set_compaction_style(o, 2);
CheckCondition(2 == rocksdb_options_get_compaction_style(o));
rocksdb_options_set_compaction_pri(o, 4);
CheckCondition(4 == rocksdb_options_get_compaction_pri(o));
rocksdb_options_set_atomic_flush(o, 1);
CheckCondition(1 == rocksdb_options_get_atomic_flush(o));
@@ -2616,6 +2622,10 @@ int main(int argc, char** argv) {
CheckCondition(1 == rocksdb_options_get_compaction_style(copy));
CheckCondition(2 == rocksdb_options_get_compaction_style(o));
rocksdb_options_set_compaction_pri(copy, 1);
CheckCondition(1 == rocksdb_options_get_compaction_pri(copy));
CheckCondition(4 == rocksdb_options_get_compaction_pri(o));
rocksdb_options_set_atomic_flush(copy, 0);
CheckCondition(0 == rocksdb_options_get_atomic_flush(copy));
CheckCondition(1 == rocksdb_options_get_atomic_flush(o));
+24 -10
View File
@@ -340,8 +340,8 @@ Compaction* CompactionPicker::CompactFiles(
#ifndef NDEBUG
assert(input_files.size());
// This compaction output should not overlap with a running compaction as
// `SanitizeCompactionInputFiles` should've checked earlier and db mutex
// shouldn't have been released since.
// `SanitizeAndConvertCompactionInputFiles` should've checked earlier and db
// mutex shouldn't have been released since.
int start_level = Compaction::kInvalidLevel;
for (const auto& in : input_files) {
// input_files should already be sorted by level
@@ -1040,19 +1040,18 @@ Status CompactionPicker::SanitizeCompactionInputFilesForAllLevels(
}
}
}
if (RangeOverlapWithCompaction(smallestkey, largestkey, output_level)) {
return Status::Aborted(
"A running compaction is writing to the same output level in an "
"overlapping key range");
}
return Status::OK();
}
Status CompactionPicker::SanitizeCompactionInputFiles(
Status CompactionPicker::SanitizeAndConvertCompactionInputFiles(
std::unordered_set<uint64_t>* input_files,
const ColumnFamilyMetaData& cf_meta, const int output_level) const {
const ColumnFamilyMetaData& cf_meta, const int output_level,
const VersionStorageInfo* vstorage,
std::vector<CompactionInputFiles>* converted_input_files) const {
assert(static_cast<int>(cf_meta.levels.size()) - 1 ==
cf_meta.levels[cf_meta.levels.size() - 1].level);
assert(converted_input_files);
if (output_level >= static_cast<int>(cf_meta.levels.size())) {
return Status::InvalidArgument(
"Output level for column family " + cf_meta.name +
@@ -1078,7 +1077,6 @@ Status CompactionPicker::SanitizeCompactionInputFiles(
Status s = SanitizeCompactionInputFilesForAllLevels(input_files, cf_meta,
output_level);
if (!s.ok()) {
return s;
}
@@ -1119,6 +1117,22 @@ Status CompactionPicker::SanitizeCompactionInputFiles(
}
}
s = GetCompactionInputsFromFileNumbers(converted_input_files, input_files,
vstorage, CompactionOptions());
if (!s.ok()) {
return s;
}
assert(converted_input_files->size() > 0);
if (output_level != 0 &&
FilesRangeOverlapWithCompaction(
*converted_input_files, output_level,
Compaction::EvaluatePenultimateLevel(
vstorage, ioptions_, (*converted_input_files)[0].level,
output_level))) {
return Status::Aborted(
"A running compaction is writing to the same output level(s) in an "
"overlapping key range");
}
return Status::OK();
}
+19 -14
View File
@@ -86,16 +86,20 @@ class CompactionPicker {
virtual bool NeedsCompaction(const VersionStorageInfo* vstorage) const = 0;
// Sanitize the input set of compaction input files.
// When the input parameters do not describe a valid compaction, the
// function will try to fix the input_files by adding necessary
// files. If it's not possible to conver an invalid input_files
// into a valid one by adding more files, the function will return a
// non-ok status with specific reason.
//
Status SanitizeCompactionInputFiles(std::unordered_set<uint64_t>* input_files,
const ColumnFamilyMetaData& cf_meta,
const int output_level) const;
// Sanitize the input set of compaction input files and convert it to
// `std::vector<CompactionInputFiles>` in the output parameter
// `converted_input_files`.
// When the input parameters do not describe a valid
// compaction, the function will try to fix the input_files by adding
// necessary files. If it's not possible to convert an invalid input_files
// into a valid one by adding more files, the function will return a
// non-ok status with specific reason.
//
Status SanitizeAndConvertCompactionInputFiles(
std::unordered_set<uint64_t>* input_files,
const ColumnFamilyMetaData& cf_meta, const int output_level,
const VersionStorageInfo* vstorage,
std::vector<CompactionInputFiles>* converted_input_files) const;
// Free up the files that participated in a compaction
//
@@ -109,8 +113,8 @@ class CompactionPicker {
// object.
//
// Caller must provide a set of input files that has been passed through
// `SanitizeCompactionInputFiles` earlier. The lock should not be released
// between that call and this one.
// `SanitizeAndConvertCompactionInputFiles` earlier. The lock should not be
// released between that call and this one.
Compaction* CompactFiles(const CompactionOptions& compact_options,
const std::vector<CompactionInputFiles>& input_files,
int output_level, VersionStorageInfo* vstorage,
@@ -120,6 +124,7 @@ class CompactionPicker {
// Converts a set of compaction input file numbers into
// a list of CompactionInputFiles.
// TODO(hx235): remove the unused paramter `compact_options`
Status GetCompactionInputsFromFileNumbers(
std::vector<CompactionInputFiles>* input_files,
std::unordered_set<uint64_t>* input_set,
@@ -225,8 +230,8 @@ class CompactionPicker {
protected:
const ImmutableOptions& ioptions_;
// A helper function to SanitizeCompactionInputFiles() that
// sanitizes "input_files" by adding necessary files.
// A helper function to SanitizeAndConvertCompactionInputFiles() that
// sanitizes "input_files" by adding necessary files.
virtual Status SanitizeCompactionInputFilesForAllLevels(
std::unordered_set<uint64_t>* input_files,
const ColumnFamilyMetaData& cf_meta, const int output_level) const;
+75 -22
View File
@@ -16,6 +16,7 @@
#include "env/mock_env.h"
#include "port/port.h"
#include "port/stack_trace.h"
#include "rocksdb/advanced_options.h"
#include "rocksdb/concurrent_task_limiter.h"
#include "rocksdb/experimental.h"
#include "rocksdb/sst_file_writer.h"
@@ -6204,29 +6205,14 @@ TEST_F(DBCompactionTest, CompactionLimiter) {
NumTableFilesAtLevel(0, 0));
}
// All CFs are pending compaction
// Wait until all CFs are pending compaction. WaitForFlushMemtable() can
// return before the next compaction is scheduled, so we need to do some
// waiting here.
unsigned int tp_len = env_->GetThreadPoolQueueLen(Env::LOW);
if (cf_count != tp_len) {
// The test is flaky and fails the assertion below.
// Print some debug information.
uint64_t num_running_flushes = 0;
if (db_->GetIntProperty(DB::Properties::kNumRunningFlushes,
&num_running_flushes)) {
fprintf(stdout, "Running flushes: %" PRIu64 "\n", num_running_flushes);
}
fprintf(stdout,
"%zu CF in compaction queue: ", pending_compaction_cfs.size());
for (const auto& cf_name : pending_compaction_cfs) {
fprintf(stdout, "%s, ", cf_name.c_str());
}
fprintf(stdout, "\n");
// print lsm
for (unsigned int cf = 0; cf < cf_count; cf++) {
fprintf(stdout, "%s: %s\n", cf_names[cf], FilesPerLevel(cf).c_str());
}
for (int i = 0; i < 10000 && tp_len < cf_count; i++) {
env_->SleepForMicroseconds(1000);
tp_len = env_->GetThreadPoolQueueLen(Env::LOW);
}
ASSERT_EQ(cf_count, tp_len);
// Unblock all compaction threads
@@ -7135,7 +7121,8 @@ class DBCompactionTestWithOngoingFileIngestionParam
SyncPoint::GetInstance()->SetCallBack(
"ExternalSstFileIngestionJob::Run", [&](void*) {
SyncPoint::GetInstance()->LoadDependency(
{{"DBImpl::CompactFilesImpl::PostSanitizeCompactionInputFiles",
{{"DBImpl::CompactFilesImpl::"
"PostSanitizeAndConvertCompactionInputFiles",
"VersionSet::LogAndApply:WriteManifest"}});
});
} else {
@@ -7617,6 +7604,72 @@ class DBCompactionTestL0FilesMisorderCorruption : public DBCompactionTest {
std::shared_ptr<test::SleepingBackgroundTask> sleeping_task_;
};
TEST_F(DBCompactionTest, CompactFilesSupportKeyPlacementRangeConflict) {
Options options;
options.create_if_missing = true;
options.compaction_style = kCompactionStyleUniversal;
options.num_levels = 3;
DestroyAndReopen(options);
// To create LSM of below shape:
// L0: [k2]
// L1: [k3],[k4]
// L2: [k1, k5]
ASSERT_OK(Put("k1", "v"));
ASSERT_OK(Put("k5", "v"));
ASSERT_OK(Flush());
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized;
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
ASSERT_EQ("0,0,1", FilesPerLevel());
ASSERT_OK(Put("k3", "v"));
ASSERT_OK(Flush());
ASSERT_OK(Put("k4", "v"));
ASSERT_OK(Flush());
ASSERT_OK(experimental::PromoteL0(db_, db_->DefaultColumnFamily(), 1));
ASSERT_EQ("0,2,1", FilesPerLevel());
ASSERT_OK(Put("k2", "v"));
ASSERT_OK(Flush());
ASSERT_EQ("1,2,1", FilesPerLevel());
Close();
// To force below two CompactFiles() in order to coerce range conflict on L1
// upon (2)
// (1): Compact [k2] at L0 and [k3] at L1 with output to L1
// (2): Compact [k4] at L1 and [k1, k5] at L2 and output to L1 and L2
options.preclude_last_level_data_seconds = 1;
Reopen(options);
ColumnFamilyMetaData cf_meta_data;
db_->GetColumnFamilyMetaData(&cf_meta_data);
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"CompactFilesImpl:0", [&](void* /*arg*/) {
std::vector<std::string> c2_input_files;
c2_input_files.push_back(cf_meta_data.levels[1].files[1].name);
c2_input_files.push_back(cf_meta_data.levels[2].files[0].name);
// To verify CompactFiles() is aborted upon range conflict instead
// of crashing upon internal assertion
Status s = db_->CompactFiles(CompactionOptions(), c2_input_files,
2 /* output_level */);
ASSERT_TRUE(s.IsAborted());
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
std::vector<std::string> c1_input_files;
c1_input_files.push_back(cf_meta_data.levels[0].files[0].name);
c1_input_files.push_back(cf_meta_data.levels[1].files[0].name);
Status s = db_->CompactFiles(CompactionOptions(), c1_input_files,
1 /* output_level */);
ASSERT_OK(s);
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
}
TEST_F(DBCompactionTestL0FilesMisorderCorruption,
FlushAfterIntraL0LevelCompactionWithIngestedFile) {
SetupOptions(CompactionStyle::kCompactionStyleLevel, "");
+12 -6
View File
@@ -229,12 +229,18 @@ Status DBImpl::GetLiveFilesStorageInfo(
// metadata.
mutex_.Lock();
if (flush_memtable) {
Status status = FlushForGetLiveFiles();
if (!status.ok()) {
mutex_.Unlock();
ROCKS_LOG_ERROR(immutable_db_options_.info_log, "Cannot Flush data %s\n",
status.ToString().c_str());
return status;
bool wal_locked = lock_wal_count_ > 0;
if (wal_locked) {
ROCKS_LOG_INFO(immutable_db_options_.info_log,
"Can't FlushForGetLiveFiles while WAL is locked");
} else {
Status status = FlushForGetLiveFiles();
if (!status.ok()) {
mutex_.Unlock();
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
"Cannot Flush data %s\n", status.ToString().c_str());
return status;
}
}
}
+466 -2
View File
@@ -17,6 +17,7 @@ class DBFollowerTest : public DBTestBase {
// Create the leader DB object
DBFollowerTest() : DBTestBase("/db_follower_test", /*env_do_fsync*/ false) {
follower_name_ = dbname_ + "/follower";
db_parent_ = dbname_;
Close();
Destroy(CurrentOptions());
EXPECT_EQ(env_->CreateDirIfMissing(dbname_), Status::OK());
@@ -27,17 +28,215 @@ class DBFollowerTest : public DBTestBase {
~DBFollowerTest() {
follower_.reset();
EXPECT_EQ(DestroyDB(follower_name_, CurrentOptions()), Status::OK());
Destroy(CurrentOptions());
dbname_ = db_parent_;
}
protected:
class DBFollowerTestFS : public FileSystemWrapper {
public:
explicit DBFollowerTestFS(const std::shared_ptr<FileSystem>& target)
: FileSystemWrapper(target),
cv_(&mutex_),
barrier_(false),
count_(0),
reinit_count_(0) {}
const char* Name() const override { return "DBFollowerTestFS"; }
IOStatus NewSequentialFile(const std::string& fname,
const FileOptions& file_opts,
std::unique_ptr<FSSequentialFile>* result,
IODebugContext* dbg = nullptr) override {
class DBFollowerTestSeqFile : public FSSequentialFileWrapper {
public:
DBFollowerTestSeqFile(DBFollowerTestFS* fs,
std::unique_ptr<FSSequentialFile>&& file,
uint64_t /*size*/)
: FSSequentialFileWrapper(file.get()),
fs_(fs),
file_(std::move(file)) {}
IOStatus Read(size_t n, const IOOptions& options, Slice* result,
char* scratch, IODebugContext* dbg) override {
fs_->BarrierWait();
return target()->Read(n, options, result, scratch, dbg);
}
private:
DBFollowerTestFS* fs_;
std::unique_ptr<FSSequentialFile> file_;
};
std::unique_ptr<FSSequentialFile> file;
IOStatus s = target()->NewSequentialFile(fname, file_opts, &file, dbg);
if (s.ok() && test::GetFileType(fname) == kDescriptorFile) {
uint64_t size = 0;
EXPECT_EQ(target()->GetFileSize(fname, IOOptions(), &size, nullptr),
IOStatus::OK());
result->reset(new DBFollowerTestSeqFile(this, std::move(file), size));
} else {
*result = std::move(file);
}
return s;
}
void BarrierInit(int count) {
MutexLock l(&mutex_);
barrier_ = true;
count_ = count;
}
void BarrierWait() {
MutexLock l(&mutex_);
if (!barrier_) {
return;
}
if (--count_ == 0) {
if (reinit_count_ > 0) {
count_ = reinit_count_;
reinit_count_ = 0;
} else {
barrier_ = false;
}
cv_.SignalAll();
} else {
cv_.Wait();
}
}
void BarrierWaitAndReinit(int count) {
MutexLock l(&mutex_);
if (!barrier_) {
return;
}
reinit_count_ = count;
if (--count_ == 0) {
if (reinit_count_ > 0) {
count_ = reinit_count_;
reinit_count_ = 0;
} else {
barrier_ = false;
}
cv_.SignalAll();
} else {
cv_.Wait();
}
}
private:
port::Mutex mutex_;
port::CondVar cv_;
bool barrier_;
int count_;
int reinit_count_;
};
class DBFollowerTestSstPartitioner : public SstPartitioner {
public:
explicit DBFollowerTestSstPartitioner(uint64_t max_keys)
: max_keys_(max_keys), num_keys_(0) {}
const char* Name() const override { return "DBFollowerTestSstPartitioner"; }
PartitionerResult ShouldPartition(
const PartitionerRequest& /*request*/) override {
if (++num_keys_ > max_keys_) {
num_keys_ = 0;
return PartitionerResult::kRequired;
} else {
return PartitionerResult::kNotRequired;
}
}
bool CanDoTrivialMove(const Slice& /*smallest_user_key*/,
const Slice& /*largest_user_key*/) override {
return true;
}
private:
uint64_t max_keys_;
uint64_t num_keys_;
};
class DBFollowerTestSstPartitionerFactory : public SstPartitionerFactory {
public:
explicit DBFollowerTestSstPartitionerFactory(uint64_t max_keys)
: max_keys_(max_keys) {}
std::unique_ptr<SstPartitioner> CreatePartitioner(
const SstPartitioner::Context& /*context*/) const override {
std::unique_ptr<SstPartitioner> partitioner;
partitioner.reset(new DBFollowerTestSstPartitioner(max_keys_));
return partitioner;
}
const char* Name() const override {
return "DBFollowerTestSstPartitionerFactory";
}
private:
uint64_t max_keys_;
};
Status OpenAsFollower() {
return DB::OpenAsFollower(CurrentOptions(), follower_name_, dbname_,
&follower_);
Options opts = CurrentOptions();
if (!follower_env_) {
follower_env_ = NewCompositeEnv(
std::make_shared<DBFollowerTestFS>(env_->GetFileSystem()));
}
opts.env = follower_env_.get();
opts.follower_refresh_catchup_period_ms = 100;
return DB::OpenAsFollower(opts, follower_name_, dbname_, &follower_);
}
std::string FollowerGet(const std::string& k) {
ReadOptions options;
options.verify_checksums = true;
std::string result;
Status s = follower()->Get(options, k, &result);
if (s.IsNotFound()) {
result = "NOT_FOUND";
} else if (!s.ok()) {
result = s.ToString();
}
return result;
}
DB* follower() { return follower_.get(); }
DBFollowerTestFS* follower_fs() {
return static_cast<DBFollowerTestFS*>(follower_env_->GetFileSystem().get());
}
void CheckDirs() {
std::vector<std::string> db_children;
std::vector<std::string> follower_children;
EXPECT_OK(env_->GetChildren(dbname_, &db_children));
EXPECT_OK(env_->GetChildren(follower_name_, &follower_children));
std::set<uint64_t> db_filenums;
std::set<uint64_t> follower_filenums;
for (auto& name : db_children) {
if (test::GetFileType(name) != kTableFile) {
continue;
}
db_filenums.insert(test::GetFileNumber(name));
}
for (auto& name : follower_children) {
if (test::GetFileType(name) != kTableFile) {
continue;
}
follower_filenums.insert(test::GetFileNumber(name));
}
db_filenums.merge(follower_filenums);
EXPECT_EQ(follower_filenums.size(), db_filenums.size());
}
private:
std::string follower_name_;
std::string db_parent_;
std::unique_ptr<Env> follower_env_;
std::unique_ptr<DB> follower_;
};
@@ -51,8 +250,273 @@ TEST_F(DBFollowerTest, Basic) {
std::string val;
ASSERT_OK(follower()->Get(ReadOptions(), "k1", &val));
ASSERT_EQ(val, "v1");
CheckDirs();
}
TEST_F(DBFollowerTest, Flush) {
SyncPoint::GetInstance()->LoadDependency({
{"DBImplFollower::TryCatchupWithLeader:Begin1", "Leader::Start"},
{"Leader::Done", "DBImplFollower::TryCatchupWithLeader:Begin2"},
{"DBImplFollower::TryCatchupWithLeader:End", "Follower::WaitForCatchup"},
});
SyncPoint::GetInstance()->EnableProcessing();
ASSERT_OK(OpenAsFollower());
TEST_SYNC_POINT("Leader::Start");
ASSERT_OK(Put("k1", "v1"));
ASSERT_OK(Flush());
TEST_SYNC_POINT("Leader::Done");
TEST_SYNC_POINT("Follower::WaitForCatchup");
std::string val;
ASSERT_OK(follower()->Get(ReadOptions(), "k1", &val));
ASSERT_EQ(val, "v1");
CheckDirs();
SyncPoint::GetInstance()->DisableProcessing();
}
// This test creates 4 L0 files, immediately followed by a compaction to L1.
// The follower replays the 4 flush records from the MANIFEST unsuccessfully,
// and then successfully recovers a Version from the compaction record
TEST_F(DBFollowerTest, RetryCatchup) {
Options opts = CurrentOptions();
opts.disable_auto_compactions = true;
Reopen(opts);
ASSERT_OK(OpenAsFollower());
SyncPoint::GetInstance()->LoadDependency({
{"DBImplFollower::TryCatchupWithLeader:Begin1", "Leader::Start"},
{"DBImpl::BackgroundCompaction:Start",
"DBImplFollower::TryCatchupWithLeader:Begin2"},
{"VersionEditHandlerPointInTime::MaybeCreateVersion:Begin1",
"DBImpl::BackgroundCompaction:BeforeCompaction"},
{"DBImpl::BackgroundCallCompaction:PurgedObsoleteFiles",
"VersionEditHandlerPointInTime::MaybeCreateVersion:Begin2"},
{"DBImplFollower::TryCatchupWithLeader:End", "Follower::WaitForCatchup"},
});
SyncPoint::GetInstance()->EnableProcessing();
TEST_SYNC_POINT("Leader::Start");
ASSERT_OK(Put("k1", "v1"));
ASSERT_OK(Flush());
ASSERT_OK(Put("k1", "v2"));
ASSERT_OK(Flush());
ASSERT_OK(Put("k1", "v3"));
ASSERT_OK(Flush());
ASSERT_OK(Put("k1", "v4"));
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr, true));
TEST_SYNC_POINT("Follower::WaitForCatchup");
ASSERT_EQ(FollowerGet("k1"), "v4");
CheckDirs();
SyncPoint::GetInstance()->ClearAllCallBacks();
SyncPoint::GetInstance()->DisableProcessing();
}
// This test validates the same as the previous test, except there is a
// MANIFEST rollover between the flushes and compaction. The follower
// does not switch to a new MANIFEST in ReadAndApply. So it would require
// another round of refresh before catching up.
TEST_F(DBFollowerTest, RetryCatchupManifestRollover) {
Options opts = CurrentOptions();
opts.disable_auto_compactions = true;
Reopen(opts);
ASSERT_OK(Put("k1", "v1"));
ASSERT_OK(Flush());
ASSERT_OK(Put("k1", "v2"));
ASSERT_OK(Flush());
ASSERT_OK(Put("k1", "v3"));
ASSERT_OK(Flush());
ASSERT_OK(OpenAsFollower());
SyncPoint::GetInstance()->LoadDependency({
{"DBImplFollower::TryCatchupWithLeader:Begin1", "Leader::Start"},
{"Leader::Flushed", "DBImplFollower::TryCatchupWithLeader:Begin2"},
{"VersionEditHandlerPointInTime::MaybeCreateVersion:Begin1",
"Leader::Done"},
{"DBImpl::BackgroundCallCompaction:PurgedObsoleteFiles",
"VersionEditHandlerPointInTime::MaybeCreateVersion:Begin2"},
{"DBImplFollower::TryCatchupWithLeader:End",
"Follower::WaitForCatchup:1"},
});
SyncPoint::GetInstance()->EnableProcessing();
TEST_SYNC_POINT("Leader::Start");
ASSERT_OK(Put("k1", "v4"));
ASSERT_OK(Flush());
TEST_SYNC_POINT("Leader::Flushed");
TEST_SYNC_POINT("Leader::Done");
Reopen(opts);
ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr, true));
TEST_SYNC_POINT("Follower::WaitForCatchup:1");
SyncPoint::GetInstance()->LoadDependency({
{"DBImplFollower::TryCatchupWithLeader:End",
"Follower::WaitForCatchup:2"},
});
TEST_SYNC_POINT("Follower::WaitForCatchup:2");
ASSERT_EQ(FollowerGet("k1"), "v4");
SyncPoint::GetInstance()->ClearAllCallBacks();
SyncPoint::GetInstance()->DisableProcessing();
}
// This test creates 4 L0 files and compacts them. The follower, during catchup,
// successfully instantiates 4 Versions corresponding to the 4 files (but
// donesn't install them yet), followed by deleting those 4 and adding a new
// file from compaction. The test verifies that the 4 L0 files are deleted
// correctly by the follower.
// We use teh Barrier* functions to ensure that the follower first sees the 4
// L0 files and is able to link them, and then sees the compaction that
// obsoletes those L0 files (so those L0 files are intermediates that it has
// to explicitly delete). Suppose we don't have any barriers, its possible
// the follower reads the L0 records and compaction records from the MANIFEST
// in one read, which means those L0 files would have already been deleted
// by the leader and the follower cannot link to them.
TEST_F(DBFollowerTest, IntermediateObsoleteFiles) {
Options opts = CurrentOptions();
opts.disable_auto_compactions = true;
Reopen(opts);
ASSERT_OK(OpenAsFollower());
follower_fs()->BarrierInit(2);
ASSERT_OK(Put("k1", "v1"));
ASSERT_OK(Flush());
ASSERT_OK(Put("k1", "v2"));
ASSERT_OK(Flush());
ASSERT_OK(Put("k1", "v3"));
ASSERT_OK(Flush());
ASSERT_OK(Put("k1", "v4"));
ASSERT_OK(Flush());
follower_fs()->BarrierWaitAndReinit(2);
ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr, true));
follower_fs()->BarrierWait();
SyncPoint::GetInstance()->LoadDependency({
{"DBImplFollower::TryCatchupWithLeader:End",
"Follower::WaitForCatchup:1"},
});
SyncPoint::GetInstance()->EnableProcessing();
TEST_SYNC_POINT("Follower::WaitForCatchup:1");
CheckDirs();
ASSERT_EQ(FollowerGet("k1"), "v4");
}
// This test verifies a scenario where the follower can recover a Version
// partially (i.e some of the additions cannot be found), and the files
// that are found are obsoleted by a subsequent VersionEdit.
TEST_F(DBFollowerTest, PartialVersionRecovery) {
Options opts = CurrentOptions();
opts.disable_auto_compactions = true;
opts.sst_partitioner_factory =
std::make_shared<DBFollowerTestSstPartitionerFactory>(1);
Reopen(opts);
ASSERT_OK(Put("k1", "v1"));
ASSERT_OK(Put("k2", "v1"));
ASSERT_OK(Put("k3", "v1"));
ASSERT_OK(Flush());
MoveFilesToLevel(2);
ASSERT_OK(Put("k1", "v2"));
ASSERT_OK(Flush());
ASSERT_OK(Put("k3", "v2"));
ASSERT_OK(Flush());
MoveFilesToLevel(1);
ASSERT_OK(OpenAsFollower());
ASSERT_OK(dbfull()->SetOptions(dbfull()->DefaultColumnFamily(),
{{"max_compaction_bytes", "1"}}));
follower_fs()->BarrierInit(2);
Slice key("k1");
ASSERT_OK(dbfull()->TEST_CompactRange(1, &key, &key, nullptr, true));
follower_fs()->BarrierWaitAndReinit(2);
// The second compaction input overlaps the previous compaction outputs
// by one file. This file is never added to VersionStorageInfo since it
// was added and deleted before the catch up completes. We later verify that
// the follower correctly deleted this file.
key = Slice("k3");
ASSERT_OK(dbfull()->TEST_CompactRange(1, &key, &key, nullptr, true));
follower_fs()->BarrierWait();
SyncPoint::GetInstance()->LoadDependency({
{"DBImplFollower::TryCatchupWithLeader:End",
"Follower::WaitForCatchup:1"},
});
SyncPoint::GetInstance()->EnableProcessing();
TEST_SYNC_POINT("Follower::WaitForCatchup:1");
CheckDirs();
ASSERT_EQ(FollowerGet("k1"), "v2");
ASSERT_EQ(FollowerGet("k2"), "v1");
ASSERT_EQ(FollowerGet("k3"), "v2");
SyncPoint::GetInstance()->DisableProcessing();
}
// This test verifies a scenario similar to the PartialVersionRecovery, except
// with a MANIFEST rollover in between. When there is a rollover, the
// follower's attempt ends without installing a new Version. The next catch up
// attempt will recover a full Version.
TEST_F(DBFollowerTest, PartialVersionRecoveryWithRollover) {
Options opts = CurrentOptions();
opts.disable_auto_compactions = true;
opts.sst_partitioner_factory =
std::make_shared<DBFollowerTestSstPartitionerFactory>(1);
Reopen(opts);
ASSERT_OK(Put("k1", "v1"));
ASSERT_OK(Put("k2", "v1"));
ASSERT_OK(Put("k3", "v1"));
ASSERT_OK(Flush());
MoveFilesToLevel(2);
ASSERT_OK(Put("k1", "v2"));
ASSERT_OK(Flush());
ASSERT_OK(Put("k3", "v2"));
ASSERT_OK(Flush());
MoveFilesToLevel(1);
opts.max_compaction_bytes = 1;
Reopen(opts);
ASSERT_OK(OpenAsFollower());
follower_fs()->BarrierInit(2);
Slice key("k1");
ASSERT_OK(dbfull()->TEST_CompactRange(1, &key, &key, nullptr, true));
follower_fs()->BarrierWaitAndReinit(2);
Reopen(opts);
key = Slice("k3");
ASSERT_OK(dbfull()->TEST_CompactRange(1, &key, &key, nullptr, true));
follower_fs()->BarrierWait();
SyncPoint::GetInstance()->LoadDependency({
{"DBImplFollower::TryCatchupWithLeader:Begin1",
"Follower::WaitForCatchup:1"},
{"Follower::WaitForCatchup:2",
"DBImplFollower::TryCatchupWithLeader:Begin2"},
});
SyncPoint::GetInstance()->EnableProcessing();
TEST_SYNC_POINT("Follower::WaitForCatchup:1");
TEST_SYNC_POINT("Follower::WaitForCatchup:2");
SyncPoint::GetInstance()->LoadDependency({
{"DBImplFollower::TryCatchupWithLeader:End",
"Follower::WaitForCatchup:3"},
});
TEST_SYNC_POINT("Follower::WaitForCatchup:3");
CheckDirs();
ASSERT_EQ(FollowerGet("k1"), "v2");
ASSERT_EQ(FollowerGet("k2"), "v1");
ASSERT_EQ(FollowerGet("k3"), "v2");
SyncPoint::GetInstance()->DisableProcessing();
}
#endif
} // namespace ROCKSDB_NAMESPACE
+143 -38
View File
@@ -17,6 +17,7 @@
#include <cstdio>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <sstream>
#include <stdexcept>
@@ -2099,7 +2100,7 @@ Status DBImpl::GetEntity(const ReadOptions& _read_options,
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
_read_options.io_activity != Env::IOActivity::kGetEntity) {
return Status::InvalidArgument(
"Cannot call GetEntity with `ReadOptions::io_activity` != "
"Can only call GetEntity with `ReadOptions::io_activity` set to "
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kGetEntity`");
}
ReadOptions read_options(_read_options);
@@ -2126,7 +2127,7 @@ Status DBImpl::GetEntity(const ReadOptions& _read_options, const Slice& key,
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
_read_options.io_activity != Env::IOActivity::kGetEntity) {
s = Status::InvalidArgument(
"Cannot call GetEntity with `ReadOptions::io_activity` != "
"Can only call GetEntity with `ReadOptions::io_activity` set to "
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kGetEntity`");
for (size_t i = 0; i < num_column_families; ++i) {
(*result)[i].SetStatus(s);
@@ -3185,22 +3186,55 @@ void DBImpl::MultiGetEntity(const ReadOptions& _read_options, size_t num_keys,
ColumnFamilyHandle** column_families,
const Slice* keys, PinnableWideColumns* results,
Status* statuses, bool sorted_input) {
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
_read_options.io_activity != Env::IOActivity::kMultiGetEntity) {
Status s = Status::InvalidArgument(
"Can only call MultiGetEntity with `ReadOptions::io_activity` is "
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kMultiGetEntity`");
assert(statuses);
if (!column_families) {
const Status s = Status::InvalidArgument(
"Cannot call MultiGetEntity without column families");
for (size_t i = 0; i < num_keys; ++i) {
if (statuses[i].ok()) {
statuses[i] = s;
}
statuses[i] = s;
}
return;
}
if (!keys) {
const Status s =
Status::InvalidArgument("Cannot call MultiGetEntity without keys");
for (size_t i = 0; i < num_keys; ++i) {
statuses[i] = s;
}
return;
}
if (!results) {
const Status s = Status::InvalidArgument(
"Cannot call MultiGetEntity without PinnableWideColumns objects");
for (size_t i = 0; i < num_keys; ++i) {
statuses[i] = s;
}
return;
}
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
_read_options.io_activity != Env::IOActivity::kMultiGetEntity) {
const Status s = Status::InvalidArgument(
"Can only call MultiGetEntity with `ReadOptions::io_activity` set to "
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kMultiGetEntity`");
for (size_t i = 0; i < num_keys; ++i) {
statuses[i] = s;
}
return;
}
ReadOptions read_options(_read_options);
if (read_options.io_activity == Env::IOActivity::kUnknown) {
read_options.io_activity = Env::IOActivity::kMultiGetEntity;
}
MultiGetCommon(read_options, num_keys, column_families, keys,
/* values */ nullptr, results, /* timestamps */ nullptr,
statuses, sorted_input);
@@ -3210,22 +3244,54 @@ void DBImpl::MultiGetEntity(const ReadOptions& _read_options,
ColumnFamilyHandle* column_family, size_t num_keys,
const Slice* keys, PinnableWideColumns* results,
Status* statuses, bool sorted_input) {
assert(statuses);
if (!column_family) {
const Status s = Status::InvalidArgument(
"Cannot call MultiGetEntity without a column family handle");
for (size_t i = 0; i < num_keys; ++i) {
statuses[i] = s;
}
return;
}
if (!keys) {
const Status s =
Status::InvalidArgument("Cannot call MultiGetEntity without keys");
for (size_t i = 0; i < num_keys; ++i) {
statuses[i] = s;
}
return;
}
if (!results) {
const Status s = Status::InvalidArgument(
"Cannot call MultiGetEntity without PinnableWideColumns objects");
for (size_t i = 0; i < num_keys; ++i) {
statuses[i] = s;
}
return;
}
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
_read_options.io_activity != Env::IOActivity::kMultiGetEntity) {
Status s = Status::InvalidArgument(
"Can only call MultiGetEntity with `ReadOptions::io_activity` is "
const Status s = Status::InvalidArgument(
"Can only call MultiGetEntity with `ReadOptions::io_activity` set to "
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kMultiGetEntity`");
for (size_t i = 0; i < num_keys; ++i) {
if (statuses[i].ok()) {
statuses[i] = s;
}
statuses[i] = s;
}
return;
}
ReadOptions read_options(_read_options);
if (read_options.io_activity == Env::IOActivity::kUnknown) {
read_options.io_activity = Env::IOActivity::kMultiGetEntity;
}
MultiGetCommon(read_options, column_family, num_keys, keys,
/* values */ nullptr, results, /* timestamps */ nullptr,
statuses, sorted_input);
@@ -3234,18 +3300,34 @@ void DBImpl::MultiGetEntity(const ReadOptions& _read_options,
void DBImpl::MultiGetEntity(const ReadOptions& _read_options, size_t num_keys,
const Slice* keys,
PinnableAttributeGroups* results) {
assert(results);
if (!keys) {
const Status s =
Status::InvalidArgument("Cannot call MultiGetEntity without keys");
for (size_t i = 0; i < num_keys; ++i) {
for (size_t j = 0; j < results[i].size(); ++j) {
results[i][j].SetStatus(s);
}
}
return;
}
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
_read_options.io_activity != Env::IOActivity::kMultiGetEntity) {
Status s = Status::InvalidArgument(
"Can only call MultiGetEntity with ReadOptions::io_activity` is "
const Status s = Status::InvalidArgument(
"Can only call MultiGetEntity with `ReadOptions::io_activity` set to "
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kMultiGetEntity`");
for (size_t i = 0; i < num_keys; ++i) {
for (size_t j = 0; j < results[i].size(); ++j) {
results[i][j].SetStatus(s);
}
}
return;
}
ReadOptions read_options(_read_options);
if (read_options.io_activity == Env::IOActivity::kUnknown) {
read_options.io_activity = Env::IOActivity::kMultiGetEntity;
@@ -3263,6 +3345,7 @@ void DBImpl::MultiGetEntity(const ReadOptions& _read_options, size_t num_keys,
++total_count;
}
}
std::vector<Status> statuses(total_count);
std::vector<PinnableWideColumns> columns(total_count);
MultiGetCommon(read_options, total_count, column_families.data(),
@@ -3283,6 +3366,15 @@ void DBImpl::MultiGetEntity(const ReadOptions& _read_options, size_t num_keys,
}
}
void DBImpl::MultiGetEntityWithCallback(
const ReadOptions& read_options, ColumnFamilyHandle* column_family,
ReadCallback* callback,
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE>* sorted_keys) {
assert(read_options.io_activity == Env::IOActivity::kMultiGetEntity);
MultiGetWithCallbackImpl(read_options, column_family, callback, sorted_keys);
}
Status DBImpl::WrapUpCreateColumnFamilies(
const ReadOptions& read_options, const WriteOptions& write_options,
const std::vector<const ColumnFamilyOptions*>& cf_options) {
@@ -5072,6 +5164,14 @@ Status DestroyDB(const std::string& dbname, const Options& options,
Env* env = soptions.env;
std::vector<std::string> filenames;
bool wal_in_db_path = soptions.IsWalDirSameAsDBPath();
auto sfm = static_cast_with_check<SstFileManagerImpl>(
options.sst_file_manager.get());
// Allocate a separate trash bucket to be used by all the to be deleted
// files, so we can later wait for this bucket to be empty before return.
std::optional<int32_t> bucket;
if (sfm) {
bucket = sfm->NewTrashBucket();
}
// Reset the logger because it holds a handle to the
// log file and prevents cleanup and directory removal
@@ -5083,6 +5183,7 @@ Status DestroyDB(const std::string& dbname, const Options& options,
/*IODebugContext*=*/nullptr)
.PermitUncheckedError();
std::set<std::string> paths_to_delete;
FileLock* lock;
const std::string lockname = LockFileName(dbname);
Status result = env->LockFile(lockname, &lock);
@@ -5099,10 +5200,9 @@ Status DestroyDB(const std::string& dbname, const Options& options,
del = DestroyDB(path_to_delete, options);
} else if (type == kTableFile || type == kWalFile ||
type == kBlobFile) {
del = DeleteDBFile(
&soptions, path_to_delete, dbname,
/*force_bg=*/false,
/*force_fg=*/(type == kWalFile) ? !wal_in_db_path : false);
del = DeleteUnaccountedDBFile(&soptions, path_to_delete, dbname,
/*force_bg=*/false,
/*force_fg=*/false, bucket);
} else {
del = env->DeleteFile(path_to_delete);
}
@@ -5111,6 +5211,7 @@ Status DestroyDB(const std::string& dbname, const Options& options,
}
}
}
paths_to_delete.insert(dbname);
std::set<std::string> paths;
for (const DbPath& db_path : options.db_paths) {
@@ -5132,18 +5233,19 @@ Status DestroyDB(const std::string& dbname, const Options& options,
(type == kTableFile ||
type == kBlobFile)) { // Lock file will be deleted at end
std::string file_path = path + "/" + fname;
Status del = DeleteDBFile(&soptions, file_path, dbname,
/*force_bg=*/false, /*force_fg=*/false);
Status del = DeleteUnaccountedDBFile(&soptions, file_path, dbname,
/*force_bg=*/false,
/*force_fg=*/false, bucket);
if (!del.ok() && result.ok()) {
result = del;
}
}
}
// TODO: Should we return an error if we cannot delete the directory?
env->DeleteDir(path).PermitUncheckedError();
}
}
paths_to_delete.merge(paths);
std::vector<std::string> walDirFiles;
std::string archivedir = ArchivalDirectory(dbname);
bool wal_dir_exists = false;
@@ -5167,46 +5269,49 @@ Status DestroyDB(const std::string& dbname, const Options& options,
// Delete archival files.
for (const auto& file : archiveFiles) {
if (ParseFileName(file, &number, &type) && type == kWalFile) {
Status del =
DeleteDBFile(&soptions, archivedir + "/" + file, archivedir,
/*force_bg=*/false, /*force_fg=*/!wal_in_db_path);
Status del = DeleteUnaccountedDBFile(
&soptions, archivedir + "/" + file, archivedir,
/*force_bg=*/false, /*force_fg=*/!wal_in_db_path, bucket);
if (!del.ok() && result.ok()) {
result = del;
}
}
}
// Ignore error in case dir contains other files
env->DeleteDir(archivedir).PermitUncheckedError();
paths_to_delete.insert(archivedir);
}
// Delete log files in the WAL dir
if (wal_dir_exists) {
for (const auto& file : walDirFiles) {
if (ParseFileName(file, &number, &type) && type == kWalFile) {
Status del =
DeleteDBFile(&soptions, LogFileName(soptions.wal_dir, number),
soptions.wal_dir, /*force_bg=*/false,
/*force_fg=*/!wal_in_db_path);
Status del = DeleteUnaccountedDBFile(
&soptions, LogFileName(soptions.wal_dir, number),
soptions.wal_dir, /*force_bg=*/false,
/*force_fg=*/!wal_in_db_path, bucket);
if (!del.ok() && result.ok()) {
result = del;
}
}
}
// Ignore error in case dir contains other files
env->DeleteDir(soptions.wal_dir).PermitUncheckedError();
paths_to_delete.insert(soptions.wal_dir);
}
// Ignore error since state is already gone
env->UnlockFile(lock).PermitUncheckedError();
env->DeleteFile(lockname).PermitUncheckedError();
// Make sure trash files are all cleared before return.
if (sfm && bucket.has_value()) {
sfm->WaitForEmptyTrashBucket(bucket.value());
}
// sst_file_manager holds a ref to the logger. Make sure the logger is
// gone before trying to remove the directory.
soptions.sst_file_manager.reset();
// Ignore error in case dir contains other files
env->DeleteDir(dbname).PermitUncheckedError();
;
for (const auto& path_to_delete : paths_to_delete) {
env->DeleteDir(path_to_delete).PermitUncheckedError();
}
}
return result;
}
+24 -11
View File
@@ -291,6 +291,11 @@ class DBImpl : public DB {
const Slice* keys,
PinnableAttributeGroups* results) override;
void MultiGetEntityWithCallback(
const ReadOptions& read_options, ColumnFamilyHandle* column_family,
ReadCallback* callback,
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE>* sorted_keys);
Status CreateColumnFamily(const ColumnFamilyOptions& cf_options,
const std::string& column_family,
ColumnFamilyHandle** handle) override {
@@ -1206,6 +1211,8 @@ class DBImpl : public DB {
return logs_.back().number;
}
void TEST_DeleteObsoleteFiles();
const std::unordered_set<uint64_t>& TEST_GetFilesGrabbedForPurge() const {
return files_grabbed_for_purge_;
}
@@ -1643,6 +1650,7 @@ class DBImpl : public DB {
friend class ForwardIterator;
friend struct SuperVersion;
friend class CompactedDBImpl;
friend class DBImplFollower;
#ifndef NDEBUG
friend class DBTest_ConcurrentFlushWAL_Test;
friend class DBTest_MixedSlowdownOptionsStop_Test;
@@ -2048,17 +2056,22 @@ class DBImpl : public DB {
mutex_.Lock();
}
if (!immutable_db_options_.unordered_write) {
// Then the writes are finished before the next write group starts
return;
if (immutable_db_options_.unordered_write) {
// Wait for the ones who already wrote to the WAL to finish their
// memtable write.
if (pending_memtable_writes_.load() != 0) {
// XXX: suspicious wait while holding DB mutex?
std::unique_lock<std::mutex> guard(switch_mutex_);
switch_cv_.wait(guard,
[&] { return pending_memtable_writes_.load() == 0; });
}
} else {
// (Writes are finished before the next write group starts.)
}
// Wait for the ones who already wrote to the WAL to finish their
// memtable write.
if (pending_memtable_writes_.load() != 0) {
std::unique_lock<std::mutex> guard(switch_mutex_);
switch_cv_.wait(guard,
[&] { return pending_memtable_writes_.load() == 0; });
// Wait for any LockWAL to clear
while (lock_wal_count_ > 0) {
bg_cv_.Wait();
}
}
@@ -2987,9 +3000,9 @@ inline Status DBImpl::FailIfReadCollapsedHistory(const ColumnFamilyData* cfd,
if (!full_history_ts_low.empty() &&
ucmp->CompareTimestamp(ts, full_history_ts_low) < 0) {
std::stringstream oss;
oss << "Read timestamp: " << ts.ToString(true)
oss << "Read timestamp: " << ucmp->TimestampToString(ts)
<< " is smaller than full_history_ts_low: "
<< Slice(full_history_ts_low).ToString(true) << std::endl;
<< ucmp->TimestampToString(full_history_ts_low) << std::endl;
return Status::InvalidArgument(oss.str());
}
return Status::OK();
+34 -14
View File
@@ -224,9 +224,15 @@ Status DBImpl::FlushMemTableToOutputFile(
// the host crashes after flushing and before WAL is persistent, the
// flushed SST may contain data from write batches whose updates to
// other (unflushed) column families are missing.
//
// When 2PC is enabled, non-recent WAL(s) may be needed for crash-recovery,
// even when there is only one CF in the DB, for prepared transactions that
// had not been committed yet. Make sure we sync them to keep the persisted
// WAL state at least as new as the persisted SST state.
const bool needs_to_sync_closed_wals =
logfile_number_ > 0 &&
versions_->GetColumnFamilySet()->NumberOfColumnFamilies() > 1;
(versions_->GetColumnFamilySet()->NumberOfColumnFamilies() > 1 ||
allow_2pc());
// If needs_to_sync_closed_wals is true, we need to record the current
// maximum memtable ID of this column family so that a later PickMemtables()
@@ -1088,7 +1094,12 @@ Status DBImpl::IncreaseFullHistoryTsLowImpl(ColumnFamilyData* cfd,
assert(ucmp->timestamp_size() == ts_low.size() && !ts_low.empty());
if (!current_ts_low.empty() &&
ucmp->CompareTimestamp(ts_low, current_ts_low) < 0) {
return Status::InvalidArgument("Cannot decrease full_history_ts_low");
std::stringstream oss;
oss << "Current full_history_ts_low: "
<< ucmp->TimestampToString(current_ts_low)
<< " is higher than provided ts: " << ucmp->TimestampToString(ts_low)
<< std::endl;
return Status::InvalidArgument(oss.str());
}
Status s = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(),
@@ -1120,6 +1131,11 @@ Status DBImpl::CompactRangeInternal(const CompactRangeOptions& options,
if (options.target_path_id >= cfd->ioptions()->cf_paths.size()) {
return Status::InvalidArgument("Invalid target path ID");
}
if (options.change_level &&
cfd->ioptions()->compaction_style == kCompactionStyleFIFO) {
return Status::NotSupported(
"FIFO compaction does not support change_level.");
}
bool flush_needed = true;
@@ -1180,6 +1196,16 @@ Status DBImpl::CompactRangeInternal(const CompactRangeOptions& options,
final_output_level, options, begin, end, exclusive,
false /* disable_trivial_move */,
std::numeric_limits<uint64_t>::max(), trim_ts);
} else if (cfd->ioptions()->compaction_style == kCompactionStyleFIFO) {
// FIFOCompactionPicker::CompactRange() will ignore the input key range
// [begin, end] and just try to pick compaction based on the configured
// option `compaction_options_fifo`. So we skip checking if [begin, end]
// overlaps with the DB here.
final_output_level = 0;
s = RunManualCompaction(cfd, /*input_level=*/0, final_output_level, options,
begin, end, exclusive,
false /* disable_trivial_move */,
std::numeric_limits<uint64_t>::max(), trim_ts);
} else {
int first_overlapped_level = kInvalidLevel;
{
@@ -1264,8 +1290,7 @@ Status DBImpl::CompactRangeInternal(const CompactRangeOptions& options,
CleanupSuperVersion(super_version);
}
if (s.ok() && first_overlapped_level != kInvalidLevel) {
if (cfd->ioptions()->compaction_style == kCompactionStyleUniversal ||
cfd->ioptions()->compaction_style == kCompactionStyleFIFO) {
if (cfd->ioptions()->compaction_style == kCompactionStyleUniversal) {
assert(first_overlapped_level == 0);
s = RunManualCompaction(
cfd, first_overlapped_level, first_overlapped_level, options, begin,
@@ -1517,16 +1542,11 @@ Status DBImpl::CompactFilesImpl(
std::to_string(cfd->ioptions()->num_levels - 1));
}
Status s = cfd->compaction_picker()->SanitizeCompactionInputFiles(
&input_set, cf_meta, output_level);
TEST_SYNC_POINT("DBImpl::CompactFilesImpl::PostSanitizeCompactionInputFiles");
if (!s.ok()) {
return s;
}
std::vector<CompactionInputFiles> input_files;
s = cfd->compaction_picker()->GetCompactionInputsFromFileNumbers(
&input_files, &input_set, version->storage_info(), compact_options);
Status s = cfd->compaction_picker()->SanitizeAndConvertCompactionInputFiles(
&input_set, cf_meta, output_level, version->storage_info(), &input_files);
TEST_SYNC_POINT(
"DBImpl::CompactFilesImpl::PostSanitizeAndConvertCompactionInputFiles");
if (!s.ok()) {
return s;
}
@@ -3340,7 +3360,7 @@ void DBImpl::BackgroundCallFlush(Env::Priority thread_pri) {
bg_cv_.SignalAll(); // In case a waiter can proceed despite the error
mutex_.Unlock();
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
"[JOB %d] Waiting after background flush error: %s"
"[JOB %d] Waiting after background flush error: %s, "
"Accumulated background error counts: %" PRIu64,
job_context.job_id, s.ToString().c_str(), error_cnt);
log_buffer.FlushBufferToLog();
+5
View File
@@ -314,6 +314,11 @@ const autovector<uint64_t>& DBImpl::TEST_GetFilesToQuarantine() const {
return error_handler_.GetFilesToQuarantine();
}
void DBImpl::TEST_DeleteObsoleteFiles() {
InstrumentedMutexLock l(&mutex_);
DeleteObsoleteFiles();
}
size_t DBImpl::TEST_EstimateInMemoryStatsHistorySize() const {
InstrumentedMutexLock l(&const_cast<DBImpl*>(this)->stats_history_mutex_);
return EstimateInMemoryStatsHistorySize();
+38 -1
View File
@@ -95,17 +95,28 @@ Status DBImplFollower::TryCatchUpWithLeader() {
assert(versions_.get() != nullptr);
assert(manifest_reader_.get() != nullptr);
Status s;
TEST_SYNC_POINT("DBImplFollower::TryCatchupWithLeader:Begin1");
TEST_SYNC_POINT("DBImplFollower::TryCatchupWithLeader:Begin2");
// read the manifest and apply new changes to the follower instance
std::unordered_set<ColumnFamilyData*> cfds_changed;
JobContext job_context(0, true /*create_superversion*/);
{
InstrumentedMutexLock lock_guard(&mutex_);
std::vector<std::string> files_to_delete;
s = static_cast_with_check<ReactiveVersionSet>(versions_.get())
->ReadAndApply(&mutex_, &manifest_reader_,
manifest_reader_status_.get(), &cfds_changed);
manifest_reader_status_.get(), &cfds_changed,
&files_to_delete);
ReleaseFileNumberFromPendingOutputs(pending_outputs_inserted_elem_);
pending_outputs_inserted_elem_.reset(new std::list<uint64_t>::iterator(
CaptureCurrentFileNumberInPendingOutputs()));
ROCKS_LOG_INFO(immutable_db_options_.info_log, "Last sequence is %" PRIu64,
static_cast<uint64_t>(versions_->LastSequence()));
ROCKS_LOG_INFO(
immutable_db_options_.info_log, "Next file number is %" PRIu64,
static_cast<uint64_t>(versions_->current_next_file_number()));
for (ColumnFamilyData* cfd : cfds_changed) {
if (cfd->IsDropped()) {
ROCKS_LOG_DEBUG(immutable_db_options_.info_log, "[%s] is dropped\n",
@@ -147,9 +158,33 @@ Status DBImplFollower::TryCatchUpWithLeader() {
sv_context.NewSuperVersion();
}
}
for (auto& file : files_to_delete) {
IOStatus io_s = fs_->DeleteFile(file, IOOptions(), nullptr);
if (!io_s.ok()) {
ROCKS_LOG_INFO(immutable_db_options_.info_log,
"Cannot delete file %s: %s", file.c_str(),
io_s.ToString().c_str());
}
}
}
job_context.Clean();
// Cleanup unused, obsolete files.
JobContext purge_files_job_context(0);
{
InstrumentedMutexLock lock_guard(&mutex_);
// Currently, follower instance does not create any database files, thus
// is unnecessary for the follower to force full scan.
FindObsoleteFiles(&purge_files_job_context, /*force=*/false);
}
if (purge_files_job_context.HaveSomethingToDelete()) {
PurgeObsoleteFiles(purge_files_job_context);
}
purge_files_job_context.Clean();
TEST_SYNC_POINT("DBImplFollower::TryCatchupWithLeader:End");
return s;
}
@@ -199,6 +234,8 @@ Status DBImplFollower::Close() {
catch_up_thread_.reset();
}
ReleaseFileNumberFromPendingOutputs(pending_outputs_inserted_elem_);
return DBImpl::Close();
}
+2 -1
View File
@@ -27,7 +27,7 @@ class DBImplFollower : public DBImplSecondary {
bool OwnTablesAndLogs() const override {
// TODO: Change this to true once we've properly implemented file
// deletion for the read scaling case
return false;
return true;
}
Status Recover(const std::vector<ColumnFamilyDescriptor>& column_families,
@@ -49,5 +49,6 @@ class DBImplFollower : public DBImplSecondary {
std::string src_path_;
port::Mutex mu_;
port::CondVar cv_;
std::unique_ptr<std::list<uint64_t>::iterator> pending_outputs_inserted_elem_;
};
} // namespace ROCKSDB_NAMESPACE
+2 -1
View File
@@ -680,7 +680,8 @@ Status DBImplSecondary::TryCatchUpWithPrimary() {
InstrumentedMutexLock lock_guard(&mutex_);
s = static_cast_with_check<ReactiveVersionSet>(versions_.get())
->ReadAndApply(&mutex_, &manifest_reader_,
manifest_reader_status_.get(), &cfds_changed);
manifest_reader_status_.get(), &cfds_changed,
/*files_to_delete=*/nullptr);
ROCKS_LOG_INFO(immutable_db_options_.info_log, "Last sequence is %" PRIu64,
static_cast<uint64_t>(versions_->LastSequence()));
+7 -1
View File
@@ -221,7 +221,11 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
return Status::InvalidArgument(
"`WriteOptions::protection_bytes_per_key` must be zero or eight");
} else if (write_options.disableWAL &&
immutable_db_options_.recycle_log_file_num > 0) {
immutable_db_options_.recycle_log_file_num > 0 &&
!(two_write_queues_ && disable_memtable)) {
// Corruption detection in recycled WALs relies on sequential sequence
// numbers, but WritePreparedTxnDB uses disableWAL internally for split
// writes
return Status::InvalidArgument(
"WriteOptions::disableWAL option is not supported if "
"DBOptions::recycle_log_file_num > 0");
@@ -2152,6 +2156,8 @@ void DBImpl::NotifyOnMemTableSealed(ColumnFamilyData* /*cfd*/,
// two_write_queues_ is true (This is to simplify the reasoning.)
Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
mutex_.AssertHeld();
assert(lock_wal_count_ == 0);
// TODO: plumb Env::IOActivity, Env::IOPriority
const ReadOptions read_options;
const WriteOptions write_options;
+10
View File
@@ -109,6 +109,16 @@ Status DBIter::GetProperty(std::string prop_name, std::string* prop) {
*prop = "Iterator is not valid.";
}
return Status::OK();
} else if (prop_name == "rocksdb.iterator.is-value-pinned") {
if (valid_) {
*prop = (pin_thru_lifetime_ && iter_.Valid() &&
iter_.value().data() == value_.data())
? "1"
: "0";
} else {
*prop = "Iterator is not valid.";
}
return Status::OK();
} else if (prop_name == "rocksdb.iterator.internal-key") {
*prop = saved_key_.GetUserKey().ToString();
return Status::OK();
+19 -4
View File
@@ -133,11 +133,17 @@ TEST_P(DBIteratorTest, IteratorProperty) {
ASSERT_NOK(iter->GetProperty("non_existing.value", &prop_value));
ASSERT_OK(iter->GetProperty("rocksdb.iterator.is-key-pinned", &prop_value));
ASSERT_EQ("0", prop_value);
ASSERT_OK(
iter->GetProperty("rocksdb.iterator.is-value-pinned", &prop_value));
ASSERT_EQ("0", prop_value);
ASSERT_OK(iter->GetProperty("rocksdb.iterator.internal-key", &prop_value));
ASSERT_EQ("1", prop_value);
iter->Next();
ASSERT_OK(iter->GetProperty("rocksdb.iterator.is-key-pinned", &prop_value));
ASSERT_EQ("Iterator is not valid.", prop_value);
ASSERT_OK(
iter->GetProperty("rocksdb.iterator.is-value-pinned", &prop_value));
ASSERT_EQ("Iterator is not valid.", prop_value);
// Get internal key at which the iteration stopped (tombstone in this case).
ASSERT_OK(iter->GetProperty("rocksdb.iterator.internal-key", &prop_value));
@@ -1680,12 +1686,15 @@ TEST_P(DBIteratorTest, PinnedDataIteratorMultipleFiles) {
ro.pin_data = true;
auto iter = NewIterator(ro);
std::vector<std::pair<Slice, std::string>> results;
std::vector<std::pair<Slice, Slice>> results;
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
std::string prop_value;
ASSERT_OK(iter->GetProperty("rocksdb.iterator.is-key-pinned", &prop_value));
ASSERT_EQ("1", prop_value);
results.emplace_back(iter->key(), iter->value().ToString());
ASSERT_OK(
iter->GetProperty("rocksdb.iterator.is-value-pinned", &prop_value));
ASSERT_EQ("1", prop_value);
results.emplace_back(iter->key(), iter->value());
}
ASSERT_EQ(results.size(), true_data.size());
@@ -1739,6 +1748,9 @@ TEST_P(DBIteratorTest, PinnedDataIteratorMergeOperator) {
std::string prop_value;
ASSERT_OK(iter->GetProperty("rocksdb.iterator.is-key-pinned", &prop_value));
ASSERT_EQ("1", prop_value);
ASSERT_OK(
iter->GetProperty("rocksdb.iterator.is-value-pinned", &prop_value));
ASSERT_EQ("0", prop_value);
results.emplace_back(iter->key(), iter->value().ToString());
}
ASSERT_OK(iter->status());
@@ -1792,12 +1804,15 @@ TEST_P(DBIteratorTest, PinnedDataIteratorReadAfterUpdate) {
}
}
std::vector<std::pair<Slice, std::string>> results;
std::vector<std::pair<Slice, Slice>> results;
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
std::string prop_value;
ASSERT_OK(iter->GetProperty("rocksdb.iterator.is-key-pinned", &prop_value));
ASSERT_EQ("1", prop_value);
results.emplace_back(iter->key(), iter->value().ToString());
ASSERT_OK(
iter->GetProperty("rocksdb.iterator.is-value-pinned", &prop_value));
ASSERT_EQ("1", prop_value);
results.emplace_back(iter->key(), iter->value());
}
ASSERT_OK(iter->status());
+2 -2
View File
@@ -732,7 +732,7 @@ TEST_P(DbMemtableKVChecksumTest, GetWithCorruptAfterMemtableInsert) {
});
SyncPoint::GetInstance()->SetCallBack(
"Memtable::SaveValue:Begin:entry", [&](void* entry) {
"Memtable::SaveValue:Found:entry", [&](void* entry) {
char* buf = *static_cast<char**>(entry);
buf[corrupt_byte_offset_] += corrupt_byte_addend_;
++corrupt_byte_offset_;
@@ -769,7 +769,7 @@ TEST_P(DbMemtableKVChecksumTest,
});
SyncPoint::GetInstance()->SetCallBack(
"Memtable::SaveValue:Begin:entry", [&](void* entry) {
"Memtable::SaveValue:Found:entry", [&](void* entry) {
char* buf = *static_cast<char**>(entry);
buf[corrupt_byte_offset_] += corrupt_byte_addend_;
++corrupt_byte_offset_;
+53 -1
View File
@@ -507,6 +507,23 @@ TEST_F(DBSSTTest, DBWithSstFileManagerForBlobFiles) {
ASSERT_EQ(files_deleted, 0);
ASSERT_EQ(files_scheduled_to_delete, 0);
Close();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"SstFileManagerImpl::ScheduleUnaccountedFileDeletion", [&](void* arg) {
assert(arg);
const std::string* const file_path =
static_cast<const std::string*>(arg);
if (EndsWith(*file_path, ".blob")) {
++files_scheduled_to_delete;
}
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"DeleteScheduler::OnDeleteFile", [&](void* arg) {
const std::string* const file_path =
static_cast<const std::string*>(arg);
if (EndsWith(*file_path, ".blob")) {
files_deleted++;
}
});
ASSERT_OK(DestroyDB(dbname_, options));
ASSERT_EQ(files_deleted, blob_files.size());
ASSERT_EQ(files_scheduled_to_delete, blob_files.size());
@@ -649,6 +666,23 @@ TEST_F(DBSSTTest, DBWithSstFileManagerForBlobFilesWithGC) {
}
Close();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"SstFileManagerImpl::ScheduleUnaccountedFileDeletion", [&](void* arg) {
assert(arg);
const std::string* const file_path =
static_cast<const std::string*>(arg);
if (EndsWith(*file_path, ".blob")) {
++files_scheduled_to_delete;
}
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"DeleteScheduler::OnDeleteFile", [&](void* arg) {
const std::string* const file_path =
static_cast<const std::string*>(arg);
if (EndsWith(*file_path, ".blob")) {
files_deleted++;
}
});
ASSERT_OK(DestroyDB(dbname_, options));
sfm->WaitForEmptyTrash();
ASSERT_EQ(files_deleted, 5);
@@ -887,7 +921,7 @@ TEST_P(DBWALTestWithParam, WALTrashCleanupOnOpen) {
// before restarting the DB.
// We have to set this on the 2nd to last file for it to delay deletion
// on the last file. (Quirk of DeleteScheduler::BackgroundEmptyTrash())
options.sst_file_manager->SetDeleteRateBytesPerSecond(1);
options.sst_file_manager->SetDeleteRateBytesPerSecond(1024 * 1024);
}
ASSERT_OK(Put("Key2", DummyString(1024, v)));
ASSERT_OK(Put("Key3", DummyString(1024, v)));
@@ -1902,6 +1936,24 @@ TEST_F(DBSSTTest, DBWithSFMForBlobFilesAtomicFlush) {
ASSERT_EQ(files_deleted, 1);
Close();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"SstFileManagerImpl::ScheduleUnaccountedFileDeletion", [&](void* arg) {
assert(arg);
const std::string* const file_path =
static_cast<const std::string*>(arg);
if (EndsWith(*file_path, ".blob")) {
++files_scheduled_to_delete;
}
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"DeleteScheduler::OnDeleteFile", [&](void* arg) {
const std::string* const file_path =
static_cast<const std::string*>(arg);
if (EndsWith(*file_path, ".blob")) {
files_deleted++;
}
});
ASSERT_OK(DestroyDB(dbname_, options));
ASSERT_EQ(files_scheduled_to_delete, 4);
+25 -17
View File
@@ -3844,6 +3844,9 @@ TEST_P(DBTestWithParam, FIFOCompactionTest) {
} else {
CompactRangeOptions cro;
cro.exclusive_manual_compaction = exclusive_manual_compaction_;
cro.change_level = true;
ASSERT_TRUE(db_->CompactRange(cro, nullptr, nullptr).IsNotSupported());
cro.change_level = false;
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
}
// only 5 files should survive
@@ -4674,24 +4677,27 @@ TEST_F(DBTest, DynamicMemtableOptions) {
#ifdef ROCKSDB_USING_THREAD_STATUS
namespace {
void VerifyOperationCount(Env* env, ThreadStatus::OperationType op_type,
bool VerifyOperationCount(Env* env, ThreadStatus::OperationType op_type,
int expected_count) {
int op_count = 0;
std::vector<ThreadStatus> thread_list;
ASSERT_OK(env->GetThreadList(&thread_list));
EXPECT_OK(env->GetThreadList(&thread_list));
for (const auto& thread : thread_list) {
if (thread.operation_type == op_type) {
op_count++;
}
}
if (op_count != expected_count) {
fprintf(stderr, "op_count: %d, expected_count %d\n", op_count,
expected_count);
for (const auto& thread : thread_list) {
fprintf(stderr, "thread id: %" PRIu64 ", thread status: %s\n",
fprintf(stderr, "thread id: %" PRIu64 ", thread status: %s, cf_name %s\n",
thread.thread_id,
thread.GetOperationName(thread.operation_type).c_str());
thread.GetOperationName(thread.operation_type).c_str(),
thread.cf_name.c_str());
}
}
ASSERT_EQ(op_count, expected_count);
return op_count == expected_count;
}
} // anonymous namespace
@@ -4791,11 +4797,11 @@ TEST_F(DBTest, ThreadStatusFlush) {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
CreateAndReopenWithCF({"pikachu"}, options);
VerifyOperationCount(env_, ThreadStatus::OP_FLUSH, 0);
ASSERT_TRUE(VerifyOperationCount(env_, ThreadStatus::OP_FLUSH, 0));
ASSERT_OK(Put(1, "foo", "v1"));
ASSERT_EQ("v1", Get(1, "foo"));
VerifyOperationCount(env_, ThreadStatus::OP_FLUSH, 0);
ASSERT_TRUE(VerifyOperationCount(env_, ThreadStatus::OP_FLUSH, 0));
uint64_t num_running_flushes = 0;
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumRunningFlushes,
@@ -4808,7 +4814,7 @@ TEST_F(DBTest, ThreadStatusFlush) {
// The first sync point is to make sure there's one flush job
// running when we perform VerifyOperationCount().
TEST_SYNC_POINT("DBTest::ThreadStatusFlush:1");
VerifyOperationCount(env_, ThreadStatus::OP_FLUSH, 1);
ASSERT_TRUE(VerifyOperationCount(env_, ThreadStatus::OP_FLUSH, 1));
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumRunningFlushes,
&num_running_flushes));
ASSERT_EQ(num_running_flushes, 1);
@@ -4819,17 +4825,11 @@ TEST_F(DBTest, ThreadStatusFlush) {
}
TEST_P(DBTestWithParam, ThreadStatusSingleCompaction) {
const int kTestKeySize = 16;
const int kTestValueSize = 984;
const int kEntrySize = kTestKeySize + kTestValueSize;
const int kEntriesPerBuffer = 100;
Options options;
options.create_if_missing = true;
options.write_buffer_size = kEntrySize * kEntriesPerBuffer;
options.compaction_style = kCompactionStyleLevel;
options.target_file_size_base = options.write_buffer_size;
options.max_bytes_for_level_base = options.target_file_size_base * 2;
options.max_bytes_for_level_multiplier = 2;
options.compression = kNoCompression;
options = CurrentOptions(options);
options.env = env_;
@@ -4864,7 +4864,7 @@ TEST_P(DBTestWithParam, ThreadStatusSingleCompaction) {
&num_running_compactions));
ASSERT_EQ(num_running_compactions, 0);
TEST_SYNC_POINT("DBTest::ThreadStatusSingleCompaction:0");
ASSERT_GE(NumTableFilesAtLevel(0),
ASSERT_EQ(NumTableFilesAtLevel(0),
options.level0_file_num_compaction_trigger);
// This makes sure at least one compaction is running.
@@ -4872,10 +4872,18 @@ TEST_P(DBTestWithParam, ThreadStatusSingleCompaction) {
if (options.enable_thread_tracking) {
// expecting one single L0 to L1 compaction
VerifyOperationCount(env_, ThreadStatus::OP_COMPACTION, 1);
// This test is flaky and fails here.
bool match = VerifyOperationCount(env_, ThreadStatus::OP_COMPACTION, 1);
if (!match) {
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumRunningCompactions,
&num_running_compactions));
fprintf(stderr, "running compaction: %" PRIu64 " lsm state: %s\n",
num_running_compactions, FilesPerLevel().c_str());
}
ASSERT_TRUE(match);
} else {
// If thread tracking is not enabled, compaction count should be 0.
VerifyOperationCount(env_, ThreadStatus::OP_COMPACTION, 0);
ASSERT_TRUE(VerifyOperationCount(env_, ThreadStatus::OP_COMPACTION, 0));
}
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumRunningCompactions,
&num_running_compactions));
+83 -22
View File
@@ -666,11 +666,25 @@ TEST_P(DBWriteTest, LockWALInEffect) {
// try the 1st WAL created during open
ASSERT_OK(Put("key0", "value"));
ASSERT_NE(options.manual_wal_flush, dbfull()->WALBufferIsEmpty());
ASSERT_OK(db_->LockWAL());
ASSERT_TRUE(dbfull()->WALBufferIsEmpty());
uint64_t wal_num = dbfull()->TEST_GetCurrentLogNumber();
// Manual flush with wait=false should abruptly fail with TryAgain
FlushOptions flush_opts;
flush_opts.wait = false;
for (bool allow_write_stall : {true, false}) {
flush_opts.allow_write_stall = allow_write_stall;
ASSERT_TRUE(db_->Flush(flush_opts).IsTryAgain());
}
ASSERT_EQ(wal_num, dbfull()->TEST_GetCurrentLogNumber());
ASSERT_OK(db_->UnlockWAL());
// try the 2nd wal created during SwitchWAL
// try the 2nd wal created during SwitchWAL (not locked this time)
ASSERT_OK(dbfull()->TEST_SwitchWAL());
ASSERT_NE(wal_num, dbfull()->TEST_GetCurrentLogNumber());
ASSERT_OK(Put("key1", "value"));
ASSERT_NE(options.manual_wal_flush, dbfull()->WALBufferIsEmpty());
ASSERT_OK(db_->LockWAL());
@@ -709,21 +723,57 @@ TEST_P(DBWriteTest, LockWALInEffect) {
}
TEST_P(DBWriteTest, LockWALConcurrentRecursive) {
// This is a micro-stress test of LockWAL and concurrency handling.
// It is considered the most convenient way to balance functional
// coverage and reproducibility (vs. the two extremes of (a) unit tests
// tailored to specific interleavings and (b) db_stress)
Options options = GetOptions();
Reopen(options);
ASSERT_OK(Put("k1", "val"));
ASSERT_OK(Put("k1", "k1_orig"));
ASSERT_OK(db_->LockWAL()); // 0 -> 1
auto frozen_seqno = db_->GetLatestSequenceNumber();
std::atomic<bool> t1_completed{false};
port::Thread t1{[&]() {
// Won't finish until WAL unlocked
ASSERT_OK(Put("k1", "val2"));
t1_completed = true;
std::string ingest_file = dbname_ + "/external.sst";
{
SstFileWriter sst_file_writer(EnvOptions(), options);
ASSERT_OK(sst_file_writer.Open(ingest_file));
ASSERT_OK(sst_file_writer.Put("k2", "k2_val"));
ExternalSstFileInfo external_info;
ASSERT_OK(sst_file_writer.Finish(&external_info));
}
AcqRelAtomic<bool> parallel_ingest_completed{false};
port::Thread parallel_ingest{[&]() {
IngestExternalFileOptions ingest_opts;
ingest_opts.move_files = true; // faster than copy
// Shouldn't finish until WAL unlocked
ASSERT_OK(db_->IngestExternalFile({ingest_file}, ingest_opts));
parallel_ingest_completed.Store(true);
}};
AcqRelAtomic<bool> flush_completed{false};
port::Thread parallel_flush{[&]() {
FlushOptions flush_opts;
// NB: Flush with wait=false case is tested above in LockWALInEffect
flush_opts.wait = true;
// allow_write_stall = true blocks in fewer cases
flush_opts.allow_write_stall = true;
// Shouldn't finish until WAL unlocked
ASSERT_OK(db_->Flush(flush_opts));
flush_completed.Store(true);
}};
AcqRelAtomic<bool> parallel_put_completed{false};
port::Thread parallel_put{[&]() {
// This can make certain failure scenarios more likely:
// sleep(1);
// Shouldn't finish until WAL unlocked
ASSERT_OK(Put("k1", "k1_mod"));
parallel_put_completed.Store(true);
}};
ASSERT_OK(db_->LockWAL()); // 1 -> 2
// Read-only ops are OK
ASSERT_EQ(Get("k1"), "val");
ASSERT_EQ(Get("k1"), "k1_orig");
{
std::vector<LiveFileStorageInfo> files;
LiveFilesStorageInfoOptions lf_opts;
@@ -732,29 +782,35 @@ TEST_P(DBWriteTest, LockWALConcurrentRecursive) {
ASSERT_OK(db_->GetLiveFilesStorageInfo({lf_opts}, &files));
}
port::Thread t2{[&]() {
port::Thread parallel_lock_wal{[&]() {
ASSERT_OK(db_->LockWAL()); // 2 -> 3 or 1 -> 2
}};
ASSERT_OK(db_->UnlockWAL()); // 2 -> 1 or 3 -> 2
// Give t1 an extra chance to jump in case of bug
// Give parallel_put an extra chance to jump in case of bug
std::this_thread::yield();
t2.join();
ASSERT_FALSE(t1_completed.load());
parallel_lock_wal.join();
ASSERT_FALSE(parallel_put_completed.Load());
ASSERT_FALSE(parallel_ingest_completed.Load());
ASSERT_FALSE(flush_completed.Load());
// Should now have 2 outstanding LockWAL
ASSERT_EQ(Get("k1"), "val");
ASSERT_EQ(Get("k1"), "k1_orig");
ASSERT_OK(db_->UnlockWAL()); // 2 -> 1
ASSERT_FALSE(t1_completed.load());
ASSERT_EQ(Get("k1"), "val");
ASSERT_FALSE(parallel_put_completed.Load());
ASSERT_FALSE(parallel_ingest_completed.Load());
ASSERT_FALSE(flush_completed.Load());
ASSERT_EQ(Get("k1"), "k1_orig");
ASSERT_EQ(Get("k2"), "NOT_FOUND");
ASSERT_EQ(frozen_seqno, db_->GetLatestSequenceNumber());
// Ensure final Unlock is concurrency safe and extra Unlock is safe but
// non-OK
std::atomic<int> unlock_ok{0};
port::Thread t3{[&]() {
port::Thread parallel_stuff{[&]() {
if (db_->UnlockWAL().ok()) {
unlock_ok++;
}
@@ -767,18 +823,23 @@ TEST_P(DBWriteTest, LockWALConcurrentRecursive) {
if (db_->UnlockWAL().ok()) {
unlock_ok++;
}
t3.join();
parallel_stuff.join();
// There was one extra unlock, so just one non-ok
ASSERT_EQ(unlock_ok.load(), 2);
// Write can proceed
t1.join();
ASSERT_TRUE(t1_completed.load());
ASSERT_EQ(Get("k1"), "val2");
parallel_put.join();
ASSERT_TRUE(parallel_put_completed.Load());
ASSERT_EQ(Get("k1"), "k1_mod");
parallel_ingest.join();
ASSERT_TRUE(parallel_ingest_completed.Load());
ASSERT_EQ(Get("k2"), "k2_val");
parallel_flush.join();
ASSERT_TRUE(flush_completed.Load());
// And new writes
ASSERT_OK(Put("k2", "val"));
ASSERT_EQ(Get("k2"), "val");
ASSERT_OK(Put("k3", "val"));
ASSERT_EQ(Get("k3"), "val");
}
TEST_P(DBWriteTest, ConcurrentlyDisabledWAL) {
+19 -4
View File
@@ -47,6 +47,8 @@ EntryType GetEntryType(ValueType value_type) {
return kEntryBlobIndex;
case kTypeWideColumnEntity:
return kEntryWideColumnEntity;
case kTypeValuePreferredSeqno:
return kEntryTimedPut;
default:
return kEntryOther;
}
@@ -155,10 +157,23 @@ void ReplaceInternalKeyWithMinTimestamp(std::string* result, const Slice& key,
result->append(key.data() + key_sz - kNumInternalBytes, kNumInternalBytes);
}
std::string ParsedInternalKey::DebugString(bool log_err_key, bool hex) const {
std::string ParsedInternalKey::DebugString(bool log_err_key, bool hex,
const Comparator* ucmp) const {
std::string result = "'";
size_t ts_sz_for_debug = ucmp == nullptr ? 0 : ucmp->timestamp_size();
if (log_err_key) {
result += user_key.ToString(hex);
if (ts_sz_for_debug == 0) {
result += user_key.ToString(hex);
} else {
assert(user_key.size() >= ts_sz_for_debug);
Slice user_key_without_ts = user_key;
user_key_without_ts.remove_suffix(ts_sz_for_debug);
result += user_key_without_ts.ToString(hex);
Slice ts = Slice(user_key.data() + user_key.size() - ts_sz_for_debug,
ts_sz_for_debug);
result += "|timestamp:";
result += ucmp->TimestampToString(ts);
}
} else {
result += "<redacted>";
}
@@ -171,11 +186,11 @@ std::string ParsedInternalKey::DebugString(bool log_err_key, bool hex) const {
return result;
}
std::string InternalKey::DebugString(bool hex) const {
std::string InternalKey::DebugString(bool hex, const Comparator* ucmp) const {
std::string result;
ParsedInternalKey parsed;
if (ParseInternalKey(rep_, &parsed, false /* log_err_key */).ok()) {
result = parsed.DebugString(true /* log_err_key */, hex); // TODO
result = parsed.DebugString(true /* log_err_key */, hex, ucmp); // TODO
} else {
result = "(bad)";
result.append(EscapeString(rep_));
+3 -2
View File
@@ -148,7 +148,8 @@ struct ParsedInternalKey {
// u contains timestamp if user timestamp feature is enabled.
ParsedInternalKey(const Slice& u, const SequenceNumber& seq, ValueType t)
: user_key(u), sequence(seq), type(t) {}
std::string DebugString(bool log_err_key, bool hex) const;
std::string DebugString(bool log_err_key, bool hex,
const Comparator* ucmp = nullptr) const;
void clear() {
user_key.clear();
@@ -503,7 +504,7 @@ class InternalKey {
AppendInternalKeyFooter(&rep_, s, t);
}
std::string DebugString(bool hex) const;
std::string DebugString(bool hex, const Comparator* ucmp = nullptr) const;
};
inline int InternalKeyComparator::Compare(const InternalKey& a,
+8
View File
@@ -30,6 +30,14 @@ class DBErrorHandlingFSTest : public DBTestBase {
fault_env_.reset(new CompositeEnvWrapper(env_, fault_fs_));
}
~DBErrorHandlingFSTest() {
// Before destroying fault_env_
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->LoadDependency({});
SyncPoint::GetInstance()->ClearAllCallBacks();
Close();
}
std::string GetManifestNameFromLiveFiles() {
std::vector<std::string> live_files;
uint64_t manifest_size;
+5 -3
View File
@@ -953,13 +953,15 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
*assigned_seqno = 0;
auto ucmp = cfd_->user_comparator();
const size_t ts_sz = ucmp->timestamp_size();
if (force_global_seqno || files_overlap_) {
if (force_global_seqno || files_overlap_ ||
compaction_style == kCompactionStyleFIFO) {
*assigned_seqno = last_seqno + 1;
// If files overlap, we have to ingest them at level 0.
if (files_overlap_) {
if (files_overlap_ || compaction_style == kCompactionStyleFIFO) {
assert(ts_sz == 0);
file_to_ingest->picked_level = 0;
if (ingestion_options_.fail_if_not_bottommost_level) {
if (ingestion_options_.fail_if_not_bottommost_level &&
cfd_->NumberLevels() > 1) {
status = Status::TryAgain(
"Files cannot be ingested to Lmax. Please make sure key range of "
"Lmax does not overlap with files to ingest.");
+62
View File
@@ -3082,6 +3082,68 @@ TEST_P(ExternalSSTFileTest,
delete iter;
}
TEST_F(ExternalSSTFileTest, FIFOCompaction) {
// FIFO always ingests SST files to L0 and assign latest sequence number.
Options options = CurrentOptions();
options.num_levels = 1;
options.compaction_style = kCompactionStyleFIFO;
options.max_open_files = -1;
DestroyAndReopen(options);
std::map<std::string, std::string> true_data;
for (int i = 0; i < 100; ++i) {
ASSERT_OK(Put(Key(i), Key(i) + "_val"));
true_data[Key(i)] = Key(i) + "_val";
}
ASSERT_OK(Flush());
ASSERT_EQ("1", FilesPerLevel());
std::vector<std::pair<std::string, std::string>> file_data;
for (int i = 0; i <= 20; i++) {
file_data.emplace_back(Key(i), Key(i) + "_ingest");
}
// Overlaps with memtable, will trigger flush
ASSERT_OK(GenerateAndAddExternalFile(options, file_data, -1,
/*allow_global_seqno=*/true, true, false,
false, false, &true_data));
ASSERT_EQ("2", FilesPerLevel());
file_data.clear();
for (int i = 100; i <= 120; i++) {
file_data.emplace_back(Key(i), Key(i) + "_ingest");
}
// global sequence number is always assigned, so this will fail
ASSERT_NOK(GenerateAndAddExternalFile(options, file_data, -1,
/*allow_global_seqno=*/false, true,
false, false, false, &true_data));
ASSERT_OK(GenerateAndAddExternalFile(options, file_data, -1,
/*allow_global_seqno=*/true, true, false,
false, false, &true_data));
// Compact to data to lower level to test multi-level FIFO later
options.num_levels = 7;
options.compaction_style = kCompactionStyleUniversal;
ASSERT_OK(TryReopen(options));
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized;
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
options.num_levels = 7;
options.compaction_style = kCompactionStyleFIFO;
ASSERT_OK(TryReopen(options));
file_data.clear();
for (int i = 200; i <= 220; i++) {
file_data.emplace_back(Key(i), Key(i) + "_ingest");
}
// Files are ingested into L0 for multi-level FIFO
ASSERT_OK(GenerateAndAddExternalFile(options, file_data, -1,
/*allow_global_seqno=*/true, true, false,
false, false, &true_data));
ASSERT_EQ("1,0,0,0,0,0,1", FilesPerLevel());
VerifyDBFromMap(true_data);
}
class ExternalSSTFileWithTimestampTest : public ExternalSSTFileTest {
public:
ExternalSSTFileWithTimestampTest() = default;
+46 -42
View File
@@ -175,22 +175,29 @@ Status ImportColumnFamilyJob::Run() {
static_cast<uint64_t>(temp_current_time);
}
// Recover files' epoch number using dummy VersionStorageInfo
VersionBuilder dummy_version_builder(
cfd_->current()->version_set()->file_options(), cfd_->ioptions(),
cfd_->table_cache(), cfd_->current()->storage_info(),
cfd_->current()->version_set(),
cfd_->GetFileMetadataCacheReservationManager());
VersionStorageInfo dummy_vstorage(
&cfd_->internal_comparator(), cfd_->user_comparator(),
cfd_->NumberLevels(), cfd_->ioptions()->compaction_style,
nullptr /* src_vstorage */, cfd_->ioptions()->force_consistency_checks,
EpochNumberRequirement::kMightMissing, cfd_->ioptions()->clock,
cfd_->GetLatestMutableCFOptions()->bottommost_file_compaction_delay,
cfd_->current()->version_set()->offpeak_time_option());
Status s;
// When importing multiple CFs, we should not reuse epoch number from ingested
// files. Since these epoch numbers were assigned by different CFs, there may
// be different files from different CFs with the same epoch number. With a
// subsequent intra-L0 compaction we may end up with files with overlapping
// key range but the same epoch number. Here we will create a dummy
// VersionStorageInfo per CF being imported. Each CF's files will be assigned
// increasing epoch numbers to avoid duplicated epoch number. This is done by
// only resetting epoch number of the new CF in the first call to
// RecoverEpochNumbers() below.
for (size_t i = 0; s.ok() && i < files_to_import_.size(); ++i) {
VersionBuilder dummy_version_builder(
cfd_->current()->version_set()->file_options(), cfd_->ioptions(),
cfd_->table_cache(), cfd_->current()->storage_info(),
cfd_->current()->version_set(),
cfd_->GetFileMetadataCacheReservationManager());
VersionStorageInfo dummy_vstorage(
&cfd_->internal_comparator(), cfd_->user_comparator(),
cfd_->NumberLevels(), cfd_->ioptions()->compaction_style,
nullptr /* src_vstorage */, cfd_->ioptions()->force_consistency_checks,
EpochNumberRequirement::kMightMissing, cfd_->ioptions()->clock,
cfd_->GetLatestMutableCFOptions()->bottommost_file_compaction_delay,
cfd_->current()->version_set()->offpeak_time_option());
for (size_t j = 0; s.ok() && j < files_to_import_[i].size(); ++j) {
const auto& f = files_to_import_[i][j];
const auto& file_metadata = *metadatas_[i][j];
@@ -218,42 +225,39 @@ Status ImportColumnFamilyJob::Run() {
f.table_properties.user_defined_timestamps_persisted));
s = dummy_version_builder.Apply(&dummy_version_edit);
}
}
if (s.ok()) {
s = dummy_version_builder.SaveTo(&dummy_vstorage);
}
if (s.ok()) {
dummy_vstorage.RecoverEpochNumbers(cfd_);
}
// Record changes from this CF import in VersionEdit, including files with
// recovered epoch numbers
if (s.ok()) {
edit_.SetColumnFamily(cfd_->GetID());
if (s.ok()) {
s = dummy_version_builder.SaveTo(&dummy_vstorage);
}
if (s.ok()) {
// force resetting epoch number for each file
dummy_vstorage.RecoverEpochNumbers(cfd_, /*restart_epoch=*/i == 0,
/*force=*/true);
edit_.SetColumnFamily(cfd_->GetID());
for (int level = 0; level < dummy_vstorage.num_levels(); level++) {
for (FileMetaData* file_meta : dummy_vstorage.LevelFiles(level)) {
edit_.AddFile(level, *file_meta);
// If incoming sequence number is higher, update local sequence
// number.
if (file_meta->fd.largest_seqno > versions_->LastSequence()) {
versions_->SetLastAllocatedSequence(file_meta->fd.largest_seqno);
versions_->SetLastPublishedSequence(file_meta->fd.largest_seqno);
versions_->SetLastSequence(file_meta->fd.largest_seqno);
}
}
}
}
// Release resources occupied by the dummy VersionStorageInfo
for (int level = 0; level < dummy_vstorage.num_levels(); level++) {
for (FileMetaData* file_meta : dummy_vstorage.LevelFiles(level)) {
edit_.AddFile(level, *file_meta);
// If incoming sequence number is higher, update local sequence number.
if (file_meta->fd.largest_seqno > versions_->LastSequence()) {
versions_->SetLastAllocatedSequence(file_meta->fd.largest_seqno);
versions_->SetLastPublishedSequence(file_meta->fd.largest_seqno);
versions_->SetLastSequence(file_meta->fd.largest_seqno);
file_meta->refs--;
if (file_meta->refs <= 0) {
delete file_meta;
}
}
}
}
// Release resources occupied by the dummy VersionStorageInfo
for (int level = 0; level < dummy_vstorage.num_levels(); level++) {
for (FileMetaData* file_meta : dummy_vstorage.LevelFiles(level)) {
file_meta->refs--;
if (file_meta->refs <= 0) {
delete file_meta;
}
}
}
return s;
}
+102
View File
@@ -946,6 +946,108 @@ TEST_F(ImportColumnFamilyTest, ImportMultiColumnFamilySeveralFilesWithOverlap) {
ASSERT_OK(db_->DestroyColumnFamilyHandle(second_cfh));
}
TEST_F(ImportColumnFamilyTest, AssignEpochNumberToMultipleCF) {
// Test ingesting CFs where L0 files could have the same epoch number.
Options options = CurrentOptions();
options.level_compaction_dynamic_level_bytes = true;
options.max_background_jobs = 8;
env_->SetBackgroundThreads(2, Env::LOW);
env_->SetBackgroundThreads(0, Env::BOTTOM);
CreateAndReopenWithCF({"CF1", "CF2"}, options);
// CF1:
// L6: [0, 99], [100, 199]
// CF2:
// L6: [1000, 1099], [1100, 1199]
for (int i = 100; i < 200; ++i) {
ASSERT_OK(Put(1, Key(i), Key(i) + "_val"));
ASSERT_OK(Put(2, Key(1000 + i), Key(1000 + i) + "_val"));
}
ASSERT_OK(Flush(1));
ASSERT_OK(Flush(2));
for (int i = 0; i < 100; ++i) {
ASSERT_OK(Put(1, Key(i), Key(i) + "_val"));
ASSERT_OK(Put(2, Key(1000 + i), Key(1000 + i) + "_val"));
}
ASSERT_OK(Flush(1));
ASSERT_OK(Flush(2));
MoveFilesToLevel(6, 1);
MoveFilesToLevel(6, 2);
// CF1:
// level 0 epoch: 5 file num 30 smallest key000010 - key000019
// level 0 epoch: 4 file num 27 smallest key000000 - key000009
// level 0 epoch: 3 file num 23 smallest key000100 - key000199
// level 6 epoch: 2 file num 20 smallest key000000 - key000099
// level 6 epoch: 1 file num 17 smallest key000100 - key000199
// CF2:
// level 0 epoch: 5 file num 31 smallest key001010 - key001019
// level 0 epoch: 4 file num 28 smallest key001000 - key001009
// level 0 epoch: 3 file num 25 smallest key001020 - key001029
// level 6 epoch: 2 file num 21 smallest key001000 - key001099
// level 6 epoch: 1 file num 18 smallest key001100 - key001199
for (int i = 100; i < 200; ++i) {
ASSERT_OK(Put(1, Key(i), Key(i) + "_val"));
}
ASSERT_OK(Flush(1));
for (int i = 20; i < 30; ++i) {
ASSERT_OK(Put(2, Key(i + 1000), Key(i + 1000) + "_val"));
}
ASSERT_OK(Flush(2));
for (int i = 0; i < 20; ++i) {
ASSERT_OK(Put(1, Key(i), Key(i) + "_val"));
ASSERT_OK(Put(2, Key(i + 1000), Key(i + 1000) + "_val"));
if (i % 10 == 9) {
ASSERT_OK(Flush(1));
ASSERT_OK(Flush(2));
}
}
ASSERT_OK(Flush(1));
ASSERT_OK(Flush(2));
// Create a CF by importing these two CF1 and CF2.
// Then two compactions will be triggerred, one to compact from L0
// to L6 (files #23 and #17), and another to do intra-L0 compaction
// for the rest of the L0 files. Before a bug fix, we used to
// directly use the epoch numbers from the ingested files in the new CF.
// This means different files from different CFs can have the same epoch
// number. If the intra-L0 compaction finishes first, it can cause a
// corruption where two L0 files can have the same epoch number but
// with overlapping key range.
Checkpoint* checkpoint1;
ASSERT_OK(Checkpoint::Create(db_, &checkpoint1));
ASSERT_OK(checkpoint1->ExportColumnFamily(handles_[1], export_files_dir_,
&metadata_ptr_));
ASSERT_OK(checkpoint1->ExportColumnFamily(handles_[2], export_files_dir2_,
&metadata_ptr2_));
ASSERT_NE(metadata_ptr_, nullptr);
ASSERT_NE(metadata_ptr2_, nullptr);
std::atomic_int compaction_counter = 0;
SyncPoint::GetInstance()->SetCallBack(
"DBImpl::BackgroundCompaction:NonTrivial:BeforeRun",
[&compaction_counter](void*) {
compaction_counter++;
if (compaction_counter == 1) {
// Wait for the next compaction to finish
TEST_SYNC_POINT("WaitForSecondCompaction");
}
});
SyncPoint::GetInstance()->LoadDependency(
{{"DBImpl::BackgroundCompaction:AfterCompaction",
"WaitForSecondCompaction"}});
SyncPoint::GetInstance()->EnableProcessing();
ImportColumnFamilyOptions import_options;
import_options.move_files = false;
std::vector<const ExportImportFilesMetaData*> metadatas = {metadata_ptr_,
metadata_ptr2_};
ASSERT_OK(db_->CreateColumnFamilyWithImport(options, "CF3", import_options,
metadatas, &import_cfh_));
WaitForCompactOptions o;
ASSERT_OK(db_->WaitForCompact(o));
delete checkpoint1;
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+9 -4
View File
@@ -487,9 +487,11 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size,
type == kRecyclableUserDefinedTimestampSizeType);
if (is_recyclable_type) {
header_size = kRecyclableHeaderSize;
if (end_of_buffer_offset_ - buffer_.size() == 0) {
recycled_ = true;
if (first_record_read_ && !recycled_) {
// A recycled log should have started with a recycled record
return kBadRecord;
}
recycled_ = true;
// We need enough for the larger header
if (buffer_.size() < static_cast<size_t>(kRecyclableHeaderSize)) {
int r = kEof;
@@ -867,9 +869,12 @@ bool FragmentBufferedReader::TryReadFragment(
int header_size = kHeaderSize;
if ((type >= kRecyclableFullType && type <= kRecyclableLastType) ||
type == kRecyclableUserDefinedTimestampSizeType) {
if (end_of_buffer_offset_ - buffer_.size() == 0) {
recycled_ = true;
if (first_record_read_ && !recycled_) {
// A recycled log should have started with a recycled record
*fragment_type_or_err = kBadRecord;
return true;
}
recycled_ = true;
header_size = kRecyclableHeaderSize;
while (buffer_.size() < static_cast<size_t>(kRecyclableHeaderSize)) {
size_t old_size = buffer_.size();
+62
View File
@@ -774,6 +774,32 @@ TEST_P(LogTest, RecycleWithTimestampSize) {
ASSERT_EQ("EOF", Read());
}
// Validates that `MaybeAddUserDefinedTimestampSizeRecord`` adds padding to the
// tail of a block and switches to a new block, if there's not enough space for
// the record.
TEST_P(LogTest, TimestampSizeRecordPadding) {
bool recyclable_log = (std::get<0>(GetParam()) != 0);
const size_t header_size =
recyclable_log ? kRecyclableHeaderSize : kHeaderSize;
const size_t data_len = kBlockSize - 2 * header_size;
const auto first_str = BigString("foo", data_len);
Write(first_str);
UnorderedMap<uint32_t, size_t> ts_sz = {
{2, sizeof(uint64_t)},
};
ASSERT_OK(
writer_->MaybeAddUserDefinedTimestampSizeRecord(WriteOptions(), ts_sz));
ASSERT_LT(writer_->TEST_block_offset(), kBlockSize);
const auto second_str = BigString("bar", 1000);
Write(second_str);
ASSERT_EQ(first_str, Read());
CheckRecordAndTimestampSize(second_str, ts_sz);
}
// Do NOT enable compression for this instantiation.
INSTANTIATE_TEST_CASE_P(
Log, LogTest,
@@ -1131,6 +1157,42 @@ TEST_P(CompressionLogTest, AlignedFragmentation) {
ASSERT_EQ("EOF", Read());
}
TEST_P(CompressionLogTest, ChecksumMismatch) {
const CompressionType kCompressionType = std::get<2>(GetParam());
const bool kCompressionEnabled = kCompressionType != kNoCompression;
const bool kRecyclableLog = (std::get<0>(GetParam()) != 0);
if (!StreamingCompressionTypeSupported(kCompressionType)) {
ROCKSDB_GTEST_SKIP("Test requires support for compression type");
return;
}
ASSERT_OK(SetupTestEnv());
Write("foooooo");
int header_len;
if (kRecyclableLog) {
header_len = kRecyclableHeaderSize;
} else {
header_len = kHeaderSize;
}
int compression_record_len;
if (kCompressionEnabled) {
compression_record_len = header_len + 4;
} else {
compression_record_len = 0;
}
IncrementByte(compression_record_len + header_len /* offset */,
14 /* delta */);
ASSERT_EQ("EOF", Read());
if (!kRecyclableLog) {
ASSERT_GT(DroppedBytes(), 0U);
ASSERT_EQ("OK", MatchError("checksum mismatch"));
} else {
ASSERT_EQ(0U, DroppedBytes());
ASSERT_EQ("", ReportMessage());
}
}
INSTANTIATE_TEST_CASE_P(
Compression, CompressionLogTest,
::testing::Combine(::testing::Values(0, 1), ::testing::Bool(),
+26 -10
View File
@@ -27,6 +27,8 @@ Writer::Writer(std::unique_ptr<WritableFileWriter>&& dest, uint64_t log_number,
block_offset_(0),
log_number_(log_number),
recycle_log_files_(recycle_log_files),
// Header size varies depending on whether we are recycling or not.
header_size_(recycle_log_files ? kRecyclableHeaderSize : kHeaderSize),
manual_flush_(manual_flush),
compression_type_(compression_type),
compress_(nullptr) {
@@ -80,10 +82,6 @@ IOStatus Writer::AddRecord(const WriteOptions& write_options,
const char* ptr = slice.data();
size_t left = slice.size();
// Header size varies depending on whether we are recycling or not.
const int header_size =
recycle_log_files_ ? kRecyclableHeaderSize : kHeaderSize;
// Fragment the record if necessary and emit it. Note that if slice
// is empty, we still want to iterate once to emit a single
// zero-length record
@@ -102,12 +100,12 @@ IOStatus Writer::AddRecord(const WriteOptions& write_options,
do {
const int64_t leftover = kBlockSize - block_offset_;
assert(leftover >= 0);
if (leftover < header_size) {
if (leftover < header_size_) {
// Switch to a new block
if (leftover > 0) {
// Fill the trailer (literal below relies on kHeaderSize and
// kRecyclableHeaderSize being <= 11)
assert(header_size <= 11);
assert(header_size_ <= 11);
s = dest_->Append(opts,
Slice("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
static_cast<size_t>(leftover)),
@@ -120,9 +118,9 @@ IOStatus Writer::AddRecord(const WriteOptions& write_options,
}
// Invariant: we never leave < header_size bytes in a block.
assert(static_cast<int64_t>(kBlockSize - block_offset_) >= header_size);
assert(static_cast<int64_t>(kBlockSize - block_offset_) >= header_size_);
const size_t avail = kBlockSize - block_offset_ - header_size;
const size_t avail = kBlockSize - block_offset_ - header_size_;
// Compress the record if compression is enabled.
// Compress() is called at least once (compress_start=true) and after the
@@ -203,8 +201,7 @@ IOStatus Writer::AddCompressionTypeRecord(const WriteOptions& write_options) {
}
}
// Initialize fields required for compression
const size_t max_output_buffer_len =
kBlockSize - (recycle_log_files_ ? kRecyclableHeaderSize : kHeaderSize);
const size_t max_output_buffer_len = kBlockSize - header_size_;
CompressionOptions opts;
constexpr uint32_t compression_format_version = 2;
compress_ = StreamingCompress::Create(compression_type_, opts,
@@ -244,6 +241,25 @@ IOStatus Writer::MaybeAddUserDefinedTimestampSizeRecord(
record.EncodeTo(&encoded);
RecordType type = recycle_log_files_ ? kRecyclableUserDefinedTimestampSizeType
: kUserDefinedTimestampSizeType;
// If there's not enough space for this record, switch to a new block.
const int64_t leftover = kBlockSize - block_offset_;
if (leftover < header_size_ + (int)encoded.size()) {
IOOptions opts;
IOStatus s = WritableFileWriter::PrepareIOOptions(write_options, opts);
if (!s.ok()) {
return s;
}
std::vector<char> trailer(leftover, '\x00');
s = dest_->Append(opts, Slice(trailer.data(), trailer.size()));
if (!s.ok()) {
return s;
}
block_offset_ = 0;
}
return EmitPhysicalRecord(write_options, type, encoded.data(),
encoded.size());
}
+3
View File
@@ -109,11 +109,14 @@ class Writer {
bool BufferIsEmpty();
size_t TEST_block_offset() const { return block_offset_; }
private:
std::unique_ptr<WritableFileWriter> dest_;
size_t block_offset_; // Current offset in block
uint64_t log_number_;
bool recycle_log_files_;
int header_size_;
// crc32c values for all supported record types. These are
// pre-computed to reduce the overhead of computing the crc of the
+17 -36
View File
@@ -928,21 +928,10 @@ struct Saver {
} // anonymous namespace
static bool SaveValue(void* arg, const char* entry) {
TEST_SYNC_POINT_CALLBACK("Memtable::SaveValue:Begin:entry", &entry);
Saver* s = static_cast<Saver*>(arg);
assert(s != nullptr);
assert(!s->value || !s->columns);
if (s->protection_bytes_per_key > 0) {
*(s->status) = MemTable::VerifyEntryChecksum(
entry, s->protection_bytes_per_key, s->allow_data_in_errors);
if (!s->status->ok()) {
ROCKS_LOG_ERROR(s->logger, "In SaveValue: %s", s->status->getState());
// Memtable entry corrupted
return false;
}
}
MergeContext* merge_context = s->merge_context;
SequenceNumber max_covering_tombstone_seq = s->max_covering_tombstone_seq;
const MergeOperator* merge_operator = s->merge_operator;
@@ -965,6 +954,22 @@ static bool SaveValue(void* arg, const char* entry) {
if (user_comparator->EqualWithoutTimestamp(user_key_slice,
s->key->user_key())) {
// Correct user key
TEST_SYNC_POINT_CALLBACK("Memtable::SaveValue:Found:entry", &entry);
std::unique_ptr<ReadLock> read_lock;
if (s->inplace_update_support) {
read_lock.reset(new ReadLock(s->mem->GetLock(s->key->user_key())));
}
if (s->protection_bytes_per_key > 0) {
*(s->status) = MemTable::VerifyEntryChecksum(
entry, s->protection_bytes_per_key, s->allow_data_in_errors);
if (!s->status->ok()) {
ROCKS_LOG_ERROR(s->logger, "In SaveValue: %s", s->status->getState());
// Memtable entry corrupted
return false;
}
}
const uint64_t tag = DecodeFixed64(key_ptr + key_length - 8);
ValueType type;
SequenceNumber seq;
@@ -1035,10 +1040,6 @@ static bool SaveValue(void* arg, const char* entry) {
return false;
}
if (s->inplace_update_support) {
s->mem->GetLock(s->key->user_key())->ReadLock();
}
Slice v = GetLengthPrefixedSlice(key_ptr + key_length);
*(s->status) = Status::OK();
@@ -1049,10 +1050,6 @@ static bool SaveValue(void* arg, const char* entry) {
s->columns->SetPlainValue(v);
}
if (s->inplace_update_support) {
s->mem->GetLock(s->key->user_key())->ReadUnlock();
}
*(s->found_final_value) = true;
*(s->is_blob_index) = true;
@@ -1060,10 +1057,6 @@ static bool SaveValue(void* arg, const char* entry) {
}
case kTypeValue:
case kTypeValuePreferredSeqno: {
if (s->inplace_update_support) {
s->mem->GetLock(s->key->user_key())->ReadLock();
}
Slice v = GetLengthPrefixedSlice(key_ptr + key_length);
if (type == kTypeValuePreferredSeqno) {
@@ -1100,10 +1093,6 @@ static bool SaveValue(void* arg, const char* entry) {
s->columns->SetPlainValue(v);
}
if (s->inplace_update_support) {
s->mem->GetLock(s->key->user_key())->ReadUnlock();
}
*(s->found_final_value) = true;
if (s->is_blob_index != nullptr) {
@@ -1113,10 +1102,6 @@ static bool SaveValue(void* arg, const char* entry) {
return false;
}
case kTypeWideColumnEntity: {
if (s->inplace_update_support) {
s->mem->GetLock(s->key->user_key())->ReadLock();
}
Slice v = GetLengthPrefixedSlice(key_ptr + key_length);
*(s->status) = Status::OK();
@@ -1158,10 +1143,6 @@ static bool SaveValue(void* arg, const char* entry) {
*(s->status) = s->columns->SetWideColumnValue(v);
}
if (s->inplace_update_support) {
s->mem->GetLock(s->key->user_key())->ReadUnlock();
}
*(s->found_final_value) = true;
if (s->is_blob_index != nullptr) {
@@ -1499,9 +1480,9 @@ Status MemTable::Update(SequenceNumber seq, ValueType value_type,
// Update value, if new value size <= previous value size
if (new_size <= prev_size) {
WriteLock wl(GetLock(lkey.user_key()));
char* p =
EncodeVarint32(const_cast<char*>(key_ptr) + key_length, new_size);
WriteLock wl(GetLock(lkey.user_key()));
memcpy(p, value.data(), value.size());
assert((unsigned)((p + value.size()) - entry) ==
(unsigned)(VarintLength(key_length) + key_length +
+39 -5
View File
@@ -152,7 +152,7 @@ Status FileChecksumRetriever::ApplyVersionEdit(VersionEdit& edit,
VersionEditHandler::VersionEditHandler(
bool read_only, std::vector<ColumnFamilyDescriptor> column_families,
VersionSet* version_set, bool track_missing_files,
VersionSet* version_set, bool track_found_and_missing_files,
bool no_error_if_files_missing, const std::shared_ptr<IOTracer>& io_tracer,
const ReadOptions& read_options, bool skip_load_table_files,
EpochNumberRequirement epoch_number_requirement)
@@ -160,7 +160,7 @@ VersionEditHandler::VersionEditHandler(
read_only_(read_only),
column_families_(std::move(column_families)),
version_set_(version_set),
track_missing_files_(track_missing_files),
track_found_and_missing_files_(track_found_and_missing_files),
no_error_if_files_missing_(no_error_if_files_missing),
io_tracer_(io_tracer),
skip_load_table_files_(skip_load_table_files),
@@ -500,7 +500,8 @@ ColumnFamilyData* VersionEditHandler::CreateCfAndInit(
assert(builders_.find(cf_id) == builders_.end());
builders_.emplace(cf_id,
VersionBuilderUPtr(new BaseReferencedVersionBuilder(cfd)));
if (track_missing_files_) {
if (track_found_and_missing_files_) {
cf_to_found_files_.emplace(cf_id, std::unordered_set<uint64_t>());
cf_to_missing_files_.emplace(cf_id, std::unordered_set<uint64_t>());
cf_to_missing_blob_files_high_.emplace(cf_id, kInvalidBlobFileNumber);
}
@@ -513,7 +514,11 @@ ColumnFamilyData* VersionEditHandler::DestroyCfAndCleanup(
auto builder_iter = builders_.find(cf_id);
assert(builder_iter != builders_.end());
builders_.erase(builder_iter);
if (track_missing_files_) {
if (track_found_and_missing_files_) {
auto found_files_iter = cf_to_found_files_.find(cf_id);
assert(found_files_iter != cf_to_found_files_.end());
cf_to_found_files_.erase(found_files_iter);
auto missing_files_iter = cf_to_missing_files_.find(cf_id);
assert(missing_files_iter != cf_to_missing_files_.end());
cf_to_missing_files_.erase(missing_files_iter);
@@ -729,7 +734,7 @@ VersionEditHandlerPointInTime::VersionEditHandlerPointInTime(
const ReadOptions& read_options,
EpochNumberRequirement epoch_number_requirement)
: VersionEditHandler(read_only, column_families, version_set,
/*track_missing_files=*/true,
/*track_found_and_missing_files=*/true,
/*no_error_if_files_missing=*/true, io_tracer,
read_options, epoch_number_requirement) {}
@@ -824,6 +829,12 @@ void VersionEditHandlerPointInTime::CheckIterationResult(
version_set_->AppendVersion(cfd, v_iter->second);
versions_.erase(v_iter);
// Let's clear found_files, since any files in that are part of the
// installed Version. Any files that got obsoleted would have already
// been moved to intermediate_files_
auto found_files_iter = cf_to_found_files_.find(cfd->GetID());
assert(found_files_iter != cf_to_found_files_.end());
found_files_iter->second.clear();
}
}
} else {
@@ -854,10 +865,16 @@ ColumnFamilyData* VersionEditHandlerPointInTime::DestroyCfAndCleanup(
Status VersionEditHandlerPointInTime::MaybeCreateVersion(
const VersionEdit& edit, ColumnFamilyData* cfd, bool force_create_version) {
TEST_SYNC_POINT("VersionEditHandlerPointInTime::MaybeCreateVersion:Begin1");
TEST_SYNC_POINT("VersionEditHandlerPointInTime::MaybeCreateVersion:Begin2");
assert(cfd != nullptr);
if (!force_create_version) {
assert(edit.GetColumnFamily() == cfd->GetID());
}
auto found_files_iter = cf_to_found_files_.find(cfd->GetID());
assert(found_files_iter != cf_to_found_files_.end());
std::unordered_set<uint64_t>& found_files = found_files_iter->second;
auto missing_files_iter = cf_to_missing_files_.find(cfd->GetID());
assert(missing_files_iter != cf_to_missing_files_.end());
std::unordered_set<uint64_t>& missing_files = missing_files_iter->second;
@@ -889,6 +906,18 @@ Status VersionEditHandlerPointInTime::MaybeCreateVersion(
auto fiter = missing_files.find(file_num);
if (fiter != missing_files.end()) {
missing_files.erase(fiter);
} else {
fiter = found_files.find(file_num);
// Only mark new files added during this catchup attempt for deletion.
// These files were never installed in VersionStorageInfo.
// Already referenced files that are deleted by a VersionEdit will
// be added to the VersionStorageInfo's obsolete files when the old
// version is dereferenced.
if (fiter != found_files.end()) {
intermediate_files_.emplace_back(
MakeTableFileName(cfd->ioptions()->cf_paths[0].path, file_num));
found_files.erase(fiter);
}
}
}
@@ -904,9 +933,14 @@ Status VersionEditHandlerPointInTime::MaybeCreateVersion(
s = VerifyFile(cfd, fpath, level, meta);
if (s.IsPathNotFound() || s.IsNotFound() || s.IsCorruption()) {
missing_files.insert(file_num);
if (s.IsCorruption()) {
found_files.insert(file_num);
}
s = Status::OK();
} else if (!s.ok()) {
break;
} else {
found_files.insert(file_num);
}
}
+17 -9
View File
@@ -104,7 +104,7 @@ using VersionBuilderUPtr = std::unique_ptr<BaseReferencedVersionBuilder>;
// To use this class and its subclasses,
// 1. Create an object of VersionEditHandler or its subclasses.
// VersionEditHandler handler(read_only, column_families, version_set,
// track_missing_files,
// track_found_and_missing_files,
// no_error_if_files_missing);
// 2. Status s = handler.Iterate(reader, &db_id);
// 3. Check s and handle possible errors.
@@ -116,16 +116,17 @@ class VersionEditHandler : public VersionEditHandlerBase {
explicit VersionEditHandler(
bool read_only,
const std::vector<ColumnFamilyDescriptor>& column_families,
VersionSet* version_set, bool track_missing_files,
VersionSet* version_set, bool track_found_and_missing_files,
bool no_error_if_files_missing,
const std::shared_ptr<IOTracer>& io_tracer,
const ReadOptions& read_options,
EpochNumberRequirement epoch_number_requirement =
EpochNumberRequirement::kMustPresent)
: VersionEditHandler(
read_only, column_families, version_set, track_missing_files,
no_error_if_files_missing, io_tracer, read_options,
/*skip_load_table_files=*/false, epoch_number_requirement) {}
: VersionEditHandler(read_only, column_families, version_set,
track_found_and_missing_files,
no_error_if_files_missing, io_tracer, read_options,
/*skip_load_table_files=*/false,
epoch_number_requirement) {}
~VersionEditHandler() override {}
@@ -144,7 +145,7 @@ class VersionEditHandler : public VersionEditHandlerBase {
protected:
explicit VersionEditHandler(
bool read_only, std::vector<ColumnFamilyDescriptor> column_families,
VersionSet* version_set, bool track_missing_files,
VersionSet* version_set, bool track_found_and_missing_files,
bool no_error_if_files_missing,
const std::shared_ptr<IOTracer>& io_tracer,
const ReadOptions& read_options, bool skip_load_table_files,
@@ -195,7 +196,8 @@ class VersionEditHandler : public VersionEditHandlerBase {
// by subsequent manifest records, Recover() will return failure status.
std::unordered_map<uint32_t, std::string> column_families_not_found_;
VersionEditParams version_edit_params_;
const bool track_missing_files_;
const bool track_found_and_missing_files_;
std::unordered_map<uint32_t, std::unordered_set<uint64_t>> cf_to_found_files_;
std::unordered_map<uint32_t, std::unordered_set<uint64_t>>
cf_to_missing_files_;
std::unordered_map<uint32_t, uint64_t> cf_to_missing_blob_files_high_;
@@ -273,6 +275,8 @@ class VersionEditHandlerPointInTime : public VersionEditHandler {
bool in_atomic_group_ = false;
std::vector<std::string> intermediate_files_;
private:
bool AtomicUpdateVersionsCompleted();
bool AtomicUpdateVersionsContains(uint32_t cfid);
@@ -310,6 +314,10 @@ class ManifestTailer : public VersionEditHandlerPointInTime {
return cfds_changed_;
}
std::vector<std::string>& GetIntermediateFiles() {
return intermediate_files_;
}
protected:
Status Initialize() override;
@@ -342,7 +350,7 @@ class DumpManifestHandler : public VersionEditHandler {
bool json)
: VersionEditHandler(
/*read_only=*/true, column_families, version_set,
/*track_missing_files=*/false,
/*track_found_and_missing_files=*/false,
/*no_error_if_files_missing=*/false, io_tracer, read_options,
/*skip_load_table_files=*/true),
verbose_(verbose),
+38 -25
View File
@@ -3130,6 +3130,10 @@ bool Version::MaybeInitializeFileMetaData(const ReadOptions& read_options,
file_meta->raw_value_size = tp->raw_value_size;
file_meta->raw_key_size = tp->raw_key_size;
file_meta->num_range_deletions = tp->num_range_deletions;
// Ensure new invariants on old files
file_meta->num_deletions =
std::max(tp->num_deletions, tp->num_range_deletions);
file_meta->num_entries = std::max(tp->num_entries, tp->num_deletions);
return true;
}
@@ -3141,6 +3145,7 @@ void VersionStorageInfo::UpdateAccumulatedStats(FileMetaData* file_meta) {
accumulated_file_size_ += file_meta->fd.GetFileSize();
accumulated_raw_key_size_ += file_meta->raw_key_size;
accumulated_raw_value_size_ += file_meta->raw_value_size;
assert(file_meta->num_entries >= file_meta->num_deletions);
accumulated_num_non_deletions_ +=
file_meta->num_entries - file_meta->num_deletions;
accumulated_num_deletions_ += file_meta->num_deletions;
@@ -4612,25 +4617,27 @@ uint64_t VersionStorageInfo::GetMaxEpochNumberOfFiles() const {
return max_epoch_number;
}
void VersionStorageInfo::RecoverEpochNumbers(ColumnFamilyData* cfd) {
cfd->ResetNextEpochNumber();
void VersionStorageInfo::RecoverEpochNumbers(ColumnFamilyData* cfd,
bool restart_epoch, bool force) {
if (restart_epoch) {
cfd->ResetNextEpochNumber();
bool reserve_epoch_num_for_file_ingested_behind =
cfd->ioptions()->allow_ingest_behind;
if (reserve_epoch_num_for_file_ingested_behind) {
uint64_t reserved_epoch_number = cfd->NewEpochNumber();
assert(reserved_epoch_number == kReservedEpochNumberForFileIngestedBehind);
ROCKS_LOG_INFO(cfd->ioptions()->info_log.get(),
"[%s]CF has reserved epoch number %" PRIu64
" for files ingested "
"behind since `Options::allow_ingest_behind` is true",
cfd->GetName().c_str(), reserved_epoch_number);
bool reserve_epoch_num_for_file_ingested_behind =
cfd->ioptions()->allow_ingest_behind;
if (reserve_epoch_num_for_file_ingested_behind) {
uint64_t reserved_epoch_number = cfd->NewEpochNumber();
assert(reserved_epoch_number ==
kReservedEpochNumberForFileIngestedBehind);
ROCKS_LOG_INFO(cfd->ioptions()->info_log.get(),
"[%s]CF has reserved epoch number %" PRIu64
" for files ingested "
"behind since `Options::allow_ingest_behind` is true",
cfd->GetName().c_str(), reserved_epoch_number);
}
}
if (HasMissingEpochNumber()) {
assert(epoch_number_requirement_ == EpochNumberRequirement::kMightMissing);
assert(num_levels_ >= 1);
bool missing_epoch_number = HasMissingEpochNumber();
if (missing_epoch_number || force) {
for (int level = num_levels_ - 1; level >= 1; --level) {
auto& files_at_level = files_[level];
if (files_at_level.empty()) {
@@ -4641,17 +4648,19 @@ void VersionStorageInfo::RecoverEpochNumbers(ColumnFamilyData* cfd) {
f->epoch_number = next_epoch_number;
}
}
for (auto file_meta_iter = files_[0].rbegin();
file_meta_iter != files_[0].rend(); file_meta_iter++) {
FileMetaData* f = *file_meta_iter;
f->epoch_number = cfd->NewEpochNumber();
}
ROCKS_LOG_WARN(cfd->ioptions()->info_log.get(),
"[%s]CF's epoch numbers are inferred based on seqno",
cfd->GetName().c_str());
epoch_number_requirement_ = EpochNumberRequirement::kMustPresent;
if (missing_epoch_number) {
assert(epoch_number_requirement_ ==
EpochNumberRequirement::kMightMissing);
ROCKS_LOG_WARN(cfd->ioptions()->info_log.get(),
"[%s]CF's epoch numbers are inferred based on seqno",
cfd->GetName().c_str());
epoch_number_requirement_ = EpochNumberRequirement::kMustPresent;
}
} else {
assert(epoch_number_requirement_ == EpochNumberRequirement::kMustPresent);
cfd->SetNextEpochNumber(
@@ -6054,8 +6063,8 @@ Status VersionSet::Recover(
true /* checksum */, 0 /* log_number */);
VersionEditHandler handler(
read_only, column_families, const_cast<VersionSet*>(this),
/*track_missing_files=*/false, no_error_if_files_missing, io_tracer_,
read_options, EpochNumberRequirement::kMightMissing);
/*track_found_and_missing_files=*/false, no_error_if_files_missing,
io_tracer_, read_options, EpochNumberRequirement::kMightMissing);
handler.Iterate(reader, &log_read_status);
s = handler.status();
if (s.ok()) {
@@ -7430,7 +7439,8 @@ Status ReactiveVersionSet::ReadAndApply(
InstrumentedMutex* mu,
std::unique_ptr<log::FragmentBufferedReader>* manifest_reader,
Status* manifest_read_status,
std::unordered_set<ColumnFamilyData*>* cfds_changed) {
std::unordered_set<ColumnFamilyData*>* cfds_changed,
std::vector<std::string>* files_to_delete) {
assert(manifest_reader != nullptr);
assert(cfds_changed != nullptr);
mu->AssertHeld();
@@ -7447,6 +7457,9 @@ Status ReactiveVersionSet::ReadAndApply(
if (s.ok()) {
*cfds_changed = std::move(manifest_tailer_->GetUpdatedColumnFamilies());
}
if (files_to_delete) {
*files_to_delete = std::move(manifest_tailer_->GetIntermediateFiles());
}
return s;
}
+11 -2
View File
@@ -341,7 +341,15 @@ class VersionStorageInfo {
EpochNumberRequirement epoch_number_requirement) {
epoch_number_requirement_ = epoch_number_requirement;
}
void RecoverEpochNumbers(ColumnFamilyData* cfd);
// Ensure all files have epoch number set.
// If there is a file missing epoch number, all files' epoch number will be
// reset according to CF's epoch number. Otherwise, the CF will be updated
// with the max epoch number of the files.
//
// @param restart_epoch This CF's epoch number will be reset to start from 0.
// @param force Force resetting all files' epoch number.
void RecoverEpochNumbers(ColumnFamilyData* cfd, bool restart_epoch = true,
bool force = false);
class FileLocation {
public:
@@ -1733,7 +1741,8 @@ class ReactiveVersionSet : public VersionSet {
InstrumentedMutex* mu,
std::unique_ptr<log::FragmentBufferedReader>* manifest_reader,
Status* manifest_read_status,
std::unordered_set<ColumnFamilyData*>* cfds_changed);
std::unordered_set<ColumnFamilyData*>* cfds_changed,
std::vector<std::string>* files_to_delete);
Status Recover(const std::vector<ColumnFamilyDescriptor>& column_families,
std::unique_ptr<log::FragmentBufferedReader>* manifest_reader,
+10 -5
View File
@@ -2742,7 +2742,8 @@ TEST_F(VersionSetAtomicGroupTest,
std::unordered_set<ColumnFamilyData*> cfds_changed;
mu.Lock();
EXPECT_OK(reactive_versions_->ReadAndApply(
&mu, &manifest_reader, manifest_reader_status.get(), &cfds_changed));
&mu, &manifest_reader, manifest_reader_status.get(), &cfds_changed,
/*files_to_delete=*/nullptr));
mu.Unlock();
EXPECT_TRUE(first_in_atomic_group_);
EXPECT_TRUE(last_in_atomic_group_);
@@ -2797,7 +2798,8 @@ TEST_F(VersionSetAtomicGroupTest,
std::unordered_set<ColumnFamilyData*> cfds_changed;
mu.Lock();
EXPECT_OK(reactive_versions_->ReadAndApply(
&mu, &manifest_reader, manifest_reader_status.get(), &cfds_changed));
&mu, &manifest_reader, manifest_reader_status.get(), &cfds_changed,
/*files_to_delete=*/nullptr));
mu.Unlock();
// Reactive version set should be empty now.
EXPECT_TRUE(reactive_versions_->TEST_read_edits_in_atomic_group() == 0);
@@ -2826,7 +2828,8 @@ TEST_F(VersionSetAtomicGroupTest,
std::unordered_set<ColumnFamilyData*> cfds_changed;
mu.Lock();
EXPECT_OK(reactive_versions_->ReadAndApply(
&mu, &manifest_reader, manifest_reader_status.get(), &cfds_changed));
&mu, &manifest_reader, manifest_reader_status.get(), &cfds_changed,
/*files_to_delete=*/nullptr));
mu.Unlock();
EXPECT_TRUE(first_in_atomic_group_);
EXPECT_FALSE(last_in_atomic_group_);
@@ -2882,7 +2885,8 @@ TEST_F(VersionSetAtomicGroupTest,
AddNewEditsToLog(kAtomicGroupSize);
mu.Lock();
EXPECT_NOK(reactive_versions_->ReadAndApply(
&mu, &manifest_reader, manifest_reader_status.get(), &cfds_changed));
&mu, &manifest_reader, manifest_reader_status.get(), &cfds_changed,
/*files_to_delete=*/nullptr));
mu.Unlock();
EXPECT_EQ(edits_[kAtomicGroupSize / 2].DebugString(),
corrupted_edit_.DebugString());
@@ -2932,7 +2936,8 @@ TEST_F(VersionSetAtomicGroupTest,
AddNewEditsToLog(kAtomicGroupSize);
mu.Lock();
EXPECT_NOK(reactive_versions_->ReadAndApply(
&mu, &manifest_reader, manifest_reader_status.get(), &cfds_changed));
&mu, &manifest_reader, manifest_reader_status.get(), &cfds_changed,
/*files_to_delete=*/nullptr));
mu.Unlock();
EXPECT_EQ(edits_[1].DebugString(),
edit_with_incorrect_group_size_.DebugString());
+138
View File
@@ -1794,6 +1794,144 @@ TEST_F(DBWideBasicTest, PinnableWideColumnsMove) {
test_move(/* fill_cache*/ true);
}
TEST_F(DBWideBasicTest, SanityChecks) {
constexpr char foo[] = "foo";
constexpr char bar[] = "bar";
constexpr size_t num_keys = 2;
{
constexpr ColumnFamilyHandle* column_family = nullptr;
PinnableWideColumns columns;
ASSERT_TRUE(db_->GetEntity(ReadOptions(), column_family, foo, &columns)
.IsInvalidArgument());
}
{
constexpr PinnableWideColumns* columns = nullptr;
ASSERT_TRUE(
db_->GetEntity(ReadOptions(), db_->DefaultColumnFamily(), foo, columns)
.IsInvalidArgument());
}
{
ReadOptions read_options;
read_options.io_activity = Env::IOActivity::kGet;
PinnableWideColumns columns;
ASSERT_TRUE(
db_->GetEntity(read_options, db_->DefaultColumnFamily(), foo, &columns)
.IsInvalidArgument());
}
{
constexpr ColumnFamilyHandle* column_family = nullptr;
std::array<Slice, num_keys> keys{{foo, bar}};
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
db_->MultiGetEntity(ReadOptions(), column_family, num_keys, keys.data(),
results.data(), statuses.data());
ASSERT_TRUE(statuses[0].IsInvalidArgument());
ASSERT_TRUE(statuses[1].IsInvalidArgument());
}
{
constexpr Slice* keys = nullptr;
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
db_->MultiGetEntity(ReadOptions(), db_->DefaultColumnFamily(), num_keys,
keys, results.data(), statuses.data());
ASSERT_TRUE(statuses[0].IsInvalidArgument());
ASSERT_TRUE(statuses[1].IsInvalidArgument());
}
{
std::array<Slice, num_keys> keys{{foo, bar}};
constexpr PinnableWideColumns* results = nullptr;
std::array<Status, num_keys> statuses;
db_->MultiGetEntity(ReadOptions(), db_->DefaultColumnFamily(), num_keys,
keys.data(), results, statuses.data());
ASSERT_TRUE(statuses[0].IsInvalidArgument());
ASSERT_TRUE(statuses[1].IsInvalidArgument());
}
{
ReadOptions read_options;
read_options.io_activity = Env::IOActivity::kMultiGet;
std::array<Slice, num_keys> keys{{foo, bar}};
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
db_->MultiGetEntity(read_options, db_->DefaultColumnFamily(), num_keys,
keys.data(), results.data(), statuses.data());
ASSERT_TRUE(statuses[0].IsInvalidArgument());
ASSERT_TRUE(statuses[1].IsInvalidArgument());
}
{
constexpr ColumnFamilyHandle** column_families = nullptr;
std::array<Slice, num_keys> keys{{foo, bar}};
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
db_->MultiGetEntity(ReadOptions(), num_keys, column_families, keys.data(),
results.data(), statuses.data());
ASSERT_TRUE(statuses[0].IsInvalidArgument());
ASSERT_TRUE(statuses[1].IsInvalidArgument());
}
{
std::array<ColumnFamilyHandle*, num_keys> column_families{
{db_->DefaultColumnFamily(), db_->DefaultColumnFamily()}};
constexpr Slice* keys = nullptr;
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
db_->MultiGetEntity(ReadOptions(), num_keys, column_families.data(), keys,
results.data(), statuses.data());
ASSERT_TRUE(statuses[0].IsInvalidArgument());
ASSERT_TRUE(statuses[1].IsInvalidArgument());
}
{
std::array<ColumnFamilyHandle*, num_keys> column_families{
{db_->DefaultColumnFamily(), db_->DefaultColumnFamily()}};
std::array<Slice, num_keys> keys{{foo, bar}};
constexpr PinnableWideColumns* results = nullptr;
std::array<Status, num_keys> statuses;
db_->MultiGetEntity(ReadOptions(), num_keys, column_families.data(),
keys.data(), results, statuses.data());
ASSERT_TRUE(statuses[0].IsInvalidArgument());
ASSERT_TRUE(statuses[1].IsInvalidArgument());
}
{
ReadOptions read_options;
read_options.io_activity = Env::IOActivity::kMultiGet;
std::array<ColumnFamilyHandle*, num_keys> column_families{
{db_->DefaultColumnFamily(), db_->DefaultColumnFamily()}};
std::array<Slice, num_keys> keys{{foo, bar}};
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
db_->MultiGetEntity(read_options, num_keys, column_families.data(),
keys.data(), results.data(), statuses.data());
ASSERT_TRUE(statuses[0].IsInvalidArgument());
ASSERT_TRUE(statuses[1].IsInvalidArgument());
}
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+160 -62
View File
@@ -54,7 +54,12 @@ class BatchedOpsStressTest : public StressTest {
const std::string v = value_body + num;
if (FLAGS_use_put_entity_one_in > 0 &&
(value_base % FLAGS_use_put_entity_one_in) == 0) {
status = batch.PutEntity(cfh, k, GenerateWideColumns(value_base, v));
if (FLAGS_use_attribute_group) {
status =
batch.PutEntity(k, GenerateAttributeGroups({cfh}, value_base, v));
} else {
status = batch.PutEntity(cfh, k, GenerateWideColumns(value_base, v));
}
} else if (FLAGS_use_timed_put_one_in > 0 &&
((value_base + kLargePrimeForCommonFactorSkew) %
FLAGS_use_timed_put_one_in) == 0) {
@@ -300,15 +305,30 @@ class BatchedOpsStressTest : public StressTest {
constexpr size_t num_keys = 10;
std::array<PinnableWideColumns, num_keys> results;
std::array<PinnableWideColumns, num_keys> column_results;
std::array<PinnableAttributeGroups, num_keys> attribute_group_results;
std::string error_msg_header = FLAGS_use_attribute_group
? "GetEntity (AttributeGroup) error"
: "GetEntity error";
for (size_t i = 0; i < num_keys; ++i) {
const std::string key = std::to_string(i) + key_suffix;
const Status s = db_->GetEntity(read_opts_copy, cfh, key, &results[i]);
Status s;
if (FLAGS_use_attribute_group) {
attribute_group_results[i].emplace_back(cfh);
s = db_->GetEntity(read_opts_copy, key, &attribute_group_results[i]);
if (s.ok()) {
s = attribute_group_results[i].back().status();
}
} else {
s = db_->GetEntity(read_opts_copy, cfh, key, &column_results[i]);
}
if (!s.ok() && !s.IsNotFound()) {
fprintf(stderr, "GetEntity error: %s\n", s.ToString().c_str());
fprintf(stderr, "%s: %s\n", error_msg_header.c_str(),
s.ToString().c_str());
thread->stats.AddErrors(1);
} else if (s.IsNotFound()) {
thread->stats.AddGets(1, 0);
@@ -317,14 +337,20 @@ class BatchedOpsStressTest : public StressTest {
}
}
for (size_t i = 0; i < num_keys; ++i) {
const WideColumns& columns = results[i].columns();
const WideColumns& columns_to_compare =
FLAGS_use_attribute_group ? attribute_group_results[0].front().columns()
: column_results[0].columns();
if (!CompareColumns(results[0].columns(), columns)) {
fprintf(stderr,
"GetEntity error: inconsistent entities for key %s: %s, %s\n",
StringToHex(key_suffix).c_str(),
WideColumnsToHex(results[0].columns()).c_str(),
for (size_t i = 1; i < num_keys; ++i) {
const WideColumns& columns =
FLAGS_use_attribute_group
? attribute_group_results[i].front().columns()
: column_results[i].columns();
if (!CompareColumns(columns_to_compare, columns)) {
fprintf(stderr, "%s: inconsistent entities for key %s: %s, %s\n",
error_msg_header.c_str(), StringToHex(key_suffix).c_str(),
WideColumnsToHex(columns_to_compare).c_str(),
WideColumnsToHex(columns).c_str());
}
@@ -338,20 +364,18 @@ class BatchedOpsStressTest : public StressTest {
if (value.empty() || value[value.size() - 1] != expected) {
fprintf(stderr,
"GetEntity error: incorrect column value for key "
"%s: incorrect column value for key "
"%s, entity %s, column value %s, expected %c\n",
StringToHex(key_suffix).c_str(),
error_msg_header.c_str(), StringToHex(key_suffix).c_str(),
WideColumnsToHex(columns).c_str(),
value.ToString(/* hex */ true).c_str(), expected);
}
}
if (!VerifyWideColumns(columns)) {
fprintf(
stderr,
"GetEntity error: inconsistent columns for key %s, entity %s\n",
StringToHex(key_suffix).c_str(),
WideColumnsToHex(columns).c_str());
fprintf(stderr, "%s: inconsistent columns for key %s, entity %s\n",
error_msg_header.c_str(), StringToHex(key_suffix).c_str(),
WideColumnsToHex(columns).c_str());
}
}
}
@@ -385,66 +409,140 @@ class BatchedOpsStressTest : public StressTest {
std::array<std::string, num_prefixes> keys;
std::array<Slice, num_prefixes> key_slices;
std::array<PinnableWideColumns, num_prefixes> results;
std::array<Status, num_prefixes> statuses;
for (size_t j = 0; j < num_prefixes; ++j) {
keys[j] = std::to_string(j) + key_suffix;
key_slices[j] = keys[j];
}
db_->MultiGetEntity(read_opts_copy, cfh, num_prefixes, key_slices.data(),
results.data(), statuses.data());
if (FLAGS_use_attribute_group) {
// AttributeGroup MultiGetEntity verification
for (size_t j = 0; j < num_prefixes; ++j) {
const Status& s = statuses[j];
if (!s.ok() && !s.IsNotFound()) {
fprintf(stderr, "MultiGetEntity error: %s\n", s.ToString().c_str());
thread->stats.AddErrors(1);
} else if (s.IsNotFound()) {
thread->stats.AddGets(1, 0);
} else {
thread->stats.AddGets(1, 1);
std::vector<PinnableAttributeGroups> results;
results.reserve(num_prefixes);
for (size_t j = 0; j < num_prefixes; ++j) {
PinnableAttributeGroups attribute_groups;
attribute_groups.emplace_back(cfh);
results.emplace_back(std::move(attribute_groups));
}
db_->MultiGetEntity(read_opts_copy, num_prefixes, key_slices.data(),
results.data());
const WideColumns& cmp_columns = results[0].columns();
const WideColumns& columns = results[j].columns();
const WideColumns& cmp_columns = results[0][0].columns();
if (!CompareColumns(cmp_columns, columns)) {
fprintf(stderr,
"MultiGetEntity error: inconsistent entities for key %s: %s, "
"%s\n",
StringToHex(key_suffix).c_str(),
WideColumnsToHex(cmp_columns).c_str(),
WideColumnsToHex(columns).c_str());
}
if (!columns.empty()) {
// The last character of each column value should be 'j' as a decimal
// digit
const char expected = static_cast<char>('0' + j);
for (const auto& column : columns) {
const Slice& value = column.value();
if (value.empty() || value[value.size() - 1] != expected) {
fprintf(stderr,
"MultiGetEntity error: incorrect column value for key "
"%s, entity %s, column value %s, expected %c\n",
StringToHex(key_suffix).c_str(),
WideColumnsToHex(columns).c_str(),
value.ToString(/* hex */ true).c_str(), expected);
}
for (size_t j = 0; j < num_prefixes; ++j) {
const auto& attribute_groups = results[j];
assert(attribute_groups.size() == 1);
const Status& s = attribute_groups[0].status();
if (!s.ok() && !s.IsNotFound()) {
fprintf(stderr, "MultiGetEntity (AttributeGroup) error: %s\n",
s.ToString().c_str());
thread->stats.AddErrors(1);
} else if (s.IsNotFound()) {
thread->stats.AddGets(1, 0);
} else {
thread->stats.AddGets(1, 1);
}
if (!VerifyWideColumns(columns)) {
const WideColumns& columns = results[j][0].columns();
if (!CompareColumns(cmp_columns, columns)) {
fprintf(stderr,
"MultiGetEntity error: inconsistent columns for key %s, "
"entity %s\n",
"MultiGetEntity (AttributeGroup) error: inconsistent "
"entities for key %s: %s, "
"%s\n",
StringToHex(key_suffix).c_str(),
WideColumnsToHex(cmp_columns).c_str(),
WideColumnsToHex(columns).c_str());
}
if (!columns.empty()) {
// The last character of each column value should be 'j' as a
// decimal digit
const char expected = static_cast<char>('0' + j);
for (const auto& column : columns) {
const Slice& value = column.value();
if (value.empty() || value[value.size() - 1] != expected) {
fprintf(stderr,
"MultiGetEntity (AttributeGroup) error: incorrect "
"column value for key "
"%s, entity %s, column value %s, expected %c\n",
StringToHex(key_suffix).c_str(),
WideColumnsToHex(columns).c_str(),
value.ToString(/* hex */ true).c_str(), expected);
}
}
if (!VerifyWideColumns(columns)) {
fprintf(stderr,
"MultiGetEntity (AttributeGroup) error: inconsistent "
"columns for key %s, "
"entity %s\n",
StringToHex(key_suffix).c_str(),
WideColumnsToHex(columns).c_str());
}
}
}
} else {
// Non-AttributeGroup MultiGetEntity verification
std::array<PinnableWideColumns, num_prefixes> results;
std::array<Status, num_prefixes> statuses;
db_->MultiGetEntity(read_opts_copy, cfh, num_prefixes,
key_slices.data(), results.data(), statuses.data());
const WideColumns& cmp_columns = results[0].columns();
for (size_t j = 0; j < num_prefixes; ++j) {
const Status& s = statuses[j];
if (!s.ok() && !s.IsNotFound()) {
fprintf(stderr, "MultiGetEntity error: %s\n", s.ToString().c_str());
thread->stats.AddErrors(1);
} else if (s.IsNotFound()) {
thread->stats.AddGets(1, 0);
} else {
thread->stats.AddGets(1, 1);
}
const WideColumns& columns = results[j].columns();
if (!CompareColumns(cmp_columns, columns)) {
fprintf(
stderr,
"MultiGetEntity error: inconsistent entities for key %s: %s, "
"%s\n",
StringToHex(key_suffix).c_str(),
WideColumnsToHex(cmp_columns).c_str(),
WideColumnsToHex(columns).c_str());
}
if (!columns.empty()) {
// The last character of each column value should be 'j' as a
// decimal digit
const char expected = static_cast<char>('0' + j);
for (const auto& column : columns) {
const Slice& value = column.value();
if (value.empty() || value[value.size() - 1] != expected) {
fprintf(stderr,
"MultiGetEntity error: incorrect column value for key "
"%s, entity %s, column value %s, expected %c\n",
StringToHex(key_suffix).c_str(),
WideColumnsToHex(columns).c_str(),
value.ToString(/* hex */ true).c_str(), expected);
}
}
if (!VerifyWideColumns(columns)) {
fprintf(stderr,
"MultiGetEntity error: inconsistent columns for key %s, "
"entity %s\n",
StringToHex(key_suffix).c_str(),
WideColumnsToHex(columns).c_str());
}
}
}
}
}
+327 -127
View File
@@ -37,25 +37,35 @@ class CfConsistencyStressTest : public StressTest {
WriteBatch batch;
Status status;
for (auto cf : rand_column_families) {
ColumnFamilyHandle* const cfh = column_families_[cf];
assert(cfh);
if (FLAGS_use_put_entity_one_in > 0 &&
(value_base % FLAGS_use_put_entity_one_in) == 0) {
status = batch.PutEntity(cfh, k, GenerateWideColumns(value_base, v));
} else if (FLAGS_use_timed_put_one_in > 0 &&
((value_base + kLargePrimeForCommonFactorSkew) %
FLAGS_use_timed_put_one_in) == 0) {
uint64_t write_unix_time = GetWriteUnixTime(thread);
status = batch.TimedPut(cfh, k, v, write_unix_time);
} else if (FLAGS_use_merge) {
status = batch.Merge(cfh, k, v);
} else {
status = batch.Put(cfh, k, v);
if (FLAGS_use_attribute_group && FLAGS_use_put_entity_one_in > 0 &&
(value_base % FLAGS_use_put_entity_one_in) == 0) {
std::vector<ColumnFamilyHandle*> cfhs;
cfhs.reserve(rand_column_families.size());
for (auto cf : rand_column_families) {
cfhs.push_back(column_families_[cf]);
}
if (!status.ok()) {
break;
status = batch.PutEntity(k, GenerateAttributeGroups(cfhs, value_base, v));
} else {
for (auto cf : rand_column_families) {
ColumnFamilyHandle* const cfh = column_families_[cf];
assert(cfh);
if (FLAGS_use_put_entity_one_in > 0 &&
(value_base % FLAGS_use_put_entity_one_in) == 0) {
status = batch.PutEntity(cfh, k, GenerateWideColumns(value_base, v));
} else if (FLAGS_use_timed_put_one_in > 0 &&
((value_base + kLargePrimeForCommonFactorSkew) %
FLAGS_use_timed_put_one_in) == 0) {
uint64_t write_unix_time = GetWriteUnixTime(thread);
status = batch.TimedPut(cfh, k, v, write_unix_time);
} else if (FLAGS_use_merge) {
status = batch.Merge(cfh, k, v);
} else {
status = batch.Put(cfh, k, v);
}
if (!status.ok()) {
break;
}
}
}
@@ -328,57 +338,122 @@ class CfConsistencyStressTest : public StressTest {
}
if (is_consistent) {
for (size_t i = 1; i < rand_column_families.size(); ++i) {
assert(rand_column_families[i] >= 0);
assert(rand_column_families[i] <
static_cast<int>(column_families_.size()));
if (FLAGS_use_attribute_group) {
PinnableAttributeGroups result;
result.reserve(rand_column_families.size());
for (size_t i = 1; i < rand_column_families.size(); ++i) {
assert(rand_column_families[i] >= 0);
assert(rand_column_families[i] <
static_cast<int>(column_families_.size()));
PinnableWideColumns result;
s = db_->GetEntity(read_opts_copy,
column_families_[rand_column_families[i]], key,
&result);
if (!s.ok() && !s.IsNotFound()) {
break;
result.emplace_back(column_families_[rand_column_families[i]]);
}
s = db_->GetEntity(read_opts_copy, key, &result);
if (s.ok()) {
for (auto& attribute_group : result) {
s = attribute_group.status();
if (!s.ok() && !s.IsNotFound()) {
break;
}
const bool found = s.ok();
const bool found = s.ok();
assert(!column_family_names_.empty());
assert(i < column_family_names_.size());
if (!cmp_found && found) {
fprintf(stderr,
"GetEntity returns different results for key %s: CF %s "
"returns not found, CF %s returns entity %s\n",
if (!cmp_found && found) {
fprintf(
stderr,
"Non-AttributeGroup GetEntity returns different results "
"than AttributeGroup GetEntity for key %s: CF %s "
"returns not found, CF %s returns entity %s \n",
StringToHex(key).c_str(), column_family_names_[0].c_str(),
column_family_names_[i].c_str(),
WideColumnsToHex(result.columns()).c_str());
is_consistent = false;
break;
}
if (cmp_found && !found) {
fprintf(stderr,
"GetEntity returns different results for key %s: CF %s "
"returns entity %s, CF %s returns not found\n",
attribute_group.column_family()->GetName().c_str(),
WideColumnsToHex(attribute_group.columns()).c_str());
is_consistent = false;
break;
}
if (cmp_found && !found) {
fprintf(
stderr,
"Non-AttributeGroup GetEntity returns different results "
"than AttributeGroup GetEntity for key %s: CF %s "
"returns entity %s, CF %s returns not found \n",
StringToHex(key).c_str(), column_family_names_[0].c_str(),
WideColumnsToHex(cmp_result.columns()).c_str(),
column_family_names_[i].c_str());
is_consistent = false;
break;
}
if (found && result != cmp_result) {
fprintf(stderr,
"GetEntity returns different results for key %s: CF %s "
attribute_group.column_family()->GetName().c_str());
is_consistent = false;
break;
}
if (found &&
attribute_group.columns() != cmp_result.columns()) {
fprintf(
stderr,
"Non-AttributeGroup GetEntity returns different results "
"than AttributeGroup GetEntity for key %s: CF %s "
"returns entity %s, CF %s returns entity %s\n",
StringToHex(key).c_str(), column_family_names_[0].c_str(),
WideColumnsToHex(cmp_result.columns()).c_str(),
column_family_names_[i].c_str(),
WideColumnsToHex(result.columns()).c_str());
is_consistent = false;
break;
attribute_group.column_family()->GetName().c_str(),
WideColumnsToHex(attribute_group.columns()).c_str());
is_consistent = false;
break;
}
}
}
} else {
for (size_t i = 1; i < rand_column_families.size(); ++i) {
assert(rand_column_families[i] >= 0);
assert(rand_column_families[i] <
static_cast<int>(column_families_.size()));
PinnableWideColumns result;
s = db_->GetEntity(read_opts_copy,
column_families_[rand_column_families[i]], key,
&result);
if (!s.ok() && !s.IsNotFound()) {
break;
}
const bool found = s.ok();
assert(!column_family_names_.empty());
assert(i < column_family_names_.size());
if (!cmp_found && found) {
fprintf(stderr,
"GetEntity returns different results for key %s: CF %s "
"returns not found, CF %s returns entity %s\n",
StringToHex(key).c_str(),
column_family_names_[0].c_str(),
column_family_names_[i].c_str(),
WideColumnsToHex(result.columns()).c_str());
is_consistent = false;
break;
}
if (cmp_found && !found) {
fprintf(stderr,
"GetEntity returns different results for key %s: CF %s "
"returns entity %s, CF %s returns not found\n",
StringToHex(key).c_str(),
column_family_names_[0].c_str(),
WideColumnsToHex(cmp_result.columns()).c_str(),
column_family_names_[i].c_str());
is_consistent = false;
break;
}
if (found && result != cmp_result) {
fprintf(stderr,
"GetEntity returns different results for key %s: CF %s "
"returns entity %s, CF %s returns entity %s\n",
StringToHex(key).c_str(),
column_family_names_[0].c_str(),
WideColumnsToHex(cmp_result.columns()).c_str(),
column_family_names_[i].c_str(),
WideColumnsToHex(result.columns()).c_str());
is_consistent = false;
break;
}
}
}
}
@@ -431,94 +506,219 @@ class CfConsistencyStressTest : public StressTest {
const size_t num_keys = rand_keys.size();
for (size_t i = 0; i < num_keys; ++i) {
const std::string key = Key(rand_keys[i]);
if (FLAGS_use_attribute_group) {
// AttributeGroup MultiGetEntity verification
std::vector<Slice> key_slices(num_cfs, key);
std::vector<PinnableWideColumns> results(num_cfs);
std::vector<Status> statuses(num_cfs);
std::vector<PinnableAttributeGroups> results;
std::vector<Slice> key_slices;
std::vector<std::string> key_strs;
results.reserve(num_keys);
key_slices.reserve(num_keys);
key_strs.reserve(num_keys);
db_->MultiGetEntity(read_opts_copy, num_cfs, cfhs.data(),
key_slices.data(), results.data(), statuses.data());
for (size_t i = 0; i < num_keys; ++i) {
key_strs.emplace_back(Key(rand_keys[i]));
key_slices.emplace_back(key_strs.back());
PinnableAttributeGroups attribute_groups;
for (auto* cfh : cfhs) {
attribute_groups.emplace_back(cfh);
}
results.emplace_back(std::move(attribute_groups));
}
db_->MultiGetEntity(read_opts_copy, num_keys, key_slices.data(),
results.data());
bool is_consistent = true;
for (size_t j = 0; j < num_cfs; ++j) {
const Status& s = statuses[j];
const Status& cmp_s = statuses[0];
const WideColumns& columns = results[j].columns();
const WideColumns& cmp_columns = results[0].columns();
for (size_t i = 0; i < num_keys; ++i) {
const auto& result = results[i];
const Status& cmp_s = result[0].status();
const WideColumns& cmp_columns = result[0].columns();
if (!s.ok() && !s.IsNotFound()) {
fprintf(stderr, "TestMultiGetEntity error: %s\n",
s.ToString().c_str());
thread->stats.AddErrors(1);
break;
}
bool has_error = false;
assert(cmp_s.ok() || cmp_s.IsNotFound());
for (size_t j = 0; j < num_cfs; ++j) {
const Status& s = result[j].status();
const WideColumns& columns = result[j].columns();
if (!s.ok() && !s.IsNotFound()) {
fprintf(stderr, "TestMultiGetEntity (AttributeGroup) error: %s\n",
s.ToString().c_str());
thread->stats.AddErrors(1);
has_error = true;
break;
}
if (s.IsNotFound()) {
if (cmp_s.ok()) {
fprintf(
stderr,
"MultiGetEntity returns different results for key %s: CF %s "
"returns entity %s, CF %s returns not found\n",
StringToHex(key).c_str(), column_family_names_[0].c_str(),
WideColumnsToHex(cmp_columns).c_str(),
column_family_names_[j].c_str());
assert(cmp_s.ok() || cmp_s.IsNotFound());
if (s.IsNotFound()) {
if (cmp_s.ok()) {
fprintf(stderr,
"MultiGetEntity (AttributeGroup) returns different "
"results for key %s: CF %s "
"returns entity %s, CF %s returns not found\n",
key_slices[i].ToString(true).c_str(),
column_family_names_[0].c_str(),
WideColumnsToHex(cmp_columns).c_str(),
column_family_names_[j].c_str());
is_consistent = false;
break;
}
continue;
}
assert(s.ok());
if (cmp_s.IsNotFound()) {
fprintf(stderr,
"MultiGetEntity (AttributeGroup) returns different results "
"for key %s: CF %s "
"returns not found, CF %s returns entity %s\n",
key_slices[i].ToString(true).c_str(),
column_family_names_[0].c_str(),
column_family_names_[j].c_str(),
WideColumnsToHex(columns).c_str());
is_consistent = false;
break;
}
continue;
}
if (columns != cmp_columns) {
fprintf(stderr,
"MultiGetEntity (AttributeGroup) returns different results "
"for key %s: CF %s "
"returns entity %s, CF %s returns entity %s\n",
key_slices[i].ToString(true).c_str(),
column_family_names_[0].c_str(),
WideColumnsToHex(cmp_columns).c_str(),
column_family_names_[j].c_str(),
WideColumnsToHex(columns).c_str());
is_consistent = false;
break;
}
assert(s.ok());
if (cmp_s.IsNotFound()) {
fprintf(stderr,
"MultiGetEntity returns different results for key %s: CF %s "
"returns not found, CF %s returns entity %s\n",
StringToHex(key).c_str(), column_family_names_[0].c_str(),
column_family_names_[j].c_str(),
WideColumnsToHex(columns).c_str());
is_consistent = false;
break;
if (!VerifyWideColumns(columns)) {
fprintf(stderr,
"MultiGetEntity (AttributeGroup) error: inconsistent "
"columns for key %s, "
"entity %s\n",
key_slices[i].ToString(true).c_str(),
WideColumnsToHex(columns).c_str());
is_consistent = false;
break;
}
}
if (columns != cmp_columns) {
fprintf(stderr,
"MultiGetEntity returns different results for key %s: CF %s "
"returns entity %s, CF %s returns entity %s\n",
StringToHex(key).c_str(), column_family_names_[0].c_str(),
WideColumnsToHex(cmp_columns).c_str(),
column_family_names_[j].c_str(),
WideColumnsToHex(columns).c_str());
is_consistent = false;
if (has_error) {
break;
}
if (!VerifyWideColumns(columns)) {
} else if (!is_consistent) {
fprintf(stderr,
"MultiGetEntity error: inconsistent columns for key %s, "
"entity %s\n",
StringToHex(key).c_str(), WideColumnsToHex(columns).c_str());
is_consistent = false;
"TestMultiGetEntity (AttributeGroup) error: results are not "
"consistent\n");
thread->stats.AddErrors(1);
// Fail fast to preserve the DB state.
thread->shared->SetVerificationFailure();
break;
} else if (cmp_s.ok()) {
thread->stats.AddGets(1, 1);
} else if (cmp_s.IsNotFound()) {
thread->stats.AddGets(1, 0);
}
}
if (!is_consistent) {
fprintf(stderr,
"TestMultiGetEntity error: results are not consistent\n");
thread->stats.AddErrors(1);
// Fail fast to preserve the DB state.
thread->shared->SetVerificationFailure();
break;
} else if (statuses[0].ok()) {
thread->stats.AddGets(1, 1);
} else if (statuses[0].IsNotFound()) {
thread->stats.AddGets(1, 0);
} else {
// Non-AttributeGroup MultiGetEntity verification
for (size_t i = 0; i < num_keys; ++i) {
const std::string key = Key(rand_keys[i]);
std::vector<Slice> key_slices(num_cfs, key);
std::vector<PinnableWideColumns> results(num_cfs);
std::vector<Status> statuses(num_cfs);
db_->MultiGetEntity(read_opts_copy, num_cfs, cfhs.data(),
key_slices.data(), results.data(), statuses.data());
bool is_consistent = true;
const Status& cmp_s = statuses[0];
const WideColumns& cmp_columns = results[0].columns();
for (size_t j = 0; j < num_cfs; ++j) {
const Status& s = statuses[j];
const WideColumns& columns = results[j].columns();
if (!s.ok() && !s.IsNotFound()) {
fprintf(stderr, "TestMultiGetEntity error: %s\n",
s.ToString().c_str());
thread->stats.AddErrors(1);
break;
}
assert(cmp_s.ok() || cmp_s.IsNotFound());
if (s.IsNotFound()) {
if (cmp_s.ok()) {
fprintf(
stderr,
"MultiGetEntity returns different results for key %s: CF %s "
"returns entity %s, CF %s returns not found\n",
StringToHex(key).c_str(), column_family_names_[0].c_str(),
WideColumnsToHex(cmp_columns).c_str(),
column_family_names_[j].c_str());
is_consistent = false;
break;
}
continue;
}
assert(s.ok());
if (cmp_s.IsNotFound()) {
fprintf(
stderr,
"MultiGetEntity returns different results for key %s: CF %s "
"returns not found, CF %s returns entity %s\n",
StringToHex(key).c_str(), column_family_names_[0].c_str(),
column_family_names_[j].c_str(),
WideColumnsToHex(columns).c_str());
is_consistent = false;
break;
}
if (columns != cmp_columns) {
fprintf(
stderr,
"MultiGetEntity returns different results for key %s: CF %s "
"returns entity %s, CF %s returns entity %s\n",
StringToHex(key).c_str(), column_family_names_[0].c_str(),
WideColumnsToHex(cmp_columns).c_str(),
column_family_names_[j].c_str(),
WideColumnsToHex(columns).c_str());
is_consistent = false;
break;
}
if (!VerifyWideColumns(columns)) {
fprintf(stderr,
"MultiGetEntity error: inconsistent columns for key %s, "
"entity %s\n",
StringToHex(key).c_str(),
WideColumnsToHex(columns).c_str());
is_consistent = false;
break;
}
}
if (!is_consistent) {
fprintf(stderr,
"TestMultiGetEntity error: results are not consistent\n");
thread->stats.AddErrors(1);
// Fail fast to preserve the DB state.
thread->shared->SetVerificationFailure();
break;
} else if (statuses[0].ok()) {
thread->stats.AddGets(1, 1);
} else if (statuses[0].IsNotFound()) {
thread->stats.AddGets(1, 0);
}
}
}
}
+11
View File
@@ -321,6 +321,17 @@ uint32_t GetValueBase(Slice s) {
return res;
}
AttributeGroups GenerateAttributeGroups(
const std::vector<ColumnFamilyHandle*>& cfhs, uint32_t value_base,
const Slice& slice) {
WideColumns columns = GenerateWideColumns(value_base, slice);
AttributeGroups attribute_groups;
for (auto* cfh : cfhs) {
attribute_groups.emplace_back(cfh, columns);
}
return attribute_groups;
}
WideColumns GenerateWideColumns(uint32_t value_base, const Slice& slice) {
WideColumns columns;
+8
View File
@@ -209,8 +209,11 @@ DECLARE_int32(ingest_external_file_one_in);
DECLARE_int32(ingest_external_file_width);
DECLARE_int32(compact_files_one_in);
DECLARE_int32(compact_range_one_in);
DECLARE_int32(promote_l0_one_in);
DECLARE_int32(mark_for_compaction_one_file_in);
DECLARE_int32(flush_one_in);
DECLARE_int32(key_may_exist_one_in);
DECLARE_int32(reset_stats_one_in);
DECLARE_int32(pause_background_one_in);
DECLARE_int32(disable_file_deletions_one_in);
DECLARE_int32(disable_manual_compaction_one_in);
@@ -250,6 +253,7 @@ DECLARE_string(memtablerep);
DECLARE_int32(prefix_size);
DECLARE_bool(use_merge);
DECLARE_uint32(use_put_entity_one_in);
DECLARE_bool(use_attribute_group);
DECLARE_bool(use_full_merge_v1);
DECLARE_int32(sync_wal_one_in);
DECLARE_bool(avoid_unnecessary_blocking_io);
@@ -758,6 +762,10 @@ WideColumns GenerateExpectedWideColumns(uint32_t value_base,
bool VerifyWideColumns(const Slice& value, const WideColumns& columns);
bool VerifyWideColumns(const WideColumns& columns);
AttributeGroups GenerateAttributeGroups(
const std::vector<ColumnFamilyHandle*>& cfhs, uint32_t value_base,
const Slice& slice);
StressTest* CreateCfConsistencyStressTest();
StressTest* CreateBatchedOpsStressTest();
StressTest* CreateNonBatchedOpsStressTest();
+15
View File
@@ -756,6 +756,10 @@ DEFINE_int32(compact_range_one_in, 0,
"If non-zero, then CompactRange() will be called once for every N "
"operations on average. 0 indicates CompactRange() is disabled.");
DEFINE_int32(promote_l0_one_in, 0,
"If non-zero, then PromoteL0() will be called once for every N "
"operations on average. 0 indicates PromoteL0() is disabled.");
DEFINE_int32(mark_for_compaction_one_file_in, 0,
"A `TablePropertiesCollectorFactory` will be registered, which "
"creates a `TablePropertiesCollector` with `NeedCompact()` "
@@ -766,6 +770,14 @@ DEFINE_int32(flush_one_in, 0,
"If non-zero, then Flush() will be called once for every N ops "
"on average. 0 indicates calls to Flush() are disabled.");
DEFINE_int32(key_may_exist_one_in, 0,
"If non-zero, then KeyMayExist() will be called "
"once for every N ops on average. 0 disables.");
DEFINE_int32(reset_stats_one_in, 0,
"If non-zero, then ResetStats() will be called "
"once for every N ops on average. 0 disables.");
DEFINE_int32(pause_background_one_in, 0,
"If non-zero, then PauseBackgroundWork()+Continue will be called "
"once for every N ops on average. 0 disables.");
@@ -940,6 +952,9 @@ DEFINE_uint32(use_put_entity_one_in, 0,
"If greater than zero, PutEntity will be used once per every N "
"write ops on average.");
DEFINE_bool(use_attribute_group, false,
"If set, use the attribute_group API to put/get entities");
DEFINE_bool(use_full_merge_v1, false,
"On true, use a merge operator that implement the deprecated "
"version of FullMerge");
+91 -10
View File
@@ -505,11 +505,13 @@ Status StressTest::AssertSame(DB* db, ColumnFamilyHandle* cf,
}
void StressTest::ProcessStatus(SharedState* shared, std::string opname,
Status s) const {
const Status& s,
bool ignore_injected_error) const {
if (s.ok()) {
return;
}
if (!s.IsIOError() || !std::strstr(s.getState(), "injected")) {
if (!s.IsIOError() || !std::strstr(s.getState(), "injected") ||
!ignore_injected_error) {
std::ostringstream oss;
oss << opname << " failed: " << s.ToString();
VerificationAbort(shared, oss.str());
@@ -956,12 +958,48 @@ void StressTest::OperateDb(ThreadState* thread) {
if (!s.ok()) {
fprintf(stderr, "LockWAL() failed: %s\n", s.ToString().c_str());
} else {
// Verify no writes during LockWAL
auto old_seqno = db_->GetLatestSequenceNumber();
// Yield for a while
do {
std::this_thread::yield();
} while (thread->rand.OneIn(2));
// Latest seqno should not have changed
// And also that WAL is not changed during LockWAL()
std::unique_ptr<LogFile> old_wal;
s = db_->GetCurrentWalFile(&old_wal);
if (!s.ok()) {
fprintf(stderr, "GetCurrentWalFile() failed: %s\n",
s.ToString().c_str());
} else {
// Yield for a while
do {
std::this_thread::yield();
} while (thread->rand.OneIn(2));
// Current WAL and size should not have changed
std::unique_ptr<LogFile> new_wal;
s = db_->GetCurrentWalFile(&new_wal);
if (!s.ok()) {
fprintf(stderr, "GetCurrentWalFile() failed: %s\n",
s.ToString().c_str());
} else {
if (old_wal->LogNumber() != new_wal->LogNumber()) {
fprintf(stderr,
"Failed: WAL number changed during LockWAL(): %" PRIu64
" to %" PRIu64 "\n",
old_wal->LogNumber(), new_wal->LogNumber());
}
// FIXME: FaultInjectionTestFS does not report file sizes that
// reflect what has been flushed. Either that needs to be fixed
// or GetSortedWals/GetLiveWalFile need to stop relying on
// asking the FS for sizes.
if (!fault_fs_guard &&
old_wal->SizeFileBytes() != new_wal->SizeFileBytes()) {
fprintf(stderr,
"Failed: WAL %" PRIu64
" size changed during LockWAL(): %" PRIu64
" to %" PRIu64 "\n",
old_wal->LogNumber(), old_wal->SizeFileBytes(),
new_wal->SizeFileBytes());
}
}
}
// Verify no writes during LockWAL
auto new_seqno = db_->GetLatestSequenceNumber();
if (old_seqno != new_seqno) {
fprintf(
@@ -969,6 +1007,7 @@ void StressTest::OperateDb(ThreadState* thread) {
"Failure: latest seqno changed from %u to %u with WAL locked\n",
(unsigned)old_seqno, (unsigned)new_seqno);
}
// Verification done. Now unlock WAL
s = db_->UnlockWAL();
if (!s.ok()) {
fprintf(stderr, "UnlockWAL() failed: %s\n", s.ToString().c_str());
@@ -1001,6 +1040,10 @@ void StressTest::OperateDb(ThreadState* thread) {
}
}
if (thread->rand.OneInOpt(FLAGS_promote_l0_one_in)) {
TestPromoteL0(thread, column_family);
}
std::vector<int> rand_column_families =
GenerateColumnFamilies(FLAGS_column_families, rand_column_family);
@@ -1041,6 +1084,11 @@ void StressTest::OperateDb(ThreadState* thread) {
ProcessStatus(shared, "VerifyGetCurrentWalFile", status);
}
if (thread->rand.OneInOpt(FLAGS_reset_stats_one_in)) {
Status status = TestResetStats();
ProcessStatus(shared, "ResetStats", status);
}
if (thread->rand.OneInOpt(FLAGS_pause_background_one_in)) {
Status status = TestPauseBackground(thread);
ProcessStatus(shared, "Pause/ContinueBackgroundWork", status);
@@ -1135,6 +1183,10 @@ void StressTest::OperateDb(ThreadState* thread) {
read_opts.timestamp = &read_ts;
}
if (thread->rand.OneInOpt(FLAGS_key_may_exist_one_in)) {
TestKeyMayExist(thread, read_opts, rand_column_families, rand_keys);
}
int prob_op = thread->rand.Uniform(100);
// Reset this in case we pick something other than a read op. We don't
// want to use a stale value when deciding at the beginning of the loop
@@ -2428,8 +2480,32 @@ void StressTest::TestCompactFiles(ThreadState* thread,
}
}
void StressTest::TestPromoteL0(ThreadState* thread,
ColumnFamilyHandle* column_family) {
int target_level = thread->rand.Next() % options_.num_levels;
Status s = db_->PromoteL0(column_family, target_level);
if (!s.ok()) {
// The second error occurs when another concurrent PromoteL0() moving the
// same files finishes first which is an allowed behavior
bool non_ok_status_allowed =
s.IsInvalidArgument() ||
(s.IsCorruption() &&
s.ToString().find("VersionBuilder: Cannot delete table file") !=
std::string::npos &&
s.ToString().find("since it is on level") != std::string::npos);
fprintf(non_ok_status_allowed ? stdout : stderr,
"Unable to perform PromoteL0(): %s under specified "
"target_level: %d.\n",
s.ToString().c_str(), target_level);
if (!non_ok_status_allowed) {
thread->shared->SafeTerminate();
}
}
}
Status StressTest::TestFlush(const std::vector<int>& rand_column_families) {
FlushOptions flush_opts;
assert(flush_opts.wait);
if (FLAGS_atomic_flush) {
return db_->Flush(flush_opts, column_families_);
}
@@ -2439,6 +2515,8 @@ Status StressTest::TestFlush(const std::vector<int>& rand_column_families) {
return db_->Flush(flush_opts, cfhs);
}
Status StressTest::TestResetStats() { return db_->ResetStats(); }
Status StressTest::TestPauseBackground(ThreadState* thread) {
Status status = db_->PauseBackgroundWork();
if (!status.ok()) {
@@ -2583,7 +2661,10 @@ void StressTest::TestCompactRange(ThreadState* thread, int64_t rand_key,
CompactRangeOptions cro;
cro.exclusive_manual_compaction = static_cast<bool>(thread->rand.Next() % 2);
cro.change_level = static_cast<bool>(thread->rand.Next() % 2);
if (static_cast<ROCKSDB_NAMESPACE::CompactionStyle>(FLAGS_compaction_style) !=
ROCKSDB_NAMESPACE::CompactionStyle::kCompactionStyleFIFO) {
cro.change_level = static_cast<bool>(thread->rand.Next() % 2);
}
if (thread->rand.OneIn(2)) {
cro.target_level = thread->rand.Next() % options_.num_levels;
}
@@ -2801,7 +2882,8 @@ void StressTest::PrintEnv() const {
fprintf(stdout, "Num times DB reopens : %d\n", FLAGS_reopen);
fprintf(stdout, "Batches/snapshots : %d\n",
FLAGS_test_batches_snapshots);
fprintf(stdout, "Do update in place : %d\n", FLAGS_in_place_update);
fprintf(stdout, "Do update in place : %d\n",
FLAGS_inplace_update_support);
fprintf(stdout, "Num keys per lock : %d\n",
1 << FLAGS_log2_keys_per_lock);
std::string compression = CompressionTypeToString(compression_type_e);
@@ -3597,7 +3679,6 @@ void InitializeOptionsFromFlags(
options.compression_opts.checksum = true;
}
options.max_manifest_file_size = FLAGS_max_manifest_file_size;
options.inplace_update_support = FLAGS_in_place_update;
options.max_subcompactions = static_cast<uint32_t>(FLAGS_subcompactions);
options.allow_concurrent_memtable_write =
FLAGS_allow_concurrent_memtable_write;
+11 -1
View File
@@ -89,6 +89,10 @@ class StressTest {
return {rand_key};
}
virtual void TestKeyMayExist(ThreadState*, const ReadOptions&,
const std::vector<int>&,
const std::vector<int64_t>&) {}
virtual Status TestGet(ThreadState* thread, const ReadOptions& read_opts,
const std::vector<int>& rand_column_families,
const std::vector<int64_t>& rand_keys) = 0;
@@ -136,6 +140,9 @@ class StressTest {
const Slice& start_key,
ColumnFamilyHandle* column_family);
virtual void TestPromoteL0(ThreadState* thread,
ColumnFamilyHandle* column_family);
// Calculate a hash value for all keys in range [start_key, end_key]
// at a certain snapshot.
uint32_t GetRangeHash(ThreadState* thread, const Snapshot* snapshot,
@@ -204,6 +211,8 @@ class StressTest {
Status TestFlush(const std::vector<int>& rand_column_families);
Status TestResetStats();
Status TestPauseBackground(ThreadState* thread);
Status TestDisableFileDeletions(ThreadState* thread);
@@ -236,7 +245,8 @@ class StressTest {
return Status::NotSupported("TestCustomOperations() must be overridden");
}
void ProcessStatus(SharedState* shared, std::string msg, Status s) const;
void ProcessStatus(SharedState* shared, std::string msg, const Status& s,
bool ignore_injected_error = true) const;
void VerificationAbort(SharedState* shared, std::string msg) const;
+4 -3
View File
@@ -251,13 +251,14 @@ int db_stress_tool(int argc, char** argv) {
}
if ((FLAGS_enable_compaction_filter || FLAGS_inplace_update_support) &&
(FLAGS_acquire_snapshot_one_in > 0 || FLAGS_compact_range_one_in > 0 ||
FLAGS_iterpercent > 0 || FLAGS_test_batches_snapshots ||
FLAGS_test_cf_consistency || FLAGS_check_multiget_consistency ||
FLAGS_iterpercent > 0 || FLAGS_prefixpercent > 0 ||
FLAGS_test_batches_snapshots || FLAGS_test_cf_consistency ||
FLAGS_check_multiget_consistency ||
FLAGS_check_multiget_entity_consistency)) {
fprintf(
stderr,
"Error: acquire_snapshot_one_in, compact_range_one_in, iterpercent, "
"test_batches_snapshots, test_cf_consistency, "
"prefixpercent, test_batches_snapshots, test_cf_consistency, "
"check_multiget_consistency, check_multiget_entity_consistency must "
"all be 0 when using compaction filter or inplace update support\n");
exit(1);
+9 -6
View File
@@ -1331,14 +1331,16 @@ uint32_t MultiOpsTxnsStressTest::GenerateNextC(ThreadState* thread) {
}
void MultiOpsTxnsStressTest::ProcessRecoveredPreparedTxnsHelper(
Transaction* txn, SharedState*) {
Transaction* txn, SharedState* shared) {
thread_local Random rand(static_cast<uint32_t>(FLAGS_seed));
if (rand.OneIn(2)) {
Status s = txn->Commit();
assert(s.ok());
ProcessStatus(shared, "ProcessRecoveredPreparedTxnsHelper", s,
/*ignore_injected_error=*/false);
} else {
Status s = txn->Rollback();
assert(s.ok());
ProcessStatus(shared, "ProcessRecoveredPreparedTxnsHelper", s,
/*ignore_injected_error=*/false);
}
}
@@ -1517,14 +1519,15 @@ void MultiOpsTxnsStressTest::PreloadDb(SharedState* shared, int threads,
WriteBatch wb;
const auto primary_index_entry = record.EncodePrimaryIndexEntry();
Status s = wb.Put(primary_index_entry.first, primary_index_entry.second);
assert(s.ok());
ProcessStatus(shared, "PreloadDB", s, /*ignore_injected_error=*/false);
const auto secondary_index_entry = record.EncodeSecondaryIndexEntry();
s = wb.Put(secondary_index_entry.first, secondary_index_entry.second);
assert(s.ok());
ProcessStatus(shared, "PreloadDB", s, /*ignore_injected_error=*/false);
s = txn_db_->Write(wopts, &wb);
assert(s.ok());
ProcessStatus(shared, "PreloadDB", s, /*ignore_injected_error=*/false);
// TODO (yanqin): make the following check optional, especially when data
// size is large.
@@ -1532,7 +1535,7 @@ void MultiOpsTxnsStressTest::PreloadDb(SharedState* shared, int threads,
tmp_rec.SetB(record.b_value());
s = tmp_rec.DecodeSecondaryIndexEntry(secondary_index_entry.first,
secondary_index_entry.second);
assert(s.ok());
ProcessStatus(shared, "PreloadDB", s, /*ignore_injected_error=*/false);
assert(tmp_rec == record);
existing_a_uniqs[tid].insert(a);
+322 -117
View File
@@ -462,6 +462,44 @@ class NonBatchedOpsStressTest : public StressTest {
bool IsStateTracked() const override { return true; }
void TestKeyMayExist(ThreadState* thread, const ReadOptions& read_opts,
const std::vector<int>& rand_column_families,
const std::vector<int64_t>& rand_keys) override {
auto cfh = column_families_[rand_column_families[0]];
std::string key_str = Key(rand_keys[0]);
Slice key = key_str;
std::string ignore;
ReadOptions read_opts_copy = read_opts;
std::string read_ts_str;
Slice read_ts_slice;
if (FLAGS_user_timestamp_size > 0) {
read_ts_str = GetNowNanos();
read_ts_slice = read_ts_str;
read_opts_copy.timestamp = &read_ts_slice;
}
bool read_older_ts = MaybeUseOlderTimestampForPointLookup(
thread, read_ts_str, read_ts_slice, read_opts_copy);
const ExpectedValue pre_read_expected_value =
thread->shared->Get(rand_column_families[0], rand_keys[0]);
bool key_may_exist = db_->KeyMayExist(read_opts_copy, cfh, key, &ignore);
const ExpectedValue post_read_expected_value =
thread->shared->Get(rand_column_families[0], rand_keys[0]);
if (!key_may_exist && !FLAGS_skip_verifydb && !read_older_ts) {
if (ExpectedValueHelper::MustHaveExisted(pre_read_expected_value,
post_read_expected_value)) {
thread->shared->SetVerificationFailure();
fprintf(stderr,
"error : inconsistent values for key %s: expected state has "
"the key, TestKeyMayExist() returns false indicating the key "
"must not exist.\n",
key.ToString(true).c_str());
}
}
}
Status TestGet(ThreadState* thread, const ReadOptions& read_opts,
const std::vector<int>& rand_column_families,
const std::vector<int64_t>& rand_keys) override {
@@ -916,7 +954,8 @@ class NonBatchedOpsStressTest : public StressTest {
const std::string key = Key(rand_keys[0]);
PinnableWideColumns from_db;
PinnableWideColumns columns_from_db;
PinnableAttributeGroups attribute_groups_from_db;
ReadOptions read_opts_copy = read_opts;
std::string read_ts_str;
@@ -929,7 +968,16 @@ class NonBatchedOpsStressTest : public StressTest {
bool read_older_ts = MaybeUseOlderTimestampForPointLookup(
thread, read_ts_str, read_ts_slice, read_opts_copy);
const Status s = db_->GetEntity(read_opts_copy, cfh, key, &from_db);
Status s;
if (FLAGS_use_attribute_group) {
attribute_groups_from_db.emplace_back(cfh);
s = db_->GetEntity(read_opts_copy, key, &attribute_groups_from_db);
if (s.ok()) {
s = attribute_groups_from_db.back().status();
}
} else {
s = db_->GetEntity(read_opts_copy, cfh, key, &columns_from_db);
}
int error_count = 0;
@@ -953,7 +1001,13 @@ class NonBatchedOpsStressTest : public StressTest {
thread->stats.AddGets(1, 1);
if (!FLAGS_skip_verifydb && !read_older_ts) {
const WideColumns& columns = from_db.columns();
if (FLAGS_use_attribute_group) {
assert(!attribute_groups_from_db.empty());
}
const WideColumns& columns =
FLAGS_use_attribute_group
? attribute_groups_from_db.back().columns()
: columns_from_db.columns();
ExpectedValue expected =
shared->Get(rand_column_families[0], rand_keys[0]);
if (!VerifyWideColumns(columns)) {
@@ -1023,130 +1077,257 @@ class NonBatchedOpsStressTest : public StressTest {
std::vector<std::string> keys(num_keys);
std::vector<Slice> key_slices(num_keys);
for (size_t i = 0; i < num_keys; ++i) {
keys[i] = Key(rand_keys[i]);
key_slices[i] = keys[i];
}
std::vector<PinnableWideColumns> results(num_keys);
std::vector<Status> statuses(num_keys);
if (fault_fs_guard) {
fault_fs_guard->EnableErrorInjection();
SharedState::ignore_read_error = false;
}
db_->MultiGetEntity(read_opts_copy, cfh, num_keys, key_slices.data(),
results.data(), statuses.data());
for (size_t i = 0; i < num_keys; ++i) {
keys[i] = Key(rand_keys[i]);
key_slices[i] = keys[i];
}
int error_count = 0;
if (fault_fs_guard) {
error_count = fault_fs_guard->GetAndResetErrorCount();
if (error_count && !SharedState::ignore_read_error) {
int stat_nok = 0;
for (const auto& s : statuses) {
if (!s.ok() && !s.IsNotFound()) {
stat_nok++;
}
}
if (stat_nok < error_count) {
// Grab mutex so multiple threads don't try to print the
// stack trace at the same time
assert(thread->shared);
MutexLock l(thread->shared->GetMutex());
fprintf(stderr, "Didn't get expected error from MultiGetEntity\n");
fprintf(stderr, "num_keys %zu Expected %d errors, seen %d\n",
num_keys, error_count, stat_nok);
fprintf(stderr, "Call stack that injected the fault\n");
fault_fs_guard->PrintFaultBacktrace();
std::terminate();
auto handle_result = [](ThreadState* _thread, const Status& s,
bool is_consistent, int err_count) {
if (!is_consistent) {
fprintf(stderr,
"TestMultiGetEntity%s error: results are not consistent\n",
FLAGS_use_attribute_group ? "(AttributeGroup)" : "");
_thread->stats.AddErrors(1);
// Fail fast to preserve the DB state
_thread->shared->SetVerificationFailure();
} else if (s.ok()) {
_thread->stats.AddGets(1, 1);
} else if (s.IsNotFound()) {
_thread->stats.AddGets(1, 0);
} else {
if (err_count == 0) {
fprintf(stderr, "MultiGetEntity%s error: %s\n",
FLAGS_use_attribute_group ? "(AttributeGroup)" : "",
s.ToString().c_str());
_thread->stats.AddErrors(1);
} else {
_thread->stats.AddVerifiedErrors(1);
}
}
};
fault_fs_guard->DisableErrorInjection();
}
if (FLAGS_use_attribute_group) {
// AttributeGroup MultiGetEntity verification
const bool check_get_entity =
!error_count && FLAGS_check_multiget_entity_consistency;
std::vector<PinnableAttributeGroups> results;
results.reserve(num_keys);
for (size_t i = 0; i < num_keys; ++i) {
PinnableAttributeGroups attribute_groups;
attribute_groups.emplace_back(cfh);
results.emplace_back(std::move(attribute_groups));
}
db_->MultiGetEntity(read_opts_copy, num_keys, key_slices.data(),
results.data());
for (size_t i = 0; i < num_keys; ++i) {
const Status& s = statuses[i];
if (fault_fs_guard) {
error_count = fault_fs_guard->GetAndResetErrorCount();
bool is_consistent = true;
if (s.ok() && !VerifyWideColumns(results[i].columns())) {
fprintf(
stderr,
"error : inconsistent columns returned by MultiGetEntity for key "
"%s: %s\n",
StringToHex(keys[i]).c_str(),
WideColumnsToHex(results[i].columns()).c_str());
is_consistent = false;
} else if (check_get_entity && (s.ok() || s.IsNotFound())) {
PinnableWideColumns cmp_result;
ThreadStatusUtil::SetThreadOperation(
ThreadStatus::OperationType::OP_GETENTITY);
const Status cmp_s =
db_->GetEntity(read_opts_copy, cfh, key_slices[i], &cmp_result);
if (!cmp_s.ok() && !cmp_s.IsNotFound()) {
fprintf(stderr, "GetEntity error: %s\n", cmp_s.ToString().c_str());
is_consistent = false;
} else if (cmp_s.IsNotFound()) {
if (s.ok()) {
fprintf(stderr,
"Inconsistent results for key %s: MultiGetEntity returned "
"ok, GetEntity returned not found\n",
StringToHex(keys[i]).c_str());
is_consistent = false;
if (error_count && !SharedState::ignore_read_error) {
int stat_nok = 0;
for (size_t i = 0; i < num_keys; ++i) {
const Status& s = results[i][0].status();
if (!s.ok() && !s.IsNotFound()) {
stat_nok++;
}
}
} else {
assert(cmp_s.ok());
if (s.IsNotFound()) {
if (stat_nok < error_count) {
// Grab mutex so multiple threads don't try to print the
// stack trace at the same time
assert(thread->shared);
MutexLock l(thread->shared->GetMutex());
fprintf(stderr,
"Inconsistent results for key %s: MultiGetEntity returned "
"not found, GetEntity returned ok\n",
StringToHex(keys[i]).c_str());
is_consistent = false;
} else {
assert(s.ok());
"Didn't get expected error from MultiGetEntity "
"(AttributeGroup)\n");
fprintf(stderr, "num_keys %zu Expected %d errors, seen %d\n",
num_keys, error_count, stat_nok);
fprintf(stderr, "Call stack that injected the fault\n");
fault_fs_guard->PrintFaultBacktrace();
std::terminate();
}
}
fault_fs_guard->DisableErrorInjection();
}
if (results[i] != cmp_result) {
fprintf(
stderr,
"Inconsistent results for key %s: MultiGetEntity returned "
"%s, GetEntity returned %s\n",
// Compare against non-attribute-group GetEntity result
const bool check_get_entity =
!error_count && FLAGS_check_multiget_entity_consistency;
for (size_t i = 0; i < num_keys; ++i) {
assert(results[i].size() == 1);
const Status& s = results[i][0].status();
bool is_consistent = true;
if (s.ok() && !VerifyWideColumns(results[i][0].columns())) {
fprintf(stderr,
"error : inconsistent columns returned by MultiGetEntity "
"(AttributeGroup) for key "
"%s: %s\n",
StringToHex(keys[i]).c_str(),
WideColumnsToHex(results[i].columns()).c_str(),
WideColumnsToHex(cmp_result.columns()).c_str());
WideColumnsToHex(results[i][0].columns()).c_str());
is_consistent = false;
} else if (check_get_entity && (s.ok() || s.IsNotFound())) {
PinnableWideColumns cmp_result;
ThreadStatusUtil::SetThreadOperation(
ThreadStatus::OperationType::OP_GETENTITY);
const Status cmp_s =
db_->GetEntity(read_opts_copy, cfh, key_slices[i], &cmp_result);
if (!cmp_s.ok() && !cmp_s.IsNotFound()) {
fprintf(stderr, "GetEntity error: %s\n", cmp_s.ToString().c_str());
is_consistent = false;
} else if (cmp_s.IsNotFound()) {
if (s.ok()) {
fprintf(stderr,
"Inconsistent results for key %s: MultiGetEntity "
"(AttributeGroup) returned "
"ok, GetEntity returned not found\n",
StringToHex(keys[i]).c_str());
is_consistent = false;
}
} else {
assert(cmp_s.ok());
if (s.IsNotFound()) {
fprintf(stderr,
"Inconsistent results for key %s: MultiGetEntity "
"(AttributeGroup) returned "
"not found, GetEntity returned ok\n",
StringToHex(keys[i]).c_str());
is_consistent = false;
} else {
assert(s.ok());
if (results[i][0].columns() != cmp_result.columns()) {
fprintf(stderr,
"Inconsistent results for key %s: MultiGetEntity "
"(AttributeGroup) returned "
"%s, GetEntity returned %s\n",
StringToHex(keys[i]).c_str(),
WideColumnsToHex(results[i][0].columns()).c_str(),
WideColumnsToHex(cmp_result.columns()).c_str());
is_consistent = false;
}
}
}
}
handle_result(thread, s, is_consistent, error_count);
if (!is_consistent) {
break;
}
}
} else {
// Non-AttributeGroup MultiGetEntity verification
std::vector<PinnableWideColumns> results(num_keys);
std::vector<Status> statuses(num_keys);
db_->MultiGetEntity(read_opts_copy, cfh, num_keys, key_slices.data(),
results.data(), statuses.data());
if (fault_fs_guard) {
error_count = fault_fs_guard->GetAndResetErrorCount();
if (error_count && !SharedState::ignore_read_error) {
int stat_nok = 0;
for (const auto& s : statuses) {
if (!s.ok() && !s.IsNotFound()) {
stat_nok++;
}
}
if (stat_nok < error_count) {
// Grab mutex so multiple threads don't try to print the
// stack trace at the same time
assert(thread->shared);
MutexLock l(thread->shared->GetMutex());
fprintf(stderr, "Didn't get expected error from MultiGetEntity\n");
fprintf(stderr, "num_keys %zu Expected %d errors, seen %d\n",
num_keys, error_count, stat_nok);
fprintf(stderr, "Call stack that injected the fault\n");
fault_fs_guard->PrintFaultBacktrace();
std::terminate();
}
}
fault_fs_guard->DisableErrorInjection();
}
if (!is_consistent) {
fprintf(stderr,
"TestMultiGetEntity error: results are not consistent\n");
thread->stats.AddErrors(1);
// Fail fast to preserve the DB state
thread->shared->SetVerificationFailure();
break;
} else if (s.ok()) {
thread->stats.AddGets(1, 1);
} else if (s.IsNotFound()) {
thread->stats.AddGets(1, 0);
} else {
if (error_count == 0) {
fprintf(stderr, "MultiGetEntity error: %s\n", s.ToString().c_str());
thread->stats.AddErrors(1);
} else {
thread->stats.AddVerifiedErrors(1);
const bool check_get_entity =
!error_count && FLAGS_check_multiget_entity_consistency;
for (size_t i = 0; i < num_keys; ++i) {
const Status& s = statuses[i];
bool is_consistent = true;
if (s.ok() && !VerifyWideColumns(results[i].columns())) {
fprintf(
stderr,
"error : inconsistent columns returned by MultiGetEntity for key "
"%s: %s\n",
StringToHex(keys[i]).c_str(),
WideColumnsToHex(results[i].columns()).c_str());
is_consistent = false;
} else if (check_get_entity && (s.ok() || s.IsNotFound())) {
PinnableWideColumns cmp_result;
ThreadStatusUtil::SetThreadOperation(
ThreadStatus::OperationType::OP_GETENTITY);
const Status cmp_s =
db_->GetEntity(read_opts_copy, cfh, key_slices[i], &cmp_result);
if (!cmp_s.ok() && !cmp_s.IsNotFound()) {
fprintf(stderr, "GetEntity error: %s\n", cmp_s.ToString().c_str());
is_consistent = false;
} else if (cmp_s.IsNotFound()) {
if (s.ok()) {
fprintf(
stderr,
"Inconsistent results for key %s: MultiGetEntity returned "
"ok, GetEntity returned not found\n",
StringToHex(keys[i]).c_str());
is_consistent = false;
}
} else {
assert(cmp_s.ok());
if (s.IsNotFound()) {
fprintf(
stderr,
"Inconsistent results for key %s: MultiGetEntity returned "
"not found, GetEntity returned ok\n",
StringToHex(keys[i]).c_str());
is_consistent = false;
} else {
assert(s.ok());
if (results[i] != cmp_result) {
fprintf(
stderr,
"Inconsistent results for key %s: MultiGetEntity returned "
"%s, GetEntity returned %s\n",
StringToHex(keys[i]).c_str(),
WideColumnsToHex(results[i].columns()).c_str(),
WideColumnsToHex(cmp_result.columns()).c_str());
is_consistent = false;
}
}
}
}
handle_result(thread, s, is_consistent, error_count);
if (!is_consistent) {
break;
}
}
}
@@ -1302,8 +1483,13 @@ class NonBatchedOpsStressTest : public StressTest {
if (FLAGS_use_put_entity_one_in > 0 &&
(value_base % FLAGS_use_put_entity_one_in) == 0) {
s = db_->PutEntity(write_opts, cfh, k,
GenerateWideColumns(value_base, v));
if (FLAGS_use_attribute_group) {
s = db_->PutEntity(write_opts, k,
GenerateAttributeGroups({cfh}, value_base, v));
} else {
s = db_->PutEntity(write_opts, cfh, k,
GenerateWideColumns(value_base, v));
}
} else if (FLAGS_use_timed_put_one_in > 0 &&
((value_base + kLargePrimeForCommonFactorSkew) %
FLAGS_use_timed_put_one_in) == 0) {
@@ -2079,7 +2265,7 @@ class NonBatchedOpsStressTest : public StressTest {
return Status::OK();
}
bool VerifyOrSyncValue(int cf, int64_t key, const ReadOptions& /*opts*/,
bool VerifyOrSyncValue(int cf, int64_t key, const ReadOptions& opts,
SharedState* shared, const std::string& value_from_db,
std::string msg_prefix, const Status& s) const {
if (shared->HasVerificationFailedYet()) {
@@ -2105,27 +2291,43 @@ class NonBatchedOpsStressTest : public StressTest {
GenerateValue(expected_value.GetValueBase(), expected_value_data,
sizeof(expected_value_data));
std::ostringstream read_u64ts;
if (opts.timestamp) {
read_u64ts << " while read with timestamp: ";
uint64_t read_ts;
if (DecodeU64Ts(*opts.timestamp, &read_ts).ok()) {
read_u64ts << std::to_string(read_ts) << ", ";
} else {
read_u64ts << s.ToString()
<< " Encoded read timestamp: " << opts.timestamp->ToString()
<< ", ";
}
}
// compare value_from_db with the value in the shared state
if (s.ok()) {
const Slice slice(value_from_db);
const uint32_t value_base_from_db = GetValueBase(slice);
if (ExpectedValueHelper::MustHaveNotExisted(expected_value,
expected_value)) {
VerificationAbort(shared, msg_prefix + ": Unexpected value found", cf,
key, value_from_db, "");
VerificationAbort(
shared, msg_prefix + ": Unexpected value found" + read_u64ts.str(),
cf, key, value_from_db, "");
return false;
}
if (!ExpectedValueHelper::InExpectedValueBaseRange(
value_base_from_db, expected_value, expected_value)) {
VerificationAbort(shared, msg_prefix + ": Unexpected value found", cf,
key, value_from_db,
Slice(expected_value_data, expected_value_data_size));
VerificationAbort(
shared, msg_prefix + ": Unexpected value found" + read_u64ts.str(),
cf, key, value_from_db,
Slice(expected_value_data, expected_value_data_size));
return false;
}
// TODO: are the length/memcmp() checks repetitive?
if (value_from_db.length() != expected_value_data_size) {
VerificationAbort(shared,
msg_prefix + ": Length of value read is not equal",
msg_prefix + ": Length of value read is not equal" +
read_u64ts.str(),
cf, key, value_from_db,
Slice(expected_value_data, expected_value_data_size));
return false;
@@ -2133,7 +2335,8 @@ class NonBatchedOpsStressTest : public StressTest {
if (memcmp(value_from_db.data(), expected_value_data,
expected_value_data_size) != 0) {
VerificationAbort(shared,
msg_prefix + ": Contents of value read don't match",
msg_prefix + ": Contents of value read don't match" +
read_u64ts.str(),
cf, key, value_from_db,
Slice(expected_value_data, expected_value_data_size));
return false;
@@ -2142,14 +2345,16 @@ class NonBatchedOpsStressTest : public StressTest {
if (ExpectedValueHelper::MustHaveExisted(expected_value,
expected_value)) {
VerificationAbort(
shared, msg_prefix + ": Value not found: " + s.ToString(), cf, key,
"", Slice(expected_value_data, expected_value_data_size));
shared,
msg_prefix + ": Value not found " + read_u64ts.str() + s.ToString(),
cf, key, "", Slice(expected_value_data, expected_value_data_size));
return false;
}
} else {
VerificationAbort(shared, msg_prefix + "Non-OK status: " + s.ToString(),
cf, key, "",
Slice(expected_value_data, expected_value_data_size));
VerificationAbort(
shared,
msg_prefix + "Non-OK status " + read_u64ts.str() + s.ToString(), cf,
key, "", Slice(expected_value_data, expected_value_data_size));
return false;
}
return true;
+3 -1
View File
@@ -1441,10 +1441,12 @@ void PosixWritableFile::SetWriteLifeTimeHint(Env::WriteLifeTimeHint hint) {
#ifdef OS_LINUX
// Suppress Valgrind "Unimplemented functionality" error.
#ifndef ROCKSDB_VALGRIND_RUN
uint64_t fcntl_hint = hint;
if (hint == write_hint_) {
return;
}
if (fcntl(fd_, F_SET_RW_HINT, &hint) == 0) {
if (fcntl(fd_, F_SET_RW_HINT, &fcntl_hint) == 0) {
write_hint_ = hint;
}
#else
+124 -23
View File
@@ -31,6 +31,7 @@ DeleteScheduler::DeleteScheduler(SystemClock* clock, FileSystem* fs,
total_trash_size_(0),
rate_bytes_per_sec_(rate_bytes_per_sec),
pending_files_(0),
next_trash_bucket_(0),
bytes_max_delete_chunk_(bytes_max_delete_chunk),
closing_(false),
cv_(&mu_),
@@ -66,10 +67,8 @@ Status DeleteScheduler::DeleteFile(const std::string& file_path,
total_trash_size_.load() > total_size * max_trash_db_ratio_.load())) {
// Rate limiting is disabled or trash size makes up more than
// max_trash_db_ratio_ (default 25%) of the total DB size
TEST_SYNC_POINT("DeleteScheduler::DeleteFile");
Status s = fs_->DeleteFile(file_path, IOOptions(), nullptr);
Status s = DeleteFileImmediately(file_path, /*accounted=*/true);
if (s.ok()) {
s = sst_file_manager_->OnDeleteFile(file_path);
ROCKS_LOG_INFO(info_log_,
"Deleted file %s immediately, rate_bytes_per_sec %" PRIi64
", total_trash_size %" PRIu64 ", total_size %" PRIi64
@@ -77,15 +76,57 @@ Status DeleteScheduler::DeleteFile(const std::string& file_path,
file_path.c_str(), rate_bytes_per_sec_.load(),
total_trash_size_.load(), total_size,
max_trash_db_ratio_.load());
InstrumentedMutexLock l(&mu_);
RecordTick(stats_.get(), FILES_DELETED_IMMEDIATELY);
}
return s;
}
return AddFileToDeletionQueue(file_path, dir_to_sync, /*bucket=*/std::nullopt,
/*accounted=*/true);
}
Status DeleteScheduler::DeleteUnaccountedFile(const std::string& file_path,
const std::string& dir_to_sync,
const bool force_bg,
std::optional<int32_t> bucket) {
uint64_t num_hard_links = 1;
fs_->NumFileLinks(file_path, IOOptions(), &num_hard_links, nullptr)
.PermitUncheckedError();
// We can tolerate rare races where we might immediately delete both links
// to a file.
if (rate_bytes_per_sec_.load() <= 0 || (!force_bg && num_hard_links > 1)) {
Status s = DeleteFileImmediately(file_path, /*accounted=*/false);
if (s.ok()) {
ROCKS_LOG_INFO(info_log_,
"Deleted file %s immediately, rate_bytes_per_sec %" PRIi64,
file_path.c_str(), rate_bytes_per_sec_.load());
}
return s;
}
return AddFileToDeletionQueue(file_path, dir_to_sync, bucket,
/*accounted=*/false);
}
Status DeleteScheduler::DeleteFileImmediately(const std::string& file_path,
bool accounted) {
TEST_SYNC_POINT("DeleteScheduler::DeleteFile");
TEST_SYNC_POINT_CALLBACK("DeleteScheduler::DeleteFile::cb",
const_cast<std::string*>(&file_path));
Status s = fs_->DeleteFile(file_path, IOOptions(), nullptr);
if (s.ok()) {
s = OnDeleteFile(file_path, accounted);
InstrumentedMutexLock l(&mu_);
RecordTick(stats_.get(), FILES_DELETED_IMMEDIATELY);
}
return s;
}
Status DeleteScheduler::AddFileToDeletionQueue(const std::string& file_path,
const std::string& dir_to_sync,
std::optional<int32_t> bucket,
bool accounted) {
// Move file to trash
std::string trash_file;
Status s = MarkAsTrash(file_path, &trash_file);
Status s = MarkAsTrash(file_path, accounted, &trash_file);
ROCKS_LOG_INFO(info_log_, "Mark file: %s as trash -- %s", trash_file.c_str(),
s.ToString().c_str());
@@ -94,7 +135,7 @@ Status DeleteScheduler::DeleteFile(const std::string& file_path,
file_path.c_str(), s.ToString().c_str());
s = fs_->DeleteFile(file_path, IOOptions(), nullptr);
if (s.ok()) {
s = sst_file_manager_->OnDeleteFile(file_path);
s = OnDeleteFile(file_path, accounted);
ROCKS_LOG_INFO(info_log_, "Deleted file %s immediately",
trash_file.c_str());
InstrumentedMutexLock l(&mu_);
@@ -104,11 +145,13 @@ Status DeleteScheduler::DeleteFile(const std::string& file_path,
}
// Update the total trash size
uint64_t trash_file_size = 0;
IOStatus io_s =
fs_->GetFileSize(trash_file, IOOptions(), &trash_file_size, nullptr);
if (io_s.ok()) {
total_trash_size_.fetch_add(trash_file_size);
if (accounted) {
uint64_t trash_file_size = 0;
IOStatus io_s =
fs_->GetFileSize(trash_file, IOOptions(), &trash_file_size, nullptr);
if (io_s.ok()) {
total_trash_size_.fetch_add(trash_file_size);
}
}
//**TODO: What should we do if we failed to
// get the file size?
@@ -117,8 +160,15 @@ Status DeleteScheduler::DeleteFile(const std::string& file_path,
{
InstrumentedMutexLock l(&mu_);
RecordTick(stats_.get(), FILES_MARKED_TRASH);
queue_.emplace(trash_file, dir_to_sync);
queue_.emplace(trash_file, dir_to_sync, accounted, bucket);
pending_files_++;
if (bucket.has_value()) {
auto iter = pending_files_in_buckets_.find(bucket.value());
assert(iter != pending_files_in_buckets_.end());
if (iter != pending_files_in_buckets_.end()) {
iter->second++;
}
}
if (pending_files_ == 1) {
cv_.SignalAll();
}
@@ -177,7 +227,7 @@ Status DeleteScheduler::CleanupDirectory(Env* env, SstFileManagerImpl* sfm,
}
Status DeleteScheduler::MarkAsTrash(const std::string& file_path,
std::string* trash_file) {
bool accounted, std::string* trash_file) {
// Sanity check of the path
size_t idx = file_path.rfind('/');
if (idx == std::string::npos || idx == file_path.size() - 1) {
@@ -211,7 +261,7 @@ Status DeleteScheduler::MarkAsTrash(const std::string& file_path,
}
cnt++;
}
if (s.ok()) {
if (s.ok() && accounted) {
s = sst_file_manager_->OnMoveFile(file_path, *trash_file);
}
return s;
@@ -235,6 +285,8 @@ void DeleteScheduler::BackgroundEmptyTrash() {
uint64_t total_deleted_bytes = 0;
int64_t current_delete_rate = rate_bytes_per_sec_.load();
while (!queue_.empty() && !closing_) {
// Satisfy static analysis.
std::optional<int32_t> bucket = std::nullopt;
if (current_delete_rate != rate_bytes_per_sec_.load()) {
// User changed the delete rate
current_delete_rate = rate_bytes_per_sec_.load();
@@ -247,14 +299,17 @@ void DeleteScheduler::BackgroundEmptyTrash() {
// Get new file to delete
const FileAndDir& fad = queue_.front();
std::string path_in_trash = fad.fname;
std::string dir_to_sync = fad.dir;
bool accounted = fad.accounted;
bucket = fad.bucket;
// We don't need to hold the lock while deleting the file
mu_.Unlock();
uint64_t deleted_bytes = 0;
bool is_complete = true;
// Delete file from trash and update total_penlty value
Status s =
DeleteTrashFile(path_in_trash, fad.dir, &deleted_bytes, &is_complete);
Status s = DeleteTrashFile(path_in_trash, dir_to_sync, accounted,
&deleted_bytes, &is_complete);
total_deleted_bytes += deleted_bytes;
mu_.Lock();
if (is_complete) {
@@ -288,12 +343,20 @@ void DeleteScheduler::BackgroundEmptyTrash() {
TEST_SYNC_POINT_CALLBACK("DeleteScheduler::BackgroundEmptyTrash:Wait",
&total_penalty);
int32_t pending_files_in_bucket = std::numeric_limits<int32_t>::max();
if (is_complete) {
pending_files_--;
if (bucket.has_value()) {
auto iter = pending_files_in_buckets_.find(bucket.value());
assert(iter != pending_files_in_buckets_.end());
if (iter != pending_files_in_buckets_.end()) {
pending_files_in_bucket = iter->second--;
}
}
}
if (pending_files_ == 0) {
// Unblock WaitForEmptyTrash since there are no more files waiting
// to be deleted
if (pending_files_ == 0 || pending_files_in_bucket == 0) {
// Unblock WaitForEmptyTrash or WaitForEmptyTrashBucket since there are
// no more files waiting to be deleted
cv_.SignalAll();
}
}
@@ -302,12 +365,14 @@ void DeleteScheduler::BackgroundEmptyTrash() {
Status DeleteScheduler::DeleteTrashFile(const std::string& path_in_trash,
const std::string& dir_to_sync,
uint64_t* deleted_bytes,
bool accounted, uint64_t* deleted_bytes,
bool* is_complete) {
uint64_t file_size;
Status s = fs_->GetFileSize(path_in_trash, IOOptions(), &file_size, nullptr);
*is_complete = true;
TEST_SYNC_POINT("DeleteScheduler::DeleteTrashFile:DeleteFile");
TEST_SYNC_POINT_CALLBACK("DeleteScheduler::DeleteTrashFile::cb",
const_cast<std::string*>(&path_in_trash));
if (s.ok()) {
bool need_full_delete = true;
if (bytes_max_delete_chunk_ != 0 && file_size > bytes_max_delete_chunk_) {
@@ -374,7 +439,7 @@ Status DeleteScheduler::DeleteTrashFile(const std::string& path_in_trash,
}
if (s.ok()) {
*deleted_bytes = file_size;
s = sst_file_manager_->OnDeleteFile(path_in_trash);
s = OnDeleteFile(path_in_trash, accounted);
}
}
}
@@ -384,12 +449,24 @@ Status DeleteScheduler::DeleteTrashFile(const std::string& path_in_trash,
path_in_trash.c_str(), s.ToString().c_str());
*deleted_bytes = 0;
} else {
total_trash_size_.fetch_sub(*deleted_bytes);
if (accounted) {
total_trash_size_.fetch_sub(*deleted_bytes);
}
}
return s;
}
Status DeleteScheduler::OnDeleteFile(const std::string& file_path,
bool accounted) {
if (accounted) {
return sst_file_manager_->OnDeleteFile(file_path);
}
TEST_SYNC_POINT_CALLBACK("DeleteScheduler::OnDeleteFile",
const_cast<std::string*>(&file_path));
return Status::OK();
}
void DeleteScheduler::WaitForEmptyTrash() {
InstrumentedMutexLock l(&mu_);
while (pending_files_ > 0 && !closing_) {
@@ -397,6 +474,30 @@ void DeleteScheduler::WaitForEmptyTrash() {
}
}
std::optional<int32_t> DeleteScheduler::NewTrashBucket() {
if (rate_bytes_per_sec_.load() <= 0) {
return std::nullopt;
}
InstrumentedMutexLock l(&mu_);
int32_t bucket_number = next_trash_bucket_++;
pending_files_in_buckets_.emplace(bucket_number, 0);
return bucket_number;
}
void DeleteScheduler::WaitForEmptyTrashBucket(int32_t bucket) {
InstrumentedMutexLock l(&mu_);
if (bucket >= next_trash_bucket_) {
return;
}
auto iter = pending_files_in_buckets_.find(bucket);
while (iter != pending_files_in_buckets_.end() && iter->second > 0 &&
!closing_) {
cv_.Wait();
iter = pending_files_in_buckets_.find(bucket);
}
pending_files_in_buckets_.erase(bucket);
}
void DeleteScheduler::MaybeCreateBackgroundThread() {
if (bg_thread_ == nullptr && rate_bytes_per_sec_.load() > 0) {
bg_thread_.reset(
+61 -8
View File
@@ -7,6 +7,7 @@
#include <map>
#include <optional>
#include <queue>
#include <string>
#include <thread>
@@ -48,16 +49,45 @@ class DeleteScheduler {
MaybeCreateBackgroundThread();
}
// Mark file as trash directory and schedule its deletion. If force_bg is
// set, it forces the file to always be deleted in the background thread,
// except when rate limiting is disabled
// Delete an accounted file that is tracked by `SstFileManager` and should be
// tracked by this `DeleteScheduler` when it's deleted.
// The file is deleted immediately if slow deletion is disabled. If force_bg
// is not set and trash to db size ratio exceeded the configured threshold,
// it is immediately deleted too. In all other cases, the file will be moved
// to a trash directory and scheduled for deletion by a background thread.
Status DeleteFile(const std::string& fname, const std::string& dir_to_sync,
const bool force_bg = false);
// Wait for all files being deleteing in the background to finish or for
// Delete an unaccounted file that is not tracked by `SstFileManager` and
// should not be tracked by this `DeleteScheduler` when it's deleted.
// The file is deleted immediately if slow deletion is disabled. If force_bg
// is not set and the file have more than 1 hard link, it is immediately
// deleted too. In all other cases, the file will be moved to a trash
// directory and scheduled for deletion by a background thread.
// This API also supports assign a file to a specified bucket created by
// `NewTrashBucket` when delete files in the background. So the caller can
// wait for a specific bucket to be empty by checking the
// `WaitForEmptyTrashBucket` API.
Status DeleteUnaccountedFile(const std::string& file_path,
const std::string& dir_to_sync,
const bool force_bg = false,
std::optional<int32_t> bucket = std::nullopt);
// Wait for all files being deleted in the background to finish or for
// destructor to be called.
void WaitForEmptyTrash();
// Creates a new trash bucket. A bucket is only created and returned when slow
// deletion is enabled.
// For each bucket that is created, the user should also call
// `WaitForEmptyTrashBucket` after scheduling file deletions to make sure the
// trash files are all cleared.
std::optional<int32_t> NewTrashBucket();
// Wait for all the files in the specified bucket to be deleted in the
// background or for the destructor to be called.
void WaitForEmptyTrashBucket(int32_t bucket);
// Return a map containing errors that happened in BackgroundEmptyTrash
// file_path => error status
std::map<std::string, Status> GetBackgroundErrors();
@@ -87,12 +117,21 @@ class DeleteScheduler {
}
private:
Status MarkAsTrash(const std::string& file_path, std::string* path_in_trash);
Status DeleteFileImmediately(const std::string& file_path, bool accounted);
Status AddFileToDeletionQueue(const std::string& file_path,
const std::string& dir_to_sync,
std::optional<int32_t> bucket, bool accounted);
Status MarkAsTrash(const std::string& file_path, bool accounted,
std::string* path_in_trash);
Status DeleteTrashFile(const std::string& path_in_trash,
const std::string& dir_to_sync,
const std::string& dir_to_sync, bool accounted,
uint64_t* deleted_bytes, bool* is_complete);
Status OnDeleteFile(const std::string& file_path, bool accounted);
void BackgroundEmptyTrash();
void MaybeCreateBackgroundThread();
@@ -104,19 +143,28 @@ class DeleteScheduler {
std::atomic<uint64_t> total_trash_size_;
// Maximum number of bytes that should be deleted per second
std::atomic<int64_t> rate_bytes_per_sec_;
// Mutex to protect queue_, pending_files_, bg_errors_, closing_, stats_
// Mutex to protect queue_, pending_files_, next_trash_bucket_,
// pending_files_in_buckets_, bg_errors_, closing_, stats_
InstrumentedMutex mu_;
struct FileAndDir {
FileAndDir(const std::string& f, const std::string& d) : fname(f), dir(d) {}
FileAndDir(const std::string& _fname, const std::string& _dir,
bool _accounted, std::optional<int32_t> _bucket)
: fname(_fname), dir(_dir), accounted(_accounted), bucket(_bucket) {}
std::string fname;
std::string dir; // empty will be skipped.
bool accounted;
std::optional<int32_t> bucket;
};
// Queue of trash files that need to be deleted
std::queue<FileAndDir> queue_;
// Number of trash files that are waiting to be deleted
int32_t pending_files_;
// Next trash bucket that can be created
int32_t next_trash_bucket_;
// A mapping from trash bucket to number of pending files in the bucket
std::map<int32_t, int32_t> pending_files_in_buckets_;
uint64_t bytes_max_delete_chunk_;
// Errors that happened in BackgroundEmptyTrash (file_path => error)
std::map<std::string, Status> bg_errors_;
@@ -127,6 +175,7 @@ class DeleteScheduler {
// Condition variable signaled in these conditions
// - pending_files_ value change from 0 => 1
// - pending_files_ value change from 1 => 0
// - a value in pending_files_in_buckets change from 1 => 0
// - closing_ value is set to true
InstrumentedCondVar cv_;
// Background thread running BackgroundEmptyTrash
@@ -138,6 +187,10 @@ class DeleteScheduler {
// If the trash size constitutes for more than this fraction of the total DB
// size we will start deleting new files passed to DeleteScheduler
// immediately
// Unaccounted files passed for deletion will not cause change in
// total_trash_size_ or affect the DeleteScheduler::total_trash_size_ over
// SstFileManager::total_size_ ratio. Their slow deletion is not subject to
// this configured threshold either.
std::atomic<double> max_trash_db_ratio_;
static const uint64_t kMicrosInSecond = 1000 * 1000LL;
std::shared_ptr<Statistics> stats_;
+141 -2
View File
@@ -78,7 +78,7 @@ class DeleteSchedulerTest : public testing::Test {
}
std::string NewDummyFile(const std::string& file_name, uint64_t size = 1024,
size_t dummy_files_dirs_idx = 0) {
size_t dummy_files_dirs_idx = 0, bool track = true) {
std::string file_path =
dummy_files_dirs_[dummy_files_dirs_idx] + "/" + file_name;
std::unique_ptr<WritableFile> f;
@@ -86,7 +86,9 @@ class DeleteSchedulerTest : public testing::Test {
std::string data(size, 'A');
EXPECT_OK(f->Append(data));
EXPECT_OK(f->Close());
EXPECT_OK(sst_file_mgr_->OnAddFile(file_path));
if (track) {
EXPECT_OK(sst_file_mgr_->OnAddFile(file_path));
}
return file_path;
}
@@ -353,6 +355,8 @@ TEST_F(DeleteSchedulerTest, DisableRateLimiting) {
ASSERT_EQ(num_files,
stats_->getAndResetTickerCount(FILES_DELETED_IMMEDIATELY));
ASSERT_FALSE(delete_scheduler_->NewTrashBucket().has_value());
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
}
@@ -718,6 +722,141 @@ TEST_F(DeleteSchedulerTest, IsTrashCheck) {
ASSERT_FALSE(DeleteScheduler::IsTrashFile("abc.trashx"));
}
TEST_F(DeleteSchedulerTest, DeleteAccountedAndUnaccountedFiles) {
rate_bytes_per_sec_ = 1024 * 1024; // 1 MB / s
NewDeleteScheduler();
// Create 100 files, every file is 1 KB
int num_files = 100; // 100 files
uint64_t file_size = 1024; // 1 KB as a file size
std::vector<std::string> generated_files;
for (int i = 0; i < num_files; i++) {
std::string file_name = "file" + std::to_string(i) + ".data";
generated_files.push_back(NewDummyFile(file_name, file_size,
/*dummy_files_dirs_idx*/ 0,
/*track=*/false));
}
for (int i = 0; i < num_files; i++) {
if (i % 2) {
ASSERT_OK(sst_file_mgr_->OnAddFile(generated_files[i], file_size));
ASSERT_OK(delete_scheduler_->DeleteFile(generated_files[i], ""));
} else {
ASSERT_OK(
delete_scheduler_->DeleteUnaccountedFile(generated_files[i], ""));
}
}
delete_scheduler_->WaitForEmptyTrash();
ASSERT_EQ(0, delete_scheduler_->GetTotalTrashSize());
ASSERT_EQ(0, sst_file_mgr_->GetTotalSize());
}
TEST_F(DeleteSchedulerTest, ConcurrentlyDeleteUnaccountedFilesInBuckets) {
int bg_delete_file = 0;
int fg_delete_file = 0;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"DeleteScheduler::DeleteTrashFile:DeleteFile",
[&](void* /*arg*/) { bg_delete_file++; });
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"DeleteScheduler::DeleteFile", [&](void* /*arg*/) { fg_delete_file++; });
rate_bytes_per_sec_ = 1024 * 1024; // 1 MB / s
NewDeleteScheduler();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
// Create 1000 files, every file is 1 KB
int num_files = 1000;
uint64_t file_size = 1024; // 1 KB as a file size
std::vector<std::string> generated_files;
for (int i = 0; i < num_files; i++) {
std::string file_name = "file" + std::to_string(i) + ".data";
generated_files.push_back(NewDummyFile(file_name, file_size,
/*dummy_files_dirs_idx*/ 0,
/*track=*/false));
}
// Concurrently delete files in different buckets and check all the buckets
// are empty.
int thread_cnt = 10;
int files_per_thread = 100;
std::atomic<int> thread_num(0);
std::vector<port::Thread> threads;
std::function<void()> delete_thread = [&]() {
std::optional<int32_t> bucket = delete_scheduler_->NewTrashBucket();
ASSERT_TRUE(bucket.has_value());
int idx = thread_num.fetch_add(1);
int range_start = idx * files_per_thread;
int range_end = range_start + files_per_thread;
for (int j = range_start; j < range_end; j++) {
ASSERT_OK(delete_scheduler_->DeleteUnaccountedFile(
generated_files[j], "", /*false_bg=*/false, bucket));
}
delete_scheduler_->WaitForEmptyTrashBucket(bucket.value());
};
for (int i = 0; i < thread_cnt; i++) {
threads.emplace_back(delete_thread);
}
for (size_t i = 0; i < threads.size(); i++) {
threads[i].join();
}
ASSERT_EQ(0, delete_scheduler_->GetTotalTrashSize());
ASSERT_EQ(0, stats_->getAndResetTickerCount(FILES_DELETED_IMMEDIATELY));
ASSERT_EQ(1000, stats_->getAndResetTickerCount(FILES_MARKED_TRASH));
ASSERT_EQ(0, fg_delete_file);
ASSERT_EQ(1000, bg_delete_file);
// OK to re check an already empty bucket
delete_scheduler_->WaitForEmptyTrashBucket(9);
// Invalid bucket return too.
delete_scheduler_->WaitForEmptyTrashBucket(100);
std::optional<int32_t> next_bucket = delete_scheduler_->NewTrashBucket();
ASSERT_TRUE(next_bucket.has_value());
ASSERT_EQ(10, next_bucket.value());
delete_scheduler_->WaitForEmptyTrashBucket(10);
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
}
TEST_F(DeleteSchedulerTest,
ImmediatelyDeleteUnaccountedFilesWithRemainingLinks) {
int bg_delete_file = 0;
int fg_delete_file = 0;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"DeleteScheduler::DeleteTrashFile:DeleteFile",
[&](void* /*arg*/) { bg_delete_file++; });
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"DeleteScheduler::DeleteFile", [&](void* /*arg*/) { fg_delete_file++; });
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
rate_bytes_per_sec_ = 1024 * 1024; // 1 MB / sec
NewDeleteScheduler();
std::string file1 = NewDummyFile("data_1", 500 * 1024,
/*dummy_files_dirs_idx*/ 0, /*track=*/false);
std::string file2 = NewDummyFile("data_2", 100 * 1024,
/*dummy_files_dirs_idx*/ 0, /*track=*/false);
ASSERT_OK(env_->LinkFile(file1, dummy_files_dirs_[0] + "/data_1b"));
ASSERT_OK(env_->LinkFile(file2, dummy_files_dirs_[0] + "/data_2b"));
// Should delete in 4 batch if there is no hardlink
ASSERT_OK(
delete_scheduler_->DeleteUnaccountedFile(file1, "", /*force_bg=*/false));
ASSERT_OK(
delete_scheduler_->DeleteUnaccountedFile(file2, "", /*force_bg=*/false));
delete_scheduler_->WaitForEmptyTrash();
ASSERT_EQ(0, delete_scheduler_->GetTotalTrashSize());
ASSERT_EQ(0, bg_delete_file);
ASSERT_EQ(2, fg_delete_file);
ASSERT_EQ(0, stats_->getAndResetTickerCount(FILES_MARKED_TRASH));
ASSERT_EQ(2, stats_->getAndResetTickerCount(FILES_DELETED_IMMEDIATELY));
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+17 -2
View File
@@ -122,8 +122,8 @@ IOStatus CreateFile(FileSystem* fs, const std::string& destination,
Status DeleteDBFile(const ImmutableDBOptions* db_options,
const std::string& fname, const std::string& dir_to_sync,
const bool force_bg, const bool force_fg) {
SstFileManagerImpl* sfm =
static_cast<SstFileManagerImpl*>(db_options->sst_file_manager.get());
SstFileManagerImpl* sfm = static_cast_with_check<SstFileManagerImpl>(
db_options->sst_file_manager.get());
if (sfm && !force_fg) {
return sfm->ScheduleFileDeletion(fname, dir_to_sync, force_bg);
} else {
@@ -131,6 +131,21 @@ Status DeleteDBFile(const ImmutableDBOptions* db_options,
}
}
Status DeleteUnaccountedDBFile(const ImmutableDBOptions* db_options,
const std::string& fname,
const std::string& dir_to_sync,
const bool force_bg, const bool force_fg,
std::optional<int32_t> bucket) {
SstFileManagerImpl* sfm = static_cast_with_check<SstFileManagerImpl>(
db_options->sst_file_manager.get());
if (sfm && !force_fg) {
return sfm->ScheduleUnaccountedFileDeletion(fname, dir_to_sync, force_bg,
bucket);
} else {
return db_options->env->DeleteFile(fname);
}
}
// requested_checksum_func_name brings the function name of the checksum
// generator in checksum_factory. Empty string is permitted, in which case the
// name of the generator created by the factory is unchecked. When
+10
View File
@@ -55,6 +55,16 @@ Status DeleteDBFile(const ImmutableDBOptions* db_options,
const std::string& fname, const std::string& path_to_sync,
const bool force_bg, const bool force_fg);
// Delete an unaccounted DB file that is not tracked by SstFileManager and will
// not be tracked by its DeleteScheduler when getting deleted.
// If a legitimate bucket is provided and this file is scheduled for slow
// deletion, it will be assigned to the specified trash bucket.
Status DeleteUnaccountedDBFile(const ImmutableDBOptions* db_options,
const std::string& fname,
const std::string& dir_to_sync,
const bool force_bg, const bool force_fg,
std::optional<int32_t> bucket);
// TODO(hx235): pass the whole DBOptions intead of its individual fields
IOStatus GenerateOneFileChecksum(
FileSystem* fs, const std::string& file_path,
+18
View File
@@ -421,10 +421,28 @@ Status SstFileManagerImpl::ScheduleFileDeletion(const std::string& file_path,
return delete_scheduler_.DeleteFile(file_path, path_to_sync, force_bg);
}
Status SstFileManagerImpl::ScheduleUnaccountedFileDeletion(
const std::string& file_path, const std::string& dir_to_sync,
const bool force_bg, std::optional<int32_t> bucket) {
TEST_SYNC_POINT_CALLBACK(
"SstFileManagerImpl::ScheduleUnaccountedFileDeletion",
const_cast<std::string*>(&file_path));
return delete_scheduler_.DeleteUnaccountedFile(file_path, dir_to_sync,
force_bg, bucket);
}
void SstFileManagerImpl::WaitForEmptyTrash() {
delete_scheduler_.WaitForEmptyTrash();
}
std::optional<int32_t> SstFileManagerImpl::NewTrashBucket() {
return delete_scheduler_.NewTrashBucket();
}
void SstFileManagerImpl::WaitForEmptyTrashBucket(int32_t bucket) {
delete_scheduler_.WaitForEmptyTrashBucket(bucket);
}
void SstFileManagerImpl::OnAddFileImpl(const std::string& file_path,
uint64_t file_size) {
auto tracked_file = tracked_files_.find(file_path);
+27 -4
View File
@@ -5,7 +5,7 @@
#pragma once
#include <optional>
#include <string>
#include "db/compaction/compaction.h"
@@ -118,17 +118,40 @@ class SstFileManagerImpl : public SstFileManager {
// not guaranteed
bool CancelErrorRecovery(ErrorHandler* db);
// Mark file as trash and schedule it's deletion. If force_bg is set, it
// Mark a file as trash and schedule its deletion. If force_bg is set, it
// forces the file to be deleting in the background regardless of DB size,
// except when rate limited delete is disabled
// except when rate limited delete is disabled.
virtual Status ScheduleFileDeletion(const std::string& file_path,
const std::string& dir_to_sync,
const bool force_bg = false);
// Wait for all files being deleteing in the background to finish or for
// Delete an unaccounted file. The file is deleted immediately if slow
// deletion is disabled. A file with more than 1 hard links will be deleted
// immediately unless force_bg is set. In other cases, files will be scheduled
// for slow deletion, and assigned to the specified bucket if a legitimate one
// is provided. A legitimate bucket is one that is created with the
// `NewTrashBucket` API, and for which `WaitForEmptyTrashBucket` hasn't been
// called yet.
virtual Status ScheduleUnaccountedFileDeletion(
const std::string& file_path, const std::string& dir_to_sync,
const bool force_bg = false,
std::optional<int32_t> bucket = std::nullopt);
// Wait for all files being deleted in the background to finish or for
// destructor to be called.
virtual void WaitForEmptyTrash();
// Creates a new trash bucket. A legitimate bucket is only created and
// returned when slow deletion is enabled.
// For each bucket that is created and used, the user should also call
// `WaitForEmptyTrashBucket` after scheduling file deletions to make sure all
// the trash files are cleared.
std::optional<int32_t> NewTrashBucket();
// Wait for all the files in the specified bucket to be deleted in the
// background or for destructor to be called.
virtual void WaitForEmptyTrashBucket(int32_t bucket);
DeleteScheduler* delete_scheduler() { return &delete_scheduler_; }
// Stop the error recovery background thread. This should be called only
+27
View File
@@ -1064,6 +1064,21 @@ rocksdb_block_based_options_set_pin_top_level_index_and_filter(
rocksdb_block_based_table_options_t*, unsigned char);
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_block_based_table_factory(
rocksdb_options_t* opt, rocksdb_block_based_table_options_t* table_options);
enum {
rocksdb_block_based_k_fallback_pinning_tier = 0,
rocksdb_block_based_k_none_pinning_tier = 1,
rocksdb_block_based_k_flush_and_similar_pinning_tier = 2,
rocksdb_block_based_k_all_pinning_tier = 3,
};
extern ROCKSDB_LIBRARY_API void
rocksdb_block_based_options_set_top_level_index_pinning_tier(
rocksdb_block_based_table_options_t*, int);
extern ROCKSDB_LIBRARY_API void
rocksdb_block_based_options_set_partition_pinning_tier(
rocksdb_block_based_table_options_t*, int);
extern ROCKSDB_LIBRARY_API void
rocksdb_block_based_options_set_unpartitioned_pinning_tier(
rocksdb_block_based_table_options_t*, int);
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_write_buffer_manager(
rocksdb_options_t* opt, rocksdb_write_buffer_manager_t* wbm);
@@ -1682,6 +1697,18 @@ extern ROCKSDB_LIBRARY_API void rocksdb_options_set_wal_compression(
extern ROCKSDB_LIBRARY_API int rocksdb_options_get_wal_compression(
rocksdb_options_t* opt);
enum {
rocksdb_k_by_compensated_size_compaction_pri = 0,
rocksdb_k_oldest_largest_seq_first_compaction_pri = 1,
rocksdb_k_oldest_smallest_seq_first_compaction_pri = 2,
rocksdb_k_min_overlapping_ratio_compaction_pri = 3,
rocksdb_k_round_robin_compaction_pri = 4
};
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_compaction_pri(
rocksdb_options_t*, int);
extern ROCKSDB_LIBRARY_API int rocksdb_options_get_compaction_pri(
rocksdb_options_t*);
/* RateLimiter */
extern ROCKSDB_LIBRARY_API rocksdb_ratelimiter_t* rocksdb_ratelimiter_create(
int64_t rate_bytes_per_sec, int64_t refill_period_us, int32_t fairness);
+5
View File
@@ -142,6 +142,11 @@ class Comparator : public Customizable, public CompareInterface {
return Slice();
}
// Return a human readable user-defined timestamp for debugging.
virtual std::string TimestampToString(const Slice& /*timestamp*/) const {
return "";
}
int CompareWithoutTimestamp(const Slice& a, const Slice& b) const {
return CompareWithoutTimestamp(a, /*a_has_ts=*/true, b, /*b_has_ts=*/true);
}
+8 -2
View File
@@ -1475,6 +1475,9 @@ class DB {
// move the files back to the minimum level capable of holding the data set
// or a given level (specified by non-negative options.target_level).
//
// For FIFO compaction, this will trigger a compaction (if available)
// based on CompactionOptionsFIFO.
//
// In case of user-defined timestamp, if enabled, `begin` and `end` should
// not contain timestamp.
virtual Status CompactRange(const CompactRangeOptions& options,
@@ -1676,8 +1679,8 @@ class DB {
// Freezes the logical state of the DB (by stopping writes), and if WAL is
// enabled, ensures that state has been flushed to DB files (as in
// FlushWAL()). This can be used for taking a Checkpoint at a known DB
// state, though the user must use options to insure no DB flush is invoked
// in this frozen state. Other operations allowed on a "read only" DB should
// state, though while the WAL is locked, flushes as part of CreateCheckpoint
// and simiar are skipped. Other operations allowed on a "read only" DB should
// work while frozen. Each LockWAL() call that returns OK must eventually be
// followed by a corresponding call to UnlockWAL(). Where supported, non-OK
// status is generally only possible with some kind of corruption or I/O
@@ -1858,6 +1861,7 @@ class DB {
// supported. 4) When an ingested file contains point data and range deletion
// for the same key, the point data currently overrides the range deletion
// regardless which one has the higher user-defined timestamps.
// For FIFO compaction, SST files will always be ingested into L0.
//
// (1) External SST files can be created using SstFileWriter
// (2) We will try to ingest the files to the lowest possible level
@@ -2005,6 +2009,8 @@ class DB {
return Status::NotSupported("SuggestCompactRange() is not implemented.");
}
// Trivially move L0 files to target level. Should not be called with another
// PromoteL0() concurrently
virtual Status PromoteL0(ColumnFamilyHandle* /*column_family*/,
int /*target_level*/) {
return Status::NotSupported("PromoteL0() is not implemented.");
+6
View File
@@ -78,6 +78,12 @@ class Iterator : public IteratorBase {
// - Iterator created with ReadOptions::pin_data = true
// - DB tables were created with
// BlockBasedTableOptions::use_delta_encoding = false.
// Property "rocksdb.iterator.is-value-pinned":
// If returning "1", this means that the Slice returned by value() is valid
// as long as the iterator is not deleted.
// It is guaranteed to always return "1" if
// - Iterator created with ReadOptions::pin_data = true
// - The value is found in a `kTypeValue` record
// Property "rocksdb.iterator.super-version-number":
// LSM version used by the iterator. The same format as DB Property
// kCurrentSuperVersionNumber. See its comment for more information.
+1
View File
@@ -67,6 +67,7 @@ enum EntryType {
kEntryBlobIndex,
kEntryDeleteWithTimestamp,
kEntryWideColumnEntity,
kEntryTimedPut, // That hasn't yet converted to a standard Put entry
kEntryOther,
};
+8
View File
@@ -72,6 +72,7 @@ class LDBCommand {
static const std::string ARG_PREPOPULATE_BLOB_CACHE;
static const std::string ARG_DECODE_BLOB_INDEX;
static const std::string ARG_DUMP_UNCOMPRESSED_BLOBS;
static const std::string ARG_READ_TIMESTAMP;
struct ParsedParams {
std::string cmd;
@@ -190,6 +191,9 @@ class LDBCommand {
bool create_if_missing_;
/** Encoded user provided uint64_t read timestamp. */
std::string read_timestamp_;
/**
* Map of options passed on the command-line.
*/
@@ -275,6 +279,10 @@ class LDBCommand {
bool ParseBooleanOption(const std::map<std::string, std::string>& options,
const std::string& option, bool default_val);
/* Populate `ropts.timestamp` from command line flag --read_timestamp */
Status MaybePopulateReadTimestamp(ColumnFamilyHandle* cfh, ReadOptions& ropts,
Slice* read_timestamp);
Options options_;
std::vector<ColumnFamilyDescriptor> column_families_;
ConfigOptions config_options_;
+35 -17
View File
@@ -5,7 +5,6 @@
#pragma once
#include <limits>
#include <string>
#include <vector>
@@ -165,7 +164,7 @@ class Transaction {
virtual void SetSnapshot() = 0;
// Similar to SetSnapshot(), but will not change the current snapshot
// until Put/Merge/Delete/GetForUpdate/MultigetForUpdate is called.
// until Put/PutEntity/Merge/Delete/GetForUpdate/MultigetForUpdate is called.
// By calling this function, the transaction will essentially call
// SetSnapshot() for you right before performing the next write/GetForUpdate.
//
@@ -268,10 +267,10 @@ class Transaction {
// points.
virtual void SetSavePoint() = 0;
// Undo all operations in this transaction (Put, Merge, Delete, PutLogData)
// since the most recent call to SetSavePoint() and removes the most recent
// SetSavePoint().
// If there is no previous call to SetSavePoint(), returns Status::NotFound()
// Undo all operations in this transaction (Put, PutEntity, Merge, Delete,
// PutLogData) since the most recent call to SetSavePoint() and removes the
// most recent SetSavePoint(). If there is no previous call to SetSavePoint(),
// returns Status::NotFound()
virtual Status RollbackToSavePoint() = 0;
// Pop the most recent save point.
@@ -318,6 +317,10 @@ class Transaction {
return s;
}
virtual Status GetEntity(const ReadOptions& options,
ColumnFamilyHandle* column_family, const Slice& key,
PinnableWideColumns* columns) = 0;
virtual std::vector<Status> MultiGet(
const ReadOptions& options,
const std::vector<ColumnFamilyHandle*>& column_family,
@@ -354,6 +357,12 @@ class Transaction {
}
}
virtual void MultiGetEntity(const ReadOptions& options,
ColumnFamilyHandle* column_family,
size_t num_keys, const Slice* keys,
PinnableWideColumns* results, Status* statuses,
bool sorted_input = false) = 0;
// Read this key and ensure that this transaction will only
// be able to be committed if this key is not written outside this
// transaction after it has first been read (or after the snapshot if a
@@ -461,9 +470,9 @@ class Transaction {
virtual Iterator* GetIterator(const ReadOptions& read_options,
ColumnFamilyHandle* column_family) = 0;
// Put, Merge, Delete, and SingleDelete behave similarly to the corresponding
// functions in WriteBatch, but will also do conflict checking on the
// keys being written.
// Put, PutEntity, Merge, Delete, and SingleDelete behave similarly to the
// corresponding functions in WriteBatch, but will also do conflict checking
// on the keys being written.
//
// assume_tracked=true expects the key be already tracked. More
// specifically, it means the the key was previous tracked in the same
@@ -489,6 +498,10 @@ class Transaction {
const bool assume_tracked = false) = 0;
virtual Status Put(const SliceParts& key, const SliceParts& value) = 0;
virtual Status PutEntity(ColumnFamilyHandle* column_family, const Slice& key,
const WideColumns& columns,
bool assume_tracked = false) = 0;
virtual Status Merge(ColumnFamilyHandle* column_family, const Slice& key,
const Slice& value,
const bool assume_tracked = false) = 0;
@@ -528,6 +541,10 @@ class Transaction {
virtual Status PutUntracked(const SliceParts& key,
const SliceParts& value) = 0;
virtual Status PutEntityUntracked(ColumnFamilyHandle* column_family,
const Slice& key,
const WideColumns& columns) = 0;
virtual Status MergeUntracked(ColumnFamilyHandle* column_family,
const Slice& key, const Slice& value) = 0;
virtual Status MergeUntracked(const Slice& key, const Slice& value) = 0;
@@ -556,18 +573,18 @@ class Transaction {
// Similar to WriteBatch::PutLogData
virtual void PutLogData(const Slice& blob) = 0;
// By default, all Put/Merge/Delete operations will be indexed in the
// transaction so that Get/GetForUpdate/GetIterator can search for these
// By default, all Put/PutEntity/Merge/Delete operations will be indexed in
// the transaction so that Get/GetForUpdate/GetIterator can search for these
// keys.
//
// If the caller does not want to fetch the keys about to be written,
// they may want to avoid indexing as a performance optimization.
// Calling DisableIndexing() will turn off indexing for all future
// Put/Merge/Delete operations until EnableIndexing() is called.
// Put/PutEntity/Merge/Delete operations until EnableIndexing() is called.
//
// If a key is Put/Merge/Deleted after DisableIndexing is called and then
// is fetched via Get/GetForUpdate/GetIterator, the result of the fetch is
// undefined.
// If a key is written (using Put/PutEntity/Merge/Delete) after
// DisableIndexing is called and then is fetched via
// Get/GetForUpdate/GetIterator, the result of the fetch is undefined.
virtual void DisableIndexing() = 0;
virtual void EnableIndexing() = 0;
@@ -578,9 +595,10 @@ class Transaction {
// number of keys that need to be checked for conflicts at commit time.
virtual uint64_t GetNumKeys() const = 0;
// Returns the number of Puts/Deletes/Merges that have been applied to this
// transaction so far.
// Returns the number of Put/PutEntity/Delete/Merge operations that have been
// applied to this transaction so far.
virtual uint64_t GetNumPuts() const = 0;
virtual uint64_t GetNumPutEntities() const = 0;
virtual uint64_t GetNumDeletes() const = 0;
virtual uint64_t GetNumMerges() const = 0;
@@ -130,7 +130,7 @@ class WriteBatchWithIndex : public WriteBatchBase {
"Cannot call this method without attribute groups");
}
return Status::NotSupported(
"PutEntity not supported by WriteBatchWithIndex");
"PutEntity with AttributeGroups not supported by WriteBatchWithIndex");
}
using WriteBatchBase::Merge;
@@ -284,7 +284,12 @@ class WriteBatchWithIndex : public WriteBatchBase {
Status GetEntityFromBatchAndDB(DB* db, const ReadOptions& read_options,
ColumnFamilyHandle* column_family,
const Slice& key,
PinnableWideColumns* columns);
PinnableWideColumns* columns) {
constexpr ReadCallback* callback = nullptr;
return GetEntityFromBatchAndDB(db, read_options, column_family, key,
columns, callback);
}
void MultiGetFromBatchAndDB(DB* db, const ReadOptions& read_options,
ColumnFamilyHandle* column_family,
@@ -310,7 +315,13 @@ class WriteBatchWithIndex : public WriteBatchBase {
ColumnFamilyHandle* column_family,
size_t num_keys, const Slice* keys,
PinnableWideColumns* results,
Status* statuses, bool sorted_input);
Status* statuses, bool sorted_input) {
constexpr ReadCallback* callback = nullptr;
MultiGetEntityFromBatchAndDB(db, read_options, column_family, num_keys,
keys, results, statuses, sorted_input,
callback);
}
// Records the state of the batch for future calls to RollbackToSavePoint().
// May be called multiple times to set multiple save points.
+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 9
#define ROCKSDB_MINOR 2
#define ROCKSDB_MINOR 3
#define ROCKSDB_PATCH 2
// Do not use these. We made the mistake of declaring macros starting with
+22 -18
View File
@@ -338,8 +338,8 @@ void BlockBasedTableIterator::InitDataBlock() {
bool use_block_cache_for_lookup = true;
if (DoesContainBlockHandles()) {
data_block_handle = block_handles_.front().handle_;
is_in_cache = block_handles_.front().is_cache_hit_;
data_block_handle = block_handles_->front().handle_;
is_in_cache = block_handles_->front().is_cache_hit_;
use_block_cache_for_lookup = false;
} else {
data_block_handle = index_iter_->value().handle;
@@ -361,7 +361,7 @@ void BlockBasedTableIterator::InitDataBlock() {
Status s;
block_iter_.Invalidate(Status::OK());
table_->NewDataBlockIterator<DataBlockIter>(
read_options_, (block_handles_.front().cachable_entry_).As<Block>(),
read_options_, (block_handles_->front().cachable_entry_).As<Block>(),
&block_iter_, s);
} else {
auto* rep = table_->get_rep();
@@ -466,8 +466,8 @@ void BlockBasedTableIterator::AsyncInitDataBlock(bool is_first_pass) {
bool is_in_cache = false;
if (DoesContainBlockHandles()) {
data_block_handle = block_handles_.front().handle_;
is_in_cache = block_handles_.front().is_cache_hit_;
data_block_handle = block_handles_->front().handle_;
is_in_cache = block_handles_->front().is_cache_hit_;
} else {
data_block_handle = index_iter_->value().handle;
}
@@ -477,7 +477,7 @@ void BlockBasedTableIterator::AsyncInitDataBlock(bool is_first_pass) {
if (is_in_cache) {
block_iter_.Invalidate(Status::OK());
table_->NewDataBlockIterator<DataBlockIter>(
read_options_, (block_handles_.front().cachable_entry_).As<Block>(),
read_options_, (block_handles_->front().cachable_entry_).As<Block>(),
&block_iter_, s);
} else {
table_->NewDataBlockIterator<DataBlockIter>(
@@ -524,7 +524,7 @@ bool BlockBasedTableIterator::MaterializeCurrentBlock() {
// BlockCacheLookupForReadAheadSize is called.
Slice first_internal_key;
if (DoesContainBlockHandles()) {
first_internal_key = block_handles_.front().first_internal_key_;
first_internal_key = block_handles_->front().first_internal_key_;
} else {
first_internal_key = index_iter_->value().first_internal_key;
}
@@ -580,7 +580,7 @@ void BlockBasedTableIterator::FindBlockForward() {
if (DoesContainBlockHandles()) {
// Advance and point to that next Block handle to make that block handle
// current.
block_handles_.pop_front();
block_handles_->pop_front();
}
if (!DoesContainBlockHandles()) {
@@ -681,7 +681,8 @@ void BlockBasedTableIterator::InitializeStartAndEndOffsets(
bool read_curr_block, bool& found_first_miss_block,
uint64_t& start_updated_offset, uint64_t& end_updated_offset,
size_t& prev_handles_size) {
prev_handles_size = block_handles_.size();
assert(block_handles_ != nullptr);
prev_handles_size = block_handles_->size();
size_t footer = table_->get_rep()->footer.GetBlockTrailerSize();
// It initialize start and end offset to begin which is covered by following
@@ -701,7 +702,7 @@ void BlockBasedTableIterator::InitializeStartAndEndOffsets(
end_updated_offset = block_handle_info.handle_.offset() + footer +
block_handle_info.handle_.size();
block_handles_.emplace_back(std::move(block_handle_info));
block_handles_->emplace_back(std::move(block_handle_info));
index_iter_->Next();
is_index_at_curr_block_ = false;
@@ -717,17 +718,17 @@ void BlockBasedTableIterator::InitializeStartAndEndOffsets(
// Initialize prev_handles_size to 0 as all those handles need to be read
// again.
prev_handles_size = 0;
start_updated_offset = block_handles_.front().handle_.offset();
end_updated_offset = block_handles_.back().handle_.offset() + footer +
block_handles_.back().handle_.size();
start_updated_offset = block_handles_->front().handle_.offset();
end_updated_offset = block_handles_->back().handle_.offset() + footer +
block_handles_->back().handle_.size();
}
} else {
// Scenario 3 : read_curr_block is false (callback made to do additional
// prefetching in buffers) and the queue already has some
// handles from first buffer.
if (DoesContainBlockHandles()) {
start_updated_offset = block_handles_.back().handle_.offset() + footer +
block_handles_.back().handle_.size();
start_updated_offset = block_handles_->back().handle_.offset() + footer +
block_handles_->back().handle_.size();
end_updated_offset = start_updated_offset;
} else {
// Scenario 4 : read_curr_block is false (callback made to do additional
@@ -789,6 +790,9 @@ void BlockBasedTableIterator::BlockCacheLookupForReadAheadSize(
// Initialize start and end offsets based on exisiting handles in the queue
// and read_curr_block argument passed.
if (block_handles_ == nullptr) {
block_handles_.reset(new std::deque<BlockHandleInfo>());
}
InitializeStartAndEndOffsets(read_curr_block, found_first_miss_block,
start_updated_offset, end_updated_offset,
prev_handles_size);
@@ -833,7 +837,7 @@ void BlockBasedTableIterator::BlockCacheLookupForReadAheadSize(
}
// Add the handle to the queue.
block_handles_.emplace_back(std::move(block_handle_info));
block_handles_->emplace_back(std::move(block_handle_info));
// Can't figure out for current block if current block
// is out of bound. But for next block we can find that.
@@ -851,9 +855,9 @@ void BlockBasedTableIterator::BlockCacheLookupForReadAheadSize(
if (found_first_miss_block) {
// Iterate cache hit block handles from the end till a Miss is there, to
// truncate and update the end offset till that Miss.
auto it = block_handles_.rbegin();
auto it = block_handles_->rbegin();
auto it_end =
block_handles_.rbegin() + (block_handles_.size() - prev_handles_size);
block_handles_->rbegin() + (block_handles_->size() - prev_handles_size);
while (it != it_end && (*it).is_cache_hit_ &&
start_updated_offset != (*it).handle_.offset()) {
+10 -3
View File
@@ -338,7 +338,8 @@ class BlockBasedTableIterator : public InternalIteratorBase<Slice> {
// different blocks when readahead_size is calculated in
// BlockCacheLookupForReadAheadSize, to avoid index_iter_ reseek,
// block_handles_ is used.
std::deque<BlockHandleInfo> block_handles_;
// `block_handles_` is lazily constructed to save CPU when it is unused
std::unique_ptr<std::deque<BlockHandleInfo>> block_handles_;
// During cache lookup to find readahead size, index_iter_ is iterated and it
// can point to a different block. is_index_at_curr_block_ keeps track of
@@ -418,7 +419,11 @@ class BlockBasedTableIterator : public InternalIteratorBase<Slice> {
: false);
}
void ClearBlockHandles() { block_handles_.clear(); }
void ClearBlockHandles() {
if (block_handles_ != nullptr) {
block_handles_->clear();
}
}
// Reset prev_block_offset_. If index_iter_ has moved ahead, it won't get
// accurate prev_block_offset_.
@@ -426,7 +431,9 @@ class BlockBasedTableIterator : public InternalIteratorBase<Slice> {
prev_block_offset_ = std::numeric_limits<uint64_t>::max();
}
bool DoesContainBlockHandles() { return !block_handles_.empty(); }
bool DoesContainBlockHandles() {
return block_handles_ != nullptr && !block_handles_->empty();
}
void InitializeStartAndEndOffsets(bool read_curr_block,
bool& found_first_miss_block,
+4
View File
@@ -23,6 +23,10 @@ Status Iterator::GetProperty(std::string prop_name, std::string* prop) {
*prop = "0";
return Status::OK();
}
if (prop_name == "rocksdb.iterator.is-value-pinned") {
*prop = "0";
return Status::OK();
}
return Status::InvalidArgument("Unidentified property.");
}
+6 -6
View File
@@ -521,22 +521,22 @@ Status SstFileDumper::ReadSequential(bool print_kv, uint64_t read_num_limit,
iter->value(), oss, output_hex_);
if (!s.ok()) {
fprintf(stderr, "%s => error deserializing wide columns\n",
ikey.DebugString(true, output_hex_).c_str());
ikey.DebugString(true, output_hex_, ucmp).c_str());
continue;
}
fprintf(stdout, "%s => %s\n",
ikey.DebugString(true, output_hex_).c_str(),
ikey.DebugString(true, output_hex_, ucmp).c_str(),
oss.str().c_str());
} else if (ikey.type == kTypeValuePreferredSeqno) {
auto [unpacked_value, preferred_seqno] =
ParsePackedValueWithSeqno(value);
fprintf(stdout, "%s => %s, %llu\n",
ikey.DebugString(true, output_hex_).c_str(),
ikey.DebugString(true, output_hex_, ucmp).c_str(),
unpacked_value.ToString(output_hex_).c_str(),
static_cast<unsigned long long>(preferred_seqno));
} else {
fprintf(stdout, "%s => %s\n",
ikey.DebugString(true, output_hex_).c_str(),
ikey.DebugString(true, output_hex_, ucmp).c_str(),
value.ToString(output_hex_).c_str());
}
} else {
@@ -545,12 +545,12 @@ Status SstFileDumper::ReadSequential(bool print_kv, uint64_t read_num_limit,
const Status s = blob_index.DecodeFrom(value);
if (!s.ok()) {
fprintf(stderr, "%s => error decoding blob index\n",
ikey.DebugString(true, output_hex_).c_str());
ikey.DebugString(true, output_hex_, ucmp).c_str());
continue;
}
fprintf(stdout, "%s => %s\n",
ikey.DebugString(true, output_hex_).c_str(),
ikey.DebugString(true, output_hex_, ucmp).c_str(),
blob_index.DebugString(output_hex_).c_str());
}
}
+24
View File
@@ -565,6 +565,30 @@ void DeleteDir(Env* env, const std::string& dirname) {
TryDeleteDir(env, dirname).PermitUncheckedError();
}
FileType GetFileType(const std::string& path) {
FileType type = kTempFile;
std::size_t found = path.find_last_of('/');
if (found == std::string::npos) {
found = 0;
}
std::string file_name = path.substr(found);
uint64_t number = 0;
ParseFileName(file_name, &number, &type);
return type;
}
uint64_t GetFileNumber(const std::string& path) {
FileType type = kTempFile;
std::size_t found = path.find_last_of('/');
if (found == std::string::npos) {
found = 0;
}
std::string file_name = path.substr(found);
uint64_t number = 0;
ParseFileName(file_name, &number, &type);
return number;
}
Status CreateEnvFromSystem(const ConfigOptions& config_options, Env** result,
std::shared_ptr<Env>* guard) {
const char* env_uri = getenv("TEST_ENV_URI");
+6
View File
@@ -882,6 +882,12 @@ Status TryDeleteDir(Env* env, const std::string& dirname);
// Delete a directory if it exists
void DeleteDir(Env* env, const std::string& dirname);
// Find the FileType from the file path
FileType GetFileType(const std::string& path);
// Get the file number given the file path
uint64_t GetFileNumber(const std::string& path);
// Creates an Env from the system environment by looking at the system
// environment variables.
Status CreateEnvFromSystem(const ConfigOptions& options, Env** result,
+1 -1
View File
@@ -125,7 +125,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")
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")
# 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
+11 -5
View File
@@ -67,7 +67,11 @@ default_params = {
"clear_column_family_one_in": 0,
"compact_files_one_in": lambda: random.choice([1000, 1000000]),
"compact_range_one_in": lambda: random.choice([1000, 1000000]),
# Disabled because of various likely related failures with
# "Cannot delete table file #N from level 0 since it is on level X"
"promote_l0_one_in": 0,
"compaction_pri": random.randint(0, 4),
"key_may_exist_one_in": lambda: random.choice([100, 100000]),
"data_block_index_type": lambda: random.choice([0, 1]),
"delpercent": 4,
"delrangepercent": 1,
@@ -116,6 +120,7 @@ default_params = {
"optimize_filters_for_memory": lambda: random.randint(0, 1),
"partition_filters": lambda: random.randint(0, 1),
"partition_pinning": lambda: random.randint(0, 3),
"reset_stats_one_in": lambda: random.choice([10000, 1000000]),
"pause_background_one_in": lambda: random.choice([10000, 1000000]),
"disable_file_deletions_one_in": lambda: random.choice([10000, 1000000]),
"disable_manual_compaction_one_in": lambda: random.choice([10000, 1000000]),
@@ -147,6 +152,7 @@ default_params = {
"use_merge": lambda: random.randint(0, 1),
# use_put_entity_one_in has to be the same across invocations for verification to work, hence no lambda
"use_put_entity_one_in": random.choice([0] * 7 + [1, 5, 10]),
"use_attribute_group": lambda: random.randint(0, 1),
# 999 -> use Bloom API
"bloom_before_level": lambda: random.choice([random.randint(-1, 2), random.randint(-1, 10), 0x7fffffff - 1, 0x7fffffff]),
"value_size_mult": 32,
@@ -834,9 +840,10 @@ def finalize_and_sanitize(src_params):
# verification.
dest_params["acquire_snapshot_one_in"] = 0
dest_params["compact_range_one_in"] = 0
# Give the iterator ops away to reads.
dest_params["readpercent"] += dest_params.get("iterpercent", 10)
# Redistribute to maintain 100% total
dest_params["readpercent"] += dest_params.get("iterpercent", 10) + dest_params.get("prefixpercent", 20)
dest_params["iterpercent"] = 0
dest_params["prefixpercent"] = 0
dest_params["check_multiget_consistency"] = 0
dest_params["check_multiget_entity_consistency"] = 0
if dest_params.get("disable_wal") == 1:
@@ -859,7 +866,6 @@ def finalize_and_sanitize(src_params):
dest_params["use_timed_put_one_in"] = 3
return dest_params
def gen_cmd_params(args):
params = {}
@@ -1109,13 +1115,13 @@ def whitebox_crash_main(args, unknown_args):
}
# Single level universal has a lot of special logic. Ensure we cover
# it sometimes.
if random.randint(0, 1) == 1:
if not args.test_tiered_storage and random.randint(0, 1) == 1:
additional_opts.update(
{
"num_levels": 1,
}
)
elif check_mode == 2:
elif check_mode == 2 and not args.test_tiered_storage:
# normal run with FIFO compaction mode
# ops_per_thread is divided by 5 because FIFO compaction
# style is quite a bit slower on reads with lot of files
+121 -22
View File
@@ -107,6 +107,7 @@ const std::string LDBCommand::ARG_PREPOPULATE_BLOB_CACHE =
const std::string LDBCommand::ARG_DECODE_BLOB_INDEX = "decode_blob_index";
const std::string LDBCommand::ARG_DUMP_UNCOMPRESSED_BLOBS =
"dump_uncompressed_blobs";
const std::string LDBCommand::ARG_READ_TIMESTAMP = "read_timestamp";
const char* LDBCommand::DELIM = " ==> ";
@@ -722,6 +723,46 @@ bool LDBCommand::ParseCompressionTypeOption(
return false;
}
Status LDBCommand::MaybePopulateReadTimestamp(ColumnFamilyHandle* cfh,
ReadOptions& ropts,
Slice* read_timestamp) {
const size_t ts_sz = cfh->GetComparator()->timestamp_size();
auto iter = option_map_.find(ARG_READ_TIMESTAMP);
if (iter == option_map_.end()) {
if (ts_sz == 0) {
return Status::OK();
}
return Status::InvalidArgument(
"column family enables user-defined timestamp while --read_timestamp "
"is not provided.");
}
if (iter->second.empty()) {
if (ts_sz == 0) {
return Status::OK();
}
return Status::InvalidArgument(
"column family enables user-defined timestamp while --read_timestamp "
"is empty.");
}
if (ts_sz == 0) {
return Status::InvalidArgument(
"column family does not enable user-defined timestamps while "
"--read_timestamp is provided.");
}
uint64_t int_timestamp;
std::istringstream iss(iter->second);
if (!(iss >> int_timestamp)) {
return Status::InvalidArgument(
"column family enables user-defined timestamp while --read_timestamp "
"is not a valid uint64 value.");
}
EncodeU64Ts(int_timestamp, &read_timestamp_);
*read_timestamp = read_timestamp_;
ropts.timestamp = read_timestamp;
return Status::OK();
}
void LDBCommand::OverrideBaseOptions() {
options_.create_if_missing = false;
@@ -769,7 +810,10 @@ void LDBCommand::OverrideBaseCFOptions(ColumnFamilyOptions* cf_opts) {
}
}
if (options_.comparator != nullptr) {
// Default comparator is BytewiseComparator, so only when it's not, it
// means user has a command line override.
if (options_.comparator != nullptr &&
options_.comparator != BytewiseComparator()) {
cf_opts->comparator = options_.comparator;
}
@@ -2846,9 +2890,9 @@ 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,
BuildCmdLineOptions({ARG_TTL, ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX})) {
: LDBCommand(options, flags, true,
BuildCmdLineOptions({ARG_TTL, ARG_HEX, ARG_KEY_HEX,
ARG_VALUE_HEX, ARG_READ_TIMESTAMP})) {
if (params.size() != 1) {
exec_state_ = LDBCommandExecuteResult::Failed(
"<key> must be specified for the get command");
@@ -2865,6 +2909,7 @@ void GetCommand::Help(std::string& ret) {
ret.append(" ");
ret.append(GetCommand::Name());
ret.append(" <key>");
ret.append(" [--" + ARG_READ_TIMESTAMP + "=<uint64_ts>] ");
ret.append(" [--" + ARG_TTL + "]");
ret.append("\n");
}
@@ -2874,8 +2919,18 @@ void GetCommand::DoCommand() {
assert(GetExecuteState().IsFailed());
return;
}
ReadOptions ropts;
Slice read_timestamp;
ColumnFamilyHandle* cfh = GetCfHandle();
Status st = MaybePopulateReadTimestamp(cfh, ropts, &read_timestamp);
if (!st.ok()) {
std::stringstream oss;
oss << "Get failed: " << st.ToString();
exec_state_ = LDBCommandExecuteResult::Failed(oss.str());
return;
}
std::string value;
Status st = db_->Get(ReadOptions(), GetCfHandle(), key_, &value);
st = db_->Get(ropts, cfh, key_, &value);
if (st.ok()) {
fprintf(stdout, "%s\n",
(is_value_hex_ ? StringToHex(value) : value).c_str());
@@ -2895,7 +2950,8 @@ MultiGetCommand::MultiGetCommand(
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, true,
BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX})) {
BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX,
ARG_READ_TIMESTAMP})) {
if (params.size() < 1) {
exec_state_ = LDBCommandExecuteResult::Failed(
"At least one <key> must be specified for multi_get.");
@@ -2911,6 +2967,7 @@ void MultiGetCommand::Help(std::string& ret) {
ret.append(" ");
ret.append(MultiGetCommand::Name());
ret.append(" <key_1> <key_2> <key_3> ...");
ret.append(" [--" + ARG_READ_TIMESTAMP + "=<uint64_ts>] ");
ret.append("\n");
}
@@ -2919,6 +2976,16 @@ void MultiGetCommand::DoCommand() {
assert(GetExecuteState().IsFailed());
return;
}
ReadOptions ropts;
Slice read_timestamp;
ColumnFamilyHandle* cfh = GetCfHandle();
Status st = MaybePopulateReadTimestamp(cfh, ropts, &read_timestamp);
if (!st.ok()) {
std::stringstream oss;
oss << "MultiGet failed: " << st.ToString();
exec_state_ = LDBCommandExecuteResult::Failed(oss.str());
return;
}
size_t num_keys = keys_.size();
std::vector<Slice> key_slices;
std::vector<PinnableSlice> values(num_keys);
@@ -2926,8 +2993,8 @@ void MultiGetCommand::DoCommand() {
for (const std::string& key : keys_) {
key_slices.emplace_back(key);
}
db_->MultiGet(ReadOptions(), GetCfHandle(), num_keys, key_slices.data(),
values.data(), statuses.data());
db_->MultiGet(ropts, cfh, num_keys, key_slices.data(), values.data(),
statuses.data());
bool failed = false;
for (size_t i = 0; i < num_keys; ++i) {
@@ -2957,9 +3024,9 @@ 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,
BuildCmdLineOptions({ARG_TTL, ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX})) {
: LDBCommand(options, flags, true,
BuildCmdLineOptions({ARG_TTL, ARG_HEX, ARG_KEY_HEX,
ARG_VALUE_HEX, ARG_READ_TIMESTAMP})) {
if (params.size() != 1) {
exec_state_ = LDBCommandExecuteResult::Failed(
"<key> must be specified for the get_entity command");
@@ -2976,6 +3043,7 @@ void GetEntityCommand::Help(std::string& ret) {
ret.append(" ");
ret.append(GetEntityCommand::Name());
ret.append(" <key>");
ret.append(" [--" + ARG_READ_TIMESTAMP + "=<uint64_ts>] ");
ret.append(" [--" + ARG_TTL + "]");
ret.append("\n");
}
@@ -2985,9 +3053,18 @@ void GetEntityCommand::DoCommand() {
assert(GetExecuteState().IsFailed());
return;
}
ReadOptions ropt;
Slice read_timestamp;
ColumnFamilyHandle* cfh = GetCfHandle();
Status st = MaybePopulateReadTimestamp(cfh, ropt, &read_timestamp);
if (!st.ok()) {
std::stringstream oss;
oss << "GetEntity failed: " << st.ToString();
exec_state_ = LDBCommandExecuteResult::Failed(oss.str());
return;
}
PinnableWideColumns pinnable_wide_columns;
Status st = db_->GetEntity(ReadOptions(), GetCfHandle(), key_,
&pinnable_wide_columns);
st = db_->GetEntity(ropt, cfh, key_, &pinnable_wide_columns);
if (st.ok()) {
std::ostringstream oss;
WideColumnsHelper::DumpWideColumns(pinnable_wide_columns.columns(), oss,
@@ -3007,7 +3084,8 @@ MultiGetEntityCommand::MultiGetEntityCommand(
const std::map<std::string, std::string>& options,
const std::vector<std::string>& flags)
: LDBCommand(options, flags, true /* is_read_only */,
BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX})) {
BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX,
ARG_READ_TIMESTAMP})) {
if (params.size() < 1) {
exec_state_ = LDBCommandExecuteResult::Failed(
"At least one <key> must be specified for the multi_get_entity "
@@ -3024,6 +3102,7 @@ void MultiGetEntityCommand::Help(std::string& ret) {
ret.append(" ");
ret.append(MultiGetEntityCommand::Name());
ret.append(" <key_1> <key_2> <key_3> ...");
ret.append(" [--" + ARG_READ_TIMESTAMP + "=<uint64_ts>] ");
ret.append("\n");
}
@@ -3033,6 +3112,16 @@ void MultiGetEntityCommand::DoCommand() {
return;
}
ReadOptions ropt;
Slice read_timestamp;
ColumnFamilyHandle* cfh = GetCfHandle();
Status st = MaybePopulateReadTimestamp(cfh, ropt, &read_timestamp);
if (!st.ok()) {
std::stringstream oss;
oss << "MultiGetEntity failed: " << st.ToString();
exec_state_ = LDBCommandExecuteResult::Failed(oss.str());
return;
}
size_t num_keys = keys_.size();
std::vector<Slice> key_slices;
std::vector<PinnableWideColumns> results(num_keys);
@@ -3041,8 +3130,8 @@ void MultiGetEntityCommand::DoCommand() {
key_slices.emplace_back(key);
}
db_->MultiGetEntity(ReadOptions(), GetCfHandle(), num_keys, key_slices.data(),
results.data(), statuses.data());
db_->MultiGetEntity(ropt, cfh, num_keys, key_slices.data(), results.data(),
statuses.data());
bool failed = false;
for (size_t i = 0; i < num_keys; ++i) {
@@ -3200,11 +3289,11 @@ void BatchPutCommand::OverrideBaseOptions() {
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,
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})),
: LDBCommand(options, flags, true,
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, ARG_READ_TIMESTAMP})),
start_key_specified_(false),
end_key_specified_(false),
max_keys_scanned_(-1),
@@ -3260,6 +3349,7 @@ void ScanCommand::Help(std::string& ret) {
ret.append(" [--" + ARG_TTL_START + "=<N>:- is inclusive]");
ret.append(" [--" + ARG_TTL_END + "=<N>:- is exclusive]");
ret.append(" [--" + ARG_NO_VALUE + "]");
ret.append(" [--" + ARG_READ_TIMESTAMP + "=<uint64_ts>] ");
ret.append("\n");
}
@@ -3271,8 +3361,17 @@ void ScanCommand::DoCommand() {
int num_keys_scanned = 0;
ReadOptions scan_read_opts;
ColumnFamilyHandle* cfh = GetCfHandle();
Slice read_timestamp;
Status st = MaybePopulateReadTimestamp(cfh, scan_read_opts, &read_timestamp);
if (!st.ok()) {
std::stringstream oss;
oss << "Scan failed: " << st.ToString();
exec_state_ = LDBCommandExecuteResult::Failed(oss.str());
return;
}
scan_read_opts.total_order_seek = true;
Iterator* it = db_->NewIterator(scan_read_opts, GetCfHandle());
Iterator* it = db_->NewIterator(scan_read_opts, cfh);
if (start_key_specified_) {
it->Seek(start_key_);
} else {
+3
View File
@@ -85,6 +85,9 @@ void LDBCommandRunner::PrintHelp(const LDBOptions& ldb_options,
"=<double,e.g.:0.25>\n");
ret.append(" --" + LDBCommand::ARG_BLOB_COMPACTION_READAHEAD_SIZE +
"=<int,e.g.:2097152>\n");
ret.append(" --" + LDBCommand::ARG_READ_TIMESTAMP +
"=<uint64_ts, e.g.:323> : read timestamp, required if column "
"family enables timestamp, otherwise invalid if provided.");
ret.append("\n\n");
ret.append("Data Access Commands:\n");
+7
View File
@@ -276,6 +276,13 @@ class ComparatorWithU64TsImpl : public Comparator {
Slice GetMinTimestamp() const override { return MinU64Ts(); }
std::string TimestampToString(const Slice& timestamp) const override {
assert(timestamp.size() == sizeof(uint64_t));
uint64_t ts = 0;
DecodeU64Ts(timestamp, &ts).PermitUncheckedError();
return std::to_string(ts);
}
using Comparator::CompareWithoutTimestamp;
int CompareWithoutTimestamp(const Slice& a, bool a_has_ts, const Slice& b,
bool b_has_ts) const override {
+3 -4
View File
@@ -38,12 +38,12 @@ void StderrLogger::Logv(const char* format, va_list ap) {
va_list ap_copy;
va_copy(ap_copy, ap);
const size_t log_suffix_len = vsnprintf(nullptr, 0, format, ap_copy);
const size_t log_suffix_len = vsnprintf(nullptr, 0, format, ap_copy) + 1;
va_end(ap_copy);
// Allocate space for the context, log_prefix, and log itself
// Extra byte for null termination
size_t buf_len = ctx_len + log_prefix_len + log_suffix_len + 1;
size_t buf_len = ctx_len + log_prefix_len + log_suffix_len;
std::unique_ptr<char[]> buf(new char[buf_len]);
// If the logger was created without a prefix, the prefix is a nullptr
@@ -55,8 +55,7 @@ void StderrLogger::Logv(const char* format, va_list ap) {
t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min,
t.tm_sec, static_cast<int>(now_tv.tv_usec),
static_cast<long long unsigned int>(thread_id), prefix);
written += vsnprintf(buf.get() + written, log_suffix_len, format, ap);
buf[written] = '\0';
vsnprintf(buf.get() + written, log_suffix_len, format, ap);
fprintf(stderr, "%s%c", buf.get(), '\n');
}
+2 -2
View File
@@ -760,7 +760,7 @@ TEST_F(BlobDBTest, SstFileManager) {
// run the same test for Get(), MultiGet() and Iterator each.
std::shared_ptr<SstFileManager> sst_file_manager(
NewSstFileManager(mock_env_.get()));
sst_file_manager->SetDeleteRateBytesPerSecond(1);
sst_file_manager->SetDeleteRateBytesPerSecond(1024 * 1024);
SstFileManagerImpl *sfm =
static_cast<SstFileManagerImpl *>(sst_file_manager.get());
@@ -818,7 +818,7 @@ TEST_F(BlobDBTest, SstFileManagerRestart) {
// run the same test for Get(), MultiGet() and Iterator each.
std::shared_ptr<SstFileManager> sst_file_manager(
NewSstFileManager(mock_env_.get()));
sst_file_manager->SetDeleteRateBytesPerSecond(1);
sst_file_manager->SetDeleteRateBytesPerSecond(1024 * 1024);
SstFileManagerImpl *sfm =
static_cast<SstFileManagerImpl *>(sst_file_manager.get());
+103
View File
@@ -23,6 +23,8 @@
#include "port/stack_trace.h"
#include "rocksdb/db.h"
#include "rocksdb/env.h"
#include "rocksdb/rocksdb_namespace.h"
#include "rocksdb/sst_file_manager.h"
#include "rocksdb/utilities/transaction_db.h"
#include "test_util/sync_point.h"
#include "test_util/testharness.h"
@@ -253,6 +255,13 @@ class CheckpointTest : public testing::Test {
}
return result;
}
int NumTableFilesAtLevel(int level) {
std::string property;
EXPECT_TRUE(db_->GetProperty(
"rocksdb.num-files-at-level" + std::to_string(level), &property));
return atoi(property.c_str());
}
};
TEST_F(CheckpointTest, GetSnapshotLink) {
@@ -975,6 +984,100 @@ TEST_F(CheckpointTest, PutRaceWithCheckpointTrackedWalSync) {
Close();
}
class CheckpointDestroyTest : public CheckpointTest,
public testing::WithParamInterface<bool> {};
TEST_P(CheckpointDestroyTest, DisableEnableSlowDeletion) {
bool slow_deletion = GetParam();
Options options = CurrentOptions();
options.num_levels = 2;
options.disable_auto_compactions = true;
Status s;
options.sst_file_manager.reset(NewSstFileManager(
options.env, options.info_log, "", slow_deletion ? 1024 * 1024 : 0,
false /* delete_existing_trash */, &s, 1));
ASSERT_OK(s);
DestroyAndReopen(options);
ASSERT_OK(Put("foo", "a"));
ASSERT_OK(Flush());
ASSERT_OK(Put("bar", "b"));
ASSERT_OK(Flush());
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
for (int i = 0; i < 10; i++) {
ASSERT_OK(Put("bar", "val" + std::to_string(i)));
ASSERT_OK(Flush());
}
ASSERT_EQ(NumTableFilesAtLevel(0), 10);
ASSERT_EQ(NumTableFilesAtLevel(1), 2);
Checkpoint* checkpoint;
ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
ASSERT_OK(checkpoint->CreateCheckpoint(snapshot_name_));
delete checkpoint;
checkpoint = nullptr;
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
ASSERT_EQ(NumTableFilesAtLevel(1), 2);
DB* snapshot_db;
ASSERT_OK(DB::Open(options, snapshot_name_, &snapshot_db));
ReadOptions read_opts;
std::string get_result;
ASSERT_OK(snapshot_db->Get(read_opts, "foo", &get_result));
ASSERT_EQ("a", get_result);
ASSERT_OK(snapshot_db->Get(read_opts, "bar", &get_result));
ASSERT_EQ("val9", get_result);
delete snapshot_db;
// Make sure original obsolete files for hard linked files are all deleted.
DBImpl* db_impl = static_cast_with_check<DBImpl>(db_);
db_impl->TEST_DeleteObsoleteFiles();
auto sfm = static_cast_with_check<SstFileManagerImpl>(
options.sst_file_manager.get());
ASSERT_NE(nullptr, sfm);
sfm->WaitForEmptyTrash();
// SST file 2-12 for "bar" will be compacted into one file on L1 during the
// compaction after checkpoint is created. SST file 1 on L1: foo, seq:
// 1 (hard links is 1 after checkpoint destroy)
std::atomic<int> bg_delete_sst{0};
std::atomic<int> fg_delete_sst{0};
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"DeleteScheduler::DeleteFile::cb", [&](void* arg) {
ASSERT_NE(nullptr, arg);
auto file_name = *static_cast<std::string*>(arg);
if (file_name.size() >= 4 &&
file_name.compare(file_name.size() - 4, 4, ".sst") == 0) {
fg_delete_sst.fetch_add(1);
}
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"DeleteScheduler::DeleteTrashFile::cb", [&](void* arg) {
ASSERT_NE(nullptr, arg);
auto file_name = *static_cast<std::string*>(arg);
if (file_name.size() >= 10 &&
file_name.compare(file_name.size() - 10, 10, ".sst.trash") == 0) {
bg_delete_sst.fetch_add(1);
}
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
ASSERT_OK(DestroyDB(snapshot_name_, options));
if (slow_deletion) {
ASSERT_EQ(fg_delete_sst, 1);
ASSERT_EQ(bg_delete_sst, 11);
} else {
ASSERT_EQ(fg_delete_sst, 12);
}
ASSERT_EQ("a", Get("foo"));
ASSERT_EQ("val9", Get("bar"));
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
}
INSTANTIATE_TEST_CASE_P(CheckpointDestroyTest, CheckpointDestroyTest,
::testing::Values(true, false));
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
@@ -1142,8 +1142,12 @@ TEST_P(OptimisticTransactionTest, UntrackedWrites) {
delete txn;
txn = txn_db->BeginTransaction(write_options);
const WideColumns untracked_columns{{"hello", "world"}};
ASSERT_OK(txn->Put("tracked", "1"));
ASSERT_OK(txn->PutUntracked("untracked", "1"));
ASSERT_OK(txn->PutEntityUntracked(txn_db->DefaultColumnFamily(), "untracked",
untracked_columns));
ASSERT_OK(txn->MergeUntracked("untracked", "2"));
ASSERT_OK(txn->DeleteUntracked("untracked"));
@@ -1159,8 +1163,12 @@ TEST_P(OptimisticTransactionTest, UntrackedWrites) {
delete txn;
txn = txn_db->BeginTransaction(write_options);
const WideColumns untracked_new_columns{{"foo", "bar"}};
ASSERT_OK(txn->Put("tracked", "10"));
ASSERT_OK(txn->PutUntracked("untracked", "A"));
ASSERT_OK(txn->PutEntityUntracked(txn_db->DefaultColumnFamily(), "untracked",
untracked_new_columns));
// Write to tracked key outside of the transaction and verify that the
// untracked keys are not written when the commit fails.
@@ -1687,6 +1695,434 @@ TEST_P(OptimisticTransactionTest, TimestampedSnapshotSetCommitTs) {
ASSERT_TRUE(s.IsNotSupported());
}
TEST_P(OptimisticTransactionTest, PutEntitySuccess) {
constexpr char foo[] = "foo";
const WideColumns foo_columns{
{kDefaultWideColumnName, "bar"}, {"col1", "val1"}, {"col2", "val2"}};
const WideColumns foo_new_columns{
{kDefaultWideColumnName, "baz"}, {"colA", "valA"}, {"colB", "valB"}};
ASSERT_OK(txn_db->PutEntity(WriteOptions(), txn_db->DefaultColumnFamily(),
foo, foo_columns));
{
std::unique_ptr<Transaction> txn(txn_db->BeginTransaction(WriteOptions()));
ASSERT_NE(txn, nullptr);
ASSERT_EQ(txn->GetNumPutEntities(), 0);
{
PinnableWideColumns columns;
ASSERT_OK(txn->GetEntity(ReadOptions(), txn_db->DefaultColumnFamily(),
foo, &columns));
ASSERT_EQ(columns.columns(), foo_columns);
}
{
PinnableSlice value;
ASSERT_OK(txn->GetForUpdate(ReadOptions(), foo, &value));
ASSERT_EQ(value, foo_columns[0].value());
}
ASSERT_OK(
txn->PutEntity(txn_db->DefaultColumnFamily(), foo, foo_new_columns));
ASSERT_EQ(txn->GetNumPutEntities(), 1);
{
PinnableWideColumns columns;
ASSERT_OK(txn->GetEntity(ReadOptions(), txn_db->DefaultColumnFamily(),
foo, &columns));
ASSERT_EQ(columns.columns(), foo_new_columns);
}
{
PinnableSlice value;
ASSERT_OK(txn->GetForUpdate(ReadOptions(), foo, &value));
ASSERT_EQ(value, foo_new_columns[0].value());
}
ASSERT_OK(txn->Commit());
}
{
PinnableWideColumns columns;
ASSERT_OK(txn_db->GetEntity(ReadOptions(), txn_db->DefaultColumnFamily(),
foo, &columns));
ASSERT_EQ(columns.columns(), foo_new_columns);
}
}
TEST_P(OptimisticTransactionTest, PutEntityWriteConflict) {
constexpr char foo[] = "foo";
const WideColumns foo_columns{
{kDefaultWideColumnName, "bar"}, {"col1", "val1"}, {"col2", "val2"}};
constexpr char baz[] = "baz";
const WideColumns baz_columns{
{kDefaultWideColumnName, "quux"}, {"colA", "valA"}, {"colB", "valB"}};
ASSERT_OK(txn_db->PutEntity(WriteOptions(), txn_db->DefaultColumnFamily(),
foo, foo_columns));
ASSERT_OK(txn_db->PutEntity(WriteOptions(), txn_db->DefaultColumnFamily(),
baz, baz_columns));
std::unique_ptr<Transaction> txn(txn_db->BeginTransaction(WriteOptions()));
ASSERT_NE(txn, nullptr);
{
PinnableWideColumns columns;
ASSERT_OK(txn->GetEntity(ReadOptions(), txn_db->DefaultColumnFamily(), foo,
&columns));
ASSERT_EQ(columns.columns(), foo_columns);
}
{
PinnableWideColumns columns;
ASSERT_OK(txn->GetEntity(ReadOptions(), txn_db->DefaultColumnFamily(), baz,
&columns));
ASSERT_EQ(columns.columns(), baz_columns);
}
{
constexpr size_t num_keys = 2;
std::array<Slice, num_keys> keys{{foo, baz}};
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
txn->MultiGetEntity(ReadOptions(), txn_db->DefaultColumnFamily(), num_keys,
keys.data(), results.data(), statuses.data());
ASSERT_OK(statuses[0]);
ASSERT_OK(statuses[1]);
ASSERT_EQ(results[0].columns(), foo_columns);
ASSERT_EQ(results[1].columns(), baz_columns);
}
const WideColumns foo_new_columns{{kDefaultWideColumnName, "FOO"},
{"hello", "world"}};
const WideColumns baz_new_columns{{kDefaultWideColumnName, "BAZ"},
{"ping", "pong"}};
ASSERT_OK(
txn->PutEntity(txn_db->DefaultColumnFamily(), foo, foo_new_columns));
ASSERT_OK(
txn->PutEntity(txn_db->DefaultColumnFamily(), baz, baz_new_columns));
{
PinnableWideColumns columns;
ASSERT_OK(txn->GetEntity(ReadOptions(), txn_db->DefaultColumnFamily(), foo,
&columns));
ASSERT_EQ(columns.columns(), foo_new_columns);
}
{
PinnableWideColumns columns;
ASSERT_OK(txn->GetEntity(ReadOptions(), txn_db->DefaultColumnFamily(), baz,
&columns));
ASSERT_EQ(columns.columns(), baz_new_columns);
}
{
constexpr size_t num_keys = 2;
std::array<Slice, num_keys> keys{{foo, baz}};
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
txn->MultiGetEntity(ReadOptions(), txn_db->DefaultColumnFamily(), num_keys,
keys.data(), results.data(), statuses.data());
ASSERT_OK(statuses[0]);
ASSERT_OK(statuses[1]);
ASSERT_EQ(results[0].columns(), foo_new_columns);
ASSERT_EQ(results[1].columns(), baz_new_columns);
}
// This PutEntity outside of a transaction will conflict with the previous
// write
const WideColumns foo_conflict_columns{{kDefaultWideColumnName, "X"},
{"conflicting", "write"}};
ASSERT_OK(txn_db->PutEntity(WriteOptions(), txn_db->DefaultColumnFamily(),
foo, foo_conflict_columns));
{
PinnableWideColumns columns;
ASSERT_OK(txn_db->GetEntity(ReadOptions(), txn_db->DefaultColumnFamily(),
foo, &columns));
ASSERT_EQ(columns.columns(), foo_conflict_columns);
}
ASSERT_TRUE(txn->Commit().IsBusy()); // Txn should not commit
// Verify that transaction did not write anything
{
PinnableWideColumns columns;
ASSERT_OK(txn_db->GetEntity(ReadOptions(), txn_db->DefaultColumnFamily(),
foo, &columns));
ASSERT_EQ(columns.columns(), foo_conflict_columns);
}
{
PinnableWideColumns columns;
ASSERT_OK(txn_db->GetEntity(ReadOptions(), txn_db->DefaultColumnFamily(),
baz, &columns));
ASSERT_EQ(columns.columns(), baz_columns);
}
{
constexpr size_t num_keys = 2;
std::array<Slice, num_keys> keys{{foo, baz}};
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
constexpr bool sorted_input = false;
txn_db->MultiGetEntity(ReadOptions(), txn_db->DefaultColumnFamily(),
num_keys, keys.data(), results.data(),
statuses.data(), sorted_input);
ASSERT_OK(statuses[0]);
ASSERT_OK(statuses[1]);
ASSERT_EQ(results[0].columns(), foo_conflict_columns);
ASSERT_EQ(results[1].columns(), baz_columns);
}
}
TEST_P(OptimisticTransactionTest, PutEntityWriteConflictTxnTxn) {
constexpr char foo[] = "foo";
const WideColumns foo_columns{
{kDefaultWideColumnName, "bar"}, {"col1", "val1"}, {"col2", "val2"}};
constexpr char baz[] = "baz";
const WideColumns baz_columns{
{kDefaultWideColumnName, "quux"}, {"colA", "valA"}, {"colB", "valB"}};
ASSERT_OK(txn_db->PutEntity(WriteOptions(), txn_db->DefaultColumnFamily(),
foo, foo_columns));
ASSERT_OK(txn_db->PutEntity(WriteOptions(), txn_db->DefaultColumnFamily(),
baz, baz_columns));
std::unique_ptr<Transaction> txn(txn_db->BeginTransaction(WriteOptions()));
ASSERT_NE(txn, nullptr);
{
PinnableWideColumns columns;
ASSERT_OK(txn->GetEntity(ReadOptions(), txn_db->DefaultColumnFamily(), foo,
&columns));
ASSERT_EQ(columns.columns(), foo_columns);
}
{
PinnableWideColumns columns;
ASSERT_OK(txn->GetEntity(ReadOptions(), txn_db->DefaultColumnFamily(), baz,
&columns));
ASSERT_EQ(columns.columns(), baz_columns);
}
{
constexpr size_t num_keys = 2;
std::array<Slice, num_keys> keys{{foo, baz}};
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
txn->MultiGetEntity(ReadOptions(), txn_db->DefaultColumnFamily(), num_keys,
keys.data(), results.data(), statuses.data());
ASSERT_OK(statuses[0]);
ASSERT_OK(statuses[1]);
ASSERT_EQ(results[0].columns(), foo_columns);
ASSERT_EQ(results[1].columns(), baz_columns);
}
const WideColumns foo_new_columns{{kDefaultWideColumnName, "FOO"},
{"hello", "world"}};
const WideColumns baz_new_columns{{kDefaultWideColumnName, "BAZ"},
{"ping", "pong"}};
ASSERT_OK(
txn->PutEntity(txn_db->DefaultColumnFamily(), foo, foo_new_columns));
ASSERT_OK(
txn->PutEntity(txn_db->DefaultColumnFamily(), baz, baz_new_columns));
{
PinnableWideColumns columns;
ASSERT_OK(txn->GetEntity(ReadOptions(), txn_db->DefaultColumnFamily(), foo,
&columns));
ASSERT_EQ(columns.columns(), foo_new_columns);
}
{
PinnableWideColumns columns;
ASSERT_OK(txn->GetEntity(ReadOptions(), txn_db->DefaultColumnFamily(), baz,
&columns));
ASSERT_EQ(columns.columns(), baz_new_columns);
}
{
constexpr size_t num_keys = 2;
std::array<Slice, num_keys> keys{{foo, baz}};
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
txn->MultiGetEntity(ReadOptions(), txn_db->DefaultColumnFamily(), num_keys,
keys.data(), results.data(), statuses.data());
ASSERT_OK(statuses[0]);
ASSERT_OK(statuses[1]);
ASSERT_EQ(results[0].columns(), foo_new_columns);
ASSERT_EQ(results[1].columns(), baz_new_columns);
}
std::unique_ptr<Transaction> conflicting_txn(
txn_db->BeginTransaction(WriteOptions()));
ASSERT_NE(conflicting_txn, nullptr);
const WideColumns foo_conflict_columns{{kDefaultWideColumnName, "X"},
{"conflicting", "write"}};
ASSERT_OK(conflicting_txn->PutEntity(txn_db->DefaultColumnFamily(), foo,
foo_conflict_columns));
ASSERT_OK(conflicting_txn->Commit());
{
PinnableWideColumns columns;
ASSERT_OK(txn_db->GetEntity(ReadOptions(), txn_db->DefaultColumnFamily(),
foo, &columns));
ASSERT_EQ(columns.columns(), foo_conflict_columns);
}
ASSERT_TRUE(txn->Commit().IsBusy()); // Txn should not commit
// Verify that transaction did not write anything
{
PinnableWideColumns columns;
ASSERT_OK(txn_db->GetEntity(ReadOptions(), txn_db->DefaultColumnFamily(),
foo, &columns));
ASSERT_EQ(columns.columns(), foo_conflict_columns);
}
{
PinnableWideColumns columns;
ASSERT_OK(txn_db->GetEntity(ReadOptions(), txn_db->DefaultColumnFamily(),
baz, &columns));
ASSERT_EQ(columns.columns(), baz_columns);
}
{
constexpr size_t num_keys = 2;
std::array<Slice, num_keys> keys{{foo, baz}};
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
constexpr bool sorted_input = false;
txn_db->MultiGetEntity(ReadOptions(), txn_db->DefaultColumnFamily(),
num_keys, keys.data(), results.data(),
statuses.data(), sorted_input);
ASSERT_OK(statuses[0]);
ASSERT_OK(statuses[1]);
ASSERT_EQ(results[0].columns(), foo_conflict_columns);
ASSERT_EQ(results[1].columns(), baz_columns);
}
}
TEST_P(OptimisticTransactionTest, EntityReadSanityChecks) {
constexpr char foo[] = "foo";
constexpr char bar[] = "bar";
constexpr size_t num_keys = 2;
std::unique_ptr<Transaction> txn(txn_db->BeginTransaction(WriteOptions()));
ASSERT_NE(txn, nullptr);
{
constexpr ColumnFamilyHandle* column_family = nullptr;
PinnableWideColumns columns;
ASSERT_TRUE(txn->GetEntity(ReadOptions(), column_family, foo, &columns)
.IsInvalidArgument());
}
{
constexpr PinnableWideColumns* columns = nullptr;
ASSERT_TRUE(txn->GetEntity(ReadOptions(), txn_db->DefaultColumnFamily(),
foo, columns)
.IsInvalidArgument());
}
{
ReadOptions read_options;
read_options.io_activity = Env::IOActivity::kGet;
PinnableWideColumns columns;
ASSERT_TRUE(txn->GetEntity(read_options, txn_db->DefaultColumnFamily(), foo,
&columns)
.IsInvalidArgument());
}
{
constexpr ColumnFamilyHandle* column_family = nullptr;
std::array<Slice, num_keys> keys{{foo, bar}};
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
constexpr bool sorted_input = false;
txn->MultiGetEntity(ReadOptions(), column_family, num_keys, keys.data(),
results.data(), statuses.data(), sorted_input);
ASSERT_TRUE(statuses[0].IsInvalidArgument());
ASSERT_TRUE(statuses[1].IsInvalidArgument());
}
{
constexpr Slice* keys = nullptr;
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
constexpr bool sorted_input = false;
txn->MultiGetEntity(ReadOptions(), txn_db->DefaultColumnFamily(), num_keys,
keys, results.data(), statuses.data(), sorted_input);
ASSERT_TRUE(statuses[0].IsInvalidArgument());
ASSERT_TRUE(statuses[1].IsInvalidArgument());
}
{
std::array<Slice, num_keys> keys{{foo, bar}};
constexpr PinnableWideColumns* results = nullptr;
std::array<Status, num_keys> statuses;
constexpr bool sorted_input = false;
txn->MultiGetEntity(ReadOptions(), txn_db->DefaultColumnFamily(), num_keys,
keys.data(), results, statuses.data(), sorted_input);
ASSERT_TRUE(statuses[0].IsInvalidArgument());
ASSERT_TRUE(statuses[1].IsInvalidArgument());
}
{
ReadOptions read_options;
read_options.io_activity = Env::IOActivity::kMultiGet;
std::array<Slice, num_keys> keys{{foo, bar}};
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
constexpr bool sorted_input = false;
txn->MultiGetEntity(read_options, txn_db->DefaultColumnFamily(), num_keys,
keys.data(), results.data(), statuses.data(),
sorted_input);
ASSERT_TRUE(statuses[0].IsInvalidArgument());
ASSERT_TRUE(statuses[1].IsInvalidArgument());
}
}
INSTANTIATE_TEST_CASE_P(
InstanceOccGroup, OptimisticTransactionTest,
testing::Values(OccValidationPolicy::kValidateSerial,
@@ -3,7 +3,6 @@
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#include "utilities/transactions/pessimistic_transaction.h"
#include <map>
@@ -218,6 +217,21 @@ inline Status WriteCommittedTxn::GetForUpdateImpl(
value, exclusive, do_validate);
}
Status WriteCommittedTxn::PutEntityImpl(ColumnFamilyHandle* column_family,
const Slice& key,
const WideColumns& columns,
bool do_validate, bool assume_tracked) {
return Operate(column_family, key, do_validate, assume_tracked,
[column_family, &key, &columns, this]() {
Status s = GetBatchForWrite()->PutEntity(column_family, key,
columns);
if (s.ok()) {
++num_put_entities_;
}
return s;
});
}
Status WriteCommittedTxn::Put(ColumnFamilyHandle* column_family,
const Slice& key, const Slice& value,
const bool assume_tracked) {
@@ -896,6 +910,11 @@ Status PessimisticTransaction::LockBatch(WriteBatch* batch,
RecordKey(column_family_id, key);
return Status::OK();
}
Status PutEntityCF(uint32_t column_family_id, const Slice& key,
const Slice& /* unused */) override {
RecordKey(column_family_id, key);
return Status::OK();
}
Status MergeCF(uint32_t column_family_id, const Slice& key,
const Slice& /* unused */) override {
RecordKey(column_family_id, key);
@@ -1152,14 +1171,15 @@ Status PessimisticTransaction::SetName(const TransactionName& name) {
if (txn_state_ == STARTED) {
if (name_.length()) {
s = Status::InvalidArgument("Transaction has already been named.");
} else if (txn_db_impl_->GetTransactionByName(name) != nullptr) {
s = Status::InvalidArgument("Transaction name must be unique.");
} else if (name.length() < 1 || name.length() > 512) {
s = Status::InvalidArgument(
"Transaction name length must be between 1 and 512 chars.");
} else {
name_ = name;
txn_db_impl_->RegisterTransaction(this);
s = txn_db_impl_->RegisterTransaction(this);
if (!s.ok()) {
name_.clear();
}
}
} else {
s = Status::InvalidArgument("Transaction is beyond state for naming.");
@@ -5,7 +5,6 @@
#pragma once
#include <algorithm>
#include <atomic>
#include <mutex>
@@ -249,6 +248,25 @@ class WriteCommittedTxn : public PessimisticTransaction {
Status PutUntracked(ColumnFamilyHandle* column_family, const SliceParts& key,
const SliceParts& value) override;
// `key` does NOT include timestamp even when it's enabled.
Status PutEntity(ColumnFamilyHandle* column_family, const Slice& key,
const WideColumns& columns,
bool assume_tracked = false) override {
const bool do_validate = !assume_tracked;
return PutEntityImpl(column_family, key, columns, do_validate,
assume_tracked);
}
Status PutEntityUntracked(ColumnFamilyHandle* column_family, const Slice& key,
const WideColumns& columns) override {
constexpr bool do_validate = false;
constexpr bool assume_tracked = false;
return PutEntityImpl(column_family, key, columns, do_validate,
assume_tracked);
}
using TransactionBaseImpl::Delete;
// `key` does NOT include timestamp even when it's enabled.
Status Delete(ColumnFamilyHandle* column_family, const Slice& key,
@@ -288,6 +306,10 @@ class WriteCommittedTxn : public PessimisticTransaction {
TValue* value, bool exclusive,
const bool do_validate);
Status PutEntityImpl(ColumnFamilyHandle* column_family, const Slice& key,
const WideColumns& columns, bool do_validate,
bool assume_tracked);
template <typename TKey, typename TOperation>
Status Operate(ColumnFamilyHandle* column_family, const TKey& key,
const bool do_validate, const bool assume_tracked,
@@ -3,10 +3,10 @@
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#include "utilities/transactions/pessimistic_transaction_db.h"
#include <cinttypes>
#include <memory>
#include <sstream>
#include <string>
#include <unordered_set>
@@ -512,15 +512,15 @@ Transaction* PessimisticTransactionDB::BeginInternalTransaction(
return txn;
}
// All user Put, Merge, Delete, and Write requests must be intercepted to make
// sure that they lock all keys that they are writing to avoid causing conflicts
// with any concurrent transactions. The easiest way to do this is to wrap all
// write operations in a transaction.
// All user Put, PutEntity, Merge, Delete, and Write requests must be
// intercepted to make sure that they lock all keys that they are writing to
// avoid causing conflicts with any concurrent transactions. The easiest way to
// do this is to wrap all write operations in a transaction.
//
// Put(), Merge(), and Delete() only lock a single key per call. Write() will
// sort its keys before locking them. This guarantees that TransactionDB write
// methods cannot deadlock with each other (but still could deadlock with a
// Transaction).
// Put(), PutEntity(), Merge(), and Delete() only lock a single key per call.
// Write() will sort its keys before locking them. This guarantees that
// TransactionDB write methods cannot deadlock with each other (but still could
// deadlock with a Transaction).
Status PessimisticTransactionDB::Put(const WriteOptions& options,
ColumnFamilyHandle* column_family,
const Slice& key, const Slice& val) {
@@ -545,6 +545,42 @@ Status PessimisticTransactionDB::Put(const WriteOptions& options,
return s;
}
Status PessimisticTransactionDB::PutEntity(const WriteOptions& options,
ColumnFamilyHandle* column_family,
const Slice& key,
const WideColumns& columns) {
{
const Status s = FailIfCfEnablesTs(this, column_family);
if (!s.ok()) {
return s;
}
}
{
std::unique_ptr<Transaction> txn(BeginInternalTransaction(options));
txn->DisableIndexing();
// Since the client didn't create a transaction, they don't care about
// conflict checking for this write. So we just need to do
// PutEntityUntracked().
{
const Status s = txn->PutEntityUntracked(column_family, key, columns);
if (!s.ok()) {
return s;
}
}
{
const Status s = txn->Commit();
if (!s.ok()) {
return s;
}
}
}
return Status::OK();
}
Status PessimisticTransactionDB::Delete(const WriteOptions& wopts,
ColumnFamilyHandle* column_family,
const Slice& key) {
@@ -687,6 +723,11 @@ void PessimisticTransactionDB::ReinitializeTransaction(
Transaction* PessimisticTransactionDB::GetTransactionByName(
const TransactionName& name) {
std::lock_guard<std::mutex> lock(name_map_mutex_);
return GetTransactionByNameLocked(name);
}
Transaction* PessimisticTransactionDB::GetTransactionByNameLocked(
const TransactionName& name) {
auto it = transactions_.find(name);
if (it == transactions_.end()) {
return nullptr;
@@ -719,13 +760,15 @@ void PessimisticTransactionDB::SetDeadlockInfoBufferSize(uint32_t target_size) {
lock_manager_->Resize(target_size);
}
void PessimisticTransactionDB::RegisterTransaction(Transaction* txn) {
Status PessimisticTransactionDB::RegisterTransaction(Transaction* txn) {
assert(txn);
assert(txn->GetName().length() > 0);
assert(GetTransactionByName(txn->GetName()) == nullptr);
assert(txn->GetState() == Transaction::STARTED);
std::lock_guard<std::mutex> lock(name_map_mutex_);
transactions_[txn->GetName()] = txn;
if (!transactions_.insert({txn->GetName(), txn}).second) {
return Status::InvalidArgument("Duplicate txn name " + txn->GetName());
}
return Status::OK();
}
void PessimisticTransactionDB::UnregisterTransaction(Transaction* txn) {
@@ -50,6 +50,20 @@ class PessimisticTransactionDB : public TransactionDB {
Status Put(const WriteOptions& options, ColumnFamilyHandle* column_family,
const Slice& key, const Slice& val) override;
Status PutEntity(const WriteOptions& options,
ColumnFamilyHandle* column_family, const Slice& key,
const WideColumns& columns) override;
Status PutEntity(const WriteOptions& /* options */, const Slice& /* key */,
const AttributeGroups& attribute_groups) override {
if (attribute_groups.empty()) {
return Status::InvalidArgument(
"Cannot call this method without attribute groups");
}
return Status::NotSupported(
"PutEntity with AttributeGroups not supported by "
"PessimisticTransactionDB");
}
using StackableDB::Delete;
Status Delete(const WriteOptions& wopts, ColumnFamilyHandle* column_family,
const Slice& key) override;
@@ -159,7 +173,7 @@ class PessimisticTransactionDB : public TransactionDB {
Transaction* GetTransactionByName(const TransactionName& name) override;
void RegisterTransaction(Transaction* txn);
Status RegisterTransaction(Transaction* txn);
void UnregisterTransaction(Transaction* txn);
// not thread safe. current use case is during recovery (single thread)
@@ -225,6 +239,7 @@ class PessimisticTransactionDB : public TransactionDB {
friend class WriteUnpreparedTransactionTest_MarkLogWithPrepSection_Test;
Transaction* BeginInternalTransaction(const WriteOptions& options);
Transaction* GetTransactionByNameLocked(const TransactionName& name);
std::shared_ptr<LockManager> lock_manager_;
+64 -3
View File
@@ -3,7 +3,6 @@
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#include "utilities/transactions/transaction_base.h"
#include <cinttypes>
@@ -90,6 +89,7 @@ void TransactionBaseImpl::Clear() {
commit_time_batch_.Clear();
tracked_locks_->Clear();
num_puts_ = 0;
num_put_entities_ = 0;
num_deletes_ = 0;
num_merges_ = 0;
@@ -177,7 +177,7 @@ void TransactionBaseImpl::SetSavePoint() {
autovector<TransactionBaseImpl::SavePoint>>());
}
save_points_->emplace(snapshot_, snapshot_needed_, snapshot_notifier_,
num_puts_, num_deletes_, num_merges_,
num_puts_, num_put_entities_, num_deletes_, num_merges_,
lock_tracker_factory_);
write_batch_.SetSavePoint();
}
@@ -190,6 +190,7 @@ Status TransactionBaseImpl::RollbackToSavePoint() {
snapshot_needed_ = save_point.snapshot_needed_;
snapshot_notifier_ = save_point.snapshot_notifier_;
num_puts_ = save_point.num_puts_;
num_put_entities_ = save_point.num_put_entities_;
num_deletes_ = save_point.num_deletes_;
num_merges_ = save_point.num_merges_;
@@ -288,6 +289,13 @@ Status TransactionBaseImpl::GetImpl(const ReadOptions& read_options,
pinnable_val);
}
Status TransactionBaseImpl::GetEntity(const ReadOptions& read_options,
ColumnFamilyHandle* column_family,
const Slice& key,
PinnableWideColumns* columns) {
return GetEntityImpl(read_options, column_family, key, columns);
}
Status TransactionBaseImpl::GetForUpdate(const ReadOptions& read_options,
ColumnFamilyHandle* column_family,
const Slice& key, std::string* value,
@@ -400,6 +408,15 @@ void TransactionBaseImpl::MultiGet(const ReadOptions& _read_options,
sorted_input);
}
void TransactionBaseImpl::MultiGetEntity(const ReadOptions& read_options,
ColumnFamilyHandle* column_family,
size_t num_keys, const Slice* keys,
PinnableWideColumns* results,
Status* statuses, bool sorted_input) {
MultiGetEntityImpl(read_options, column_family, num_keys, keys, results,
statuses, sorted_input);
}
std::vector<Status> TransactionBaseImpl::MultiGetForUpdate(
const ReadOptions& read_options,
const std::vector<ColumnFamilyHandle*>& column_family,
@@ -451,6 +468,32 @@ Iterator* TransactionBaseImpl::GetIterator(const ReadOptions& read_options,
&read_options);
}
Status TransactionBaseImpl::PutEntityImpl(ColumnFamilyHandle* column_family,
const Slice& key,
const WideColumns& columns,
bool do_validate,
bool assume_tracked) {
{
constexpr bool read_only = false;
constexpr bool exclusive = true;
const Status s = TryLock(column_family, key, read_only, exclusive,
do_validate, assume_tracked);
if (!s.ok()) {
return s;
}
}
{
const Status s = GetBatchForWrite()->PutEntity(column_family, key, columns);
if (!s.ok()) {
return s;
}
}
++num_put_entities_;
return Status::OK();
}
Status TransactionBaseImpl::Put(ColumnFamilyHandle* column_family,
const Slice& key, const Slice& value,
const bool assume_tracked) {
@@ -678,6 +721,10 @@ uint64_t TransactionBaseImpl::GetElapsedTime() const {
uint64_t TransactionBaseImpl::GetNumPuts() const { return num_puts_; }
uint64_t TransactionBaseImpl::GetNumPutEntities() const {
return num_put_entities_;
}
uint64_t TransactionBaseImpl::GetNumDeletes() const { return num_deletes_; }
uint64_t TransactionBaseImpl::GetNumMerges() const { return num_merges_; }
@@ -705,7 +752,8 @@ void TransactionBaseImpl::TrackKey(uint32_t cfh_id, const std::string& key,
}
}
// Gets the write batch that should be used for Put/Merge/Deletes.
// Gets the write batch that should be used for Put/PutEntity/Merge/Delete
// operations.
//
// Returns either a WriteBatch or WriteBatchWithIndex depending on whether
// DisableIndexing() has been called.
@@ -769,6 +817,19 @@ Status TransactionBaseImpl::RebuildFromWriteBatch(WriteBatch* src_batch) {
return txn_->Put(db_->GetColumnFamilyHandle(cf), key, val);
}
Status PutEntityCF(uint32_t cf, const Slice& key,
const Slice& entity) override {
Slice entity_copy = entity;
WideColumns columns;
const Status s =
WideColumnSerialization::Deserialize(entity_copy, columns);
if (!s.ok()) {
return s;
}
return txn_->PutEntity(db_->GetColumnFamilyHandle(cf), key, columns);
}
Status DeleteCF(uint32_t cf, const Slice& key) override {
return txn_->Delete(db_->GetColumnFamilyHandle(cf), key);
}
+63 -10
View File
@@ -5,7 +5,6 @@
#pragma once
#include <stack>
#include <string>
#include <vector>
@@ -37,10 +36,11 @@ class TransactionBaseImpl : public Transaction {
void Reinitialize(DB* db, const WriteOptions& write_options);
// Called before executing Put, Merge, Delete, and GetForUpdate. If TryLock
// returns non-OK, the Put/Merge/Delete/GetForUpdate will be failed.
// do_validate will be false if called from PutUntracked, DeleteUntracked,
// MergeUntracked, or GetForUpdate(do_validate=false)
// Called before executing Put, PutEntity, Merge, Delete, and GetForUpdate. If
// TryLock returns non-OK, the Put/PutEntity/Merge/Delete/GetForUpdate will be
// failed. do_validate will be false if called from PutUntracked,
// PutEntityUntracked, DeleteUntracked, MergeUntracked, or
// GetForUpdate(do_validate=false)
virtual Status TryLock(ColumnFamilyHandle* column_family, const Slice& key,
bool read_only, bool exclusive,
const bool do_validate = true,
@@ -66,6 +66,10 @@ class TransactionBaseImpl : public Transaction {
return Get(options, db_->DefaultColumnFamily(), key, value);
}
Status GetEntity(const ReadOptions& options,
ColumnFamilyHandle* column_family, const Slice& key,
PinnableWideColumns* columns) override;
using Transaction::GetForUpdate;
Status GetForUpdate(const ReadOptions& options,
ColumnFamilyHandle* column_family, const Slice& key,
@@ -112,6 +116,11 @@ class TransactionBaseImpl : public Transaction {
const Slice* keys, PinnableSlice* values, Status* statuses,
const bool sorted_input = false) override;
void MultiGetEntity(const ReadOptions& options,
ColumnFamilyHandle* column_family, size_t num_keys,
const Slice* keys, PinnableWideColumns* results,
Status* statuses, bool sorted_input = false) override;
using Transaction::MultiGetForUpdate;
std::vector<Status> MultiGetForUpdate(
const ReadOptions& options,
@@ -145,6 +154,15 @@ class TransactionBaseImpl : public Transaction {
return Put(nullptr, key, value);
}
Status PutEntity(ColumnFamilyHandle* column_family, const Slice& key,
const WideColumns& columns,
bool assume_tracked = false) override {
const bool do_validate = !assume_tracked;
return PutEntityImpl(column_family, key, columns, do_validate,
assume_tracked);
}
Status Merge(ColumnFamilyHandle* column_family, const Slice& key,
const Slice& value, const bool assume_tracked = false) override;
Status Merge(const Slice& key, const Slice& value) override {
@@ -181,6 +199,15 @@ class TransactionBaseImpl : public Transaction {
return PutUntracked(nullptr, key, value);
}
Status PutEntityUntracked(ColumnFamilyHandle* column_family, const Slice& key,
const WideColumns& columns) override {
constexpr bool do_validate = false;
constexpr bool assume_tracked = false;
return PutEntityImpl(column_family, key, columns, do_validate,
assume_tracked);
}
Status MergeUntracked(ColumnFamilyHandle* column_family, const Slice& key,
const Slice& value) override;
Status MergeUntracked(const Slice& key, const Slice& value) override {
@@ -240,6 +267,8 @@ class TransactionBaseImpl : public Transaction {
uint64_t GetNumPuts() const override;
uint64_t GetNumPutEntities() const override;
uint64_t GetNumDeletes() const override;
uint64_t GetNumMerges() const override;
@@ -276,6 +305,26 @@ class TransactionBaseImpl : public Transaction {
Status GetImpl(const ReadOptions& options, ColumnFamilyHandle* column_family,
const Slice& key, PinnableSlice* value) override;
Status GetEntityImpl(const ReadOptions& options,
ColumnFamilyHandle* column_family, const Slice& key,
PinnableWideColumns* columns) {
return write_batch_.GetEntityFromBatchAndDB(db_, options, column_family,
key, columns);
}
void MultiGetEntityImpl(const ReadOptions& options,
ColumnFamilyHandle* column_family, size_t num_keys,
const Slice* keys, PinnableWideColumns* results,
Status* statuses, bool sorted_input) {
write_batch_.MultiGetEntityFromBatchAndDB(db_, options, column_family,
num_keys, keys, results, statuses,
sorted_input);
}
Status PutEntityImpl(ColumnFamilyHandle* column_family, const Slice& key,
const WideColumns& columns, bool do_validate,
bool assume_tracked);
// Add a key to the list of tracked keys.
//
// seqno is the earliest seqno this key was involved with this transaction.
@@ -320,6 +369,7 @@ class TransactionBaseImpl : public Transaction {
// Count of various operations pending in this transaction
uint64_t num_puts_ = 0;
uint64_t num_put_entities_ = 0;
uint64_t num_deletes_ = 0;
uint64_t num_merges_ = 0;
@@ -328,6 +378,7 @@ class TransactionBaseImpl : public Transaction {
bool snapshot_needed_ = false;
std::shared_ptr<TransactionNotifier> snapshot_notifier_;
uint64_t num_puts_ = 0;
uint64_t num_put_entities_ = 0;
uint64_t num_deletes_ = 0;
uint64_t num_merges_ = 0;
@@ -336,12 +387,14 @@ class TransactionBaseImpl : public Transaction {
SavePoint(std::shared_ptr<const Snapshot> snapshot, bool snapshot_needed,
std::shared_ptr<TransactionNotifier> snapshot_notifier,
uint64_t num_puts, uint64_t num_deletes, uint64_t num_merges,
uint64_t num_puts, uint64_t num_put_entities,
uint64_t num_deletes, uint64_t num_merges,
const LockTrackerFactory& lock_tracker_factory)
: snapshot_(snapshot),
snapshot_needed_(snapshot_needed),
snapshot_notifier_(snapshot_notifier),
num_puts_(num_puts),
num_put_entities_(num_put_entities),
num_deletes_(num_deletes),
num_merges_(num_merges),
new_locks_(lock_tracker_factory.Create()) {}
@@ -373,10 +426,10 @@ class TransactionBaseImpl : public Transaction {
// prepare phase is not skipped.
WriteBatch commit_time_batch_;
// If true, future Put/Merge/Deletes will be indexed in the
// WriteBatchWithIndex.
// If false, future Put/Merge/Deletes will be inserted directly into the
// underlying WriteBatch and not indexed in the WriteBatchWithIndex.
// If true, future Put/PutEntity/Merge/Delete operations will be indexed in
// the WriteBatchWithIndex. If false, future Put/PutEntity/Merge/Delete
// operations will be inserted directly into the underlying WriteBatch and not
// indexed in the WriteBatchWithIndex.
bool indexing_enabled_;
// SetSnapshotOnNextOperation() has been called and the snapshot has not yet
+378 -1
View File
@@ -3,10 +3,10 @@
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#include "utilities/transactions/transaction_test.h"
#include <algorithm>
#include <array>
#include <functional>
#include <string>
#include <thread>
@@ -2210,6 +2210,10 @@ TEST_P(TransactionTest, TwoPhaseLogRollingTest2) {
* hidden behind improperly summed sequence ids
*/
TEST_P(TransactionTest, TwoPhaseOutOfOrderDelete) {
// WAL recycling incompatible with disableWAL (below)
options.recycle_log_file_num = 0;
ASSERT_OK(ReOpenNoDelete());
DBImpl* db_impl = static_cast_with_check<DBImpl>(db->GetRootDB());
WriteOptions wal_on, wal_off;
wal_on.sync = true;
@@ -3621,6 +3625,9 @@ TEST_P(TransactionTest, UntrackedWrites) {
// Untracked writes should succeed even though key was written after snapshot
s = txn->PutUntracked("untracked", "1");
ASSERT_OK(s);
s = txn->PutEntityUntracked(db->DefaultColumnFamily(), "untracked",
{{"hello", "world"}});
ASSERT_OK(s);
s = txn->MergeUntracked("untracked", "2");
ASSERT_OK(s);
s = txn->DeleteUntracked("untracked");
@@ -3629,6 +3636,8 @@ TEST_P(TransactionTest, UntrackedWrites) {
// Conflict
s = txn->Put("untracked", "3");
ASSERT_TRUE(s.IsBusy());
s = txn->PutEntity(db->DefaultColumnFamily(), "untracked", {{"foo", "bar"}});
ASSERT_TRUE(s.IsBusy());
s = txn->Commit();
ASSERT_OK(s);
@@ -6984,6 +6993,329 @@ TEST_P(TransactionTest, UnlockWALStallCleared) {
}
}
TEST_P(TransactionTest, PutEntitySuccess) {
const TxnDBWritePolicy write_policy = std::get<2>(GetParam());
if (write_policy != TxnDBWritePolicy::WRITE_COMMITTED) {
ROCKSDB_GTEST_BYPASS("Test only WriteCommitted for now");
return;
}
constexpr char foo[] = "foo";
const WideColumns foo_columns{
{kDefaultWideColumnName, "bar"}, {"col1", "val1"}, {"col2", "val2"}};
const WideColumns foo_new_columns{
{kDefaultWideColumnName, "baz"}, {"colA", "valA"}, {"colB", "valB"}};
ASSERT_OK(db->PutEntity(WriteOptions(), db->DefaultColumnFamily(), foo,
foo_columns));
{
std::unique_ptr<Transaction> txn(
db->BeginTransaction(WriteOptions(), TransactionOptions()));
ASSERT_NE(txn, nullptr);
ASSERT_NE(txn->GetID(), 0);
ASSERT_EQ(txn->GetNumPutEntities(), 0);
{
PinnableWideColumns columns;
ASSERT_OK(txn->GetEntity(ReadOptions(), db->DefaultColumnFamily(), foo,
&columns));
ASSERT_EQ(columns.columns(), foo_columns);
}
{
PinnableSlice value;
ASSERT_OK(txn->GetForUpdate(ReadOptions(), foo, &value));
ASSERT_EQ(value, foo_columns[0].value());
}
ASSERT_OK(txn->PutEntity(db->DefaultColumnFamily(), foo, foo_new_columns));
ASSERT_EQ(txn->GetNumPutEntities(), 1);
{
PinnableWideColumns columns;
ASSERT_OK(txn->GetEntity(ReadOptions(), db->DefaultColumnFamily(), foo,
&columns));
ASSERT_EQ(columns.columns(), foo_new_columns);
}
{
PinnableSlice value;
ASSERT_OK(txn->GetForUpdate(ReadOptions(), foo, &value));
ASSERT_EQ(value, foo_new_columns[0].value());
}
ASSERT_OK(txn->Commit());
}
{
PinnableWideColumns columns;
ASSERT_OK(
db->GetEntity(ReadOptions(), db->DefaultColumnFamily(), foo, &columns));
ASSERT_EQ(columns.columns(), foo_new_columns);
}
}
TEST_P(TransactionTest, PutEntityWriteConflict) {
const TxnDBWritePolicy write_policy = std::get<2>(GetParam());
if (write_policy != TxnDBWritePolicy::WRITE_COMMITTED) {
ROCKSDB_GTEST_BYPASS("Test only WriteCommitted for now");
return;
}
constexpr char foo[] = "foo";
const WideColumns foo_columns{
{kDefaultWideColumnName, "bar"}, {"col1", "val1"}, {"col2", "val2"}};
constexpr char baz[] = "baz";
const WideColumns baz_columns{
{kDefaultWideColumnName, "quux"}, {"colA", "valA"}, {"colB", "valB"}};
ASSERT_OK(db->PutEntity(WriteOptions(), db->DefaultColumnFamily(), foo,
foo_columns));
ASSERT_OK(db->PutEntity(WriteOptions(), db->DefaultColumnFamily(), baz,
baz_columns));
std::unique_ptr<Transaction> txn(db->BeginTransaction(WriteOptions()));
ASSERT_NE(txn, nullptr);
{
PinnableWideColumns columns;
ASSERT_OK(txn->GetEntity(ReadOptions(), db->DefaultColumnFamily(), foo,
&columns));
ASSERT_EQ(columns.columns(), foo_columns);
}
{
PinnableWideColumns columns;
ASSERT_OK(txn->GetEntity(ReadOptions(), db->DefaultColumnFamily(), baz,
&columns));
ASSERT_EQ(columns.columns(), baz_columns);
}
{
constexpr size_t num_keys = 2;
std::array<Slice, num_keys> keys{{foo, baz}};
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
txn->MultiGetEntity(ReadOptions(), db->DefaultColumnFamily(), num_keys,
keys.data(), results.data(), statuses.data());
ASSERT_OK(statuses[0]);
ASSERT_OK(statuses[1]);
ASSERT_EQ(results[0].columns(), foo_columns);
ASSERT_EQ(results[1].columns(), baz_columns);
}
const WideColumns foo_new_columns{{kDefaultWideColumnName, "FOO"},
{"hello", "world"}};
const WideColumns baz_new_columns{{kDefaultWideColumnName, "BAZ"},
{"ping", "pong"}};
ASSERT_OK(txn->PutEntity(db->DefaultColumnFamily(), foo, foo_new_columns));
ASSERT_OK(txn->PutEntity(db->DefaultColumnFamily(), baz, baz_new_columns));
{
PinnableWideColumns columns;
ASSERT_OK(txn->GetEntity(ReadOptions(), db->DefaultColumnFamily(), foo,
&columns));
ASSERT_EQ(columns.columns(), foo_new_columns);
}
{
PinnableWideColumns columns;
ASSERT_OK(txn->GetEntity(ReadOptions(), db->DefaultColumnFamily(), baz,
&columns));
ASSERT_EQ(columns.columns(), baz_new_columns);
}
{
constexpr size_t num_keys = 2;
std::array<Slice, num_keys> keys{{foo, baz}};
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
txn->MultiGetEntity(ReadOptions(), db->DefaultColumnFamily(), num_keys,
keys.data(), results.data(), statuses.data());
ASSERT_OK(statuses[0]);
ASSERT_OK(statuses[1]);
ASSERT_EQ(results[0].columns(), foo_new_columns);
ASSERT_EQ(results[1].columns(), baz_new_columns);
}
// This PutEntity outside of a transaction will conflict with the previous
// write
const WideColumns foo_conflict_columns{{kDefaultWideColumnName, "X"},
{"conflicting", "write"}};
ASSERT_TRUE(db->PutEntity(WriteOptions(), db->DefaultColumnFamily(), foo,
foo_conflict_columns)
.IsTimedOut());
{
PinnableWideColumns columns;
ASSERT_OK(
db->GetEntity(ReadOptions(), db->DefaultColumnFamily(), foo, &columns));
ASSERT_EQ(columns.columns(), foo_columns);
}
{
PinnableWideColumns columns;
ASSERT_OK(
db->GetEntity(ReadOptions(), db->DefaultColumnFamily(), baz, &columns));
ASSERT_EQ(columns.columns(), baz_columns);
}
{
constexpr size_t num_keys = 2;
std::array<Slice, num_keys> keys{{foo, baz}};
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
constexpr bool sorted_input = false;
db->MultiGetEntity(ReadOptions(), db->DefaultColumnFamily(), num_keys,
keys.data(), results.data(), statuses.data(),
sorted_input);
ASSERT_OK(statuses[0]);
ASSERT_OK(statuses[1]);
ASSERT_EQ(results[0].columns(), foo_columns);
ASSERT_EQ(results[1].columns(), baz_columns);
}
ASSERT_OK(txn->Commit());
{
PinnableWideColumns columns;
ASSERT_OK(
db->GetEntity(ReadOptions(), db->DefaultColumnFamily(), foo, &columns));
ASSERT_EQ(columns.columns(), foo_new_columns);
}
{
PinnableWideColumns columns;
ASSERT_OK(
db->GetEntity(ReadOptions(), db->DefaultColumnFamily(), baz, &columns));
ASSERT_EQ(columns.columns(), baz_new_columns);
}
{
constexpr size_t num_keys = 2;
std::array<Slice, num_keys> keys{{foo, baz}};
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
constexpr bool sorted_input = false;
db->MultiGetEntity(ReadOptions(), db->DefaultColumnFamily(), num_keys,
keys.data(), results.data(), statuses.data(),
sorted_input);
ASSERT_OK(statuses[0]);
ASSERT_OK(statuses[1]);
ASSERT_EQ(results[0].columns(), foo_new_columns);
ASSERT_EQ(results[1].columns(), baz_new_columns);
}
}
TEST_P(TransactionTest, EntityReadSanityChecks) {
constexpr char foo[] = "foo";
constexpr char bar[] = "bar";
constexpr size_t num_keys = 2;
std::unique_ptr<Transaction> txn(db->BeginTransaction(WriteOptions()));
ASSERT_NE(txn, nullptr);
{
constexpr ColumnFamilyHandle* column_family = nullptr;
PinnableWideColumns columns;
ASSERT_TRUE(txn->GetEntity(ReadOptions(), column_family, foo, &columns)
.IsInvalidArgument());
}
{
constexpr PinnableWideColumns* columns = nullptr;
ASSERT_TRUE(
txn->GetEntity(ReadOptions(), db->DefaultColumnFamily(), foo, columns)
.IsInvalidArgument());
}
{
ReadOptions read_options;
read_options.io_activity = Env::IOActivity::kGet;
PinnableWideColumns columns;
ASSERT_TRUE(
txn->GetEntity(read_options, db->DefaultColumnFamily(), foo, &columns)
.IsInvalidArgument());
}
{
constexpr ColumnFamilyHandle* column_family = nullptr;
std::array<Slice, num_keys> keys{{foo, bar}};
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
constexpr bool sorted_input = false;
txn->MultiGetEntity(ReadOptions(), column_family, num_keys, keys.data(),
results.data(), statuses.data(), sorted_input);
ASSERT_TRUE(statuses[0].IsInvalidArgument());
ASSERT_TRUE(statuses[1].IsInvalidArgument());
}
{
constexpr Slice* keys = nullptr;
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
constexpr bool sorted_input = false;
txn->MultiGetEntity(ReadOptions(), db->DefaultColumnFamily(), num_keys,
keys, results.data(), statuses.data(), sorted_input);
ASSERT_TRUE(statuses[0].IsInvalidArgument());
ASSERT_TRUE(statuses[1].IsInvalidArgument());
}
{
std::array<Slice, num_keys> keys{{foo, bar}};
constexpr PinnableWideColumns* results = nullptr;
std::array<Status, num_keys> statuses;
constexpr bool sorted_input = false;
txn->MultiGetEntity(ReadOptions(), db->DefaultColumnFamily(), num_keys,
keys.data(), results, statuses.data(), sorted_input);
ASSERT_TRUE(statuses[0].IsInvalidArgument());
ASSERT_TRUE(statuses[1].IsInvalidArgument());
}
{
ReadOptions read_options;
read_options.io_activity = Env::IOActivity::kMultiGet;
std::array<Slice, num_keys> keys{{foo, bar}};
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
constexpr bool sorted_input = false;
txn->MultiGetEntity(read_options, db->DefaultColumnFamily(), num_keys,
keys.data(), results.data(), statuses.data(),
sorted_input);
ASSERT_TRUE(statuses[0].IsInvalidArgument());
ASSERT_TRUE(statuses[1].IsInvalidArgument());
}
}
TEST_F(TransactionDBTest, CollapseKey) {
ASSERT_OK(ReOpen());
ASSERT_OK(db->Put({}, "hello", "world"));
@@ -7033,6 +7365,51 @@ TEST_F(TransactionDBTest, CollapseKey) {
}
}
TEST_F(TransactionDBTest, FlushedLogWithPendingPrepareIsSynced) {
// Repro for a bug where we missed a necessary sync of the old WAL during
// memtable flush. It happened due to applying an optimization to skip syncing
// the old WAL in too many scenarios (all memtable flushes on single CF
// databases). That optimization is only valid when memtable flush can
// guarantee the old WAL will not be read by crash-recovery. When the old WAL
// contains a prepare record without its corresponding commit, that WAL will
// be read by crash-recovery and therefore it must be synced.
const int kStartIndex = 1000;
int next_index = kStartIndex;
ASSERT_OK(ReOpen());
ASSERT_OK(
db->Put(WriteOptions(), "key" + std::to_string(next_index), "value"));
next_index++;
Transaction* txn = db->BeginTransaction(WriteOptions(), TransactionOptions());
ASSERT_OK(txn->SetName("xid" + std::to_string(next_index)));
ASSERT_OK(
txn->Put(Slice("key" + std::to_string(next_index)), Slice("value")));
next_index++;
ASSERT_OK(txn->Prepare());
// Set it directly writable so new WAL containing the commit record will be
// recovered despite not being explicitly synced.
fault_fs->SetFilesystemDirectWritable(true);
ASSERT_OK(db->Flush(FlushOptions()));
ASSERT_OK(txn->Commit());
delete txn;
ASSERT_OK(
db->Put(WriteOptions(), "key" + std::to_string(next_index), "value"));
next_index++;
ASSERT_OK(ReOpenNoDelete());
for (int i = kStartIndex; i < next_index; i++) {
PinnableSlice value;
ASSERT_OK(db->Get(ReadOptions(), db->DefaultColumnFamily(),
"key" + std::to_string(i), &value));
ASSERT_EQ("value", value);
}
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
@@ -63,6 +63,8 @@ class TransactionTestBase : public ::testing::Test {
options.unordered_write = write_ordering == kUnorderedWrite;
options.level0_file_num_compaction_trigger = 2;
options.merge_operator = MergeOperators::CreateFromStringId("stringappend");
// Recycling log file is generally more challenging for correctness
options.recycle_log_file_num = 2;
special_env.skip_fsync_ = true;
fault_fs.reset(new FaultInjectionTestFS(FileSystem::Default()));
env.reset(new CompositeEnvWrapper(&special_env, fault_fs));
@@ -823,11 +823,41 @@ void WriteBatchWithIndex::MultiGetFromBatchAndDB(
}
Status WriteBatchWithIndex::GetEntityFromBatchAndDB(
DB* db, const ReadOptions& read_options, ColumnFamilyHandle* column_family,
DB* db, const ReadOptions& _read_options, ColumnFamilyHandle* column_family,
const Slice& key, PinnableWideColumns* columns, ReadCallback* callback) {
assert(db);
assert(column_family);
assert(columns);
if (!db) {
return Status::InvalidArgument(
"Cannot call GetEntityFromBatchAndDB without a DB object");
}
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
_read_options.io_activity != Env::IOActivity::kGetEntity) {
return Status::InvalidArgument(
"Can only call GetEntityFromBatchAndDB with `ReadOptions::io_activity` "
"set to `Env::IOActivity::kUnknown` or `Env::IOActivity::kGetEntity`");
}
ReadOptions read_options(_read_options);
if (read_options.io_activity == Env::IOActivity::kUnknown) {
read_options.io_activity = Env::IOActivity::kGetEntity;
}
if (!column_family) {
return Status::InvalidArgument(
"Cannot call GetEntityFromBatchAndDB without a column family handle");
}
const Comparator* const ucmp = rep->comparator.GetComparator(column_family);
size_t ts_sz = ucmp ? ucmp->timestamp_size() : 0;
if (ts_sz > 0 && !read_options.timestamp) {
return Status::InvalidArgument("Must specify timestamp");
}
if (!columns) {
return Status::InvalidArgument(
"Cannot call GetEntityFromBatchAndDB without a PinnableWideColumns "
"object");
}
columns->Reset();
@@ -872,46 +902,78 @@ Status WriteBatchWithIndex::GetEntityFromBatchAndDB(
return s;
}
Status WriteBatchWithIndex::GetEntityFromBatchAndDB(
DB* db, const ReadOptions& read_options, ColumnFamilyHandle* column_family,
const Slice& key, PinnableWideColumns* columns) {
void WriteBatchWithIndex::MultiGetEntityFromBatchAndDB(
DB* db, const ReadOptions& _read_options, ColumnFamilyHandle* column_family,
size_t num_keys, const Slice* keys, PinnableWideColumns* results,
Status* statuses, bool sorted_input, ReadCallback* callback) {
assert(statuses);
if (!db) {
return Status::InvalidArgument(
"Cannot call GetEntityFromBatchAndDB without a DB object");
const Status s = Status::InvalidArgument(
"Cannot call MultiGetEntityFromBatchAndDB without a DB object");
for (size_t i = 0; i < num_keys; ++i) {
statuses[i] = s;
}
return;
}
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
_read_options.io_activity != Env::IOActivity::kMultiGetEntity) {
const Status s = Status::InvalidArgument(
"Can only call MultiGetEntityFromBatchAndDB with "
"`ReadOptions::io_activity` set to `Env::IOActivity::kUnknown` or "
"`Env::IOActivity::kMultiGetEntity`");
for (size_t i = 0; i < num_keys; ++i) {
if (statuses[i].ok()) {
statuses[i] = s;
}
}
return;
}
ReadOptions read_options(_read_options);
if (read_options.io_activity == Env::IOActivity::kUnknown) {
read_options.io_activity = Env::IOActivity::kMultiGetEntity;
}
if (!column_family) {
return Status::InvalidArgument(
"Cannot call GetEntityFromBatchAndDB without a column family handle");
const Status s = Status::InvalidArgument(
"Cannot call MultiGetEntityFromBatchAndDB without a column family "
"handle");
for (size_t i = 0; i < num_keys; ++i) {
statuses[i] = s;
}
return;
}
const Comparator* const ucmp = rep->comparator.GetComparator(column_family);
size_t ts_sz = ucmp ? ucmp->timestamp_size() : 0;
const size_t ts_sz = ucmp ? ucmp->timestamp_size() : 0;
if (ts_sz > 0 && !read_options.timestamp) {
return Status::InvalidArgument("Must specify timestamp");
const Status s = Status::InvalidArgument("Must specify timestamp");
for (size_t i = 0; i < num_keys; ++i) {
statuses[i] = s;
}
return;
}
if (!columns) {
return Status::InvalidArgument(
"Cannot call GetEntityFromBatchAndDB without a PinnableWideColumns "
"object");
if (!keys) {
const Status s = Status::InvalidArgument(
"Cannot call MultiGetEntityFromBatchAndDB without keys");
for (size_t i = 0; i < num_keys; ++i) {
statuses[i] = s;
}
return;
}
constexpr ReadCallback* callback = nullptr;
return GetEntityFromBatchAndDB(db, read_options, column_family, key, columns,
callback);
}
void WriteBatchWithIndex::MultiGetEntityFromBatchAndDB(
DB* db, const ReadOptions& read_options, ColumnFamilyHandle* column_family,
size_t num_keys, const Slice* keys, PinnableWideColumns* results,
Status* statuses, bool sorted_input, ReadCallback* callback) {
assert(db);
assert(column_family);
assert(keys);
assert(results);
assert(statuses);
if (!results) {
const Status s = Status::InvalidArgument(
"Cannot call MultiGetEntityFromBatchAndDB without "
"PinnableWideColumns objects");
for (size_t i = 0; i < num_keys; ++i) {
statuses[i] = s;
}
return;
}
struct MergeTuple {
MergeTuple(const Slice& _key, Status* _s, MergeContext&& _merge_context,
@@ -990,8 +1052,8 @@ void WriteBatchWithIndex::MultiGetEntityFromBatchAndDB(
static_cast_with_check<DBImpl>(db->GetRootDB())
->PrepareMultiGetKeys(sorted_keys.size(), sorted_input, &sorted_keys);
static_cast_with_check<DBImpl>(db->GetRootDB())
->MultiGetWithCallback(read_options, column_family, callback,
&sorted_keys);
->MultiGetEntityWithCallback(read_options, column_family, callback,
&sorted_keys);
for (const auto& merge : merges) {
if (merge.s->ok() || merge.s->IsNotFound()) { // DB lookup succeeded
@@ -1001,57 +1063,6 @@ void WriteBatchWithIndex::MultiGetEntityFromBatchAndDB(
}
}
void WriteBatchWithIndex::MultiGetEntityFromBatchAndDB(
DB* db, const ReadOptions& read_options, ColumnFamilyHandle* column_family,
size_t num_keys, const Slice* keys, PinnableWideColumns* results,
Status* statuses, bool sorted_input) {
assert(statuses);
if (!db) {
for (size_t i = 0; i < num_keys; ++i) {
statuses[i] = Status::InvalidArgument(
"Cannot call MultiGetEntityFromBatchAndDB without a DB object");
}
}
if (!column_family) {
for (size_t i = 0; i < num_keys; ++i) {
statuses[i] = Status::InvalidArgument(
"Cannot call MultiGetEntityFromBatchAndDB without a column family "
"handle");
}
}
const Comparator* const ucmp = rep->comparator.GetComparator(column_family);
const size_t ts_sz = ucmp ? ucmp->timestamp_size() : 0;
if (ts_sz > 0 && !read_options.timestamp) {
for (size_t i = 0; i < num_keys; ++i) {
statuses[i] = Status::InvalidArgument("Must specify timestamp");
}
return;
}
if (!keys) {
for (size_t i = 0; i < num_keys; ++i) {
statuses[i] = Status::InvalidArgument(
"Cannot call MultiGetEntityFromBatchAndDB without keys");
}
}
if (!results) {
for (size_t i = 0; i < num_keys; ++i) {
statuses[i] = Status::InvalidArgument(
"Cannot call MultiGetEntityFromBatchAndDB without "
"PinnableWideColumns objects");
}
}
constexpr ReadCallback* callback = nullptr;
MultiGetEntityFromBatchAndDB(db, read_options, column_family, num_keys, keys,
results, statuses, sorted_input, callback);
}
void WriteBatchWithIndex::SetSavePoint() { rep->write_batch.SetSavePoint(); }
Status WriteBatchWithIndex::RollbackToSavePoint() {
@@ -2973,6 +2973,128 @@ TEST_P(WriteBatchWithIndexTest, GetEntityFromBatch) {
}
}
TEST_P(WriteBatchWithIndexTest, EntityReadSanityChecks) {
ASSERT_OK(OpenDB());
constexpr char foo[] = "foo";
constexpr char bar[] = "bar";
constexpr size_t num_keys = 2;
{
constexpr DB* db = nullptr;
PinnableWideColumns columns;
ASSERT_TRUE(batch_
->GetEntityFromBatchAndDB(db, ReadOptions(),
db_->DefaultColumnFamily(), foo,
&columns)
.IsInvalidArgument());
}
{
constexpr ColumnFamilyHandle* column_family = nullptr;
PinnableWideColumns columns;
ASSERT_TRUE(batch_
->GetEntityFromBatchAndDB(db_, ReadOptions(), column_family,
foo, &columns)
.IsInvalidArgument());
}
{
constexpr PinnableWideColumns* columns = nullptr;
ASSERT_TRUE(batch_
->GetEntityFromBatchAndDB(db_, ReadOptions(),
db_->DefaultColumnFamily(), foo,
columns)
.IsInvalidArgument());
}
{
ReadOptions read_options;
read_options.io_activity = Env::IOActivity::kGet;
PinnableWideColumns columns;
ASSERT_TRUE(batch_
->GetEntityFromBatchAndDB(db_, read_options,
db_->DefaultColumnFamily(), foo,
&columns)
.IsInvalidArgument());
}
{
constexpr DB* db = nullptr;
std::array<Slice, num_keys> keys{{foo, bar}};
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
constexpr bool sorted_input = false;
batch_->MultiGetEntityFromBatchAndDB(
db, ReadOptions(), db_->DefaultColumnFamily(), num_keys, keys.data(),
results.data(), statuses.data(), sorted_input);
ASSERT_TRUE(statuses[0].IsInvalidArgument());
ASSERT_TRUE(statuses[1].IsInvalidArgument());
}
{
constexpr ColumnFamilyHandle* column_family = nullptr;
std::array<Slice, num_keys> keys{{foo, bar}};
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
constexpr bool sorted_input = false;
batch_->MultiGetEntityFromBatchAndDB(db_, ReadOptions(), column_family,
num_keys, keys.data(), results.data(),
statuses.data(), sorted_input);
ASSERT_TRUE(statuses[0].IsInvalidArgument());
ASSERT_TRUE(statuses[1].IsInvalidArgument());
}
{
constexpr Slice* keys = nullptr;
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
constexpr bool sorted_input = false;
batch_->MultiGetEntityFromBatchAndDB(
db_, ReadOptions(), db_->DefaultColumnFamily(), num_keys, keys,
results.data(), statuses.data(), sorted_input);
ASSERT_TRUE(statuses[0].IsInvalidArgument());
ASSERT_TRUE(statuses[1].IsInvalidArgument());
}
{
std::array<Slice, num_keys> keys{{foo, bar}};
constexpr PinnableWideColumns* results = nullptr;
std::array<Status, num_keys> statuses;
constexpr bool sorted_input = false;
batch_->MultiGetEntityFromBatchAndDB(
db_, ReadOptions(), db_->DefaultColumnFamily(), num_keys, keys.data(),
results, statuses.data(), sorted_input);
ASSERT_TRUE(statuses[0].IsInvalidArgument());
ASSERT_TRUE(statuses[1].IsInvalidArgument());
}
{
ReadOptions read_options;
read_options.io_activity = Env::IOActivity::kMultiGet;
std::array<Slice, num_keys> keys{{foo, bar}};
std::array<PinnableWideColumns, num_keys> results;
std::array<Status, num_keys> statuses;
constexpr bool sorted_input = false;
batch_->MultiGetEntityFromBatchAndDB(
db_, read_options, db_->DefaultColumnFamily(), num_keys, keys.data(),
results.data(), statuses.data(), sorted_input);
ASSERT_TRUE(statuses[0].IsInvalidArgument());
ASSERT_TRUE(statuses[1].IsInvalidArgument());
}
}
INSTANTIATE_TEST_CASE_P(WBWI, WriteBatchWithIndexTest, testing::Bool());
} // namespace ROCKSDB_NAMESPACE