Coverage Report

Created: 2026-11-12 08:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/rocksdb/utilities/transactions/write_unprepared_txn.h
Line
Count
Source
1
// Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
2
//  This source code is licensed under both the GPLv2 (found in the
3
//  COPYING file in the root directory) and Apache 2.0 License
4
//  (found in the LICENSE.Apache file in the root directory).
5
6
#pragma once
7
8
#include <set>
9
10
#include "utilities/transactions/write_prepared_txn.h"
11
#include "utilities/transactions/write_unprepared_txn_db.h"
12
13
namespace ROCKSDB_NAMESPACE {
14
15
class WriteUnpreparedTxnDB;
16
class WriteUnpreparedTxn;
17
18
// WriteUnprepared transactions needs to be able to read their own uncommitted
19
// writes, and supporting this requires some careful consideration. Because
20
// writes in the current transaction may be flushed to DB already, we cannot
21
// rely on the contents of WriteBatchWithIndex to determine whether a key should
22
// be visible or not, so we have to remember to check the DB for any uncommitted
23
// keys that should be visible to us. First, we will need to change the seek to
24
// snapshot logic, to seek to max_visible_seq = max(snap_seq, max_unprep_seq).
25
// Any key greater than max_visible_seq should not be visible because they
26
// cannot be unprepared by the current transaction and they are not in its
27
// snapshot.
28
//
29
// When we seek to max_visible_seq, one of these cases will happen:
30
// 1. We hit a unprepared key from the current transaction.
31
// 2. We hit a unprepared key from the another transaction.
32
// 3. We hit a committed key with snap_seq < seq < max_unprep_seq.
33
// 4. We hit a committed key with seq <= snap_seq.
34
//
35
// IsVisibleFullCheck handles all cases correctly.
36
//
37
// Other notes:
38
// Note that max_visible_seq is only calculated once at iterator construction
39
// time, meaning if the same transaction is adding more unprep seqs through
40
// writes during iteration, these newer writes may not be visible. This is not a
41
// problem for MySQL though because it avoids modifying the index as it is
42
// scanning through it to avoid the Halloween Problem. Instead, it scans the
43
// index once up front, and modifies based on a temporary copy.
44
//
45
// In DBIter, there is a "reseek" optimization if the iterator skips over too
46
// many keys. However, this assumes that the reseek seeks exactly to the
47
// required key. In write unprepared, even after seeking directly to
48
// max_visible_seq, some iteration may be required before hitting a visible key,
49
// and special precautions must be taken to avoid performing another reseek,
50
// leading to an infinite loop.
51
//
52
class WriteUnpreparedTxnReadCallback : public ReadCallback {
53
 public:
54
  WriteUnpreparedTxnReadCallback(
55
      WritePreparedTxnDB* db, SequenceNumber snapshot,
56
      SequenceNumber min_uncommitted,
57
      const std::map<SequenceNumber, size_t>& unprep_seqs,
58
      SnapshotBackup backed_by_snapshot)
59
      // Pass our last uncommitted seq as the snapshot to the parent class to
60
      // ensure that the parent will not prematurely filter out own writes. We
61
      // will do the exact comparison against snapshots in IsVisibleFullCheck
62
      // override.
63
0
      : ReadCallback(CalcMaxVisibleSeq(unprep_seqs, snapshot), min_uncommitted),
64
0
        db_(db),
65
0
        unprep_seqs_(unprep_seqs),
66
0
        wup_snapshot_(snapshot),
67
0
        backed_by_snapshot_(backed_by_snapshot) {
68
0
    (void)backed_by_snapshot_;  // to silence unused private field warning
69
0
  }
70
71
  // No copying allowed
72
  WriteUnpreparedTxnReadCallback(const WriteUnpreparedTxnReadCallback&) =
73
      delete;
74
  WriteUnpreparedTxnReadCallback& operator=(
75
      const WriteUnpreparedTxnReadCallback&) = delete;
76
  WriteUnpreparedTxnReadCallback(WriteUnpreparedTxnReadCallback&&) = delete;
77
  WriteUnpreparedTxnReadCallback& operator=(WriteUnpreparedTxnReadCallback&&) =
78
      delete;
79
80
0
  ~WriteUnpreparedTxnReadCallback() override {
81
    // If it is not backed by snapshot, the caller must check validity
82
0
    assert(valid_checked_ || backed_by_snapshot_ == kBackedByDBSnapshot);
83
0
  }
84
85
  bool IsVisibleFullCheck(SequenceNumber seq) override;
86
87
0
  inline bool valid() {
88
0
    valid_checked_ = true;
89
0
    return snap_released_ == false;
90
0
  }
91
92
0
  void Refresh(SequenceNumber seq) override {
93
0
    max_visible_seq_ = std::max(max_visible_seq_, seq);
94
0
    wup_snapshot_ = seq;
95
0
  }
96
97
  static SequenceNumber CalcMaxVisibleSeq(
98
      const std::map<SequenceNumber, size_t>& unprep_seqs,
99
0
      SequenceNumber snapshot_seq) {
100
0
    SequenceNumber max_unprepared = 0;
101
0
    if (unprep_seqs.size()) {
102
0
      max_unprepared =
103
0
          unprep_seqs.rbegin()->first + unprep_seqs.rbegin()->second - 1;
104
0
    }
105
0
    return std::max(max_unprepared, snapshot_seq);
106
0
  }
107
108
 private:
109
  WritePreparedTxnDB* db_;
110
  const std::map<SequenceNumber, size_t>& unprep_seqs_;
111
  SequenceNumber wup_snapshot_;
112
  // Whether max_visible_seq_ is backed by a snapshot
113
  const SnapshotBackup backed_by_snapshot_;
114
  bool snap_released_ = false;
115
  // Safety check to ensure that the caller has checked invalid statuses
116
  bool valid_checked_ = false;
117
};
118
119
class WriteUnpreparedTxn : public WritePreparedTxn {
120
 public:
121
  WriteUnpreparedTxn(WriteUnpreparedTxnDB* db,
122
                     const WriteOptions& write_options,
123
                     const TransactionOptions& txn_options);
124
125
  ~WriteUnpreparedTxn() override;
126
127
  using TransactionBaseImpl::Put;
128
  Status Put(ColumnFamilyHandle* column_family, const Slice& key,
129
             const Slice& value, const bool assume_tracked = false) override;
130
  Status Put(ColumnFamilyHandle* column_family, const SliceParts& key,
131
             const SliceParts& value,
132
             const bool assume_tracked = false) override;
133
134
  using TransactionBaseImpl::Merge;
135
  Status Merge(ColumnFamilyHandle* column_family, const Slice& key,
136
               const Slice& value, const bool assume_tracked = false) override;
137
138
  using TransactionBaseImpl::Delete;
139
  Status Delete(ColumnFamilyHandle* column_family, const Slice& key,
140
                const bool assume_tracked = false) override;
141
  Status Delete(ColumnFamilyHandle* column_family, const SliceParts& key,
142
                const bool assume_tracked = false) override;
143
144
  using TransactionBaseImpl::SingleDelete;
145
  Status SingleDelete(ColumnFamilyHandle* column_family, const Slice& key,
146
                      const bool assume_tracked = false) override;
147
  Status SingleDelete(ColumnFamilyHandle* column_family, const SliceParts& key,
148
                      const bool assume_tracked = false) override;
149
150
  // In WriteUnprepared, untracked writes will break snapshot validation logic.
151
  // Snapshot validation will only check the largest sequence number of a key to
152
  // see if it was committed or not. However, an untracked unprepared write will
153
  // hide smaller committed sequence numbers.
154
  //
155
  // TODO(lth): Investigate whether it is worth having snapshot validation
156
  // validate all values larger than snap_seq. Otherwise, we should return
157
  // Status::NotSupported for untracked writes.
158
159
  Status RebuildFromWriteBatch(WriteBatch*) override;
160
161
0
  uint64_t GetLastLogNumber() const override { return last_log_number_; }
162
163
0
  void RemoveActiveIterator(Iterator* iter) {
164
0
    active_iterators_.erase(
165
0
        std::remove(active_iterators_.begin(), active_iterators_.end(), iter),
166
0
        active_iterators_.end());
167
0
  }
168
169
 protected:
170
  void Initialize(const TransactionOptions& txn_options) override;
171
172
  Status PrepareInternal() override;
173
174
  Status CommitWithoutPrepareInternal() override;
175
  Status CommitInternal() override;
176
177
  Status RollbackInternal() override;
178
179
  void Clear() override;
180
181
  void SetSavePoint() override;
182
  Status RollbackToSavePoint() override;
183
  Status PopSavePoint() override;
184
185
  // Read APIs need to be overridden so that a ReadCallback handles
186
  // read-your-own-write.
187
  using Transaction::Get;
188
  Status Get(const ReadOptions& _read_options,
189
             ColumnFamilyHandle* column_family, const Slice& key,
190
             PinnableSlice* value) override;
191
192
  using Transaction::MultiGet;
193
  void MultiGet(const ReadOptions& _read_options,
194
                ColumnFamilyHandle* column_family, const size_t num_keys,
195
                const Slice* keys, PinnableSlice* values, Status* statuses,
196
                const bool sorted_input = false) override;
197
198
  using Transaction::GetIterator;
199
  Iterator* GetIterator(const ReadOptions& options) override;
200
  Iterator* GetIterator(const ReadOptions& options,
201
                        ColumnFamilyHandle* column_family) override;
202
203
  Status ValidateSnapshot(ColumnFamilyHandle* column_family, const Slice& key,
204
                          SequenceNumber* tracked_at_seq) override;
205
206
 private:
207
  friend class WriteUnpreparedTransactionTest_ReadYourOwnWrite_Test;
208
  friend class WriteUnpreparedTransactionTest_RecoveryTest_Test;
209
  friend class WriteUnpreparedTransactionTest_UnpreparedBatch_Test;
210
  friend class WriteUnpreparedTxnDB;
211
212
  const std::map<SequenceNumber, size_t>& GetUnpreparedSequenceNumbers();
213
  using Transaction::GetImpl;
214
  Status GetImpl(const ReadOptions& options, ColumnFamilyHandle* column_family,
215
                 const Slice& key, PinnableSlice* value) override;
216
217
  Status GetEntityImpl(const ReadOptions& options,
218
                       ColumnFamilyHandle* column_family, const Slice& key,
219
                       PinnableWideColumns* columns) override;
220
221
  void MultiGetEntityImpl(const ReadOptions& options,
222
                          ColumnFamilyHandle* column_family, size_t num_keys,
223
                          const Slice* keys, PinnableWideColumns* results,
224
                          Status* statuses, bool sorted_input) override;
225
226
  Status WriteRollbackKeys(const LockTracker& tracked_keys,
227
                           WriteBatchWithIndex* rollback_batch,
228
                           ReadCallback* callback, const ReadOptions& roptions);
229
230
  Status MaybeFlushWriteBatchToDB();
231
  Status FlushWriteBatchToDB(bool prepared);
232
  Status FlushWriteBatchToDBInternal(bool prepared);
233
  Status FlushWriteBatchWithSavePointToDB();
234
  Status RollbackToSavePointInternal();
235
  Status HandleWrite(const std::function<Status()>& do_write);
236
237
  // For write unprepared, we check on every writebatch append to see if
238
  // write_batch_flush_threshold_ has been exceeded, and then call
239
  // FlushWriteBatchToDB if so. This logic is encapsulated in
240
  // MaybeFlushWriteBatchToDB.
241
  int64_t write_batch_flush_threshold_;
242
  WriteUnpreparedTxnDB* wupt_db_;
243
244
  // Ordered list of unprep_seq sequence numbers that we have already written
245
  // to DB.
246
  //
247
  // This maps unprep_seq => prepare_batch_cnt for each unprepared batch
248
  // written by this transaction.
249
  //
250
  // Note that this contains both prepared and unprepared batches, since they
251
  // are treated similarily in prepare heap/commit map, so it simplifies the
252
  // commit callbacks.
253
  std::map<SequenceNumber, size_t> unprep_seqs_;
254
255
  uint64_t last_log_number_;
256
257
  // Recovered transactions have tracked_keys_ populated, but are not actually
258
  // locked for efficiency reasons. For recovered transactions, skip unlocking
259
  // keys when transaction ends.
260
  bool recovered_txn_;
261
262
  // Track the largest sequence number at which we performed snapshot
263
  // validation. If snapshot validation was skipped because no snapshot was set,
264
  // then this is set to GetLastPublishedSequence. This value is useful because
265
  // it means that for keys that have unprepared seqnos, we can guarantee that
266
  // no committed keys by other transactions can exist between
267
  // largest_validated_seq_ and max_unprep_seq. See
268
  // WriteUnpreparedTxnDB::NewIterator for an explanation for why this is
269
  // necessary for iterator Prev().
270
  //
271
  // Currently this value only increases during the lifetime of a transaction,
272
  // but in some cases, we should be able to restore the previously largest
273
  // value when calling RollbackToSavepoint.
274
  SequenceNumber largest_validated_seq_;
275
276
  struct SavePoint {
277
    // Record of unprep_seqs_ at this savepoint. The set of unprep_seq is
278
    // used during RollbackToSavepoint to determine visibility when restoring
279
    // old values.
280
    //
281
    // TODO(lth): Since all unprep_seqs_ sets further down the stack must be
282
    // subsets, this can potentially be deduplicated by just storing set
283
    // difference. Investigate if this is worth it.
284
    std::map<SequenceNumber, size_t> unprep_seqs_;
285
286
    // This snapshot will be used to read keys at this savepoint if we call
287
    // RollbackToSavePoint.
288
    std::unique_ptr<ManagedSnapshot> snapshot_;
289
290
    SavePoint(const std::map<SequenceNumber, size_t>& seqs,
291
              ManagedSnapshot* snapshot)
292
0
        : unprep_seqs_(seqs), snapshot_(snapshot) {}
293
  };
294
295
  // We have 3 data structures holding savepoint information:
296
  // 1. TransactionBaseImpl::save_points_
297
  // 2. WriteUnpreparedTxn::flushed_save_points_
298
  // 3. WriteUnpreparecTxn::unflushed_save_points_
299
  //
300
  // TransactionBaseImpl::save_points_ holds information about all write
301
  // batches, including the current in-memory write_batch_, or unprepared
302
  // batches that have been written out. Its responsibility is just to track
303
  // which keys have been modified in every savepoint.
304
  //
305
  // WriteUnpreparedTxn::flushed_save_points_ holds information about savepoints
306
  // set on unprepared batches that have already flushed. It holds the snapshot
307
  // and unprep_seqs at that savepoint, so that the rollback process can
308
  // determine which keys were visible at that point in time.
309
  //
310
  // WriteUnpreparecTxn::unflushed_save_points_ holds information about
311
  // savepoints on the current in-memory write_batch_. It simply records the
312
  // size of the write batch at every savepoint.
313
  //
314
  // TODO(lth): Remove the redundancy between save_point_boundaries_ and
315
  // write_batch_.save_points_.
316
  //
317
  // Based on this information, here are some invariants:
318
  // size(unflushed_save_points_) = size(write_batch_.save_points_)
319
  // size(flushed_save_points_) + size(unflushed_save_points_)
320
  //   = size(save_points_)
321
  //
322
  std::unique_ptr<autovector<WriteUnpreparedTxn::SavePoint>>
323
      flushed_save_points_;
324
  std::unique_ptr<autovector<size_t>> unflushed_save_points_;
325
326
  // It is currently unsafe to flush a write batch if there are active iterators
327
  // created from this transaction. This is because we use WriteBatchWithIndex
328
  // to do merging reads from the DB and the write batch. If we flush the write
329
  // batch, it is possible that the delta iterator on the iterator will point to
330
  // invalid memory.
331
  std::vector<Iterator*> active_iterators_;
332
333
  // Untracked keys that we have to rollback.
334
  //
335
  // TODO(lth): Currently we we do not record untracked keys per-savepoint.
336
  // This means that when rolling back to savepoints, we have to check all
337
  // keys in the current transaction for rollback. Note that this is only
338
  // inefficient, but still correct because we take a snapshot at every
339
  // savepoint, and we will use that snapshot to construct the rollback batch.
340
  // The rollback batch will then contain a reissue of the same marker.
341
  //
342
  // A more optimal solution would be to only check keys changed since the
343
  // last savepoint. Also, it may make sense to merge this into tracked_keys_
344
  // and differentiate between tracked but not locked keys to avoid having two
345
  // very similar data structures.
346
  using KeySet = std::unordered_map<uint32_t, std::vector<std::string>>;
347
  KeySet untracked_keys_;
348
};
349
350
}  // namespace ROCKSDB_NAMESPACE