MXS-1992 Implement cache size control
The cache now enforces the defined maximum size by evicting some entries in case the insertion of a new entry would cause the max size to be exceeded. Currently the eviction algorithm simply removes a random element.
This commit is contained in:
@ -435,13 +435,25 @@ typedef struct query_classifier
|
|||||||
} QUERY_CLASSIFIER;
|
} QUERY_CLASSIFIER;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* QC_CACHE specifies the limits of the query classification cache.
|
* QC_CACHE_PROPERTIES specifies the limits of the query classification cache.
|
||||||
*/
|
*/
|
||||||
typedef struct QC_CACHE_PROPERTIES
|
typedef struct QC_CACHE_PROPERTIES
|
||||||
{
|
{
|
||||||
int64_t max_size; /** The maximum size of the cache. */
|
int64_t max_size; /** The maximum size of the cache. */
|
||||||
} QC_CACHE_PROPERTIES;
|
} QC_CACHE_PROPERTIES;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* QC_CACHE_STATS provides statistics of the cache.
|
||||||
|
*/
|
||||||
|
typedef struct QC_CACHE_STATS
|
||||||
|
{
|
||||||
|
int64_t size; /** The current size of the cache. */
|
||||||
|
int64_t inserts; /** The number of inserts. */
|
||||||
|
int64_t hits; /** The number of hits. */
|
||||||
|
int64_t misses; /** The number of misses. */
|
||||||
|
int64_t evictions; /** The number of evictions. */
|
||||||
|
} QC_CACHE_STATS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads and sets up the default query classifier.
|
* Loads and sets up the default query classifier.
|
||||||
*
|
*
|
||||||
@ -850,4 +862,13 @@ void qc_set_server_version(uint64_t version);
|
|||||||
*/
|
*/
|
||||||
uint64_t qc_get_server_version();
|
uint64_t qc_get_server_version();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache statistics for the calling thread.
|
||||||
|
*
|
||||||
|
* @param stats[out] Cache statistics.
|
||||||
|
*
|
||||||
|
* @return True, if caching is enabled, false otherwise.
|
||||||
|
*/
|
||||||
|
bool qc_get_cache_stats(QC_CACHE_STATS* stats);
|
||||||
|
|
||||||
MXS_END_DECLS
|
MXS_END_DECLS
|
||||||
|
|||||||
@ -14,6 +14,8 @@
|
|||||||
#include "internal/query_classifier.h"
|
#include "internal/query_classifier.h"
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
|
#include <random>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <maxscale/alloc.h>
|
#include <maxscale/alloc.h>
|
||||||
#include <maxscale/atomic.h>
|
#include <maxscale/atomic.h>
|
||||||
@ -47,20 +49,43 @@ struct type_name_info
|
|||||||
const char DEFAULT_QC_NAME[] = "qc_sqlite";
|
const char DEFAULT_QC_NAME[] = "qc_sqlite";
|
||||||
const char QC_TRX_PARSE_USING[] = "QC_TRX_PARSE_USING";
|
const char QC_TRX_PARSE_USING[] = "QC_TRX_PARSE_USING";
|
||||||
|
|
||||||
static struct this_unit
|
class ThisUnit
|
||||||
{
|
{
|
||||||
|
public:
|
||||||
|
ThisUnit()
|
||||||
|
: classifier(nullptr)
|
||||||
|
, qc_trx_parse_using(QC_TRX_PARSE_USING_PARSER)
|
||||||
|
, qc_sql_mode(QC_SQL_MODE_DEFAULT)
|
||||||
|
, m_cache_max_size(std::numeric_limits<int64_t>::max())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ThisUnit(const ThisUnit&) = delete;
|
||||||
|
ThisUnit& operator=(const ThisUnit&) = delete;
|
||||||
|
|
||||||
QUERY_CLASSIFIER* classifier;
|
QUERY_CLASSIFIER* classifier;
|
||||||
qc_trx_parse_using_t qc_trx_parse_using;
|
qc_trx_parse_using_t qc_trx_parse_using;
|
||||||
qc_sql_mode_t qc_sql_mode;
|
qc_sql_mode_t qc_sql_mode;
|
||||||
int64_t cache_max_size;
|
|
||||||
} this_unit =
|
int64_t cache_max_size() const
|
||||||
{
|
{
|
||||||
nullptr, // classifier
|
// In principle, std::memory_order_acquire should be used here, but that causes
|
||||||
QC_TRX_PARSE_USING_PARSER, // qc_trx_parse_using
|
// a performance penalty of ~5% when running a sysbench test.
|
||||||
QC_SQL_MODE_DEFAULT, // qc_sql_mode
|
return m_cache_max_size.load(std::memory_order_relaxed);
|
||||||
INT64_MAX // cache_max_size; TODO: Make this configurable
|
}
|
||||||
|
|
||||||
|
void set_cache_max_size(int64_t cache_max_size)
|
||||||
|
{
|
||||||
|
// In principle, std::memory_order_release should be used here.
|
||||||
|
m_cache_max_size.store(cache_max_size, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::atomic<int64_t> m_cache_max_size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static ThisUnit this_unit;
|
||||||
|
|
||||||
class QCInfoCache;
|
class QCInfoCache;
|
||||||
|
|
||||||
static thread_local struct
|
static thread_local struct
|
||||||
@ -85,7 +110,9 @@ public:
|
|||||||
QCInfoCache& operator=(const QCInfoCache&) = delete;
|
QCInfoCache& operator=(const QCInfoCache&) = delete;
|
||||||
|
|
||||||
QCInfoCache()
|
QCInfoCache()
|
||||||
|
: m_reng(m_rdev())
|
||||||
{
|
{
|
||||||
|
memset(&m_stats, 0, sizeof(m_stats));
|
||||||
}
|
}
|
||||||
|
|
||||||
~QCInfoCache()
|
~QCInfoCache()
|
||||||
@ -120,15 +147,21 @@ public:
|
|||||||
ss_dassert(this_unit.classifier);
|
ss_dassert(this_unit.classifier);
|
||||||
this_unit.classifier->qc_info_dup(entry.pInfo);
|
this_unit.classifier->qc_info_dup(entry.pInfo);
|
||||||
pInfo = entry.pInfo;
|
pInfo = entry.pInfo;
|
||||||
|
|
||||||
|
++m_stats.hits;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// If the sql_mode has changed, we discard the existing result.
|
// If the sql_mode has changed, we discard the existing result.
|
||||||
ss_dassert(this_unit.classifier);
|
erase(i);
|
||||||
this_unit.classifier->qc_info_close(entry.pInfo);
|
|
||||||
m_infos.erase(i);
|
++m_stats.misses;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++m_stats.misses;
|
||||||
|
}
|
||||||
|
|
||||||
return pInfo;
|
return pInfo;
|
||||||
}
|
}
|
||||||
@ -138,25 +171,32 @@ public:
|
|||||||
ss_dassert(peek(canonical_stmt) == nullptr);
|
ss_dassert(peek(canonical_stmt) == nullptr);
|
||||||
ss_dassert(this_unit.classifier);
|
ss_dassert(this_unit.classifier);
|
||||||
|
|
||||||
|
int64_t cache_max_size = this_unit.cache_max_size();
|
||||||
|
int64_t size = canonical_stmt.size();
|
||||||
|
|
||||||
|
if (size <= cache_max_size)
|
||||||
|
{
|
||||||
|
int64_t required_space = (m_stats.size + size) - cache_max_size;
|
||||||
|
|
||||||
|
if (required_space > 0)
|
||||||
|
{
|
||||||
|
make_space(required_space);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_stats.size + size <= cache_max_size)
|
||||||
|
{
|
||||||
this_unit.classifier->qc_info_dup(pInfo);
|
this_unit.classifier->qc_info_dup(pInfo);
|
||||||
|
|
||||||
m_infos.emplace(canonical_stmt, Entry(pInfo, this_unit.qc_sql_mode));
|
m_infos.emplace(canonical_stmt, Entry(pInfo, this_unit.qc_sql_mode));
|
||||||
|
|
||||||
|
++m_stats.inserts;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void erase(const std::string& canonical_stmt)
|
void get_stats(QC_CACHE_STATS* pStats)
|
||||||
{
|
{
|
||||||
auto i = m_infos.find(canonical_stmt);
|
*pStats = m_stats;
|
||||||
ss_dassert(i != m_infos.end());
|
|
||||||
|
|
||||||
if (i != m_infos.end())
|
|
||||||
{
|
|
||||||
QC_STMT_INFO* pInfo = i->second.pInfo;
|
|
||||||
|
|
||||||
ss_dassert(this_unit.classifier);
|
|
||||||
this_unit.classifier->qc_info_close(pInfo);
|
|
||||||
|
|
||||||
m_infos.erase(i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -174,12 +214,79 @@ private:
|
|||||||
|
|
||||||
typedef std::unordered_map<std::string, Entry> InfosByStmt;
|
typedef std::unordered_map<std::string, Entry> InfosByStmt;
|
||||||
|
|
||||||
|
void erase(InfosByStmt::iterator& i)
|
||||||
|
{
|
||||||
|
ss_dassert(i != m_infos.end());
|
||||||
|
|
||||||
|
m_stats.size -= i->first.size();
|
||||||
|
|
||||||
|
ss_dassert(this_unit.classifier);
|
||||||
|
this_unit.classifier->qc_info_close(i->second.pInfo);
|
||||||
|
|
||||||
|
m_infos.erase(i);
|
||||||
|
|
||||||
|
++m_stats.evictions;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool erase(const std::string& canonical_stmt)
|
||||||
|
{
|
||||||
|
bool erased = false;
|
||||||
|
|
||||||
|
auto i = m_infos.find(canonical_stmt);
|
||||||
|
ss_dassert(i != m_infos.end());
|
||||||
|
|
||||||
|
if (i != m_infos.end())
|
||||||
|
{
|
||||||
|
erase(i);
|
||||||
|
erased = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return erased;
|
||||||
|
}
|
||||||
|
|
||||||
|
void make_space(int64_t required_space)
|
||||||
|
{
|
||||||
|
int64_t freed_space = 0;
|
||||||
|
|
||||||
|
std::uniform_int_distribution<> dis(0, m_infos.bucket_count() - 1);
|
||||||
|
|
||||||
|
while ((freed_space < required_space) && !m_infos.empty())
|
||||||
|
{
|
||||||
|
freed_space += evict(dis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t evict(std::uniform_int_distribution<>& dis)
|
||||||
|
{
|
||||||
|
int64_t freed_space = 0;
|
||||||
|
|
||||||
|
int bucket = dis(m_reng);
|
||||||
|
ss_dassert((bucket >= 0) && (bucket < static_cast<int>(m_infos.bucket_count())));
|
||||||
|
|
||||||
|
auto i = m_infos.begin(bucket);
|
||||||
|
|
||||||
|
// We just remove the first entry in the bucket. In the general case
|
||||||
|
// there will be just one.
|
||||||
|
if (i != m_infos.end(bucket))
|
||||||
|
{
|
||||||
|
freed_space += i->first.size();
|
||||||
|
|
||||||
|
ss_debug(bool erased =) erase(i->first);
|
||||||
|
ss_dassert(erased);
|
||||||
|
}
|
||||||
|
|
||||||
|
return freed_space;
|
||||||
|
}
|
||||||
|
|
||||||
InfosByStmt m_infos;
|
InfosByStmt m_infos;
|
||||||
|
QC_CACHE_STATS m_stats;
|
||||||
|
std::random_device m_rdev;
|
||||||
|
std::mt19937 m_reng;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool use_cached_result()
|
bool use_cached_result()
|
||||||
{
|
{
|
||||||
return atomic_load_int64(&this_unit.cache_max_size) != 0;
|
return this_unit.cache_max_size() != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool has_not_been_parsed(GWBUF* pStmt)
|
bool has_not_been_parsed(GWBUF* pStmt)
|
||||||
@ -293,7 +400,7 @@ bool qc_setup(const QC_CACHE_PROPERTIES* cache_properties,
|
|||||||
MXS_NOTICE("Query classification results are not cached.");
|
MXS_NOTICE("Query classification results are not cached.");
|
||||||
}
|
}
|
||||||
|
|
||||||
this_unit.cache_max_size = cache_max_size;
|
this_unit.set_cache_max_size(cache_max_size);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -1089,3 +1196,20 @@ void qc_set_sql_mode(qc_sql_mode_t sql_mode)
|
|||||||
this_unit.qc_sql_mode = sql_mode;
|
this_unit.qc_sql_mode = sql_mode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool qc_get_cache_stats(QC_CACHE_STATS* pStats)
|
||||||
|
{
|
||||||
|
QC_TRACE();
|
||||||
|
|
||||||
|
bool rv = false;
|
||||||
|
|
||||||
|
QCInfoCache* pInfo_cache = this_thread.pInfo_cache;
|
||||||
|
|
||||||
|
if (pInfo_cache && use_cached_result())
|
||||||
|
{
|
||||||
|
pInfo_cache->get_stats(pStats);
|
||||||
|
rv = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user