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 #ifndef ROCKSDB_LITE
7
8 #include "utilities/blob_db/blob_compaction_filter.h"
9 #include "db/dbformat.h"
10
11 #include <cinttypes>
12
13 namespace ROCKSDB_NAMESPACE {
14 namespace blob_db {
15
FilterV2(int,const Slice & key,ValueType value_type,const Slice & value,std::string *,std::string *) const16 CompactionFilter::Decision BlobIndexCompactionFilterBase::FilterV2(
17 int /*level*/, const Slice& key, ValueType value_type, const Slice& value,
18 std::string* /*new_value*/, std::string* /*skip_until*/) const {
19 if (value_type != kBlobIndex) {
20 return Decision::kKeep;
21 }
22 BlobIndex blob_index;
23 Status s = blob_index.DecodeFrom(value);
24 if (!s.ok()) {
25 // Unable to decode blob index. Keeping the value.
26 return Decision::kKeep;
27 }
28 if (blob_index.HasTTL() && blob_index.expiration() <= current_time_) {
29 // Expired
30 expired_count_++;
31 expired_size_ += key.size() + value.size();
32 return Decision::kRemove;
33 }
34 if (!blob_index.IsInlined() &&
35 blob_index.file_number() < context_.next_file_number &&
36 context_.current_blob_files.count(blob_index.file_number()) == 0) {
37 // Corresponding blob file gone (most likely, evicted by FIFO eviction).
38 evicted_count_++;
39 evicted_size_ += key.size() + value.size();
40 return Decision::kRemove;
41 }
42 if (context_.fifo_eviction_seq > 0 && blob_index.HasTTL() &&
43 blob_index.expiration() < context_.evict_expiration_up_to) {
44 // Hack: Internal key is passed to BlobIndexCompactionFilter for it to
45 // get sequence number.
46 ParsedInternalKey ikey;
47 bool ok = ParseInternalKey(key, &ikey);
48 // Remove keys that could have been remove by last FIFO eviction.
49 // If get error while parsing key, ignore and continue.
50 if (ok && ikey.sequence < context_.fifo_eviction_seq) {
51 evicted_count_++;
52 evicted_size_ += key.size() + value.size();
53 return Decision::kRemove;
54 }
55 }
56 return Decision::kKeep;
57 }
58
~BlobIndexCompactionFilterGC()59 BlobIndexCompactionFilterGC::~BlobIndexCompactionFilterGC() {
60 if (blob_file_) {
61 CloseAndRegisterNewBlobFile();
62 }
63
64 assert(context_gc_.blob_db_impl);
65
66 ROCKS_LOG_INFO(context_gc_.blob_db_impl->db_options_.info_log,
67 "GC pass finished %s: encountered %" PRIu64 " blobs (%" PRIu64
68 " bytes), relocated %" PRIu64 " blobs (%" PRIu64
69 " bytes), created %" PRIu64 " new blob file(s)",
70 !gc_stats_.HasError() ? "successfully" : "with failure",
71 gc_stats_.AllBlobs(), gc_stats_.AllBytes(),
72 gc_stats_.RelocatedBlobs(), gc_stats_.RelocatedBytes(),
73 gc_stats_.NewFiles());
74
75 RecordTick(statistics(), BLOB_DB_GC_NUM_KEYS_RELOCATED,
76 gc_stats_.RelocatedBlobs());
77 RecordTick(statistics(), BLOB_DB_GC_BYTES_RELOCATED,
78 gc_stats_.RelocatedBytes());
79 RecordTick(statistics(), BLOB_DB_GC_NUM_NEW_FILES, gc_stats_.NewFiles());
80 RecordTick(statistics(), BLOB_DB_GC_FAILURES, gc_stats_.HasError());
81 }
82
PrepareBlobOutput(const Slice & key,const Slice & existing_value,std::string * new_value) const83 CompactionFilter::BlobDecision BlobIndexCompactionFilterGC::PrepareBlobOutput(
84 const Slice& key, const Slice& existing_value,
85 std::string* new_value) const {
86 assert(new_value);
87
88 const BlobDBImpl* const blob_db_impl = context_gc_.blob_db_impl;
89 (void)blob_db_impl;
90
91 assert(blob_db_impl);
92 assert(blob_db_impl->bdb_options_.enable_garbage_collection);
93
94 BlobIndex blob_index;
95 const Status s = blob_index.DecodeFrom(existing_value);
96 if (!s.ok()) {
97 gc_stats_.SetError();
98 return BlobDecision::kCorruption;
99 }
100
101 if (blob_index.IsInlined()) {
102 gc_stats_.AddBlob(blob_index.value().size());
103
104 return BlobDecision::kKeep;
105 }
106
107 gc_stats_.AddBlob(blob_index.size());
108
109 if (blob_index.HasTTL()) {
110 return BlobDecision::kKeep;
111 }
112
113 if (blob_index.file_number() >= context_gc_.cutoff_file_number) {
114 return BlobDecision::kKeep;
115 }
116
117 // Note: each compaction generates its own blob files, which, depending on the
118 // workload, might result in many small blob files. The total number of files
119 // is bounded though (determined by the number of compactions and the blob
120 // file size option).
121 if (!OpenNewBlobFileIfNeeded()) {
122 gc_stats_.SetError();
123 return BlobDecision::kIOError;
124 }
125
126 PinnableSlice blob;
127 CompressionType compression_type = kNoCompression;
128 if (!ReadBlobFromOldFile(key, blob_index, &blob, &compression_type)) {
129 gc_stats_.SetError();
130 return BlobDecision::kIOError;
131 }
132
133 uint64_t new_blob_file_number = 0;
134 uint64_t new_blob_offset = 0;
135 if (!WriteBlobToNewFile(key, blob, &new_blob_file_number, &new_blob_offset)) {
136 gc_stats_.SetError();
137 return BlobDecision::kIOError;
138 }
139
140 if (!CloseAndRegisterNewBlobFileIfNeeded()) {
141 gc_stats_.SetError();
142 return BlobDecision::kIOError;
143 }
144
145 BlobIndex::EncodeBlob(new_value, new_blob_file_number, new_blob_offset,
146 blob.size(), compression_type);
147
148 gc_stats_.AddRelocatedBlob(blob_index.size());
149
150 return BlobDecision::kChangeValue;
151 }
152
OpenNewBlobFileIfNeeded() const153 bool BlobIndexCompactionFilterGC::OpenNewBlobFileIfNeeded() const {
154 if (blob_file_) {
155 assert(writer_);
156 return true;
157 }
158
159 BlobDBImpl* const blob_db_impl = context_gc_.blob_db_impl;
160 assert(blob_db_impl);
161
162 const Status s = blob_db_impl->CreateBlobFileAndWriter(
163 /* has_ttl */ false, ExpirationRange(), "GC", &blob_file_, &writer_);
164 if (!s.ok()) {
165 ROCKS_LOG_ERROR(blob_db_impl->db_options_.info_log,
166 "Error opening new blob file during GC, status: %s",
167 s.ToString().c_str());
168
169 return false;
170 }
171
172 assert(blob_file_);
173 assert(writer_);
174
175 gc_stats_.AddNewFile();
176
177 return true;
178 }
179
ReadBlobFromOldFile(const Slice & key,const BlobIndex & blob_index,PinnableSlice * blob,CompressionType * compression_type) const180 bool BlobIndexCompactionFilterGC::ReadBlobFromOldFile(
181 const Slice& key, const BlobIndex& blob_index, PinnableSlice* blob,
182 CompressionType* compression_type) const {
183 BlobDBImpl* const blob_db_impl = context_gc_.blob_db_impl;
184 assert(blob_db_impl);
185
186 const Status s = blob_db_impl->GetRawBlobFromFile(
187 key, blob_index.file_number(), blob_index.offset(), blob_index.size(),
188 blob, compression_type);
189
190 if (!s.ok()) {
191 ROCKS_LOG_ERROR(blob_db_impl->db_options_.info_log,
192 "Error reading blob during GC, key: %s (%s), status: %s",
193 key.ToString(/* output_hex */ true).c_str(),
194 blob_index.DebugString(/* output_hex */ true).c_str(),
195 s.ToString().c_str());
196
197 return false;
198 }
199
200 return true;
201 }
202
WriteBlobToNewFile(const Slice & key,const Slice & blob,uint64_t * new_blob_file_number,uint64_t * new_blob_offset) const203 bool BlobIndexCompactionFilterGC::WriteBlobToNewFile(
204 const Slice& key, const Slice& blob, uint64_t* new_blob_file_number,
205 uint64_t* new_blob_offset) const {
206 assert(new_blob_file_number);
207 assert(new_blob_offset);
208
209 assert(blob_file_);
210 *new_blob_file_number = blob_file_->BlobFileNumber();
211
212 assert(writer_);
213 uint64_t new_key_offset = 0;
214 const Status s = writer_->AddRecord(key, blob, kNoExpiration, &new_key_offset,
215 new_blob_offset);
216
217 if (!s.ok()) {
218 const BlobDBImpl* const blob_db_impl = context_gc_.blob_db_impl;
219 assert(blob_db_impl);
220
221 ROCKS_LOG_ERROR(
222 blob_db_impl->db_options_.info_log,
223 "Error writing blob to new file %s during GC, key: %s, status: %s",
224 blob_file_->PathName().c_str(),
225 key.ToString(/* output_hex */ true).c_str(), s.ToString().c_str());
226 return false;
227 }
228
229 const uint64_t new_size =
230 BlobLogRecord::kHeaderSize + key.size() + blob.size();
231 blob_file_->BlobRecordAdded(new_size);
232
233 BlobDBImpl* const blob_db_impl = context_gc_.blob_db_impl;
234 assert(blob_db_impl);
235
236 blob_db_impl->total_blob_size_ += new_size;
237
238 return true;
239 }
240
CloseAndRegisterNewBlobFileIfNeeded() const241 bool BlobIndexCompactionFilterGC::CloseAndRegisterNewBlobFileIfNeeded() const {
242 const BlobDBImpl* const blob_db_impl = context_gc_.blob_db_impl;
243 assert(blob_db_impl);
244
245 assert(blob_file_);
246 if (blob_file_->GetFileSize() < blob_db_impl->bdb_options_.blob_file_size) {
247 return true;
248 }
249
250 return CloseAndRegisterNewBlobFile();
251 }
252
CloseAndRegisterNewBlobFile() const253 bool BlobIndexCompactionFilterGC::CloseAndRegisterNewBlobFile() const {
254 BlobDBImpl* const blob_db_impl = context_gc_.blob_db_impl;
255 assert(blob_db_impl);
256 assert(blob_file_);
257
258 Status s;
259
260 {
261 WriteLock wl(&blob_db_impl->mutex_);
262
263 s = blob_db_impl->CloseBlobFile(blob_file_);
264
265 // Note: we delay registering the new blob file until it's closed to
266 // prevent FIFO eviction from processing it during the GC run.
267 blob_db_impl->RegisterBlobFile(blob_file_);
268 }
269
270 assert(blob_file_->Immutable());
271 blob_file_.reset();
272
273 if (!s.ok()) {
274 ROCKS_LOG_ERROR(blob_db_impl->db_options_.info_log,
275 "Error closing new blob file %s during GC, status: %s",
276 blob_file_->PathName().c_str(), s.ToString().c_str());
277
278 return false;
279 }
280
281 return true;
282 }
283
284 std::unique_ptr<CompactionFilter>
CreateCompactionFilter(const CompactionFilter::Context &)285 BlobIndexCompactionFilterFactory::CreateCompactionFilter(
286 const CompactionFilter::Context& /*context*/) {
287 assert(env());
288
289 int64_t current_time = 0;
290 Status s = env()->GetCurrentTime(¤t_time);
291 if (!s.ok()) {
292 return nullptr;
293 }
294 assert(current_time >= 0);
295
296 assert(blob_db_impl());
297
298 BlobCompactionContext context;
299 blob_db_impl()->GetCompactionContext(&context);
300
301 return std::unique_ptr<CompactionFilter>(new BlobIndexCompactionFilter(
302 std::move(context), current_time, statistics()));
303 }
304
305 std::unique_ptr<CompactionFilter>
CreateCompactionFilter(const CompactionFilter::Context &)306 BlobIndexCompactionFilterFactoryGC::CreateCompactionFilter(
307 const CompactionFilter::Context& /*context*/) {
308 assert(env());
309
310 int64_t current_time = 0;
311 Status s = env()->GetCurrentTime(¤t_time);
312 if (!s.ok()) {
313 return nullptr;
314 }
315 assert(current_time >= 0);
316
317 assert(blob_db_impl());
318
319 BlobCompactionContext context;
320 BlobCompactionContextGC context_gc;
321 blob_db_impl()->GetCompactionContext(&context, &context_gc);
322
323 return std::unique_ptr<CompactionFilter>(new BlobIndexCompactionFilterGC(
324 std::move(context), std::move(context_gc), current_time, statistics()));
325 }
326
327 } // namespace blob_db
328 } // namespace ROCKSDB_NAMESPACE
329 #endif // ROCKSDB_LITE
330