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