skip to content

An ETL job copied new Parquet files into a lakehouse table's data directory — why don't queries return them?

level: seniorimportance: should knowfreq 50%

answer

  1. the folder is not the source of truth
  2. no commit means no membership
  3. these files have a name, and a cleanup job
  4. what deletes them, weeks later

basics

~20 s

Readers resolve a table's contents from its metadata layer, never from a directory listing. Files copied straight into storage were never registered by a commit, so they are orphans: invisible to queries and eligible for deletion by the table's cleanup job.

solid answer

~50 s

A table-format table is defined by the file list in its metadata, not by what happens to be in the folder. A job that writes Parquet directly to the storage path bypasses the commit entirely, so no version of the table references those files — every query correctly ignores them. Worse, the situation is not merely inert: the maintenance job that removes files unreferenced by any retained version will eventually delete them, so the data silently disappears. The mirror-image failure is just as common — deleting a file straight from storage leaves metadata pointing at a missing path, and queries then fail with file-not-found instead of silently omitting rows. The fix is to write through the table format's API, or to use its supported path for adopting existing files, which writes a commit and collects statistics. Then lock the prefix so only the table's writers can write to it.

code

text · 8 lines
text
# On storage
s3://lake/warehouse/orders/data/
  00000-0-9a1f.parquet     # referenced by table metadata
  00001-0-3c7d.parquet     # referenced by table metadata
  etl_export_2026_03_01.parquet   # copied in by hand -- referenced by nothing

# Queries return rows from the first two only.
# The orphan-cleanup job deletes the third once it passes the age threshold.

go deeper

for a junior

Recall the rule: a table-format table contains exactly the files its metadata lists. Copying a file into the folder does not add it, because no commit happened.

for a middle

Explain both directions of the same mistake — copied-in files are invisible orphans, deleted-out files cause file-not-found errors — and name the correct write path through the format's API.

for a senior

Show the operational consequence and the recovery: the orphan-cleanup job will eventually delete this data, so you have a limited window; adopt the files with a real commit, then lock down the prefix and audit for other bypassing writers.

for a principal

Own the platform guardrails: storage permissions scoped so only table writers can write the data prefix, external producers given landing zones, lifecycle rules kept off managed tables, and drift detection running as a routine report.

## What actually happened The job did its work correctly at the file layer and skipped the table layer entirely. Valid Parquet files now sit in the table's storage path, but no commit was made, so no version of the table lists them. Since a table-format reader builds its scan from the metadata file list rather than from a directory listing, the query result is not a bug — it is the guarantee working. If a listing could add files to a table, every failure mode that table formats were built to eliminate would come straight back. These unreferenced files have a name: **orphan files**. They also arise naturally from failed or killed jobs that wrote data files before their commit, which is why every format ships some form of orphan cleanup. ## Why this is worse than "the rows are missing" Three consequences follow, in increasing severity. First, the rows are absent from query results while consuming storage you pay for. Second, the table's own bookkeeping — row counts, per-file statistics, snapshot sizes — is unaffected and therefore *correct about the table but wrong about the folder*, which makes the discrepancy hard to spot from the table side. Third, and the real hazard: cleanup of unreferenced files, run on a retention threshold, will delete them. The team then loses data that was never in the table, often weeks later, with no obvious link back to the ETL job that wrote it. Orphan cleanup is also the reason these jobs use a conservative age threshold: a file written moments ago by a legitimate in-flight writer is indistinguishable from an orphan, so removing recent files can destroy a commit that was about to succeed. ## The mirror-image failure The symmetric mistake is deleting files directly — a tidy-up script, a lifecycle rule expiring objects by age, a manual `rm` on a partition prefix. Metadata still references those paths, so queries fail loudly with file-not-found rather than returning fewer rows. That is arguably the better failure because it is visible, but it is harder to repair: you must roll the table back to a version whose files still exist, or rewrite the affected metadata. Storage lifecycle rules are a particularly nasty version of this, because they act on their own schedule and delete files that older versions still need for time travel. ## How to fix it properly The correct write path is always through the table format: the engine's insert or merge, or the format's write API, so that data files and the metadata commit that registers them are produced together. When the files genuinely already exist and re-writing them is wasteful — a bulk import, a hand-off from an external producer — every major format provides a supported way to adopt existing data files into a table. That path is not a copy: it reads each file's footer to collect schema and statistics, checks compatibility, and then writes a real commit referencing the existing paths. That is the crucial difference from `cp`: a commit happens and statistics are recorded, so planning can prune the new files like any others. ## Preventing recurrence - **Lock the prefix.** Grant write permission on the table's storage path only to the identities that write through the table format. Direct writers should get a permission error, not a silent no-op. - **Never point a lifecycle rule at a managed table's data prefix.** Retention is the table format's job, driven by version expiry, not by object age. - **Give external producers a landing zone.** Let them write wherever they like outside the table, then ingest with a real commit. - **Detect drift.** Run the orphan-file scan in a report-only mode periodically; a sudden pile of unreferenced files in a table prefix is the signature of a bypassing writer, and finding it before the deletion window closes is what saves the data. ## The one-sentence answer "The table is its metadata, not its directory — the files were never committed, so they are orphans: invisible now and deletable later." Everything else follows from that.

  • What is the opposite mistake — deleting a data file straight from storage — and how does it show up?
    Metadata still references the deleted path, so queries fail with a file-not-found error rather than quietly returning fewer rows. It is more visible but harder to repair: you either roll the table back to a version whose files still exist, or rewrite the metadata that references the missing file. Storage lifecycle rules expiring objects by age cause this silently and on a schedule.
  • If the files already exist and are valid Parquet, is there a way to add them without rewriting?
    Yes. Each format offers a supported path for adopting existing data files: it validates schema compatibility, reads each file's footer to collect statistics, and writes a genuine commit that references the existing paths. No bytes are copied, but unlike a plain file copy there is a commit and there are statistics, so the files are visible and prunable.
  • Why do orphan-cleanup jobs default to only deleting files older than a long threshold?
    Because a data file written seconds ago by an in-flight writer looks exactly like an orphan — it exists on storage and no committed version references it yet. A short threshold would delete the output of a commit that was about to succeed. The threshold must comfortably exceed your longest write, and cleanup should not run while unusually long jobs are active.
  • How do you detect this class of problem before data is lost?
    Run the orphan scan periodically in a report-only mode and alert on unreferenced files inside a managed table's prefix. A steady accumulation points to a writer bypassing the table layer. Pair it with storage permissions that only allow the table's own writers into that prefix, so the bypass fails loudly rather than accumulating quietly.

saying these in an interview costs you the question

  • Expecting an engine to pick up new files by listing the directory
  • Suggesting a metadata refresh or repair will register copied files
  • Treating orphan files as harmless wasted storage
  • Deleting data files directly and assuming queries just lose rows
  • Applying object-lifecycle expiry rules to a managed table's data prefix

context