PachaTree migration failed part-way — is there any supported way to resume it?

Setup: InfluxDB 3 Enterprise (home licence), single node, object-store=file, shard-count=1. Now on 3.11.1.

What happened

On 2026-08-01 I upgraded 3.10.5 → 3.11.0 and restarted with --upgrade-pacha-tree. The migration ran for 14 minutes and stopped at status: failed.

The trigger was on my side: 14 catalog entries pointed at Parquet files that no longer existed on disk — deleted by retention months before the upgrade, i.e. a pre-existing catalog-integrity issue. Every failure reads:

External error: External: Object at location
/data/influxdb3/<node>/dbs/25/1/2026-06-11/08-50/00000000000015021636.parquet
not found: No such file or directory (os error 2)

I dropped the 11 affected tables the next day, so the original blocker is gone.

Where that left the cluster

system.upgrade_parquet, unchanged for 10 days:

status count
imported 6344
failed 14
not_started 630 (2.0 GiB)

The catalog is in hybrid ParquetAndPachaTree mode. No data loss — every table is queryable and hybrid reads across the cutover are exact. The problem is that ~21 GiB of pre-upgrade data now sits in a frozen legacy tree: retention still hides out-of-retention rows from queries, but no longer deletes the underlying files. Nothing has been written to or removed from that tree since the cutover instant, so “wait for it to expire” isn’t an option.

What I’ve tried

1. Restarting with --upgrade-pacha-tree again (3.11.1). The flag is parsed and honoured, and then:

upgrade_coordinator: Skipping enterprise upgrade coordinator.
                     compactor=<node> import=migration status=Failed
bulk_import_scheduler: No work remaining; waiting for new coordinators.

So a Failed import is deliberately never auto-resumed. Both state files came back byte-identical.

2. influxdb3 manage cleanup-parquet --dry-run (new in 3.11.1):

409 Conflict: catalog storage mode is ParquetAndPachaTree; cleanup requires PachaTree

Correctly gated — but unreachable, because I have no way to get to PachaTree.

3. influxdb3 import upload per file. This works well and is safe — additive, dedupes on time+tags, no duplicate rows. I used it to make one table PachaTree-native. But it runs under its own import name, so the migration ledger still reads failed and the legacy files stay on disk.

Questions

  1. Is there a supported way to reset or retry a bulk import stuck at Failed, so the coordinator picks up the 630 not_started files? I can’t find one in serve, manage or import, or in the docs.
  2. Should a missing source file be skippable rather than terminal? One dangling catalog reference stopped 630 unrelated files from ever being migrated, and the failed status is sticky even after the offending tables are dropped.
  3. Is the catalog storage mode readable anywhere other than by triggering that 409? It’s the authoritative “did the migration complete” signal, but it appears in no system table.

The only documented exit I can see is manage downgrade-to-parquet, which discards everything written since the upgrade — 10 days now, and growing daily. I’d rather not.

Happy to supply the full ledger, state files or logs.

Many thanks,
Peter

Hey @pradig do you have any retention period set up on your database/tables?

If you’re not sure, you could take the db and table ID from the path in the error for the missing file:

/data/influxdb3/<node>/dbs/25/1/

is database with ID 25 and table with ID 1.

You could see which database/table these correspond to via system table queries:

select * from system.databases
select * from system.tables where database_id = 25

It is possible that retention removed a file while the migration was taking place. We have opened a ticket internally to look into this.

Thanks @hiltontj — yes, retention is configured, and the hypothesis is half right: retention
did delete those files. But not during the migration. Every retention-caused deletion here
predates the upgrade by weeks to months, and the remaining five refs are in a database that had
no retention at all.

Here’s the mapping you asked for.

The three databases behind the 14 failed refs

path in the error database retention on 2026-08-01 refs
dbs/25/1/ network, table vigor 30d 1
dbs/32/{9..16}/ proxmox, 8 stray tables 30d 8
dbs/lorawan-7/lorawan_{up,rx}-{0,1}/ lorawan none 5

I didn’t need the ID lookup in the end — system.upgrade_parquet carries plain
database/table names. Worth flagging for anyone else following this thread, though:
select * from system.tables where database_id = 25 no longer resolves for me, because I dropped those 11 tables on 2026-08-02. The table column in upgrade_parquet also comes back NULL for them now, while database still resolves.

Retention deleted the files — long before the upgrade

The parent directory mtimes date the deletions precisely, and both land exactly one retention
period after the partition timestamp named in the dangling reference:

db partition in the failed ref + 30d actual dir mtime vs. upgrade (2026-08-01 14:30)
25 network 2026-06-11 08:50 2026-07-11 08:50 dbs/25/ 2026-07-11 08:58 21 days earlier
32 proxmox 2026-03-13 18:10 2026-04-12 18:10 dbs/32/ 2026-04-12 18:26 111 days earlier

Table dir 1 is absent under dbs/25/ (0, 2–8 present), and 9–16 are absent under dbs/32/
(0, 1, 2, 5–8 present) — consistent with a retention sweep removing each table’s whole
directory at those moments. So there was no race with the migration: the catalog had been
carrying references to files that retention removed 3 weeks and 3.6 months earlier.

The other five can’t be retention at all

lorawan had no retention period configured on 2026-08-01 — I only set 30d on 2026-08-03, two
days after the failure. Those five refs are also the only ones in the whole ledger using the
old <name>-<id> directory scheme (dbs/lorawan-7/lorawan_up-0/…) instead of bare numeric ids. dbs/lorawan-7/ doesn’t exist on this node at all; the live path is dbs/7/. Whatever removed those files, retention wasn’t it.

One useful difference: before I dropped those two tables, any query whose time predicate
overlapped 2025-08-11 hard-failed mid-body with the same NotFound. So the file was genuinely
gone, not merely mis-pathed. The network and proxmox orphans instead returned 0 rows
silently at any time range, because retention prunes them out of the query plan — which is
exactly why they sat unnoticed for months.

What I think this means

The condition the migration tripped on isn’t “retention raced the import”. It’s that the
catalog can retain references to files that no longer exist, apparently indefinitely, and
normal querying hides that from the operator whenever retention is active. Any long-lived
instance with retention enabled could therefore hit this on its first PachaTree upgrade with no
prior warning.

The cost is also disproportionate to the damage. All 11 failed jobs recorded
bytes_written=0 row_count=0 — nothing was actually being lost — yet those 14 refs stopped 630 unrelated, healthy files (2.0 GiB) from ever being migrated, and the Failed status is sticky.
On 3.11.1 the coupling is now explicit in the logs:

upgrade_coordinator: Skipping enterprise upgrade coordinator.
                     compactor=<node> import=migration status=Failed

One dangling reference from months ago → permanently frozen migration → 21 GiB stuck in the
legacy tree, with no route to PachaTree and therefore none to cleanup-parquet either.

Two things would have prevented it: a pre-flight catalog-integrity check before the migration
starts, and treating a missing source file as skippable-and-recorded rather than terminal —
that alone would have let the other 6,974 files through.

But I still need a way out of the state I’m actually in, which is the one question I can’t
answer from the docs: is there a supported way to reset a bulk import from Failed back to a resumable state, now that the offending tables are gone? downgrade-to-parquet costs me 11 days of writes today and grows daily.

Happy to run any query, or hand over the ledger, state files or logs.


Minor reporting nit while I’m here: the ledger has one row per source file, but last_message
appears to be per job. My three lorawan rows for the 12-30 and 12-40 partitions all
carry the message naming the 12-50 path, which is misleading when you’re trying to work out
precisely which files are missing.

Follow-up: retry-upgrade-to-pacha-tree worked, but the migration still can’t complete — compactor orphans ~89% of what it writes

Follow-up to PachaTree migration failed part-way — is there any supported way to resume it?

Setup: InfluxDB 3 Enterprise (home licence), single node, object-store=file,
shard-count=1, 12 cores / 62 GB RAM, one SATA SSD. Now on 3.11.2.

First: thank you — 3.11.2 shipped exactly what I asked for in that thread, and the stale-gen1 reference handling matches the diagnosis I posted on 2026-08-12. The retry command does what it says. The migration still doesn’t complete, for a different reason, and that’s what this
post is about.

1. The retry command works

Run on 2026-08-22 05:34 UTC:

$ influxdb3 manage retry-upgrade-to-pacha-tree
Sources re-checked:      11
Skipped (source gone):   0
Skipped (table dropped): 11
Requeued:                0
Unresolved:              0
Upgrade status:          upgrading

Migration state reset. Restart the compactor node with --upgrade-pacha-tree to resume.

Then upgrade-pacha-tree="true" + restart. The ledger drained in about nine minutes:

status before after
imported 6344 6974
failed 14 0
skipped_table_dropped 0 14
not_started 630 0

No data loss at this stage — four fixed-window row-count probes (a settled 1 h window, a
settled 24 h window, a 2020 month in an infinite-retention DB, and a pre-cutover hour that
existed only in the legacy tree) all returned byte-identical counts afterwards.

2. But the migration never completes

Eight hours later, and across five restarts:

SELECT node_id, mode, status FROM system.upgrade_parquet_node;
node_id mode status
collect-eu ingest completed
collect-eu compactor upgrading
$ influxdb3 manage cleanup-parquet --dry-run
409 Conflict: catalog storage mode is ParquetAndPachaTree; cleanup requires PachaTree

The bulk import scheduler is idle and says so — No work remaining; waiting for new coordinators. — yet the compactor node never leaves upgrading.

Q1: what advances the compactor node from upgrading to completed? With the ledger fully drained and the scheduler reporting no work, I can’t find the remaining precondition.

3. The reason, I think: ~89% of compactor output is never referenced

.pt files on disk (find /data/influxdb3/*/pr-eu/cv2/ -name '*.pt'):   126,302
files referenced by system.pt_compaction_files:                        13,809
orphaned:                                                             112,493  (89.1%)

Oldest orphan mtime is 2026-08-01 19:12 — hours after the original failed migration. The
newest are being written continuously right now.

This matches everything else I can measure. Over a 40-minute window sampling
system.pt_compaction_files every 5 minutes:

value change
referenced files 13,809 constant
distinct windows 2,152 constant
rows 234.1M → 234.4M +7,357/min

and measured ingest over the same period was 5,876 lines/min — i.e. the only growth is live
data arriving. No imported legacy content is being adopted into the catalog.

Meanwhile the compactor reads far more than it writes:

sum(rate(influxdb3_compactor_input_bytes_total[20m]))   ~4,800 GiB/day
sum(rate(influxdb3_compactor_output_bytes_total[20m]))    ~270 GiB/day
output ÷ input                                             ~0.06

Pre-migration that input rate was 2–3.5 GiB/day. It has been ~1,400× that, steadily, for
eight hours, on a 15 GB dataset. influxdb3_compactor_windows reports 20,688 and has not
changed in eight hours, while system.pt_compaction_files reports 2,152 distinct windows —
those two appear to count different things, which made the metric misleading as a progress
signal.

Q2: is a ~89% orphan rate expected during or after a migration? If those files are pending
adoption, what drives it? If they’re garbage, what reclaims them — files_queued_for_cleanup
oscillates between ~1,500 and ~4,300 and never drains.

4. system.upgrade_parquet reports “imported” for data that is not queryable in PachaTree

This is the part I’d most like corrected, because it’s the one that could cost someone their
data.

The ledger reads imported=6974, not_started=0. It also names the affected tables directly —
electricity_meter 82 files, adsb_daily 353 files, all imported. But setting
--disable-hybrid-query=true (so reads come only from PachaTree) for ~100 seconds:

table retention hybrid ON hybrid OFF invisible
electricity.electricity_meter infinite 533,971 133,153 75%
tracking_lt.adsb_daily infinite 1,482 87 94%

Reverting restored both counts exactly. Nothing was written or deleted — this was read-path
only.

So the legacy Parquet files are still the only readable copy of that data, and
manage cleanup-parquet deletes them. The 409 in §2 is currently the only thing preventing
that. If I had succeeded in reaching PachaTree mode, the documented next step would have destroyed roughly 400,000 rows.

Worth flagging for anyone else verifying a migration: 7 of my 9 probes stayed green during
that loss.
Only long-history, infinite-retention tables showed it. A representative sample is
not a safety check here.

Q3: should status = imported in system.upgrade_parquet imply the data is queryable from PachaTree? If not — and evidently it doesn’t — is there a system table that reports actual
PachaTree coverage per table, so a migration can be verified before cleanup-parquet is run?

5. Separately: the default merge concurrency livelocks a single-node local-storage deployment

--compactor-max-concurrent-merges defaults to 64 × available parallelism = 768 here.
After the migration (when compactor_windows went 2,154 → 20,688 and the scheduler began
emitting bursts of 22 plans), that default deadlocked the compactor completely:

compaction plans produced        15
compaction plans COMPLETED        0     ← in 12 minutes
lease renewal timeouts           10
V2 checkpoint reloads             9     (20,688 windows each, ~every 80 s)

with

ERROR ...coordinator::lease_holder: lease renewal failed with error
  e=Generic LocalFileSystem error: conditional put: lock acquisition timed out
    after 50 attempts: Resource temporarily unavailable (os error 11)
WARN  ...coordinator::lease_holder: primary lease lost during renewal

The node expires its own lease and immediately reacquires it — expired_holder and the
reacquiring node_id are the same UUID — reloading the entire checkpoint each time, so no plan ever survives to completion.

This drive’s fsync latency is a sustained 70–78 ms (QLC, no power-loss protection), and I
want to be clear that this is pre-existing and not caused by the migration — node_exporter
shows the same 70–78 ms for hours before it, rising only to ~83 ms after. What changed was
the volume of compaction work.

Capping concurrency restored completions, but did not change the underlying behaviour:

cap compactor input output ÷ input completed ÷ dispatched compactor_windows
768 (default) 0 plans completed / 12 min 20,688
8 ~2,400 GiB/day 0.041 0.50 20,688
2 ~4,850 GiB/day 0.055 0.89 20,688
1 ~4,286 GiB/day 0.061 0.92 20,688

An 8× reduction in concurrency changed total disk writes by ~5% (441 → 462 GiB/day). The
orphaning is invariant across a 768× range of this setting.

Q4: is 64 × available parallelism intended for object-store=file single-node
deployments?
On anything without deep request parallelism it appears able to starve the
primary lease renewal, which needs a durable conditional put on the same filesystem.

What I’ve ruled out

  • Concurrency — invariant across 768 / 8 / 2 / 1 (table above).
  • --final-compaction-age — set to 15m (legacy default is 72h) hoping to force
    finalisation to L4. Zero L3/L4 activity across ten samples over 30 minutes. I believe the
    idle clock never elapses because Import and Retention plans touch the data continuously.
  • Waiting — eight hours, five restarts, coverage static throughout.
  • downgrade-to-parquet — would discard three weeks of writes. Not acceptable.

Where I am now

Running happily in hybrid mode: ingest is correct, every query returns complete results, and
the four correctness probes are exact. The costs are ~460 GiB/day of write amplification on a
consumer SSD, and a legacy tree I can’t retire.

I’m not blocked in any urgent sense and I’m not asking for a fix on any timeline — but I’d
value knowing whether Q1–Q3 indicate a defect or a misunderstanding on my side, and Q3 in
particular seems worth a docs warning regardless.

Happy to supply the full ledger, system.pt_compaction_files extracts, state files, logs or
the orphan file listing.

Correction to my post above — I was wrong.

I reported that after retry-upgrade-to-pacha-tree the compactor node stayed at upgrading and the catalog never reached PachaTree. It did reach it, on its own, four days later (2026-08-26), with no intervention. upgrade_parquet_node now shows the compactor row completed, and cleanup-parquet no longer returns the 409.

I called it stuck after watching it for under an hour. It was working the whole time. The retry command in 3.11.2 does exactly what it says — it just needed far longer than I gave it. Thank you for shipping it.

One small suggestion, offered gently: nothing I could find in the logs, metrics or system tables distinguished “grinding through a multi-day backlog” from “wedged”. Any progress signal on the import would have saved this thread.

Apologies for the noise.

Peter

Hey @pradig thanks for providing additional info, and sorry you’re still having issues.

Can you help by providing the following files from object store:

<cluster>/cv2/delete_queue.ptdq.json
<cluster>/compactor_v2_bulk_import.json
<cluster>/import/migration/migration-inventory.json
<cluster>/cluster_pacha_tree_upgrade_state.json
<node>/ingest_pacha_tree_upgrade_state.json

In addition, a CSV dump of the following query:

SELECT * FROM system.upgrade_parquet;

Would you be able to zip it up and share it here on the thread?

In addition, any logs you can share would also be helpful.