> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://docs.ampdb.io/api/mcp` to find what you need.

# ampsync

`ampsync` copies the tables of an Amp [dataset](/get-started/concepts/datasets) into PostgreSQL and keeps them up to
date, so you can read Amp's data with anything that speaks PostgreSQL: your app's database layer, a BI tool, or `psql`.
For each table, it runs a [streaming query](/querying/streaming-queries) and writes each batch of rows as Amp delivers
it. Once it has caught up, it keeps running and writes new blocks as Amp extracts them.

It writes each row exactly once, even when it's interrupted. Each time it finishes a range of blocks, it records a
*checkpoint* in PostgreSQL, so when it stops or crashes partway through a range, it deletes the rows it wrote since its
last checkpoint on its next start, and writes them again. When a [reorg](/get-started/concepts/reorgs) replaces
blocks it has already written, it deletes their rows and writes the new blocks'.

One `ampsync` process syncs one version of one dataset. To sync several datasets, run one process for each.

## Prerequisites

* A running Amp with a deployed dataset, like the one the [Quickstart](/get-started/quickstart) starts with
  [solo mode](/running-amp/solo-mode). The examples sync
  [`acme/eth_transfers@1.0.0`](/datasets/derived-datasets#the-erc-20-transfers-dataset), a derived dataset you
  create on top of the Quickstart's, from the query server at `localhost:1602`.

* A PostgreSQL database that `ampsync` can create tables in. Give it a database, or a schema, of its own (see
  [Share a database](/clis/ampsync#share-a-database)). To start one with Docker:

  ```bash
  docker run -d --name ampsync-db -p 5432:5432 -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=ampsync postgres:17
  ```

* The database's URL in `DATABASE_URL`, which `ampsync` reads:

  ```bash
  export DATABASE_URL=postgresql://postgres:postgres@localhost:5432/ampsync
  ```

## Install

[`ampup`](/clis/ampup) doesn't install `ampsync`. Download it from Amp's releases, build it from source, or run its
Docker image.

### Download a release

Each [release](https://github.com/edgeandnode/amp/releases) of Amp has an `ampsync` binary for Linux and macOS, on
x86_64 and aarch64: `ampsync-linux-x86_64`, `ampsync-linux-aarch64`, `ampsync-darwin-x86_64`, and
`ampsync-darwin-aarch64`. Download the one for your machine with the [GitHub CLI](https://cli.github.com), and put it on
your `PATH`, like in `~/.local/bin`:

```bash
gh release download v0.0.42 --repo edgeandnode/amp --pattern ampsync-darwin-aarch64
chmod +x ampsync-darwin-aarch64
mkdir -p ~/.local/bin
mv ampsync-darwin-aarch64 ~/.local/bin/ampsync
```

Use the release that matches your Amp's version (`ampd --version`).

### Build from source

You need Git, [Rust's toolchain](https://rustup.rs), a C compiler, and CMake. Amp's repository pins the Rust version
it builds with, which `rustup` installs on the first build. Clone the repository, check out the release you want, and
build `ampsync` alone:

```bash
git clone https://github.com/edgeandnode/amp.git
cd amp
git checkout v0.0.42
cargo build --release -p ampsync
```

A first build compiles every dependency, so it takes a while. The binary is `target/release/ampsync`; copy it to a
directory on your `PATH`:

```bash
cp target/release/ampsync ~/.local/bin/
```

### Docker image

Amp publishes an image, `ghcr.io/edgeandnode/ampsync`, tagged with each release (like `v0.0.42`), `latest` for the
latest release, and `nightly`. Its entry point is `ampsync`, so pass the command and set the options with
[environment variables](/clis/ampsync#environment-variables):

```bash
docker run --rm --stop-signal SIGINT \
  -e DATASET=acme/eth_transfers@1.0.0 \
  -e TABLES=transfers \
  -e DATABASE_URL=postgresql://postgres:postgres@host.docker.internal:5432/ampsync \
  -e AMP_FLIGHT_ADDR=http://host.docker.internal:1602 \
  ghcr.io/edgeandnode/ampsync:v0.0.42 sync
```

Inside the container, `localhost` is the container itself, so reach Amp and PostgreSQL on your machine through
`host.docker.internal`. Docker Desktop provides that name; on Linux, add `--add-host=host.docker.internal:host-gateway`.
`--stop-signal SIGINT` makes `docker stop` [stop it cleanly](/clis/ampsync#stop-and-restart).

### Check the version

`ampsync --version` prints the version of `ampsync`'s own package, not Amp's release: every release so far prints the
same.

:::terminal
```bash
ampsync --version
```

```
ampsync 0.1.0
```
:::

## Sync a dataset

`ampsync sync` is its only command. Give it the dataset, the tables to sync, and the database:

:::terminal
```bash
ampsync sync --dataset acme/eth_transfers@1.0.0 --tables transfers
```

```
2026-10-30T21:27:20.385126Z  INFO ampsync::commands::sync: Starting ampsync
2026-10-30T21:27:20.392619Z  INFO ampsync::commands::sync: Database connection established
2026-10-30T21:27:20.410812Z  INFO ampsync::commands::sync: State store migrations complete
2026-10-30T21:27:20.410841Z  INFO ampsync::commands::sync: Dataset: acme/eth_transfers@1.0.0
2026-10-30T21:27:20.410937Z  INFO ampsync::commands::sync: Tables to sync: 1 tables
2026-10-30T21:27:20.410946Z  INFO ampsync::commands::sync:   - transfers
…
2026-10-30T21:27:20.411271Z  INFO ampsync::commands::sync: Ampsync is running. Press Ctrl+C to stop.
…
2026-10-30T21:27:20.439422Z  INFO ampsync::task: table_created destination_table=transfers
2026-10-30T21:27:20.439462Z  INFO run{table=transfers dataset=acme/eth_transfers@1.0.0}: ampsync::task: starting_task
2026-10-30T21:27:20.458566Z  INFO run{…}:handle_event{…}: ampsync::task: data_event_processing tx_id=0 rows=8192
2026-10-30T21:27:20.624957Z  INFO run{…}:handle_event{…}: ampsync::task: data_event_processed tx_id=0 rows=8192
…
2026-10-30T21:27:22.011241Z  INFO run{…}:handle_event{…}: ampsync::task: data_event_processed tx_id=26 rows=4444
```
:::

It reads the database from `DATABASE_URL`, and connects to the query server at `http://localhost:1602` unless you set
[`--amp-flight-addr`](/clis/ampsync#options). Each `data_event_processed` line is a batch of rows written to PostgreSQL. This
dataset ends at block 24,000,099, so after its 110,969 transfers, `ampsync` waits for blocks that never come. With a
dataset that follows the chain, it keeps writing new blocks. Press Ctrl+C to stop it.

In PostgreSQL, the dataset's table is now a table of the same name, with the state tables `ampsync` keeps:

:::terminal
```bash
psql "$DATABASE_URL" -c '\dt'
```

```
                  List of relations
 Schema |           Name            | Type  |  Owner
--------+---------------------------+-------+----------
 public | _sqlx_migrations          | table | postgres
 public | amp_client_buffer_entries | table | postgres
 public | amp_client_state          | table | postgres
 public | transfers                 | table | postgres
(4 rows)
```
:::

:::terminal
```bash
psql "$DATABASE_URL" -c "SELECT _tx_id, _row_index, block_num, log_index, '0x' || encode(token, 'hex') AS token FROM transfers ORDER BY block_num, log_index LIMIT 3"
```

```
 _tx_id | _row_index | block_num | log_index |                   token
--------+------------+-----------+-----------+--------------------------------------------
      0 |          0 |  24000000 |         0 | 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2
      0 |          1 |  24000000 |         1 | 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48
      0 |          2 |  24000000 |         3 | 0xdac17f958d2ee523a2206206994597c13d831ec7
(3 rows)
```
:::

To sync several tables of the dataset, list them, separated by commas: `--tables blocks,transactions,logs`. Each table
syncs on its own, at its own pace.

## Tables it creates

### Your data

`ampsync` creates a table for each table it syncs, when it first starts, with the dataset table's columns and two of
its own in front:

| Column       | Type      | Description                                                                                                      |
| ------------ | --------- | ---------------------------------------------------------------------------------------------------------------- |
| `_tx_id`     | `BIGINT`  | The batch that wrote the row. `ampsync` numbers batches in order, and deletes rows by batch when it undoes work. |
| `_row_index` | `INTEGER` | The row's position in its batch, starting at 0.                                                                  |

Together, they're the table's primary key. They change when `ampsync` writes rows again after a restart or a reorg, so
don't use them to identify rows in your app. Here's the `transfers` table:

:::terminal
```bash
psql "$DATABASE_URL" -c '\d transfers'
```

```
                        Table "public.transfers"
   Column   |           Type           | Collation | Nullable | Default
------------+--------------------------+-----------+----------+---------
 _tx_id     | bigint                   |           | not null |
 _row_index | integer                  |           | not null |
 _block_num | numeric(20,0)            |           | not null |
 _ts        | timestamp with time zone |           | not null |
 block_num  | numeric(20,0)            |           | not null |
 timestamp  | timestamp with time zone |           | not null |
 tx_hash    | bytea                    |           | not null |
 log_index  | bigint                   |           | not null |
 token      | bytea                    |           | not null |
Indexes:
    "transfers_pkey" PRIMARY KEY, btree (_tx_id, _row_index)
```
:::

Each column gets the PostgreSQL type that holds all of its values:

| Dataset column type                        | PostgreSQL type                   |
| ------------------------------------------ | --------------------------------- |
| Unsigned 64-bit integer, like `block_num`  | `NUMERIC(20,0)`                   |
| Signed 64-bit and unsigned 32-bit integers | `BIGINT`                          |
| Signed 32-bit integer                      | `INTEGER`                         |
| Decimal, like `difficulty`                 | `NUMERIC` with the same precision |
| Binary, like hashes and addresses          | `BYTEA`                           |
| Timestamp with a time zone                 | `TIMESTAMPTZ`                     |
| String                                     | `TEXT`                            |
| Boolean                                    | `BOOLEAN`                         |
| List                                       | An array of its items' type       |
| Struct                                     | `JSONB`                           |

`ampsync` creates a table only when it doesn't exist, and never changes it afterwards.

### Table names

A table has the dataset table's name, unless you add to it with [`--table-version`](/clis/ampsync#options), which appends the
dataset's revision, and [`--table-suffix`](/clis/ampsync#options), which appends a suffix after that. They let you sync two versions
of a dataset into the same database, side by side:

```bash
ampsync sync --dataset acme/eth_mainnet@1.0.0 --tables blocks,transactions,logs --table-version
```

| Options                                | `transfers` from `acme/eth_transfers@1.0.0` becomes |
| -------------------------------------- | --------------------------------------------------- |
| none                                   | `transfers`                                         |
| `--table-version`                      | `transfers_v1_0_0`                                  |
| `--table-suffix green`                 | `transfers_green`                                   |
| `--table-version --table-suffix green` | `transfers_v1_0_0_green`                            |

The revision is used as you wrote it in `--dataset`, with every character other than letters and digits replaced by
`_`: with `acme/eth_transfers`, which means its latest version, the table is `transfers_vlatest`. Names may contain
only letters, digits, `_`, and `$`, must start with a letter or `_`, and can be at most 63 bytes long.

### State

`ampsync` keeps its progress in three tables of its own, which it creates in the same database:

| Table                       | What it holds                                                                                                                                         |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `amp_client_state`          | One row per synced table, its *stream*, named after the dataset and the PostgreSQL table, with the next batch number to use.                          |
| `amp_client_buffer_entries` | The stream's recent checkpoints: the block ranges, with their hashes, that it finished writing, for the last [`--retention-blocks`](/clis/ampsync#options) blocks. |
| `_sqlx_migrations`          | Which versions of the two tables above exist, so `ampsync` can upgrade them.                                                                          |

:::terminal
```bash
psql "$DATABASE_URL" -c 'SELECT * FROM amp_client_state'
```

```
             stream_id              | next_transaction_id |          updated_at
------------------------------------+---------------------+-------------------------------
 acme/eth_transfers@1.0.0:transfers |                  28 | 2026-10-30 21:27:22.010781+00
(1 row)
```
:::

Don't change these tables while `ampsync` runs.

### Share a database

`ampsync` needs its state tables to itself. In a database where another app already uses `_sqlx_migrations`, it stops
at startup with `migration … was previously applied but is missing in the resolved migrations`. To share a database,
give `ampsync` a schema: create it, and name it in the URL's `search_path` option. `ampsync` then creates its tables,
and yours, in that schema:

```bash
psql "$DATABASE_URL" -c 'CREATE SCHEMA ampsync'
ampsync sync --dataset acme/eth_transfers@1.0.0 --tables transfers \
  --database-url 'postgresql://postgres:postgres@localhost:5432/myapp?options=-csearch_path%3Dampsync'
```

Two `ampsync` processes can share a database or schema, as long as they don't sync into the same table.

## Stop and restart

Press Ctrl+C, or send `SIGINT`, to stop `ampsync`. It finishes the batch it's writing, and exits with code 0:

```
2026-10-30T21:27:36.227747Z  INFO ampsync::manager: Shutdown signal received, stopping tasks...
2026-10-30T21:27:36.227786Z  INFO run{table=transfers dataset=acme/eth_transfers@1.0.0}: ampsync::task: shutdown_requested
2026-10-30T21:27:36.227824Z  INFO ampsync::manager: task_stopped_cleanly table=transfers
2026-10-30T21:27:36.227897Z  INFO ampsync::manager: All tasks stopped
2026-10-30T21:27:36.227907Z  INFO ampsync::commands::sync: Ampsync shutdown complete
```

When you start it again with the same dataset and tables, it continues from its last checkpoint, without writing any
row twice.

### Crash recovery

If `ampsync` stops without finishing, because it crashed, the machine went down, or it was killed, the rows it wrote
since its last checkpoint are in the table, but not recorded as complete. On its next start, `ampsync` deletes them,
then has Amp send them again. Here, it was killed after writing 9 batches of a first sync, with no checkpoint yet:

:::terminal
```bash
ampsync sync --dataset acme/eth_transfers@1.0.0 --tables transfers
```

```
…
2026-10-30T21:19:43.497171Z  INFO run{table=transfers dataset=acme/eth_transfers@1.0.0}: ampsync::task: starting_task
2026-10-30T21:19:43.499648Z  WARN run{…}:handle_event{…}: ampsync::task: undo_event_processing tx_id=10 invalidate_start=0 invalidate_end=9 invalidate_count=10 cause=Rewind
2026-10-30T21:19:43.514475Z  WARN run{…}:handle_event{…}: ampsync::task: undo_event_processed tx_id=10 invalidate_start=0 invalidate_end=9
2026-10-30T21:19:43.535555Z  INFO run{…}:handle_event{…}: ampsync::task: data_event_processing tx_id=11 rows=8192
…
```
:::

`cause=Rewind` marks this cleanup: it deleted the rows of batches 0 to 9, then wrote them again from batch 11. The
table ends with the same 110,969 rows as an uninterrupted sync.

`ampsync` stops cleanly only on `SIGINT`. Any other signal ends it at once, like a crash, including the `SIGTERM` that
`kill`, `docker stop`, and Kubernetes send; its next start cleans up as above. To stop it cleanly in a container, make
`SIGINT` its stop signal: `docker run --stop-signal SIGINT`, or `stop_signal: SIGINT` in Docker Compose.

### Start over

Dropping a table isn't enough to sync it again from the start: `ampsync` recreates it empty, and continues from its
checkpoint. Delete the table's stream too, while `ampsync` is stopped. Its `stream_id` is the dataset, as you wrote it
in `--dataset`, and the PostgreSQL table:

```bash
psql "$DATABASE_URL" -c 'DROP TABLE transfers' \
  -c "DELETE FROM amp_client_state WHERE stream_id = 'acme/eth_transfers@1.0.0:transfers'"
```

## Reorgs

A [reorg](/get-started/concepts/reorgs) replaces blocks at the head of a chain with others. When Amp extracts the new
blocks, it tells streaming queries which block range changed, and `ampsync` deletes the rows of the batches that covered
it, then writes the new blocks'. You don't need to do anything.

Here, `ampsync` follows a local test chain, in which blocks 30 and 31 were replaced:

```
2026-10-30T21:23:34.116961Z  WARN run{…}:handle_event{…}: ampsync::task: undo_event_processing tx_id=10 invalidate_start=6 invalidate_end=9 invalidate_count=4 cause=Reorg([InvalidationRange { network: "anvil", numbers: 30..=32 }])
2026-10-30T21:23:34.119758Z  WARN run{…}:handle_event{…}: ampsync::task: undo_event_processed tx_id=10 invalidate_start=6 invalidate_end=9
2026-10-30T21:23:34.121358Z  INFO run{…}:handle_event{…}: ampsync::task: data_event_processing tx_id=11 rows=3
2026-10-30T21:23:34.132823Z  INFO run{…}:handle_event{…}: ampsync::task: data_event_processed tx_id=11 rows=3
```

Before the reorg, and after it, the `blocks` table holds:

```
 _tx_id | block_num |                                hash
--------+-----------+--------------------------------------------------------------------
      2 |        28 | 0xf5f79894b7094967c5d4070b5254b39d6bf138385db605e549ffed48414e6899
      4 |        29 | 0xbbabeb4974945b242b44ae04c70b7483e440c0b5c7bf9d8eb359f0a7f6689379
      6 |        30 | 0x2a6c34176545523433718d6b14c1feba1553d504f7b7cafa92a21ed02e0aa810
      8 |        31 | 0xe4cfb93dba25dcb333a86f737c8ee6b171d10f3c0f3b43226346a040f9655b81
```

```
 _tx_id | block_num |                                hash
--------+-----------+--------------------------------------------------------------------
      2 |        28 | 0xf5f79894b7094967c5d4070b5254b39d6bf138385db605e549ffed48414e6899
      4 |        29 | 0xbbabeb4974945b242b44ae04c70b7483e440c0b5c7bf9d8eb359f0a7f6689379
     11 |        30 | 0xd720c9fb4ef6abe8dfa8ac8df9c490b9c11f45b2cec311c8f84e08e71e7923f4
     11 |        31 | 0x2eb46bdefd3543f7af0e0ced2a99e7ede300f3f8de3f6e2dbb5b66d9eac20223
     11 |        32 | 0xa9eb73be4cd0eb27a5dc42e387281faa36979fde7958027787d6df009a176d64
```

`ampsync` can undo a reorg only back to a checkpoint it still has:

* **Depth**: it keeps checkpoints for the last [`--retention-blocks`](/clis/ampsync#options) blocks, 128 by default. A reorg deeper
  than that fails with `reorg error: unrecoverable reorg`. Raise it for chains with deeper reorgs.
* **Checkpoint boundaries**: the reorg must start at the first block of a checkpoint. When `ampsync` keeps up with a
  chain, each checkpoint usually covers one block, but one can cover several, like on the first sync, or when several
  blocks arrive at once. A reorg that starts after a checkpoint's first block and within its range fails with
  `reorg error: partial reorg detected`.

Either failure stops the table's sync, after [its retries](/clis/ampsync#failures-and-retries) fail the same way. To recover,
[start that table over](/clis/ampsync#start-over).

## Failures and retries

`ampsync` retries a PostgreSQL operation that fails for a passing reason, like a lost connection, too many connections,
a deadlock, or a database that's starting up, for up to 30 seconds.

Beyond that, each table's sync restarts on its own when it fails, like when Amp is unreachable, the table isn't in the
dataset, or a write fails. It logs `task_failed_restarting` with the error, waits, and starts the table again from its
last checkpoint. It waits 1 second the first time, then twice as long each time, up to 256 seconds. After the 10th
failure in a row, it logs `max_restart_attempts_reached` and stops syncing that table. The other tables, and the
process, keep running, so watch the logs or the [health check](/clis/ampsync#health-checks), and restart `ampsync` once you've fixed
the cause.

## Health checks

With [`--health-port`](/clis/ampsync#options), `ampsync` answers HTTP requests to `/healthz` on that port, on every network
interface, so a container orchestrator can restart it when it stops syncing:

:::terminal
```bash
curl -i http://localhost:8080/healthz
```

```
HTTP/1.1 200 OK
content-length: 0
date: Fri, 30 Oct 2026 21:20:31 GMT
```
:::

It answers `503 Service Unavailable` when a table has stopped syncing after its [last retry](/clis/ampsync#failures-and-retries), if
strict mode is on:

```
HTTP/1.1 503 Service Unavailable
content-length: 0
date: Fri, 30 Oct 2026 21:32:45 GMT
```

[`--strict-health`](/clis/ampsync#options) turns strict mode on or off. Without it, strict mode is on when you sync one table, where
a stopped table leaves the process with nothing to do, and off when you sync several, where you may prefer the other
tables to keep syncing.

| `--strict-health` | `/healthz` answers                                      |
| ----------------- | ------------------------------------------------------- |
| `true`            | `503` once any table has stopped syncing, `200` before. |
| `false`           | Always `200`.                                           |
| unset, one table  | Like `true`.                                            |
| unset, several    | Like `false`.                                           |

## Options

```
ampsync sync [OPTIONS] --dataset DATASET --tables TABLES --database-url DATABASE_URL
```

Each option can also be set with the environment variable in its row. A flag takes precedence over its environment
variable.

| Flag                                        | Environment variable         | Type                         | Default                                   | Description                                                                                                                                                       |
| ------------------------------------------- | ---------------------------- | ---------------------------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `-d`, `--dataset <DATASET>`                 | `DATASET`                    | dataset reference            | required                                  | The dataset to sync (see [Dataset references](/clis/ampsync#dataset-references)), like `--dataset acme/eth_transfers@1.0.0`.                                                   |
| `-t`, `--tables <TABLES>`                   | `TABLES`                     | table names, comma-separated | required                                  | The dataset's tables to sync, like `--tables blocks,transactions,logs`. You can also repeat the flag.                                                             |
| `--database-url <DATABASE_URL>`             | `DATABASE_URL`               | PostgreSQL URL               | required                                  | The database to sync into, like `postgresql://postgres:postgres@localhost:5432/ampsync`.                                                                          |
| `--amp-flight-addr <AMP_FLIGHT_ADDR>`       | `AMP_FLIGHT_ADDR`            | URL                          | `http://localhost:1602`                   | Amp's [query server](/running-amp/query-server), its Arrow Flight address, like `--amp-flight-addr https://amp.example.com:1602`. `https` URLs use TLS.           |
| `--auth-token <AUTH_TOKEN>`                 | `AMP_AUTH_TOKEN`             | string                       | none                                      | A token to send as `Authorization: Bearer`, for an Amp that requires one. Prefer the environment variable.                                                        |
| `--table-version`                           | `TABLE_VERSION`              | flag                         | off                                       | Appends the dataset's revision to each [table name](/clis/ampsync#table-names), like `transfers_v1_0_0`.                                                                       |
| `--table-suffix <TABLE_SUFFIX>`             | `TABLE_SUFFIX`               | string                       | none                                      | Appends `_` and the suffix to each [table name](/clis/ampsync#table-names), like `--table-suffix green` for `transfers_green`.                                                 |
| `--retention-blocks <RETENTION_BLOCKS>`     | `RETENTION_BLOCKS`           | integer, 64 or more          | `128`                                     | How many blocks of checkpoints to keep, which is the deepest [reorg](/clis/ampsync#reorgs) `ampsync` can undo, like `--retention-blocks 256`.                                  |
| `--max-db-connections <MAX_DB_CONNECTIONS>` | `MAX_DB_CONNECTIONS`         | integer, 1 to 1000           | `10`                                      | The most connections to PostgreSQL `ampsync` opens, shared by all tables, like `--max-db-connections 20`.                                                         |
| `--grpc-max-decode-mb <GRPC_MAX_DECODE_MB>` | `AMPSYNC_GRPC_MAX_DECODE_MB` | integer, 1 to 512            | `32`                                      | The largest message from Amp, in MiB, that `ampsync` accepts, like `--grpc-max-decode-mb 128`. Raise it if a table fails with `decoded message length too large`. |
| `--health-port <HEALTH_PORT>`               | `HEALTH_PORT`                | port                         | none                                      | Serves [`/healthz`](/clis/ampsync#health-checks) on this port, on all interfaces, like `--health-port 8080`.                                                                   |
| `--strict-health <STRICT_HEALTH>`           | `STRICT_HEALTH`              | `true` or `false`            | `true` for one table, `false` for several | Whether `/healthz` answers `503` once a table stops syncing (see [Health checks](/clis/ampsync#health-checks)).                                                                |

`TABLE_VERSION` and `STRICT_HEALTH` take `true` or `false`. `ampsync sync -h` prints a summary of the options,
`--help` their full descriptions, and `ampsync --version` (`-V`) [the version](/clis/ampsync#check-the-version).

:::warning
`ampsync sync --help` prints the value of each environment variable it reads, including `AMP_AUTH_TOKEN` and the
password in `DATABASE_URL`. Mind where its output goes.
:::

### Dataset references

`--dataset` names the dataset as `namespace/name@revision`, where you can leave out the namespace and the revision:

| Reference                  | Means                                            |
| -------------------------- | ------------------------------------------------ |
| `acme/eth_transfers@1.0.0` | Version `1.0.0` of `acme/eth_transfers`.         |
| `eth_transfers@1.0.0`      | The `_` namespace: `_/eth_transfers@1.0.0`.      |
| `acme/eth_transfers`       | The latest version: `acme/eth_transfers@latest`. |
| `eth_transfers`            | `_/eth_transfers@latest`.                        |

The revision is a version, like `1.0.0`; a manifest hash, 64 hexadecimal characters; `latest`, the latest version; or
`dev`, the development version (see [Versioning](/datasets/versioning)). Name a version: `ampsync` names its streams,
and [`--table-version`](/clis/ampsync#table-names) its tables, after the revision as you wrote it, so with `latest` neither changes
when a new version comes out.

## Logging

`ampsync` logs to standard error, at the `info` level by default, as in the examples above. Set `AMP_LOG` to change
the level of `ampsync`'s and Amp's own messages: `error`, `warn`, `info`, `debug`, or `trace`. At `debug`, it also logs
each checkpoint (`cursor_event_processing`). `RUST_LOG` sets the level of the libraries it uses, `error` by default.

```bash
AMP_LOG=debug ampsync sync --dataset acme/eth_transfers@1.0.0 --tables transfers
```

The messages to watch:

| Message                        | Level   | Means                                                                                     |
| ------------------------------ | ------- | ----------------------------------------------------------------------------------------- |
| `data_event_processed`         | `INFO`  | A batch of rows was written, with its number (`tx_id`) and row count.                     |
| `undo_event_processing`        | `WARN`  | Batches are being deleted after a restart (`cause=Rewind`) or a reorg (`cause=Reorg(…)`). |
| `task_failed_restarting`       | `WARN`  | A table's sync failed, and restarts after `backoff_secs`.                                 |
| `max_restart_attempts_reached` | `ERROR` | A table's sync failed 10 times in a row, and stopped.                                     |

## Environment variables

| Variable                     | Description                                                  |
| ---------------------------- | ------------------------------------------------------------ |
| `DATASET`                    | Like [`--dataset`](/clis/ampsync#options).                                |
| `TABLES`                     | Like [`--tables`](/clis/ampsync#options).                                 |
| `DATABASE_URL`               | Like [`--database-url`](/clis/ampsync#options).                           |
| `AMP_FLIGHT_ADDR`            | Like [`--amp-flight-addr`](/clis/ampsync#options).                        |
| `AMP_AUTH_TOKEN`             | Like [`--auth-token`](/clis/ampsync#options).                             |
| `TABLE_VERSION`              | Like [`--table-version`](/clis/ampsync#options).                          |
| `TABLE_SUFFIX`               | Like [`--table-suffix`](/clis/ampsync#options).                           |
| `RETENTION_BLOCKS`           | Like [`--retention-blocks`](/clis/ampsync#options).                       |
| `MAX_DB_CONNECTIONS`         | Like [`--max-db-connections`](/clis/ampsync#options).                     |
| `AMPSYNC_GRPC_MAX_DECODE_MB` | Like [`--grpc-max-decode-mb`](/clis/ampsync#options).                     |
| `HEALTH_PORT`                | Like [`--health-port`](/clis/ampsync#options).                            |
| `STRICT_HEALTH`              | Like [`--strict-health`](/clis/ampsync#options).                          |
| `AMP_LOG`                    | The [log level](/clis/ampsync#logging) of `ampsync`'s and Amp's messages. |
| `RUST_LOG`                   | The [log level](/clis/ampsync#logging) of the libraries `ampsync` uses.   |

## Exit codes

| Code | Meaning                                                                                                  |
| ---- | -------------------------------------------------------------------------------------------------------- |
| `0`  | It stopped on `SIGINT`, like Ctrl+C.                                                                     |
| `1`  | It couldn't start: the database, its state tables, Amp's address, or the table names failed (see below). |
| `2`  | An option is missing or invalid.                                                                         |

Once running, `ampsync` doesn't exit when a table fails: see [Failures and retries](/clis/ampsync#failures-and-retries).

## Errors

At startup, `ampsync` exits with code 1, printing `Error:` and its causes:

```
Error: Failed to connect to database

Caused by:
    pool timed out while waiting for an open connection
```

It couldn't connect to the database in `--database-url` within 30 seconds. Check that PostgreSQL is running, and the
URL's host, port, user, password, and database.

```
Error: Failed to run state store migrations

Caused by:
    0: state store error: failed to run PostgreSQL migrations
    1: migration 20240101000000 was previously applied but is missing in the resolved migrations
```

Another app's migrations are in the database. Give `ampsync` [its own database or schema](/clis/ampsync#share-a-database).

```
Error: Failed to create amp-client-flight

Caused by:
    0: gRPC transport error
    1: transport error
```

Nothing answers at `--amp-flight-addr`. Check that Amp is running, and the address and port of its query server.

```
Error: Failed to build table mappings

Caused by:
    0: Source table name 'bad-table' is not a valid identifier
    1: Identifier contains invalid character: '-'
```

A [table name](/clis/ampsync#table-names) isn't valid, is longer than 63 bytes with its version and suffix, or appears twice
(`Duplicate destination table 'transfers' (from source tables 'transfers' and 'transfers')`).

While running, a table's sync fails and [retries](/clis/ampsync#failures-and-retries), logging `task_failed_restarting` with the
cause in `error_source`:

* `table 'amp.acme/eth_transfers@1.0.0.nope' not found`: the dataset or the table doesn't exist on that Amp, or isn't
  deployed. Check the names with [`ampctl dataset inspect`](/clis/ampctl/dataset).
* `decoded message length too large: found 1322413 bytes, the limit is: 1048576 bytes`: a message from Amp is larger
  than [`--grpc-max-decode-mb`](/clis/ampsync#options). Raise it.
* `reorg error: partial reorg detected` or `reorg error: unrecoverable reorg`: `ampsync` can't undo a [reorg](/clis/ampsync#reorgs).
* ``malformed `authorization` header``, or another authentication error: Amp rejected `--auth-token`.

Invalid options print what's wrong and exit with code 2:

```
error: invalid value '10' for '--retention-blocks <RETENTION_BLOCKS>': 10 is not in 64..18446744073709551615

For more information, try '--help'.
```
