cache_simulator.cc 11 KB

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