From: Willy Tarreau Date: Wed, 10 Sep 2025 16:47:50 +0000 (+0200) Subject: MEDIUM: stick-table: move process_table_expire() to a single thread X-Git-Tag: v3.3-dev9~150 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=e05afda249299cdd415268b7e24884ed963e3cdb;p=thirdparty%2Fhaproxy.git MEDIUM: stick-table: move process_table_expire() to a single thread A big deal of the task_queue() contention is caused by this function because it's created using task_new_anywhere() and is subject to heavy updates. Let's turn it to single thread by rotating the assigned threads during initialization so that a table only runs on one thread at a time. However there's a trick: the function used to call task_queue() to requeue the task if it had advanced its timer (may only happen when learning an entry from a peer). We can't do that anymore since we can't queue another thread's task. Thus instead of the task needs to be scheduled earlier than previously planned, we simply perform a wakeup. It will likely do nothing and will self-adjust its next wakeup timer. Doing so halves the number of multi-thread task wakeups. In addition the request rate at saturation increased by 12% with 16 peers and 40 tables on a 16 8-thread processes. This should improve the situation described by Felipe in issues #3084 and #3101. This should be backported to 3.2 after some extended checks. --- diff --git a/src/stick_table.c b/src/stick_table.c index b7f6f89bb..b3ff76878 100644 --- a/src/stick_table.c +++ b/src/stick_table.c @@ -740,9 +740,11 @@ void stktable_requeue_exp(struct stktable *t, const struct stksess *ts) new_exp = tick_first(expire, old_exp); } - task_queue(t->exp_task); - HA_RWLOCK_WRUNLOCK(STK_TABLE_LOCK, &t->lock); + + /* the timer was advanced, only the task can update it */ + if (!tick_isset(old_exp) || tick_is_lt(new_exp, old_exp)) + task_wakeup(t->exp_task, TASK_WOKEN_OTHER); } /* Returns a valid or initialized stksess for the specified stktable_key in the @@ -1087,6 +1089,7 @@ struct task *process_table_expire(struct task *task, void *context, unsigned int */ int stktable_init(struct stktable *t, char **err_msg) { + static int operating_thread = 0; int peers_retval = 0; int shard; int i; @@ -1106,9 +1109,11 @@ int stktable_init(struct stktable *t, char **err_msg) t->pool = create_pool("sticktables", sizeof(struct stksess) + round_ptr_size(t->data_size) + t->key_size, MEM_F_SHARED); if ( t->expire ) { - t->exp_task = task_new_anywhere(); + t->exp_task = task_new_on(operating_thread); if (!t->exp_task) goto mem_error; + operating_thread = (operating_thread + 1) % global.nbthread; + t->exp_task->process = process_table_expire; t->exp_task->context = (void *)t; }