/src/rocksdb/utilities/transactions/write_unprepared_txn.cc
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 | | #include "utilities/transactions/write_unprepared_txn.h" |
7 | | |
8 | | #include "db/db_impl/db_impl.h" |
9 | | #include "util/cast_util.h" |
10 | | #include "utilities/transactions/write_unprepared_txn_db.h" |
11 | | #include "utilities/write_batch_with_index/write_batch_with_index_internal.h" |
12 | | |
13 | | namespace ROCKSDB_NAMESPACE { |
14 | | |
15 | 0 | bool WriteUnpreparedTxnReadCallback::IsVisibleFullCheck(SequenceNumber seq) { |
16 | | // Since unprep_seqs maps prep_seq => prepare_batch_cnt, to check if seq is |
17 | | // in unprep_seqs, we have to check if seq is equal to prep_seq or any of |
18 | | // the prepare_batch_cnt seq nums after it. |
19 | | // |
20 | | // TODO(lth): Can be optimized with std::lower_bound if unprep_seqs is |
21 | | // large. |
22 | 0 | for (const auto& it : unprep_seqs_) { |
23 | 0 | if (it.first <= seq && seq < it.first + it.second) { |
24 | 0 | return true; |
25 | 0 | } |
26 | 0 | } |
27 | | |
28 | 0 | bool snap_released = false; |
29 | 0 | auto ret = |
30 | 0 | db_->IsInSnapshot(seq, wup_snapshot_, min_uncommitted_, &snap_released); |
31 | 0 | assert(!snap_released || backed_by_snapshot_ == kUnbackedByDBSnapshot); |
32 | 0 | snap_released_ |= snap_released; |
33 | 0 | return ret; |
34 | 0 | } |
35 | | |
36 | | WriteUnpreparedTxn::WriteUnpreparedTxn(WriteUnpreparedTxnDB* txn_db, |
37 | | const WriteOptions& write_options, |
38 | | const TransactionOptions& txn_options) |
39 | 0 | : WritePreparedTxn(txn_db, write_options, txn_options), |
40 | 0 | wupt_db_(txn_db), |
41 | 0 | last_log_number_(0), |
42 | 0 | recovered_txn_(false), |
43 | 0 | largest_validated_seq_(0) { |
44 | 0 | if (txn_options.write_batch_flush_threshold < 0) { |
45 | 0 | write_batch_flush_threshold_ = |
46 | 0 | txn_db_impl_->GetTxnDBOptions().default_write_batch_flush_threshold; |
47 | 0 | } else { |
48 | 0 | write_batch_flush_threshold_ = txn_options.write_batch_flush_threshold; |
49 | 0 | } |
50 | 0 | } |
51 | | |
52 | 0 | WriteUnpreparedTxn::~WriteUnpreparedTxn() { |
53 | 0 | if (!unprep_seqs_.empty()) { |
54 | 0 | assert(log_number_ > 0); |
55 | 0 | assert(GetId() > 0); |
56 | 0 | assert(!name_.empty()); |
57 | | |
58 | | // We should rollback regardless of GetState, but some unit tests that |
59 | | // test crash recovery run the destructor assuming that rollback does not |
60 | | // happen, so that rollback during recovery can be exercised. |
61 | 0 | if (GetState() == STARTED || GetState() == LOCKS_STOLEN) { |
62 | 0 | auto s = RollbackInternal(); |
63 | 0 | assert(s.ok()); |
64 | 0 | if (!s.ok()) { |
65 | 0 | ROCKS_LOG_FATAL( |
66 | 0 | wupt_db_->info_log_, |
67 | 0 | "Rollback of WriteUnprepared transaction failed in destructor: %s", |
68 | 0 | s.ToString().c_str()); |
69 | 0 | } |
70 | 0 | dbimpl_->logs_with_prep_tracker()->MarkLogAsHavingPrepSectionFlushed( |
71 | 0 | log_number_); |
72 | 0 | } |
73 | 0 | } |
74 | | |
75 | | // Clear the tracked locks so that ~PessimisticTransaction does not |
76 | | // try to unlock keys for recovered transactions. |
77 | 0 | if (recovered_txn_) { |
78 | 0 | tracked_locks_->Clear(); |
79 | 0 | } |
80 | 0 | } |
81 | | |
82 | 0 | void WriteUnpreparedTxn::Initialize(const TransactionOptions& txn_options) { |
83 | 0 | PessimisticTransaction::Initialize(txn_options); |
84 | 0 | if (txn_options.write_batch_flush_threshold < 0) { |
85 | 0 | write_batch_flush_threshold_ = |
86 | 0 | txn_db_impl_->GetTxnDBOptions().default_write_batch_flush_threshold; |
87 | 0 | } else { |
88 | 0 | write_batch_flush_threshold_ = txn_options.write_batch_flush_threshold; |
89 | 0 | } |
90 | |
|
91 | 0 | unprep_seqs_.clear(); |
92 | 0 | flushed_save_points_.reset(nullptr); |
93 | 0 | unflushed_save_points_.reset(nullptr); |
94 | 0 | recovered_txn_ = false; |
95 | 0 | largest_validated_seq_ = 0; |
96 | 0 | assert(active_iterators_.empty()); |
97 | 0 | active_iterators_.clear(); |
98 | 0 | untracked_keys_.clear(); |
99 | 0 | } |
100 | | |
101 | | Status WriteUnpreparedTxn::HandleWrite( |
102 | 0 | const std::function<Status()>& do_write) { |
103 | 0 | Status s; |
104 | 0 | if (active_iterators_.empty()) { |
105 | 0 | s = MaybeFlushWriteBatchToDB(); |
106 | 0 | if (!s.ok()) { |
107 | 0 | return s; |
108 | 0 | } |
109 | 0 | } |
110 | 0 | s = do_write(); |
111 | 0 | if (s.ok()) { |
112 | 0 | if (snapshot_) { |
113 | 0 | largest_validated_seq_ = |
114 | 0 | std::max(largest_validated_seq_, snapshot_->GetSequenceNumber()); |
115 | 0 | } else { |
116 | | // TODO(lth): We should use the same number as tracked_at_seq in TryLock, |
117 | | // because what is actually being tracked is the sequence number at which |
118 | | // this key was locked at. |
119 | 0 | largest_validated_seq_ = db_impl_->GetLastPublishedSequence(); |
120 | 0 | } |
121 | 0 | } |
122 | 0 | return s; |
123 | 0 | } |
124 | | |
125 | | Status WriteUnpreparedTxn::Put(ColumnFamilyHandle* column_family, |
126 | | const Slice& key, const Slice& value, |
127 | 0 | const bool assume_tracked) { |
128 | 0 | return HandleWrite([&]() { |
129 | 0 | return TransactionBaseImpl::Put(column_family, key, value, assume_tracked); |
130 | 0 | }); |
131 | 0 | } |
132 | | |
133 | | Status WriteUnpreparedTxn::Put(ColumnFamilyHandle* column_family, |
134 | | const SliceParts& key, const SliceParts& value, |
135 | 0 | const bool assume_tracked) { |
136 | 0 | return HandleWrite([&]() { |
137 | 0 | return TransactionBaseImpl::Put(column_family, key, value, assume_tracked); |
138 | 0 | }); |
139 | 0 | } |
140 | | |
141 | | Status WriteUnpreparedTxn::Merge(ColumnFamilyHandle* column_family, |
142 | | const Slice& key, const Slice& value, |
143 | 0 | const bool assume_tracked) { |
144 | 0 | return HandleWrite([&]() { |
145 | 0 | return TransactionBaseImpl::Merge(column_family, key, value, |
146 | 0 | assume_tracked); |
147 | 0 | }); |
148 | 0 | } |
149 | | |
150 | | Status WriteUnpreparedTxn::Delete(ColumnFamilyHandle* column_family, |
151 | 0 | const Slice& key, const bool assume_tracked) { |
152 | 0 | return HandleWrite([&]() { |
153 | 0 | return TransactionBaseImpl::Delete(column_family, key, assume_tracked); |
154 | 0 | }); |
155 | 0 | } |
156 | | |
157 | | Status WriteUnpreparedTxn::Delete(ColumnFamilyHandle* column_family, |
158 | | const SliceParts& key, |
159 | 0 | const bool assume_tracked) { |
160 | 0 | return HandleWrite([&]() { |
161 | 0 | return TransactionBaseImpl::Delete(column_family, key, assume_tracked); |
162 | 0 | }); |
163 | 0 | } |
164 | | |
165 | | Status WriteUnpreparedTxn::SingleDelete(ColumnFamilyHandle* column_family, |
166 | | const Slice& key, |
167 | 0 | const bool assume_tracked) { |
168 | 0 | return HandleWrite([&]() { |
169 | 0 | return TransactionBaseImpl::SingleDelete(column_family, key, |
170 | 0 | assume_tracked); |
171 | 0 | }); |
172 | 0 | } |
173 | | |
174 | | Status WriteUnpreparedTxn::SingleDelete(ColumnFamilyHandle* column_family, |
175 | | const SliceParts& key, |
176 | 0 | const bool assume_tracked) { |
177 | 0 | return HandleWrite([&]() { |
178 | 0 | return TransactionBaseImpl::SingleDelete(column_family, key, |
179 | 0 | assume_tracked); |
180 | 0 | }); |
181 | 0 | } |
182 | | |
183 | | // WriteUnpreparedTxn::RebuildFromWriteBatch is only called on recovery. For |
184 | | // WriteUnprepared, the write batches have already been written into the |
185 | | // database during WAL replay, so all we have to do is just to "retrack" the key |
186 | | // so that rollbacks are possible. |
187 | | // |
188 | | // Calling TryLock instead of TrackKey is also possible, but as an optimization, |
189 | | // recovered transactions do not hold locks on their keys. This follows the |
190 | | // implementation in PessimisticTransactionDB::Initialize where we set |
191 | | // skip_concurrency_control to true. |
192 | 0 | Status WriteUnpreparedTxn::RebuildFromWriteBatch(WriteBatch* wb) { |
193 | 0 | struct TrackKeyHandler : public WriteBatch::Handler { |
194 | 0 | WriteUnpreparedTxn* txn_; |
195 | 0 | bool rollback_merge_operands_; |
196 | |
|
197 | 0 | TrackKeyHandler(WriteUnpreparedTxn* txn, bool rollback_merge_operands) |
198 | 0 | : txn_(txn), rollback_merge_operands_(rollback_merge_operands) {} |
199 | |
|
200 | 0 | Status PutCF(uint32_t cf, const Slice& key, const Slice&) override { |
201 | 0 | txn_->TrackKey(cf, key.ToString(), kMaxSequenceNumber, |
202 | 0 | false /* read_only */, true /* exclusive */); |
203 | 0 | return Status::OK(); |
204 | 0 | } |
205 | |
|
206 | 0 | Status DeleteCF(uint32_t cf, const Slice& key) override { |
207 | 0 | txn_->TrackKey(cf, key.ToString(), kMaxSequenceNumber, |
208 | 0 | false /* read_only */, true /* exclusive */); |
209 | 0 | return Status::OK(); |
210 | 0 | } |
211 | |
|
212 | 0 | Status SingleDeleteCF(uint32_t cf, const Slice& key) override { |
213 | 0 | txn_->TrackKey(cf, key.ToString(), kMaxSequenceNumber, |
214 | 0 | false /* read_only */, true /* exclusive */); |
215 | 0 | return Status::OK(); |
216 | 0 | } |
217 | |
|
218 | 0 | Status MergeCF(uint32_t cf, const Slice& key, const Slice&) override { |
219 | 0 | if (rollback_merge_operands_) { |
220 | 0 | txn_->TrackKey(cf, key.ToString(), kMaxSequenceNumber, |
221 | 0 | false /* read_only */, true /* exclusive */); |
222 | 0 | } |
223 | 0 | return Status::OK(); |
224 | 0 | } |
225 | | |
226 | | // Recovered batches do not contain 2PC markers. |
227 | 0 | Status MarkBeginPrepare(bool) override { return Status::InvalidArgument(); } |
228 | |
|
229 | 0 | Status MarkEndPrepare(const Slice&) override { |
230 | 0 | return Status::InvalidArgument(); |
231 | 0 | } |
232 | |
|
233 | 0 | Status MarkNoop(bool) override { return Status::InvalidArgument(); } |
234 | |
|
235 | 0 | Status MarkCommit(const Slice&) override { |
236 | 0 | return Status::InvalidArgument(); |
237 | 0 | } |
238 | |
|
239 | 0 | Status MarkRollback(const Slice&) override { |
240 | 0 | return Status::InvalidArgument(); |
241 | 0 | } |
242 | 0 | }; |
243 | |
|
244 | 0 | TrackKeyHandler handler(this, |
245 | 0 | wupt_db_->txn_db_options_.rollback_merge_operands); |
246 | 0 | return wb->Iterate(&handler); |
247 | 0 | } |
248 | | |
249 | 0 | Status WriteUnpreparedTxn::MaybeFlushWriteBatchToDB() { |
250 | 0 | const bool kPrepared = true; |
251 | 0 | Status s; |
252 | 0 | if (write_batch_flush_threshold_ > 0 && |
253 | 0 | write_batch_.GetWriteBatch()->Count() > 0 && |
254 | 0 | write_batch_.GetDataSize() > |
255 | 0 | static_cast<size_t>(write_batch_flush_threshold_)) { |
256 | 0 | assert(GetState() != PREPARED); |
257 | 0 | s = FlushWriteBatchToDB(!kPrepared); |
258 | 0 | } |
259 | 0 | return s; |
260 | 0 | } |
261 | | |
262 | 0 | Status WriteUnpreparedTxn::FlushWriteBatchToDB(bool prepared) { |
263 | | // If the current write batch contains savepoints, then some special handling |
264 | | // is required so that RollbackToSavepoint can work. |
265 | | // |
266 | | // RollbackToSavepoint is not supported after Prepare() is called, so only do |
267 | | // this for unprepared batches. |
268 | 0 | if (!prepared && unflushed_save_points_ != nullptr && |
269 | 0 | !unflushed_save_points_->empty()) { |
270 | 0 | return FlushWriteBatchWithSavePointToDB(); |
271 | 0 | } |
272 | | |
273 | 0 | return FlushWriteBatchToDBInternal(prepared); |
274 | 0 | } |
275 | | |
276 | 0 | Status WriteUnpreparedTxn::FlushWriteBatchToDBInternal(bool prepared) { |
277 | 0 | if (name_.empty()) { |
278 | 0 | assert(!prepared); |
279 | | #ifndef NDEBUG |
280 | | static std::atomic_ullong autogen_id{0}; |
281 | | // To avoid changing all tests to call SetName, just autogenerate one. |
282 | | if (wupt_db_->txn_db_options_.autogenerate_name) { |
283 | | auto s = SetName(std::string("autoxid") + |
284 | | std::to_string(autogen_id.fetch_add(1))); |
285 | | assert(s.ok()); |
286 | | } else |
287 | | #endif |
288 | 0 | { |
289 | 0 | return Status::InvalidArgument("Cannot write to DB without SetName."); |
290 | 0 | } |
291 | 0 | } |
292 | | |
293 | 0 | struct UntrackedKeyHandler : public WriteBatch::Handler { |
294 | 0 | WriteUnpreparedTxn* txn_; |
295 | 0 | bool rollback_merge_operands_; |
296 | |
|
297 | 0 | UntrackedKeyHandler(WriteUnpreparedTxn* txn, bool rollback_merge_operands) |
298 | 0 | : txn_(txn), rollback_merge_operands_(rollback_merge_operands) {} |
299 | |
|
300 | 0 | Status AddUntrackedKey(uint32_t cf, const Slice& key) { |
301 | 0 | auto str = key.ToString(); |
302 | 0 | PointLockStatus lock_status = |
303 | 0 | txn_->tracked_locks_->GetPointLockStatus(cf, str); |
304 | 0 | if (!lock_status.locked) { |
305 | 0 | txn_->untracked_keys_[cf].push_back(str); |
306 | 0 | } |
307 | 0 | return Status::OK(); |
308 | 0 | } |
309 | |
|
310 | 0 | Status PutCF(uint32_t cf, const Slice& key, const Slice&) override { |
311 | 0 | return AddUntrackedKey(cf, key); |
312 | 0 | } |
313 | |
|
314 | 0 | Status DeleteCF(uint32_t cf, const Slice& key) override { |
315 | 0 | return AddUntrackedKey(cf, key); |
316 | 0 | } |
317 | |
|
318 | 0 | Status SingleDeleteCF(uint32_t cf, const Slice& key) override { |
319 | 0 | return AddUntrackedKey(cf, key); |
320 | 0 | } |
321 | |
|
322 | 0 | Status MergeCF(uint32_t cf, const Slice& key, const Slice&) override { |
323 | 0 | if (rollback_merge_operands_) { |
324 | 0 | return AddUntrackedKey(cf, key); |
325 | 0 | } |
326 | 0 | return Status::OK(); |
327 | 0 | } |
328 | | |
329 | | // The only expected 2PC marker is the initial Noop marker. |
330 | 0 | Status MarkNoop(bool empty_batch) override { |
331 | 0 | return empty_batch ? Status::OK() : Status::InvalidArgument(); |
332 | 0 | } |
333 | |
|
334 | 0 | Status MarkBeginPrepare(bool) override { return Status::InvalidArgument(); } |
335 | |
|
336 | 0 | Status MarkEndPrepare(const Slice&) override { |
337 | 0 | return Status::InvalidArgument(); |
338 | 0 | } |
339 | |
|
340 | 0 | Status MarkCommit(const Slice&) override { |
341 | 0 | return Status::InvalidArgument(); |
342 | 0 | } |
343 | |
|
344 | 0 | Status MarkRollback(const Slice&) override { |
345 | 0 | return Status::InvalidArgument(); |
346 | 0 | } |
347 | 0 | }; |
348 | |
|
349 | 0 | UntrackedKeyHandler handler( |
350 | 0 | this, wupt_db_->txn_db_options_.rollback_merge_operands); |
351 | 0 | auto s = GetWriteBatch()->GetWriteBatch()->Iterate(&handler); |
352 | 0 | assert(s.ok()); |
353 | | |
354 | | // TODO(lth): Reduce duplicate code with WritePrepared prepare logic. |
355 | 0 | WriteOptions write_options = write_options_; |
356 | 0 | write_options.disableWAL = false; |
357 | 0 | const bool WRITE_AFTER_COMMIT = true; |
358 | 0 | const bool first_prepare_batch = log_number_ == 0; |
359 | | // MarkEndPrepare will change Noop marker to the appropriate marker. |
360 | 0 | s = WriteBatchInternal::MarkEndPrepare(GetWriteBatch()->GetWriteBatch(), |
361 | 0 | name_, !WRITE_AFTER_COMMIT, !prepared); |
362 | 0 | assert(s.ok()); |
363 | | // For each duplicate key we account for a new sub-batch |
364 | 0 | prepare_batch_cnt_ = GetWriteBatch()->SubBatchCnt(); |
365 | | // AddPrepared better to be called in the pre-release callback otherwise there |
366 | | // is a non-zero chance of max advancing prepare_seq and readers assume the |
367 | | // data as committed. |
368 | | // Also having it in the PreReleaseCallback allows in-order addition of |
369 | | // prepared entries to PreparedHeap and hence enables an optimization. Refer |
370 | | // to SmallestUnCommittedSeq for more details. |
371 | 0 | AddPreparedCallback add_prepared_callback( |
372 | 0 | wpt_db_, db_impl_, prepare_batch_cnt_, |
373 | 0 | db_impl_->immutable_db_options().two_write_queues, first_prepare_batch); |
374 | 0 | const bool DISABLE_MEMTABLE = true; |
375 | 0 | uint64_t seq_used = kMaxSequenceNumber; |
376 | | // log_number_ should refer to the oldest log containing uncommitted data |
377 | | // from the current transaction. This means that if log_number_ is set, |
378 | | // WriteImpl should not overwrite that value, so set wal_used to nullptr if |
379 | | // log_number_ is already set. |
380 | 0 | s = db_impl_->WriteImpl(write_options, GetWriteBatch()->GetWriteBatch(), |
381 | 0 | /*callback*/ nullptr, /*user_write_cb=*/nullptr, |
382 | 0 | &last_log_number_, |
383 | 0 | /*log ref*/ 0, !DISABLE_MEMTABLE, &seq_used, |
384 | 0 | prepare_batch_cnt_, &add_prepared_callback); |
385 | 0 | if (log_number_ == 0) { |
386 | 0 | log_number_ = last_log_number_; |
387 | 0 | } |
388 | 0 | assert(!s.ok() || seq_used != kMaxSequenceNumber); |
389 | 0 | auto prepare_seq = seq_used; |
390 | | |
391 | | // Only call SetId if it hasn't been set yet. |
392 | 0 | if (GetId() == 0) { |
393 | 0 | SetId(prepare_seq); |
394 | 0 | } |
395 | | // unprep_seqs_ will also contain prepared seqnos since they are treated in |
396 | | // the same way in the prepare/commit callbacks. See the comment on the |
397 | | // definition of unprep_seqs_. |
398 | 0 | if (s.ok()) { |
399 | 0 | unprep_seqs_[prepare_seq] = prepare_batch_cnt_; |
400 | 0 | } |
401 | | |
402 | | // Reset transaction state. |
403 | 0 | if (!prepared) { |
404 | 0 | prepare_batch_cnt_ = 0; |
405 | 0 | const bool kClear = true; |
406 | 0 | TransactionBaseImpl::InitWriteBatch(kClear); |
407 | 0 | } |
408 | |
|
409 | 0 | return s; |
410 | 0 | } |
411 | | |
412 | 0 | Status WriteUnpreparedTxn::FlushWriteBatchWithSavePointToDB() { |
413 | 0 | assert(unflushed_save_points_ != nullptr && |
414 | 0 | unflushed_save_points_->size() > 0); |
415 | 0 | assert(save_points_ != nullptr && save_points_->size() > 0); |
416 | 0 | assert(save_points_->size() >= unflushed_save_points_->size()); |
417 | | |
418 | | // Handler class for creating an unprepared batch from a savepoint. |
419 | 0 | struct SavePointBatchHandler : public WriteBatch::Handler { |
420 | 0 | WriteBatchWithIndex* wb_; |
421 | 0 | const std::map<uint32_t, ColumnFamilyHandle*>& handles_; |
422 | |
|
423 | 0 | SavePointBatchHandler( |
424 | 0 | WriteBatchWithIndex* wb, |
425 | 0 | const std::map<uint32_t, ColumnFamilyHandle*>& handles) |
426 | 0 | : wb_(wb), handles_(handles) {} |
427 | |
|
428 | 0 | Status PutCF(uint32_t cf, const Slice& key, const Slice& value) override { |
429 | 0 | return wb_->Put(handles_.at(cf), key, value); |
430 | 0 | } |
431 | |
|
432 | 0 | Status DeleteCF(uint32_t cf, const Slice& key) override { |
433 | 0 | return wb_->Delete(handles_.at(cf), key); |
434 | 0 | } |
435 | |
|
436 | 0 | Status SingleDeleteCF(uint32_t cf, const Slice& key) override { |
437 | 0 | return wb_->SingleDelete(handles_.at(cf), key); |
438 | 0 | } |
439 | |
|
440 | 0 | Status MergeCF(uint32_t cf, const Slice& key, const Slice& value) override { |
441 | 0 | return wb_->Merge(handles_.at(cf), key, value); |
442 | 0 | } |
443 | | |
444 | | // The only expected 2PC marker is the initial Noop marker. |
445 | 0 | Status MarkNoop(bool empty_batch) override { |
446 | 0 | return empty_batch ? Status::OK() : Status::InvalidArgument(); |
447 | 0 | } |
448 | |
|
449 | 0 | Status MarkBeginPrepare(bool) override { return Status::InvalidArgument(); } |
450 | |
|
451 | 0 | Status MarkEndPrepare(const Slice&) override { |
452 | 0 | return Status::InvalidArgument(); |
453 | 0 | } |
454 | |
|
455 | 0 | Status MarkCommit(const Slice&) override { |
456 | 0 | return Status::InvalidArgument(); |
457 | 0 | } |
458 | |
|
459 | 0 | Status MarkRollback(const Slice&) override { |
460 | 0 | return Status::InvalidArgument(); |
461 | 0 | } |
462 | 0 | }; |
463 | | |
464 | | // The comparator of the default cf is passed in, similar to the |
465 | | // initialization of TransactionBaseImpl::write_batch_. This comparator is |
466 | | // only used if the write batch encounters an invalid cf id, and falls back to |
467 | | // this comparator. |
468 | 0 | WriteBatchWithIndex wb(wpt_db_->DefaultColumnFamily()->GetComparator(), 0, |
469 | 0 | true, 0, write_options_.protection_bytes_per_key); |
470 | | // Swap with write_batch_ so that wb contains the complete write batch. The |
471 | | // actual write batch that will be flushed to DB will be built in |
472 | | // write_batch_, and will be read by FlushWriteBatchToDBInternal. |
473 | 0 | std::swap(wb, write_batch_); |
474 | 0 | TransactionBaseImpl::InitWriteBatch(); |
475 | |
|
476 | 0 | size_t prev_boundary = WriteBatchInternal::kHeader; |
477 | 0 | const bool kPrepared = true; |
478 | 0 | for (size_t i = 0; i < unflushed_save_points_->size() + 1; i++) { |
479 | 0 | bool trailing_batch = i == unflushed_save_points_->size(); |
480 | 0 | SavePointBatchHandler sp_handler(&write_batch_, |
481 | 0 | *wupt_db_->GetCFHandleMap().get()); |
482 | 0 | size_t curr_boundary = trailing_batch ? wb.GetWriteBatch()->GetDataSize() |
483 | 0 | : (*unflushed_save_points_)[i]; |
484 | | |
485 | | // Construct the partial write batch up to the savepoint. |
486 | | // |
487 | | // Theoretically, a memcpy between the write batches should be sufficient |
488 | | // since the rewriting into the batch should produce the exact same byte |
489 | | // representation. Rebuilding the WriteBatchWithIndex index is still |
490 | | // necessary though, and would imply doing two passes over the batch though. |
491 | 0 | Status s = WriteBatchInternal::Iterate(wb.GetWriteBatch(), &sp_handler, |
492 | 0 | prev_boundary, curr_boundary); |
493 | 0 | if (!s.ok()) { |
494 | 0 | return s; |
495 | 0 | } |
496 | | |
497 | 0 | if (write_batch_.GetWriteBatch()->Count() > 0) { |
498 | | // Flush the write batch. |
499 | 0 | s = FlushWriteBatchToDBInternal(!kPrepared); |
500 | 0 | if (!s.ok()) { |
501 | 0 | return s; |
502 | 0 | } |
503 | 0 | } |
504 | | |
505 | 0 | if (!trailing_batch) { |
506 | 0 | if (flushed_save_points_ == nullptr) { |
507 | 0 | flushed_save_points_.reset( |
508 | 0 | new autovector<WriteUnpreparedTxn::SavePoint>()); |
509 | 0 | } |
510 | 0 | flushed_save_points_->emplace_back( |
511 | 0 | unprep_seqs_, new ManagedSnapshot(db_impl_, wupt_db_->GetSnapshot())); |
512 | 0 | } |
513 | |
|
514 | 0 | prev_boundary = curr_boundary; |
515 | 0 | const bool kClear = true; |
516 | 0 | TransactionBaseImpl::InitWriteBatch(kClear); |
517 | 0 | } |
518 | | |
519 | 0 | unflushed_save_points_->clear(); |
520 | 0 | return Status::OK(); |
521 | 0 | } |
522 | | |
523 | 0 | Status WriteUnpreparedTxn::PrepareInternal() { |
524 | 0 | const bool kPrepared = true; |
525 | 0 | return FlushWriteBatchToDB(kPrepared); |
526 | 0 | } |
527 | | |
528 | 0 | Status WriteUnpreparedTxn::CommitWithoutPrepareInternal() { |
529 | 0 | if (unprep_seqs_.empty()) { |
530 | 0 | assert(log_number_ == 0); |
531 | 0 | assert(GetId() == 0); |
532 | 0 | return WritePreparedTxn::CommitWithoutPrepareInternal(); |
533 | 0 | } |
534 | | |
535 | | // TODO(lth): We should optimize commit without prepare to not perform |
536 | | // a prepare under the hood. |
537 | 0 | auto s = PrepareInternal(); |
538 | 0 | if (!s.ok()) { |
539 | 0 | return s; |
540 | 0 | } |
541 | 0 | return CommitInternal(); |
542 | 0 | } |
543 | | |
544 | 0 | Status WriteUnpreparedTxn::CommitInternal() { |
545 | | // TODO(lth): Reduce duplicate code with WritePrepared commit logic. |
546 | | |
547 | | // We take the commit-time batch and append the Commit marker. The Memtable |
548 | | // will ignore the Commit marker in non-recovery mode |
549 | 0 | WriteBatch* working_batch = GetCommitTimeWriteBatch(); |
550 | 0 | const bool empty = working_batch->Count() == 0; |
551 | 0 | auto s = WriteBatchInternal::MarkCommit(working_batch, name_); |
552 | 0 | assert(s.ok()); |
553 | |
|
554 | 0 | const bool for_recovery = use_only_the_last_commit_time_batch_for_recovery_; |
555 | 0 | if (!empty) { |
556 | | // When not writing to memtable, we can still cache the latest write batch. |
557 | | // The cached batch will be written to memtable in WriteRecoverableState |
558 | | // during FlushMemTable |
559 | 0 | if (for_recovery) { |
560 | 0 | WriteBatchInternal::SetAsLatestPersistentState(working_batch); |
561 | 0 | } else { |
562 | 0 | return Status::InvalidArgument( |
563 | 0 | "Commit-time-batch can only be used if " |
564 | 0 | "use_only_the_last_commit_time_batch_for_recovery is true"); |
565 | 0 | } |
566 | 0 | } |
567 | | |
568 | 0 | const bool includes_data = !empty && !for_recovery; |
569 | 0 | size_t commit_batch_cnt = 0; |
570 | 0 | if (UNLIKELY(includes_data)) { |
571 | 0 | ROCKS_LOG_WARN(db_impl_->immutable_db_options().info_log, |
572 | 0 | "Duplicate key overhead"); |
573 | 0 | SubBatchCounter counter(*wpt_db_->GetCFComparatorMap()); |
574 | 0 | s = working_batch->Iterate(&counter); |
575 | 0 | assert(s.ok()); |
576 | 0 | commit_batch_cnt = counter.BatchCount(); |
577 | 0 | } |
578 | 0 | const bool disable_memtable = !includes_data; |
579 | 0 | const bool do_one_write = |
580 | 0 | !db_impl_->immutable_db_options().two_write_queues || disable_memtable; |
581 | |
|
582 | 0 | WriteUnpreparedCommitEntryPreReleaseCallback update_commit_map( |
583 | 0 | wpt_db_, db_impl_, unprep_seqs_, commit_batch_cnt); |
584 | 0 | const bool kFirstPrepareBatch = true; |
585 | 0 | AddPreparedCallback add_prepared_callback( |
586 | 0 | wpt_db_, db_impl_, commit_batch_cnt, |
587 | 0 | db_impl_->immutable_db_options().two_write_queues, !kFirstPrepareBatch); |
588 | 0 | PreReleaseCallback* pre_release_callback; |
589 | 0 | if (do_one_write) { |
590 | 0 | pre_release_callback = &update_commit_map; |
591 | 0 | } else { |
592 | 0 | pre_release_callback = &add_prepared_callback; |
593 | 0 | } |
594 | 0 | uint64_t seq_used = kMaxSequenceNumber; |
595 | | // Since the prepared batch is directly written to memtable, there is |
596 | | // already a connection between the memtable and its WAL, so there is no |
597 | | // need to redundantly reference the log that contains the prepared data. |
598 | 0 | const uint64_t zero_log_number = 0ull; |
599 | 0 | size_t batch_cnt = UNLIKELY(commit_batch_cnt) ? commit_batch_cnt : 1; |
600 | 0 | s = db_impl_->WriteImpl(write_options_, working_batch, nullptr, nullptr, |
601 | 0 | nullptr, zero_log_number, disable_memtable, &seq_used, |
602 | 0 | batch_cnt, pre_release_callback); |
603 | 0 | assert(!s.ok() || seq_used != kMaxSequenceNumber); |
604 | 0 | const SequenceNumber commit_batch_seq = seq_used; |
605 | 0 | if (LIKELY(do_one_write || !s.ok())) { |
606 | 0 | if (LIKELY(s.ok())) { |
607 | | // Note RemovePrepared should be called after WriteImpl that publishsed |
608 | | // the seq. Otherwise SmallestUnCommittedSeq optimization breaks. |
609 | 0 | for (const auto& seq : unprep_seqs_) { |
610 | 0 | wpt_db_->RemovePrepared(seq.first, seq.second); |
611 | 0 | } |
612 | 0 | unprep_seqs_.clear(); |
613 | 0 | flushed_save_points_.reset(nullptr); |
614 | 0 | unflushed_save_points_.reset(nullptr); |
615 | 0 | } else if (UNLIKELY(!do_one_write)) { |
616 | | // The commit write failed after add_prepared_callback may have added a |
617 | | // prepared entry for commit_batch_seq. Since commit_batch_seq is never |
618 | | // stored in unprep_seqs_ on this path, a follow-up Rollback() cannot |
619 | | // remove it, so clean it up here. unprep_seqs_ is otherwise preserved so |
620 | | // the transaction can still be rolled back after the caller recovers |
621 | | // from the write error. |
622 | 0 | wpt_db_->RemovePrepared(commit_batch_seq, commit_batch_cnt); |
623 | 0 | } |
624 | 0 | return s; |
625 | 0 | } // else do the 2nd write to publish seq |
626 | | |
627 | | // Populate unprep_seqs_ with commit_batch_seq, since we treat data in the |
628 | | // commit write batch as just another "unprepared" batch. This will also |
629 | | // update the unprep_seqs_ in the update_commit_map callback. |
630 | 0 | unprep_seqs_[commit_batch_seq] = commit_batch_cnt; |
631 | 0 | WriteUnpreparedCommitEntryPreReleaseCallback |
632 | 0 | update_commit_map_with_commit_batch(wpt_db_, db_impl_, unprep_seqs_, 0); |
633 | | |
634 | | // Note: the 2nd write comes with a performance penality. So if we have too |
635 | | // many of commits accompanied with ComitTimeWriteBatch and yet we cannot |
636 | | // enable use_only_the_last_commit_time_batch_for_recovery_ optimization, |
637 | | // two_write_queues should be disabled to avoid many additional writes here. |
638 | | |
639 | | // Update commit map only from the 2nd queue |
640 | 0 | WriteBatch empty_batch; |
641 | 0 | s = empty_batch.PutLogData(Slice()); |
642 | 0 | assert(s.ok()); |
643 | | // In the absence of Prepare markers, use Noop as a batch separator |
644 | 0 | s = WriteBatchInternal::InsertNoop(&empty_batch); |
645 | 0 | assert(s.ok()); |
646 | 0 | const bool DISABLE_MEMTABLE = true; |
647 | 0 | const size_t ONE_BATCH = 1; |
648 | 0 | const uint64_t NO_REF_LOG = 0; |
649 | 0 | s = db_impl_->WriteImpl(write_options_, &empty_batch, nullptr, nullptr, |
650 | 0 | nullptr, NO_REF_LOG, DISABLE_MEMTABLE, &seq_used, |
651 | 0 | ONE_BATCH, &update_commit_map_with_commit_batch); |
652 | 0 | assert(!s.ok() || seq_used != kMaxSequenceNumber); |
653 | | // Note RemovePrepared should be called after WriteImpl that publishsed the |
654 | | // seq. Otherwise SmallestUnCommittedSeq optimization breaks. |
655 | 0 | for (const auto& seq : unprep_seqs_) { |
656 | 0 | wpt_db_->RemovePrepared(seq.first, seq.second); |
657 | 0 | } |
658 | 0 | unprep_seqs_.clear(); |
659 | 0 | flushed_save_points_.reset(nullptr); |
660 | 0 | unflushed_save_points_.reset(nullptr); |
661 | 0 | return s; |
662 | 0 | } |
663 | | |
664 | | Status WriteUnpreparedTxn::WriteRollbackKeys( |
665 | | const LockTracker& lock_tracker, WriteBatchWithIndex* rollback_batch, |
666 | 0 | ReadCallback* callback, const ReadOptions& roptions) { |
667 | | // This assertion can be removed when range lock is supported. |
668 | 0 | assert(lock_tracker.IsPointLockSupported()); |
669 | 0 | const auto& cf_map = *wupt_db_->GetCFHandleMap(); |
670 | 0 | auto WriteRollbackKey = [&](const std::string& key, uint32_t cfid) { |
671 | 0 | const auto& cf_handle = cf_map.at(cfid); |
672 | 0 | PinnableSlice pinnable_val; |
673 | 0 | bool not_used; |
674 | 0 | DBImpl::GetImplOptions get_impl_options; |
675 | 0 | get_impl_options.column_family = cf_handle; |
676 | 0 | get_impl_options.value = &pinnable_val; |
677 | 0 | get_impl_options.value_found = ¬_used; |
678 | 0 | get_impl_options.callback = callback; |
679 | 0 | auto s = db_impl_->GetImpl(roptions, key, get_impl_options); |
680 | |
|
681 | 0 | if (s.ok()) { |
682 | 0 | s = rollback_batch->Put(cf_handle, key, pinnable_val); |
683 | 0 | assert(s.ok()); |
684 | 0 | } else if (s.IsNotFound()) { |
685 | 0 | if (wupt_db_->ShouldRollbackWithSingleDelete(cf_handle, key)) { |
686 | 0 | s = rollback_batch->SingleDelete(cf_handle, key); |
687 | 0 | } else { |
688 | 0 | s = rollback_batch->Delete(cf_handle, key); |
689 | 0 | } |
690 | 0 | assert(s.ok()); |
691 | 0 | } else { |
692 | 0 | return s; |
693 | 0 | } |
694 | | |
695 | 0 | return Status::OK(); |
696 | 0 | }; |
697 | |
|
698 | 0 | std::unique_ptr<LockTracker::ColumnFamilyIterator> cf_it( |
699 | 0 | lock_tracker.GetColumnFamilyIterator()); |
700 | 0 | assert(cf_it != nullptr); |
701 | 0 | while (cf_it->HasNext()) { |
702 | 0 | ColumnFamilyId cf = cf_it->Next(); |
703 | 0 | std::unique_ptr<LockTracker::KeyIterator> key_it( |
704 | 0 | lock_tracker.GetKeyIterator(cf)); |
705 | 0 | assert(key_it != nullptr); |
706 | 0 | while (key_it->HasNext()) { |
707 | 0 | const std::string& key = key_it->Next(); |
708 | 0 | auto s = WriteRollbackKey(key, cf); |
709 | 0 | if (!s.ok()) { |
710 | 0 | return s; |
711 | 0 | } |
712 | 0 | } |
713 | 0 | } |
714 | | |
715 | 0 | for (const auto& cfkey : untracked_keys_) { |
716 | 0 | const auto cfid = cfkey.first; |
717 | 0 | const auto& keys = cfkey.second; |
718 | 0 | for (const auto& key : keys) { |
719 | 0 | auto s = WriteRollbackKey(key, cfid); |
720 | 0 | if (!s.ok()) { |
721 | 0 | return s; |
722 | 0 | } |
723 | 0 | } |
724 | 0 | } |
725 | | |
726 | 0 | return Status::OK(); |
727 | 0 | } |
728 | | |
729 | 0 | Status WriteUnpreparedTxn::RollbackInternal() { |
730 | | // TODO(lth): Reduce duplicate code with WritePrepared rollback logic. |
731 | 0 | WriteBatchWithIndex rollback_batch( |
732 | 0 | wpt_db_->DefaultColumnFamily()->GetComparator(), 0, true, 0, |
733 | 0 | write_options_.protection_bytes_per_key); |
734 | 0 | assert(GetId() != kMaxSequenceNumber); |
735 | 0 | assert(GetId() > 0); |
736 | 0 | Status s; |
737 | 0 | auto read_at_seq = kMaxSequenceNumber; |
738 | | // TODO: plumb Env::IOActivity, Env::IOPriority |
739 | 0 | ReadOptions roptions; |
740 | | // to prevent callback's seq to be overrriden inside DBImpk::Get |
741 | 0 | roptions.snapshot = wpt_db_->GetMaxSnapshot(); |
742 | | // Note that we do not use WriteUnpreparedTxnReadCallback because we do not |
743 | | // need to read our own writes when reading prior versions of the key for |
744 | | // rollback. |
745 | 0 | WritePreparedTxnReadCallback callback(wpt_db_, read_at_seq); |
746 | | // TODO(lth): We write rollback batch all in a single batch here, but this |
747 | | // should be subdivded into multiple batches as well. In phase 2, when key |
748 | | // sets are read from WAL, this will happen naturally. |
749 | 0 | s = WriteRollbackKeys(*tracked_locks_, &rollback_batch, &callback, roptions); |
750 | 0 | if (!s.ok()) { |
751 | 0 | return s; |
752 | 0 | } |
753 | | |
754 | | // The Rollback marker will be used as a batch separator |
755 | 0 | s = WriteBatchInternal::MarkRollback(rollback_batch.GetWriteBatch(), name_); |
756 | 0 | assert(s.ok()); |
757 | 0 | bool do_one_write = !db_impl_->immutable_db_options().two_write_queues; |
758 | 0 | const bool DISABLE_MEMTABLE = true; |
759 | 0 | const uint64_t NO_REF_LOG = 0; |
760 | 0 | uint64_t seq_used = kMaxSequenceNumber; |
761 | | // Rollback batch may contain duplicate keys, because tracked_keys_ is not |
762 | | // comparator aware. |
763 | 0 | auto rollback_batch_cnt = rollback_batch.SubBatchCnt(); |
764 | | // We commit the rolled back prepared batches. Although this is |
765 | | // counter-intuitive, i) it is safe to do so, since the prepared batches are |
766 | | // already canceled out by the rollback batch, ii) adding the commit entry to |
767 | | // CommitCache will allow us to benefit from the existing mechanism in |
768 | | // CommitCache that keeps an entry evicted due to max advance and yet overlaps |
769 | | // with a live snapshot around so that the live snapshot properly skips the |
770 | | // entry even if its prepare seq is lower than max_evicted_seq_. |
771 | | // |
772 | | // TODO(lth): RollbackInternal is conceptually very similar to |
773 | | // CommitInternal, with the rollback batch simply taking on the role of |
774 | | // CommitTimeWriteBatch. We should be able to merge the two code paths. |
775 | 0 | WriteUnpreparedCommitEntryPreReleaseCallback update_commit_map( |
776 | 0 | wpt_db_, db_impl_, unprep_seqs_, rollback_batch_cnt); |
777 | | // Note: the rollback batch does not need AddPrepared since it is written to |
778 | | // DB in one shot. min_uncommitted still works since it requires capturing |
779 | | // data that is written to DB but not yet committed, while the rollback |
780 | | // batch commits with PreReleaseCallback. |
781 | 0 | s = db_impl_->WriteImpl(write_options_, rollback_batch.GetWriteBatch(), |
782 | 0 | nullptr, nullptr, nullptr, NO_REF_LOG, |
783 | 0 | !DISABLE_MEMTABLE, &seq_used, rollback_batch_cnt, |
784 | 0 | do_one_write ? &update_commit_map : nullptr); |
785 | 0 | assert(!s.ok() || seq_used != kMaxSequenceNumber); |
786 | 0 | if (!s.ok()) { |
787 | 0 | return s; |
788 | 0 | } |
789 | 0 | if (do_one_write) { |
790 | 0 | for (const auto& seq : unprep_seqs_) { |
791 | 0 | wpt_db_->RemovePrepared(seq.first, seq.second); |
792 | 0 | } |
793 | 0 | unprep_seqs_.clear(); |
794 | 0 | flushed_save_points_.reset(nullptr); |
795 | 0 | unflushed_save_points_.reset(nullptr); |
796 | 0 | return s; |
797 | 0 | } // else do the 2nd write for commit |
798 | | |
799 | 0 | uint64_t& prepare_seq = seq_used; |
800 | | // Populate unprep_seqs_ with rollback_batch_cnt, since we treat data in the |
801 | | // rollback write batch as just another "unprepared" batch. This will also |
802 | | // update the unprep_seqs_ in the update_commit_map callback. |
803 | 0 | unprep_seqs_[prepare_seq] = rollback_batch_cnt; |
804 | 0 | WriteUnpreparedCommitEntryPreReleaseCallback |
805 | 0 | update_commit_map_with_rollback_batch(wpt_db_, db_impl_, unprep_seqs_, 0); |
806 | |
|
807 | 0 | ROCKS_LOG_DETAILS(db_impl_->immutable_db_options().info_log, |
808 | 0 | "RollbackInternal 2nd write prepare_seq: %" PRIu64, |
809 | 0 | prepare_seq); |
810 | 0 | WriteBatch empty_batch; |
811 | 0 | const size_t ONE_BATCH = 1; |
812 | 0 | s = empty_batch.PutLogData(Slice()); |
813 | 0 | assert(s.ok()); |
814 | | // In the absence of Prepare markers, use Noop as a batch separator |
815 | 0 | s = WriteBatchInternal::InsertNoop(&empty_batch); |
816 | 0 | assert(s.ok()); |
817 | 0 | s = db_impl_->WriteImpl(write_options_, &empty_batch, nullptr, nullptr, |
818 | 0 | nullptr, NO_REF_LOG, DISABLE_MEMTABLE, &seq_used, |
819 | 0 | ONE_BATCH, &update_commit_map_with_rollback_batch); |
820 | 0 | assert(!s.ok() || seq_used != kMaxSequenceNumber); |
821 | | // Mark the txn as rolled back |
822 | 0 | if (s.ok()) { |
823 | 0 | for (const auto& seq : unprep_seqs_) { |
824 | 0 | wpt_db_->RemovePrepared(seq.first, seq.second); |
825 | 0 | } |
826 | 0 | } |
827 | |
|
828 | 0 | unprep_seqs_.clear(); |
829 | 0 | flushed_save_points_.reset(nullptr); |
830 | 0 | unflushed_save_points_.reset(nullptr); |
831 | 0 | return s; |
832 | 0 | } |
833 | | |
834 | 0 | void WriteUnpreparedTxn::Clear() { |
835 | 0 | if (!recovered_txn_) { |
836 | 0 | txn_db_impl_->UnLock(this, *tracked_locks_); |
837 | 0 | } |
838 | 0 | unprep_seqs_.clear(); |
839 | 0 | flushed_save_points_.reset(nullptr); |
840 | 0 | unflushed_save_points_.reset(nullptr); |
841 | 0 | recovered_txn_ = false; |
842 | 0 | largest_validated_seq_ = 0; |
843 | 0 | for (auto& it : active_iterators_) { |
844 | 0 | auto bdit = static_cast<BaseDeltaIterator*>(it); |
845 | 0 | bdit->Invalidate(Status::InvalidArgument( |
846 | 0 | "Cannot use iterator after transaction has finished")); |
847 | 0 | } |
848 | 0 | active_iterators_.clear(); |
849 | 0 | untracked_keys_.clear(); |
850 | 0 | TransactionBaseImpl::Clear(); |
851 | 0 | } |
852 | | |
853 | 0 | void WriteUnpreparedTxn::SetSavePoint() { |
854 | 0 | assert((unflushed_save_points_ ? unflushed_save_points_->size() : 0) + |
855 | 0 | (flushed_save_points_ ? flushed_save_points_->size() : 0) == |
856 | 0 | (save_points_ ? save_points_->size() : 0)); |
857 | 0 | PessimisticTransaction::SetSavePoint(); |
858 | 0 | if (unflushed_save_points_ == nullptr) { |
859 | 0 | unflushed_save_points_.reset(new autovector<size_t>()); |
860 | 0 | } |
861 | 0 | unflushed_save_points_->push_back(write_batch_.GetDataSize()); |
862 | 0 | } |
863 | | |
864 | 0 | Status WriteUnpreparedTxn::RollbackToSavePoint() { |
865 | 0 | assert((unflushed_save_points_ ? unflushed_save_points_->size() : 0) + |
866 | 0 | (flushed_save_points_ ? flushed_save_points_->size() : 0) == |
867 | 0 | (save_points_ ? save_points_->size() : 0)); |
868 | 0 | if (unflushed_save_points_ != nullptr && unflushed_save_points_->size() > 0) { |
869 | 0 | Status s = PessimisticTransaction::RollbackToSavePoint(); |
870 | 0 | assert(!s.IsNotFound()); |
871 | 0 | unflushed_save_points_->pop_back(); |
872 | 0 | return s; |
873 | 0 | } |
874 | | |
875 | 0 | if (flushed_save_points_ != nullptr && !flushed_save_points_->empty()) { |
876 | 0 | return RollbackToSavePointInternal(); |
877 | 0 | } |
878 | | |
879 | 0 | return Status::NotFound(); |
880 | 0 | } |
881 | | |
882 | 0 | Status WriteUnpreparedTxn::RollbackToSavePointInternal() { |
883 | 0 | Status s; |
884 | |
|
885 | 0 | const bool kClear = true; |
886 | 0 | TransactionBaseImpl::InitWriteBatch(kClear); |
887 | |
|
888 | 0 | assert(flushed_save_points_->size() > 0); |
889 | 0 | WriteUnpreparedTxn::SavePoint& top = flushed_save_points_->back(); |
890 | |
|
891 | 0 | assert(save_points_ != nullptr && save_points_->size() > 0); |
892 | 0 | const LockTracker& tracked_keys = *save_points_->top().new_locks_; |
893 | | |
894 | | // TODO: plumb Env::IOActivity, Env::IOPriority |
895 | 0 | ReadOptions roptions; |
896 | 0 | roptions.snapshot = top.snapshot_->snapshot(); |
897 | 0 | SequenceNumber min_uncommitted = |
898 | 0 | static_cast_with_check<const SnapshotImpl>(roptions.snapshot) |
899 | 0 | ->min_uncommitted_; |
900 | 0 | SequenceNumber snap_seq = roptions.snapshot->GetSequenceNumber(); |
901 | 0 | WriteUnpreparedTxnReadCallback callback(wupt_db_, snap_seq, min_uncommitted, |
902 | 0 | top.unprep_seqs_, |
903 | 0 | kBackedByDBSnapshot); |
904 | 0 | s = WriteRollbackKeys(tracked_keys, &write_batch_, &callback, roptions); |
905 | 0 | if (!s.ok()) { |
906 | 0 | return s; |
907 | 0 | } |
908 | | |
909 | 0 | const bool kPrepared = true; |
910 | 0 | s = FlushWriteBatchToDBInternal(!kPrepared); |
911 | 0 | if (!s.ok()) { |
912 | 0 | return s; |
913 | 0 | } |
914 | | |
915 | | // PessimisticTransaction::RollbackToSavePoint will call also call |
916 | | // RollbackToSavepoint on write_batch_. However, write_batch_ is empty and has |
917 | | // no savepoints because this savepoint has already been flushed. Work around |
918 | | // this by setting a fake savepoint. |
919 | 0 | write_batch_.SetSavePoint(); |
920 | 0 | s = PessimisticTransaction::RollbackToSavePoint(); |
921 | 0 | assert(s.ok()); |
922 | 0 | if (!s.ok()) { |
923 | 0 | return s; |
924 | 0 | } |
925 | | |
926 | 0 | flushed_save_points_->pop_back(); |
927 | 0 | return s; |
928 | 0 | } |
929 | | |
930 | 0 | Status WriteUnpreparedTxn::PopSavePoint() { |
931 | 0 | assert((unflushed_save_points_ ? unflushed_save_points_->size() : 0) + |
932 | 0 | (flushed_save_points_ ? flushed_save_points_->size() : 0) == |
933 | 0 | (save_points_ ? save_points_->size() : 0)); |
934 | 0 | if (unflushed_save_points_ != nullptr && unflushed_save_points_->size() > 0) { |
935 | 0 | Status s = PessimisticTransaction::PopSavePoint(); |
936 | 0 | assert(!s.IsNotFound()); |
937 | 0 | unflushed_save_points_->pop_back(); |
938 | 0 | return s; |
939 | 0 | } |
940 | | |
941 | 0 | if (flushed_save_points_ != nullptr && !flushed_save_points_->empty()) { |
942 | | // PessimisticTransaction::PopSavePoint will call also call PopSavePoint on |
943 | | // write_batch_. However, write_batch_ is empty and has no savepoints |
944 | | // because this savepoint has already been flushed. Work around this by |
945 | | // setting a fake savepoint. |
946 | 0 | write_batch_.SetSavePoint(); |
947 | 0 | Status s = PessimisticTransaction::PopSavePoint(); |
948 | 0 | assert(!s.IsNotFound()); |
949 | 0 | flushed_save_points_->pop_back(); |
950 | 0 | return s; |
951 | 0 | } |
952 | | |
953 | 0 | return Status::NotFound(); |
954 | 0 | } |
955 | | |
956 | | void WriteUnpreparedTxn::MultiGet(const ReadOptions& _read_options, |
957 | | ColumnFamilyHandle* column_family, |
958 | | const size_t num_keys, const Slice* keys, |
959 | | PinnableSlice* values, Status* statuses, |
960 | 0 | const bool sorted_input) { |
961 | 0 | if (_read_options.io_activity != Env::IOActivity::kUnknown && |
962 | 0 | _read_options.io_activity != Env::IOActivity::kMultiGet) { |
963 | 0 | Status s = Status::InvalidArgument( |
964 | 0 | "Can only call MultiGet with `ReadOptions::io_activity` is " |
965 | 0 | "`Env::IOActivity::kUnknown` or `Env::IOActivity::kMultiGet`"); |
966 | |
|
967 | 0 | for (size_t i = 0; i < num_keys; ++i) { |
968 | 0 | if (statuses[i].ok()) { |
969 | 0 | statuses[i] = s; |
970 | 0 | } |
971 | 0 | } |
972 | 0 | return; |
973 | 0 | } |
974 | 0 | ReadOptions read_options(_read_options); |
975 | 0 | if (read_options.io_activity == Env::IOActivity::kUnknown) { |
976 | 0 | read_options.io_activity = Env::IOActivity::kMultiGet; |
977 | 0 | } |
978 | 0 | SequenceNumber min_uncommitted; |
979 | 0 | SequenceNumber snap_seq; |
980 | 0 | const SnapshotBackup backed_by_snapshot = wupt_db_->AssignMinMaxSeqs( |
981 | 0 | read_options.snapshot, &min_uncommitted, &snap_seq); |
982 | 0 | WriteUnpreparedTxnReadCallback callback(wupt_db_, snap_seq, min_uncommitted, |
983 | 0 | unprep_seqs_, backed_by_snapshot); |
984 | 0 | write_batch_.MultiGetFromBatchAndDB(db_, read_options, column_family, |
985 | 0 | num_keys, keys, values, statuses, |
986 | 0 | sorted_input, &callback); |
987 | 0 | if (UNLIKELY(!callback.valid() || |
988 | 0 | !wupt_db_->ValidateSnapshot(snap_seq, backed_by_snapshot))) { |
989 | 0 | wupt_db_->WPRecordTick(TXN_GET_TRY_AGAIN); |
990 | 0 | for (size_t i = 0; i < num_keys; i++) { |
991 | 0 | statuses[i] = Status::TryAgain(); |
992 | 0 | } |
993 | 0 | } |
994 | 0 | } |
995 | | |
996 | | Status WriteUnpreparedTxn::Get(const ReadOptions& _read_options, |
997 | | ColumnFamilyHandle* column_family, |
998 | 0 | const Slice& key, PinnableSlice* value) { |
999 | 0 | if (_read_options.io_activity != Env::IOActivity::kUnknown && |
1000 | 0 | _read_options.io_activity != Env::IOActivity::kGet) { |
1001 | 0 | return Status::InvalidArgument( |
1002 | 0 | "Can only call Get with `ReadOptions::io_activity` is " |
1003 | 0 | "`Env::IOActivity::kUnknown` or `Env::IOActivity::kGet`"); |
1004 | 0 | } |
1005 | 0 | ReadOptions read_options(_read_options); |
1006 | 0 | if (read_options.io_activity == Env::IOActivity::kUnknown) { |
1007 | 0 | read_options.io_activity = Env::IOActivity::kGet; |
1008 | 0 | } |
1009 | |
|
1010 | 0 | return GetImpl(read_options, column_family, key, value); |
1011 | 0 | } |
1012 | | |
1013 | | Status WriteUnpreparedTxn::GetImpl(const ReadOptions& options, |
1014 | | ColumnFamilyHandle* column_family, |
1015 | 0 | const Slice& key, PinnableSlice* value) { |
1016 | 0 | SequenceNumber min_uncommitted; |
1017 | 0 | SequenceNumber snap_seq; |
1018 | 0 | const SnapshotBackup backed_by_snapshot = |
1019 | 0 | wupt_db_->AssignMinMaxSeqs(options.snapshot, &min_uncommitted, &snap_seq); |
1020 | 0 | WriteUnpreparedTxnReadCallback callback(wupt_db_, snap_seq, min_uncommitted, |
1021 | 0 | unprep_seqs_, backed_by_snapshot); |
1022 | 0 | auto res = write_batch_.GetFromBatchAndDB(db_, options, column_family, key, |
1023 | 0 | value, &callback); |
1024 | 0 | if (LIKELY(callback.valid() && |
1025 | 0 | wupt_db_->ValidateSnapshot(snap_seq, backed_by_snapshot))) { |
1026 | 0 | return res; |
1027 | 0 | } else { |
1028 | 0 | res.PermitUncheckedError(); |
1029 | 0 | wupt_db_->WPRecordTick(TXN_GET_TRY_AGAIN); |
1030 | 0 | return Status::TryAgain(); |
1031 | 0 | } |
1032 | 0 | } |
1033 | | |
1034 | | Status WriteUnpreparedTxn::GetEntityImpl(const ReadOptions& options, |
1035 | | ColumnFamilyHandle* column_family, |
1036 | | const Slice& key, |
1037 | 0 | PinnableWideColumns* columns) { |
1038 | 0 | SequenceNumber min_uncommitted; |
1039 | 0 | SequenceNumber snap_seq; |
1040 | 0 | const SnapshotBackup backed_by_snapshot = |
1041 | 0 | wupt_db_->AssignMinMaxSeqs(options.snapshot, &min_uncommitted, &snap_seq); |
1042 | 0 | WriteUnpreparedTxnReadCallback callback(wupt_db_, snap_seq, min_uncommitted, |
1043 | 0 | unprep_seqs_, backed_by_snapshot); |
1044 | 0 | Status res = write_batch_.GetEntityFromBatchAndDB(db_, options, column_family, |
1045 | 0 | key, columns, &callback); |
1046 | 0 | if (LIKELY(callback.valid() && |
1047 | 0 | wupt_db_->ValidateSnapshot(snap_seq, backed_by_snapshot))) { |
1048 | 0 | return res; |
1049 | 0 | } else { |
1050 | 0 | res.PermitUncheckedError(); |
1051 | 0 | wupt_db_->WPRecordTick(TXN_GET_TRY_AGAIN); |
1052 | 0 | return Status::TryAgain(); |
1053 | 0 | } |
1054 | 0 | } |
1055 | | |
1056 | | void WriteUnpreparedTxn::MultiGetEntityImpl(const ReadOptions& options, |
1057 | | ColumnFamilyHandle* column_family, |
1058 | | size_t num_keys, const Slice* keys, |
1059 | | PinnableWideColumns* results, |
1060 | | Status* statuses, |
1061 | 0 | bool sorted_input) { |
1062 | 0 | SequenceNumber min_uncommitted; |
1063 | 0 | SequenceNumber snap_seq; |
1064 | 0 | const SnapshotBackup backed_by_snapshot = |
1065 | 0 | wupt_db_->AssignMinMaxSeqs(options.snapshot, &min_uncommitted, &snap_seq); |
1066 | 0 | WriteUnpreparedTxnReadCallback callback(wupt_db_, snap_seq, min_uncommitted, |
1067 | 0 | unprep_seqs_, backed_by_snapshot); |
1068 | 0 | write_batch_.MultiGetEntityFromBatchAndDB(db_, options, column_family, |
1069 | 0 | num_keys, keys, results, statuses, |
1070 | 0 | sorted_input, &callback); |
1071 | 0 | if (UNLIKELY(!callback.valid() || |
1072 | 0 | !wupt_db_->ValidateSnapshot(snap_seq, backed_by_snapshot))) { |
1073 | 0 | wupt_db_->WPRecordTick(TXN_GET_TRY_AGAIN); |
1074 | 0 | for (size_t i = 0; i < num_keys; i++) { |
1075 | 0 | statuses[i] = Status::TryAgain(); |
1076 | 0 | } |
1077 | 0 | } |
1078 | 0 | } |
1079 | | |
1080 | | namespace { |
1081 | 0 | static void CleanupWriteUnpreparedWBWIIterator(void* arg1, void* arg2) { |
1082 | 0 | auto txn = static_cast<WriteUnpreparedTxn*>(arg1); |
1083 | 0 | auto iter = static_cast<Iterator*>(arg2); |
1084 | 0 | txn->RemoveActiveIterator(iter); |
1085 | 0 | } |
1086 | | } // anonymous namespace |
1087 | | |
1088 | 0 | Iterator* WriteUnpreparedTxn::GetIterator(const ReadOptions& options) { |
1089 | 0 | return GetIterator(options, wupt_db_->DefaultColumnFamily()); |
1090 | 0 | } |
1091 | | |
1092 | | Iterator* WriteUnpreparedTxn::GetIterator(const ReadOptions& options, |
1093 | 0 | ColumnFamilyHandle* column_family) { |
1094 | | // Make sure to get iterator from WriteUnprepareTxnDB, not the root db. |
1095 | 0 | Iterator* db_iter = wupt_db_->NewIterator(options, column_family, this); |
1096 | 0 | assert(db_iter); |
1097 | |
|
1098 | 0 | auto iter = |
1099 | 0 | write_batch_.NewIteratorWithBase(column_family, db_iter, &options); |
1100 | 0 | active_iterators_.push_back(iter); |
1101 | 0 | iter->RegisterCleanup(CleanupWriteUnpreparedWBWIIterator, this, iter); |
1102 | 0 | return iter; |
1103 | 0 | } |
1104 | | |
1105 | | Status WriteUnpreparedTxn::ValidateSnapshot(ColumnFamilyHandle* column_family, |
1106 | | const Slice& key, |
1107 | 0 | SequenceNumber* tracked_at_seq) { |
1108 | | // TODO(lth): Reduce duplicate code with WritePrepared ValidateSnapshot logic. |
1109 | 0 | assert(snapshot_); |
1110 | |
|
1111 | 0 | SequenceNumber min_uncommitted = |
1112 | 0 | static_cast_with_check<const SnapshotImpl>(snapshot_.get()) |
1113 | 0 | ->min_uncommitted_; |
1114 | 0 | SequenceNumber snap_seq = snapshot_->GetSequenceNumber(); |
1115 | | // tracked_at_seq is either max or the last snapshot with which this key was |
1116 | | // trackeed so there is no need to apply the IsInSnapshot to this comparison |
1117 | | // here as tracked_at_seq is not a prepare seq. |
1118 | 0 | if (*tracked_at_seq <= snap_seq) { |
1119 | | // If the key has been previous validated at a sequence number earlier |
1120 | | // than the curent snapshot's sequence number, we already know it has not |
1121 | | // been modified. |
1122 | 0 | return Status::OK(); |
1123 | 0 | } |
1124 | | |
1125 | 0 | *tracked_at_seq = snap_seq; |
1126 | |
|
1127 | 0 | ColumnFamilyHandle* cfh = |
1128 | 0 | column_family ? column_family : db_impl_->DefaultColumnFamily(); |
1129 | |
|
1130 | 0 | WriteUnpreparedTxnReadCallback snap_checker( |
1131 | 0 | wupt_db_, snap_seq, min_uncommitted, unprep_seqs_, kBackedByDBSnapshot); |
1132 | | // TODO(yanqin): Support user-defined timestamp. |
1133 | 0 | return TransactionUtil::CheckKeyForConflicts( |
1134 | 0 | db_impl_, cfh, key.ToString(), snap_seq, /*ts=*/nullptr, |
1135 | 0 | false /* cache_only */, &snap_checker, min_uncommitted, |
1136 | 0 | txn_db_impl_->GetTxnDBOptions().enable_udt_validation); |
1137 | 0 | } |
1138 | | |
1139 | | const std::map<SequenceNumber, size_t>& |
1140 | 0 | WriteUnpreparedTxn::GetUnpreparedSequenceNumbers() { |
1141 | 0 | return unprep_seqs_; |
1142 | 0 | } |
1143 | | |
1144 | | } // namespace ROCKSDB_NAMESPACE |