cache_simulator.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  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. #include "utilities/simulator_cache/cache_simulator.h"
  6. #include <algorithm>
  7. #include "db/dbformat.h"
  8. #include "rocksdb/trace_record.h"
  9. namespace ROCKSDB_NAMESPACE {
  10. namespace {
  11. const std::string kGhostCachePrefix = "ghost_";
  12. } // namespace
  13. GhostCache::GhostCache(std::shared_ptr<Cache> sim_cache)
  14. : sim_cache_(sim_cache) {}
  15. bool GhostCache::Admit(const Slice& lookup_key) {
  16. auto handle = sim_cache_->Lookup(lookup_key);
  17. if (handle != nullptr) {
  18. sim_cache_->Release(handle);
  19. return true;
  20. }
  21. // TODO: Should we check for errors here?
  22. auto s = sim_cache_->Insert(lookup_key, /*obj=*/nullptr,
  23. &kNoopCacheItemHelper, lookup_key.size());
  24. s.PermitUncheckedError();
  25. return false;
  26. }
  27. CacheSimulator::CacheSimulator(std::unique_ptr<GhostCache>&& ghost_cache,
  28. std::shared_ptr<Cache> sim_cache)
  29. : ghost_cache_(std::move(ghost_cache)), sim_cache_(sim_cache) {}
  30. void CacheSimulator::Access(const BlockCacheTraceRecord& access) {
  31. bool admit = true;
  32. const bool is_user_access =
  33. BlockCacheTraceHelper::IsUserAccess(access.caller);
  34. bool is_cache_miss = true;
  35. if (ghost_cache_ && !access.no_insert) {
  36. admit = ghost_cache_->Admit(access.block_key);
  37. }
  38. auto handle = sim_cache_->Lookup(access.block_key);
  39. if (handle != nullptr) {
  40. sim_cache_->Release(handle);
  41. is_cache_miss = false;
  42. } else {
  43. if (!access.no_insert && admit && access.block_size > 0) {
  44. // Ignore errors on insert
  45. auto s = sim_cache_->Insert(access.block_key, /*obj=*/nullptr,
  46. &kNoopCacheItemHelper, access.block_size);
  47. s.PermitUncheckedError();
  48. }
  49. }
  50. miss_ratio_stats_.UpdateMetrics(access.access_timestamp, is_user_access,
  51. is_cache_miss);
  52. }
  53. void MissRatioStats::UpdateMetrics(uint64_t timestamp_in_ms,
  54. bool is_user_access, bool is_cache_miss) {
  55. uint64_t timestamp_in_seconds = timestamp_in_ms / kMicrosInSecond;
  56. num_accesses_timeline_[timestamp_in_seconds] += 1;
  57. num_accesses_ += 1;
  58. if (num_misses_timeline_.find(timestamp_in_seconds) ==
  59. num_misses_timeline_.end()) {
  60. num_misses_timeline_[timestamp_in_seconds] = 0;
  61. }
  62. if (is_cache_miss) {
  63. num_misses_ += 1;
  64. num_misses_timeline_[timestamp_in_seconds] += 1;
  65. }
  66. if (is_user_access) {
  67. user_accesses_ += 1;
  68. if (is_cache_miss) {
  69. user_misses_ += 1;
  70. }
  71. }
  72. }
  73. Cache::Priority PrioritizedCacheSimulator::ComputeBlockPriority(
  74. const BlockCacheTraceRecord& access) const {
  75. if (access.block_type == TraceType::kBlockTraceFilterBlock ||
  76. access.block_type == TraceType::kBlockTraceIndexBlock ||
  77. access.block_type == TraceType::kBlockTraceUncompressionDictBlock) {
  78. return Cache::Priority::HIGH;
  79. }
  80. return Cache::Priority::LOW;
  81. }
  82. void PrioritizedCacheSimulator::AccessKVPair(
  83. const Slice& key, uint64_t value_size, Cache::Priority priority,
  84. const BlockCacheTraceRecord& access, bool no_insert, bool is_user_access,
  85. bool* is_cache_miss, bool* admitted, bool update_metrics) {
  86. assert(is_cache_miss);
  87. assert(admitted);
  88. *is_cache_miss = true;
  89. *admitted = true;
  90. if (ghost_cache_ && !no_insert) {
  91. *admitted = ghost_cache_->Admit(key);
  92. }
  93. auto handle = sim_cache_->Lookup(key);
  94. if (handle != nullptr) {
  95. sim_cache_->Release(handle);
  96. *is_cache_miss = false;
  97. } else if (!no_insert && *admitted && value_size > 0) {
  98. // TODO: Should we check for an error here?
  99. auto s = sim_cache_->Insert(key, /*obj=*/nullptr, &kNoopCacheItemHelper,
  100. value_size,
  101. /*handle=*/nullptr, priority);
  102. s.PermitUncheckedError();
  103. }
  104. if (update_metrics) {
  105. miss_ratio_stats_.UpdateMetrics(access.access_timestamp, is_user_access,
  106. *is_cache_miss);
  107. }
  108. }
  109. void PrioritizedCacheSimulator::Access(const BlockCacheTraceRecord& access) {
  110. bool is_cache_miss = true;
  111. bool admitted = true;
  112. AccessKVPair(access.block_key, access.block_size,
  113. ComputeBlockPriority(access), access, access.no_insert,
  114. BlockCacheTraceHelper::IsUserAccess(access.caller),
  115. &is_cache_miss, &admitted, /*update_metrics=*/true);
  116. }
  117. void HybridRowBlockCacheSimulator::Access(const BlockCacheTraceRecord& access) {
  118. // TODO (haoyu): We only support Get for now. We need to extend the tracing
  119. // for MultiGet, i.e., non-data block accesses must log all keys in a
  120. // MultiGet.
  121. bool is_cache_miss = true;
  122. bool admitted = false;
  123. if (access.caller == TableReaderCaller::kUserGet &&
  124. access.get_id != BlockCacheTraceHelper::kReservedGetId) {
  125. // This is a Get request.
  126. const std::string& row_key = BlockCacheTraceHelper::ComputeRowKey(access);
  127. GetRequestStatus& status = getid_status_map_[access.get_id];
  128. if (status.is_complete) {
  129. // This Get request completes.
  130. // Skip future accesses to its index/filter/data
  131. // blocks. These block lookups are unnecessary if we observe a hit for the
  132. // referenced key-value pair already. Thus, we treat these lookups as
  133. // hits. This is also to ensure the total number of accesses are the same
  134. // when comparing to other policies.
  135. miss_ratio_stats_.UpdateMetrics(access.access_timestamp,
  136. /*is_user_access=*/true,
  137. /*is_cache_miss=*/false);
  138. return;
  139. }
  140. if (status.row_key_status.find(row_key) == status.row_key_status.end()) {
  141. // This is the first time that this key is accessed. Look up the key-value
  142. // pair first. Do not update the miss/accesses metrics here since it will
  143. // be updated later.
  144. AccessKVPair(row_key, access.referenced_data_size, Cache::Priority::HIGH,
  145. access,
  146. /*no_insert=*/false,
  147. /*is_user_access=*/true, &is_cache_miss, &admitted,
  148. /*update_metrics=*/false);
  149. InsertResult result = InsertResult::NO_INSERT;
  150. if (admitted && access.referenced_data_size > 0) {
  151. result = InsertResult::INSERTED;
  152. } else if (admitted) {
  153. result = InsertResult::ADMITTED;
  154. }
  155. status.row_key_status[row_key] = result;
  156. }
  157. if (!is_cache_miss) {
  158. // A cache hit.
  159. status.is_complete = true;
  160. miss_ratio_stats_.UpdateMetrics(access.access_timestamp,
  161. /*is_user_access=*/true,
  162. /*is_cache_miss=*/false);
  163. return;
  164. }
  165. // The row key-value pair observes a cache miss. We need to access its
  166. // index/filter/data blocks.
  167. InsertResult inserted = status.row_key_status[row_key];
  168. AccessKVPair(
  169. access.block_key, access.block_size, ComputeBlockPriority(access),
  170. access,
  171. /*no_insert=*/!insert_blocks_upon_row_kvpair_miss_ || access.no_insert,
  172. /*is_user_access=*/true, &is_cache_miss, &admitted,
  173. /*update_metrics=*/true);
  174. if (access.referenced_data_size > 0 && inserted == InsertResult::ADMITTED) {
  175. // TODO: Should we check for an error here?
  176. auto s =
  177. sim_cache_->Insert(row_key, /*obj=*/nullptr, &kNoopCacheItemHelper,
  178. access.referenced_data_size,
  179. /*handle=*/nullptr, Cache::Priority::HIGH);
  180. s.PermitUncheckedError();
  181. status.row_key_status[row_key] = InsertResult::INSERTED;
  182. }
  183. return;
  184. }
  185. AccessKVPair(access.block_key, access.block_size,
  186. ComputeBlockPriority(access), access, access.no_insert,
  187. BlockCacheTraceHelper::IsUserAccess(access.caller),
  188. &is_cache_miss, &admitted, /*update_metrics=*/true);
  189. }
  190. BlockCacheTraceSimulator::BlockCacheTraceSimulator(
  191. uint64_t warmup_seconds, uint32_t downsample_ratio,
  192. const std::vector<CacheConfiguration>& cache_configurations)
  193. : warmup_seconds_(warmup_seconds),
  194. downsample_ratio_(downsample_ratio),
  195. cache_configurations_(cache_configurations) {}
  196. Status BlockCacheTraceSimulator::InitializeCaches() {
  197. for (auto const& config : cache_configurations_) {
  198. for (auto cache_capacity : config.cache_capacities) {
  199. // Scale down the cache capacity since the trace contains accesses on
  200. // 1/'downsample_ratio' blocks.
  201. uint64_t simulate_cache_capacity = cache_capacity / downsample_ratio_;
  202. std::shared_ptr<CacheSimulator> sim_cache;
  203. std::unique_ptr<GhostCache> ghost_cache;
  204. std::string cache_name = config.cache_name;
  205. if (cache_name.find(kGhostCachePrefix) != std::string::npos) {
  206. ghost_cache.reset(new GhostCache(
  207. NewLRUCache(config.ghost_cache_capacity, /*num_shard_bits=*/1,
  208. /*strict_capacity_limit=*/false,
  209. /*high_pri_pool_ratio=*/0)));
  210. cache_name = cache_name.substr(kGhostCachePrefix.size());
  211. }
  212. if (cache_name == "lru") {
  213. sim_cache = std::make_shared<CacheSimulator>(
  214. std::move(ghost_cache),
  215. NewLRUCache(simulate_cache_capacity, config.num_shard_bits,
  216. /*strict_capacity_limit=*/false,
  217. /*high_pri_pool_ratio=*/0));
  218. } else if (cache_name == "lru_priority") {
  219. sim_cache = std::make_shared<PrioritizedCacheSimulator>(
  220. std::move(ghost_cache),
  221. NewLRUCache(simulate_cache_capacity, config.num_shard_bits,
  222. /*strict_capacity_limit=*/false,
  223. /*high_pri_pool_ratio=*/0.5));
  224. } else if (cache_name == "lru_hybrid") {
  225. sim_cache = std::make_shared<HybridRowBlockCacheSimulator>(
  226. std::move(ghost_cache),
  227. NewLRUCache(simulate_cache_capacity, config.num_shard_bits,
  228. /*strict_capacity_limit=*/false,
  229. /*high_pri_pool_ratio=*/0.5),
  230. /*insert_blocks_upon_row_kvpair_miss=*/true);
  231. } else if (cache_name == "lru_hybrid_no_insert_on_row_miss") {
  232. sim_cache = std::make_shared<HybridRowBlockCacheSimulator>(
  233. std::move(ghost_cache),
  234. NewLRUCache(simulate_cache_capacity, config.num_shard_bits,
  235. /*strict_capacity_limit=*/false,
  236. /*high_pri_pool_ratio=*/0.5),
  237. /*insert_blocks_upon_row_kvpair_miss=*/false);
  238. } else {
  239. // Not supported.
  240. return Status::InvalidArgument("Unknown cache name " +
  241. config.cache_name);
  242. }
  243. sim_caches_[config].push_back(sim_cache);
  244. }
  245. }
  246. return Status::OK();
  247. }
  248. void BlockCacheTraceSimulator::Access(const BlockCacheTraceRecord& access) {
  249. if (trace_start_time_ == 0) {
  250. trace_start_time_ = access.access_timestamp;
  251. }
  252. // access.access_timestamp is in microseconds.
  253. if (!warmup_complete_ &&
  254. trace_start_time_ + warmup_seconds_ * kMicrosInSecond <=
  255. access.access_timestamp) {
  256. for (auto& config_caches : sim_caches_) {
  257. for (auto& sim_cache : config_caches.second) {
  258. sim_cache->reset_counter();
  259. }
  260. }
  261. warmup_complete_ = true;
  262. }
  263. for (auto& config_caches : sim_caches_) {
  264. for (auto& sim_cache : config_caches.second) {
  265. sim_cache->Access(access);
  266. }
  267. }
  268. }
  269. } // namespace ROCKSDB_NAMESPACE