1 //  Copyright (c) 2013, 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/persistent_cache/volatile_tier_impl.h"
9 
10 #include <string>
11 
12 namespace ROCKSDB_NAMESPACE {
13 
DeleteCacheData(VolatileCacheTier::CacheData * data)14 void VolatileCacheTier::DeleteCacheData(VolatileCacheTier::CacheData* data) {
15   assert(data);
16   delete data;
17 }
18 
~VolatileCacheTier()19 VolatileCacheTier::~VolatileCacheTier() { index_.Clear(&DeleteCacheData); }
20 
Stats()21 PersistentCache::StatsType VolatileCacheTier::Stats() {
22   std::map<std::string, double> stat;
23   stat.insert({"persistent_cache.volatile_cache.hits",
24                static_cast<double>(stats_.cache_hits_)});
25   stat.insert({"persistent_cache.volatile_cache.misses",
26                static_cast<double>(stats_.cache_misses_)});
27   stat.insert({"persistent_cache.volatile_cache.inserts",
28                static_cast<double>(stats_.cache_inserts_)});
29   stat.insert({"persistent_cache.volatile_cache.evicts",
30                static_cast<double>(stats_.cache_evicts_)});
31   stat.insert({"persistent_cache.volatile_cache.hit_pct",
32                static_cast<double>(stats_.CacheHitPct())});
33   stat.insert({"persistent_cache.volatile_cache.miss_pct",
34                static_cast<double>(stats_.CacheMissPct())});
35 
36   auto out = PersistentCacheTier::Stats();
37   out.push_back(stat);
38   return out;
39 }
40 
Insert(const Slice & page_key,const char * data,const size_t size)41 Status VolatileCacheTier::Insert(const Slice& page_key, const char* data,
42                                  const size_t size) {
43   // precondition
44   assert(data);
45   assert(size);
46 
47   // increment the size
48   size_ += size;
49 
50   // check if we have overshot the limit, if so evict some space
51   while (size_ > max_size_) {
52     if (!Evict()) {
53       // unable to evict data, we give up so we don't spike read
54       // latency
55       assert(size_ >= size);
56       size_ -= size;
57       return Status::TryAgain("Unable to evict any data");
58     }
59   }
60 
61   assert(size_ >= size);
62 
63   // insert order: LRU, followed by index
64   std::string key(page_key.data(), page_key.size());
65   std::string value(data, size);
66   std::unique_ptr<CacheData> cache_data(
67       new CacheData(std::move(key), std::move(value)));
68   bool ok = index_.Insert(cache_data.get());
69   if (!ok) {
70     // decrement the size that we incremented ahead of time
71     assert(size_ >= size);
72     size_ -= size;
73     // failed to insert to cache, block already in cache
74     return Status::TryAgain("key already exists in volatile cache");
75   }
76 
77   cache_data.release();
78   stats_.cache_inserts_++;
79   return Status::OK();
80 }
81 
Lookup(const Slice & page_key,std::unique_ptr<char[]> * result,size_t * size)82 Status VolatileCacheTier::Lookup(const Slice& page_key,
83                                  std::unique_ptr<char[]>* result,
84                                  size_t* size) {
85   CacheData key(std::move(page_key.ToString()));
86   CacheData* kv;
87   bool ok = index_.Find(&key, &kv);
88   if (ok) {
89     // set return data
90     result->reset(new char[kv->value.size()]);
91     memcpy(result->get(), kv->value.c_str(), kv->value.size());
92     *size = kv->value.size();
93     // drop the reference on cache data
94     kv->refs_--;
95     // update stats
96     stats_.cache_hits_++;
97     return Status::OK();
98   }
99 
100   stats_.cache_misses_++;
101 
102   if (next_tier()) {
103     return next_tier()->Lookup(page_key, result, size);
104   }
105 
106   return Status::NotFound("key not found in volatile cache");
107 }
108 
Erase(const Slice &)109 bool VolatileCacheTier::Erase(const Slice& /*key*/) {
110   assert(!"not supported");
111   return true;
112 }
113 
Evict()114 bool VolatileCacheTier::Evict() {
115   CacheData* edata = index_.Evict();
116   if (!edata) {
117     // not able to evict any object
118     return false;
119   }
120 
121   stats_.cache_evicts_++;
122 
123   // push the evicted object to the next level
124   if (next_tier()) {
125     next_tier()->Insert(Slice(edata->key), edata->value.c_str(),
126                         edata->value.size());
127   }
128 
129   // adjust size and destroy data
130   size_ -= edata->value.size();
131   delete edata;
132 
133   return true;
134 }
135 
136 }  // namespace ROCKSDB_NAMESPACE
137 
138 #endif
139