feat(pgsql,mysql): gate ProcessAllSessions_Partition by pool-acquire NULL ratio

ProcessAllSessions_Partition (Pass 1, the 3-way A/B/C session
classification) is cheap and beneficial when block B (sessions
waiting for a backend) is non-empty: it lets block A finish
queries and release conns to block B waiters in the same outer
iteration. When no sessions are contending for backends, partition
is unnecessary work.

Detect the contention state with a per-worker counter pair tracked
at the get_MyConn_from_pool() call site: every call increments
partition_pool_attempts; calls that returned NULL (couldn't acquire,
session must wait) also increment partition_pool_nulls. At the top
of each process_all_sessions outer iteration, the worker computes
the NULL ratio and updates a hysteresis state machine: 3 consecutive
iterations with ratio >= 5% flips partition_active to true; 3
consecutive iterations below the threshold flips it back.

Mechanism:
* Cheapest possible signal: 1 int increment per pool acquire, 1
  branch per outer iteration.
* Hysteresis (3 consecutive iterations either direction) filters
  transient bursts without needing min-sample / asymmetric-threshold
  tuning.
* Single threshold 5% works in both directions by virtue of the
  streak filter -- the ratio has to be sustained near the boundary
  for flapping to occur, which doesn't match any realistic workload.

Empirical verification at 1 worker thread, 500c clients, 4 KB rows,
50-conn backend pool, SSL on, 120 s:

  20c (no contention):   1576 tps, 13 ms avg latency, gate stays OFF
  100c (some):           1487 tps, 67 ms,             gate stays ON
  500c (heavy):          1487 tps, 336 ms,            gate stays ON

The gate transitions automatically; no admin variable to learn.

Applied symmetrically to MySQL_Thread / MySQL_Session and
PgSQL_Thread / PgSQL_Session. See #5791 for analysis.
pull/5799/head
Rene Cannao 1 month ago
parent 25d9ea8c4c
commit 5b5b9373e3

@ -116,6 +116,12 @@ class __attribute__((aligned(64))) MySQL_Thread : public Base_Thread
PtrArray *cached_connections;
unsigned int push_local_counter; // round-robin counter for bounded local caching: cache 1-in-N where N = mysql_threads
// State for the ratio-based partition gate. See PgSQL_Thread for the equivalent.
unsigned int partition_pool_attempts;
unsigned int partition_pool_nulls;
unsigned int partition_streak;
bool partition_active;
#ifdef IDLE_THREADS
struct epoll_event events[MY_EPOLL_THREAD_MAXEVENTS];
int efd;
@ -160,6 +166,14 @@ class __attribute__((aligned(64))) MySQL_Thread : public Base_Thread
public:
// Recorded at the get_MyConn_from_pool() call site by sessions inside this
// worker. Consumed and reset at the top of each process_all_sessions outer
// iteration to compute the ratio-based partition gate.
inline void note_pool_attempt(bool was_null) {
++partition_pool_attempts;
if (was_null) ++partition_pool_nulls;
}
void *gen_args; // this is a generic pointer to create any sort of structure
ProxySQL_Poll<MySQL_Data_Stream> mypolls;

@ -162,6 +162,14 @@ private:
PtrArray* cached_connections;
unsigned int push_local_counter; // round-robin counter for bounded local caching: cache 1-in-N where N = pgsql_threads
// State for the ratio-based partition gate. Counters are incremented at the
// get_MyConn_from_pool() call site within process_all_sessions; consumed and
// reset at the top of the next outer iteration.
unsigned int partition_pool_attempts;
unsigned int partition_pool_nulls;
unsigned int partition_streak;
bool partition_active;
#ifdef IDLE_THREADS
struct epoll_event events[MY_EPOLL_THREAD_MAXEVENTS];
int efd;
@ -214,6 +222,14 @@ protected:
public:
// Recorded at the get_MyConn_from_pool() call site by sessions inside this
// worker. Consumed and reset at the top of each process_all_sessions outer
// iteration to compute the ratio-based partition gate.
inline void note_pool_attempt(bool was_null) {
++partition_pool_attempts;
if (was_null) ++partition_pool_nulls;
}
void* gen_args; // this is a generic pointer to create any sort of structure
ProxySQL_Poll<PgSQL_Data_Stream> mypolls;

@ -7820,6 +7820,7 @@ void MySQL_Session::handler___client_DSS_QUERY_SENT___server_DSS_NOT_INITIALIZED
} else {
mc=MyHGM->get_MyConn_from_pool(mybe->hostgroup_id, this, (session_fast_forward || qpo->create_new_conn), NULL, 0, (int)qpo->max_lag_ms);
}
thread->note_pool_attempt(mc == NULL);
#ifdef STRESSTEST_POOL
if (mc && (loops < NUM_SLOW_LOOPS - 1)) {
if (mc->mysql) {

@ -4453,7 +4453,29 @@ void MySQL_Thread::process_all_sessions() {
}
#endif // IDLE_THREADS
if (sess_sort && mysql_sessions->len > 3) {
ProcessAllSessions_Partition<MySQL_Session>();
// Activate partition only when the pool acquire NULL-ratio
// (computed from get_MyConn_from_pool calls within the previous
// iteration) has been above HI_NUM/HI_DEN for STREAK consecutive
// iterations. Disable symmetrically once the ratio has been below
// HI_NUM/HI_DEN for STREAK consecutive iterations.
const unsigned int HI_NUM = 1, HI_DEN = 20; // 5%
const unsigned int STREAK = 3;
bool stressed = (partition_pool_attempts > 0)
&& (partition_pool_nulls * HI_DEN >= partition_pool_attempts * HI_NUM);
if (stressed == partition_active) {
partition_streak = 0;
} else {
if (++partition_streak >= STREAK) {
partition_active = stressed;
partition_streak = 0;
}
}
partition_pool_attempts = 0;
partition_pool_nulls = 0;
if (partition_active) {
ProcessAllSessions_Partition<MySQL_Session>();
}
}
for (n=0; n<mysql_sessions->len; n++) {
MySQL_Session *sess=(MySQL_Session *)mysql_sessions->index(n);
@ -4785,6 +4807,10 @@ MySQL_Thread::MySQL_Thread() {
my_idle_conns=NULL;
cached_connections=NULL;
push_local_counter=0;
partition_pool_attempts=0;
partition_pool_nulls=0;
partition_streak=0;
partition_active=false;
mysql_sessions=NULL;
mirror_queue_mysql_sessions=NULL;
mirror_queue_mysql_sessions_cache=NULL;
@ -6473,7 +6499,8 @@ void MySQL_Thread::push_MyConn_local(MySQL_Connection *c) {
// Rationale: avoids the connection-hoarding behavior that starved sibling
// workers at high client count, while preserving most of the lock-amortization
// benefit at lower client counts.
MySrvC *mysrvc=(MySrvC *)c->parent;
MySrvC *mysrvc=NULL;
mysrvc=(MySrvC *)c->parent;
// reset insert_id #1093
c->mysql->insert_id = 0;
if (mysrvc->get_status() == MYSQL_SERVER_STATUS_ONLINE) {

@ -5383,6 +5383,7 @@ void PgSQL_Session::handler___client_DSS_QUERY_SENT___server_DSS_NOT_INITIALIZED
else {
mc = PgHGM->get_MyConn_from_pool(mybe->hostgroup_id, this, (session_fast_forward || qpo->create_new_conn), NULL, 0, (int)qpo->max_lag_ms);
}
thread->note_pool_attempt(mc == NULL);
#ifdef STRESSTEST_POOL
if (mc && (loops < NUM_SLOW_LOOPS - 1)) {
if (mc->pgsql) {

@ -3833,7 +3833,29 @@ void PgSQL_Thread::process_all_sessions() {
}
#endif // IDLE_THREADS
if (sess_sort && mysql_sessions->len > 3) {
ProcessAllSessions_Partition<PgSQL_Session>();
// Activate partition only when the pool acquire NULL-ratio
// (computed from get_MyConn_from_pool calls within the previous
// iteration) has been above HI_NUM/HI_DEN for STREAK consecutive
// iterations. Disable symmetrically once the ratio has been below
// HI_NUM/HI_DEN for STREAK consecutive iterations.
const unsigned int HI_NUM = 1, HI_DEN = 20; // 5%
const unsigned int STREAK = 3;
bool stressed = (partition_pool_attempts > 0)
&& (partition_pool_nulls * HI_DEN >= partition_pool_attempts * HI_NUM);
if (stressed == partition_active) {
partition_streak = 0;
} else {
if (++partition_streak >= STREAK) {
partition_active = stressed;
partition_streak = 0;
}
}
partition_pool_attempts = 0;
partition_pool_nulls = 0;
if (partition_active) {
ProcessAllSessions_Partition<PgSQL_Session>();
}
}
for (n = 0; n < mysql_sessions->len; n++) {
PgSQL_Session* sess = (PgSQL_Session*)mysql_sessions->index(n);
@ -4207,6 +4229,10 @@ PgSQL_Thread::PgSQL_Thread() {
my_idle_conns = NULL;
cached_connections = NULL;
push_local_counter = 0;
partition_pool_attempts = 0;
partition_pool_nulls = 0;
partition_streak = 0;
partition_active = false;
mysql_sessions = NULL;
mirror_queue_mysql_sessions = NULL;
mirror_queue_mysql_sessions_cache = NULL;

Loading…
Cancel
Save