YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

/Users/deen/code/yugabyte-db/src/yb/tablet/tablet_random_access-test.cc
Line
Count
Source (jump to first uncovered line)
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
//
18
// The following only applies to changes made to this file as part of YugaByte development.
19
//
20
// Portions Copyright (c) YugaByte, Inc.
21
//
22
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
23
// in compliance with the License.  You may obtain a copy of the License at
24
//
25
// http://www.apache.org/licenses/LICENSE-2.0
26
//
27
// Unless required by applicable law or agreed to in writing, software distributed under the License
28
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
29
// or implied.  See the License for the specific language governing permissions and limitations
30
// under the License.
31
//
32
33
#include <algorithm>
34
#include <limits>
35
#include <string>
36
#include <unordered_set>
37
#include <vector>
38
39
#include <glog/logging.h>
40
#include <gtest/gtest.h>
41
42
#include "yb/common/partial_row.h"
43
#include "yb/common/ql_protocol_util.h"
44
#include "yb/common/ql_rowblock.h"
45
#include "yb/common/schema.h"
46
47
#include "yb/gutil/casts.h"
48
#include "yb/gutil/strings/join.h"
49
#include "yb/gutil/strings/numbers.h"
50
#include "yb/gutil/strings/substitute.h"
51
#include "yb/gutil/walltime.h"
52
53
#include "yb/tablet/local_tablet_writer.h"
54
#include "yb/tablet/read_result.h"
55
#include "yb/tablet/tablet-test-util.h"
56
#include "yb/tablet/tablet.h"
57
58
#include "yb/util/env.h"
59
#include "yb/util/status_log.h"
60
#include "yb/util/stopwatch.h"
61
#include "yb/util/test_macros.h"
62
#include "yb/util/test_util.h"
63
#include "yb/util/thread.h"
64
65
DEFINE_int32(keyspace_size, 300, "number of unique row keys to insert/mutate");
66
DEFINE_int32(runtime_seconds, 1, "number of seconds to run the test");
67
DEFINE_int32(sleep_between_background_ops_ms, 100,
68
             "number of milliseconds to sleep between flushing or compacting");
69
DEFINE_int32(update_delete_ratio, 4, "ratio of update:delete when mutating existing rows");
70
71
using std::string;
72
using std::vector;
73
74
enum TestOp {
75
  TEST_INSERT,
76
  TEST_UPDATE,
77
  TEST_DELETE,
78
  TEST_FLUSH_OPS,
79
  TEST_FLUSH_TABLET,
80
  TEST_NUM_OP_TYPES // max value for enum
81
};
82
MAKE_ENUM_LIMITS(TestOp, TEST_INSERT, TEST_NUM_OP_TYPES);
83
84
namespace yb {
85
namespace tablet {
86
87
const char* TestOp_names[] = {
88
  "TEST_INSERT",
89
  "TEST_UPDATE",
90
  "TEST_DELETE",
91
  "TEST_FLUSH_OPS",
92
  "TEST_FLUSH_TABLET",
93
};
94
95
// Test which does only random operations against a tablet, including update and random
96
// get (ie scans with equal lower and upper bounds).
97
//
98
// The test maintains an in-memory copy of the expected state of the tablet, and uses only
99
// a single thread, so that it's easy to verify that the tablet always matches the expected
100
// state.
101
class TestRandomAccess : public YBTabletTest {
102
  static constexpr auto VALUE_NOT_FOUND = "";
103
104
 public:
105
  TestRandomAccess()
106
    : YBTabletTest(
107
7
        Schema({ ColumnSchema("key", INT32, false, true), ColumnSchema("val", INT32, true) }, 1)) {
108
7
    OverrideFlagForSlowTests("keyspace_size", "30000");
109
7
    OverrideFlagForSlowTests("runtime_seconds", "10");
110
7
    OverrideFlagForSlowTests("sleep_between_background_ops_ms", "1000");
111
112
7
    expected_tablet_state_.resize(FLAGS_keyspace_size);
113
7
  }
114
115
7
  void SetUp() override {
116
7
    YBTabletTest::SetUp();
117
7
    writer_.reset(new LocalTabletWriter(tablet().get()));
118
7
  }
119
120
  // Pick a random row of the table, verify its current state, and then
121
  // modify it in some way. The modifications may include multiple mutations
122
  // to the same row in a single batch (eg insert/update/delete).
123
  //
124
  // The mutations are always valid. For example:
125
  // - inserting if it doesn't exist yet
126
  // - perform an update or delete the row if it does exist.
127
  //
128
  // TODO: should add a version of this test which also tries invalid operations
129
  // and validates the correct errors.
130
400
  void DoRandomBatch() {
131
400
    if (expected_tablet_state_.size() == 0)
132
0
      return;
133
400
    int key = rand_r(&random_seed_) % expected_tablet_state_.size();
134
400
    string& cur_val = expected_tablet_state_[key];
135
136
    // Check that a read yields what we expect.
137
400
    string val_in_table = GetRow(key);
138
    // Since we start with expected_tablet_state_ sized `keyspace_size`, there might not
139
    // be all keys present initially. So we do not assert for the value when key is not present.
140
400
    ASSERT_EQ(cur_val, val_in_table);
141
142
400
    LocalTabletWriter::Batch pending;
143
1.60k
    for (int i = 0; i < 3; i++) {
144
1.20k
      int new_val = rand_r(&random_seed_);
145
1.20k
      if (cur_val.empty()) {
146
        // If there is no row, then insert one.
147
360
        cur_val = InsertRow(key, new_val, &pending);
148
840
      } else {
149
840
        if (new_val % (FLAGS_update_delete_ratio + 1) == 0) {
150
173
          cur_val = DeleteRow(key, &pending);
151
667
        } else {
152
667
          cur_val = MutateRow(key, new_val, &pending);
153
667
        }
154
840
      }
155
1.20k
    }
156
400
    CHECK_OK(writer_->WriteBatch(&pending));
157
400
  }
158
159
1
  void DoRandomBatches() {
160
1
    int op_count = 0;
161
1
    Stopwatch s;
162
1
    s.start();
163
5
    while (s.elapsed().wall_seconds() < FLAGS_runtime_seconds) {
164
404
      for (int i = 0; i < 100; i++) {
165
400
        ASSERT_NO_FATALS(DoRandomBatch());
166
400
        op_count++;
167
400
      }
168
4
    }
169
1
    LOG(INFO) << "Ran " << op_count << " ops "
170
1
              << "(" << (op_count / s.elapsed().wall_seconds()) << " ops/sec)";
171
1
  }
172
173
  // Wakes up periodically to perform a flush or compaction.
174
1
  void BackgroundOpThread() {
175
14
    while (!done_.WaitFor(MonoDelta::FromMilliseconds(FLAGS_sleep_between_background_ops_ms))) {
176
13
      CHECK_OK(tablet()->Flush(tablet::FlushMode::kSync));
177
13
    }
178
1
  }
179
180
  std::string UpsertRow(QLWriteRequestPB::QLStmtType stmt, int key, int val,
181
8.22k
                        LocalTabletWriter::Batch* batch) {
182
8.22k
    QLWriteRequestPB* req = batch->Add();
183
8.22k
    req->set_type(stmt);
184
8.22k
    QLAddInt32HashValue(req, key);
185
8.22k
    if (val & 1) {
186
5.62k
      QLAddNullColumnValue(req, kFirstColumnId + 1);
187
2.59k
    } else {
188
2.59k
      QLAddInt32ColumnValue(req, kFirstColumnId + 1, val);
189
2.59k
    }
190
5.62k
    return Format("{ int32:$0, $1 }", key, val & 1 ? "null" : Format("int32:$0", val));
191
8.22k
  }
192
193
  // Adds an insert for the given key/value pair to 'ops', returning the new stringified
194
  // value of the row.
195
460
  string InsertRow(int key, int val, LocalTabletWriter::Batch* batch) {
196
460
    return UpsertRow(QLWriteRequestPB::QL_STMT_INSERT, key, val, batch);
197
460
  }
198
199
  // Adds an update of the given key/value pair to 'ops', returning the new stringified
200
  // value of the row.
201
7.76k
  string MutateRow(int key, uint32_t new_val, LocalTabletWriter::Batch* batch) {
202
7.76k
    return UpsertRow(QLWriteRequestPB::QL_STMT_UPDATE, key, new_val, batch);
203
7.76k
  }
204
205
  // Adds a delete of the given row to 'ops', returning an empty string (indicating that
206
  // the row no longer exists).
207
270
  std::string DeleteRow(int key, LocalTabletWriter::Batch* batch) {
208
270
    auto req = batch->Add();
209
270
    req->set_type(QLWriteRequestPB::QL_STMT_DELETE);
210
270
    QLAddInt32HashValue(req, key);
211
270
    return std::string();
212
270
  }
213
214
  // Random-read the given row, returning its current value.
215
  // If the row doesn't exist, returns "()".
216
990
  string GetRow(int key) {
217
990
    ReadHybridTime read_time = ReadHybridTime::SingleTime(CHECK_RESULT(tablet()->SafeTime()));
218
990
    QLReadRequestPB req;
219
990
    auto* condition = req.mutable_where_expr()->mutable_condition();
220
990
    QLSetInt32Condition(condition, kFirstColumnId, QL_OP_EQUAL, key);
221
990
    QLReadRequestResult result;
222
990
    TransactionMetadataPB transaction;
223
990
    QLAddColumns(schema_, {}, &req);
224
990
    EXPECT_OK(tablet()->HandleQLReadRequest(
225
990
        CoarseTimePoint::max() /* deadline */, read_time, req, transaction, &result));
226
227
990
    EXPECT_EQ(QLResponsePB::YQL_STATUS_OK, result.response.status());
228
229
990
    auto row_block = CreateRowBlock(QLClient::YQL_CLIENT_CQL, schema_, result.rows_data);
230
990
    if (row_block->row_count() == 0) {
231
456
      return VALUE_NOT_FOUND;
232
456
    }
233
534
    EXPECT_EQ(1, row_block->row_count());
234
534
    return row_block->row(0).ToString();
235
534
  }
236
237
 protected:
238
  void RunFuzzCase(const vector<TestOp>& ops,
239
                   int update_multiplier);
240
241
  // The current expected state of the tablet.
242
  vector<string> expected_tablet_state_;
243
244
  // Latch triggered when the main thread is finished performing
245
  // operations. This stops the compact/flush thread.
246
  CountDownLatch done_{1};
247
248
  std::unique_ptr<LocalTabletWriter> writer_;
249
250
  unsigned int random_seed_ = SeedRandom();
251
};
252
253
1
TEST_F(TestRandomAccess, Test) {
254
1
  scoped_refptr<Thread> flush_thread;
255
1
  CHECK_OK(Thread::Create(
256
1
      "test", "flush", std::bind(&TestRandomAccess::BackgroundOpThread, this), &flush_thread));
257
258
1
  DoRandomBatches();
259
1
  done_.CountDown();
260
1
  flush_thread->Join();
261
1
}
262
263
2
void GenerateTestCase(vector<TestOp>* ops, size_t len) {
264
2
  bool exists = false;
265
2
  bool ops_pending = false;
266
2
  ops->clear();
267
2
  unsigned int random_seed = SeedRandom();
268
889
  while (ops->size() < len) {
269
887
    TestOp r = static_cast<TestOp>(rand_r(&random_seed) % enum_limits<TestOp>::max_enumerator);
270
887
    switch (r) {
271
173
      case TEST_INSERT:
272
173
        if (exists) continue;
273
89
        ops->push_back(TEST_INSERT);
274
89
        ops_pending = true;
275
89
        exists = true;
276
89
        break;
277
193
      case TEST_UPDATE:
278
193
        if (!exists) continue;
279
101
        ops->push_back(TEST_UPDATE);
280
101
        ops_pending = true;
281
101
        break;
282
179
      case TEST_DELETE:
283
179
        if (!exists) continue;
284
87
        ops->push_back(TEST_DELETE);
285
87
        exists = false;
286
87
        break;
287
157
      case TEST_FLUSH_OPS:
288
157
        if (ops_pending) {
289
88
          ops->push_back(TEST_FLUSH_OPS);
290
88
          ops_pending = false;
291
88
        }
292
157
        break;
293
185
      case TEST_FLUSH_TABLET:
294
185
        ops->push_back(TEST_FLUSH_TABLET);
295
185
        break;
296
0
      default:
297
0
        LOG(FATAL);
298
887
    }
299
887
  }
300
2
}
301
302
6
string DumpTestCase(const vector<TestOp>& ops) {
303
6
  vector<string> names;
304
590
  for (TestOp test_op : ops) {
305
590
    names.push_back(TestOp_names[test_op]);
306
590
  }
307
6
  return JoinStrings(names, ",\n");
308
6
}
309
310
void TestRandomAccess::RunFuzzCase(const vector<TestOp>& test_ops,
311
6
                                   int update_multiplier = 1) {
312
6
  LOG(INFO) << "test case: " << DumpTestCase(test_ops);
313
314
6
  LocalTabletWriter writer(tablet().get());
315
6
  LocalTabletWriter::Batch batch;
316
317
6
  string cur_val = "";
318
6
  string pending_val = "";
319
320
6
  int i = 0;
321
590
  for (TestOp test_op : test_ops) {
322
590
    string val_in_table = GetRow(1);
323
590
    if (val_in_table != VALUE_NOT_FOUND) {
324
391
      ASSERT_EQ(cur_val, val_in_table);
325
391
    }
326
327
590
    i++;
328
590
    LOG(INFO) << TestOp_names[test_op];
329
590
    switch (test_op) {
330
100
      case TEST_INSERT:
331
100
        pending_val = InsertRow(1, i, &batch);
332
100
        break;
333
104
      case TEST_UPDATE:
334
7.20k
        for (int j = 0; j < update_multiplier; j++) {
335
7.09k
          pending_val = MutateRow(1, i, &batch);
336
7.09k
        }
337
104
        break;
338
97
      case TEST_DELETE:
339
97
        pending_val = DeleteRow(1, &batch);
340
97
        break;
341
96
      case TEST_FLUSH_OPS:
342
96
        ASSERT_OK(writer.WriteBatch(&batch));
343
96
        cur_val = pending_val;
344
96
        break;
345
193
      case TEST_FLUSH_TABLET:
346
193
        ASSERT_OK(tablet()->Flush(tablet::FlushMode::kSync));
347
193
        break;
348
0
      default:
349
0
        LOG(FATAL) << test_op;
350
590
    }
351
590
  }
352
6
}
353
354
// Generates a random test sequence and runs it.
355
// The logs of this test are designed to easily be copy-pasted and create
356
// more specific test cases like TestFuzz<N> below.
357
1
TEST_F(TestRandomAccess, TestFuzz) {
358
1
  SeedRandom();
359
1
  vector<TestOp> test_ops;
360
1
  GenerateTestCase(&test_ops, 500);
361
1
  RunFuzzCase(test_ops);
362
1
}
363
364
// Generates a random test case, but the UPDATEs are all repeated 1000 times.
365
// This results in very large batches which are likely to span multiple delta blocks
366
// when flushed.
367
1
TEST_F(TestRandomAccess, TestFuzzHugeBatches) {
368
1
  SeedRandom();
369
1
  vector<TestOp> test_ops;
370
1
  GenerateTestCase(&test_ops, AllowSlowTests() ? 1000 : 50);
371
1
  RunFuzzCase(test_ops, 1000);
372
1
}
373
374
// A particular test case which previously failed TestFuzz.
375
1
TEST_F(TestRandomAccess, TestFuzz1) {
376
1
  TestOp test_ops[] = {
377
    // Get an inserted row.
378
1
    TEST_INSERT,
379
1
    TEST_FLUSH_OPS,
380
1
    TEST_FLUSH_TABLET,
381
    // DELETE and INSERT.
382
1
    TEST_DELETE,
383
1
    TEST_INSERT,
384
1
    TEST_FLUSH_OPS,
385
1
    TEST_FLUSH_TABLET,
386
1
  };
387
1
  RunFuzzCase(vector<TestOp>(test_ops, test_ops + arraysize(test_ops)));
388
1
}
389
390
// A particular test case which previously failed TestFuzz.
391
1
TEST_F(TestRandomAccess, TestFuzz2) {
392
1
  TestOp test_ops[] = {
393
1
    TEST_INSERT,
394
1
    TEST_DELETE,
395
1
    TEST_FLUSH_OPS,
396
1
    TEST_FLUSH_TABLET,
397
1
    TEST_INSERT,
398
1
    TEST_DELETE,
399
1
    TEST_INSERT,
400
1
    TEST_FLUSH_OPS,
401
1
    TEST_FLUSH_TABLET,
402
1
    TEST_DELETE,
403
1
  };
404
1
  RunFuzzCase(vector<TestOp>(test_ops, test_ops + arraysize(test_ops)));
405
1
}
406
407
// A particular test case which previously failed TestFuzz.
408
1
TEST_F(TestRandomAccess, TestFuzz3) {
409
1
  TestOp test_ops[] = {
410
1
    TEST_INSERT,
411
1
    TEST_DELETE,
412
1
    TEST_FLUSH_OPS,
413
1
    TEST_FLUSH_TABLET,
414
1
    TEST_INSERT,
415
1
    TEST_DELETE,
416
1
  };
417
1
  RunFuzzCase(vector<TestOp>(test_ops, test_ops + arraysize(test_ops)));
418
1
}
419
420
// A particular test case which previously failed TestFuzz.
421
1
TEST_F(TestRandomAccess, TestFuzz4) {
422
1
  TestOp test_ops[] = {
423
1
    TEST_INSERT,
424
1
    TEST_FLUSH_OPS,
425
1
    TEST_FLUSH_TABLET,
426
1
    TEST_DELETE,
427
1
    TEST_INSERT,
428
1
    TEST_UPDATE,
429
1
    TEST_DELETE,
430
1
    TEST_FLUSH_OPS,
431
1
    TEST_FLUSH_TABLET,
432
1
    TEST_INSERT,
433
1
    TEST_UPDATE,
434
1
    TEST_UPDATE,
435
1
    TEST_DELETE,
436
1
    TEST_INSERT,
437
1
    TEST_DELETE,
438
1
    TEST_FLUSH_OPS,
439
1
    TEST_FLUSH_TABLET,
440
1
  };
441
1
  RunFuzzCase(vector<TestOp>(test_ops, test_ops + arraysize(test_ops)));
442
1
}
443
444
} // namespace tablet
445
} // namespace yb