Skip to content

Commit c3c4bea

Browse files
committed
feat(renderd): configure request queue limits
1 parent fddb0a9 commit c3c4bea

14 files changed

Lines changed: 258 additions & 21 deletions

docs/man/renderd.conf.5

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,19 @@ Specify the number of threads to be used for \fBrenderd\fR.
4444
A value of \fB'-1'\fR will configure \fBnum_threads\fR to the number of cores on the system.
4545
The default value is \fB'4'\fR (macro definition \fB'NUM_THREADS'\fR).
4646

47+
.TP
48+
.B request_queue_limit
49+
Specify the maximum number of requests kept in each time-critical request queue: priority, normal, low priority, and bulk.
50+
When one of these queues is full, new requests overflow into the dirty queue if space is available.
51+
The default value is \fB'256'\fR when metatiles are enabled, or \fB'512'\fR without metatiles (macro definition \fB'DEFAULT_REQUEST_QUEUE_LIMIT'\fR).
52+
53+
.TP
54+
.B dirty_queue_limit
55+
Specify the maximum number of requests kept in the dirty queue.
56+
The dirty queue is the background queue for expired or overflowed tiles where no client is waiting for the response.
57+
Larger values can preserve more expired work for quiet periods, but they can also increase memory use and make it easier for important overflowed tiles to sit behind lower-priority background work.
58+
The default value is \fB'8000'\fR when metatiles are enabled, or \fB'10000'\fR without metatiles (macro definition \fB'DEFAULT_DIRTY_QUEUE_LIMIT'\fR).
59+
4760
.TP
4861
.B pid_file
4962
Specify the file path into which the PID will be written by \fBrenderd\fR.
@@ -189,6 +202,8 @@ Only used by \fBrenderd\fR.
189202

190203
.SH SEE ALSO
191204
.BR renderd(1)
205+
.PP
206+
See \fBdocs/renderd-queue-tuning.md\fR in the source tree for queue tuning and rollout guidance.
192207
.BR
193208

194209
.SH AUTHOR

docs/renderd-queue-tuning.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# renderd Queue Tuning
2+
3+
`renderd` keeps incoming render requests in five queues:
4+
5+
- priority request queue: missing tiles where a client is waiting
6+
- request queue: stale tiles where a client is waiting
7+
- low priority request queue: less urgent stale/style refresh work
8+
- dirty queue: background work where no client waits for the response
9+
- bulk request queue: explicit bulk rendering work
10+
11+
Requests are fetched in that priority order. When a time-critical request queue
12+
is full, new requests overflow into the dirty queue. That prevents immediate
13+
drops, but it also means the overflowed request no longer gets client-waiting
14+
priority.
15+
16+
## Configuration
17+
18+
Set the queue limits in the active `[renderd]` section of `renderd.conf`:
19+
20+
```ini
21+
[renderd]
22+
num_threads=4
23+
request_queue_limit=256
24+
dirty_queue_limit=8000
25+
```
26+
27+
`request_queue_limit` applies separately to each of the priority, normal, low
28+
priority, and bulk queues. `dirty_queue_limit` applies to the background dirty
29+
queue. If the dirty queue is also full, new overflow work is dropped.
30+
31+
The defaults keep the previous compiled-in behavior:
32+
33+
- with metatiles: `request_queue_limit=256`, `dirty_queue_limit=8000`
34+
- without metatiles: `request_queue_limit=512`, `dirty_queue_limit=10000`
35+
36+
## Operating Guidance
37+
38+
Increase `dirty_queue_limit` when dirty tiles are dropped during peak load but
39+
the server has quiet periods later where it can catch up. This is useful for
40+
large public tile services where preserving expired work can improve freshness
41+
after the peak passes.
42+
43+
Do not increase `dirty_queue_limit` blindly on an overloaded server. If the
44+
higher-priority queues are continuously non-empty, dirty work may still starve,
45+
and a larger dirty queue mainly stores more backlog. Watch queue length, queue
46+
time, render throughput, and dropped-tile metrics before and after the change.
47+
48+
Increase gradually. For example, move from the default to a limit sized for
49+
roughly 10 minutes of dirty work, then one hour, before trying day-scale values.
50+
The right value depends on metatile size, render throughput, expiry volume, and
51+
whether the service has predictable low-load windows.
52+
53+
## Rollout Checklist
54+
55+
1. Record current `renderd_queue`, `renderd_queue_time`, and
56+
`renderd_processed` Munin graphs.
57+
2. Set `dirty_queue_limit` in `renderd.conf`.
58+
3. Restart `renderd` during a maintenance window.
59+
4. Confirm startup logs show the intended `request_queue_limit` and
60+
`dirty_queue_limit`.
61+
5. Watch whether dropped dirty work decreases without sustained growth in queue
62+
time.
63+
6. Roll back to the previous values if dirty queue time grows continuously or
64+
missing-tile requests appear to be delayed by old background work.
65+
66+
This setting only changes queue capacity. It does not implement bounded
67+
overtaking or fairness between queues; those would require a larger queue
68+
scheduler change.

etc/renderd/renderd.conf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ stats_file=/run/renderd/renderd.stats
66
socketname=/run/renderd/renderd.sock
77
num_threads=4
88
tile_dir=/var/cache/renderd/tiles
9+
;request_queue_limit=256
10+
;dirty_queue_limit=8000
911

1012
[mapnik]
1113
plugins_dir=/usr/lib/mapnik/3.1/input

etc/renderd/renderd.conf.examples

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@ stats_file=/run/renderd/renderd.stats
66
socketname=/run/renderd/renderd.sock
77
num_threads=4
88
tile_dir=/var/cache/renderd/tiles
9+
;request_queue_limit=256
10+
;dirty_queue_limit=8000
911

1012
;[renderd]
1113
;iphostname=::1
1214
;ipport=7654
1315
;num_threads=4
1416
;tile_dir=rados://tiles/etc/ceph/ceph.conf
17+
;request_queue_limit=256
18+
;dirty_queue_limit=8000
1519
;pid_file=/run/renderd/renderd_rados.pid
1620
;stats_file=/run/renderd/renderd.stats
1721

etc/renderd/renderd.conf.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ pid_file=@RENDERD_PIDFILE@
33
socketname=@RENDERD_SOCKET@
44
stats_file=@RENDERD_RUN_DIR@/renderd.stats
55
tile_dir=@RENDERD_TILE_DIR@
6+
; request_queue_limit=256
7+
; dirty_queue_limit=8000
68

79
[mapnik]
810
font_dir=@MAPNIK_FONTS_DIR@

includes/render_config.h

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,13 @@
114114
// Metatiles are much larger in size so we don't need big queues to handle large areas
115115
#ifdef METATILE
116116
#define QUEUE_MAX (64)
117-
#define REQ_LIMIT (256)
118-
#define DIRTY_LIMIT (8000)
117+
#define DEFAULT_REQUEST_QUEUE_LIMIT (256)
118+
#define DEFAULT_DIRTY_QUEUE_LIMIT (8000)
119119

120120
#else
121121
#define QUEUE_MAX (1024)
122-
#define REQ_LIMIT (512)
123-
#define DIRTY_LIMIT (10000)
124-
#define HASHIDX_SIZE 22123
122+
#define DEFAULT_REQUEST_QUEUE_LIMIT (512)
123+
#define DEFAULT_DIRTY_QUEUE_LIMIT (10000)
125124
#endif
126125

127126
// Penalty for client making an invalid request (in seconds)

includes/renderd.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,11 @@ typedef struct {
4242
const char *socketname;
4343
const char *stats_filename;
4444
const char *tile_dir;
45+
int dirty_queue_limit;
4546
int ipport;
4647
int mapnik_font_dir_recurse;
4748
int num_threads;
49+
int request_queue_limit;
4850
} renderd_config;
4951

5052
typedef struct {

includes/request_queue.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ struct item_idx {
5050
};
5151

5252
struct request_queue {
53+
int dirtyLimit;
5354
int hashidxSize;
55+
int requestLimit;
5456
struct item reqHead, reqPrioHead, reqLowHead, reqBulkHead, dirtyHead, renderHead;
5557
struct item_idx *item_hashidx;
5658
int reqNum, reqPrioNum, reqLowNum, reqBulkNum, dirtyNum;
@@ -60,6 +62,7 @@ struct request_queue {
6062
};
6163

6264
struct request_queue *request_queue_init();
65+
struct request_queue *request_queue_init_with_limits(int request_limit, int dirty_limit);
6366
void request_queue_close(struct request_queue *queue);
6467

6568
struct item *request_queue_fetch_request(struct request_queue *queue);

src/renderd.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -809,7 +809,7 @@ int main(int argc, char **argv)
809809
}
810810

811811
g_logger(G_LOG_LEVEL_INFO, "Initialising request queue");
812-
render_request_queue = request_queue_init();
812+
render_request_queue = request_queue_init_with_limits(config.request_queue_limit, config.dirty_queue_limit);
813813

814814
if (render_request_queue == NULL) {
815815
g_logger(G_LOG_LEVEL_CRITICAL, "Failed to initialise request queue");

src/renderd_config.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,9 @@ void process_renderd_sections(dictionary *ini, const char *config_file_name, ren
425425
copy_string(section, &configs_dest[renderd_section_num].name, renderd_strlen + 2);
426426

427427
process_config_int(ini, section, "ipport", &configs_dest[renderd_section_num].ipport, 0);
428+
process_config_int(ini, section, "dirty_queue_limit", &configs_dest[renderd_section_num].dirty_queue_limit, DEFAULT_DIRTY_QUEUE_LIMIT);
428429
process_config_int(ini, section, "num_threads", &configs_dest[renderd_section_num].num_threads, NUM_THREADS);
430+
process_config_int(ini, section, "request_queue_limit", &configs_dest[renderd_section_num].request_queue_limit, DEFAULT_REQUEST_QUEUE_LIMIT);
429431
process_config_string(ini, section, "iphostname", &configs_dest[renderd_section_num].iphostname, "", INILINE_MAX);
430432
process_config_string(ini, section, "pid_file", &configs_dest[renderd_section_num].pid_filename, RENDERD_PIDFILE, PATH_MAX);
431433
process_config_string(ini, section, "socketname", &configs_dest[renderd_section_num].socketname, RENDERD_SOCKET, PATH_MAX);
@@ -436,6 +438,16 @@ void process_renderd_sections(dictionary *ini, const char *config_file_name, ren
436438
configs_dest[renderd_section_num].num_threads = sysconf(_SC_NPROCESSORS_ONLN);
437439
}
438440

441+
if (configs_dest[renderd_section_num].request_queue_limit < 1) {
442+
g_logger(G_LOG_LEVEL_CRITICAL, "Specified request_queue_limit (%i) is too small, must be greater than or equal to %i.", configs_dest[renderd_section_num].request_queue_limit, 1);
443+
exit(7);
444+
}
445+
446+
if (configs_dest[renderd_section_num].dirty_queue_limit < 0) {
447+
g_logger(G_LOG_LEVEL_CRITICAL, "Specified dirty_queue_limit (%i) is too small, must be greater than or equal to %i.", configs_dest[renderd_section_num].dirty_queue_limit, 0);
448+
exit(7);
449+
}
450+
439451
if (strnlen(configs_dest[renderd_section_num].socketname, PATH_MAX) >= renderd_socketname_maxlen) {
440452
g_logger(G_LOG_LEVEL_CRITICAL, "Specified socketname (%s) exceeds maximum allowed length of %i.", configs_dest[renderd_section_num].socketname, renderd_socketname_maxlen);
441453
exit(7);
@@ -510,6 +522,8 @@ void process_config_file(const char *config_file_name, int active_renderd_sectio
510522
}
511523

512524
g_logger(G_LOG_LEVEL_DEBUG, "\trenderd(%i): num_threads = '%i'", i, config_slaves[i].num_threads);
525+
g_logger(G_LOG_LEVEL_DEBUG, "\trenderd(%i): request_queue_limit = '%i'", i, config_slaves[i].request_queue_limit);
526+
g_logger(G_LOG_LEVEL_DEBUG, "\trenderd(%i): dirty_queue_limit = '%i'", i, config_slaves[i].dirty_queue_limit);
513527
g_logger(G_LOG_LEVEL_DEBUG, "\trenderd(%i): pid_file = '%s'", i, config_slaves[i].pid_filename);
514528

515529
if (strnlen(config_slaves[i].stats_filename, PATH_MAX)) {
@@ -526,6 +540,8 @@ void process_config_file(const char *config_file_name, int active_renderd_sectio
526540
}
527541

528542
g_logger(log_level, "\trenderd: num_threads = '%i'", config.num_threads);
543+
g_logger(log_level, "\trenderd: request_queue_limit = '%i'", config.request_queue_limit);
544+
g_logger(log_level, "\trenderd: dirty_queue_limit = '%i'", config.dirty_queue_limit);
529545

530546
if (active_renderd_section_num == 0 && num_slave_threads > 0) {
531547
g_logger(log_level, "\trenderd: num_slave_threads = '%i'", num_slave_threads);

0 commit comments

Comments
 (0)