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(&current_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(&current_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