diff --git a/README.md b/README.md index fe7af8b..5173943 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ matches the purge request. - [Compatibility](#compatibility) - [Installation](#installation) - [Directives](#directives) +- [Variables](#variables) - [Partial key purge](#partial-key-purge) - [Sample configurations](#sample-configurations) - [Performance tuning](#performance-tuning) @@ -253,6 +254,39 @@ variant regardless. --- +## Variables + +These read-only variables are always available when the module is loaded. +They return `"-"` when `cache_purge_background_queue` is `off`. + +### `$cache_purge_queue_size` + +Current number of entries waiting in the background purge queue. Updated +atomically — safe to read from any worker process. Useful for capacity +monitoring and alerting. + +### `$cache_purge_queue_max_size` + +The configured maximum queue depth (`cache_purge_queue_size`). Constant for +the lifetime of the nginx process. Useful alongside `$cache_purge_queue_size` +to compute queue utilisation. + +**Example — log queue depth on every request:** + +```nginx +log_format purge_mon '$remote_addr [$time_local] ' + 'queue=$cache_purge_queue_size/$cache_purge_queue_max_size'; +access_log /var/log/nginx/purge.log purge_mon; +``` + +**Example — expose as a response header:** + +```nginx +add_header X-Purge-Queue-Depth $cache_purge_queue_size; +``` + +--- + ## Partial key purge When the exact cache key is not known — for example because it includes cookie @@ -428,6 +462,9 @@ keep `batch_size` low and `throttle_ms` high to avoid iowait spikes. Successful background purges return `202 Accepted`. The response body uses the format set by `cache_purge_response_type`. +Use `$cache_purge_queue_size` and `$cache_purge_queue_max_size` to track queue +depth in logs or response headers — see [Variables](#variables). + Relevant log messages: | Level | Condition | diff --git a/ngx_cache_purge_module.c b/ngx_cache_purge_module.c index a1e54e4..cdb8e92 100644 --- a/ngx_cache_purge_module.c +++ b/ngx_cache_purge_module.c @@ -225,6 +225,11 @@ static ngx_uint_t ngx_http_cache_purge_hash_key(ngx_str_t *cache_path, static ngx_http_cache_purge_queue_item_t *ngx_http_cache_purge_find_duplicate( ngx_http_cache_purge_queue_t *queue, ngx_uint_t hash, ngx_str_t *cache_path, ngx_str_t *key); +static ngx_int_t ngx_http_cache_purge_add_variables(ngx_conf_t *cf); +static ngx_int_t ngx_http_cache_purge_queue_size_variable( + ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_cache_purge_queue_max_size_variable( + ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); # if (NGX_HTTP_FASTCGI) char *ngx_http_fastcgi_cache_purge_conf(ngx_conf_t *cf, @@ -309,6 +314,21 @@ char *ngx_http_cache_purge_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); +/* -- variable table ----------------------------------------------------- */ + +static ngx_http_variable_t ngx_http_cache_purge_vars[] = { + + { ngx_string("cache_purge_queue_size"), NULL, + ngx_http_cache_purge_queue_size_variable, 0, + NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("cache_purge_queue_max_size"), NULL, + ngx_http_cache_purge_queue_max_size_variable, 0, + NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_null_string, NULL, NULL, 0, 0, 0 } +}; + /* -- module commands ---------------------------------------------------- */ static ngx_command_t ngx_http_cache_purge_module_commands[] = { @@ -396,7 +416,7 @@ static ngx_command_t ngx_http_cache_purge_module_commands[] = { static ngx_http_module_t ngx_http_cache_purge_module_ctx = { NULL, /* preconfiguration */ - NULL, /* postconfiguration */ + ngx_http_cache_purge_add_variables, /* postconfiguration */ ngx_http_cache_purge_create_main_conf, /* create main conf */ ngx_http_cache_purge_init_main_conf, /* init main conf */ NULL, /* create srv conf */ @@ -425,6 +445,97 @@ static ngx_event_t ngx_cache_purge_event; static ngx_http_cache_purge_main_conf_t *ngx_cache_purge_main_conf; +/* -- variables ---------------------------------------------------------- */ + +static ngx_int_t +ngx_http_cache_purge_add_variables(ngx_conf_t *cf) +{ + ngx_http_variable_t *var, *v; + + for (v = ngx_http_cache_purge_vars; v->name.len; v++) { + var = ngx_http_add_variable(cf, &v->name, v->flags); + if (var == NULL) { + return NGX_ERROR; + } + var->get_handler = v->get_handler; + var->data = v->data; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_cache_purge_queue_size_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_http_cache_purge_main_conf_t *cmcf; + ngx_uint_t size; + u_char *p; + u_char buf[NGX_ATOMIC_T_LEN]; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_cache_purge_module); + + if (!cmcf->background_purge || cmcf->queue == NULL) { + v->data = (u_char *) "-"; + v->len = 1; + v->valid = 1; + v->not_found = 0; + return NGX_OK; + } + + size = (ngx_uint_t) ngx_atomic_fetch_add(&cmcf->queue->size, 0); + + p = ngx_sprintf(buf, "%ui", size); + + v->data = ngx_pnalloc(r->pool, p - buf); + if (v->data == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(v->data, buf, p - buf); + v->len = p - buf; + v->valid = 1; + v->not_found = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_cache_purge_queue_max_size_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_http_cache_purge_main_conf_t *cmcf; + u_char *p; + u_char buf[NGX_INT_T_LEN]; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_cache_purge_module); + + if (!cmcf->background_purge || cmcf->queue == NULL) { + v->data = (u_char *) "-"; + v->len = 1; + v->valid = 1; + v->not_found = 0; + return NGX_OK; + } + + p = ngx_sprintf(buf, "%ui", cmcf->queue->max_size); + + v->data = ngx_pnalloc(r->pool, p - buf); + if (v->data == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(v->data, buf, p - buf); + v->len = p - buf; + v->valid = 1; + v->not_found = 0; + + return NGX_OK; +} + + /* -- main configuration ------------------------------------------------- */ static void * diff --git a/t/background_queue.t b/t/background_queue.t index 173d02f..28f2fa8 100644 --- a/t/background_queue.t +++ b/t/background_queue.t @@ -128,3 +128,38 @@ PURGE /cache/test [202, 202] --- response_body eval [qr{Key: /cache/same}, qr{Key: /cache/same}] + +=== TEST 7: $cache_purge_queue_size and $cache_purge_queue_max_size return integers when queue is enabled +--- http_config + proxy_cache_path $TEST_NGINX_SERVROOT/cache levels=1:2 + keys_zone=qsv_zone:1m; + cache_purge_background_queue on; + cache_purge_queue_size 10; +--- config + location /health { + add_header X-Queue-Size $cache_purge_queue_size; + add_header X-Queue-MaxSize $cache_purge_queue_max_size; + return 200 "ok"; + } +--- request +GET /health +--- error_code: 200 +--- response_headers_like +X-Queue-Size: \d+ +X-Queue-MaxSize: 10 + +=== TEST 8: $cache_purge_queue_size and $cache_purge_queue_max_size return "-" when queue is disabled +--- http_config + cache_purge_background_queue off; +--- config + location /health { + add_header X-Queue-Size $cache_purge_queue_size; + add_header X-Queue-MaxSize $cache_purge_queue_max_size; + return 200 "ok"; + } +--- request +GET /health +--- error_code: 200 +--- response_headers +X-Queue-Size: - +X-Queue-MaxSize: -