Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Troubleshooting and FAQ

Start with the exit code (CLI) and the diagnostic’s code: every message names the file and line it is about, and most say what to do in their help: line.

The config

no config directory at ..., ... is a file; --config expects the ferry/ directory (F0001), no ferry.yaml in ... (F0103). --config points at the directory holding ferry.yaml: not at the file, and not at its parent. Without --config, commands look for ./ferry.

environment variable not set: NAME (F0108). Export it in the shell that runs dataferry. Variables are read only from the environment, and only in connections and runtime.state.path.

`${` in a flow (F0108). Flows can’t use environment variables: write the value into the flow. It then becomes part of the config hash, which is what keeps a resumed run identical to the first attempt.

unknown key (F0105). A typo, or a key in the wrong file or at the wrong level. The help line suggests the closest key.

expected an expression (a string) ... found boolean. An unquoted true, null or number where an expression belongs: quote it.

`index` is a reserved name (F0110). Write $index for the position, or "index" for a field of that name. The same goes for parent, item and key.

Planning

could not discover the entities of ... (exit 5). Either the database can’t be reached (check the URL, the network, credentials and TLS), or the source’s schema can’t be read. For PostgreSQL, every table of the connection’s schema: must have a primary key and supported column types, even tables no flow reads; the message lists each offending table. Point schema: at a schema holding only what you migrate, or fix the tables. For MongoDB, a time-series collection or a field named _ferry_... in the database blocks discovery.

A remote database without TLS fails to connect. Remote hosts require TLS unless you turn it off. For a server reached by name without TLS (a CI service container), add sslmode=disable (Postgres) or tls=false (MongoDB) to the URL.

A certificate isn’t trusted. Certificates are always verified. For a private CA, give its PEM file: sslrootcert= (Postgres) or tlsCAFile= (MongoDB).

no field ... in entity ... (F0110) for a field that exists. Names are case-sensitive. For MongoDB, the schema comes from a sample of 1,000 documents: a field none of them has isn’t in the schema. Check what plan reports for the entity, and see schema inference.

unconstrained numeric has no fixed precision. Declare the Postgres column as numeric(p, s). The message also suggests casting it in a query, but the source reads tables only, not views or queries, so the column type itself must change (or migrate from a copy of the table that has one).

A type error (F0111) on a typed column. The conversion from the expression’s type to type: can lose information. Convert explicitly in the expression (int(x), int(round(x))), or add allow_lossy: true if you accept what the lossy table says will happen.

flow ... sets on_error ... but this runner applies on_error to the whole run (F0112). Per-flow on_error and max_errors aren’t supported yet: set them in runtime.yaml.

Running

The run stopped with failed partitions (exit 6). The stderr lines F0602 say which partition and why. Fix the cause, then dataferry retry --failed <run-id>. If the fix is a change to the config, the hash changes: start a new run instead.

Records are rejected that look fine. Read the reason in the dead-letter table or in the error. Common ones: int() of text that isn’t exactly an integer (' 3', '3.0'); a MongoDB field with mixed types, read as json, so int() sees "7" with its quotes; a NOT NULL key column with a null; a MongoDB document with a field the sample didn’t see.

prepare fails on a table that exists. The existing table doesn’t fit what the flow writes: a column type that differs (an integer column for an int64 value), a NOT NULL column the flow doesn’t write, or, for upsert, no unique index on exactly the key columns. The message lists every problem. Change the table, or the flow’s types.

has no key, so re-running a partition can't replace the rows it wrote before. Give the target a key:, or keep the state store on the sink (runtime.state.connection: <sink>), the only case where a keyless target can be written safely.

resume is refused (exit 8). The config’s meaning or the dataferry version changed since the run started. Tuning settings (workers, memory_budget, partition_rows, the source limits) don’t count, so you can change those. Anything else: restore it, or start a new run.

resume waits before doing anything. After a crash or kill -9, the dead process’s leases have to expire first, at most 30 seconds. After a Ctrl-C there is no wait.

A run uses too much memory. Lower memory_budget (it sizes batches and read-ahead) or workers, and resume: both are tuning settings. A merge that stages a large joined entity sorts it in a quarter of the budget, spilling the rest to the work directory.

A run is too slow, or too hard on the source. More workers read more partitions at once. source_max_connections and source_rows_per_sec cap what the run asks of the sources. Keep the state store on the sink so partitions aren’t written twice after a crash.

Verification

Verification fails on tables that had rows before the run. It compares whole tables with what the run wrote, so rows it didn’t write count as differences. That is by design: it is meant for full loads into empty tables. Load into empty tables, or read the report’s Tables section knowing that.

done partitions have no write-time record. The work directory of the run was lost or changed (a different --work-dir, a new container). Verification needs the records each partition wrote there: keep the work directory for the life of a run, including across resume.

An assertion fails. The report shows both values. If the mapping is right and the assertion is wrong, fix the assertion: that changes the config hash, so start a new run.

FAQ

Which databases does it support? MongoDB and PostgreSQL as sources, PostgreSQL as the sink, in this release. Others can be added as connectors in your own build.

Is it safe to stop a run? Yes. Ctrl-C, a crash or a lost connection loses at most the partitions in flight; resume continues from the committed ones and writes nothing twice (Running migrations).

Is it exactly-once? For flows whose sink is the state store’s connection, yes: a partition’s rows and its done-record commit in one transaction. Other flows may write a partition twice after a crash, and the second write replaces the first.

Does it keep the target in sync with the source? No. A run copies what the source holds when it reads it. There is no change data capture, and upserts never delete.

Can several machines share a run? No: one process runs a run. Use workers for parallelism within it.

Can I run it in CI or a container? Yes. validate needs no database. For run, keep the state store in Postgres (so it survives the container) and the work directory on a persistent volume (verification needs it).

Where are the logs? There are none beyond what the commands print: stdout for output, stderr for diagnostics. The report in .ferry/runs/<run-id>/ is the record of a run.

How do I see what a run will do without running it? dataferry plan. It shows the DDL, the strategies and every type, and writes nothing.