Skip to content

Commit b216083

Browse files
Jaesoo Leefacebook-github-bot
authored andcommitted
async IO support for RAID0 and refactoring around Device implementations
Summary: This change supports async IO for the RAID0 devices (i.e., multiple device files). To do so, this change introduces the concept of IOOp and IOReq concepts where a single IOReq can be split into multiple IOOp for its operation. This change also performs the refactoring around FileDevice and RAID0Device such that FileDevice can support all the combinations of single device vs multiple devices and async vs sync IOs. This change disables static listing for //cachelib/navy/common/tests:tests because it is not supported for value parameterized tests. Reviewed By: therealgymmy Differential Revision: D47459535 fbshipit-source-id: d272c4052d913c71b4c986ded516c5a471421492
1 parent dfc1294 commit b216083

File tree

15 files changed

+975
-942
lines changed

15 files changed

+975
-942
lines changed

cachelib/allocator/nvmcache/NavyConfig.cpp

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,13 @@ RandomAPConfig& RandomAPConfig::setAdmProbability(double admProbability) {
6464
}
6565

6666
// device settings
67-
void NavyConfig::setIoThreads(unsigned int numIoThreads,
68-
unsigned int qDepthPerThread) {
69-
if (usesRaidFiles()) {
67+
void NavyConfig::enableAsyncIo(unsigned int qDepth, bool enableIoUring) {
68+
if (qDepth == 0) {
7069
throw std::invalid_argument(
71-
"AsyncDevice is not yet supported for RAID files");
70+
folly::sformat("qdepth {} should be >=1 to use async IO", qDepth));
7271
}
73-
numIoThreads_ = numIoThreads;
74-
qDepthPerThread_ = qDepthPerThread;
72+
ioEngine_ = enableIoUring ? IoEngine::IoUring : IoEngine::LibAio;
73+
qDepth_ = qDepth;
7574
}
7675

7776
void NavyConfig::setSimpleFile(const std::string& fileName,
@@ -220,6 +219,8 @@ std::map<std::string, std::string> NavyConfig::serialize() const {
220219
configMap["navyConfig::truncateFile"] = truncateFile_ ? "true" : "false";
221220
configMap["navyConfig::deviceMaxWriteSize"] =
222221
folly::to<std::string>(deviceMaxWriteSize_);
222+
configMap["navyConfig::ioEngine"] = getIoEngineName(ioEngine_).str();
223+
configMap["navyConfig::QDepth"] = folly::to<std::string>(qDepth_);
223224

224225
// Job scheduler settings
225226
configMap["navyConfig::readerThreads"] =
@@ -229,10 +230,6 @@ std::map<std::string, std::string> NavyConfig::serialize() const {
229230
configMap["navyConfig::navyReqOrderingShards"] =
230231
folly::to<std::string>(navyReqOrderingShards_);
231232

232-
configMap["navyConfig::numIoThreads"] = folly::to<std::string>(numIoThreads_);
233-
configMap["navyConfig::QDepthPerThread"] =
234-
folly::to<std::string>(qDepthPerThread_);
235-
configMap["navyConfig::enableIoUring"] = enableIoUring_ ? "true" : "false";
236233
// Other settings
237234
configMap["navyConfig::maxConcurrentInserts"] =
238235
folly::to<std::string>(maxConcurrentInserts_);

cachelib/allocator/nvmcache/NavyConfig.h

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,21 @@ class EnginesConfig {
440440
BigHashConfig bigHashConfig_;
441441
};
442442

443+
enum class IoEngine : uint8_t { IoUring, LibAio, Sync };
444+
445+
inline const folly::StringPiece getIoEngineName(IoEngine e) {
446+
switch (e) {
447+
case IoEngine::IoUring:
448+
return "io_uring";
449+
case IoEngine::LibAio:
450+
return "libaio";
451+
case IoEngine::Sync:
452+
return "sync";
453+
}
454+
XDCHECK(false);
455+
return "invalid";
456+
}
457+
443458
/**
444459
* NavyConfig provides APIs for users to set up Navy related settings for
445460
* NvmCache.
@@ -478,16 +493,15 @@ class NavyConfig {
478493
const RandomAPConfig& randomAdmPolicy() const { return randomAPConfig_; }
479494

480495
// ============ Device settings =============
481-
bool getEnableIoUring() const { return enableIoUring_; }
482-
unsigned int getNumIoThreads() const { return numIoThreads_; }
483-
unsigned int getQDepthPerThread() const { return qDepthPerThread_; }
484496
uint64_t getBlockSize() const { return blockSize_; }
485497
const std::string& getFileName() const;
486498
const std::vector<std::string>& getRaidPaths() const;
487499
uint64_t getDeviceMetadataSize() const { return deviceMetadataSize_; }
488500
uint64_t getFileSize() const { return fileSize_; }
489501
bool getTruncateFile() const { return truncateFile_; }
490502
uint32_t getDeviceMaxWriteSize() const { return deviceMaxWriteSize_; }
503+
IoEngine getIoEngine() const { return ioEngine_; }
504+
unsigned int getQDepth() const { return qDepth_; }
491505

492506
// Return a const BlockCacheConfig to read values of its parameters.
493507
const BigHashConfig& bigHash() const {
@@ -523,16 +537,6 @@ class NavyConfig {
523537
RandomAPConfig& enableRandomAdmPolicy();
524538

525539
// ============ Device settings =============
526-
// Set the number of IO threads and queue depth per thread.
527-
// If set, AsyncDevice instead of Device will be created with given
528-
// configurations. For now, AsyncDevice is not supported for RAID.
529-
void setIoThreads(unsigned int numIoThreads, unsigned int qDepthPerThread);
530-
531-
// Enable io_uring engine; applicable for AsyncDevice only
532-
void setEnableIoUring(bool enableIoUring) noexcept {
533-
enableIoUring_ = enableIoUring;
534-
}
535-
536540
// Set the device block size, i.e., minimum unit of IO
537541
void setBlockSize(uint64_t blockSize) noexcept { blockSize_ = blockSize; }
538542
// Set the parameters for a simple file.
@@ -557,6 +561,9 @@ class NavyConfig {
557561
deviceMaxWriteSize_ = deviceMaxWriteSize;
558562
}
559563

564+
// Enable AsyncIo
565+
void enableAsyncIo(unsigned int qDepth, bool enableIoUring);
566+
560567
// ============ BlockCache settings =============
561568
// Return BlockCacheConfig for configuration.
562569
BlockCacheConfig& blockCache() noexcept {
@@ -627,12 +634,12 @@ class NavyConfig {
627634
// This is only used when in-mem buffer is enabled.
628635
uint32_t deviceMaxWriteSize_{};
629636

630-
// Number of IO threads for AsyncDevice.
631-
unsigned int numIoThreads_{0};
632-
// Number of queue depth per thread for AsyncDevice.
633-
unsigned int qDepthPerThread_{64};
634-
// Enable io_uring
635-
bool enableIoUring_{true};
637+
// IoEngine type used for IO
638+
IoEngine ioEngine_{IoEngine::Sync};
639+
640+
// Number of queue depth per thread for async IO.
641+
// 0 for Sync io engine and >1 for libaio and io_uring
642+
unsigned int qDepth_{0};
636643

637644
// ============ Engines settings =============
638645
// Currently we support one pair of engines.

cachelib/allocator/nvmcache/NavySetup.cpp

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -312,37 +312,28 @@ std::unique_ptr<cachelib::navy::Device> createDevice(
312312
std::shared_ptr<navy::DeviceEncryptor> encryptor) {
313313
auto blockSize = config.getBlockSize();
314314
auto maxDeviceWriteSize = config.getDeviceMaxWriteSize();
315-
316-
if (config.usesRaidFiles()) {
317-
auto stripeSize = getRegionSize(config);
318-
return cachelib::navy::createRAIDDevice(
319-
config.getRaidPaths(),
320-
alignDown(config.getFileSize(), stripeSize),
321-
config.getTruncateFile(),
322-
blockSize,
323-
stripeSize,
324-
std::move(encryptor),
325-
maxDeviceWriteSize > 0 ? alignDown(maxDeviceWriteSize, blockSize) : 0);
326-
} else if (config.usesSimpleFile()) {
327-
if (config.getNumIoThreads() > 0) {
328-
return cachelib::navy::createAsyncFileDevice(
329-
config.getFileName(),
330-
config.getFileSize(),
331-
config.getTruncateFile(),
332-
blockSize,
333-
config.getNumIoThreads(),
334-
config.getQDepthPerThread(),
335-
std::move(encryptor),
336-
maxDeviceWriteSize > 0 ? alignDown(maxDeviceWriteSize, blockSize) : 0,
337-
config.getEnableIoUring());
315+
if (config.usesRaidFiles() || config.usesSimpleFile()) {
316+
auto stripeSize = 0;
317+
auto fileSize = config.getFileSize();
318+
std::vector<std::string> filePaths;
319+
if (config.usesSimpleFile()) {
320+
filePaths.emplace_back(config.getFileName());
321+
} else {
322+
stripeSize = getRegionSize(config);
323+
filePaths = config.getRaidPaths();
324+
fileSize = alignDown(fileSize, stripeSize);
338325
}
326+
339327
return cachelib::navy::createFileDevice(
340-
config.getFileName(),
341-
config.getFileSize(),
328+
filePaths,
329+
fileSize,
342330
config.getTruncateFile(),
343331
blockSize,
344-
std::move(encryptor),
345-
maxDeviceWriteSize > 0 ? alignDown(maxDeviceWriteSize, blockSize) : 0);
332+
stripeSize,
333+
maxDeviceWriteSize > 0 ? alignDown(maxDeviceWriteSize, blockSize) : 0,
334+
config.getIoEngine(),
335+
config.getQDepth(),
336+
std::move(encryptor));
346337
} else {
347338
return cachelib::navy::createMemoryDevice(config.getFileSize(),
348339
std::move(encryptor), blockSize);

cachelib/allocator/nvmcache/tests/NavyConfigTest.cpp

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ const uint64_t deviceMetadataSize = 1024 * 1024 * 1024;
4343
const uint64_t fileSize = 10 * 1024 * 1024;
4444
const bool truncateFile = false;
4545
const uint32_t deviceMaxWriteSize = 4 * 1024 * 1024;
46+
const navy::IoEngine ioEngine = navy::IoEngine::IoUring;
47+
const unsigned int qDepth = 64;
4648

4749
// BlockCache settings
4850
const uint32_t blockCacheRegionSize = 16 * 1024 * 1024;
@@ -87,6 +89,7 @@ void setDeviceTestSettings(NavyConfig& config) {
8789
config.setRaidFiles(raidPaths, fileSize, truncateFile);
8890
config.setDeviceMetadataSize(deviceMetadataSize);
8991
config.setDeviceMaxWriteSize(deviceMaxWriteSize);
92+
config.enableAsyncIo(qDepth, ioEngine == navy::IoEngine::IoUring);
9093
}
9194

9295
void setBlockCacheTestSettings(NavyConfig& config) {
@@ -183,9 +186,8 @@ TEST(NavyConfigTest, Serialization) {
183186
expectedConfigMap["navyConfig::fileSize"] = "10485760";
184187
expectedConfigMap["navyConfig::truncateFile"] = "false";
185188
expectedConfigMap["navyConfig::deviceMaxWriteSize"] = "4194304";
186-
expectedConfigMap["navyConfig::numIoThreads"] = "0";
187-
expectedConfigMap["navyConfig::QDepthPerThread"] = "64";
188-
expectedConfigMap["navyConfig::enableIoUring"] = "true";
189+
expectedConfigMap["navyConfig::ioEngine"] = "io_uring";
190+
expectedConfigMap["navyConfig::QDepth"] = "64";
189191

190192
expectedConfigMap["navyConfig::blockCacheLru"] = "false";
191193
expectedConfigMap["navyConfig::blockCacheRegionSize"] = "16777216";
@@ -245,30 +247,47 @@ TEST(NavyConfigTest, AdmissionPolicy) {
245247
}
246248

247249
TEST(NavyConfigTest, Device) {
248-
NavyConfig config1{};
249-
config1.setBlockSize(blockSize);
250-
config1.setDeviceMetadataSize(deviceMetadataSize);
251-
EXPECT_EQ(config1.getBlockSize(), blockSize);
252-
EXPECT_EQ(config1.getDeviceMetadataSize(), deviceMetadataSize);
253-
254-
// set simple file
255-
config1.setSimpleFile(fileName, fileSize, truncateFile);
256-
EXPECT_EQ(config1.getFileName(), fileName);
257-
EXPECT_EQ(config1.getFileSize(), fileSize);
258-
EXPECT_EQ(config1.getTruncateFile(), truncateFile);
259-
EXPECT_THROW(config1.setRaidFiles(raidPaths, fileSize, truncateFile),
260-
std::invalid_argument);
261-
262-
// set RAID files
263-
NavyConfig config2{};
264-
EXPECT_THROW(config2.setRaidFiles(raidPathsInvalid, fileSize, truncateFile),
265-
std::invalid_argument);
266-
config2.setRaidFiles(raidPaths, fileSize, truncateFile);
267-
EXPECT_EQ(config2.getRaidPaths(), raidPaths);
268-
EXPECT_EQ(config2.getFileSize(), fileSize);
269-
EXPECT_EQ(config1.getTruncateFile(), truncateFile);
270-
EXPECT_THROW(config2.setSimpleFile(fileName, fileSize, truncateFile),
271-
std::invalid_argument);
250+
{
251+
NavyConfig config{};
252+
config.setBlockSize(blockSize);
253+
config.setDeviceMetadataSize(deviceMetadataSize);
254+
EXPECT_EQ(config.getBlockSize(), blockSize);
255+
EXPECT_EQ(config.getDeviceMetadataSize(), deviceMetadataSize);
256+
257+
// set simple file
258+
config.setSimpleFile(fileName, fileSize, truncateFile);
259+
EXPECT_EQ(config.getFileName(), fileName);
260+
EXPECT_EQ(config.getFileSize(), fileSize);
261+
EXPECT_EQ(config.getTruncateFile(), truncateFile);
262+
EXPECT_THROW(config.setRaidFiles(raidPaths, fileSize, truncateFile),
263+
std::invalid_argument);
264+
}
265+
266+
{
267+
// set RAID files
268+
NavyConfig config{};
269+
EXPECT_THROW(config.setRaidFiles(raidPathsInvalid, fileSize, truncateFile),
270+
std::invalid_argument);
271+
config.setRaidFiles(raidPaths, fileSize, truncateFile);
272+
EXPECT_EQ(config.getRaidPaths(), raidPaths);
273+
EXPECT_EQ(config.getFileSize(), fileSize);
274+
EXPECT_EQ(config.getTruncateFile(), truncateFile);
275+
EXPECT_THROW(config.setSimpleFile(fileName, fileSize, truncateFile),
276+
std::invalid_argument);
277+
}
278+
{
279+
// set io engines
280+
NavyConfig config{};
281+
EXPECT_EQ(config.getIoEngine(), navy::IoEngine::Sync);
282+
EXPECT_EQ(config.getQDepth(), 0);
283+
EXPECT_THROW(config.enableAsyncIo(0, false), std::invalid_argument);
284+
EXPECT_THROW(config.enableAsyncIo(0, true), std::invalid_argument);
285+
config.enableAsyncIo(1, false);
286+
EXPECT_EQ(config.getIoEngine(), navy::IoEngine::LibAio);
287+
config.enableAsyncIo(64, true);
288+
EXPECT_EQ(config.getIoEngine(), navy::IoEngine::IoUring);
289+
EXPECT_EQ(config.getQDepth(), 64);
290+
}
272291
}
273292

274293
TEST(NavyConfigTest, BlockCache) {

cachelib/allocator/nvmcache/tests/NavySetupTest.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ TEST(NavySetupTest, EnginesSetup) {
233233
// 20MB block cache 0.
234234
cfg.blockCache().setSize(20 * 1024 * 1024);
235235
navy::EnginesConfig pair1;
236+
pair1.blockCache().setRegionSize(4 * 1024 * 1024);
236237
// Second bighash: 5 pct, 640 threshold.
237238
pair1.bigHash().setSizePctAndMaxItemSize(5, 640);
238239
cfg.addEnginePair(std::move(pair1));
@@ -252,6 +253,7 @@ TEST(NavySetupTest, EnginesSetup) {
252253
// 20MB block cache 0.
253254
cfg.blockCache().setSize(20 * 1024 * 1024);
254255
navy::EnginesConfig pair1;
256+
pair1.blockCache().setRegionSize(4 * 1024 * 1024);
255257
// Second bighash: 5 pct, 640 threshold.
256258
pair1.bigHash().setSizePctAndMaxItemSize(5, 640);
257259
cfg.addEnginePair(std::move(pair1));
@@ -269,6 +271,7 @@ TEST(NavySetupTest, EnginesSetup) {
269271
// 20MB block cache 0.
270272
cfg.blockCache().setSize(20 * 1024 * 1024);
271273
navy::EnginesConfig pair1;
274+
pair1.blockCache().setRegionSize(4 * 1024 * 1024);
272275
// Second bighash: 5 pct, 640 threshold.
273276
pair1.bigHash().setSizePctAndMaxItemSize(5, 640);
274277
cfg.addEnginePair(std::move(pair1));
@@ -284,6 +287,7 @@ TEST(NavySetupTest, EnginesSetup) {
284287
// 20MB block cache 0.
285288
cfg.blockCache().setSize(20 * 1024 * 1024);
286289
navy::EnginesConfig pair1;
290+
pair1.blockCache().setRegionSize(4 * 1024 * 1024);
287291
// Second bighash: 5 pct, 640 threshold.
288292
pair1.bigHash().setSizePctAndMaxItemSize(5, 640);
289293
// Will throw. Set size for the second block cache.

cachelib/cachebench/cache/Cache-inl.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -212,10 +212,12 @@ Cache<Allocator>::Cache(const CacheConfig& config,
212212

213213
nvmConfig.navyConfig.setMaxParcelMemoryMB(config_.navyParcelMemoryMB);
214214

215-
if (config_.navyNumIoThreads > 0) {
216-
nvmConfig.navyConfig.setIoThreads(config_.navyNumIoThreads,
217-
config_.navyQDepthPerThread);
215+
// Enable async IO if qdepth is greater than 0
216+
if (config_.navyQDepth > 0) {
217+
nvmConfig.navyConfig.enableAsyncIo(config_.navyQDepth,
218+
config_.navyEnableIoUring);
218219
}
220+
219221
nvmConfig.navyConfig.setReaderAndWriterThreads(config_.navyReaderThreads,
220222
config_.navyWriterThreads);
221223

cachelib/cachebench/util/CacheConfig.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ CacheConfig::CacheConfig(const folly::dynamic& configJson) {
7474
JSONSetVal(configJson, navyProbabilityReinsertionThreshold);
7575
JSONSetVal(configJson, navyReaderThreads);
7676
JSONSetVal(configJson, navyWriterThreads);
77-
JSONSetVal(configJson, navyNumIoThreads);
78-
JSONSetVal(configJson, navyQDepthPerThread);
77+
JSONSetVal(configJson, navyQDepth);
78+
JSONSetVal(configJson, navyEnableIoUring);
7979
JSONSetVal(configJson, navyCleanRegions);
8080
JSONSetVal(configJson, navyAdmissionWriteRateMB);
8181
JSONSetVal(configJson, navyMaxConcurrentInserts);

cachelib/cachebench/util/CacheConfig.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,10 @@ struct CacheConfig : public JSONConfig {
184184
// number of asynchronous worker thread for navy write operation,
185185
uint32_t navyWriterThreads{32};
186186

187-
uint32_t navyNumIoThreads{0};
188-
189-
uint32_t navyQDepthPerThread{1024};
187+
// QDepth >= 1 for async IO
188+
uint32_t navyQDepth{0};
189+
// Use either io_uring or libaio for async IO
190+
bool navyEnableIoUring{true};
190191

191192
// buffer of clean regions to be maintained free to ensure writes
192193
// into navy don't queue behind a reclaim of region.

0 commit comments

Comments
 (0)