# 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. Any representation change follows the published 1.x migration and deprecation policy. The release contract records the complete GUC name set and a normalized hash of the Rust registration source. PostgreSQL integration tests verify documented defaults, ranges, contexts, and invalid-value behavior against `pg_settings`. ## 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.allow_rls_tables` | bool | `false` | `USERSET` | `on`, `off` | `graph.build()` row-level security acknowledgment | | `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`, registered-edge-row relationship `CREATE`, 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.query_memory_mb` | int | `64` | `USERSET` | `1` to `32768` | Per-statement graph query workspace cap | | `graph.maintenance_memory_mb` | int | `256` | `SUSET` | `1` to `32768` | Per-operation sync, compaction, and analytics workspace cap | | `graph.spill_disk_limit_mb` | int | `4096` | `SUSET` | `1` to `1048576` | Per-operation staging and spill-disk cap | | `graph.query_work_limit` | int | `10000000` | `USERSET` | `1` to `2000000000` | Expansion and blocking-operator work cap | | `graph.operation_timeout_ms` | int | `0` | `USERSET` | `0` to `86400000` | Graph-operation elapsed breaker; `0` uses PostgreSQL cancellation only | | `graph.build_batch_size` | int | `10000` | `SUSET` | `1` to `1000000` | Maximum SPI cursor/spool rows; byte pressure can flush a batch earlier | | `graph.low_memory_build` | bool | `false` | `SUSET` | `on`, `off` | Allow rebuilds to unload the current backend-local graph before constructing the replacement | | `graph.oom_action` | string | `error` | `SUSET` | `error`; deprecated aliases `readonly`, `read_only`, `read-only` | Compatibility setting; every over-budget build is rejected before allocation | | `graph.max_loaded_graphs_per_backend` | int | `1` | `SUSET` | `0` to `1` | Runtime loaded-graph slot cap for each PostgreSQL backend | | `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.projection_retention_generations` | int | `2` | `USERSET` | `1` to `1000` | Minimum valid projection manifest generations retained by GC | `graph.build()` estimates memory in checked integer bytes from registered tables and edges before constructing the active engine. Missing or zero table statistics trigger an exact row-count preflight. Every over-budget build raises SQLSTATE `53200` with diagnostic `PG001` before engine allocation. The `readonly`, `read_only`, and `read-only` values remain readable for 1.x configuration compatibility but are deprecated and have the same safe over-budget rejection behavior as `error`. The runtime build budget subtracts current backend-local serving residency and a safety reserve of one sixteenth of the configured limit, capped at 64 MiB. Node, filter, resolution, relationship, CSR, and persistence phases reserve against the remainder. Adaptive spool batches use one sixteenth of the remainder, with a 64 KiB minimum and 8 MiB maximum when that much memory is available. Set `graph.low_memory_build = on` when rebuilds fail only because the current backend already has a graph loaded. In that mode, `graph.build()` may unload the current backend-local graph before constructing the replacement. This lowers peak memory for rebuilds at the cost of losing the old loaded graph in the building backend if the build fails. Other PostgreSQL backends are unaffected. It does not make a final graph that exceeds `graph.memory_limit_mb` fit; reduce the graph size or raise the limit for that case. `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 includes each backend's immutable artifact snapshot in private memory, then projects an instance total for the supplied backend count. The shared-memory columns remain present for 1.x compatibility and currently return zero. Load, query, sync, compaction, and analytics operations also reserve their workspaces before growing large graph-owned structures. Their effective memory allowance is the smaller of the operation-specific setting and the remaining `graph.memory_limit_mb` after the selected graph's backend-private residency. Queries count candidate scans, neighbor expansions, path work, hydration, and blocking projection work against `graph.query_work_limit`. Maintenance paths also enforce row and disk budgets derived from their public operation inputs. When a breaker is reached, pgGraph cancels the operation with SQLSTATE `54000` and diagnostic `PG007`. PostgreSQL statement cancellation and `statement_timeout` remain authoritative; `graph.operation_timeout_ms` adds an optional graph-local elapsed limit. The failed operation does not publish a partially validated graph or projection generation. ```sql SET graph.query_memory_mb = 128; SET graph.query_work_limit = 20000000; SET graph.operation_timeout_ms = 5000; SELECT * FROM graph.resource_status(); ``` `graph.resource_status()` reports the most recent governed operation in the current PostgreSQL backend. It is diagnostic telemetry, not cluster-wide accounting. `graph.max_loaded_graphs_per_backend` is also enforced per backend. The current runtime has one backend-local graph engine slot, so the supported range is `0` to `1`. Set it to `0` only when you want to block runtime graph loads on a server while still allowing metadata and build operations. Mapped GQL writes use transaction-local overlays. If a node, edge, or overlay memory limit would be exceeded, the current SQL statement aborts with SQLSTATE `54000` and diagnostic `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.hot_eager_load` | bool | `false` | `SUSET` | Attempts to load `hot` graphs immediately when `graph.select_graph()` selects them | | `graph.idle_unload_secs` | int | `0` | `SUSET` | Reserved idle-unload threshold; `0` disables idle unload | | `graph.data_dir` | string | `graph` | `SUSET` | Subdirectory under `$PGDATA` that contains one artifact root per graph id | `graph.auto_load` applies to `hot` and `warm` graphs. `cold` graphs keep their artifacts available for explicit `graph.load_graph()` calls but do not auto-load during query execution. `graph.hot_eager_load` is an opt-in selection policy for `hot` graphs that already have persisted artifacts. Default graph artifact root and compatibility path: ```text $PGDATA/graph/00000000-0000-0000-0000-000000000001/ main.pggraph logical compatibility path projection-current.json current-generation pointer projection-generation-.json immutable manifest projection-generation--base.pggraph immutable base artifact ``` When a valid current pointer and manifest exist, the manifest's base artifact takes precedence over `main.pggraph`. A store without projection metadata can still load `main.pggraph` as a compatibility fallback. Operators should use `graph.graph_runtime_status()` and `graph.projection_status()` for presence and health checks instead of inferring the serving file from directory names. ## 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.query_memory_mb = 64 graph.maintenance_memory_mb = 256 graph.spill_disk_limit_mb = 4096 graph.query_work_limit = 10000000 graph.operation_timeout_ms = 0 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.projection_retention_generations = 2 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`.