Skip to main content

GraphQL and General-Purpose Indexer

The GraphQL and sui-indexer-alt (Indexer) stack are part of the Sui data access infrastructure. The stack provides access to onchain data through a high-performance GraphQL service backed by a scalable and general-purpose indexer. This stack is optimized for developers who need flexible queries, structured output, and historical access to data (with configurable retention) across the Sui network.

GraphQL is ideal for applications that require rich query patterns over structured data, such as fetching owned objects, transaction history, specific onchain attributes, and more. The GraphQL service runs on top of a Postgres-compatible database that is updated by different Indexer pipelines in parallel.

The General-purpose Indexer includes configurable checkpoint-based pipelines that extract data from the Sui remote checkpoint store and full nodes. The pipelines write processed data to a database optimized for GraphQL query access.

Together, the GraphQL service and Indexer offer a modular and production-ready data stack for builders, wallet developers, explorers, and indexer/data providers.

info

JSON-RPC is deprecated. Migrate to either gRPC or GraphQL RPC before the week of July 27, 2026, when JSON-RPC is disabled on Sui Foundation mainnet full nodes. Full decommission, including code removal, is planned for mid-October 2026. For a method mapping, decision criteria, and the full timeline, see the JSON-RPC Migration Guide.

Refer to the list of RPC or data providers that have enabled gRPC on their full nodes or offer GraphQL RPC. Contact a provider directly to request access. If your RPC or data provider doesn’t yet support these data access methods, ask them to enable support or contact the Sui Foundation team on Discord, Telegram, or Slack for help.

info

Refer to Access Sui Data for an overview of options to access Sui network data.

See Sui Data Access for more information on how the stack serves application queries.

Production fullnode endpoints

The https://fullnode.mainnet.sui.io:443, https://fullnode.testnet.sui.io:443, and https://fullnode.devnet.sui.io:443 endpoints are public-good services for testing only. They are not intended for a production indexer or GraphQL stack. For every fullnode URL in this guide, use a production-grade gRPC endpoint from an RPC provider with sufficient capacity and retention for your workload.

Requester Pays checkpoint buckets

The full-retention Mainnet and Testnet Google Cloud Storage (GCS) checkpoint buckets are Requester Pays enabled. Before running a production backfill or recovery, configure Google credentials that can charge an enabled billing project:

$ export GOOGLE_APPLICATION_CREDENTIALS=/path/to/gcp-credentials.json
$ export GOOGLE_CLOUD_PROJECT=your-billing-project

Workload Identity or another supported Google credential source can replace the credential file. Every --remote-store-gcs command must also include the x-goog-user-project header. See Google Cloud's Use Requester Pays documentation for billing and IAM requirements.

Indexer setup

The indexer consists of multiple pipelines that each read, transform, and write checkpoint data to various Postgres tables. Multiple instances of the indexer can run in parallel, each configured by its own TOML file.

Hardware requirements

  • CPU: 2 cores per instance
  • Memory: 4GB per instance

Internally at Mysten Labs, indexer instances use 8 CPUs and 16 GiB of memory for backfill, and scale down to 0.5 CPU and 1 GiB of memory for normal operation at network tip.

Storage requirements

The general-purpose indexer writes to a Postgres database. The storage footprint estimations outlined below are based on the network as of early 2026, and might fluctuate in relation to network growth. These numbers should be seen as directional rather than exact figures.

The bulk of the storage is consumed by obj_versions at 8.2 TB. A pruning strategy is in development.

The unpruned tx_balance_changes table adds another 1.2 TB.

A 30-day retention adds 1.8 TB on top, while a 90-day retention contributes up to an additional 3.1 TB.

30-day retention:

TableHeap (GB)Idx (GB)
tx_affected_objects64–70276–397
tx_calls27–30239–366
ev_struct_inst15–19174–267
tx_affected_addresses17–1863–92
ev_emit_mod8–954–85
tx_digests821–25
tx_kinds514–16
cp_sequence_numbers105
TOTAL426–4301,822–1,836

90-day retention:

TableHeap (GB)Idx (GB)
tx_affected_objects188–202580–752
tx_calls82–87560–715
ev_struct_inst45–50432–531
tx_affected_addresses50–54151–183
ev_emit_mod22–24129–167
tx_digests24–2645–55
tx_kinds15–1630–36
cp_sequence_numbers105
TOTAL698–7792,430–3,165

Run sui-indexer-alt

Run production ingestion in 2 phases: backfill from the full-retention Requester Pays GCS bucket, then stop the backfill process and restart from the saved watermarks with fullnode gRPC for steady state. Run 1 indexer process for each configuration file and assign every co-located process a unique metrics address.

Phase 1: backfill

Unprunable pipelines must start at genesis:

$ sui-indexer-alt indexer \
--config <CONFIG_FILE> \
--database-url <DATABASE_URL> \
--remote-store-gcs <REMOTE_STORE_GCS_BUCKET> \
--remote-store-header "x-goog-user-project:${GOOGLE_CLOUD_PROJECT}" \
--metrics-address <UNIQUE_METRICS_ADDRESS>

Prunable pipelines can start at a checkpoint based on their retention period:

$ sui-indexer-alt indexer \
--config <CONFIG_FILE> \
--database-url <DATABASE_URL> \
--remote-store-gcs <REMOTE_STORE_GCS_BUCKET> \
--remote-store-header "x-goog-user-project:${GOOGLE_CLOUD_PROJECT}" \
--first-checkpoint <CHECKPOINT_NUMBER> \
--metrics-address <UNIQUE_METRICS_ADDRESS>

For prunable pipelines, calculate the first-checkpoint based on your retention period:

  • 30-day retention: Start from checkpoint current_checkpoint - 10368000
  • 90-day retention: Start from checkpoint current_checkpoint - 31104000
CLI parameterDescription
<CONFIG_FILE>Path to indexer configuration file.
<DATABASE_URL>Postgres database connection string.
<REMOTE_STORE_GCS_BUCKET>Full-retention GCS bucket name. Use mysten-mainnet-checkpoints-use4 for Mainnet or mysten-testnet-checkpoints-use4 for Testnet.
<CHECKPOINT_NUMBER>For prunable pipelines only, the checkpoint at which indexing starts.
<UNIQUE_METRICS_ADDRESS>Prometheus bind address unique to this process, for example 127.0.0.1:9184.

Examples

Unprunable pipeline from genesis:

$ sui-indexer-alt indexer \
--config unpruned.toml \
--database-url postgres://username:password@localhost:5432/database \
--remote-store-gcs mysten-mainnet-checkpoints-use4 \
--remote-store-header "x-goog-user-project:${GOOGLE_CLOUD_PROJECT}" \
--metrics-address 127.0.0.1:9184

Prunable pipeline with 30-day retention (assuming current checkpoint is 100,000,000):

$ sui-indexer-alt indexer \
--config events.toml \
--database-url postgres://username:password@localhost:5432/database \
--remote-store-gcs mysten-mainnet-checkpoints-use4 \
--remote-store-header "x-goog-user-project:${GOOGLE_CLOUD_PROJECT}" \
--first-checkpoint 89632000 \
--metrics-address 127.0.0.1:9187

Phase 2: steady state

After a backfill process reaches the network tip, stop it and restart the same configuration against a production-grade fullnode gRPC endpoint from an RPC provider. Existing pipelines resume from their committed watermarks. Use --rpc-api-url for polling and --streaming-url for low-latency checkpoint streaming:

$ export FULLNODE_GRPC_URL=https://your-rpc-provider.example:443

$ sui-indexer-alt indexer \
--config events.toml \
--database-url postgres://username:password@localhost:5432/database \
--rpc-api-url "$FULLNODE_GRPC_URL" \
--streaming-url "$FULLNODE_GRPC_URL" \
--metrics-address 127.0.0.1:9187

If recovery requires checkpoints outside the fullnode's retention, stop the process, recover from the GCS bucket, and then switch back to fullnode gRPC.

info

For checkpoint ingestion, the public https://checkpoints.mainnet.sui.io and https://checkpoints.testnet.sui.io stores are for testing only. Do not use them for production backfill, recovery, fallback, or steady state. See Checkpoint Data Sources for the source matrix.

Run config recommendations

Use the TOML files below; they are grouped by pipeline speed. All pipelines in an instance are limited by the slowest pipeline in that instance so these files each contain pipelines that run at approximately the same speed.

info

The example configurations linked below use a fixed concurrency of 20 for simplicity, but this leads to longer backfill times. When backfilling a pipeline, consider setting the ingest-concurrency to a higher initial concurrency, such as { kind = "adaptive", initial = 200, min = 50, max = 2000 }. The indexer automatically adjusts concurrency based on current load and performance. Once caught up to network tip, you can manually set the max concurrency to a lower value. Mysten-internal indexers are capped at 20.

Config TOMLTypeDescriptionPipelinesBackfill timeData retentionStart checkpoint
events.tomlPrunableLightweight event tables.
  • ev_emit_mod
  • ev_struct_inst
1-2 daysConfigurable (for example, 30 or 90 days)Based on retention period
Click to open
events.toml
[pruner]
retention = 31104000

[pipeline.ev_emit_mod.pruner]

[pipeline.ev_struct_inst.pruner]
obj_versions.tomlUnprunableObject versions table containing complete object version to checkpoint mappings.obj_versions10-14 daysMust retain all dataGenesis (checkpoint 0)
Click to open
obj_versions.toml
[ingestion]
ingest-concurrency = { kind = "fixed", value = 20 }

[committer]
write-concurrency = 10

# obj_versions pipeline pruning not yet supported

[pipeline.obj_versions]
tx_affected_addresses.tomlPrunableMidweight transaction table.tx_affected_addresses1-2 daysConfigurable (for example, 30 or 90 days)Based on retention period
Click to open
tx_affected_addresses.toml
[ingestion]
ingest-concurrency = { kind = "fixed", value = 20 }

[committer]
write-concurrency = 10

[pruner]
retention = 31104000

[pipeline.tx_affected_addresses.pruner]
tx_affected_objects.tomlPrunableMidweight transaction table.tx_affected_objects1-2 daysConfigurable (for example, 30 or 90 days)Based on retention period
Click to open
tx_affected_objects.toml
[committer]
write-concurrency = 20

[pruner]
retention = 31104000

[pipeline.tx_affected_objects.pruner]
tx_calls.tomlPrunableMidweight transaction table.tx_calls1-2 daysConfigurable (for example, 30 or 90 days)Based on retention period
Click to open
tx_calls.toml
[committer]
write-concurrency = 10

[pruner]
retention = 31104000

[pipeline.tx_calls.pruner]
tx_kinds.tomlPrunableMidweight transaction table.tx_kinds1-2 daysConfigurable (for example, 30 or 90 days)Based on retention period
Click to open
tx_kinds.toml
[pruner]
retention = 31104000

[pipeline.tx_kinds.pruner]
unpruned.tomlUnprunableFoundational reference data that other queries depend on.
  • cp_sequence_numbers
  • kv_epoch_ends
  • kv_epoch_starts
  • kv_feature_flags
  • kv_packages
  • kv_protocol_configs
  • sum_displays
  • tx_balance_changes
  • tx_digests
2-4 daysMust retain all dataGenesis (checkpoint 0)
Click to open
unpruned.toml
[ingestion]
ingest-concurrency = { kind = "fixed", value = 20 }

[pipeline.cp_sequence_numbers]

[pipeline.kv_epoch_ends]

[pipeline.kv_epoch_starts]

[pipeline.kv_feature_flags]

[pipeline.kv_packages]

[pipeline.kv_protocol_configs]

[pipeline.sum_displays]

[pipeline.tx_balance_changes]

[pipeline.tx_digests]

Consistent store setup

The consistent store maintains live ownership and balance indexes in its own RocksDB database and exposes checkpoint-consistent reads over gRPC. It does not use the Postgres DATABASE_URL and does not replace the General-purpose Indexer. Keep its database path on persistent local SSD storage.

All 4 pipelines run in 1 process. Their watermarks are independent from the Postgres indexer's watermarks, and throughput is limited by the slowest consistent-store pipeline.

Hardware requirements

  • CPU: 8 cores
  • Memory: 32GB

Restore from a formal snapshot

Without a restore, a new consistent store must process checkpoints from genesis. For production, restore the latest formal snapshot into an empty RocksDB path before starting the service.

The restore command reads from two distinct sources:

  1. Exactly 1 formal snapshot source, such as --http, --gcs, --azure, --s3, or --local.
  2. A required --remote-store-url. The restorer uses this store to map the snapshot epoch to its ending checkpoint and fetch that checkpoint; it is not the post-restore catch-up source.

Mainnet and Testnet restores can use https://checkpoints.mainnet.sui.io or https://checkpoints.testnet.sui.io for --remote-store-url, respectively.

$ export CONSISTENT_STORE_PATH=/path/to/consistent-store
$ mkdir -p "$(dirname "$CONSISTENT_STORE_PATH")"

$ sui-indexer-alt-consistent-store restore \
--database-path "$CONSISTENT_STORE_PATH" \
--http https://formal-snapshot.mainnet.sui.io \
--remote-store-url https://checkpoints.mainnet.sui.io \
--object-file-concurrency 5 \
--pipeline address_balances \
--pipeline balances \
--pipeline object_by_owner \
--pipeline object_by_type \
--config consistent-store.toml \
--metrics-address 127.0.0.1:9186
CLI parameterDescription
--database-pathEmpty path where the RocksDB database is created.
--http, --gcs, --azure, --s3, or --localFormal snapshot source. Specify exactly one. See managed snapshots.
--remote-store-urlPublic HTTPS checkpoint store used to read the epoch mapping and selected end-of-epoch checkpoint during restore.
--object-file-concurrencyNumber of formal-snapshot object files to download concurrently.
--pipelineRuntime pipeline name to restore. Repeat once for each required pipeline.
--configConsistent-store TOML configuration. Only its RocksDB settings are used during restore.

Each --pipeline is required because restore materializes only explicitly selected pipelines. Let the command exit successfully before starting run.

Use the latest formal snapshot so the public HTTPS checkpoint store still retains its end-of-epoch checkpoint. If that checkpoint is no longer available, restore cannot complete and you must choose a newer formal snapshot.

Run and catch up with fullnode gRPC

After restore completes, start the combined consistent-store indexer and gRPC service. The polling client catches up after the restored watermark, and streaming supplies new checkpoints at the network tip. Use a production-grade fullnode endpoint from an RPC provider:

$ export FULLNODE_GRPC_URL=https://your-rpc-provider.example:443

$ sui-indexer-alt-consistent-store run \
--database-path "$CONSISTENT_STORE_PATH" \
--rpc-api-url "$FULLNODE_GRPC_URL" \
--streaming-url "$FULLNODE_GRPC_URL" \
--rpc-listen-address 127.0.0.1:7001 \
--metrics-address 127.0.0.1:9186 \
--config consistent-store.toml

Wait for the service to report SERVING and for every pipeline watermark to approach the network tip before routing GraphQL traffic to it. Only 1 process can open a given RocksDB path. Back up the RocksDB path separately from Postgres, and stop the consistent-store process before taking a filesystem-level copy.

If a successfully restored watermark is older than the fullnode's retention window, use GCS for the catch-up phase:

$ sui-indexer-alt-consistent-store run \
--database-path "$CONSISTENT_STORE_PATH" \
--remote-store-gcs mysten-mainnet-checkpoints-use4 \
--remote-store-header "x-goog-user-project:${GOOGLE_CLOUD_PROJECT}" \
--rpc-listen-address 127.0.0.1:7001 \
--metrics-address 127.0.0.1:9186 \
--config consistent-store.toml

Do not route traffic to this temporary catch-up process. After it reaches the network tip, stop it and restart with fullnode gRPC.

To build a consistent store without a formal snapshot, run from an empty path against the full-retention GCS bucket and process from checkpoint 0. Do not set --first-checkpoint to a later checkpoint, because doing so produces incomplete ownership and balance state.

For Testnet, use https://formal-snapshot.testnet.sui.io, https://checkpoints.testnet.sui.io, and a production-grade Testnet fullnode endpoint from an RPC provider. Use the mysten-testnet-checkpoints-use4 bucket when GCS catch-up is necessary. Keep every source on the same network and use a separate RocksDB path per network.

Run config recommendations

The CLI runtime pipeline names use underscores, while the corresponding TOML keys use hyphens:

Runtime pipeline (--pipeline)TOML keyDescription
address_balancesaddress-balancesAddress balance accumulator state.
balancesbalancesCoin balance state.
object_by_ownerobject-by-ownerLive objects indexed by owner.
object_by_typeobject-by-typeLive objects indexed by type.

A formal-snapshot restore of all pipelines typically takes 1–2 hours. All pipeline state must be retained.

Click to open
consistent-store.toml
[ingestion]
ingest-concurrency = { kind = "fixed", value = 20 }

[pipeline.address-balances]

[pipeline.balances]

[pipeline.object-by-owner]

[pipeline.object-by-type]

GraphQL RPC server setup

GraphQL RPC server reads data from the general-purpose indexer's database (Postgres), the consistent store, and the archival service.

  • Ensure that all unprunable indexer pipelines (obj_versions.toml and unpruned.toml) have fully caught up to the network tip before starting the GraphQL RPC server. The GraphQL service only operates normally once these pipelines are complete.

Hardware requirements

  • CPU: 2 cores per instance
  • Memory: 4GB per instance

Scale the number of nodes based on the desired read throughput requirements of your client applications.

GraphQL RPC server dependencies

The GraphQL RPC server relies on multiple backend services to fulfill different types of queries:

  • Archival service (--ledger-grpc-url) provides historical data for most queries involving checkpoints, objects, and transactions.
  • Consistent store (--consistent-store-url) serves live data for queries related to current object and balance ownership.
  • Postgres database (--database-url) is the primary store for most queries, except for direct object and transaction lookups handled by the Archival service.
  • Fullnode gRPC (--fullnode-rpc-url) powers transaction simulation and execution.

Set the appropriate service URLs in your run command based on the query types your GraphQL RPC server needs to support.

Run sui-indexer-alt-graphql

info

If you use the Sui Foundation-hosted public good archival service on Testnet or Mainnet, you might encounter performance issues. The team plans to address these before the GraphQL RPC and Archival Service reach general availability.

Use the following command to run a GraphQL RPC server node:

$ sui-indexer-alt-graphql rpc \
--config <PATH_TO_GRAPHQL_CONFIG_FILE> \
--indexer-config <PATH_TO_INDEXER_CONFIG_FILE_1> \
--indexer-config <PATH_TO_INDEXER_CONFIG_FILE_2> \
--indexer-config <PATH_TO_INDEXER_CONFIG_FILE_3> \
--ledger-grpc-url <LEDGER_GRPC_URL> \
--consistent-store-url <CONSISTENT_STORE_URL> \
--database-url <DATABASE_URL> \
--fullnode-rpc-url <FULLNODE_GRPC_URL> \
--rpc-listen-address <RPC_LISTEN_ADDRESS> \
--metrics-address <METRICS_ADDRESS>

Multiple --indexer-config parameters can be provided, one for each general-purpose indexer instance.

CLI parameterDescription
PATH_TO_GRAPHQL_CONFIG_FILEPath to the optional GraphQL RPC server configuration file.
PATH_TO_INDEXER_CONFIG_FILEPath to a General-purpose Indexer configuration file; repeat for each group of pipelines.
LEDGER_GRPC_URLArchival Service LedgerService gRPC URL.
CONSISTENT_STORE_URLConsistent store gRPC URL. Use http:// unless you explicitly configured its TLS listener.
DATABASE_URLPostgres database connection string.
FULLNODE_GRPC_URLProduction-grade fullnode gRPC URL from an RPC provider, used for transaction simulation and execution.
RPC_LISTEN_ADDRESSGraphQL bind address, for example 127.0.0.1:7000.
METRICS_ADDRESSPrometheus bind address unique to this process, for example 127.0.0.1:9185.

Example:

$ export FULLNODE_GRPC_URL=https://your-rpc-provider.example:443

$ sui-indexer-alt-graphql rpc \
--config graphql.toml \
--indexer-config events.toml \
--indexer-config obj_versions.toml \
--indexer-config tx_affected_addresses.toml \
--indexer-config tx_affected_objects.toml \
--indexer-config tx_calls.toml \
--indexer-config tx_kinds.toml \
--indexer-config unpruned.toml \
--ledger-grpc-url https://archive.mainnet.sui.io:443 \
--consistent-store-url http://127.0.0.1:7001 \
--database-url postgres://username:password@localhost:5432/database \
--fullnode-rpc-url "$FULLNODE_GRPC_URL" \
--rpc-listen-address 127.0.0.1:7000 \
--metrics-address 127.0.0.1:9185

With these addresses, GraphQL is available at http://127.0.0.1:7000/graphql, its health check is at http://127.0.0.1:7000/health, and Prometheus metrics are at http://127.0.0.1:9185/metrics. Assign a different metrics address to every co-located indexer process; all 3 binaries otherwise default to port 9184.

Generating configuration

You can run the GraphQL RPC server without a configuration file, which uses default values. To customize settings, generate a configuration file using the following command and edit it as needed:

$ sui-indexer-alt-graphql generate-config > <PATH_TO_GRAPHQL_CONFIG_FILE>

This produces output similar to the following:

Click to open
graphql.toml
[limits]
mutation-timeout-ms = 74000
query-timeout-ms = 40000
max-query-depth = 20
max-query-nodes = 300
max-output-nodes = 1000000
max-tx-payload-size = 174763
max-query-payload-size = 5000
default-page-size = 20
max-page-size = 50
max-multi-get-size = 200
page-size-override-fx-object-changes = 1024
page-size-override-packages = 200
max-type-argument-depth = 16
max-type-argument-width = 32
max-type-nodes = 256
max-move-value-depth = 128
max-move-value-bound = 1048576
max-display-field-depth = 10
max-display-output-size = 1048576
max-disassembled-module-size = 1048576

[health]
max-checkpoint-lag-ms = 300000

[name-service]
package-address = "0xd22b24490e0bae52676651b4f56660a5ff8022a2576e0089f79b3c88d44e08f0"
registry-id = "0xe64cd9db9f829c6cc405d9790bd71567ae07259855f4fba6f02c84f52298c106"
reverse-registry-id = "0x2fd099e17a292d2bc541df474f9fafa595653848cbabb2d7a4656ec786a1969f"

[watermark]
watermark-polling-interval-ms = 500

[zklogin]
env = "Prod"
max-epoch-upper-bound-delta = 30

Indexer/GraphQL Postgres-compatible database setup

Both the indexer and GraphQL server require a Postgres-compatible database shared between them.

These GraphQL request throughputs were tested against the following recommended specs:

  • 500 requests per second when the indexer is backfilling from genesis or running a restore
  • 1000 requests per second when the indexer is indexing from the network tip (~4.25 checkpoints per second)

AlloyDB Omni

AlloyDB Omni recommends 8GB RAM per vCPU. See the AlloyDB Omni hardware requirements for details. Allocating less than this results in the database closing indexer and GraphQL connections during load testing.

Hardware requirements

  • CPU: 6 cores
  • Memory: 48GB

Vanilla Postgres

You can also use a standard Postgres installation.

Hardware requirements

  • CPU: 6 cores
  • Memory: 48GB

Adding a new pipeline

Adding a new pipeline to an existing indexer currently requires these steps:

  1. Start a new pipeline in its own indexer instance, optionally with the --first-checkpoint <checkpoint> flag set if you want to start from a checkpoint after genesis.
    • The --first-checkpoint flag is only respected if no watermark record exists for the pipeline (the pipeline has not run before). You must manually remove the watermark record if you want to run the pipeline with a different value of --first-checkpoint, or it is ignored.
  2. After the new pipeline catches up to the tip of the network, you can optionally merge it into another indexer instance.

Re-indexing from a different checkpoint

Once the pipeline starts indexing data and the indexer starts recording watermarks, future runs of the pipeline ignore the --first-checkpoint flag and always resume from the last recorded checkpoint.

To restart and index from a different initial checkpoint, delete the watermark record for that pipeline. Delete the existing data for that pipeline as well to avoid any data inconsistencies.

Use the following template for the general-purpose indexer to write to Postgres.

BEGIN;

-- One statement per pipeline to re-index
TRUNCATE TABLE ev_emit_mod;


DELETE FROM watermarks
WHERE pipeline IN (
-- List all pipelines to re-index
'ev_emit_mod'
);

SELECT * FROM WATERMARKS; -- Verify that the watermark record(s) have been deleted

-- Verify that the tables have been cleared

COMMIT;

Reducing indexer pipeline table and index bloat

Bloat occurs when dead rows from deletes accumulate faster than autovacuum can reclaim them. This happens in two cases:

  • You enable pruning on a previously unpruned pipeline.
  • You reduce the retention period on an already-pruned pipeline.

Mysten Labs uses the following autovacuum settings for a 32-vCPU, 256 GiB Postgres instance. Adjust these proportionally for your setup:

ParameterValue
autovacuum_max_workers8
autovacuum_vacuum_cost_limit8000
autovacuum_vacuum_scale_factor0.01
maintenance_work_mem13GB
max_parallel_maintenance_workers4

While autovacuum enables reusing dead rows for future inserts, it does not return reclaimed disk space to the operating system. The on-disk footprint remains until you compact tables and indexes using one of the following tools:

ToolTypeACCESS EXCLUSIVE lockingSchedulableLink
VACUUM FULLBuilt-inEntire operationNoPostgreSQL VACUUM documentation
pg_repackExtensionBriefly during initial and final stepNopg_repack documentation
pg_squeezeExtensionBriefly during final stepYespg_squeeze on GitHub