YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

/Users/deen/code/yugabyte-db/src/yb/integration-tests/alter_table-randomized-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 <map>
35
#include <vector>
36
37
#include "yb/client/client-test-util.h"
38
#include "yb/client/client.h"
39
#include "yb/client/error.h"
40
#include "yb/client/schema.h"
41
#include "yb/client/session.h"
42
#include "yb/client/table.h"
43
#include "yb/client/table_alterer.h"
44
#include "yb/client/table_creator.h"
45
#include "yb/client/table_handle.h"
46
#include "yb/client/yb_op.h"
47
48
#include "yb/gutil/casts.h"
49
#include "yb/gutil/map-util.h"
50
#include "yb/gutil/stl_util.h"
51
#include "yb/gutil/strings/substitute.h"
52
53
#include "yb/integration-tests/cluster_verifier.h"
54
#include "yb/integration-tests/external_mini_cluster.h"
55
56
#include "yb/util/random.h"
57
#include "yb/util/result.h"
58
#include "yb/util/status_log.h"
59
#include "yb/util/test_util.h"
60
61
using namespace std::literals;
62
63
namespace yb {
64
65
using client::YBClient;
66
using client::YBClientBuilder;
67
using client::YBTableType;
68
using client::YBError;
69
using client::YBqlWriteOp;
70
using client::YBSchema;
71
using client::YBSchemaBuilder;
72
using client::YBSession;
73
using client::YBTable;
74
using client::YBTableAlterer;
75
using client::YBTableCreator;
76
using client::YBTableName;
77
using client::YBValue;
78
using std::shared_ptr;
79
using std::make_pair;
80
using std::map;
81
using std::pair;
82
using std::vector;
83
using strings::SubstituteAndAppend;
84
85
static const YBTableName kTableName(YQL_DATABASE_CQL, "my_keyspace", "test-table");
86
static const int kMaxColumns = 30;
87
88
class AlterTableRandomized : public YBTest {
89
 public:
90
2
  void SetUp() override {
91
2
    YBTest::SetUp();
92
93
2
    ExternalMiniClusterOptions opts;
94
2
    opts.num_tablet_servers = 3;
95
    // Because this test performs a lot of alter tables, we end up flushing
96
    // and rewriting metadata files quite a bit. Globally disabling fsync
97
    // speeds the test runtime up dramatically.
98
2
    opts.extra_tserver_flags.push_back("--never_fsync");
99
2
    cluster_.reset(new ExternalMiniCluster(opts));
100
2
    ASSERT_OK(cluster_->Start());
101
102
2
    client_ = ASSERT_RESULT(cluster_->CreateClient());
103
2
  }
104
105
2
  void TearDown() override {
106
2
    client_.reset();
107
2
    cluster_->Shutdown();
108
2
    YBTest::TearDown();
109
2
  }
110
111
22
  void RestartTabletServer(size_t idx) {
112
22
    LOG(INFO) << "Restarting TS " << idx;
113
22
    cluster_->tablet_server(idx)->Shutdown();
114
22
    CHECK_OK(cluster_->tablet_server(idx)->Restart());
115
22
    CHECK_OK(cluster_->WaitForTabletsRunning(
116
22
        cluster_->tablet_server(idx), MonoDelta::FromSeconds(60)));
117
22
  }
118
119
 protected:
120
  std::unique_ptr<ExternalMiniCluster> cluster_;
121
  std::unique_ptr<YBClient> client_;
122
};
123
124
typedef std::vector<std::pair<std::string, int32_t>> Row;
125
126
// We use this special value to denote NULL values.
127
// We ensure that we never insert or update to this value except in the case of NULLable columns.
128
const int32_t kNullValue = 0xdeadbeef;
129
130
47
std::string RowToString(const Row& row) {
131
47
  string ret = "{ ";
132
47
  bool first = true;
133
1.41k
  for (const auto& e : row) {
134
1.41k
    if (!first) {
135
1.36k
      ret.append(", ");
136
1.36k
    }
137
1.41k
    first = false;
138
1.41k
    if (e.second == kNullValue) {
139
357
      ret += "null";
140
1.05k
    } else {
141
1.05k
      SubstituteAndAppend(&ret, "int32:$0", e.second);
142
1.05k
    }
143
1.41k
  }
144
47
  ret += " }";
145
146
47
  return ret;
147
47
}
148
149
struct TableState {
150
2
  TableState() {
151
2
    col_names_.push_back("key");
152
2
    col_nullable_.push_back(false);
153
2
  }
154
155
  void GenRandomRow(int32_t key, int32_t seed,
156
377
                    vector<pair<string, int32_t>>* row) {
157
377
    if (seed == kNullValue) {
158
0
      seed++;
159
0
    }
160
377
    row->clear();
161
377
    row->push_back(make_pair("key", key));
162
7.42k
    for (size_t i = 1; i < col_names_.size(); i++) {
163
7.05k
      int32_t val;
164
7.05k
      if (col_nullable_[i] && seed % 2 == 1) {
165
1.77k
        val = kNullValue;
166
5.27k
      } else {
167
5.27k
        val = seed;
168
5.27k
      }
169
7.05k
      row->push_back(make_pair(col_names_[i], val));
170
7.05k
    }
171
377
  }
172
173
396
  bool Insert(const Row& data) {
174
396
    DCHECK_EQ("key", data[0].first);
175
396
    int32_t key = data[0].second;
176
177
396
    return rows_.emplace(key, data).second;
178
396
  }
179
180
201
  bool Update(const vector<pair<string, int32_t>>& data) {
181
201
    DCHECK_EQ("key", data[0].first);
182
201
    int32_t key = data[0].second;
183
201
    auto it = rows_.find(key);
184
201
    if (it == rows_.end()) return false;
185
186
201
    it->second = data;
187
201
    return true;
188
201
  }
189
190
349
  void Delete(int32_t row_key) {
191
0
    CHECK(rows_.erase(row_key)) << "row key " << row_key << " not found";
192
349
  }
193
194
69
  void AddColumnWithDefault(const string& name, int32_t def, bool nullable) {
195
69
    col_names_.push_back(name);
196
69
    col_nullable_.push_back(nullable);
197
1.41k
    for (auto& e : rows_) {
198
1.41k
      e.second.emplace_back(name, def);
199
1.41k
    }
200
69
  }
201
202
39
  void DropColumn(const string& name) {
203
39
    auto col_it = std::find(col_names_.begin(), col_names_.end(), name);
204
39
    auto index = col_it - col_names_.begin();
205
39
    col_names_.erase(col_it);
206
39
    col_nullable_.erase(col_nullable_.begin() + index);
207
686
    for (auto& e : rows_) {
208
686
      e.second.erase(e.second.begin() + index);
209
686
    }
210
39
  }
211
212
536
  int32_t GetRandomRowKey(int32_t rand) {
213
536
    CHECK(!rows_.empty());
214
536
    auto it = rows_.begin();
215
536
    std::advance(it, rand % rows_.size());
216
536
    return it->first;
217
536
  }
218
219
21
  void ToStrings(vector<string>* strs) {
220
21
    strs->clear();
221
47
    for (const auto& e : rows_) {
222
47
      strs->push_back(RowToString(e.second));
223
47
    }
224
21
  }
225
226
  // The name of each column.
227
  vector<string> col_names_;
228
229
  // For each column, whether it is NULLable.
230
  // Has the same length as col_names_.
231
  vector<bool> col_nullable_;
232
233
  std::map<int32_t, Row> rows_;
234
};
235
236
struct MirrorTable {
237
  explicit MirrorTable(client::YBClient* client)
238
2
      : client_(client) {}
239
240
2
  Status Create() {
241
2
    RETURN_NOT_OK(client_->CreateNamespaceIfNotExists(kTableName.namespace_name(),
242
2
                                                      kTableName.namespace_type()));
243
2
    YBSchema schema;
244
2
    YBSchemaBuilder b;
245
2
    b.AddColumn("key")->Type(INT32)->HashPrimaryKey()->NotNull();
246
2
    CHECK_OK(b.Build(&schema));
247
2
    std::unique_ptr<YBTableCreator> table_creator(client_->NewTableCreator());
248
2
    return table_creator->table_name(kTableName)
249
2
        .schema(&schema)
250
2
        .Create();
251
2
  }
252
253
377
  bool TryInsert(int32_t row_key, int32_t rand) {
254
377
    vector<pair<string, int32_t>> row;
255
377
    ts_.GenRandomRow(row_key, rand, &row);
256
377
    return TryInsert(row);
257
377
  }
258
259
396
  bool TryInsert(const Row& row) {
260
396
    if (!ts_.Insert(row)) {
261
0
      return false;
262
0
    }
263
264
396
    Status s = DoRealOp(row, INSERT);
265
396
    CHECK_OK(s);
266
267
396
    return true;
268
396
  }
269
270
331
  void DeleteRandomRow(uint32_t rand) {
271
331
    if (ts_.rows_.empty()) return;
272
330
    DeleteRow(ts_.GetRandomRowKey(rand));
273
330
  }
274
275
349
  void DeleteRow(int32_t row_key) {
276
349
    Row del = {{"key", row_key}};
277
349
    ts_.Delete(row_key);
278
349
    CHECK_OK(DoRealOp(del, DELETE));
279
349
  }
280
281
206
  void UpdateRandomRow(uint32_t rand) {
282
206
    if (ts_.rows_.empty()) return;
283
206
    int32_t row_key = ts_.GetRandomRowKey(rand);
284
285
206
    vector<pair<string, int32_t>> update;
286
206
    update.push_back(make_pair("key", row_key));
287
4.05k
    for (size_t i = 1; i < num_columns(); i++) {
288
3.84k
      auto val = static_cast<int32_t>(rand * i);
289
3.84k
      if (val == kNullValue) val++;
290
3.84k
      if (ts_.col_nullable_[i] && val % 2 == 1) {
291
377
        val = kNullValue;
292
377
      }
293
3.84k
      update.push_back(make_pair(ts_.col_names_[i], val));
294
3.84k
    }
295
296
206
    if (update.size() == 1) {
297
      // No columns got updated. Just ignore this update.
298
5
      return;
299
5
    }
300
301
201
    Status s = DoRealOp(update, UPDATE);
302
201
    if (s.IsNotFound()) {
303
0
      CHECK(!ts_.Update(update)) << "real table said not-found, fake table succeeded";
304
0
      return;
305
0
    }
306
201
    CHECK_OK(s);
307
308
201
    CHECK(ts_.Update(update));
309
201
  }
310
311
49
  void AddAColumn(const string& name) {
312
49
    bool nullable = random() % 2 == 1;
313
49
    return AddAColumn(name, nullable);
314
49
  }
315
316
69
  void AddAColumn(const string& name, bool nullable) {
317
    // Add to the real table.
318
69
    std::unique_ptr<YBTableAlterer> table_alterer(client_->NewTableAlterer(kTableName));
319
320
69
    table_alterer->AddColumn(name)->Type(INT32);
321
69
    ASSERT_OK(table_alterer->Alter());
322
323
    // Add to the mirror state.
324
69
    ts_.AddColumnWithDefault(name, kNullValue, nullable);
325
69
  }
326
327
39
  void DropAColumn(const string& name) {
328
39
    std::unique_ptr<YBTableAlterer> table_alterer(client_->NewTableAlterer(kTableName));
329
39
    CHECK_OK(table_alterer->DropColumn(name)->Alter());
330
39
    ts_.DropColumn(name);
331
39
  }
332
333
20
  void DropRandomColumn(int seed) {
334
20
    if (num_columns() == 1) return;
335
336
20
    string name = ts_.col_names_[1 + (seed % (num_columns() - 1))];
337
20
    DropAColumn(name);
338
20
  }
339
340
4.15k
  size_t num_columns() const {
341
4.15k
    return ts_.col_names_.size();
342
4.15k
  }
343
344
21
  void Verify() {
345
    // First scan the real table
346
21
    vector<string> rows;
347
21
    {
348
21
      client::TableHandle table;
349
21
      CHECK_OK(table.Open(kTableName, client_));
350
21
      client::ScanTableToStrings(table, &rows);
351
21
    }
352
21
    std::sort(rows.begin(), rows.end());
353
354
    // Then get our mock table.
355
21
    vector<string> expected;
356
21
    ts_.ToStrings(&expected);
357
358
    // They should look the same.
359
21
    LogVectorDiff(expected, rows);
360
21
    ASSERT_EQ(expected, rows);
361
21
  }
362
363
 private:
364
  enum OpType {
365
    INSERT, UPDATE, DELETE
366
  };
367
368
946
  Status DoRealOp(const vector<pair<string, int32_t>>& data, OpType op_type) {
369
946
    auto deadline = MonoTime::Now() + 15s;
370
946
    shared_ptr<YBSession> session = client_->NewSession();
371
946
    session->SetTimeout(15s);
372
946
    shared_ptr<YBTable> table;
373
946
    RETURN_NOT_OK(client_->OpenTable(kTableName, &table));
374
946
    for (;;) {
375
946
      auto op = CreateOp(table, op_type);
376
946
      auto* const req = op->mutable_request();
377
946
      bool first = true;
378
946
      auto schema = table->schema();
379
11.8k
      for (const auto& d : data) {
380
11.8k
        if (first) {
381
946
          req->add_hashed_column_values()->mutable_value()->set_int32_value(d.second);
382
946
          first = false;
383
946
          continue;
384
946
        }
385
10.9k
        auto column_value = req->add_column_values();
386
146k
        for (size_t i = 0; i < schema.num_columns(); ++i) {
387
146k
          if (schema.Column(i).name() == d.first) {
388
10.9k
            column_value->set_column_id(schema.ColumnId(i));
389
10.9k
            auto value = column_value->mutable_expr()->mutable_value();
390
10.9k
            if (d.second != kNullValue) {
391
8.77k
              value->set_int32_value(d.second);
392
8.77k
            }
393
10.9k
            break;
394
10.9k
          }
395
146k
        }
396
10.9k
      }
397
946
      session->Apply(op);
398
946
      const auto flush_status = session->FlushAndGetOpsErrors();
399
946
      const auto& s = flush_status.status;
400
946
      if (s.ok()) {
401
946
        if (op->response().status() == QLResponsePB::YQL_STATUS_SCHEMA_VERSION_MISMATCH &&
402
0
            MonoTime::Now() < deadline) {
403
0
          continue;
404
0
        }
405
0
        CHECK(op->succeeded()) << op->response().ShortDebugString();
406
946
        return s;
407
946
      }
408
409
0
      CHECK_EQ(flush_status.errors.size(), 1);
410
0
      return flush_status.errors[0]->status();
411
0
    }
412
946
  }
413
414
946
  shared_ptr<YBqlWriteOp> CreateOp(const shared_ptr<YBTable>& table, OpType op_type) {
415
946
    switch (op_type) {
416
396
      case INSERT:
417
396
        return shared_ptr<YBqlWriteOp>(table->NewQLInsert());
418
201
      case UPDATE:
419
201
        return shared_ptr<YBqlWriteOp>(table->NewQLUpdate());
420
349
      case DELETE:
421
349
        return shared_ptr<YBqlWriteOp>(table->NewQLDelete());
422
0
    }
423
0
    return shared_ptr<YBqlWriteOp>();
424
0
  }
425
426
  YBClient* client_;
427
  TableState ts_;
428
};
429
430
// Stress test for various alter table scenarios. This performs a random sequence of:
431
//   - insert a row (using the latest schema)
432
//   - delete a random row
433
//   - update a row (all columns with the latest schema)
434
//   - add a new column
435
//   - drop a column
436
//   - restart the tablet server
437
//
438
// During the sequence of operations, a "mirror" of the table in memory is kept up to
439
// date. We periodically scan the actual table, and ensure that the data in YB
440
// matches our in-memory "mirror".
441
1
TEST_F(AlterTableRandomized, TestRandomSequence) {
442
1
  MirrorTable t(client_.get());
443
1
  ASSERT_OK(t.Create());
444
445
1
  Random rng(SeedRandom());
446
447
1
  const int n_iters = AllowSlowTests() ? 2000 : 1000;
448
1.00k
  for (int i = 0; i < n_iters; i++) {
449
    // Perform different operations with varying probability.
450
    // We mostly insert and update, with occasional deletes,
451
    // and more occasional table alterations or restarts.
452
1.00k
    int r = rng.Uniform(1000);
453
1.00k
    if (r < 400) {
454
377
      bool inserted = t.TryInsert(1000000 + rng.Uniform(1000000), rng.Next());
455
377
      if (!inserted) {
456
0
        continue;
457
0
      }
458
623
    } else if (r < 600) {
459
206
      t.UpdateRandomRow(rng.Next());
460
417
    } else if (r < 920) {
461
331
      t.DeleteRandomRow(rng.Next());
462
86
    } else if (r < 970) {
463
63
      if (t.num_columns() < kMaxColumns) {
464
49
        t.AddAColumn(strings::Substitute("c$0", i));
465
49
      }
466
23
    } else if (r < 995) {
467
20
      t.DropRandomColumn(rng.Next());
468
3
    } else {
469
3
      RestartTabletServer(rng.Uniform64(cluster_->num_tablet_servers()));
470
3
    }
471
472
1.00k
    if (i % 1000 == 0) {
473
1
      LOG(INFO) << "Verifying iteration " << i;
474
1
      ASSERT_NO_FATALS(t.Verify());
475
1
      LOG(INFO) << "Verification of iteration " << i << " successful";
476
1
    }
477
1.00k
  }
478
479
1
  LOG(INFO) << "About to do the last verification";
480
1
  ASSERT_NO_FATALS(t.Verify());
481
1
  LOG(INFO) << "Last verification succeeded";
482
483
  // Not only should the data returned by a scanner match what we expect,
484
  // we also expect all of the replicas to agree with each other.
485
1
  ClusterVerifier cluster_verifier(cluster_.get());
486
1
  ASSERT_NO_FATALS(cluster_verifier.CheckCluster());
487
1
}
488
489
1
TEST_F(AlterTableRandomized, AddDropRestart) {
490
1
  MirrorTable t(client_.get());
491
1
  ASSERT_OK(t.Create());
492
493
1
  t.AddAColumn("value", true);
494
20
  for (int key = 1; key != 20; ++key) {
495
19
    LOG(INFO) << "================================================================================";
496
19
    auto column = Format("c_$0", key);
497
19
    t.AddAColumn(column, true);
498
19
    t.TryInsert({{ "key", key }, { "value", 666 }, { column, 1111 }});
499
19
    t.DropAColumn(column);
500
19
    RestartTabletServer(0);
501
19
    t.DeleteRow(key);
502
19
    ASSERT_NO_FATALS(t.Verify());
503
19
  }
504
1
}
505
506
}  // namespace yb