/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 | | ValueRef GenRandomPrimitiveValue(RandomNumberGenerator* rng, QLValuePB* holder); |
60 | | |
61 | | // Generate a random sequence of primitive values. |
62 | | std::vector<PrimitiveValue> GenRandomPrimitiveValues(RandomNumberGenerator* rng, |
63 | | int max_num = kMaxNumRandomDocKeyParts); |
64 | | |
65 | | // Generate a "minimal" DocKey. |
66 | | DocKey CreateMinimalDocKey(RandomNumberGenerator* rng, UseHash use_hash); |
67 | | |
68 | | // Generate a random DocKey with up to the default number of components. |
69 | | DocKey GenRandomDocKey(RandomNumberGenerator* rng, UseHash use_hash); |
70 | | |
71 | | std::vector<DocKey> GenRandomDocKeys(RandomNumberGenerator* rng, UseHash use_hash, int num_keys); |
72 | | |
73 | | std::vector<SubDocKey> GenRandomSubDocKeys(RandomNumberGenerator* rng, |
74 | | UseHash use_hash, |
75 | | int num_keys); |
76 | | |
77 | | template<typename T> |
78 | 716k | const T& RandomElementOf(const std::vector<T>& v, RandomNumberGenerator* rng) { |
79 | 716k | return v[(*rng)() % v.size()]; |
80 | 716k | } yb::docdb::DocKey const& yb::docdb::RandomElementOf<yb::docdb::DocKey>(std::__1::vector<yb::docdb::DocKey, std::__1::allocator<yb::docdb::DocKey> > const&, std::__1::mersenne_twister_engine<unsigned long long, 64ul, 312ul, 156ul, 31ul, 13043109905998158313ull, 29ul, 6148914691236517205ull, 17ul, 8202884508482404352ull, 37ul, 18444473444759240704ull, 43ul, 6364136223846793005ull>*) Line | Count | Source | 78 | 228k | const T& RandomElementOf(const std::vector<T>& v, RandomNumberGenerator* rng) { | 79 | 228k | return v[(*rng)() % v.size()]; | 80 | 228k | } |
yb::docdb::PrimitiveValue const& yb::docdb::RandomElementOf<yb::docdb::PrimitiveValue>(std::__1::vector<yb::docdb::PrimitiveValue, std::__1::allocator<yb::docdb::PrimitiveValue> > const&, std::__1::mersenne_twister_engine<unsigned long long, 64ul, 312ul, 156ul, 31ul, 13043109905998158313ull, 29ul, 6148914691236517205ull, 17ul, 8202884508482404352ull, 37ul, 18444473444759240704ull, 43ul, 6364136223846793005ull>*) Line | Count | Source | 78 | 488k | const T& RandomElementOf(const std::vector<T>& v, RandomNumberGenerator* rng) { | 79 | 488k | return v[(*rng)() % v.size()]; | 80 | 488k | } |
|
81 | | |
82 | | // Represents a full logical snapshot of a RocksDB instance. An instance of this class will record |
83 | | // the state of a RocksDB instance via Capture, which can then be written to a new RocksDB instance |
84 | | // via RestoreTo. |
85 | | class LogicalRocksDBDebugSnapshot { |
86 | | public: |
87 | 14 | LogicalRocksDBDebugSnapshot() {} |
88 | | void Capture(rocksdb::DB* rocksdb); |
89 | | void RestoreTo(rocksdb::DB *rocksdb) const; |
90 | | private: |
91 | | std::vector<std::pair<std::string, std::string>> kvs; |
92 | | string docdb_debug_dump_str; |
93 | | }; |
94 | | |
95 | | class DocDBRocksDBFixture : public DocDBRocksDBUtil { |
96 | | public: |
97 | | void AssertDocDbDebugDumpStrEq(const string &expected); |
98 | | void FullyCompactHistoryBefore(HybridTime history_cutoff); |
99 | | |
100 | | // num_files_to_compact - number of files that should participate in the minor compaction |
101 | | // start_index - the index of the file to start with (0 = the oldest file, -1 = compact |
102 | | // num_files_to_compact newest files). |
103 | | void MinorCompaction( |
104 | | HybridTime history_cutoff, size_t num_files_to_compact, ssize_t start_index = -1); |
105 | | |
106 | | size_t NumSSTableFiles(); |
107 | | StringVector SSTableFileNames(); |
108 | | |
109 | | CHECKED_STATUS InitRocksDBDir() override; |
110 | | CHECKED_STATUS InitRocksDBOptions() override; |
111 | | TabletId tablet_id() override; |
112 | | CHECKED_STATUS FormatDocWriteBatch(const DocWriteBatch& dwb, std::string* dwb_str); |
113 | | }; |
114 | | |
115 | | // Perform a major compaction on the given database. |
116 | | CHECKED_STATUS FullyCompactDB(rocksdb::DB* rocksdb); |
117 | | |
118 | | class DocDBLoadGenerator { |
119 | | public: |
120 | | static constexpr uint64_t kDefaultRandomSeed = 23874297385L; |
121 | | |
122 | | DocDBLoadGenerator(DocDBRocksDBFixture* fixture, |
123 | | int num_doc_keys, |
124 | | int num_unique_subkeys, |
125 | | UseHash use_hash, |
126 | | ResolveIntentsDuringRead resolve_intents = ResolveIntentsDuringRead::kTrue, |
127 | | int deletion_chance = 100, |
128 | | int max_nesting_level = 10, |
129 | | uint64 random_seed = kDefaultRandomSeed, |
130 | | int verification_frequency = 100); |
131 | | ~DocDBLoadGenerator(); |
132 | | |
133 | | // Performs a random DocDB operation according to the configured options. This also verifies |
134 | | // the consistency of RocksDB-backed DocDB (which is close to the production codepath) with an |
135 | | // in-memory non-thread-safe data structure maintained just for sanity checking. Such verification |
136 | | // is only performed from time to time, not on every call to PerformOperation. |
137 | | // |
138 | | // The caller should wrap calls to this function in NO_FATALS. |
139 | | // |
140 | | // @param compact_history If this is set, we perform the RocksDB-backed DocDB read before and |
141 | | // after history cleanup, and verify that the state of the document is |
142 | | // the same in both cases. |
143 | | void PerformOperation(bool compact_history = false); |
144 | | |
145 | | // @return The next "iteration number" to be performed when PerformOperation is called. |
146 | | int next_iteration() const { return iteration_; } |
147 | | |
148 | | // The hybrid_time of the last operation performed is always based on the last iteration number. |
149 | | // Most times it will be one less than what next_iteration() would return, if we convert the |
150 | | // hybrid_time to an integer. This can only be called after PerformOperation() has been called at |
151 | | // least once. |
152 | | // |
153 | | // @return The hybrid_time of the last operation performed. |
154 | | HybridTime last_operation_ht() const; |
155 | | |
156 | | void FlushRocksDB(); |
157 | | |
158 | | // Generate a random unsiged 64-bit integer using the random number generator maintained by this |
159 | | // object. |
160 | 47.8k | uint64_t NextRandom() { return random_(); } |
161 | | |
162 | | // Generate a random integer from 0 to n - 1 using the random number generator maintained by this |
163 | | // object. |
164 | 47.7k | int NextRandomInt(int n) { return NextRandom() % n; } |
165 | | |
166 | | // Capture and remember a "logical DocDB snapshot" (not to be confused with what we call |
167 | | // a "logical RocksDB snapshot"). This keeps track of all document keys and corresponding |
168 | | // documents existing at the latest hybrid_time. |
169 | | void CaptureDocDbSnapshot(); |
170 | | |
171 | | void VerifyOldestSnapshot(); |
172 | | void VerifyRandomDocDbSnapshot(); |
173 | | |
174 | | // Perform a flashback query at the time of the latest snapshot before the given cleanup |
175 | | // hybrid_time and compare it to the state recorded with the snapshot. Expect the two to diverge |
176 | | // using ASSERT_TRUE. This is used for testing that old history is actually being cleaned up |
177 | | // during compactions. |
178 | | void CheckIfOldestSnapshotIsStillValid(const HybridTime cleanup_ht); |
179 | | |
180 | | // Removes all snapshots taken before the given hybrid_time. This is done to test history cleanup. |
181 | | void RemoveSnapshotsBefore(HybridTime ht); |
182 | | |
183 | | size_t num_divergent_old_snapshot() { return divergent_snapshot_ht_and_cleanup_ht_.size(); } |
184 | | |
185 | | std::vector<std::pair<int, int>> divergent_snapshot_ht_and_cleanup_ht() { |
186 | | return divergent_snapshot_ht_and_cleanup_ht_; |
187 | | } |
188 | | |
189 | | private: |
190 | 0 | rocksdb::DB* rocksdb() { return fixture_->rocksdb(); } |
191 | 234k | DocDB doc_db() { return fixture_->doc_db(); } |
192 | | |
193 | | DocDBRocksDBFixture* fixture_; |
194 | | RandomNumberGenerator random_; // Using default seed. |
195 | | std::vector<DocKey> doc_keys_; |
196 | | |
197 | | // Whether we should pass transaction context during reads, so DocDB tries to resolve write |
198 | | // intents. |
199 | | const ResolveIntentsDuringRead resolve_intents_; |
200 | | |
201 | | std::vector<PrimitiveValue> possible_subkeys_; |
202 | | int iteration_; |
203 | | InMemDocDbState in_mem_docdb_; |
204 | | |
205 | | // Deletions happen once every this number of iterations. |
206 | | const int deletion_chance_; |
207 | | |
208 | | // If this is 1, we'll only use primitive-type documents. If this is 2, we'll make some documents |
209 | | // objects (maps). If this is 3, we'll use maps of maps, etc. |
210 | | const int max_nesting_level_; |
211 | | |
212 | | HybridTime last_operation_ht_; |
213 | | |
214 | | std::vector<InMemDocDbState> docdb_snapshots_; |
215 | | |
216 | | // HybridTimes and cleanup hybrid_times of examples when |
217 | | std::vector<std::pair<int, int>> divergent_snapshot_ht_and_cleanup_ht_; |
218 | | |
219 | | // PerformOperation() will verify DocDB state consistency once in this number of iterations. |
220 | | const int verification_frequency_; |
221 | | |
222 | | const InMemDocDbState& GetOldestSnapshot(); |
223 | | |
224 | | // Perform a "flashback query" in the RocksDB-based DocDB at the hybrid_time |
225 | | // snapshot.captured_at() and verify that the state matches what's in the provided snapshot. |
226 | | // This is only invoked on snapshots whose capture hybrid_time has not been garbage-collected, |
227 | | // and therefore we always expect this verification to succeed. |
228 | | // |
229 | | // Calls to this function should be wrapped in NO_FATALS. |
230 | | void VerifySnapshot(const InMemDocDbState& snapshot); |
231 | | |
232 | | // Look at whether the given snapshot is still valid, and if not, track it in |
233 | | // divergent_snapshot_ht_and_cleanup_ht_, so we can later verify that some snapshots have become |
234 | | // invalid after history cleanup. |
235 | | void RecordSnapshotDivergence(const InMemDocDbState &snapshot, HybridTime cleanup_ht); |
236 | | |
237 | | TransactionOperationContext GetReadOperationTransactionContext(); |
238 | | }; |
239 | | |
240 | | // Used for pre-processing multi-line DocDB debug dump strings in tests. Removes common indentation |
241 | | // and C++-style comments and applies backslash line continuation. |
242 | | string TrimDocDbDebugDumpStr(const string& debug_dump); |
243 | | |
244 | | #define ASSERT_DOCDB_DEBUG_DUMP_STR_EQ(expected) \ |
245 | | do { \ |
246 | | ASSERT_STR_EQ_VERBOSE_TRIMMED( \ |
247 | | ::yb::util::ApplyEagerLineContinuation(expected), DocDBDebugDumpToStr()); \ |
248 | | } while(false) |
249 | | |
250 | | } // namespace docdb |
251 | | } // namespace yb |
252 | | |
253 | | #endif // YB_DOCDB_DOCDB_TEST_UTIL_H_ |