From 1be9528227fbb4497c280c0c08a52495d9fe98d2 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Thu, 5 Jul 2018 12:19:15 +0300 Subject: [PATCH] MXS-1624 Provide initial implemenation of QC caching The mapping from a canonical statement to the query classification result is maintained by the class QCInfoCache of which there exist an instance per thread. That way no locking is needed but the information will be cached multiple times (but that is a smaller price to pay). Currently the information is stored in a regular std::unordered_map, which means that the consumed amount of memory will just keep on growing unless the number of canonical statements used by clients happens to have an upper bound. The LRU cache (that provides means for putting a bound on the amount of memory used and number of items) used in the cache filter will be generalized and be taken into use here as well. The key is now the canonical statement itself, which means that a fair amount of memory will be used. To preserve memory it might make sense to use a hashed value instead, although that at least in principle opens up the possibility for unintended collisions. This feature will also be made configurable. --- include/maxscale/query_classifier.h | 2 +- server/core/query_classifier.cc | 211 +++++++++++++++++++++++++++- 2 files changed, 205 insertions(+), 8 deletions(-) diff --git a/include/maxscale/query_classifier.h b/include/maxscale/query_classifier.h index ce07d6b76..86843a18e 100644 --- a/include/maxscale/query_classifier.h +++ b/include/maxscale/query_classifier.h @@ -426,7 +426,7 @@ typedef struct query_classifier QC_STMT_INFO* (*qc_info_dup)(QC_STMT_INFO* info); /** - * Closes a dupped info object.After the info objec has been closed, it must + * Closes a dupped info object. After the info object has been closed, it must * not be accessed. * * @param info The info to be closed. diff --git a/server/core/query_classifier.cc b/server/core/query_classifier.cc index 2b15aa3b4..4523b9373 100644 --- a/server/core/query_classifier.cc +++ b/server/core/query_classifier.cc @@ -12,11 +12,14 @@ */ #include "internal/query_classifier.h" +#include +#include +#include +#include #include #include -#include -#include #include +#include #include #include "internal/modules.h" @@ -43,14 +46,181 @@ struct type_name_info const char DEFAULT_QC_NAME[] = "qc_sqlite"; const char QC_TRX_PARSE_USING[] = "QC_TRX_PARSE_USING"; -struct this_unit +static struct this_unit { QUERY_CLASSIFIER* classifier; qc_trx_parse_using_t qc_trx_parse_using; + int32_t use_cached_result; } this_unit = { nullptr, - QC_TRX_PARSE_USING_PARSER + QC_TRX_PARSE_USING_PARSER, + 1 // TODO: Make this configurable +}; + +class QCInfoCache; + +static thread_local struct +{ + QCInfoCache* pInfo_cache; +} this_thread = +{ + nullptr +}; + + +/** + * @class QCInfoCache + * + * An instance of this class maintains a mapping from a canonical statement to + * the QC_STMT_INFO object created by the actual query classifier. + */ +class QCInfoCache +{ +public: + QCInfoCache(const QCInfoCache&) = delete; + QCInfoCache& operator=(const QCInfoCache&) = delete; + + QCInfoCache() + { + } + + ~QCInfoCache() + { + ss_dassert(this_unit.classifier); + + for (auto a : m_infos) + { + this_unit.classifier->qc_info_close(a.second); + } + } + + QC_STMT_INFO* peek(const std::string& canonical_stmt) const + { + auto i = m_infos.find(canonical_stmt); + + return i != m_infos.end() ? i->second : nullptr; + } + + QC_STMT_INFO* get(const std::string& canonical_stmt) const + { + QC_STMT_INFO* pInfo = peek(canonical_stmt); + + if (pInfo) + { + ss_dassert(this_unit.classifier); + this_unit.classifier->qc_info_dup(pInfo); + } + + return pInfo; + } + + void insert(const std::string& canonical_stmt, QC_STMT_INFO* pInfo) + { + ss_dassert(peek(canonical_stmt) == nullptr); + ss_dassert(this_unit.classifier); + + this_unit.classifier->qc_info_dup(pInfo); + + m_infos.emplace(canonical_stmt, pInfo); + } + + void erase(const std::string& canonical_stmt) + { + auto i = m_infos.find(canonical_stmt); + ss_dassert(i != m_infos.end()); + + if (i != m_infos.end()) + { + QC_STMT_INFO* pInfo = i->second; + + ss_dassert(this_unit.classifier); + this_unit.classifier->qc_info_close(pInfo); + + m_infos.erase(i); + } + } + +private: + typedef std::unordered_map InfosByStmt; + + InfosByStmt m_infos; +}; + +bool use_cached_result() +{ + return atomic_load_int32(&this_unit.use_cached_result) != 0; +} + +bool has_not_been_parsed(GWBUF* pStmt) +{ + // A GWBUF has not been parsed, if it does not have a parsing info object attached. + return gwbuf_get_buffer_object_data(pStmt, GWBUF_PARSING_INFO) == nullptr; +} + +void info_object_close(void* pData) +{ + ss_dassert(this_unit.classifier); + this_unit.classifier->qc_info_close(static_cast(pData)); +} + + +/** + * @class QCInfoCacheScope + * + * QCInfoCacheScope is somewhat like a guard or RAII class that + * in the constructor + * - figures out whether the query classification cache should be used, + * - checks whether the classification result already exists, and + * - if it does attaches it to the GWBUF + * and in the destructor + * - if the query classification result was not already present, + * stores the result it in the cache. + */ +class QCInfoCacheScope +{ +public: + QCInfoCacheScope(const QCInfoCacheScope&) = delete; + QCInfoCacheScope& operator=(const QCInfoCacheScope&) = delete; + + QCInfoCacheScope(GWBUF* pStmt) + : m_pStmt(pStmt) + { + if (use_cached_result() && has_not_been_parsed(m_pStmt)) + { + char* zCanonical = qc_get_canonical(m_pStmt); + + if (zCanonical) + { + m_canonical = zCanonical; + MXS_FREE(zCanonical); + + QC_STMT_INFO* pInfo = this_thread.pInfo_cache->get(m_canonical); + + if (pInfo) + { + gwbuf_add_buffer_object(m_pStmt, GWBUF_PARSING_INFO, pInfo, info_object_close); + m_canonical.clear(); + } + } + } + } + + ~QCInfoCacheScope() + { + if (!m_canonical.empty()) + { + void* pData = gwbuf_get_buffer_object_data(m_pStmt, GWBUF_PARSING_INFO); + ss_dassert(pData); + QC_STMT_INFO* pInfo = static_cast(pData); + + this_thread.pInfo_cache->insert(m_canonical, pInfo); + } + } + +private: + GWBUF* m_pStmt; + std::string m_canonical; }; } @@ -168,11 +338,24 @@ bool qc_thread_init(uint32_t kind) QC_TRACE(); ss_dassert(this_unit.classifier); - bool rc = true; + bool rc = false; - if (kind & QC_INIT_PLUGIN) + this_thread.pInfo_cache = new (std::nothrow) QCInfoCache; + + if (this_thread.pInfo_cache) { - rc = this_unit.classifier->qc_thread_init() == 0; + rc = true; + + if (kind & QC_INIT_PLUGIN) + { + rc = this_unit.classifier->qc_thread_init() == 0; + } + + if (!rc) + { + delete this_thread.pInfo_cache; + this_thread.pInfo_cache = nullptr; + } } return rc; @@ -187,6 +370,9 @@ void qc_thread_end(uint32_t kind) { this_unit.classifier->qc_thread_end(); } + + delete this_thread.pInfo_cache; + this_thread.pInfo_cache = nullptr; } qc_parse_result_t qc_parse(GWBUF* query, uint32_t collect) @@ -208,6 +394,7 @@ uint32_t qc_get_type_mask(GWBUF* query) uint32_t type_mask = QUERY_TYPE_UNKNOWN; + QCInfoCacheScope scope(query); this_unit.classifier->qc_get_type_mask(query, &type_mask); return type_mask; @@ -220,6 +407,7 @@ qc_query_op_t qc_get_operation(GWBUF* query) int32_t op = QUERY_OP_UNDEFINED; + QCInfoCacheScope scope(query); this_unit.classifier->qc_get_operation(query, &op); return (qc_query_op_t)op; @@ -232,6 +420,7 @@ char* qc_get_created_table_name(GWBUF* query) char* name = NULL; + QCInfoCacheScope scope(query); this_unit.classifier->qc_get_created_table_name(query, &name); return name; @@ -244,6 +433,7 @@ bool qc_is_drop_table_query(GWBUF* query) int32_t is_drop_table = 0; + QCInfoCacheScope scope(query); this_unit.classifier->qc_is_drop_table_query(query, &is_drop_table); return (is_drop_table != 0) ? true : false; @@ -257,6 +447,7 @@ char** qc_get_table_names(GWBUF* query, int* tblsize, bool fullnames) char** names = NULL; *tblsize = 0; + QCInfoCacheScope scope(query); this_unit.classifier->qc_get_table_names(query, fullnames, &names, tblsize); return names; @@ -293,6 +484,7 @@ bool qc_query_has_clause(GWBUF* query) int32_t has_clause = 0; + QCInfoCacheScope scope(query); this_unit.classifier->qc_query_has_clause(query, &has_clause); return (has_clause != 0) ? true : false; @@ -307,6 +499,7 @@ void qc_get_field_info(GWBUF* query, const QC_FIELD_INFO** infos, size_t* n_info uint32_t n = 0; + QCInfoCacheScope scope(query); this_unit.classifier->qc_get_field_info(query, infos, &n); *n_infos = n; @@ -321,6 +514,7 @@ void qc_get_function_info(GWBUF* query, const QC_FUNCTION_INFO** infos, size_t* uint32_t n = 0; + QCInfoCacheScope scope(query); this_unit.classifier->qc_get_function_info(query, infos, &n); *n_infos = n; @@ -334,6 +528,7 @@ char** qc_get_database_names(GWBUF* query, int* sizep) char** names = NULL; *sizep = 0; + QCInfoCacheScope scope(query); this_unit.classifier->qc_get_database_names(query, &names, sizep); return names; @@ -346,6 +541,7 @@ char* qc_get_prepare_name(GWBUF* query) char* name = NULL; + QCInfoCacheScope scope(query); this_unit.classifier->qc_get_prepare_name(query, &name); return name; @@ -358,6 +554,7 @@ GWBUF* qc_get_preparable_stmt(GWBUF* stmt) GWBUF* preparable_stmt = NULL; + QCInfoCacheScope scope(stmt); this_unit.classifier->qc_get_preparable_stmt(stmt, &preparable_stmt); return preparable_stmt;