summaryrefslogtreecommitdiffstats
path: root/src/rocksdb/cache/cache_reservation_manager.h
blob: 147aaa915d7986bd05aaec817113f404acc353ec (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
//  Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
//  This source code is licensed under both the GPLv2 (found in the
//  COPYING file in the root directory) and Apache 2.0 License
//  (found in the LICENSE.Apache file in the root directory).
//
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. See the AUTHORS file for names of contributors.

#pragma once

#include <atomic>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <mutex>
#include <vector>

#include "cache/cache_entry_roles.h"
#include "cache/cache_key.h"
#include "rocksdb/cache.h"
#include "rocksdb/slice.h"
#include "rocksdb/status.h"
#include "util/coding.h"

namespace ROCKSDB_NAMESPACE {
// CacheReservationManager is an interface for reserving cache space for the
// memory used
class CacheReservationManager {
 public:
  // CacheReservationHandle is for managing the lifetime of a cache reservation
  // for an incremental amount of memory used (i.e, incremental_memory_used)
  class CacheReservationHandle {
   public:
    virtual ~CacheReservationHandle() {}
  };
  virtual ~CacheReservationManager() {}
  virtual Status UpdateCacheReservation(std::size_t new_memory_used) = 0;
  // TODO(hx235): replace the usage of
  // `UpdateCacheReservation(memory_used_delta, increase)` with
  // `UpdateCacheReservation(new_memory_used)` so that we only have one
  // `UpdateCacheReservation` function
  virtual Status UpdateCacheReservation(std::size_t memory_used_delta,
                                        bool increase) = 0;
  virtual Status MakeCacheReservation(
      std::size_t incremental_memory_used,
      std::unique_ptr<CacheReservationManager::CacheReservationHandle>
          *handle) = 0;
  virtual std::size_t GetTotalReservedCacheSize() = 0;
  virtual std::size_t GetTotalMemoryUsed() = 0;
};

// CacheReservationManagerImpl implements interface CacheReservationManager
// for reserving cache space for the memory used by inserting/releasing dummy
// entries in the cache.
//
// This class is NOT thread-safe, except that GetTotalReservedCacheSize()
// can be called without external synchronization.
template <CacheEntryRole R>
class CacheReservationManagerImpl
    : public CacheReservationManager,
      public std::enable_shared_from_this<CacheReservationManagerImpl<R>> {
 public:
  class CacheReservationHandle
      : public CacheReservationManager::CacheReservationHandle {
   public:
    CacheReservationHandle(
        std::size_t incremental_memory_used,
        std::shared_ptr<CacheReservationManagerImpl> cache_res_mgr);
    ~CacheReservationHandle() override;

   private:
    std::size_t incremental_memory_used_;
    std::shared_ptr<CacheReservationManagerImpl> cache_res_mgr_;
  };

  // Construct a CacheReservationManagerImpl
  // @param cache The cache where dummy entries are inserted and released for
  // reserving cache space
  // @param delayed_decrease If set true, then dummy entries won't be released
  //                         immediately when memory usage decreases.
  //                         Instead, it will be released when the memory usage
  //                         decreases to 3/4 of what we have reserved so far.
  //                         This is for saving some future dummy entry
  //                         insertion when memory usage increases are likely to
  //                         happen in the near future.
  //
  // REQUIRED: cache is not nullptr
  explicit CacheReservationManagerImpl(std::shared_ptr<Cache> cache,
                                       bool delayed_decrease = false);

  // no copy constructor, copy assignment, move constructor, move assignment
  CacheReservationManagerImpl(const CacheReservationManagerImpl &) = delete;
  CacheReservationManagerImpl &operator=(const CacheReservationManagerImpl &) =
      delete;
  CacheReservationManagerImpl(CacheReservationManagerImpl &&) = delete;
  CacheReservationManagerImpl &operator=(CacheReservationManagerImpl &&) =
      delete;

  ~CacheReservationManagerImpl() override;

  // One of the two ways of reserving/releasing cache space,
  // see MakeCacheReservation() for the other.
  //
  // Use ONLY one of these two ways to prevent unexpected behavior.
  //
  // Insert and release dummy entries in the cache to
  // match the size of total dummy entries with the least multiple of
  // kSizeDummyEntry greater than or equal to new_mem_used
  //
  // Insert dummy entries if new_memory_used > cache_allocated_size_;
  //
  // Release dummy entries if new_memory_used < cache_allocated_size_
  // (and new_memory_used < cache_allocated_size_ * 3/4
  // when delayed_decrease is set true);
  //
  // Keey dummy entries the same if (1) new_memory_used == cache_allocated_size_
  // or (2) new_memory_used is in the interval of
  // [cache_allocated_size_ * 3/4, cache_allocated_size) when delayed_decrease
  // is set true.
  //
  // @param new_memory_used The number of bytes used by new memory
  //        The most recent new_memoy_used passed in will be returned
  //        in GetTotalMemoryUsed() even when the call return non-ok status.
  //
  //        Since the class is NOT thread-safe, external synchronization on the
  //        order of calling UpdateCacheReservation() is needed if you want
  //        GetTotalMemoryUsed() indeed returns the latest memory used.
  //
  // @return On inserting dummy entries, it returns Status::OK() if all dummy
  //         entry insertions succeed.
  //         Otherwise, it returns the first non-ok status;
  //         On releasing dummy entries, it always returns Status::OK().
  //         On keeping dummy entries the same, it always returns Status::OK().
  Status UpdateCacheReservation(std::size_t new_memory_used) override;

  Status UpdateCacheReservation(std::size_t /* memory_used_delta */,
                                bool /* increase */) override {
    return Status::NotSupported();
  }

  // One of the two ways of reserving cache space and releasing is done through
  // destruction of CacheReservationHandle.
  // See UpdateCacheReservation() for the other way.
  //
  // Use ONLY one of these two ways to prevent unexpected behavior.
  //
  // Insert dummy entries in the cache for the incremental memory usage
  // to match the size of total dummy entries with the least multiple of
  // kSizeDummyEntry greater than or equal to the total memory used.
  //
  // A CacheReservationHandle is returned as an output parameter.
  // The reserved dummy entries are automatically released on the destruction of
  // this handle, which achieves better RAII per cache reservation.
  //
  // WARNING: Deallocate all the handles of the CacheReservationManager object
  //          before deallocating the object to prevent unexpected behavior.
  //
  // @param incremental_memory_used The number of bytes increased in memory
  //        usage.
  //
  //        Calling GetTotalMemoryUsed() afterward will return the total memory
  //        increased by this number, even when calling MakeCacheReservation()
  //        returns non-ok status.
  //
  //        Since the class is NOT thread-safe, external synchronization in
  //        calling MakeCacheReservation() is needed if you want
  //        GetTotalMemoryUsed() indeed returns the latest memory used.
  //
  // @param handle An pointer to std::unique_ptr<CacheReservationHandle> that
  //        manages the lifetime of the cache reservation represented by the
  //        handle.
  //
  // @return It returns Status::OK() if all dummy
  //         entry insertions succeed.
  //         Otherwise, it returns the first non-ok status;
  //
  // REQUIRES: handle != nullptr
  Status MakeCacheReservation(
      std::size_t incremental_memory_used,
      std::unique_ptr<CacheReservationManager::CacheReservationHandle> *handle)
      override;

  // Return the size of the cache (which is a multiple of kSizeDummyEntry)
  // successfully reserved by calling UpdateCacheReservation().
  //
  // When UpdateCacheReservation() returns non-ok status,
  // calling GetTotalReservedCacheSize() after that might return a slightly
  // smaller number than the actual reserved cache size due to
  // the returned number will always be a multiple of kSizeDummyEntry
  // and cache full might happen in the middle of inserting a dummy entry.
  std::size_t GetTotalReservedCacheSize() override;

  // Return the latest total memory used indicated by the most recent call of
  // UpdateCacheReservation(std::size_t new_memory_used);
  std::size_t GetTotalMemoryUsed() override;

  static constexpr std::size_t GetDummyEntrySize() { return kSizeDummyEntry; }

  // For testing only - it is to help ensure the NoopDeleterForRole<R>
  // accessed from CacheReservationManagerImpl and the one accessed from the
  // test are from the same translation units
  static Cache::DeleterFn TEST_GetNoopDeleterForRole();

 private:
  static constexpr std::size_t kSizeDummyEntry = 256 * 1024;

  Slice GetNextCacheKey();

  Status ReleaseCacheReservation(std::size_t incremental_memory_used);
  Status IncreaseCacheReservation(std::size_t new_mem_used);
  Status DecreaseCacheReservation(std::size_t new_mem_used);

  std::shared_ptr<Cache> cache_;
  bool delayed_decrease_;
  std::atomic<std::size_t> cache_allocated_size_;
  std::size_t memory_used_;
  std::vector<Cache::Handle *> dummy_handles_;
  CacheKey cache_key_;
};

class ConcurrentCacheReservationManager
    : public CacheReservationManager,
      public std::enable_shared_from_this<ConcurrentCacheReservationManager> {
 public:
  class CacheReservationHandle
      : public CacheReservationManager::CacheReservationHandle {
   public:
    CacheReservationHandle(
        std::shared_ptr<ConcurrentCacheReservationManager> cache_res_mgr,
        std::unique_ptr<CacheReservationManager::CacheReservationHandle>
            cache_res_handle) {
      assert(cache_res_mgr && cache_res_handle);
      cache_res_mgr_ = cache_res_mgr;
      cache_res_handle_ = std::move(cache_res_handle);
    }

    ~CacheReservationHandle() override {
      std::lock_guard<std::mutex> lock(cache_res_mgr_->cache_res_mgr_mu_);
      cache_res_handle_.reset();
    }

   private:
    std::shared_ptr<ConcurrentCacheReservationManager> cache_res_mgr_;
    std::unique_ptr<CacheReservationManager::CacheReservationHandle>
        cache_res_handle_;
  };

  explicit ConcurrentCacheReservationManager(
      std::shared_ptr<CacheReservationManager> cache_res_mgr) {
    cache_res_mgr_ = std::move(cache_res_mgr);
  }
  ConcurrentCacheReservationManager(const ConcurrentCacheReservationManager &) =
      delete;
  ConcurrentCacheReservationManager &operator=(
      const ConcurrentCacheReservationManager &) = delete;
  ConcurrentCacheReservationManager(ConcurrentCacheReservationManager &&) =
      delete;
  ConcurrentCacheReservationManager &operator=(
      ConcurrentCacheReservationManager &&) = delete;

  ~ConcurrentCacheReservationManager() override {}

  inline Status UpdateCacheReservation(std::size_t new_memory_used) override {
    std::lock_guard<std::mutex> lock(cache_res_mgr_mu_);
    return cache_res_mgr_->UpdateCacheReservation(new_memory_used);
  }

  inline Status UpdateCacheReservation(std::size_t memory_used_delta,
                                       bool increase) override {
    std::lock_guard<std::mutex> lock(cache_res_mgr_mu_);
    std::size_t total_mem_used = cache_res_mgr_->GetTotalMemoryUsed();
    Status s;
    if (!increase) {
      assert(total_mem_used >= memory_used_delta);
      s = cache_res_mgr_->UpdateCacheReservation(total_mem_used -
                                                 memory_used_delta);
    } else {
      s = cache_res_mgr_->UpdateCacheReservation(total_mem_used +
                                                 memory_used_delta);
    }
    return s;
  }

  inline Status MakeCacheReservation(
      std::size_t incremental_memory_used,
      std::unique_ptr<CacheReservationManager::CacheReservationHandle> *handle)
      override {
    std::unique_ptr<CacheReservationManager::CacheReservationHandle>
        wrapped_handle;
    Status s;
    {
      std::lock_guard<std::mutex> lock(cache_res_mgr_mu_);
      s = cache_res_mgr_->MakeCacheReservation(incremental_memory_used,
                                               &wrapped_handle);
    }
    (*handle).reset(
        new ConcurrentCacheReservationManager::CacheReservationHandle(
            std::enable_shared_from_this<
                ConcurrentCacheReservationManager>::shared_from_this(),
            std::move(wrapped_handle)));
    return s;
  }
  inline std::size_t GetTotalReservedCacheSize() override {
    return cache_res_mgr_->GetTotalReservedCacheSize();
  }
  inline std::size_t GetTotalMemoryUsed() override {
    std::lock_guard<std::mutex> lock(cache_res_mgr_mu_);
    return cache_res_mgr_->GetTotalMemoryUsed();
  }

 private:
  std::mutex cache_res_mgr_mu_;
  std::shared_ptr<CacheReservationManager> cache_res_mgr_;
};
}  // namespace ROCKSDB_NAMESPACE