polardbxengine/sql/ccl/ccl.h

802 lines
22 KiB
C++

/* Copyright (c) 2018, 2021, Alibaba and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.
This program is also distributed with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL/PolarDB-X Engine hereby grant you an
additional permission to link the program and your derivative works with the
separately licensed software that they have included with
MySQL/PolarDB-X Engine.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License, version 2.0, for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
#ifndef SQL_CCL_CCL_INCLUDED
#define SQL_CCL_CCL_INCLUDED
#include <atomic>
#include "my_sqlcommand.h"
#include "mysql/psi/mysql_rwlock.h"
#include "sql/ccl/ccl_bucket.h"
#include "sql/ccl/ccl_common.h"
#include "sql/ccl/ccl_table_common.h"
/**
Concurrency control module.
*/
/* Every wait time interval (second) */
#define CCL_RETRY_INTERVAL 5
/* The default wait time (second) */
#define CCL_LONG_WAIT ((ulong)3600L * 24L)
/* The default max wait thread */
#define CCL_DEFAULT_WAITING_COUNT ((ulonglong)0L)
class TABLE_LIST;
class THD;
class LEX;
class Item;
namespace im {
class Ccl_slot;
class Ccl_queue_hint;
/* The max timeout when concurrency control */
extern ulong ccl_wait_timeout;
/* The max waiting count when concurrency control */
extern ulonglong ccl_max_waiting_count;
/* Result structure for show_ccl_rule */
typedef struct Ccl_show_result {
ulonglong id;
LEX_CSTRING type;
LEX_STRING schema;
LEX_STRING table;
ulonglong concurrency_count;
LEX_STRING state;
LEX_STRING ordered;
ulonglong matched;
ulonglong running;
ulonglong waiting;
LEX_STRING keywords;
public:
Ccl_show_result() {
id = 0;
type = {nullptr, 0};
schema = {nullptr, 0};
table = {nullptr, 0};
concurrency_count = 0;
matched = 0;
running = 0;
waiting = 0;
keywords = {nullptr, 0};
}
} Ccl_show_result;
/**
Concurrency control thread local context.
*/
class Ccl_comply {
public:
explicit Ccl_comply(THD *thd) : m_thd(thd), m_slot(nullptr), m_version(0) {}
~Ccl_comply();
/**
Comply the rule when execute the statement.
@param[in] sql_command Query command
@param[in] all_tables Table list
@param[in] query Statement string
@retval true Rule worked
@retval false Skipped
*/
bool comply_rule(enum_sql_command sql_command, TABLE_LIST *all_tables,
const LEX_CSTRING &query);
/**
Comply the queue hint when execute the statement.
@param[in] lex Lex context
@param[in] hint Queue hint
@retval true Rule worked
@retval false Skipped
*/
bool comply_queue(THD *thd, LEX *lex, Ccl_queue_hint *hint);
/**
Comply the where cond when execute the statement.
@param[in] thd Thread context
@param[in] cond Where cond
*/
bool comply_cond(THD *thd, Item *cond);
/**
End the statement if comply some rule.
*/
void comply_end();
private:
THD *m_thd;
Ccl_slot *m_slot;
ulonglong m_version;
};
/**
The keywords within a rule
*/
class Ccl_keywords : public PSI_memory_base, public Disable_copy_base {
/* Normally no more than 3 keywords in a rule. */
static constexpr const size_t PREALLOC = 3;
typedef Keyword_array_type<PREALLOC> Keyword_array;
/* Original keyword string is separated by ";" */
static constexpr const char *SEPARATOR = ";";
public:
Ccl_keywords(const char *source, PSI_memory_key key)
: PSI_memory_base(key), m_keyword_array(key) {
build_keyword(source);
}
virtual ~Ccl_keywords() { m_keyword_array.clear(); }
/**
Build the keyword array from string
@param[in] source string
*/
void build_keyword(const char *source);
size_t size() const { return m_keyword_array.size(); }
/**
Whether the query string match the keywords.
@param[in] query Statement
@param[in] ordered Match the keyword orderly or not
@retval true Matched
@retval false Not matched
*/
bool match(const LEX_CSTRING &query, bool ordered) const;
private:
/* keywords array */
Keyword_array m_keyword_array;
};
/**
The concurrency control rule definition.
*/
class Ccl_rule : public PSI_memory_base, public Disable_copy_base {
public:
explicit Ccl_rule(const Ccl_record *record, PSI_memory_key key)
: PSI_memory_base(key),
m_rule_id(record->id),
m_type(record->type),
m_schema(record->schema_name ? record->schema_name : ""),
m_table(record->table_name ? record->table_name : ""),
m_concurrency_count(record->concurrency_count),
m_ccl_keywords(record->keywords ? record->keywords : "", key),
m_state(record->state),
m_order(record->ordered),
m_slot(nullptr),
m_original_keywords(record->keywords ? record->keywords : ""),
m_matched(0) {}
explicit Ccl_rule(ulonglong bucket_size, PSI_memory_key key)
: PSI_memory_base(key),
m_rule_id(0),
m_type(Ccl_rule_type::RULE_QUEUE),
m_schema(""),
m_table(""),
m_concurrency_count(bucket_size),
m_ccl_keywords("", key),
m_state(Ccl_rule_state::RULE_ACTIVE),
m_order(Ccl_rule_ordered::RULE_DISORDER),
m_slot(nullptr),
m_original_keywords(""),
m_matched(0) {}
/* Except of slot, Other elements will be destructed automatically */
virtual ~Ccl_rule() { unbind_slot(); }
/**
Search an available slot for rule.
report error message by level if failed.
@param[in] level error level
@retval true Failure
@retval false Success
*/
bool bind_slot(Ccl_error_level level);
/**
Release the slot back to ring_buffer,
Call it when destruct rule.
*/
void unbind_slot();
const String_ccl &get_schema() const { return m_schema; }
const String_ccl &get_table() const { return m_table; }
const String_ccl &get_keywords() const { return m_original_keywords; }
Ccl_slot *get_slot() const { return m_slot; }
ulonglong get_concurrency_count() const { return m_concurrency_count; }
ulonglong get_rule_id() const { return m_rule_id; }
Ccl_rule_state get_state() const { return m_state; }
Ccl_rule_type get_type() const { return m_type; }
Ccl_rule_ordered get_ordered() const { return m_order; }
ulonglong get_matched() const { return m_matched; }
bool keyword_is_null() const { return m_ccl_keywords.size() == 0; }
bool match_keyword(const LEX_CSTRING &query) const;
Ccl_slot *retrieve_slot(ulonglong *version);
void set_id(ulonglong value) { m_rule_id = value; }
private:
ulonglong m_rule_id;
Ccl_rule_type m_type;
String_ccl m_schema;
String_ccl m_table;
ulonglong m_concurrency_count;
Ccl_keywords m_ccl_keywords;
Ccl_rule_state m_state;
Ccl_rule_ordered m_order;
Ccl_slot *m_slot;
String_ccl m_original_keywords;
ulonglong m_matched;
};
/**
Rule set for every kind of rule type
*/
class Ccl_rule_set : public PSI_memory_base, public Disable_copy_base {
public:
static constexpr const size_t PREALLOC = 10;
/**
Concurrency control containe three category of rules:
1) schema + table + keyword(optional)
- Firstly, search rules according to [schema + table],
and match keyword if has.
2) keywords
- Secondly, search the rules according keyword.
3) command
- Last, only one rule by sql command.
So it corresponds three containers:
1) Hash on Intact key [schema + table]
2) Array
3) Ccl_rule
*/
/**
Revision History:
-----------------
1. Add ccl queue strategy
-- It allocated several rules according to variable ccl_queue_bucket_count,
Every bucket controled the concurrency count by ccl_queue_bucket_size.
So it use array to save all queue rules, and mapped the position by hash value.
*/
using Intact_object_rules = Ccl_rule_map_type<Ccl_rule>;
using Intact_value_type = Intact_object_rules::value_type;
using Intact_key_type = Intact_object_rules::key_type;
/* Array of keyword rules. element type is ccl_rule pointer */
using Keyword_rules = Ccl_rule_list_type<Ccl_rule *, PREALLOC>;
/* Array of queue rules. */
using Queue_buckets =
Ccl_rule_list_type<Ccl_rule *, CCL_QUEUE_BUCKET_COUNT_MAX>;
class Rule_lock_helper : public Disable_unnamed_object {
public:
explicit Rule_lock_helper(mysql_rwlock_t *lock, bool exclusive)
: m_locked(false), m_lock(lock) {
if (exclusive)
mysql_rwlock_wrlock(m_lock);
else
mysql_rwlock_rdlock(m_lock);
m_locked = true;
}
void unlock() {
mysql_rwlock_unlock(m_lock);
m_locked = false;
}
~Rule_lock_helper() {
if (m_locked) mysql_rwlock_unlock(m_lock);
}
private:
bool m_locked;
mysql_rwlock_t *m_lock;
};
public:
Ccl_rule_set(Ccl_rule_type type, PSI_memory_key key)
: PSI_memory_base(key),
m_intact_rules(key),
m_intact_size(0),
m_keyword_rules(key),
m_keyword_size(0),
m_command_rule(nullptr),
m_command_size(0),
m_queue_buckets(key),
m_queue_size(0),
m_type(type) {
mysql_rwlock_init(key_rwlock_rule_container, &m_intact_lock);
mysql_rwlock_init(key_rwlock_rule_container, &m_keyword_lock);
mysql_rwlock_init(key_rwlock_rule_container, &m_command_lock);
mysql_rwlock_init(key_rwlock_rule_container, &m_queue_lock);
}
virtual ~Ccl_rule_set() {
clear_rules();
clear_queue_buckets(true);
mysql_rwlock_destroy(&m_intact_lock);
mysql_rwlock_destroy(&m_keyword_lock);
mysql_rwlock_destroy(&m_command_lock);
mysql_rwlock_destroy(&m_queue_lock);
}
/**
Comply the rules, the matching order is : intact rule, keyword rule,
command rule.
@param[in] all_tables Table list
@param[in] query Statement string
@param[out] version the matched slot version
@retval slot
*/
Ccl_slot *comply_rule(TABLE_LIST *all_tables, const LEX_CSTRING &query,
ulonglong *version);
/**
Comply the queue rules.
@param[in] hash hash value
@param[out] version the matched slot version
@retval slot
*/
Ccl_slot *comply_queue(ulonglong hash, ulonglong *version);
/**
Refresh the rules.
Report the error message by level.
@param[in] group rule group
@param[in] force_clearn whether clear cache
@param[in] level error level
@retval added rule count
*/
size_t refresh_rules(Ccl_record_group &group, bool force_clean,
Ccl_error_level level);
/**
Collect all active queue buckets.
@param[in] mem_root memory pool
@param[out] results rule result container
*/
void collect_queue_buckets(MEM_ROOT *mem_root,
std::vector<Ccl_show_result *> &results);
/**
Collect all active rules.
@param[in] mem_root memory pool
@param[out] results rule result container
*/
void collect_rules(MEM_ROOT *mem_root,
std::vector<Ccl_show_result *> &results);
/**
Insert the intact rules into cache.
Report corressponding error message by level.
@param[in] rules Rule set
@param[in] level Error level
@retval affected rule count
*/
size_t insert_intact_rules(std::vector<Ccl_record *> &rules,
Ccl_error_level level);
/**
Insert the keyword rules into cache.
Report corressponding error message by level.
@param[in] rules Rule set
@param[in] level Error level
@retval affected rule count
*/
size_t insert_keyword_rules(std::vector<Ccl_record *> &rules,
Ccl_error_level level);
/**
Insert the command rules into cache.
Report corressponding error message by level.
@param[in] rules Rule set
@param[in] level Error level
@retval affected rule count
*/
size_t insert_command_rules(std::vector<Ccl_record *> &rules,
Ccl_error_level level);
/**
Clear the rule cache by rule_id.
@param[in] rule_id rule id
@retval affected rows
*/
size_t delete_rule(ulonglong rule_id);
/**
Clear all rules from intact, keyword, command container.
*/
void clear_rules();
/**
Clear all the queue bucket.
*/
void clear_queue_buckets(bool need_lock);
/**
Init the queue buckets
@param[in] bucket_count The bucket count
@param[in] bucket_size The bucket size
@param[in] level Error level
@retval successful item count
*/
size_t init_queue_buckets(ulonglong bucket_count, ulonglong bucket_size,
Ccl_error_level level);
private:
/* Clear intact container */
void clear_intact_rules();
/* Clear keyword container */
void clear_keyword_rules();
/* Clear command container */
void clear_command_rule();
/**
clear the rule from intact container by rule_id.
@param[in] rule_id rule id
@retval affected rows
*/
size_t delete_intact_rule(ulonglong rule_id);
/**
clear the rule from keyword container by rule_id.
@param[in] rule_id rule id
@retval affected rows
*/
size_t delete_keyword_rule(ulonglong rule_id);
/**
clear the rule from command container by rule_id.
@param[in] rule_id rule id
@retval affected rows
*/
size_t delete_command_rule(ulonglong rule_id);
private:
/* Hash on [schema + table] */
Intact_object_rules m_intact_rules;
mysql_rwlock_t m_intact_lock;
std::atomic_ullong m_intact_size;
/* Array on [keywords] */
Keyword_rules m_keyword_rules;
mysql_rwlock_t m_keyword_lock;
std::atomic_ullong m_keyword_size;
/* Single command rule */
Ccl_rule *m_command_rule;
mysql_rwlock_t m_command_lock;
std::atomic_ullong m_command_size;
Queue_buckets m_queue_buckets;
mysql_rwlock_t m_queue_lock;
std::atomic_ullong m_queue_size;
Ccl_rule_type m_type;
};
/**
Singleton concurrency control system
Prepare a rule set container for every kind of rule type
(SELECT, UPDATE, INSERT, DELETE).
Rule set container initilized when ccl_init(), and didn't support to
add new rule type dynamically, so it's not necessary to be protected
by lock.
*/
class System_ccl : public PSI_memory_base, public Disable_copy_base {
public:
System_ccl(PSI_memory_key key)
: PSI_memory_base(key),
m_select_rule_set(Ccl_rule_type::RULE_SELECT, key),
m_update_rule_set(Ccl_rule_type::RULE_UPDATE, key),
m_insert_rule_set(Ccl_rule_type::RULE_INSERT, key),
m_delete_rule_set(Ccl_rule_type::RULE_DELETE, key),
m_queue_bucket_set(Ccl_rule_type::RULE_QUEUE, key) {}
/**
Refresh the rules.
Report the error message by level.
@param[in] group rule group
@param[in] type rule type
@param[in] force_clearn whether clear cache
@param[in] level error level
@retval added rule count
*/
size_t refresh_rules(Ccl_record_group &group, Ccl_rule_type type,
bool force_clean, Ccl_error_level level);
/* Clear all active rules */
void clear_rules();
/**
Collect all active rules.
@param[in] mem_root memory pool
@param[out] results rule result container
*/
void collect_rules(MEM_ROOT *mem_root,
std::vector<Ccl_show_result *> &results);
/**
Collect all active queue buckets.
@param[in] mem_root memory pool
@param[out] results rule result container
*/
void collect_queue_buckets(MEM_ROOT *mem_root,
std::vector<Ccl_show_result *> &results);
/**
Delete the rule by rule id.
@param[in] rule_id rule id
@retval affected rule count
*/
size_t delete_rule(ulonglong rule_id);
/**
Get the corresponding rule set by type.
*/
Ccl_rule_set *get_rule_set(Ccl_rule_type type) {
DBUG_ENTER("get_rule_set");
switch (type) {
case Ccl_rule_type::RULE_SELECT:
DBUG_RETURN(&m_select_rule_set);
case Ccl_rule_type::RULE_UPDATE:
DBUG_RETURN(&m_update_rule_set);
case Ccl_rule_type::RULE_INSERT:
DBUG_RETURN(&m_insert_rule_set);
case Ccl_rule_type::RULE_DELETE:
DBUG_RETURN(&m_delete_rule_set);
/* Pls use get_queue_buckets() get queue_buckets */
case Ccl_rule_type::RULE_QUEUE:
default:
DBUG_ASSERT(0);
DBUG_RETURN(nullptr);
}
}
/* Get queue buckets */
Ccl_rule_set *get_queue_buckets() { return &m_queue_bucket_set; }
public:
/* Singleton instance */
static System_ccl *instance() { return m_system_ccl; }
/* Singleton object */
static System_ccl *m_system_ccl;
/* Destructor */
virtual ~System_ccl() {}
private:
/* SELECT rule set */
Ccl_rule_set m_select_rule_set;
/* UPDATE rule set */
Ccl_rule_set m_update_rule_set;
/* INSERT rule set */
Ccl_rule_set m_insert_rule_set;
/* DELETE rule set */
Ccl_rule_set m_delete_rule_set;
/* Queue buckets */
Ccl_rule_set m_queue_bucket_set;
};
/**
Ccl rule only keeped the static configure.
Every valid rule will has corresponding slot which maintained
dynamic running state.
*/
class Ccl_slot {
public:
Ccl_slot();
virtual ~Ccl_slot();
/**
Acquire this slot;
@param[in] version Unique version
@param[in] cc_count Concurrency count
@retval true Success
@retval false Failure
*/
bool acquire(ulonglong version, ulonglong cc_count);
/**
Release this slot;
Should call it if return the slot back to ring_buffer.
*/
void release();
/**
Enter the condition.
@param[in] thd thread context.
@param[in] version the thread local version.
*/
void enter_cond(THD *thd, ulonglong version);
/**
Exit the condition.
*/
void exit_cond();
void set_concurrency(ulonglong value) { m_concurrency_count = value; }
void reset_concurrency() { m_concurrency_count = 0; }
ulonglong get_version() const { return m_version; }
ulonglong get_running() const { return m_running; }
ulonglong get_waiting() const { return m_waiting; }
void lock();
void unlock();
private:
mysql_mutex_t m_mutex;
mysql_cond_t m_cond;
/* Concurrency count from ccl rule */
ulonglong m_concurrency_count;
/* Running count */
ulonglong m_running;
/* Waiting count */
ulonglong m_waiting;
/* Every rule should has unique version when assign a slot */
ulonglong m_version;
/* if available or not */
std::atomic<bool> m_state;
};
/**
Pre-allocateded array as the cycle buffer, every slot can be used
from head to tail and again.
*/
template <typename Element_type, size_t Prealloc>
class Ring_buffer : public PSI_memory_base, public Disable_copy_base {
public:
/* Array structure */
using Buffer = Prealloced_array<Element_type *, Prealloc>;
explicit Ring_buffer(PSI_memory_key key)
: PSI_memory_base(key),
m_buffer(key),
m_pos(0),
m_size(Prealloc),
m_global_version(1) {}
/* Destructor of Ring_buffer */
virtual ~Ring_buffer() {
for (typename Buffer::const_iterator it = m_buffer.cbegin();
it != m_buffer.cend(); it++) {
destroy_object<Element_type>(*it);
}
m_buffer.clear();
}
/* Get slots */
Buffer &get_slots() { return m_buffer; }
/**
Acquire an available slot, return null if still unavailable
after Prealloc times.
@param[in] cc_count Concurrency count
@retval Element_type Available slot
@retval nullptr Unavailable slot
*/
Element_type *acquire_slot(ulonglong cc_count) {
ulonglong begin_pos;
ulonglong version;
size_t loop = 0;
DBUG_ENTER("Ring_buffer::acquire_slot");
begin_pos = m_pos.load();
version = m_global_version.fetch_add(1);
while (!(m_buffer[begin_pos % m_size]->acquire(version, cc_count)) &&
(loop <= m_size)) {
begin_pos++;
loop++;
}
/* Still unavailable slot retry loop times */
if (loop > m_size) {
DBUG_RETURN(nullptr);
} else {
/* Set next begin position to improve efficiency */
m_pos.store(begin_pos + 1);
DBUG_RETURN(m_buffer[begin_pos % m_size]);
}
}
/* Singleton instance */
static Ring_buffer *instance() { return m_ring_buffer; }
static Ring_buffer *m_ring_buffer;
private:
/* Slot array */
Buffer m_buffer;
/* Last available position + 1 */
std::atomic_ullong m_pos;
/* Array size */
size_t m_size;
/* Global version for every acquire_slot */
std::atomic_ullong m_global_version;
};
/**
Concurrency control system allowed max slot size setting.
1) 64
The max rule records in mysql.concurrency_control
2) CCL_QUEUE_BUCKET_COUNT_MAX
The max queue bucket count
*/
#define CCL_SLOT_SIZE (64 + CCL_QUEUE_BUCKET_COUNT_MAX)
/* Instantiation of slots */
using Ccl_ring_slots = Ring_buffer<Ccl_slot, CCL_SLOT_SIZE>;
} /*namespace im */
#endif