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 "db/blob/blob_file_cache.h"
7 
8 #include <cassert>
9 #include <string>
10 
11 #include "db/blob/blob_log_format.h"
12 #include "db/blob/blob_log_writer.h"
13 #include "env/mock_env.h"
14 #include "file/filename.h"
15 #include "file/read_write_util.h"
16 #include "file/writable_file_writer.h"
17 #include "options/cf_options.h"
18 #include "rocksdb/cache.h"
19 #include "rocksdb/env.h"
20 #include "rocksdb/file_system.h"
21 #include "rocksdb/options.h"
22 #include "rocksdb/statistics.h"
23 #include "test_util/sync_point.h"
24 #include "test_util/testharness.h"
25 
26 namespace ROCKSDB_NAMESPACE {
27 
28 namespace {
29 
30 // Creates a test blob file with a single blob in it.
WriteBlobFile(uint32_t column_family_id,const ImmutableOptions & immutable_options,uint64_t blob_file_number)31 void WriteBlobFile(uint32_t column_family_id,
32                    const ImmutableOptions& immutable_options,
33                    uint64_t blob_file_number) {
34   assert(!immutable_options.cf_paths.empty());
35 
36   const std::string blob_file_path =
37       BlobFileName(immutable_options.cf_paths.front().path, blob_file_number);
38 
39   std::unique_ptr<FSWritableFile> file;
40   ASSERT_OK(NewWritableFile(immutable_options.fs.get(), blob_file_path, &file,
41                             FileOptions()));
42 
43   std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
44       std::move(file), blob_file_path, FileOptions(), immutable_options.clock));
45 
46   constexpr Statistics* statistics = nullptr;
47   constexpr bool use_fsync = false;
48   constexpr bool do_flush = false;
49 
50   BlobLogWriter blob_log_writer(std::move(file_writer), immutable_options.clock,
51                                 statistics, blob_file_number, use_fsync,
52                                 do_flush);
53 
54   constexpr bool has_ttl = false;
55   constexpr ExpirationRange expiration_range;
56 
57   BlobLogHeader header(column_family_id, kNoCompression, has_ttl,
58                        expiration_range);
59 
60   ASSERT_OK(blob_log_writer.WriteHeader(header));
61 
62   constexpr char key[] = "key";
63   constexpr char blob[] = "blob";
64 
65   std::string compressed_blob;
66 
67   uint64_t key_offset = 0;
68   uint64_t blob_offset = 0;
69 
70   ASSERT_OK(blob_log_writer.AddRecord(key, blob, &key_offset, &blob_offset));
71 
72   BlobLogFooter footer;
73   footer.blob_count = 1;
74   footer.expiration_range = expiration_range;
75 
76   std::string checksum_method;
77   std::string checksum_value;
78 
79   ASSERT_OK(
80       blob_log_writer.AppendFooter(footer, &checksum_method, &checksum_value));
81 }
82 
83 }  // anonymous namespace
84 
85 class BlobFileCacheTest : public testing::Test {
86  protected:
BlobFileCacheTest()87   BlobFileCacheTest() { mock_env_.reset(MockEnv::Create(Env::Default())); }
88 
89   std::unique_ptr<Env> mock_env_;
90 };
91 
TEST_F(BlobFileCacheTest,GetBlobFileReader)92 TEST_F(BlobFileCacheTest, GetBlobFileReader) {
93   Options options;
94   options.env = mock_env_.get();
95   options.statistics = CreateDBStatistics();
96   options.cf_paths.emplace_back(
97       test::PerThreadDBPath(mock_env_.get(),
98                             "BlobFileCacheTest_GetBlobFileReader"),
99       0);
100   options.enable_blob_files = true;
101 
102   constexpr uint32_t column_family_id = 1;
103   ImmutableOptions immutable_options(options);
104   constexpr uint64_t blob_file_number = 123;
105 
106   WriteBlobFile(column_family_id, immutable_options, blob_file_number);
107 
108   constexpr size_t capacity = 10;
109   std::shared_ptr<Cache> backing_cache = NewLRUCache(capacity);
110 
111   FileOptions file_options;
112   constexpr HistogramImpl* blob_file_read_hist = nullptr;
113 
114   BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
115                                 &file_options, column_family_id,
116                                 blob_file_read_hist, nullptr /*IOTracer*/);
117 
118   // First try: reader should be opened and put in cache
119   CacheHandleGuard<BlobFileReader> first;
120 
121   ASSERT_OK(blob_file_cache.GetBlobFileReader(blob_file_number, &first));
122   ASSERT_NE(first.GetValue(), nullptr);
123   ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
124   ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
125 
126   // Second try: reader should be served from cache
127   CacheHandleGuard<BlobFileReader> second;
128 
129   ASSERT_OK(blob_file_cache.GetBlobFileReader(blob_file_number, &second));
130   ASSERT_NE(second.GetValue(), nullptr);
131   ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
132   ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
133 
134   ASSERT_EQ(first.GetValue(), second.GetValue());
135 }
136 
TEST_F(BlobFileCacheTest,GetBlobFileReader_Race)137 TEST_F(BlobFileCacheTest, GetBlobFileReader_Race) {
138   Options options;
139   options.env = mock_env_.get();
140   options.statistics = CreateDBStatistics();
141   options.cf_paths.emplace_back(
142       test::PerThreadDBPath(mock_env_.get(),
143                             "BlobFileCacheTest_GetBlobFileReader_Race"),
144       0);
145   options.enable_blob_files = true;
146 
147   constexpr uint32_t column_family_id = 1;
148   ImmutableOptions immutable_options(options);
149   constexpr uint64_t blob_file_number = 123;
150 
151   WriteBlobFile(column_family_id, immutable_options, blob_file_number);
152 
153   constexpr size_t capacity = 10;
154   std::shared_ptr<Cache> backing_cache = NewLRUCache(capacity);
155 
156   FileOptions file_options;
157   constexpr HistogramImpl* blob_file_read_hist = nullptr;
158 
159   BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
160                                 &file_options, column_family_id,
161                                 blob_file_read_hist, nullptr /*IOTracer*/);
162 
163   CacheHandleGuard<BlobFileReader> first;
164   CacheHandleGuard<BlobFileReader> second;
165 
166   SyncPoint::GetInstance()->SetCallBack(
167       "BlobFileCache::GetBlobFileReader:DoubleCheck", [&](void* /* arg */) {
168         // Disabling sync points to prevent infinite recursion
169         SyncPoint::GetInstance()->DisableProcessing();
170 
171         ASSERT_OK(blob_file_cache.GetBlobFileReader(blob_file_number, &second));
172         ASSERT_NE(second.GetValue(), nullptr);
173         ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
174         ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
175       });
176   SyncPoint::GetInstance()->EnableProcessing();
177 
178   ASSERT_OK(blob_file_cache.GetBlobFileReader(blob_file_number, &first));
179   ASSERT_NE(first.GetValue(), nullptr);
180   ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
181   ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
182 
183   ASSERT_EQ(first.GetValue(), second.GetValue());
184 
185   SyncPoint::GetInstance()->DisableProcessing();
186   SyncPoint::GetInstance()->ClearAllCallBacks();
187 }
188 
TEST_F(BlobFileCacheTest,GetBlobFileReader_IOError)189 TEST_F(BlobFileCacheTest, GetBlobFileReader_IOError) {
190   Options options;
191   options.env = mock_env_.get();
192   options.statistics = CreateDBStatistics();
193   options.cf_paths.emplace_back(
194       test::PerThreadDBPath(mock_env_.get(),
195                             "BlobFileCacheTest_GetBlobFileReader_IOError"),
196       0);
197   options.enable_blob_files = true;
198 
199   constexpr size_t capacity = 10;
200   std::shared_ptr<Cache> backing_cache = NewLRUCache(capacity);
201 
202   ImmutableOptions immutable_options(options);
203   FileOptions file_options;
204   constexpr uint32_t column_family_id = 1;
205   constexpr HistogramImpl* blob_file_read_hist = nullptr;
206 
207   BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
208                                 &file_options, column_family_id,
209                                 blob_file_read_hist, nullptr /*IOTracer*/);
210 
211   // Note: there is no blob file with the below number
212   constexpr uint64_t blob_file_number = 123;
213 
214   CacheHandleGuard<BlobFileReader> reader;
215 
216   ASSERT_TRUE(
217       blob_file_cache.GetBlobFileReader(blob_file_number, &reader).IsIOError());
218   ASSERT_EQ(reader.GetValue(), nullptr);
219   ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
220   ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 1);
221 }
222 
TEST_F(BlobFileCacheTest,GetBlobFileReader_CacheFull)223 TEST_F(BlobFileCacheTest, GetBlobFileReader_CacheFull) {
224   Options options;
225   options.env = mock_env_.get();
226   options.statistics = CreateDBStatistics();
227   options.cf_paths.emplace_back(
228       test::PerThreadDBPath(mock_env_.get(),
229                             "BlobFileCacheTest_GetBlobFileReader_CacheFull"),
230       0);
231   options.enable_blob_files = true;
232 
233   constexpr uint32_t column_family_id = 1;
234   ImmutableOptions immutable_options(options);
235   constexpr uint64_t blob_file_number = 123;
236 
237   WriteBlobFile(column_family_id, immutable_options, blob_file_number);
238 
239   constexpr size_t capacity = 0;
240   constexpr int num_shard_bits = -1;  // determined automatically
241   constexpr bool strict_capacity_limit = true;
242   std::shared_ptr<Cache> backing_cache =
243       NewLRUCache(capacity, num_shard_bits, strict_capacity_limit);
244 
245   FileOptions file_options;
246   constexpr HistogramImpl* blob_file_read_hist = nullptr;
247 
248   BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
249                                 &file_options, column_family_id,
250                                 blob_file_read_hist, nullptr /*IOTracer*/);
251 
252   // Insert into cache should fail since it has zero capacity and
253   // strict_capacity_limit is set
254   CacheHandleGuard<BlobFileReader> reader;
255 
256   ASSERT_TRUE(blob_file_cache.GetBlobFileReader(blob_file_number, &reader)
257                   .IsIncomplete());
258   ASSERT_EQ(reader.GetValue(), nullptr);
259   ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
260   ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 1);
261 }
262 
263 }  // namespace ROCKSDB_NAMESPACE
264 
main(int argc,char ** argv)265 int main(int argc, char** argv) {
266   ::testing::InitGoogleTest(&argc, argv);
267   return RUN_ALL_TESTS();
268 }
269