1
0
Fork 0
arangodb/arangod/Cache/Manager.h

294 lines
11 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2014-2017 ArangoDB GmbH, Cologne, Germany
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Daniel H. Larkin
////////////////////////////////////////////////////////////////////////////////
#ifndef ARANGODB_CACHE_MANAGER_H
#define ARANGODB_CACHE_MANAGER_H
#include "Basics/Common.h"
#include "Basics/asio-helper.h"
#include "Cache/CachedValue.h"
#include "Cache/Common.h"
#include "Cache/FrequencyBuffer.h"
#include "Cache/Metadata.h"
#include "Cache/State.h"
#include "Cache/Table.h"
#include "Cache/Transaction.h"
#include "Cache/TransactionManager.h"
#include <stdint.h>
#include <atomic>
#include <chrono>
#include <memory>
#include <set>
#include <stack>
#include <utility>
namespace arangodb {
namespace cache {
class Cache; // forward declaration
class FreeMemoryTask; // forward declaration
class MigrateTask; // forward declaration
class Rebalancer; // forward declaration
////////////////////////////////////////////////////////////////////////////////
/// @brief Coordinates a system of caches all sharing a single memory pool.
///
/// Allows clients to create and destroy both transactional and
/// non-transactional caches with individual usage limits, but all subject to a
/// combined global limit. Re-uses memory from old, destroyed caches if possible
/// when allocating new ones to allow fast creation and destruction of
/// short-lived caches.
///
/// The global limit may be adjusted, and compliance may be achieved through
/// asynchronous background tasks. The manager periodically rebalances the
/// allocations across the pool of caches to allow more frequently used ones to
/// have more space.
///
/// There should be a single Manager instance exposed via
/// CacheManagerFeature::MANAGER --- use this unless you are very certain you
/// need a different instance.
////////////////////////////////////////////////////////////////////////////////
class Manager {
protected:
struct cmp_weak_ptr {
bool operator()(std::weak_ptr<Cache> const& left,
std::weak_ptr<Cache> const& right) const;
};
struct hash_weak_ptr {
size_t operator()(const std::weak_ptr<Cache>& wp) const;
};
typedef std::function<bool(std::function<void()>)> PostFn;
public:
static const uint64_t minSize;
typedef FrequencyBuffer<std::weak_ptr<Cache>, cmp_weak_ptr, hash_weak_ptr>
AccessStatBuffer;
typedef FrequencyBuffer<uint8_t> FindStatBuffer;
typedef std::vector<std::pair<std::shared_ptr<Cache>, double>> PriorityList;
typedef std::chrono::time_point<std::chrono::steady_clock> time_point;
public:
//////////////////////////////////////////////////////////////////////////////
/// @brief Initialize the manager with a scheduler post method and global
/// usage limit.
//////////////////////////////////////////////////////////////////////////////
Manager(PostFn schedulerPost, uint64_t globalLimit,
bool enableWindowedStats = true);
~Manager();
//////////////////////////////////////////////////////////////////////////////
/// @brief Creates an individual cache.
///
/// The type must be specified. It is possible that the cache cannot be
/// created (e.g. in situations of extreme memory pressure), in which case the
/// returned pointer will be nullptr. If the second parameter is true, then
/// windowed stats will be collected. This incurs some memory and overhead and
/// but only a slight performance hit. The windowed stats refer to only a
/// recent window in time, rather than over the full lifetime of the cache.
/// The third parameter controls the maximum size of the cache over its
/// lifetime. It should likely only be set to a non-default value for
/// infrequently accessed or short-lived caches.
//////////////////////////////////////////////////////////////////////////////
std::shared_ptr<Cache> createCache(CacheType type,
bool enableWindowedStats = false,
uint64_t maxSize = UINT64_MAX);
//////////////////////////////////////////////////////////////////////////////
/// @brief Destroy the given cache.
//////////////////////////////////////////////////////////////////////////////
void destroyCache(std::shared_ptr<Cache> cache);
//////////////////////////////////////////////////////////////////////////////
/// @brief Prepare for shutdown.
//////////////////////////////////////////////////////////////////////////////
void beginShutdown();
//////////////////////////////////////////////////////////////////////////////
/// @brief Actually shutdown the manager and all caches.
//////////////////////////////////////////////////////////////////////////////
void shutdown();
//////////////////////////////////////////////////////////////////////////////
/// @brief Change the global usage limit.
//////////////////////////////////////////////////////////////////////////////
bool resize(uint64_t newGlobalLimit);
//////////////////////////////////////////////////////////////////////////////
/// @brief Report the current global usage limit.
//////////////////////////////////////////////////////////////////////////////
uint64_t globalLimit();
//////////////////////////////////////////////////////////////////////////////
/// @brief Report the current amoutn of memory allocated to all caches.
///
/// This serves as an upper bound on the current memory usage of all caches.
/// The actual global usage is not recorded, as this would require significant
/// additional synchronization between the caches and slow things down
/// considerably.
//////////////////////////////////////////////////////////////////////////////
uint64_t globalAllocation();
std::pair<double, double> globalHitRates();
//////////////////////////////////////////////////////////////////////////////
/// @brief Open a new transaction.
///
/// The transaction is considered read-only if it is guaranteed not to write
/// to the backing store. A read-only transaction may, however, write to the
/// cache.
//////////////////////////////////////////////////////////////////////////////
Transaction* beginTransaction(bool readOnly);
//////////////////////////////////////////////////////////////////////////////
/// @brief Signal the end of a transaction. Deletes the passed Transaction.
//////////////////////////////////////////////////////////////////////////////
void endTransaction(Transaction* tx);
//////////////////////////////////////////////////////////////////////////////
/// @brief Post a function to the scheduler
//////////////////////////////////////////////////////////////////////////////
bool post(std::function<void()> fn);
private:
// use sizeof(std::shared_ptr<Cache>) + 32 for sizeof
// std::set<std::shared_ptr<Cache>> node -- should be valid for most libraries
static constexpr uint64_t cacheRecordOverhead =
sizeof(std::shared_ptr<Cache>) + 32;
// assume at most 16 slots in each stack -- TODO: check validity
static constexpr uint64_t tableListsOverhead =
32 * 16 * sizeof(std::shared_ptr<Cache>);
static constexpr int64_t triesFast = 100;
static constexpr int64_t triesSlow = 1000;
// simple state variable for locking and other purposes
State _state;
// structure to handle access frequency monitoring
Manager::AccessStatBuffer _accessStats;
std::atomic<uint64_t> _accessCounter;
// structures to handle hit rate monitoring
bool _enableWindowedStats;
std::unique_ptr<Manager::FindStatBuffer> _findStats;
std::atomic<uint64_t> _findHits;
std::atomic<uint64_t> _findMisses;
// set of pointers to keep track of registered caches
std::set<std::shared_ptr<Cache>> _caches;
// actual tables to lease out
std::stack<std::shared_ptr<Table>> _tables[32];
// global statistics
uint64_t _globalSoftLimit;
uint64_t _globalHardLimit;
uint64_t _globalHighwaterMark;
uint64_t _fixedAllocation;
uint64_t _spareTableAllocation;
uint64_t _globalAllocation;
// transaction management
TransactionManager _transactions;
// task management
enum TaskEnvironment { none, rebalancing, resizing };
PostFn _schedulerPost;
uint64_t _resizeAttempt;
std::atomic<uint64_t> _outstandingTasks;
std::atomic<uint64_t> _rebalancingTasks;
std::atomic<uint64_t> _resizingTasks;
Manager::time_point _rebalanceCompleted;
// friend class tasks and caches to allow access
friend class Cache;
friend class FreeMemoryTask;
friend struct Metadata;
friend class MigrateTask;
friend class PlainCache;
friend class Rebalancer;
friend class TransactionalCache;
private: // used by caches
// register and unregister individual caches
std::tuple<bool, Metadata, std::shared_ptr<Table>> registerCache(
uint64_t fixedSize, uint64_t maxSize);
void unregisterCache(std::shared_ptr<Cache> cache);
// allow individual caches to request changes to their allocations
std::pair<bool, Manager::time_point> requestGrow(
std::shared_ptr<Cache> cache);
std::pair<bool, Manager::time_point> requestMigrate(
std::shared_ptr<Cache> cache, uint32_t requestedLogSize);
// stat reporting
void reportAccess(std::shared_ptr<Cache> cache);
void reportHitStat(Stat stat);
private: // used internally and by tasks
static constexpr double highwaterMultiplier = 0.8;
static const uint64_t minCacheAllocation;
static const std::chrono::milliseconds rebalancingGracePeriod;
// check if shutdown or shutting down
bool isOperational() const;
// check if there is already a global process running
bool globalProcessRunning() const;
// coordinate state with task lifecycles
void prepareTask(TaskEnvironment environment);
void unprepareTask(TaskEnvironment environment);
// periodically run to rebalance allocations globally
bool rebalance(bool onlyCalculate = false);
// helpers for global resizing
void shrinkOvergrownCaches(TaskEnvironment environment);
void freeUnusedTables();
bool adjustGlobalLimitsIfAllowed(uint64_t newGlobalLimit);
// methods to adjust individual caches
void resizeCache(TaskEnvironment environment, std::shared_ptr<Cache> cache,
uint64_t newLimit);
void migrateCache(TaskEnvironment environment, std::shared_ptr<Cache> cache,
std::shared_ptr<Table> table);
std::shared_ptr<Table> leaseTable(uint32_t logSize);
void reclaimTable(std::shared_ptr<Table> table, bool internal = false);
// helpers for individual allocations
bool increaseAllowed(uint64_t increase, bool privileged = false) const;
// helper for lr-accessed heuristics
std::shared_ptr<PriorityList> priorityList();
// helper for wait times
Manager::time_point futureTime(uint64_t millisecondsFromNow);
bool pastRebalancingGracePeriod() const;
};
}; // end namespace cache
}; // end namespace arangodb
#endif