YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

/Users/deen/code/yugabyte-db/src/yb/docdb/docdb_test_util.cc
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
#include "yb/docdb/docdb_test_util.h"
15
16
#include <algorithm>
17
#include <memory>
18
#include <sstream>
19
20
#include "yb/common/hybrid_time.h"
21
22
#include "yb/docdb/doc_key.h"
23
#include "yb/docdb/doc_reader.h"
24
#include "yb/docdb/docdb-internal.h"
25
#include "yb/docdb/docdb.h"
26
#include "yb/docdb/docdb_compaction_filter.h"
27
#include "yb/docdb/docdb_debug.h"
28
#include "yb/docdb/in_mem_docdb.h"
29
30
#include "yb/gutil/strings/substitute.h"
31
32
#include "yb/rocksdb/db/filename.h"
33
34
#include "yb/rocksutil/write_batch_formatter.h"
35
36
#include "yb/util/bytes_formatter.h"
37
#include "yb/util/env.h"
38
#include "yb/util/path_util.h"
39
#include "yb/util/scope_exit.h"
40
#include "yb/util/status.h"
41
#include "yb/util/string_trim.h"
42
#include "yb/util/test_macros.h"
43
#include "yb/util/tostring.h"
44
45
using std::endl;
46
using std::make_shared;
47
using std::string;
48
using std::unique_ptr;
49
using std::vector;
50
using std::stringstream;
51
52
using strings::Substitute;
53
54
using yb::util::ApplyEagerLineContinuation;
55
using yb::FormatBytesAsStr;
56
using yb::util::TrimStr;
57
using yb::util::LeftShiftTextBlock;
58
using yb::util::TrimCppComments;
59
60
namespace yb {
61
namespace docdb {
62
63
namespace {
64
65
class NonTransactionalStatusProvider: public TransactionStatusManager {
66
 public:
67
0
  HybridTime LocalCommitTime(const TransactionId& id) override {
68
0
    Fail();
69
0
    return HybridTime::kInvalid;
70
0
  }
71
72
0
  boost::optional<CommitMetadata> LocalCommitData(const TransactionId& id) override {
73
0
    Fail();
74
0
    return boost::none;
75
0
  }
76
77
0
  void RequestStatusAt(const StatusRequest& request) override {
78
0
    Fail();
79
0
  }
80
81
0
  Result<TransactionMetadata> PrepareMetadata(const TransactionMetadataPB& pb) override {
82
0
    Fail();
83
0
    return STATUS(Expired, "");
84
0
  }
85
86
0
  int64_t RegisterRequest() override {
87
0
    Fail();
88
0
    return 0;
89
0
  }
90
91
0
  void UnregisterRequest(int64_t) override {
92
0
    Fail();
93
0
  }
94
95
0
  void Abort(const TransactionId& id, TransactionStatusCallback callback) override {
96
0
    Fail();
97
0
  }
98
99
0
  void Cleanup(TransactionIdSet&& set) override {
100
0
    Fail();
101
0
  }
102
103
  void FillPriorities(
104
0
      boost::container::small_vector_base<std::pair<TransactionId, uint64_t>>* inout) override {
105
0
    Fail();
106
0
  }
107
108
0
  HybridTime MinRunningHybridTime() const override {
109
0
    return HybridTime::kMax;
110
0
  }
111
112
0
  Result<HybridTime> WaitForSafeTime(HybridTime safe_time, CoarseTimePoint deadline) override {
113
0
    return STATUS(NotSupported, "WaitForSafeTime not implemented");
114
0
  }
115
116
0
  const TabletId& tablet_id() const override {
117
0
    static TabletId result;
118
0
    return result;
119
0
  }
120
121
 private:
122
0
  static void Fail() {
123
0
    LOG(FATAL) << "Internal error: trying to get transaction status for non transactional table";
124
0
  }
125
};
126
127
NonTransactionalStatusProvider kNonTransactionalStatusProvider;
128
129
} // namespace
130
131
const TransactionOperationContext kNonTransactionalOperationContext = {
132
    TransactionId::Nil(), &kNonTransactionalStatusProvider
133
};
134
135
0
PrimitiveValue GenRandomPrimitiveValue(RandomNumberGenerator* rng) {
136
0
  static vector<string> kFruit = {
137
0
      "Apple",
138
0
      "Apricot",
139
0
      "Avocado",
140
0
      "Banana",
141
0
      "Bilberry",
142
0
      "Blackberry",
143
0
      "Blackcurrant",
144
0
      "Blood orange",
145
0
      "Blueberry",
146
0
      "Boysenberry",
147
0
      "Cantaloupe",
148
0
      "Cherimoya",
149
0
      "Cherry",
150
0
      "Clementine",
151
0
      "Cloudberry",
152
0
      "Coconut",
153
0
      "Cranberry",
154
0
      "Cucumber",
155
0
      "Currant",
156
0
      "Custard apple",
157
0
      "Damson",
158
0
      "Date",
159
0
      "Decaisnea Fargesii",
160
0
      "Dragonfruit",
161
0
      "Durian",
162
0
      "Elderberry",
163
0
      "Feijoa",
164
0
      "Fig",
165
0
      "Goji berry",
166
0
      "Gooseberry",
167
0
      "Grape",
168
0
      "Grapefruit",
169
0
      "Guava",
170
0
      "Honeyberry",
171
0
      "Honeydew",
172
0
      "Huckleberry",
173
0
      "Jabuticaba",
174
0
      "Jackfruit",
175
0
      "Jambul",
176
0
      "Jujube",
177
0
      "Juniper berry",
178
0
      "Kiwifruit",
179
0
      "Kumquat",
180
0
      "Lemon",
181
0
      "Lime",
182
0
      "Longan",
183
0
      "Loquat",
184
0
      "Lychee",
185
0
      "Mandarine",
186
0
      "Mango",
187
0
      "Marionberry",
188
0
      "Melon",
189
0
      "Miracle fruit",
190
0
      "Mulberry",
191
0
      "Nance",
192
0
      "Nectarine",
193
0
      "Olive",
194
0
      "Orange",
195
0
      "Papaya",
196
0
      "Passionfruit",
197
0
      "Peach",
198
0
      "Pear",
199
0
      "Persimmon",
200
0
      "Physalis",
201
0
      "Pineapple",
202
0
      "Plantain",
203
0
      "Plum",
204
0
      "Plumcot (or Pluot)",
205
0
      "Pomegranate",
206
0
      "Pomelo",
207
0
      "Prune (dried plum)",
208
0
      "Purple mangosteen",
209
0
      "Quince",
210
0
      "Raisin",
211
0
      "Rambutan",
212
0
      "Raspberry",
213
0
      "Redcurrant",
214
0
      "Salak",
215
0
      "Salal berry",
216
0
      "Salmonberry",
217
0
      "Satsuma",
218
0
      "Star fruit",
219
0
      "Strawberry",
220
0
      "Tamarillo",
221
0
      "Tamarind",
222
0
      "Tangerine",
223
0
      "Tomato",
224
0
      "Ugli fruit",
225
0
      "Watermelon",
226
0
      "Yuzu"
227
0
  };
228
0
  switch ((*rng)() % 6) {
229
0
    case 0:
230
0
      return PrimitiveValue(static_cast<int64_t>((*rng)()));
231
0
    case 1: {
232
0
      string s;
233
0
      for (size_t j = 0; j < (*rng)() % 50; ++j) {
234
0
        s.push_back((*rng)() & 0xff);
235
0
      }
236
0
      return PrimitiveValue(s);
237
0
    }
238
0
    case 2: return PrimitiveValue(ValueType::kNullLow);
239
0
    case 3: return PrimitiveValue(ValueType::kTrue);
240
0
    case 4: return PrimitiveValue(ValueType::kFalse);
241
0
    case 5: return PrimitiveValue(kFruit[(*rng)() % kFruit.size()]);
242
0
  }
243
0
  LOG(FATAL) << "Should never get here";
244
0
  return PrimitiveValue();  // to make the compiler happy
245
0
}
246
247
248
// Generate a vector of random primitive values.
249
0
vector<PrimitiveValue> GenRandomPrimitiveValues(RandomNumberGenerator* rng, int max_num) {
250
0
  vector<PrimitiveValue> result;
251
0
  for (size_t i = 0; i < (*rng)() % (max_num + 1); ++i) {
252
0
    result.push_back(GenRandomPrimitiveValue(rng));
253
0
  }
254
0
  return result;
255
0
}
256
257
0
DocKey CreateMinimalDocKey(RandomNumberGenerator* rng, UseHash use_hash) {
258
0
  return use_hash ? DocKey(static_cast<DocKeyHash>((*rng)()), std::vector<PrimitiveValue>(),
259
0
      std::vector<PrimitiveValue>()) : DocKey();
260
0
}
261
262
0
DocKey GenRandomDocKey(RandomNumberGenerator* rng, UseHash use_hash) {
263
0
  if (use_hash) {
264
0
    return DocKey(
265
0
        static_cast<uint32_t>((*rng)()),  // this is just a random value, not a hash function result
266
0
        GenRandomPrimitiveValues(rng),
267
0
        GenRandomPrimitiveValues(rng));
268
0
  } else {
269
0
    return DocKey(GenRandomPrimitiveValues(rng));
270
0
  }
271
0
}
272
273
0
vector<DocKey> GenRandomDocKeys(RandomNumberGenerator* rng, UseHash use_hash, int num_keys) {
274
0
  vector<DocKey> result;
275
0
  result.push_back(CreateMinimalDocKey(rng, use_hash));
276
0
  for (int iteration = 0; iteration < num_keys; ++iteration) {
277
0
    result.push_back(GenRandomDocKey(rng, use_hash));
278
0
  }
279
0
  return result;
280
0
}
281
282
0
vector<SubDocKey> GenRandomSubDocKeys(RandomNumberGenerator* rng, UseHash use_hash, int num_keys) {
283
0
  vector<SubDocKey> result;
284
0
  result.push_back(SubDocKey(CreateMinimalDocKey(rng, use_hash), HybridTime((*rng)())));
285
0
  for (int iteration = 0; iteration < num_keys; ++iteration) {
286
0
    result.push_back(SubDocKey(GenRandomDocKey(rng, use_hash)));
287
0
    for (size_t i = 0; i < (*rng)() % (kMaxNumRandomSubKeys + 1); ++i) {
288
0
      result.back().AppendSubKeysAndMaybeHybridTime(GenRandomPrimitiveValue(rng));
289
0
    }
290
0
    const IntraTxnWriteId write_id = static_cast<IntraTxnWriteId>(
291
0
        (*rng)() % 2 == 0 ? 0 : (*rng)() % 1000000);
292
0
    result.back().set_hybrid_time(DocHybridTime(HybridTime((*rng)()), write_id));
293
0
  }
294
0
  return result;
295
0
}
296
// ------------------------------------------------------------------------------------------------
297
298
0
void LogicalRocksDBDebugSnapshot::Capture(rocksdb::DB* rocksdb) {
299
0
  kvs.clear();
300
0
  rocksdb::ReadOptions read_options;
301
0
  auto iter = unique_ptr<rocksdb::Iterator>(rocksdb->NewIterator(read_options));
302
0
  iter->SeekToFirst();
303
0
  while (iter->Valid()) {
304
0
    kvs.emplace_back(iter->key().ToBuffer(), iter->value().ToBuffer());
305
0
    iter->Next();
306
0
  }
307
  // Save the DocDB debug dump as a string so we can check that we've properly restored the snapshot
308
  // in RestoreTo.
309
0
  docdb_debug_dump_str = DocDBDebugDumpToStr(rocksdb);
310
0
}
311
312
0
void LogicalRocksDBDebugSnapshot::RestoreTo(rocksdb::DB *rocksdb) const {
313
0
  rocksdb::ReadOptions read_options;
314
0
  rocksdb::WriteOptions write_options;
315
0
  auto iter = unique_ptr<rocksdb::Iterator>(rocksdb->NewIterator(read_options));
316
0
  iter->SeekToFirst();
317
0
  while (iter->Valid()) {
318
0
    ASSERT_OK(rocksdb->Delete(write_options, iter->key()));
319
0
    iter->Next();
320
0
  }
321
0
  for (const auto& kv : kvs) {
322
0
    ASSERT_OK(rocksdb->Put(write_options, kv.first, kv.second));
323
0
  }
324
0
  ASSERT_OK(FullyCompactDB(rocksdb));
325
0
  ASSERT_EQ(docdb_debug_dump_str, DocDBDebugDumpToStr(rocksdb));
326
0
}
327
328
// ------------------------------------------------------------------------------------------------
329
330
DocDBLoadGenerator::DocDBLoadGenerator(DocDBRocksDBFixture* fixture,
331
                                       const int num_doc_keys,
332
                                       const int num_unique_subkeys,
333
                                       const UseHash use_hash,
334
                                       const ResolveIntentsDuringRead resolve_intents,
335
                                       const int deletion_chance,
336
                                       const int max_nesting_level,
337
                                       const uint64 random_seed,
338
                                       const int verification_frequency)
339
    : fixture_(fixture),
340
      doc_keys_(GenRandomDocKeys(&random_, use_hash, num_doc_keys)),
341
      resolve_intents_(resolve_intents),
342
      possible_subkeys_(GenRandomPrimitiveValues(&random_, num_unique_subkeys)),
343
      iteration_(1),
344
      deletion_chance_(deletion_chance),
345
      max_nesting_level_(max_nesting_level),
346
0
      verification_frequency_(verification_frequency) {
347
0
  CHECK_GE(max_nesting_level_, 1);
348
  // Use a fixed seed so that tests are deterministic.
349
0
  random_.seed(random_seed);
350
351
  // This is done so we can use VerifySnapshot with in_mem_docdb_. That should preform a "latest"
352
  // read.
353
0
  in_mem_docdb_.SetCaptureHybridTime(HybridTime::kMax);
354
0
}
355
356
0
DocDBLoadGenerator::~DocDBLoadGenerator() = default;
357
358
0
void DocDBLoadGenerator::PerformOperation(bool compact_history) {
359
  // Increment the iteration right away so we can return from the function at any time.
360
0
  const int current_iteration = iteration_;
361
0
  ++iteration_;
362
363
0
  DOCDB_DEBUG_LOG("Starting iteration i=$0", current_iteration);
364
0
  auto dwb = fixture_->MakeDocWriteBatch();
365
0
  const auto& doc_key = RandomElementOf(doc_keys_, &random_);
366
0
  const KeyBytes encoded_doc_key(doc_key.Encode());
367
368
0
  const SubDocument* current_doc = in_mem_docdb_.GetDocument(doc_key);
369
370
0
  bool is_deletion = false;
371
0
  if (current_doc != nullptr &&
372
0
      current_doc->value_type() != ValueType::kObject) {
373
    // The entire document is not an object, let's delete it.
374
0
    is_deletion = true;
375
0
  }
376
377
0
  vector<PrimitiveValue> subkeys;
378
0
  if (!is_deletion) {
379
    // Add up to (max_nesting_level_ - 1) subkeys. Combined with the document key itself, this
380
    // gives us the desired maximum nesting level.
381
0
    for (size_t j = 0; j < random_() % max_nesting_level_; ++j) {
382
0
      if (current_doc != nullptr && current_doc->value_type() != ValueType::kObject) {
383
        // We can't add any more subkeys because we've found a primitive subdocument.
384
0
        break;
385
0
      }
386
0
      subkeys.emplace_back(RandomElementOf(possible_subkeys_, &random_));
387
0
      if (current_doc != nullptr) {
388
0
        current_doc = current_doc->GetChild(subkeys.back());
389
0
      }
390
0
    }
391
0
  }
392
393
0
  const DocPath doc_path(encoded_doc_key, subkeys);
394
0
  const auto value = GenRandomPrimitiveValue(&random_);
395
0
  const HybridTime hybrid_time(current_iteration);
396
0
  last_operation_ht_ = hybrid_time;
397
398
0
  if (random_() % deletion_chance_ == 0) {
399
0
    is_deletion = true;
400
0
  }
401
402
0
  const bool doc_already_exists_in_mem =
403
0
      in_mem_docdb_.GetDocument(doc_key) != nullptr;
404
405
0
  if (is_deletion) {
406
0
    DOCDB_DEBUG_LOG("Iteration $0: deleting doc path $1", current_iteration, doc_path.ToString());
407
0
    ASSERT_OK(dwb.DeleteSubDoc(doc_path, ReadHybridTime::Max()));
408
0
    ASSERT_OK(in_mem_docdb_.DeleteSubDoc(doc_path));
409
0
  } else {
410
0
    DOCDB_DEBUG_LOG("Iteration $0: setting value at doc path $1 to $2",
411
0
                    current_iteration, doc_path.ToString(), value.ToString());
412
0
    ASSERT_OK(in_mem_docdb_.SetPrimitive(doc_path, value));
413
0
    const auto set_primitive_status = dwb.SetPrimitive(doc_path, value);
414
0
    if (!set_primitive_status.ok()) {
415
0
      DocDBDebugDump(rocksdb(), std::cerr, StorageDbType::kRegular);
416
0
      LOG(INFO) << "doc_path=" << doc_path.ToString();
417
0
    }
418
0
    ASSERT_OK(set_primitive_status);
419
0
  }
420
421
  // We perform our randomly chosen operation first, both on the production version of DocDB
422
  // sitting on top of RocksDB, and on the in-memory single-threaded debug version used for
423
  // validation.
424
0
  ASSERT_OK(fixture_->WriteToRocksDB(dwb, hybrid_time));
425
0
  const SubDocument* const subdoc_from_mem = in_mem_docdb_.GetDocument(doc_key);
426
427
0
  TransactionOperationContext txn_op_context = GetReadOperationTransactionContext();
428
429
  // In case we are asked to compact history, we read the document from RocksDB before and after the
430
  // compaction, and expect to get the same result in both cases.
431
0
  for (int do_compaction_now = 0; do_compaction_now <= compact_history; ++do_compaction_now) {
432
0
    if (do_compaction_now) {
433
      // This will happen between the two iterations of the loop. If compact_history is false,
434
      // there is only one iteration and the compaction does not happen.
435
0
      fixture_->FullyCompactHistoryBefore(hybrid_time);
436
0
    }
437
0
    SubDocKey sub_doc_key(doc_key);
438
0
    auto encoded_sub_doc_key = sub_doc_key.EncodeWithoutHt();
439
0
    auto doc_from_rocksdb_opt = ASSERT_RESULT(TEST_GetSubDocument(
440
0
      encoded_sub_doc_key, doc_db(), rocksdb::kDefaultQueryId, txn_op_context,
441
0
      CoarseTimePoint::max() /* deadline */));
442
0
    if (is_deletion && (
443
0
            doc_path.num_subkeys() == 0 ||  // Deleted the entire sub-document,
444
0
            !doc_already_exists_in_mem)) {  // or the document did not exist in the first place.
445
      // In this case, after performing the deletion operation, we definitely should not see the
446
      // top-level document in RocksDB or in the in-memory database.
447
0
      ASSERT_FALSE(doc_from_rocksdb_opt);
448
0
      ASSERT_EQ(nullptr, subdoc_from_mem);
449
0
    } else {
450
      // This is not a deletion, or we've deleted a sub-key from a document, but the top-level
451
      // document should still be there in RocksDB.
452
0
      ASSERT_TRUE(doc_from_rocksdb_opt);
453
0
      ASSERT_NE(nullptr, subdoc_from_mem);
454
455
0
      ASSERT_EQ(*subdoc_from_mem, *doc_from_rocksdb_opt);
456
0
      DOCDB_DEBUG_LOG("Retrieved a document from RocksDB: $0", doc_from_rocksdb_opt->ToString());
457
0
      ASSERT_STR_EQ_VERBOSE_TRIMMED(subdoc_from_mem->ToString(), doc_from_rocksdb_opt->ToString());
458
0
    }
459
0
  }
460
461
0
  if (current_iteration % verification_frequency_ == 0) {
462
    // in_mem_docdb_ has its captured_at() hybrid_time set to HybridTime::kMax, so the following
463
    // will result in checking the latest state of DocDB stored in RocksDB against in_mem_docdb_.
464
0
    ASSERT_NO_FATALS(VerifySnapshot(in_mem_docdb_))
465
0
        << "Discrepancy between RocksDB-based and in-memory DocDB state found after iteration "
466
0
        << current_iteration;
467
0
  }
468
0
}
469
470
0
HybridTime DocDBLoadGenerator::last_operation_ht() const {
471
0
  CHECK(last_operation_ht_.is_valid());
472
0
  return last_operation_ht_;
473
0
}
474
475
0
void DocDBLoadGenerator::FlushRocksDB() {
476
0
  LOG(INFO) << "Forcing a RocksDB flush after hybrid_time " << last_operation_ht().value();
477
0
  ASSERT_OK(fixture_->FlushRocksDbAndWait());
478
0
}
479
480
0
void DocDBLoadGenerator::CaptureDocDbSnapshot() {
481
  // Capture snapshots from time to time.
482
0
  docdb_snapshots_.emplace_back();
483
0
  docdb_snapshots_.back().CaptureAt(doc_db(), HybridTime::kMax);
484
0
  docdb_snapshots_.back().SetCaptureHybridTime(last_operation_ht_);
485
0
}
486
487
0
void DocDBLoadGenerator::VerifyOldestSnapshot() {
488
0
  if (!docdb_snapshots_.empty()) {
489
0
    ASSERT_NO_FATALS(VerifySnapshot(GetOldestSnapshot()));
490
0
  }
491
0
}
492
493
0
void DocDBLoadGenerator::CheckIfOldestSnapshotIsStillValid(const HybridTime cleanup_ht) {
494
0
  if (docdb_snapshots_.empty()) {
495
0
    return;
496
0
  }
497
498
0
  const InMemDocDbState* latest_snapshot_before_ht = nullptr;
499
0
  for (const auto& snapshot : docdb_snapshots_) {
500
0
    const HybridTime snap_ht = snapshot.captured_at();
501
0
    if (snap_ht.CompareTo(cleanup_ht) < 0 &&
502
0
        (latest_snapshot_before_ht == nullptr ||
503
0
         latest_snapshot_before_ht->captured_at().CompareTo(snap_ht) < 0)) {
504
0
      latest_snapshot_before_ht = &snapshot;
505
0
    }
506
0
  }
507
508
0
  if (latest_snapshot_before_ht == nullptr) {
509
0
    return;
510
0
  }
511
512
0
  const auto& snapshot = *latest_snapshot_before_ht;
513
0
  LOG(INFO) << "Checking whether snapshot at hybrid_time "
514
0
            << snapshot.captured_at().ToDebugString()
515
0
            << " is no longer valid after history cleanup for hybrid_times before "
516
0
            << cleanup_ht.ToDebugString()
517
0
            << ", last operation hybrid_time: " << last_operation_ht() << ".";
518
0
  RecordSnapshotDivergence(snapshot, cleanup_ht);
519
0
}
520
521
0
void DocDBLoadGenerator::VerifyRandomDocDbSnapshot() {
522
0
  if (!docdb_snapshots_.empty()) {
523
0
    const int snapshot_idx = NextRandomInt(narrow_cast<int>(docdb_snapshots_.size()));
524
0
    ASSERT_NO_FATALS(VerifySnapshot(docdb_snapshots_[snapshot_idx]));
525
0
  }
526
0
}
527
528
0
void DocDBLoadGenerator::RemoveSnapshotsBefore(HybridTime ht) {
529
0
  docdb_snapshots_.erase(
530
0
      std::remove_if(docdb_snapshots_.begin(),
531
0
                     docdb_snapshots_.end(),
532
0
                     [=](const InMemDocDbState& entry) { return entry.captured_at() < ht; }),
533
0
      docdb_snapshots_.end());
534
  // Double-check that there is no state corruption in any of the snapshots. Such corruption
535
  // happened when I (Mikhail) initially forgot to add the "erase" call above (as per the
536
  // "erase/remove" C++ idiom), and ended up with a bunch of moved-from objects still in the
537
  // snapshots array.
538
0
  for (const auto& snapshot : docdb_snapshots_) {
539
0
    snapshot.SanityCheck();
540
0
  }
541
0
}
542
543
0
const InMemDocDbState& DocDBLoadGenerator::GetOldestSnapshot() {
544
0
  CHECK(!docdb_snapshots_.empty());
545
0
  return *std::min_element(
546
0
      docdb_snapshots_.begin(),
547
0
      docdb_snapshots_.end(),
548
0
      [](const InMemDocDbState& a, const InMemDocDbState& b) {
549
0
        return a.captured_at() < b.captured_at();
550
0
      });
551
0
}
552
553
0
void DocDBLoadGenerator::VerifySnapshot(const InMemDocDbState& snapshot) {
554
0
  const HybridTime snap_ht = snapshot.captured_at();
555
0
  InMemDocDbState flashback_state;
556
557
0
  string details_msg;
558
0
  {
559
0
    stringstream details_ss;
560
0
    details_ss << "After operation at hybrid_time " << last_operation_ht().value() << ": "
561
0
        << "performing a flashback query at hybrid_time " << snap_ht.ToDebugString() << " "
562
0
        << "(last operation's hybrid_time: " << last_operation_ht() << ") "
563
0
        << "and verifying it against the snapshot captured at that hybrid_time.";
564
0
    details_msg = details_ss.str();
565
0
  }
566
0
  LOG(INFO) << details_msg;
567
568
0
  flashback_state.CaptureAt(doc_db(), snap_ht);
569
0
  const bool is_match = flashback_state.EqualsAndLogDiff(snapshot);
570
0
  if (!is_match) {
571
0
    LOG(ERROR) << details_msg << "\nDOCDB SNAPSHOT VERIFICATION FAILED, DOCDB STATE:";
572
0
    fixture_->DocDBDebugDumpToConsole();
573
0
  }
574
0
  ASSERT_TRUE(is_match) << details_msg;
575
0
}
576
577
void DocDBLoadGenerator::RecordSnapshotDivergence(const InMemDocDbState &snapshot,
578
0
                                                  const HybridTime cleanup_ht) {
579
0
  InMemDocDbState flashback_state;
580
0
  const auto snap_ht = snapshot.captured_at();
581
0
  flashback_state.CaptureAt(doc_db(), snap_ht);
582
0
  if (!flashback_state.EqualsAndLogDiff(snapshot, /* log_diff = */ false)) {
583
    // Implicitly converting hybrid_times to ints. That's OK, because we're using small enough
584
    // integer values for hybrid_times.
585
0
    divergent_snapshot_ht_and_cleanup_ht_.emplace_back(snapshot.captured_at().value(),
586
0
                                                       cleanup_ht.value());
587
0
  }
588
0
}
589
590
0
TransactionOperationContext DocDBLoadGenerator::GetReadOperationTransactionContext() {
591
0
  if (resolve_intents_) {
592
0
    return kNonTransactionalOperationContext;
593
0
  }
594
0
  return TransactionOperationContext();
595
0
}
596
597
// ------------------------------------------------------------------------------------------------
598
599
0
void DocDBRocksDBFixture::AssertDocDbDebugDumpStrEq(const string &expected) {
600
0
  const string debug_dump_str = TrimDocDbDebugDumpStr(DocDBDebugDumpToStr());
601
0
  const string expected_str = TrimDocDbDebugDumpStr(expected);
602
0
  if (expected_str != debug_dump_str) {
603
0
    auto expected_lines = StringSplit(expected_str, '\n');
604
0
    auto actual_lines = StringSplit(debug_dump_str, '\n');
605
0
    vector<size_t> mismatch_line_numbers;
606
0
    for (size_t i = 0; i < std::min(expected_lines.size(), actual_lines.size()); ++i) {
607
0
      if (expected_lines[i] != actual_lines[i]) {
608
0
        mismatch_line_numbers.push_back(i + 1);
609
0
      }
610
0
    }
611
0
    LOG(ERROR) << "Assertion failure"
612
0
               << "\nExpected DocDB contents:\n\n" << expected_str << "\n"
613
0
               << "\nActual DocDB contents:\n\n" << debug_dump_str << "\n"
614
0
               << "\nExpected # of lines: " << expected_lines.size()
615
0
               << ", actual # of lines: " << actual_lines.size()
616
0
               << "\nLines not matching: " << AsString(mismatch_line_numbers)
617
0
               << "\nPlease check if source files have trailing whitespace and remove it.";
618
619
0
    FAIL();
620
0
  }
621
0
}
622
623
0
void DocDBRocksDBFixture::FullyCompactHistoryBefore(HybridTime history_cutoff) {
624
0
  LOG(INFO) << "Major-compacting history before hybrid_time " << history_cutoff;
625
0
  SetHistoryCutoffHybridTime(history_cutoff);
626
0
  auto se = ScopeExit([this] {
627
0
    SetHistoryCutoffHybridTime(HybridTime::kMin);
628
0
  });
629
630
0
  ASSERT_OK(FlushRocksDbAndWait());
631
0
  ASSERT_OK(FullyCompactDB(regular_db_.get()));
632
0
}
633
634
void DocDBRocksDBFixture::MinorCompaction(
635
    HybridTime history_cutoff,
636
    size_t num_files_to_compact,
637
0
    ssize_t start_index) {
638
639
0
  ASSERT_OK(FlushRocksDbAndWait());
640
0
  SetHistoryCutoffHybridTime(history_cutoff);
641
0
  auto se = ScopeExit([this] {
642
0
    SetHistoryCutoffHybridTime(HybridTime::kMin);
643
0
  });
644
645
0
  rocksdb::ColumnFamilyMetaData cf_meta;
646
0
  regular_db_->GetColumnFamilyMetaData(&cf_meta);
647
648
0
  vector<string> compaction_input_file_names;
649
0
  vector<string> remaining_file_names;
650
651
0
  size_t initial_num_files = 0;
652
0
  {
653
0
    const auto& files = cf_meta.levels[0].files;
654
0
    initial_num_files = files.size();
655
0
    ASSERT_LE(num_files_to_compact, files.size());
656
0
    vector<string> file_names;
657
0
    for (const auto& sst_meta : files) {
658
0
      file_names.push_back(sst_meta.name);
659
0
    }
660
0
    SortByKey(file_names.begin(), file_names.end(), rocksdb::TableFileNameToNumber);
661
662
0
    if (start_index < 0) {
663
0
      start_index = file_names.size() - num_files_to_compact;
664
0
    }
665
666
0
    for (size_t i = 0; i < file_names.size(); ++i) {
667
0
      if (implicit_cast<size_t>(start_index) <= i &&
668
0
          compaction_input_file_names.size() < num_files_to_compact) {
669
0
        compaction_input_file_names.push_back(file_names[i]);
670
0
      } else {
671
0
        remaining_file_names.push_back(file_names[i]);
672
0
      }
673
0
    }
674
0
    ASSERT_EQ(num_files_to_compact, compaction_input_file_names.size())
675
0
        << "Tried to add " << num_files_to_compact << " files starting with index " << start_index
676
0
        << ", ended up adding " << yb::ToString(compaction_input_file_names)
677
0
        << " and leaving " << yb::ToString(remaining_file_names) << " out. All files: "
678
0
        << yb::ToString(file_names);
679
680
0
    LOG(INFO) << "Minor-compacting history before hybrid_time " << history_cutoff << ":\n"
681
0
              << "  files being compacted: " << yb::ToString(compaction_input_file_names) << "\n"
682
0
              << "  other files: " << yb::ToString(remaining_file_names);
683
684
0
    ASSERT_OK(regular_db_->CompactFiles(
685
0
        rocksdb::CompactionOptions(),
686
0
        compaction_input_file_names,
687
0
        /* output_level */ 0));
688
0
    const auto sstables_after_compaction = SSTableFileNames();
689
0
    LOG(INFO) << "SSTable files after compaction: " << sstables_after_compaction.size()
690
0
              << " (" << yb::ToString(sstables_after_compaction) << ")";
691
0
    for (const auto& remaining_file : remaining_file_names) {
692
0
      ASSERT_TRUE(
693
0
          std::find(sstables_after_compaction.begin(), sstables_after_compaction.end(),
694
0
                    remaining_file) != sstables_after_compaction.end()
695
0
      ) << "File " << remaining_file << " not found in file list after compaction: "
696
0
        << yb::ToString(sstables_after_compaction) << ", even though none of these files were "
697
0
        << "supposed to be compacted: " << yb::ToString(remaining_file_names);
698
0
    }
699
0
  }
700
701
0
  regular_db_->GetColumnFamilyMetaData(&cf_meta);
702
0
  vector<string> files_after_compaction;
703
0
  for (const auto& sst_meta : cf_meta.levels[0].files) {
704
0
    files_after_compaction.push_back(sst_meta.name);
705
0
  }
706
0
  const int64_t expected_resulting_num_files = initial_num_files - num_files_to_compact + 1;
707
0
  ASSERT_EQ(expected_resulting_num_files,
708
0
            static_cast<int64_t>(cf_meta.levels[0].files.size()))
709
0
      << "Files after compaction: " << yb::ToString(files_after_compaction);
710
0
}
711
712
0
size_t DocDBRocksDBFixture::NumSSTableFiles() {
713
0
  rocksdb::ColumnFamilyMetaData cf_meta;
714
0
  regular_db_->GetColumnFamilyMetaData(&cf_meta);
715
0
  return cf_meta.levels[0].files.size();
716
0
}
717
718
0
StringVector DocDBRocksDBFixture::SSTableFileNames() {
719
0
  rocksdb::ColumnFamilyMetaData cf_meta;
720
0
  regular_db_->GetColumnFamilyMetaData(&cf_meta);
721
0
  StringVector files;
722
0
  for (const auto& sstable_meta : cf_meta.levels[0].files) {
723
0
    files.push_back(sstable_meta.name);
724
0
  }
725
0
  SortByKey(files.begin(), files.end(), rocksdb::TableFileNameToNumber);
726
0
  return files;
727
0
}
728
729
0
Status DocDBRocksDBFixture::FormatDocWriteBatch(const DocWriteBatch &dwb, string* dwb_str) {
730
0
  WriteBatchFormatter formatter;
731
0
  rocksdb::WriteBatch rocksdb_write_batch;
732
0
  RETURN_NOT_OK(PopulateRocksDBWriteBatch(dwb, &rocksdb_write_batch));
733
0
  RETURN_NOT_OK(rocksdb_write_batch.Iterate(&formatter));
734
0
  *dwb_str = formatter.str();
735
0
  return Status::OK();
736
0
}
737
738
0
Status FullyCompactDB(rocksdb::DB* rocksdb) {
739
0
  rocksdb::CompactRangeOptions compact_range_options;
740
0
  return rocksdb->CompactRange(compact_range_options, nullptr, nullptr);
741
0
}
742
743
0
Status DocDBRocksDBFixture::InitRocksDBDir() {
744
0
  string test_dir;
745
0
  RETURN_NOT_OK(Env::Default()->GetTestDirectory(&test_dir));
746
0
  rocksdb_dir_ = JoinPathSegments(test_dir, StringPrintf("mytestdb-%d", rand()));
747
0
  CHECK(!rocksdb_dir_.empty());  // Check twice before we recursively delete anything.
748
0
  CHECK_NE(rocksdb_dir_, "/");
749
0
  RETURN_NOT_OK(Env::Default()->DeleteRecursively(rocksdb_dir_));
750
0
  RETURN_NOT_OK(Env::Default()->DeleteRecursively(IntentsDBDir()));
751
0
  return Status::OK();
752
0
}
753
754
0
string DocDBRocksDBFixture::tablet_id() {
755
0
  return "mytablet";
756
0
}
757
758
0
Status DocDBRocksDBFixture::InitRocksDBOptions() {
759
0
  RETURN_NOT_OK(InitCommonRocksDBOptionsForTests());
760
0
  return Status::OK();
761
0
}
762
763
0
string TrimDocDbDebugDumpStr(const string& debug_dump_str) {
764
0
  return TrimStr(ApplyEagerLineContinuation(LeftShiftTextBlock(TrimCppComments(debug_dump_str))));
765
0
}
766
767
}  // namespace docdb
768
}  // namespace yb