# Configuration
All extension settings are PostgreSQL GUCs with the `graph.` prefix. They are
registered in `_PG_init()` by `graph/src/config.rs`.
String-valued settings such as `graph.oom_action`, `graph.sync_mode`,
`graph.query_freshness`, and `graph.build_scan_mode` are parsed into typed Rust
enums at the extension boundary. They remain string GUCs for compatibility with
existing configuration files, aliases such as `readonly`/`read_only`, and
current warning or reserved-mode behavior. A future PostgreSQL enum-GUC change
would require an explicit migration plan for those aliases and error messages.
## Setting Values
Use session-local settings for experiments:
```sql
SET graph.default_max_depth = 4;
SET graph.max_nodes = 50000;
```
Use database or role settings for application defaults:
```sql
ALTER DATABASE mydb SET graph.default_search_mode = 'exact';
ALTER ROLE app_user SET graph.default_hydrate = false;
```
Use `ALTER SYSTEM` or `postgresql.conf` for `SUSET` operational settings:
```sql
ALTER SYSTEM SET graph.memory_limit_mb = 4096;
SELECT pg_reload_conf();
```
## Query Defaults
| GUC | Type | Default | Context | Range or values | Used by |
|---|---:|---:|---|---|---|
| `graph.enabled` | bool | `true` | `USERSET` | `on`, `off` | Query kill switch |
| `graph.default_max_depth` | int | `5` | `USERSET` | `1` to `100` | `graph.traverse()`, workflow wrappers |
| `graph.default_search_mode` | string | `contains` | `USERSET` | `contains`, `exact`, `prefix`, `token` | `graph.traverse_search()`, workflow wrappers |
| `graph.default_case_sensitive` | bool | `false` | `USERSET` | `on`, `off` | Search wrappers |
| `graph.default_hydrate` | bool | `true` | `USERSET` | `on`, `off` | `graph.traverse_search()` hydrate default |
| `graph.query_freshness` | string | `apply_pending_sync` | `USERSET` | `apply_pending_sync`, `off`, `error_on_pending` | Topology-read pending sync policy |
| `graph.max_nodes` | int | `100000` | `USERSET` | `1` to `10000000` | Traversal circuit breaker |
| `graph.max_frontier` | int | `100000` | `USERSET` | `1` to `10000000` | BFS/DFS frontier circuit breaker |
| `graph.max_exact_path_count` | int | `100000` | `USERSET` | `1` to `10000000` | `graph.path_count_estimate()`, `graph.aggregate()` |
| `graph.enforce_tenant_scope` | bool | `true` | `USERSET` | `on`, `off` | Tenanted graph queries |
| `graph.tenant_setting` | string | empty | `USERSET` | Any session GUC name | Tenant fallback |
| `graph.build_scan_mode` | string | `select` | `USERSET` | `select`; `copy` is reserved | Build scanner |
| `graph.default_projection_mode` | string | `csr_readonly` | `USERSET` | `csr_readonly`, `mutable_overlay` | Default `graph.build()` projection mode |
| `graph.mutable_enabled` | bool | `false` | `USERSET` | `on`, `off` | Allows `mutable_overlay` builds |
`graph.build_scan_mode = 'copy'` is present as a reserved mode, but the current
code returns an error because safe server-side COPY hooks are not available
through the pgrx path used here. Use `select`.
`graph.default_projection_mode = 'csr_readonly'` preserves the immutable
read-mostly engine. `mutable_overlay` is an opt-in build mode for
transaction-local GQL writes such as single-node mapped `CREATE` and mapped
property `SET`/`REMOVE`, mapped edge-row `DELETE`, and mapped node
`DETACH DELETE`/`MERGE`; selecting it requires `graph.mutable_enabled = on`.
## Memory And Build Settings
| GUC | Type | Default | Context | Range or values | Effect |
|---|---:|---:|---|---|---|
| `graph.memory_limit_mb` | int | `2048` | `SUSET` | `64` to `32768` | Pre-build OOM guard and status limit |
| `graph.build_batch_size` | int | `10000` | `SUSET` | `1` to `1000000` | SPI cursor fetch size and spool batch size |
| `graph.oom_action` | string | `error` | `SUSET` | `error`, `readonly`, `read_only`, `read-only` | Behavior when estimated build memory exceeds the limit |
| `graph.max_tx_delta_nodes` | int | `100000` | `USERSET` | `0` to `10000000` | Maximum transaction-local node deltas before a mapped GQL write aborts |
| `graph.max_tx_delta_edges` | int | `100000` | `USERSET` | `0` to `10000000` | Maximum transaction-local edge deltas before a mapped GQL write aborts |
| `graph.max_overlay_memory_mb` | int | `256` | `USERSET` | `1` to `32768` | Maximum estimated transaction-overlay heap before a mapped GQL write aborts |
| `graph.compaction_threshold` | int | `50000` | `USERSET` | `1` to `10000000` | Delta or tombstone count at which status surfaces recommend compaction |
`graph.build()` estimates memory from registered tables and edges before
constructing the active engine. With `graph.oom_action = 'error'`, it raises `PG001`.
With `graph.oom_action = 'readonly'`, it logs a warning and marks the built
engine read-only.
`graph.memory_limit_mb` is enforced per PostgreSQL backend. To size a database
where multiple sessions may load or build the graph, build or load the graph in
one representative backend and run:
```sql
SELECT *
FROM graph.memory_profile(concurrent_backends := 8);
```
The result separates backend-private heap from mmap-backed bytes that are
physically shareable through the OS page cache, then projects an instance total
for the supplied backend count.
Mapped GQL writes use transaction-local overlays. If a node, edge, or overlay
memory limit would be exceeded, the current SQL statement aborts with `PG019`
and PostgreSQL rolls back the source-table DML for that statement.
```sql
ALTER SYSTEM SET graph.memory_limit_mb = 8192;
ALTER SYSTEM SET graph.oom_action = 'error';
SELECT pg_reload_conf();
```
## Persistence Settings
| GUC | Type | Default | Context | Effect |
|---|---:|---:|---|---|
| `graph.persist_on_build` | bool | `true` | `USERSET` | Writes the `.pggraph` artifact after successful build/vacuum/maintenance paths that request persistence |
| `graph.auto_load` | bool | `true` | `SUSET` | Loads an existing artifact on first query in an empty backend |
| `graph.data_dir` | string | `graph` | `SUSET` | Subdirectory under `$PGDATA` for `main.pggraph` and `main.pggraph.sync` |
Default artifact path:
```text
$PGDATA/graph/main.pggraph
$PGDATA/graph/main.pggraph.sync
```
## Sync Settings
| GUC | Type | Default | Context | Range or values | Effect |
|---|---:|---:|---|---|---|
| `graph.sync_mode` | string | `trigger` | `SUSET` | `manual`, `trigger`, `wal` | Sync strategy selector |
| `graph.edge_buffer_size` | int | `100000` | `SUSET` | `1000` to `10000000` | Maximum pending edge mutations before read-only mode |
| `graph.sync_batch_size` | int | `1000` | `SUSET` | `1` to `100000` | Maximum sync-log rows replayed in one internal batch |
| `graph.vacuum_interval_secs` | int | `60` | `SUSET` | `5` to `86400` | Reserved maintenance interval setting; not scheduled by current code |
`graph.sync_mode = 'wal'` is parsed but reserved. Current code rejects WAL mode
for active sync operations and asks callers to use `manual` or `trigger`.
## Recommended Starting Configuration
```conf
shared_preload_libraries = 'graph'
graph.memory_limit_mb = 4096
graph.persist_on_build = on
graph.auto_load = on
graph.build_batch_size = 10000
graph.sync_batch_size = 1000
graph.max_nodes = 100000
graph.max_frontier = 100000
graph.default_max_depth = 5
graph.default_search_mode = 'contains'
graph.default_hydrate = on
graph.default_projection_mode = 'csr_readonly'
graph.mutable_enabled = off
graph.query_freshness = 'apply_pending_sync'
graph.sync_mode = 'trigger'
```
Use lower traversal circuit breakers on multi-tenant OLTP primaries, and raise
them only for trusted administrative or analytical roles.
`graph.default_hydrate` currently affects `graph.traverse_search()` when its
`hydrate` argument is omitted. Primitive `graph.search()` and `graph.traverse()`
default their own `hydrate` arguments to `true`.