Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 |
Expand Down
113 changes: 112 additions & 1 deletion ngx_cache_purge_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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[] = {
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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 *
Expand Down
35 changes: 35 additions & 0 deletions t/background_queue.t
Original file line number Diff line number Diff line change
Expand Up @@ -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: -