/Users/deen/code/yugabyte-db/src/yb/docdb/docdb_test_util.h
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) YugaByte, Inc. |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
4 | | // in compliance with the License. You may obtain a copy of the License at |
5 | | // |
6 | | // http://www.apache.org/licenses/LICENSE-2.0 |
7 | | // |
8 | | // Unless required by applicable law or agreed to in writing, software distributed under the License |
9 | | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
10 | | // or implied. See the License for the specific language governing permissions and limitations |
11 | | // under the License. |
12 | | // |
13 | | |
14 | | #ifndef YB_DOCDB_DOCDB_TEST_UTIL_H_ |
15 | | #define YB_DOCDB_DOCDB_TEST_UTIL_H_ |
16 | | |
17 | | #include <random> |
18 | | #include <string> |
19 | | #include <utility> |
20 | | #include <vector> |
21 | | |
22 | | #include "yb/docdb/docdb.h" |
23 | | #include "yb/docdb/docdb_util.h" |
24 | | #include "yb/docdb/in_mem_docdb.h" |
25 | | #include "yb/docdb/subdocument.h" |
26 | | |
27 | | #include "yb/util/strongly_typed_bool.h" |
28 | | |
29 | | namespace yb { |
30 | | namespace docdb { |
31 | | |
32 | | using RandomNumberGenerator = std::mt19937_64; |
33 | | |
34 | | // Maximum number of components in a randomly-generated DocKey. |
35 | | static constexpr int kMaxNumRandomDocKeyParts = 10; |
36 | | |
37 | | // Maximum number of subkeys in a randomly-generated SubDocKey. |
38 | | static constexpr int kMaxNumRandomSubKeys = 10; |
39 | | |
40 | | YB_STRONGLY_TYPED_BOOL(ResolveIntentsDuringRead); |
41 | | YB_STRONGLY_TYPED_BOOL(UseHash); |
42 | | |
43 | | // Intended only for testing, when we want to enable transaction aware code path for cases when we |
44 | | // really have no transactions. This way we will test that transaction aware code path works |
45 | | // correctly in absence of transactions and also doesn't use transaction status provider (it |
46 | | // shouldn't because there are no transactions). |
47 | | // TODO(dtxn) everywhere(?) in tests code where we use kNonTransactionalOperationContext we need |
48 | | // to check both code paths - for transactional tables (passing kNonTransactionalOperationContext) |
49 | | // and non-transactional tables (passing boost::none as transaction context). |
50 | | // https://yugabyte.atlassian.net/browse/ENG-2177. |
51 | | extern const TransactionOperationContext kNonTransactionalOperationContext; |
52 | | |
53 | | // Note: test data generator methods below are using a non-const reference for the random number |
54 | | // generator for simplicity, even though it is against Google C++ Style Guide. If we used a pointer, |
55 | | // we would have to invoke the RNG as (*rng)(). |
56 | | |
57 | | // Generate a random primitive value. |
58 | | PrimitiveValue GenRandomPrimitiveValue(RandomNumberGenerator* rng); |
59 | | |
60 | | // Generate a random sequence of primitive values. |
61 | | std::vector<PrimitiveValue> GenRandomPrimitiveValues(RandomNumberGenerator* rng, |
62 | | int max_num = kMaxNumRandomDocKeyParts); |
63 | | |
64 | | // Generate a "minimal" DocKey. |
65 | | DocKey CreateMinimalDocKey(RandomNumberGenerator* rng, UseHash use_hash); |
66 | | |
67 | | // Generate a random DocKey with up to the default number of components. |
68 | | DocKey GenRandomDocKey(RandomNumberGenerator* rng, UseHash use_hash); |
69 | | |
70 | | std::vector<DocKey> GenRandomDocKeys(RandomNumberGenerator* rng, UseHash use_hash, int num_keys); |
71 | | |
72 | | std::vector<SubDocKey> GenRandomSubDocKeys(RandomNumberGenerator* rng, |
73 | | UseHash use_hash, |
74 | | int num_keys); |
75 | | |
76 | | template<typename T> |
77 | 0 | const T& RandomElementOf(const std::vector<T>& v, RandomNumberGenerator* rng) { |
78 | 0 | return v[(*rng)() % v.size()]; |
79 | 0 | } Unexecuted instantiation: _ZN2yb5docdb15RandomElementOfINS0_6DocKeyEEERKT_RKNSt3__16vectorIS3_NS6_9allocatorIS3_EEEEPNS6_23mersenne_twister_engineIyLm64ELm312ELm156ELm31ELy13043109905998158313ELm29ELy6148914691236517205ELm17ELy8202884508482404352ELm37ELy18444473444759240704ELm43ELy6364136223846793005EEE Unexecuted instantiation: _ZN2yb5docdb15RandomElementOfINS0_14PrimitiveValueEEERKT_RKNSt3__16vectorIS3_NS6_9allocatorIS3_EEEEPNS6_23mersenne_twister_engineIyLm64ELm312ELm156ELm31ELy13043109905998158313ELm29ELy6148914691236517205ELm17ELy8202884508482404352ELm37ELy18444473444759240704ELm43ELy6364136223846793005EEE |
80 | | |
81 | | // Represents a full logical snapshot of a RocksDB instance. An instance of this class will record |
82 | | // the state of a RocksDB instance via Capture, which can then be written to a new RocksDB instance |
83 | | // via RestoreTo. |
84 | | class LogicalRocksDBDebugSnapshot { |
85 | | public: |
86 | 0 | LogicalRocksDBDebugSnapshot() {} |
87 | | void Capture(rocksdb::DB* rocksdb); |
88 | | void RestoreTo(rocksdb::DB *rocksdb) const; |
89 | | private: |
90 | | std::vector<std::pair<std::string, std::string>> kvs; |
91 | | string docdb_debug_dump_str; |
92 | | }; |
93 | | |
94 | | class DocDBRocksDBFixture : public DocDBRocksDBUtil { |
95 | | public: |
96 | | void AssertDocDbDebugDumpStrEq(const string &expected); |
97 | | void FullyCompactHistoryBefore(HybridTime history_cutoff); |
98 | | |
99 | | // num_files_to_compact - number of files that should participate in the minor compaction |
100 | | // start_index - the index of the file to start with (0 = the oldest file, -1 = compact |
101 | | // num_files_to_compact newest files). |
102 | | void MinorCompaction( |
103 | | HybridTime history_cutoff, size_t num_files_to_compact, ssize_t start_index = -1); |
104 | | |
105 | | size_t NumSSTableFiles(); |
106 | | StringVector SSTableFileNames(); |
107 | | |
108 | | CHECKED_STATUS InitRocksDBDir() override; |
109 | | CHECKED_STATUS InitRocksDBOptions() override; |
110 | | TabletId tablet_id() override; |
111 | | CHECKED_STATUS FormatDocWriteBatch(const DocWriteBatch& dwb, std::string* dwb_str); |
112 | | }; |
113 | | |
114 | | // Perform a major compaction on the given database. |
115 | | CHECKED_STATUS FullyCompactDB(rocksdb::DB* rocksdb); |
116 | | |
117 | | class DocDBLoadGenerator { |
118 | | public: |
119 | | static constexpr uint64_t kDefaultRandomSeed = 23874297385L; |
120 | | |
121 | | DocDBLoadGenerator(DocDBRocksDBFixture* fixture, |
122 | | int num_doc_keys, |
123 | | int num_unique_subkeys, |
124 | | UseHash use_hash, |
125 | | ResolveIntentsDuringRead resolve_intents = ResolveIntentsDuringRead::kTrue, |
126 | | int deletion_chance = 100, |
127 | | int max_nesting_level = 10, |
128 | | uint64 random_seed = kDefaultRandomSeed, |
129 | | int verification_frequency = 100); |
130 | | ~DocDBLoadGenerator(); |
131 | | |
132 | | // Performs a random DocDB operation according to the configured options. This also verifies |
133 | | // the consistency of RocksDB-backed DocDB (which is close to the production codepath) with an |
134 | | // in-memory non-thread-safe data structure maintained just for sanity checking. Such verification |
135 | | // is only performed from time to time, not on every call to PerformOperation. |
136 | | // |
137 | | // The caller should wrap calls to this function in NO_FATALS. |
138 | | // |
139 | | // @param compact_history If this is set, we perform the RocksDB-backed DocDB read before and |
140 | | // after history cleanup, and verify that the state of the document is |
141 | | // the same in both cases. |
142 | | void PerformOperation(bool compact_history = false); |
143 | | |
144 | | // @return The next "iteration number" to be performed when PerformOperation is called. |
145 | 0 | int next_iteration() const { return iteration_; } |
146 | | |
147 | | // The hybrid_time of the last operation performed is always based on the last iteration number. |
148 | | // Most times it will be one less than what next_iteration() would return, if we convert the |
149 | | // hybrid_time to an integer. This can only be called after PerformOperation() has been called at |
150 | | // least once. |
151 | | // |
152 | | // @return The hybrid_time of the last operation performed. |
153 | | HybridTime last_operation_ht() const; |
154 | | |
155 | | void FlushRocksDB(); |
156 | | |
157 | | // Generate a random unsiged 64-bit integer using the random number generator maintained by this |
158 | | // object. |
159 | 0 | uint64_t NextRandom() { return random_(); } |
160 | | |
161 | | // Generate a random integer from 0 to n - 1 using the random number generator maintained by this |
162 | | // object. |
163 | 0 | int NextRandomInt(int n) { return NextRandom() % n; } |
164 | | |
165 | | // Capture and remember a "logical DocDB snapshot" (not to be confused with what we call |
166 | | // a "logical RocksDB snapshot"). This keeps track of all document keys and corresponding |
167 | | // documents existing at the latest hybrid_time. |
168 | | void CaptureDocDbSnapshot(); |
169 | | |
170 | | void VerifyOldestSnapshot(); |
171 | | void VerifyRandomDocDbSnapshot(); |
172 | | |
173 | | // Perform a flashback query at the time of the latest snapshot before the given cleanup |
174 | | // hybrid_time and compare it to the state recorded with the snapshot. Expect the two to diverge |
175 | | // using ASSERT_TRUE. This is used for testing that old history is actually being cleaned up |
176 | | // during compactions. |
177 | | void CheckIfOldestSnapshotIsStillValid(const HybridTime cleanup_ht); |
178 | | |
179 | | // Removes all snapshots taken before the given hybrid_time. This is done to test history cleanup. |
180 | | void RemoveSnapshotsBefore(HybridTime ht); |
181 | | |
182 | 0 | size_t num_divergent_old_snapshot() { return divergent_snapshot_ht_and_cleanup_ht_.size(); } |
183 | | |
184 | 0 | std::vector<std::pair<int, int>> divergent_snapshot_ht_and_cleanup_ht() { |
185 | 0 | return divergent_snapshot_ht_and_cleanup_ht_; |
186 | 0 | } |
187 | | |
188 | | private: |
189 | 0 | rocksdb::DB* rocksdb() { return fixture_->rocksdb(); } |
190 | 0 | DocDB doc_db() { return fixture_->doc_db(); } |
191 | | |
192 | | DocDBRocksDBFixture* fixture_; |
193 | | RandomNumberGenerator random_; // Using default seed. |
194 | | std::vector<DocKey> doc_keys_; |
195 | | |
196 | | // Whether we should pass transaction context during reads, so DocDB tries to resolve write |
197 | | // intents. |
198 | | const ResolveIntentsDuringRead resolve_intents_; |
199 | | |
200 | | std::vector<PrimitiveValue> possible_subkeys_; |
201 | | int iteration_; |
202 | | InMemDocDbState in_mem_docdb_; |
203 | | |
204 | | // Deletions happen once every this number of iterations. |
205 | | const int deletion_chance_; |
206 | | |
207 | | // If this is 1, we'll only use primitive-type documents. If this is 2, we'll make some documents |
208 | | // objects (maps). If this is 3, we'll use maps of maps, etc. |
209 | | const int max_nesting_level_; |
210 | | |
211 | | HybridTime last_operation_ht_; |
212 | | |
213 | | std::vector<InMemDocDbState> docdb_snapshots_; |
214 | | |
215 | | // HybridTimes and cleanup hybrid_times of examples when |
216 | | std::vector<std::pair<int, int>> divergent_snapshot_ht_and_cleanup_ht_; |
217 | | |
218 | | // PerformOperation() will verify DocDB state consistency once in this number of iterations. |
219 | | const int verification_frequency_; |
220 | | |
221 | | const InMemDocDbState& GetOldestSnapshot(); |
222 | | |
223 | | // Perform a "flashback query" in the RocksDB-based DocDB at the hybrid_time |
224 | | // snapshot.captured_at() and verify that the state matches what's in the provided snapshot. |
225 | | // This is only invoked on snapshots whose capture hybrid_time has not been garbage-collected, |
226 | | // and therefore we always expect this verification to succeed. |
227 | | // |
228 | | // Calls to this function should be wrapped in NO_FATALS. |
229 | | void VerifySnapshot(const InMemDocDbState& snapshot); |
230 | | |
231 | | // Look at whether the given snapshot is still valid, and if not, track it in |
232 | | // divergent_snapshot_ht_and_cleanup_ht_, so we can later verify that some snapshots have become |
233 | | // invalid after history cleanup. |
234 | | void RecordSnapshotDivergence(const InMemDocDbState &snapshot, HybridTime cleanup_ht); |
235 | | |
236 | | TransactionOperationContext GetReadOperationTransactionContext(); |
237 | | }; |
238 | | |
239 | | // Used for pre-processing multi-line DocDB debug dump strings in tests. Removes common indentation |
240 | | // and C++-style comments and applies backslash line continuation. |
241 | | string TrimDocDbDebugDumpStr(const string& debug_dump); |
242 | | |
243 | | #define ASSERT_DOCDB_DEBUG_DUMP_STR_EQ(expected) \ |
244 | 0 | do { \ |
245 | 0 | ASSERT_STR_EQ_VERBOSE_TRIMMED( \ |
246 | 0 | ::yb::util::ApplyEagerLineContinuation(expected), DocDBDebugDumpToStr()); \ |
247 | 0 | } while(false) |
248 | | |
249 | | } // namespace docdb |
250 | | } // namespace yb |
251 | | |
252 | | #endif // YB_DOCDB_DOCDB_TEST_UTIL_H_ |