# io_workers

> io_workers — Number of IO worker processes, for io_method=worker. Observed in PG18; its last measured boot default is 3 in PG18, with sighup context. It was removed in PG19 Beta 3.
---

> [!NOTE]
> **Fact — official short description:** “Number of IO worker processes, for io_method=worker.”

## Identity {#identity}

| Field | Value | Meaning |
| --- | --- | --- |
| Type | `integer` | Upstream pg_settings type |
| Context | `sighup` | Takes effect after configuration reload |
| Unit | — | Raw unit |
| Range | `1` – `32` | Raw limits in the last observed version |
| Enum values | — | — for non-enum types |
| Category | Resource Usage / I/O | Upstream classification |
| Latest boot value | `3` | 3 |
{.fields meta="-"}

## Lifecycle {#lifecycle}

| Fact | Value |
| --- | --- |
| First observed | PG18 |
| Present in | PG18 |
| Removed in | PG19 Beta 3 |
| Introduction commit | [`55b454d0e140`](https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=55b454d0e14084c841a034073abbf1a0ea937a45) — aio: Infrastructure for io_method=worker |
| Commit date | 2025-03-18 |
| Discussion | [thread 1](https://postgr.es/m/uvrtrknj4kdytuboidbhwclo4gxhswwcpgadptsjvjqcluzmah%40brqs62irg4dt) · [thread 2](https://postgr.es/m/20210223100344.llw5an2aklengrmn@alap3.anarazel.de) · [thread 3](https://postgr.es/m/stj36ea6yyhoxtqkhpieia2z4krnam7qyetc57rfezgk4zgapf@gcnactj4z56m) |

## Default history {#default-history}

| Versions | Raw `boot_val` | Unit | Human value |
| --- | --- | --- | --- |
| PG18 | `3` | — | 3 |
{.full-width caption="Measured PG9.0–19 Beta 3 boot defaults"}

## How it works {#mechanism}

io_workers sets the number of dedicated I/O worker processes used by PostgreSQL 18 when io_method=worker. It has no effect for io_uring or sync.

I/O workers execute asynchronous requests on behalf of database processes; this is an execution pool, distinct from parallel query workers and background worker slots.

Changing the count is reloadable, but useful capacity still depends on io_max_concurrency, workload queue depth, storage latency, and CPU. More workers do not guarantee more device throughput. Its SIGHUP context allows configuration reload without a server restart.

## Tuning advice {#tuning-advice}

> [!TIP]
> **Advice.** These are workload-specific starting points and must be validated with measurements.

| Workload | Guidance |
| --- | --- |
| OLTP | Tune io_workers only when io_method=worker. Start from the PG18 default 3, measure worker saturation, CPU use, and tail latency, and increase the pool only when queued eligible I/O waits for worker execution rather than for the device. |
| OLAP | Sustained eligible scans can benefit from enough I/O workers to feed storage, but extra processes add scheduling overhead and cannot exceed the per-process or device concurrency bottleneck. Compare throughput and worker utilization; request size is controlled elsewhere. |
| Small nodes | Keep 3 or a measured smaller value when process and CPU budgets are tight. io_workers has no effect under io_uring or sync, so never spend tuning effort on it until io_method is confirmed as worker. |
{.full-width}

## Pigsty {#pigsty}

Values use the fixed 8-vCPU, 32-GiB, 100-GiB SSD fixture and render the current Pigsty templates for PG18; this does not assert current Pigsty support for that historical or beta release.

| Template | Effective value | Versus upstream boot | Source expression |
| --- | --- | --- | --- |
| OLTP | `4` | different | `{{ pg_io_workers }}` |
| OLAP | `4` | different | `{{ pg_io_workers }}` |
| CRIT | `4` | different | `{{ pg_io_workers }}` |
| TINY | `3` | same as boot | `{{ pg_io_workers }}` |
{.full-width}

> [!CAUTION]
> **Advice — pending human review.** Fact from the current Pigsty template projection: OLTP: PG18 = 4 (dcs); OLAP: PG18 = 4 (dcs); CRIT: PG18 = 4 (dcs); TINY: PG18 = 3 (dcs). Advice, pending human review — Editorial inference, pending maintainer review: The resolved worker pool gives PG18's worker AIO method more executors on non-tiny profiles while keeping the tiny profile at the upstream boot count.

## Common pitfalls {#common-pitfalls}

- Changing io_workers while io_method is io_uring or sync, where it has no effect.
- Treating worker-process count as bytes per request or as the per-process I/O ceiling.
- Adding workers when the storage device, not the worker pool, is already saturated.
- Forgetting that the reloadable pool still consumes process slots, CPU, and scheduling capacity.

## Related parameters {#related-parameters}

[`io_method`](/parameters/io-method/) · [`io_max_concurrency`](/parameters/io-max-concurrency/) · [`effective_io_concurrency`](/parameters/effective-io-concurrency/) · [`maintenance_io_concurrency`](/parameters/maintenance-io-concurrency/) · [`max_worker_processes`](/parameters/max-worker-processes/)

## References {#references}

- [PostgreSQL 18 — io_workers](https://www.postgresql.org/docs/18/runtime-config-resource.html#GUC-IO-WORKERS)
- [Machine-readable GUC export](/data/guc.jsonl)
