@nxtedition/rocksdb 7.0.24 → 7.0.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/binding.cc +3 -1
  2. package/deps/rocksdb/rocksdb/CMakeLists.txt +5 -0
  3. package/deps/rocksdb/rocksdb/Makefile +6 -2
  4. package/deps/rocksdb/rocksdb/TARGETS +14 -0
  5. package/deps/rocksdb/rocksdb/cache/cache_bench_tool.cc +4 -1
  6. package/deps/rocksdb/rocksdb/cache/cache_helpers.h +20 -0
  7. package/deps/rocksdb/rocksdb/cache/cache_reservation_manager_test.cc +2 -2
  8. package/deps/rocksdb/rocksdb/cache/cache_test.cc +44 -31
  9. package/deps/rocksdb/rocksdb/cache/clock_cache.cc +491 -722
  10. package/deps/rocksdb/rocksdb/cache/clock_cache.h +468 -2
  11. package/deps/rocksdb/rocksdb/cache/compressed_secondary_cache.cc +1 -1
  12. package/deps/rocksdb/rocksdb/cache/fast_lru_cache.cc +51 -52
  13. package/deps/rocksdb/rocksdb/cache/fast_lru_cache.h +28 -16
  14. package/deps/rocksdb/rocksdb/cache/lru_cache.cc +12 -1
  15. package/deps/rocksdb/rocksdb/cache/lru_cache.h +1 -0
  16. package/deps/rocksdb/rocksdb/cache/lru_cache_test.cc +170 -36
  17. package/deps/rocksdb/rocksdb/db/blob/blob_file_cache_test.cc +1 -1
  18. package/deps/rocksdb/rocksdb/db/blob/blob_file_reader.cc +63 -36
  19. package/deps/rocksdb/rocksdb/db/blob/blob_file_reader.h +4 -6
  20. package/deps/rocksdb/rocksdb/db/blob/blob_file_reader_test.cc +57 -38
  21. package/deps/rocksdb/rocksdb/db/blob/blob_read_request.h +58 -0
  22. package/deps/rocksdb/rocksdb/db/blob/blob_source.cc +164 -74
  23. package/deps/rocksdb/rocksdb/db/blob/blob_source.h +42 -29
  24. package/deps/rocksdb/rocksdb/db/blob/blob_source_test.cc +419 -62
  25. package/deps/rocksdb/rocksdb/db/blob/db_blob_basic_test.cc +208 -8
  26. package/deps/rocksdb/rocksdb/db/c.cc +68 -0
  27. package/deps/rocksdb/rocksdb/db/c_test.c +95 -2
  28. package/deps/rocksdb/rocksdb/db/column_family.cc +12 -3
  29. package/deps/rocksdb/rocksdb/db/compaction/compaction.cc +92 -15
  30. package/deps/rocksdb/rocksdb/db/compaction/compaction.h +76 -4
  31. package/deps/rocksdb/rocksdb/db/compaction/compaction_iterator.cc +52 -1
  32. package/deps/rocksdb/rocksdb/db/compaction/compaction_iterator.h +30 -1
  33. package/deps/rocksdb/rocksdb/db/compaction/compaction_iterator_test.cc +126 -0
  34. package/deps/rocksdb/rocksdb/db/compaction/compaction_job.cc +203 -1584
  35. package/deps/rocksdb/rocksdb/db/compaction/compaction_job.h +93 -26
  36. package/deps/rocksdb/rocksdb/db/compaction/compaction_job_test.cc +87 -1
  37. package/deps/rocksdb/rocksdb/db/compaction/compaction_outputs.cc +314 -0
  38. package/deps/rocksdb/rocksdb/db/compaction/compaction_outputs.h +328 -0
  39. package/deps/rocksdb/rocksdb/db/compaction/compaction_picker.cc +32 -6
  40. package/deps/rocksdb/rocksdb/db/compaction/compaction_picker.h +4 -1
  41. package/deps/rocksdb/rocksdb/db/compaction/compaction_picker_fifo.cc +7 -3
  42. package/deps/rocksdb/rocksdb/db/compaction/compaction_picker_level.cc +174 -33
  43. package/deps/rocksdb/rocksdb/db/compaction/compaction_picker_test.cc +474 -7
  44. package/deps/rocksdb/rocksdb/db/compaction/compaction_picker_universal.cc +5 -2
  45. package/deps/rocksdb/rocksdb/db/compaction/compaction_service_job.cc +825 -0
  46. package/deps/rocksdb/rocksdb/db/compaction/compaction_state.cc +46 -0
  47. package/deps/rocksdb/rocksdb/db/compaction/compaction_state.h +42 -0
  48. package/deps/rocksdb/rocksdb/db/compaction/subcompaction_state.cc +223 -0
  49. package/deps/rocksdb/rocksdb/db/compaction/subcompaction_state.h +255 -0
  50. package/deps/rocksdb/rocksdb/db/compaction/tiered_compaction_test.cc +1253 -0
  51. package/deps/rocksdb/rocksdb/db/corruption_test.cc +32 -8
  52. package/deps/rocksdb/rocksdb/db/db_basic_test.cc +3 -1
  53. package/deps/rocksdb/rocksdb/db/db_block_cache_test.cc +13 -8
  54. package/deps/rocksdb/rocksdb/db/db_bloom_filter_test.cc +376 -0
  55. package/deps/rocksdb/rocksdb/db/db_compaction_test.cc +103 -78
  56. package/deps/rocksdb/rocksdb/db/db_impl/db_impl.cc +4 -6
  57. package/deps/rocksdb/rocksdb/db/db_impl/db_impl.h +0 -8
  58. package/deps/rocksdb/rocksdb/db/db_impl/db_impl_open.cc +10 -3
  59. package/deps/rocksdb/rocksdb/db/db_impl/db_impl_secondary.cc +21 -6
  60. package/deps/rocksdb/rocksdb/db/db_impl/db_impl_secondary.h +19 -1
  61. package/deps/rocksdb/rocksdb/db/db_iter.cc +91 -14
  62. package/deps/rocksdb/rocksdb/db/db_iter.h +5 -0
  63. package/deps/rocksdb/rocksdb/db/db_kv_checksum_test.cc +33 -0
  64. package/deps/rocksdb/rocksdb/db/db_properties_test.cc +79 -0
  65. package/deps/rocksdb/rocksdb/db/db_range_del_test.cc +2 -0
  66. package/deps/rocksdb/rocksdb/db/db_test2.cc +1 -1
  67. package/deps/rocksdb/rocksdb/db/db_wal_test.cc +5 -2
  68. package/deps/rocksdb/rocksdb/db/db_with_timestamp_basic_test.cc +185 -0
  69. package/deps/rocksdb/rocksdb/db/dbformat.cc +1 -4
  70. package/deps/rocksdb/rocksdb/db/dbformat.h +2 -8
  71. package/deps/rocksdb/rocksdb/db/internal_stats.cc +71 -29
  72. package/deps/rocksdb/rocksdb/db/internal_stats.h +160 -5
  73. package/deps/rocksdb/rocksdb/db/log_reader.cc +29 -3
  74. package/deps/rocksdb/rocksdb/db/log_reader.h +12 -3
  75. package/deps/rocksdb/rocksdb/db/repair_test.cc +1 -3
  76. package/deps/rocksdb/rocksdb/db/version_edit.cc +6 -0
  77. package/deps/rocksdb/rocksdb/db/version_set.cc +93 -129
  78. package/deps/rocksdb/rocksdb/db/version_set.h +4 -4
  79. package/deps/rocksdb/rocksdb/db/version_set_sync_and_async.h +2 -2
  80. package/deps/rocksdb/rocksdb/db/version_set_test.cc +42 -35
  81. package/deps/rocksdb/rocksdb/db/write_batch.cc +10 -2
  82. package/deps/rocksdb/rocksdb/db/write_batch_internal.h +4 -1
  83. package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_common.cc +10 -4
  84. package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_common.h +3 -3
  85. package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_driver.cc +3 -2
  86. package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_gflags.cc +4 -0
  87. package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_shared_state.h +5 -1
  88. package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_test_base.cc +140 -8
  89. package/deps/rocksdb/rocksdb/db_stress_tool/db_stress_test_base.h +12 -0
  90. package/deps/rocksdb/rocksdb/db_stress_tool/multi_ops_txns_stress.cc +46 -7
  91. package/deps/rocksdb/rocksdb/db_stress_tool/multi_ops_txns_stress.h +7 -0
  92. package/deps/rocksdb/rocksdb/db_stress_tool/no_batched_ops_stress.cc +27 -7
  93. package/deps/rocksdb/rocksdb/env/composite_env_wrapper.h +8 -0
  94. package/deps/rocksdb/rocksdb/env/env_posix.cc +14 -0
  95. package/deps/rocksdb/rocksdb/env/env_test.cc +130 -1
  96. package/deps/rocksdb/rocksdb/env/fs_posix.cc +7 -1
  97. package/deps/rocksdb/rocksdb/env/io_posix.cc +18 -50
  98. package/deps/rocksdb/rocksdb/env/io_posix.h +53 -6
  99. package/deps/rocksdb/rocksdb/file/file_prefetch_buffer.cc +8 -10
  100. package/deps/rocksdb/rocksdb/file/file_prefetch_buffer.h +3 -7
  101. package/deps/rocksdb/rocksdb/file/prefetch_test.cc +239 -259
  102. package/deps/rocksdb/rocksdb/file/random_access_file_reader.cc +84 -19
  103. package/deps/rocksdb/rocksdb/file/random_access_file_reader.h +24 -4
  104. package/deps/rocksdb/rocksdb/include/rocksdb/advanced_options.h +1 -1
  105. package/deps/rocksdb/rocksdb/include/rocksdb/c.h +31 -1
  106. package/deps/rocksdb/rocksdb/include/rocksdb/cache.h +11 -7
  107. package/deps/rocksdb/rocksdb/include/rocksdb/compaction_job_stats.h +2 -0
  108. package/deps/rocksdb/rocksdb/include/rocksdb/db.h +14 -0
  109. package/deps/rocksdb/rocksdb/include/rocksdb/env.h +20 -0
  110. package/deps/rocksdb/rocksdb/include/rocksdb/options.h +37 -13
  111. package/deps/rocksdb/rocksdb/include/rocksdb/perf_context.h +7 -0
  112. package/deps/rocksdb/rocksdb/include/rocksdb/statistics.h +14 -0
  113. package/deps/rocksdb/rocksdb/include/rocksdb/threadpool.h +9 -0
  114. package/deps/rocksdb/rocksdb/include/rocksdb/write_batch.h +13 -13
  115. package/deps/rocksdb/rocksdb/logging/auto_roll_logger.cc +12 -2
  116. package/deps/rocksdb/rocksdb/monitoring/perf_context.cc +38 -0
  117. package/deps/rocksdb/rocksdb/monitoring/statistics.cc +7 -1
  118. package/deps/rocksdb/rocksdb/port/win/env_win.cc +17 -0
  119. package/deps/rocksdb/rocksdb/port/win/env_win.h +8 -0
  120. package/deps/rocksdb/rocksdb/port/win/io_win.cc +6 -3
  121. package/{prebuilds → deps/rocksdb/rocksdb/prebuilds}/linux-x64/node.napi.node +0 -0
  122. package/deps/rocksdb/rocksdb/src.mk +5 -0
  123. package/deps/rocksdb/rocksdb/table/block_based/block.h +1 -2
  124. package/deps/rocksdb/rocksdb/table/block_based/block_based_table_builder.cc +1 -1
  125. package/deps/rocksdb/rocksdb/table/block_based/block_based_table_iterator.cc +5 -2
  126. package/deps/rocksdb/rocksdb/table/block_based/block_based_table_reader.cc +1 -1
  127. package/deps/rocksdb/rocksdb/table/block_based/block_based_table_reader_impl.h +15 -12
  128. package/deps/rocksdb/rocksdb/table/block_based/block_prefetcher.cc +5 -4
  129. package/deps/rocksdb/rocksdb/table/block_based/block_prefetcher.h +2 -1
  130. package/deps/rocksdb/rocksdb/table/block_based/filter_policy.cc +1 -1
  131. package/deps/rocksdb/rocksdb/table/block_based/partitioned_index_iterator.cc +4 -4
  132. package/deps/rocksdb/rocksdb/table/block_fetcher.cc +1 -2
  133. package/deps/rocksdb/rocksdb/table/get_context.cc +1 -0
  134. package/deps/rocksdb/rocksdb/table/sst_file_dumper.cc +1 -2
  135. package/deps/rocksdb/rocksdb/tools/db_bench_tool.cc +24 -4
  136. package/deps/rocksdb/rocksdb/util/async_file_reader.cc +1 -1
  137. package/deps/rocksdb/rocksdb/util/compression.h +2 -0
  138. package/deps/rocksdb/rocksdb/util/thread_list_test.cc +18 -1
  139. package/deps/rocksdb/rocksdb/util/threadpool_imp.cc +67 -4
  140. package/deps/rocksdb/rocksdb/util/threadpool_imp.h +8 -0
  141. package/deps/rocksdb/rocksdb/utilities/backup/backup_engine.cc +15 -12
  142. package/deps/rocksdb/rocksdb/utilities/backup/backup_engine_test.cc +4 -2
  143. package/deps/rocksdb/rocksdb/utilities/simulator_cache/sim_cache_test.cc +1 -1
  144. package/deps/rocksdb/rocksdb.gyp +5 -1
  145. package/package.json +1 -1
  146. package/prebuilds/darwin-arm64/node.napi.node +0 -0
@@ -9,831 +9,600 @@
9
9
 
10
10
  #include "cache/clock_cache.h"
11
11
 
12
- #ifndef SUPPORT_CLOCK_CACHE
12
+ #include <cassert>
13
+ #include <cstdint>
14
+ #include <cstdio>
15
+ #include <functional>
13
16
 
14
- namespace ROCKSDB_NAMESPACE {
15
-
16
- std::shared_ptr<Cache> NewClockCache(
17
- size_t /*capacity*/, int /*num_shard_bits*/, bool /*strict_capacity_limit*/,
18
- CacheMetadataChargePolicy /*metadata_charge_policy*/) {
19
- // Clock cache not supported.
20
- return nullptr;
21
- }
17
+ #include "monitoring/perf_context_imp.h"
18
+ #include "monitoring/statistics.h"
19
+ #include "port/lang.h"
20
+ #include "util/distributed_mutex.h"
21
+ #include "util/hash.h"
22
+ #include "util/math.h"
23
+ #include "util/random.h"
22
24
 
23
- } // namespace ROCKSDB_NAMESPACE
25
+ namespace ROCKSDB_NAMESPACE {
24
26
 
25
- #else
27
+ namespace clock_cache {
26
28
 
27
- #include <assert.h>
28
- #include <atomic>
29
- #include <deque>
29
+ ClockHandleTable::ClockHandleTable(int hash_bits)
30
+ : length_bits_(hash_bits),
31
+ length_bits_mask_((uint32_t{1} << length_bits_) - 1),
32
+ occupancy_(0),
33
+ occupancy_limit_(static_cast<uint32_t>((uint32_t{1} << length_bits_) *
34
+ kStrictLoadFactor)),
35
+ array_(new ClockHandle[size_t{1} << length_bits_]) {
36
+ assert(hash_bits <= 32);
37
+ }
30
38
 
31
- // "tbb/concurrent_hash_map.h" requires RTTI if exception is enabled.
32
- // Disable it so users can chooose to disable RTTI.
33
- #ifndef ROCKSDB_USE_RTTI
34
- #define TBB_USE_EXCEPTIONS 0
35
- #endif
36
- #include "cache/sharded_cache.h"
37
- #include "port/lang.h"
38
- #include "port/malloc.h"
39
- #include "port/port.h"
40
- #include "tbb/concurrent_hash_map.h"
41
- #include "util/autovector.h"
42
- #include "util/distributed_mutex.h"
39
+ ClockHandleTable::~ClockHandleTable() {
40
+ ApplyToEntriesRange([](ClockHandle* h) { h->FreeData(); }, 0, GetTableSize());
41
+ }
43
42
 
44
- namespace ROCKSDB_NAMESPACE {
43
+ ClockHandle* ClockHandleTable::Lookup(const Slice& key) {
44
+ int probe = 0;
45
+ int slot = FindVisibleElement(key, probe, 0);
46
+ return (slot == -1) ? nullptr : &array_[slot];
47
+ }
45
48
 
46
- namespace {
47
-
48
- // An implementation of the Cache interface based on CLOCK algorithm, with
49
- // better concurrent performance than LRUCache. The idea of CLOCK algorithm
50
- // is to maintain all cache entries in a circular list, and an iterator
51
- // (the "head") pointing to the last examined entry. Eviction starts from the
52
- // current head. Each entry is given a second chance before eviction, if it
53
- // has been access since last examine. In contrast to LRU, no modification
54
- // to the internal data-structure (except for flipping the usage bit) needs
55
- // to be done upon lookup. This gives us oppertunity to implement a cache
56
- // with better concurrency.
57
- //
58
- // Each cache entry is represented by a cache handle, and all the handles
59
- // are arranged in a circular list, as describe above. Upon erase of an entry,
60
- // we never remove the handle. Instead, the handle is put into a recycle bin
61
- // to be re-use. This is to avoid memory dealocation, which is hard to deal
62
- // with in concurrent environment.
63
- //
64
- // The cache also maintains a concurrent hash map for lookup. Any concurrent
65
- // hash map implementation should do the work. We currently use
66
- // tbb::concurrent_hash_map because it supports concurrent erase.
67
- //
68
- // Each cache handle has the following flags and counters, which are squeeze
69
- // in an atomic interger, to make sure the handle always be in a consistent
70
- // state:
71
- //
72
- // * In-cache bit: whether the entry is reference by the cache itself. If
73
- // an entry is in cache, its key would also be available in the hash map.
74
- // * Usage bit: whether the entry has been access by user since last
75
- // examine for eviction. Can be reset by eviction.
76
- // * Reference count: reference count by user.
77
- //
78
- // An entry can be reference only when it's in cache. An entry can be evicted
79
- // only when it is in cache, has no usage since last examine, and reference
80
- // count is zero.
81
- //
82
- // The follow figure shows a possible layout of the cache. Boxes represents
83
- // cache handles and numbers in each box being in-cache bit, usage bit and
84
- // reference count respectively.
85
- //
86
- // hash map:
87
- // +-------+--------+
88
- // | key | handle |
89
- // +-------+--------+
90
- // | "foo" | 5 |-------------------------------------+
91
- // +-------+--------+ |
92
- // | "bar" | 2 |--+ |
93
- // +-------+--------+ | |
94
- // | |
95
- // head | |
96
- // | | |
97
- // circular list: | | |
98
- // +-------+ +-------+ +-------+ +-------+ +-------+ +-------
99
- // |(0,0,0)|---|(1,1,0)|---|(0,0,0)|---|(0,1,3)|---|(1,0,0)|---| ...
100
- // +-------+ +-------+ +-------+ +-------+ +-------+ +-------
101
- // | |
102
- // +-------+ +-----------+
103
- // | |
104
- // +---+---+
105
- // recycle bin: | 1 | 3 |
106
- // +---+---+
107
- //
108
- // Suppose we try to insert "baz" into the cache at this point and the cache is
109
- // full. The cache will first look for entries to evict, starting from where
110
- // head points to (the second entry). It resets usage bit of the second entry,
111
- // skips the third and fourth entry since they are not in cache, and finally
112
- // evict the fifth entry ("foo"). It looks at recycle bin for available handle,
113
- // grabs handle 3, and insert the key into the handle. The following figure
114
- // shows the resulting layout.
115
- //
116
- // hash map:
117
- // +-------+--------+
118
- // | key | handle |
119
- // +-------+--------+
120
- // | "baz" | 3 |-------------+
121
- // +-------+--------+ |
122
- // | "bar" | 2 |--+ |
123
- // +-------+--------+ | |
124
- // | |
125
- // | | head
126
- // | | |
127
- // circular list: | | |
128
- // +-------+ +-------+ +-------+ +-------+ +-------+ +-------
129
- // |(0,0,0)|---|(1,0,0)|---|(1,0,0)|---|(0,1,3)|---|(0,0,0)|---| ...
130
- // +-------+ +-------+ +-------+ +-------+ +-------+ +-------
131
- // | |
132
- // +-------+ +-----------------------------------+
133
- // | |
134
- // +---+---+
135
- // recycle bin: | 1 | 5 |
136
- // +---+---+
137
- //
138
- // A global mutex guards the circular list, the head, and the recycle bin.
139
- // We additionally require that modifying the hash map needs to hold the mutex.
140
- // As such, Modifying the cache (such as Insert() and Erase()) require to
141
- // hold the mutex. Lookup() only access the hash map and the flags associated
142
- // with each handle, and don't require explicit locking. Release() has to
143
- // acquire the mutex only when it releases the last reference to the entry and
144
- // the entry has been erased from cache explicitly. A future improvement could
145
- // be to remove the mutex completely.
146
- //
147
- // Benchmark:
148
- // We run readrandom db_bench on a test DB of size 13GB, with size of each
149
- // level:
150
- //
151
- // Level Files Size(MB)
152
- // -------------------------
153
- // L0 1 0.01
154
- // L1 18 17.32
155
- // L2 230 182.94
156
- // L3 1186 1833.63
157
- // L4 4602 8140.30
158
- //
159
- // We test with both 32 and 16 read threads, with 2GB cache size (the whole DB
160
- // doesn't fits in) and 64GB cache size (the whole DB can fit in cache), and
161
- // whether to put index and filter blocks in block cache. The benchmark runs
162
- // with
163
- // with RocksDB 4.10. We got the following result:
164
- //
165
- // Threads Cache Cache ClockCache LRUCache
166
- // Size Index/Filter Throughput(MB/s) Hit Throughput(MB/s) Hit
167
- // 32 2GB yes 466.7 85.9% 433.7 86.5%
168
- // 32 2GB no 529.9 72.7% 532.7 73.9%
169
- // 32 64GB yes 649.9 99.9% 507.9 99.9%
170
- // 32 64GB no 740.4 99.9% 662.8 99.9%
171
- // 16 2GB yes 278.4 85.9% 283.4 86.5%
172
- // 16 2GB no 318.6 72.7% 335.8 73.9%
173
- // 16 64GB yes 391.9 99.9% 353.3 99.9%
174
- // 16 64GB no 433.8 99.8% 419.4 99.8%
175
-
176
- // Cache entry meta data.
177
- struct CacheHandle {
178
- Slice key;
179
- void* value;
180
- size_t charge;
181
- Cache::DeleterFn deleter;
182
- uint32_t hash;
183
-
184
- // Addition to "charge" to get "total charge" under metadata policy.
185
- uint32_t meta_charge;
186
-
187
- // Flags and counters associated with the cache handle:
188
- // lowest bit: in-cache bit
189
- // second lowest bit: usage bit
190
- // the rest bits: reference count
191
- // The handle is unused when flags equals to 0. The thread decreases the count
192
- // to 0 is responsible to put the handle back to recycle_ and cleanup memory.
193
- std::atomic<uint32_t> flags;
194
-
195
- CacheHandle() = default;
196
-
197
- CacheHandle(const CacheHandle& a) { *this = a; }
198
-
199
- CacheHandle(const Slice& k, void* v,
200
- void (*del)(const Slice& key, void* value))
201
- : key(k), value(v), deleter(del) {}
202
-
203
- CacheHandle& operator=(const CacheHandle& a) {
204
- // Only copy members needed for deletion.
205
- key = a.key;
206
- value = a.value;
207
- deleter = a.deleter;
208
- return *this;
49
+ ClockHandle* ClockHandleTable::Insert(ClockHandle* h, ClockHandle** old) {
50
+ int probe = 0;
51
+ int slot =
52
+ FindVisibleElementOrAvailableSlot(h->key(), probe, 1 /*displacement*/);
53
+ *old = nullptr;
54
+ if (slot == -1) {
55
+ return nullptr;
209
56
  }
210
57
 
211
- inline static uint32_t CalcMetadataCharge(
212
- Slice key, CacheMetadataChargePolicy metadata_charge_policy) {
213
- size_t meta_charge = 0;
214
- if (metadata_charge_policy == kFullChargeCacheMetadata) {
215
- meta_charge += sizeof(CacheHandle);
216
- #ifdef ROCKSDB_MALLOC_USABLE_SIZE
217
- meta_charge +=
218
- malloc_usable_size(static_cast<void*>(const_cast<char*>(key.data())));
219
- #else
220
- meta_charge += key.size();
221
- #endif
58
+ if (array_[slot].IsEmpty() || array_[slot].IsTombstone()) {
59
+ bool empty = array_[slot].IsEmpty();
60
+ Assign(slot, h);
61
+ ClockHandle* new_entry = &array_[slot];
62
+ if (empty) {
63
+ // This used to be an empty slot.
64
+ return new_entry;
65
+ }
66
+ // It used to be a tombstone, so there may already be a copy of the
67
+ // key in the table.
68
+ slot = FindVisibleElement(h->key(), probe, 0 /*displacement*/);
69
+ if (slot == -1) {
70
+ // No existing copy of the key.
71
+ return new_entry;
222
72
  }
223
- assert(meta_charge <= UINT32_MAX);
224
- return static_cast<uint32_t>(meta_charge);
73
+ *old = &array_[slot];
74
+ return new_entry;
75
+ } else {
76
+ // There is an existing copy of the key.
77
+ *old = &array_[slot];
78
+ // Find an available slot for the new element.
79
+ array_[slot].displacements++;
80
+ slot = FindAvailableSlot(h->key(), probe, 1 /*displacement*/);
81
+ if (slot == -1) {
82
+ // No available slots. Roll back displacements.
83
+ probe = 0;
84
+ slot = FindVisibleElement(h->key(), probe, -1);
85
+ array_[slot].displacements--;
86
+ FindAvailableSlot(h->key(), probe, -1);
87
+ return nullptr;
88
+ }
89
+ Assign(slot, h);
90
+ return &array_[slot];
225
91
  }
92
+ }
226
93
 
227
- inline size_t GetTotalCharge() { return charge + meta_charge; }
228
- };
94
+ void ClockHandleTable::Remove(ClockHandle* h) {
95
+ assert(!h->IsInClockList()); // Already off the clock list.
96
+ int probe = 0;
97
+ FindSlot(
98
+ h->key(), [&h](ClockHandle* e) { return e == h; }, probe,
99
+ -1 /*displacement*/);
100
+ h->SetIsVisible(false);
101
+ h->SetIsElement(false);
102
+ occupancy_--;
103
+ }
229
104
 
230
- // Key of hash map. We store hash value with the key for convenience.
231
- struct ClockCacheKey {
232
- Slice key;
233
- uint32_t hash_value;
105
+ void ClockHandleTable::Assign(int slot, ClockHandle* h) {
106
+ ClockHandle* dst = &array_[slot];
107
+ uint32_t disp = dst->displacements;
108
+ *dst = *h;
109
+ dst->displacements = disp;
110
+ dst->SetIsVisible(true);
111
+ dst->SetIsElement(true);
112
+ dst->SetClockPriority(ClockHandle::ClockPriority::NONE);
113
+ occupancy_++;
114
+ }
234
115
 
235
- ClockCacheKey() = default;
116
+ void ClockHandleTable::Exclude(ClockHandle* h) { h->SetIsVisible(false); }
236
117
 
237
- ClockCacheKey(const Slice& k, uint32_t h) {
238
- key = k;
239
- hash_value = h;
240
- }
118
+ int ClockHandleTable::FindVisibleElement(const Slice& key, int& probe,
119
+ int displacement) {
120
+ return FindSlot(
121
+ key, [&](ClockHandle* h) { return h->Matches(key) && h->IsVisible(); },
122
+ probe, displacement);
123
+ }
241
124
 
242
- static bool equal(const ClockCacheKey& a, const ClockCacheKey& b) {
243
- return a.hash_value == b.hash_value && a.key == b.key;
244
- }
125
+ int ClockHandleTable::FindAvailableSlot(const Slice& key, int& probe,
126
+ int displacement) {
127
+ return FindSlot(
128
+ key, [](ClockHandle* h) { return h->IsEmpty() || h->IsTombstone(); },
129
+ probe, displacement);
130
+ }
245
131
 
246
- static size_t hash(const ClockCacheKey& a) {
247
- return static_cast<size_t>(a.hash_value);
248
- }
249
- };
250
-
251
- struct CleanupContext {
252
- // List of values to be deleted, along with the key and deleter.
253
- autovector<CacheHandle> to_delete_value;
254
-
255
- // List of keys to be deleted.
256
- autovector<const char*> to_delete_key;
257
- };
258
-
259
- // A cache shard which maintains its own CLOCK cache.
260
- class ClockCacheShard final : public CacheShard {
261
- public:
262
- // Hash map type.
263
- using HashTable =
264
- tbb::concurrent_hash_map<ClockCacheKey, CacheHandle*, ClockCacheKey>;
265
-
266
- ClockCacheShard();
267
- ~ClockCacheShard() override;
268
-
269
- // Interfaces
270
- void SetCapacity(size_t capacity) override;
271
- void SetStrictCapacityLimit(bool strict_capacity_limit) override;
272
- Status Insert(const Slice& key, uint32_t hash, void* value, size_t charge,
273
- void (*deleter)(const Slice& key, void* value),
274
- Cache::Handle** handle, Cache::Priority priority) override;
275
- Status Insert(const Slice& key, uint32_t hash, void* value,
276
- const Cache::CacheItemHelper* helper, size_t charge,
277
- Cache::Handle** handle, Cache::Priority priority) override {
278
- return Insert(key, hash, value, charge, helper->del_cb, handle, priority);
279
- }
280
- Cache::Handle* Lookup(const Slice& key, uint32_t hash) override;
281
- Cache::Handle* Lookup(const Slice& key, uint32_t hash,
282
- const Cache::CacheItemHelper* /*helper*/,
283
- const Cache::CreateCallback& /*create_cb*/,
284
- Cache::Priority /*priority*/, bool /*wait*/,
285
- Statistics* /*stats*/) override {
286
- return Lookup(key, hash);
287
- }
288
- bool Release(Cache::Handle* handle, bool /*useful*/,
289
- bool erase_if_last_ref) override {
290
- return Release(handle, erase_if_last_ref);
291
- }
292
- bool IsReady(Cache::Handle* /*handle*/) override { return true; }
293
- void Wait(Cache::Handle* /*handle*/) override {}
294
-
295
- // If the entry in in cache, increase reference count and return true.
296
- // Return false otherwise.
297
- //
298
- // Not necessary to hold mutex_ before being called.
299
- bool Ref(Cache::Handle* handle) override;
300
- bool Release(Cache::Handle* handle, bool erase_if_last_ref = false) override;
301
- void Erase(const Slice& key, uint32_t hash) override;
302
- bool EraseAndConfirm(const Slice& key, uint32_t hash,
303
- CleanupContext* context);
304
- size_t GetUsage() const override;
305
- size_t GetPinnedUsage() const override;
306
- void EraseUnRefEntries() override;
307
- void ApplyToSomeEntries(
308
- const std::function<void(const Slice& key, void* value, size_t charge,
309
- DeleterFn deleter)>& callback,
310
- uint32_t average_entries_per_lock, uint32_t* state) override;
311
-
312
- private:
313
- static const uint32_t kInCacheBit = 1;
314
- static const uint32_t kUsageBit = 2;
315
- static const uint32_t kRefsOffset = 2;
316
- static const uint32_t kOneRef = 1 << kRefsOffset;
317
-
318
- // Helper functions to extract cache handle flags and counters.
319
- static bool InCache(uint32_t flags) { return flags & kInCacheBit; }
320
- static bool HasUsage(uint32_t flags) { return flags & kUsageBit; }
321
- static uint32_t CountRefs(uint32_t flags) { return flags >> kRefsOffset; }
322
-
323
- // Decrease reference count of the entry. If this decreases the count to 0,
324
- // recycle the entry. If set_usage is true, also set the usage bit.
325
- //
326
- // returns true if a value is erased.
327
- //
328
- // Not necessary to hold mutex_ before being called.
329
- bool Unref(CacheHandle* handle, bool set_usage, CleanupContext* context);
330
-
331
- // Unset in-cache bit of the entry. Recycle the handle if necessary.
332
- //
333
- // returns true if a value is erased.
334
- //
335
- // Has to hold mutex_ before being called.
336
- bool UnsetInCache(CacheHandle* handle, CleanupContext* context);
337
-
338
- // Put the handle back to recycle_ list, and put the value associated with
339
- // it into to-be-deleted list. It doesn't cleanup the key as it might be
340
- // reused by another handle.
341
- //
342
- // Has to hold mutex_ before being called.
343
- void RecycleHandle(CacheHandle* handle, CleanupContext* context);
344
-
345
- // Delete keys and values in to-be-deleted list. Call the method without
346
- // holding mutex, as destructors can be expensive.
347
- void Cleanup(const CleanupContext& context);
348
-
349
- // Examine the handle for eviction. If the handle is in cache, usage bit is
350
- // not set, and referece count is 0, evict it from cache. Otherwise unset
351
- // the usage bit.
352
- //
353
- // Has to hold mutex_ before being called.
354
- bool TryEvict(CacheHandle* value, CleanupContext* context);
355
-
356
- // Scan through the circular list, evict entries until we get enough capacity
357
- // for new cache entry of specific size. Return true if success, false
358
- // otherwise.
359
- //
360
- // Has to hold mutex_ before being called.
361
- bool EvictFromCache(size_t charge, CleanupContext* context);
362
-
363
- CacheHandle* Insert(const Slice& key, uint32_t hash, void* value,
364
- size_t change,
365
- void (*deleter)(const Slice& key, void* value),
366
- bool hold_reference, CleanupContext* context,
367
- bool* overwritten);
368
-
369
- // Guards list_, head_, and recycle_. In addition, updating table_ also has
370
- // to hold the mutex, to avoid the cache being in inconsistent state.
371
- mutable DMutex mutex_;
372
-
373
- // The circular list of cache handles. Initially the list is empty. Once a
374
- // handle is needed by insertion, and no more handles are available in
375
- // recycle bin, one more handle is appended to the end.
376
- //
377
- // We use std::deque for the circular list because we want to make sure
378
- // pointers to handles are valid through out the life-cycle of the cache
379
- // (in contrast to std::vector), and be able to grow the list (in contrast
380
- // to statically allocated arrays).
381
- std::deque<CacheHandle> list_;
382
-
383
- // Pointer to the next handle in the circular list to be examine for
384
- // eviction.
385
- size_t head_;
386
-
387
- // Recycle bin of cache handles.
388
- autovector<CacheHandle*> recycle_;
389
-
390
- // Maximum cache size.
391
- std::atomic<size_t> capacity_;
392
-
393
- // Current total size of the cache.
394
- std::atomic<size_t> usage_;
395
-
396
- // Total un-released cache size.
397
- std::atomic<size_t> pinned_usage_;
398
-
399
- // Whether allow insert into cache if cache is full.
400
- std::atomic<bool> strict_capacity_limit_;
401
-
402
- // Hash table (tbb::concurrent_hash_map) for lookup.
403
- HashTable table_;
404
- };
405
-
406
- ClockCacheShard::ClockCacheShard()
407
- : head_(0), usage_(0), pinned_usage_(0), strict_capacity_limit_(false) {}
408
-
409
- ClockCacheShard::~ClockCacheShard() {
410
- for (auto& handle : list_) {
411
- uint32_t flags = handle.flags.load(std::memory_order_relaxed);
412
- if (InCache(flags) || CountRefs(flags) > 0) {
413
- if (handle.deleter != nullptr) {
414
- (*handle.deleter)(handle.key, handle.value);
415
- }
416
- delete[] handle.key.data();
132
+ int ClockHandleTable::FindVisibleElementOrAvailableSlot(const Slice& key,
133
+ int& probe,
134
+ int displacement) {
135
+ return FindSlot(
136
+ key,
137
+ [&](ClockHandle* h) {
138
+ return h->IsEmpty() || h->IsTombstone() ||
139
+ (h->Matches(key) && h->IsVisible());
140
+ },
141
+ probe, displacement);
142
+ }
143
+
144
+ inline int ClockHandleTable::FindSlot(const Slice& key,
145
+ std::function<bool(ClockHandle*)> cond,
146
+ int& probe, int displacement) {
147
+ uint32_t base = ModTableSize(Hash(key.data(), key.size(), kProbingSeed1));
148
+ uint32_t increment =
149
+ ModTableSize((Hash(key.data(), key.size(), kProbingSeed2) << 1) | 1);
150
+ uint32_t current = ModTableSize(base + probe * increment);
151
+ while (true) {
152
+ ClockHandle* h = &array_[current];
153
+ probe++;
154
+ if (current == base && probe > 1) {
155
+ // We looped back.
156
+ return -1;
157
+ }
158
+ if (cond(h)) {
159
+ return current;
160
+ }
161
+ if (h->IsEmpty()) {
162
+ // We check emptyness after the condition, because
163
+ // the condition may be emptyness.
164
+ return -1;
417
165
  }
166
+ h->displacements += displacement;
167
+ current = ModTableSize(current + increment);
418
168
  }
419
169
  }
420
170
 
421
- size_t ClockCacheShard::GetUsage() const {
422
- return usage_.load(std::memory_order_relaxed);
171
+ ClockCacheShard::ClockCacheShard(
172
+ size_t capacity, size_t estimated_value_size, bool strict_capacity_limit,
173
+ CacheMetadataChargePolicy metadata_charge_policy)
174
+ : capacity_(capacity),
175
+ strict_capacity_limit_(strict_capacity_limit),
176
+ clock_pointer_(0),
177
+ table_(
178
+ CalcHashBits(capacity, estimated_value_size, metadata_charge_policy)),
179
+ usage_(0),
180
+ clock_usage_(0) {
181
+ set_metadata_charge_policy(metadata_charge_policy);
423
182
  }
424
183
 
425
- size_t ClockCacheShard::GetPinnedUsage() const {
426
- return pinned_usage_.load(std::memory_order_relaxed);
184
+ void ClockCacheShard::EraseUnRefEntries() {
185
+ autovector<ClockHandle> last_reference_list;
186
+ {
187
+ DMutexLock l(mutex_);
188
+ uint32_t slot = 0;
189
+ do {
190
+ ClockHandle* old = &(table_.array_[slot]);
191
+ if (!old->IsInClockList()) {
192
+ continue;
193
+ }
194
+ ClockRemove(old);
195
+ table_.Remove(old);
196
+ assert(usage_ >= old->total_charge);
197
+ usage_ -= old->total_charge;
198
+ last_reference_list.push_back(*old);
199
+ slot = table_.ModTableSize(slot + 1);
200
+ } while (slot != 0);
201
+ }
202
+
203
+ // Free the entries here outside of mutex for performance reasons.
204
+ for (auto& h : last_reference_list) {
205
+ h.FreeData();
206
+ }
427
207
  }
428
208
 
429
209
  void ClockCacheShard::ApplyToSomeEntries(
430
210
  const std::function<void(const Slice& key, void* value, size_t charge,
431
211
  DeleterFn deleter)>& callback,
432
212
  uint32_t average_entries_per_lock, uint32_t* state) {
433
- assert(average_entries_per_lock > 0);
213
+ // The state is essentially going to be the starting hash, which works
214
+ // nicely even if we resize between calls because we use upper-most
215
+ // hash bits for table indexes.
434
216
  DMutexLock l(mutex_);
217
+ uint32_t length_bits = table_.GetLengthBits();
218
+ uint32_t length = table_.GetTableSize();
435
219
 
436
- // Figure out the range to iterate, update `state`
437
- size_t list_size = list_.size();
438
- size_t start_idx = *state;
439
- size_t end_idx = start_idx + average_entries_per_lock;
440
- if (start_idx > list_size) {
441
- // Shouldn't reach here, but recoverable
442
- assert(false);
443
- // Mark finished with all
444
- *state = UINT32_MAX;
445
- return;
446
- }
447
- if (end_idx >= list_size || end_idx >= UINT32_MAX) {
448
- // This also includes the hypothetical case of >4 billion
449
- // cache handles.
450
- end_idx = list_size;
451
- // Mark finished with all
220
+ assert(average_entries_per_lock > 0);
221
+ // Assuming we are called with same average_entries_per_lock repeatedly,
222
+ // this simplifies some logic (index_end will not overflow).
223
+ assert(average_entries_per_lock < length || *state == 0);
224
+
225
+ uint32_t index_begin = *state >> (32 - length_bits);
226
+ uint32_t index_end = index_begin + average_entries_per_lock;
227
+ if (index_end >= length) {
228
+ // Going to end
229
+ index_end = length;
452
230
  *state = UINT32_MAX;
453
231
  } else {
454
- *state = static_cast<uint32_t>(end_idx);
232
+ *state = index_end << (32 - length_bits);
455
233
  }
456
234
 
457
- // Do the iteration
458
- auto cur = list_.begin() + start_idx;
459
- auto end = list_.begin() + end_idx;
460
- for (; cur != end; ++cur) {
461
- const CacheHandle& handle = *cur;
462
- // Use relaxed semantics instead of acquire semantics since we are
463
- // holding mutex
464
- uint32_t flags = handle.flags.load(std::memory_order_relaxed);
465
- if (InCache(flags)) {
466
- callback(handle.key, handle.value, handle.charge, handle.deleter);
467
- }
468
- }
235
+ table_.ApplyToEntriesRange(
236
+ [callback,
237
+ metadata_charge_policy = metadata_charge_policy_](ClockHandle* h) {
238
+ callback(h->key(), h->value, h->GetCharge(metadata_charge_policy),
239
+ h->deleter);
240
+ },
241
+ index_begin, index_end);
469
242
  }
470
243
 
471
- void ClockCacheShard::RecycleHandle(CacheHandle* handle,
472
- CleanupContext* context) {
473
- mutex_.AssertHeld();
474
- assert(!InCache(handle->flags) && CountRefs(handle->flags) == 0);
475
- context->to_delete_key.push_back(handle->key.data());
476
- context->to_delete_value.emplace_back(*handle);
477
- size_t total_charge = handle->GetTotalCharge();
478
- // clearing `handle` fields would go here but not strictly required
479
- recycle_.push_back(handle);
480
- usage_.fetch_sub(total_charge, std::memory_order_relaxed);
244
+ void ClockCacheShard::ClockRemove(ClockHandle* h) {
245
+ assert(h->IsInClockList());
246
+ h->SetClockPriority(ClockHandle::ClockPriority::NONE);
247
+ assert(clock_usage_ >= h->total_charge);
248
+ clock_usage_ -= h->total_charge;
481
249
  }
482
250
 
483
- void ClockCacheShard::Cleanup(const CleanupContext& context) {
484
- for (const CacheHandle& handle : context.to_delete_value) {
485
- if (handle.deleter) {
486
- (*handle.deleter)(handle.key, handle.value);
487
- }
488
- }
489
- for (const char* key : context.to_delete_key) {
490
- delete[] key;
491
- }
251
+ void ClockCacheShard::ClockInsert(ClockHandle* h) {
252
+ assert(!h->IsInClockList());
253
+ bool is_high_priority =
254
+ h->HasHit() || h->GetCachePriority() == Cache::Priority::HIGH;
255
+ h->SetClockPriority(static_cast<ClockHandle::ClockPriority>(
256
+ is_high_priority * ClockHandle::ClockPriority::HIGH +
257
+ (1 - is_high_priority) * ClockHandle::ClockPriority::MEDIUM));
258
+ clock_usage_ += h->total_charge;
492
259
  }
493
260
 
494
- bool ClockCacheShard::Ref(Cache::Handle* h) {
495
- auto handle = reinterpret_cast<CacheHandle*>(h);
496
- // CAS loop to increase reference count.
497
- uint32_t flags = handle->flags.load(std::memory_order_relaxed);
498
- while (InCache(flags)) {
499
- // Use acquire semantics on success, as further operations on the cache
500
- // entry has to be order after reference count is increased.
501
- if (handle->flags.compare_exchange_weak(flags, flags + kOneRef,
502
- std::memory_order_acquire,
503
- std::memory_order_relaxed)) {
504
- if (CountRefs(flags) == 0) {
505
- // No reference count before the operation.
506
- size_t total_charge = handle->GetTotalCharge();
507
- pinned_usage_.fetch_add(total_charge, std::memory_order_relaxed);
508
- }
509
- return true;
261
+ void ClockCacheShard::EvictFromClock(size_t charge,
262
+ autovector<ClockHandle>* deleted) {
263
+ assert(charge <= capacity_);
264
+ while (clock_usage_ > 0 && (usage_ + charge) > capacity_) {
265
+ ClockHandle* old = &table_.array_[clock_pointer_];
266
+ clock_pointer_ = table_.ModTableSize(clock_pointer_ + 1);
267
+ // Clock list contains only elements which can be evicted.
268
+ if (!old->IsInClockList()) {
269
+ continue;
510
270
  }
271
+ if (old->GetClockPriority() == ClockHandle::ClockPriority::LOW) {
272
+ ClockRemove(old);
273
+ table_.Remove(old);
274
+ assert(usage_ >= old->total_charge);
275
+ usage_ -= old->total_charge;
276
+ deleted->push_back(*old);
277
+ return;
278
+ }
279
+ old->DecreaseClockPriority();
511
280
  }
512
- return false;
513
281
  }
514
282
 
515
- bool ClockCacheShard::Unref(CacheHandle* handle, bool set_usage,
516
- CleanupContext* context) {
517
- if (set_usage) {
518
- handle->flags.fetch_or(kUsageBit, std::memory_order_relaxed);
519
- }
520
- // If the handle reaches state refs=0 and InCache=true after this
521
- // atomic operation then we cannot access `handle` afterward, because
522
- // it could be evicted before we access the `handle`.
523
- size_t total_charge = handle->GetTotalCharge();
524
-
525
- // Use acquire-release semantics as previous operations on the cache entry
526
- // has to be order before reference count is decreased, and potential cleanup
527
- // of the entry has to be order after.
528
- uint32_t flags = handle->flags.fetch_sub(kOneRef, std::memory_order_acq_rel);
529
- assert(CountRefs(flags) > 0);
530
- if (CountRefs(flags) == 1) {
531
- // this is the last reference.
532
- pinned_usage_.fetch_sub(total_charge, std::memory_order_relaxed);
533
- // Cleanup if it is the last reference.
534
- if (!InCache(flags)) {
535
- DMutexLock l(mutex_);
536
- RecycleHandle(handle, context);
537
- }
538
- }
539
- return context->to_delete_value.size();
540
- }
541
-
542
- bool ClockCacheShard::UnsetInCache(CacheHandle* handle,
543
- CleanupContext* context) {
544
- mutex_.AssertHeld();
545
- // Use acquire-release semantics as previous operations on the cache entry
546
- // has to be order before reference count is decreased, and potential cleanup
547
- // of the entry has to be order after.
548
- uint32_t flags =
549
- handle->flags.fetch_and(~kInCacheBit, std::memory_order_acq_rel);
550
- // Cleanup if it is the last reference.
551
- if (InCache(flags) && CountRefs(flags) == 0) {
552
- RecycleHandle(handle, context);
553
- }
554
- return context->to_delete_value.size();
555
- }
556
-
557
- bool ClockCacheShard::TryEvict(CacheHandle* handle, CleanupContext* context) {
558
- mutex_.AssertHeld();
559
- uint32_t flags = kInCacheBit;
560
- if (handle->flags.compare_exchange_strong(flags, 0, std::memory_order_acquire,
561
- std::memory_order_relaxed)) {
562
- bool erased __attribute__((__unused__)) =
563
- table_.erase(ClockCacheKey(handle->key, handle->hash));
564
- assert(erased);
565
- RecycleHandle(handle, context);
566
- return true;
567
- }
568
- handle->flags.fetch_and(~kUsageBit, std::memory_order_relaxed);
569
- return false;
283
+ size_t ClockCacheShard::CalcEstimatedHandleCharge(
284
+ size_t estimated_value_size,
285
+ CacheMetadataChargePolicy metadata_charge_policy) {
286
+ ClockHandle h;
287
+ h.CalcTotalCharge(estimated_value_size, metadata_charge_policy);
288
+ return h.total_charge;
570
289
  }
571
290
 
572
- bool ClockCacheShard::EvictFromCache(size_t charge, CleanupContext* context) {
573
- size_t usage = usage_.load(std::memory_order_relaxed);
574
- size_t capacity = capacity_.load(std::memory_order_relaxed);
575
- if (usage == 0) {
576
- return charge <= capacity;
577
- }
578
- size_t new_head = head_;
579
- bool second_iteration = false;
580
- while (usage + charge > capacity) {
581
- assert(new_head < list_.size());
582
- if (TryEvict(&list_[new_head], context)) {
583
- usage = usage_.load(std::memory_order_relaxed);
584
- }
585
- new_head = (new_head + 1 >= list_.size()) ? 0 : new_head + 1;
586
- if (new_head == head_) {
587
- if (second_iteration) {
588
- return false;
589
- } else {
590
- second_iteration = true;
591
- }
592
- }
593
- }
594
- head_ = new_head;
595
- return true;
291
+ int ClockCacheShard::CalcHashBits(
292
+ size_t capacity, size_t estimated_value_size,
293
+ CacheMetadataChargePolicy metadata_charge_policy) {
294
+ size_t handle_charge =
295
+ CalcEstimatedHandleCharge(estimated_value_size, metadata_charge_policy);
296
+ assert(handle_charge > 0);
297
+ uint32_t num_entries =
298
+ static_cast<uint32_t>(capacity / (kLoadFactor * handle_charge)) + 1;
299
+ assert(num_entries <= uint32_t{1} << 31);
300
+ return FloorLog2((num_entries << 1) - 1);
596
301
  }
597
302
 
598
303
  void ClockCacheShard::SetCapacity(size_t capacity) {
599
- CleanupContext context;
304
+ assert(false); // Not supported. TODO(Guido) Support it?
305
+ autovector<ClockHandle> last_reference_list;
600
306
  {
601
307
  DMutexLock l(mutex_);
602
- capacity_.store(capacity, std::memory_order_relaxed);
603
- EvictFromCache(0, &context);
308
+ capacity_ = capacity;
309
+ EvictFromClock(0, &last_reference_list);
310
+ }
311
+
312
+ // Free the entries here outside of mutex for performance reasons.
313
+ for (auto& h : last_reference_list) {
314
+ h.FreeData();
604
315
  }
605
- Cleanup(context);
606
316
  }
607
317
 
608
318
  void ClockCacheShard::SetStrictCapacityLimit(bool strict_capacity_limit) {
609
- strict_capacity_limit_.store(strict_capacity_limit,
610
- std::memory_order_relaxed);
611
- }
612
-
613
- CacheHandle* ClockCacheShard::Insert(
614
- const Slice& key, uint32_t hash, void* value, size_t charge,
615
- void (*deleter)(const Slice& key, void* value), bool hold_reference,
616
- CleanupContext* context, bool* overwritten) {
617
- assert(overwritten != nullptr && *overwritten == false);
618
- uint32_t meta_charge =
619
- CacheHandle::CalcMetadataCharge(key, metadata_charge_policy_);
620
- size_t total_charge = charge + meta_charge;
621
319
  DMutexLock l(mutex_);
622
- bool success = EvictFromCache(total_charge, context);
623
- bool strict = strict_capacity_limit_.load(std::memory_order_relaxed);
624
- if (!success && (strict || !hold_reference)) {
625
- context->to_delete_key.push_back(key.data());
626
- if (!hold_reference) {
627
- context->to_delete_value.emplace_back(key, value, deleter);
628
- }
629
- return nullptr;
630
- }
631
- // Grab available handle from recycle bin. If recycle bin is empty, create
632
- // and append new handle to end of circular list.
633
- CacheHandle* handle = nullptr;
634
- if (!recycle_.empty()) {
635
- handle = recycle_.back();
636
- recycle_.pop_back();
637
- } else {
638
- list_.emplace_back();
639
- handle = &list_.back();
640
- }
641
- // Fill handle.
642
- handle->key = key;
643
- handle->hash = hash;
644
- handle->value = value;
645
- handle->charge = charge;
646
- handle->meta_charge = meta_charge;
647
- handle->deleter = deleter;
648
- uint32_t flags = hold_reference ? kInCacheBit + kOneRef : kInCacheBit;
649
-
650
- // TODO investigate+fix suspected race condition:
651
- // [thread 1] Lookup starts, up to Ref()
652
- // [thread 2] Erase/evict the entry just looked up
653
- // [thread 1] Ref() the handle, even though it's in the recycle bin
654
- // [thread 2] Insert with recycling that handle
655
- // Here we obliterate the other thread's Ref
656
- // Possible fix: never blindly overwrite the flags, but only make
657
- // relative updates (fetch_add, etc).
658
- handle->flags.store(flags, std::memory_order_relaxed);
659
- HashTable::accessor accessor;
660
- if (table_.find(accessor, ClockCacheKey(key, hash))) {
661
- *overwritten = true;
662
- CacheHandle* existing_handle = accessor->second;
663
- table_.erase(accessor);
664
- UnsetInCache(existing_handle, context);
665
- }
666
- table_.insert(HashTable::value_type(ClockCacheKey(key, hash), handle));
667
- if (hold_reference) {
668
- pinned_usage_.fetch_add(total_charge, std::memory_order_relaxed);
669
- }
670
- usage_.fetch_add(total_charge, std::memory_order_relaxed);
671
- return handle;
320
+ strict_capacity_limit_ = strict_capacity_limit;
672
321
  }
673
322
 
674
323
  Status ClockCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
675
- size_t charge,
676
- void (*deleter)(const Slice& key, void* value),
677
- Cache::Handle** out_handle,
678
- Cache::Priority /*priority*/) {
679
- CleanupContext context;
680
- HashTable::accessor accessor;
681
- char* key_data = new char[key.size()];
682
- memcpy(key_data, key.data(), key.size());
683
- Slice key_copy(key_data, key.size());
684
- bool overwritten = false;
685
- CacheHandle* handle = Insert(key_copy, hash, value, charge, deleter,
686
- out_handle != nullptr, &context, &overwritten);
687
- Status s;
688
- if (out_handle != nullptr) {
689
- if (handle == nullptr) {
690
- s = Status::Incomplete("Insert failed due to CLOCK cache being full.");
324
+ size_t charge, Cache::DeleterFn deleter,
325
+ Cache::Handle** handle,
326
+ Cache::Priority priority) {
327
+ if (key.size() != kCacheKeySize) {
328
+ return Status::NotSupported("ClockCache only supports key size " +
329
+ std::to_string(kCacheKeySize) + "B");
330
+ }
331
+
332
+ ClockHandle tmp;
333
+ tmp.value = value;
334
+ tmp.deleter = deleter;
335
+ tmp.hash = hash;
336
+ tmp.CalcTotalCharge(charge, metadata_charge_policy_);
337
+ tmp.SetCachePriority(priority);
338
+ for (int i = 0; i < kCacheKeySize; i++) {
339
+ tmp.key_data[i] = key.data()[i];
340
+ }
341
+
342
+ Status s = Status::OK();
343
+ autovector<ClockHandle> last_reference_list;
344
+ {
345
+ DMutexLock l(mutex_);
346
+ assert(table_.GetOccupancy() <= table_.GetOccupancyLimit());
347
+ // Free the space following strict clock policy until enough space
348
+ // is freed or the clock list is empty.
349
+ EvictFromClock(tmp.total_charge, &last_reference_list);
350
+ if ((usage_ + tmp.total_charge > capacity_ &&
351
+ (strict_capacity_limit_ || handle == nullptr)) ||
352
+ table_.GetOccupancy() == table_.GetOccupancyLimit()) {
353
+ if (handle == nullptr) {
354
+ // Don't insert the entry but still return ok, as if the entry inserted
355
+ // into cache and get evicted immediately.
356
+ last_reference_list.push_back(tmp);
357
+ } else {
358
+ if (table_.GetOccupancy() == table_.GetOccupancyLimit()) {
359
+ // TODO: Consider using a distinct status for this case, but usually
360
+ // it will be handled the same way as reaching charge capacity limit
361
+ s = Status::MemoryLimit(
362
+ "Insert failed because all slots in the hash table are full.");
363
+ } else {
364
+ s = Status::MemoryLimit(
365
+ "Insert failed because the total charge has exceeded the "
366
+ "capacity.");
367
+ }
368
+ }
691
369
  } else {
692
- *out_handle = reinterpret_cast<Cache::Handle*>(handle);
370
+ // Insert into the cache. Note that the cache might get larger than its
371
+ // capacity if not enough space was freed up.
372
+ ClockHandle* old;
373
+ ClockHandle* h = table_.Insert(&tmp, &old);
374
+ assert(h != nullptr); // We're below occupancy, so this insertion should
375
+ // never fail.
376
+ usage_ += h->total_charge;
377
+ if (old != nullptr) {
378
+ s = Status::OkOverwritten();
379
+ assert(old->IsVisible());
380
+ table_.Exclude(old);
381
+ if (!old->HasRefs()) {
382
+ // old is in clock because it's in cache and its reference count is 0.
383
+ ClockRemove(old);
384
+ table_.Remove(old);
385
+ assert(usage_ >= old->total_charge);
386
+ usage_ -= old->total_charge;
387
+ last_reference_list.push_back(*old);
388
+ }
389
+ }
390
+ if (handle == nullptr) {
391
+ ClockInsert(h);
392
+ } else {
393
+ // If caller already holds a ref, no need to take one here.
394
+ if (!h->HasRefs()) {
395
+ h->Ref();
396
+ }
397
+ *handle = reinterpret_cast<Cache::Handle*>(h);
398
+ }
693
399
  }
694
400
  }
695
- if (overwritten) {
696
- assert(s.ok());
697
- s = Status::OkOverwritten();
401
+
402
+ // Free the entries here outside of mutex for performance reasons.
403
+ for (auto& h : last_reference_list) {
404
+ h.FreeData();
698
405
  }
699
- Cleanup(context);
406
+
700
407
  return s;
701
408
  }
702
409
 
703
- Cache::Handle* ClockCacheShard::Lookup(const Slice& key, uint32_t hash) {
704
- HashTable::const_accessor accessor;
705
- if (!table_.find(accessor, ClockCacheKey(key, hash))) {
706
- return nullptr;
707
- }
708
- CacheHandle* handle = accessor->second;
709
- accessor.release();
710
- // Ref() could fail if another thread sneak in and evict/erase the cache
711
- // entry before we are able to hold reference.
712
- if (!Ref(reinterpret_cast<Cache::Handle*>(handle))) {
713
- return nullptr;
714
- }
715
- // Double check the key since the handle may now representing another key
716
- // if other threads sneak in, evict/erase the entry and re-used the handle
717
- // for another cache entry.
718
- if (hash != handle->hash || key != handle->key) {
719
- CleanupContext context;
720
- Unref(handle, false, &context);
721
- // It is possible Unref() delete the entry, so we need to cleanup.
722
- Cleanup(context);
723
- return nullptr;
410
+ Cache::Handle* ClockCacheShard::Lookup(const Slice& key, uint32_t /* hash */) {
411
+ ClockHandle* h = nullptr;
412
+ {
413
+ DMutexLock l(mutex_);
414
+ h = table_.Lookup(key);
415
+ if (h != nullptr) {
416
+ assert(h->IsVisible());
417
+ if (!h->HasRefs()) {
418
+ // The entry is in clock since it's in the hash table and has no
419
+ // external references.
420
+ ClockRemove(h);
421
+ }
422
+ h->Ref();
423
+ h->SetHit();
424
+ }
724
425
  }
725
- return reinterpret_cast<Cache::Handle*>(handle);
426
+ return reinterpret_cast<Cache::Handle*>(h);
726
427
  }
727
428
 
728
- bool ClockCacheShard::Release(Cache::Handle* h, bool erase_if_last_ref) {
729
- CleanupContext context;
730
- CacheHandle* handle = reinterpret_cast<CacheHandle*>(h);
731
- bool erased = Unref(handle, true, &context);
732
- if (erase_if_last_ref && !erased) {
733
- erased = EraseAndConfirm(handle->key, handle->hash, &context);
734
- }
735
- Cleanup(context);
736
- return erased;
429
+ bool ClockCacheShard::Ref(Cache::Handle* h) {
430
+ ClockHandle* e = reinterpret_cast<ClockHandle*>(h);
431
+ DMutexLock l(mutex_);
432
+ // To create another reference - entry must be already externally referenced.
433
+ assert(e->HasRefs());
434
+ e->Ref();
435
+ return true;
737
436
  }
738
437
 
739
- void ClockCacheShard::Erase(const Slice& key, uint32_t hash) {
740
- CleanupContext context;
741
- EraseAndConfirm(key, hash, &context);
742
- Cleanup(context);
743
- }
438
+ bool ClockCacheShard::Release(Cache::Handle* handle, bool erase_if_last_ref) {
439
+ if (handle == nullptr) {
440
+ return false;
441
+ }
442
+ ClockHandle* h = reinterpret_cast<ClockHandle*>(handle);
443
+ ClockHandle copy;
444
+ bool last_reference = false;
445
+ assert(!h->IsInClockList());
446
+ {
447
+ DMutexLock l(mutex_);
448
+ last_reference = h->Unref();
449
+ if (last_reference && h->IsVisible()) {
450
+ // The item is still in cache, and nobody else holds a reference to it.
451
+ if (usage_ > capacity_ || erase_if_last_ref) {
452
+ // The clock list must be empty since the cache is full.
453
+ assert(clock_usage_ == 0 || erase_if_last_ref);
454
+ // Take this opportunity and remove the item.
455
+ table_.Remove(h);
456
+ } else {
457
+ // Put the item back on the clock list, and don't free it.
458
+ ClockInsert(h);
459
+ last_reference = false;
460
+ }
461
+ }
462
+ // If it was the last reference, then decrement the cache usage.
463
+ if (last_reference) {
464
+ assert(usage_ >= h->total_charge);
465
+ usage_ -= h->total_charge;
466
+ copy = *h;
467
+ }
468
+ }
744
469
 
745
- bool ClockCacheShard::EraseAndConfirm(const Slice& key, uint32_t hash,
746
- CleanupContext* context) {
747
- DMutexLock l(mutex_);
748
- HashTable::accessor accessor;
749
- bool erased = false;
750
- if (table_.find(accessor, ClockCacheKey(key, hash))) {
751
- CacheHandle* handle = accessor->second;
752
- table_.erase(accessor);
753
- erased = UnsetInCache(handle, context);
470
+ // Free the entry here outside of mutex for performance reasons.
471
+ if (last_reference) {
472
+ copy.FreeData();
754
473
  }
755
- return erased;
474
+ return last_reference;
756
475
  }
757
476
 
758
- void ClockCacheShard::EraseUnRefEntries() {
759
- CleanupContext context;
477
+ void ClockCacheShard::Erase(const Slice& key, uint32_t /* hash */) {
478
+ ClockHandle copy;
479
+ bool last_reference = false;
760
480
  {
761
481
  DMutexLock l(mutex_);
762
- table_.clear();
763
- for (auto& handle : list_) {
764
- UnsetInCache(&handle, &context);
482
+ ClockHandle* h = table_.Lookup(key);
483
+ if (h != nullptr) {
484
+ table_.Exclude(h);
485
+ if (!h->HasRefs()) {
486
+ // The entry is in Clock since it's in cache and has no external
487
+ // references.
488
+ ClockRemove(h);
489
+ table_.Remove(h);
490
+ assert(usage_ >= h->total_charge);
491
+ usage_ -= h->total_charge;
492
+ last_reference = true;
493
+ copy = *h;
494
+ }
765
495
  }
766
496
  }
767
- Cleanup(context);
768
- }
769
-
770
- class ClockCache final : public ShardedCache {
771
- public:
772
- ClockCache(size_t capacity, int num_shard_bits, bool strict_capacity_limit,
773
- CacheMetadataChargePolicy metadata_charge_policy)
774
- : ShardedCache(capacity, num_shard_bits, strict_capacity_limit) {
775
- int num_shards = 1 << num_shard_bits;
776
- shards_ = new ClockCacheShard[num_shards];
777
- for (int i = 0; i < num_shards; i++) {
778
- shards_[i].set_metadata_charge_policy(metadata_charge_policy);
779
- }
780
- SetCapacity(capacity);
781
- SetStrictCapacityLimit(strict_capacity_limit);
497
+ // Free the entry here outside of mutex for performance reasons.
498
+ // last_reference will only be true if e != nullptr.
499
+ if (last_reference) {
500
+ copy.FreeData();
782
501
  }
502
+ }
783
503
 
784
- ~ClockCache() override { delete[] shards_; }
504
+ size_t ClockCacheShard::GetUsage() const {
505
+ DMutexLock l(mutex_);
506
+ return usage_;
507
+ }
785
508
 
786
- const char* Name() const override { return "ClockCache"; }
509
+ size_t ClockCacheShard::GetPinnedUsage() const {
510
+ DMutexLock l(mutex_);
511
+ assert(usage_ >= clock_usage_);
512
+ return usage_ - clock_usage_;
513
+ }
787
514
 
788
- CacheShard* GetShard(uint32_t shard) override {
789
- return reinterpret_cast<CacheShard*>(&shards_[shard]);
790
- }
515
+ std::string ClockCacheShard::GetPrintableOptions() const {
516
+ return std::string{};
517
+ }
791
518
 
792
- const CacheShard* GetShard(uint32_t shard) const override {
793
- return reinterpret_cast<CacheShard*>(&shards_[shard]);
519
+ ClockCache::ClockCache(size_t capacity, size_t estimated_value_size,
520
+ int num_shard_bits, bool strict_capacity_limit,
521
+ CacheMetadataChargePolicy metadata_charge_policy)
522
+ : ShardedCache(capacity, num_shard_bits, strict_capacity_limit) {
523
+ assert(estimated_value_size > 0 ||
524
+ metadata_charge_policy != kDontChargeCacheMetadata);
525
+ num_shards_ = 1 << num_shard_bits;
526
+ shards_ = reinterpret_cast<ClockCacheShard*>(
527
+ port::cacheline_aligned_alloc(sizeof(ClockCacheShard) * num_shards_));
528
+ size_t per_shard = (capacity + (num_shards_ - 1)) / num_shards_;
529
+ for (int i = 0; i < num_shards_; i++) {
530
+ new (&shards_[i])
531
+ ClockCacheShard(per_shard, estimated_value_size, strict_capacity_limit,
532
+ metadata_charge_policy);
794
533
  }
534
+ }
795
535
 
796
- void* Value(Handle* handle) override {
797
- return reinterpret_cast<const CacheHandle*>(handle)->value;
536
+ ClockCache::~ClockCache() {
537
+ if (shards_ != nullptr) {
538
+ assert(num_shards_ > 0);
539
+ for (int i = 0; i < num_shards_; i++) {
540
+ shards_[i].~ClockCacheShard();
541
+ }
542
+ port::cacheline_aligned_free(shards_);
798
543
  }
544
+ }
799
545
 
800
- size_t GetCharge(Handle* handle) const override {
801
- return reinterpret_cast<const CacheHandle*>(handle)->charge;
802
- }
546
+ CacheShard* ClockCache::GetShard(uint32_t shard) {
547
+ return reinterpret_cast<CacheShard*>(&shards_[shard]);
548
+ }
803
549
 
804
- uint32_t GetHash(Handle* handle) const override {
805
- return reinterpret_cast<const CacheHandle*>(handle)->hash;
806
- }
550
+ const CacheShard* ClockCache::GetShard(uint32_t shard) const {
551
+ return reinterpret_cast<CacheShard*>(&shards_[shard]);
552
+ }
807
553
 
808
- DeleterFn GetDeleter(Handle* handle) const override {
809
- return reinterpret_cast<const CacheHandle*>(handle)->deleter;
810
- }
554
+ void* ClockCache::Value(Handle* handle) {
555
+ return reinterpret_cast<const ClockHandle*>(handle)->value;
556
+ }
811
557
 
812
- void DisownData() override {
813
- // Leak data only if that won't generate an ASAN/valgrind warning
814
- if (!kMustFreeHeapAllocations) {
815
- shards_ = nullptr;
816
- }
558
+ size_t ClockCache::GetCharge(Handle* handle) const {
559
+ CacheMetadataChargePolicy metadata_charge_policy = kDontChargeCacheMetadata;
560
+ if (num_shards_ > 0) {
561
+ metadata_charge_policy = shards_[0].metadata_charge_policy_;
817
562
  }
563
+ return reinterpret_cast<const ClockHandle*>(handle)->GetCharge(
564
+ metadata_charge_policy);
565
+ }
818
566
 
819
- void WaitAll(std::vector<Handle*>& /*handles*/) override {}
567
+ Cache::DeleterFn ClockCache::GetDeleter(Handle* handle) const {
568
+ auto h = reinterpret_cast<const ClockHandle*>(handle);
569
+ return h->deleter;
570
+ }
820
571
 
821
- private:
822
- ClockCacheShard* shards_;
823
- };
572
+ uint32_t ClockCache::GetHash(Handle* handle) const {
573
+ return reinterpret_cast<const ClockHandle*>(handle)->hash;
574
+ }
824
575
 
825
- } // end anonymous namespace
576
+ void ClockCache::DisownData() {
577
+ // Leak data only if that won't generate an ASAN/valgrind warning.
578
+ if (!kMustFreeHeapAllocations) {
579
+ shards_ = nullptr;
580
+ num_shards_ = 0;
581
+ }
582
+ }
583
+
584
+ } // namespace clock_cache
826
585
 
827
586
  std::shared_ptr<Cache> NewClockCache(
828
587
  size_t capacity, int num_shard_bits, bool strict_capacity_limit,
829
588
  CacheMetadataChargePolicy metadata_charge_policy) {
589
+ return NewLRUCache(capacity, num_shard_bits, strict_capacity_limit, 0.5,
590
+ nullptr, kDefaultToAdaptiveMutex, metadata_charge_policy);
591
+ }
592
+
593
+ std::shared_ptr<Cache> ExperimentalNewClockCache(
594
+ size_t capacity, size_t estimated_value_size, int num_shard_bits,
595
+ bool strict_capacity_limit,
596
+ CacheMetadataChargePolicy metadata_charge_policy) {
597
+ if (num_shard_bits >= 20) {
598
+ return nullptr; // The cache cannot be sharded into too many fine pieces.
599
+ }
830
600
  if (num_shard_bits < 0) {
831
601
  num_shard_bits = GetDefaultCacheShardBits(capacity);
832
602
  }
833
- return std::make_shared<ClockCache>(
834
- capacity, num_shard_bits, strict_capacity_limit, metadata_charge_policy);
603
+ return std::make_shared<clock_cache::ClockCache>(
604
+ capacity, estimated_value_size, num_shard_bits, strict_capacity_limit,
605
+ metadata_charge_policy);
835
606
  }
836
607
 
837
608
  } // namespace ROCKSDB_NAMESPACE
838
-
839
- #endif // SUPPORT_CLOCK_CACHE