@@ -20,6 +20,89 @@ using namespace facebook::cachelib::navy;
2020
2121namespace facebook ::cachelib::interface {
2222
23+ /* *
24+ * In-memory buffer for data cached in flash. Reference counting via
25+ * RegionDescriptors is used to ensure the backing Region is still resident &
26+ * valid while working on this cache item. The buffer's layout is:
27+ *
28+ * ---------------------------------------------------------------------------
29+ * | creation & expiry time || value || (empty) | key | entry descriptor |
30+ * ---------------------------------------------------------------------------
31+ */
32+ class FlashCacheItem : public CacheItem {
33+ public:
34+ using EntryDesc = BlockCache::EntryDesc;
35+
36+ static constexpr size_t kMetadataSize = 2 * sizeof (uint32_t );
37+
38+ // Allocation constructor
39+ // Note: valueSize *must* include space for creation & expiration time
40+ FlashCacheItem (const HashedKey& hashedKey,
41+ uint32_t valueSize,
42+ std::tuple<RegionDescriptor, size_t , RelAddress>&& allocation,
43+ const uint32_t creationTime,
44+ const uint32_t ttlSecs)
45+ : desc_(std::move(std::get<0 >(allocation))),
46+ relAddr_ (std::move(std::get<2 >(allocation))),
47+ buffer_(std::get<1 >(allocation)) {
48+ XDCHECK_GE (valueSize, 2 * sizeof (uint32_t ))
49+ << " valueSize doesn't include space for metadata" ;
50+ BlockCache::writeEntryDescAndKey (buffer_, hashedKey, valueSize);
51+ uint32_t * metadata = reinterpret_cast <uint32_t *>(buffer_.data ());
52+ metadata[0 ] = creationTime;
53+ metadata[1 ] = ttlSecs > 0 ? creationTime + ttlSecs : 0 ;
54+ }
55+
56+ RegionDescriptor& getRegionDescriptor () noexcept { return desc_; }
57+ RelAddress& getRelAddress () noexcept { return relAddr_; }
58+ Buffer& getBuffer () noexcept { return buffer_; }
59+ EntryDesc* getEntryDescriptor () const noexcept {
60+ auto entryEnd = const_cast <uint8_t *>(buffer_.data ()) + buffer_.size ();
61+ return reinterpret_cast <EntryDesc*>(entryEnd - sizeof (EntryDesc));
62+ }
63+
64+ // ------------------------------ Interface ------------------------------ //
65+
66+ uint32_t getCreationTime () const noexcept override {
67+ return reinterpret_cast <const uint32_t *>(buffer_.data ())[0 ];
68+ }
69+ uint32_t getExpiryTime () const noexcept override {
70+ return reinterpret_cast <const uint32_t *>(buffer_.data ())[1 ];
71+ }
72+ // Ref-counting for FlashCache is different than RAMCache - multiple
73+ // RAMCacheItems all refer to a single cache item in memory whereas each
74+ // FlashCacheItem is separate (even if it references the same cache item in
75+ // Region memory).
76+ //
77+ // The only thing we care about is Region refcounting via RegionDescriptor. We
78+ // don't need to increment on create (creating the RegionDescriptor does that)
79+ // and we should always release the cache item to release the descriptor.
80+ void incrementRefCount () noexcept override {}
81+ bool decrementRefCount () noexcept override {
82+ // get cache to release the descriptor (need access to RegionManager)
83+ return true ;
84+ }
85+ Key getKey () const noexcept override {
86+ auto * desc = getEntryDescriptor ();
87+ return Key{reinterpret_cast <const char *>(desc) - desc->keySize ,
88+ desc->keySize };
89+ }
90+ void * getMemory () const noexcept override {
91+ return const_cast <uint8_t *>(buffer_.data () + kMetadataSize );
92+ }
93+ uint32_t getMemorySize () const noexcept override {
94+ return getEntryDescriptor ()->valueSize - kMetadataSize ;
95+ }
96+ uint32_t getTotalSize () const noexcept override { return buffer_.size (); }
97+
98+ private:
99+ // Used for region lifecycle management & indexing
100+ RegionDescriptor desc_;
101+ RelAddress relAddr_;
102+ // Data is buffered in RAM
103+ Buffer buffer_;
104+ };
105+
23106/* static */ Result<FlashCacheComponent> FlashCacheComponent::create (
24107 std::string name, navy::BlockCache::Config&& config) noexcept {
25108 try {
@@ -34,21 +117,83 @@ const std::string& FlashCacheComponent::getName() const noexcept {
34117}
35118
36119folly::coro::Task<Result<AllocatedHandle>> FlashCacheComponent::allocate (
37- Key /* key */ ,
38- uint32_t /* size */ ,
39- uint32_t /* creationTime */ ,
40- uint32_t /* ttlSecs */ ) {
41- co_return makeError (Error::Code::UNIMPLEMENTED, " not yet implemented" );
120+ Key key, uint32_t size, uint32_t creationTime, uint32_t ttlSecs) {
121+ if (key.empty ()) {
122+ co_return makeError (Error::Code::INVALID_ARGUMENTS, " empty key" );
123+ }
124+
125+ HashedKey hashedKey (key);
126+ uint32_t valueSize = size + FlashCacheItem::kMetadataSize ;
127+ auto ret = co_await onWorkerThread (
128+ [=, this ]() { return cache_->allocateForInsert (hashedKey, valueSize); },
129+ [this ](auto && result) {
130+ if (result.hasValue ()) {
131+ // Successfully allocated Region memory but coroutine was cancelled -
132+ // close the descriptor to release refcount on the Region
133+ auto & [desc, slotSize, _] = result.value ();
134+ cache_->addHole (slotSize);
135+ cache_->regionManager_ .close (std::move (desc));
136+ }
137+ });
138+ if (ret.hasError ()) {
139+ co_return makeError (Error::Code::ALLOCATE_FAILED,
140+ " could not allocate space in a region" );
141+ }
142+ // Note: we can't checksum until the user has finished writing their data,
143+ // defer until they call insert()
144+ auto * item = new FlashCacheItem (hashedKey, valueSize, std::move (ret).value (),
145+ creationTime, ttlSecs);
146+ co_return AllocatedHandle (*this , *item);
147+ }
148+
149+ folly::coro::Task<UnitResult> FlashCacheComponent::insertImpl (
150+ AllocatedHandle&& handle, bool allowReplace) {
151+ if (!handle) {
152+ co_return makeError (Error::Code::INVALID_ARGUMENTS,
153+ " empty AllocatedHandle" );
154+ }
155+
156+ auto & fccItem = reinterpret_cast <FlashCacheItem&>(*handle);
157+ auto * desc = fccItem.getEntryDescriptor ();
158+ auto logicalSizeWritten = desc->keySize + desc->valueSize ;
159+ auto totalSize = fccItem.getTotalSize ();
160+ auto keyHash = desc->keyHash ;
161+
162+ // Note: we don't need to run this on a worker thread because everything here
163+ // is synchronous - only allocation is async
164+
165+ if (cache_->checksumData_ ) {
166+ desc->cs = checksum (fccItem.getBuffer ().view ().slice (0 , desc->valueSize ));
167+ }
168+ cache_->regionManager_ .write (fccItem.getRelAddress (),
169+ std::move (fccItem.getBuffer ()));
170+ cache_->logicalWrittenCount_ .add (logicalSizeWritten);
171+ auto inserted = cache_->updateIndex (keyHash, totalSize,
172+ fccItem.getRelAddress (), allowReplace);
173+ if (inserted) {
174+ setInserted (handle, true );
175+ auto _ = std::move (handle);
176+ co_return folly::unit;
177+ } else {
178+ // Note: do hole accounting in release()
179+ co_return makeError (Error::Code::ALREADY_INSERTED, " key already inserted" );
180+ }
42181}
43182
44183folly::coro::Task<UnitResult> FlashCacheComponent::insert (
45- AllocatedHandle&& /* handle */ ) {
46- co_return makeError (Error::Code::UNIMPLEMENTED, " not yet implemented " );
184+ AllocatedHandle&& handle) {
185+ co_return co_await insertImpl ( std::move (handle), /* allowReplace */ false );
47186}
48187
49188folly::coro::Task<Result<std::optional<AllocatedHandle>>>
50- FlashCacheComponent::insertOrReplace (AllocatedHandle&& /* handle */ ) {
51- co_return makeError (Error::Code::UNIMPLEMENTED, " not yet implemented" );
189+ FlashCacheComponent::insertOrReplace (AllocatedHandle&& handle) {
190+ auto inserted =
191+ co_await insertImpl (std::move (handle), /* allowReplace */ true );
192+ XDCHECK (inserted) << " insertOrReplace should never fail" ;
193+ // Note: returning the old data requires a flash read, don't expose for now
194+ co_return std::move (inserted).then ([](auto &&) {
195+ return Result<std::optional<AllocatedHandle>>(std::nullopt );
196+ });
52197}
53198
54199folly::coro::Task<Result<std::optional<ReadHandle>>> FlashCacheComponent::find (
@@ -75,8 +220,14 @@ FlashCacheComponent::FlashCacheComponent(std::string&& name,
75220 : name_(std::move(name)),
76221 cache_(std::make_unique<navy::BlockCache>(std::move(config))) {}
77222
78- folly::coro::Task<void > FlashCacheComponent::release (CacheItem& /* item */ ,
79- bool /* inserted */ ) {
223+ folly::coro::Task<void > FlashCacheComponent::release (CacheItem& item,
224+ bool inserted) {
225+ auto & fccItem = reinterpret_cast <FlashCacheItem&>(item);
226+ if (!inserted) {
227+ cache_->addHole (fccItem.getTotalSize ());
228+ }
229+ cache_->regionManager_ .close (std::move (fccItem.getRegionDescriptor ()));
230+ delete &item;
80231 co_return ;
81232}
82233
0 commit comments