Skip to content

Object storage reads for external Parquet, which #388 depends on #393

Description

@ChronicallyJD

Our external Parquet support reads local files only. Parquet is usually not on a local
file system, so the feature currently works where the data mostly is not.

Measured on author/main

$ grep -ril "s3\|libcurl\|http" src/*.c src/*.h
src/columnar_parquet_reader.c

The single match is pg_sub_s32_overflow, so there is no object-store code, no HTTP
client and no credential handling anywhere in the tree. The FDW accepts two options,
path and partition_columns, and path is a local path or a glob over one.

Why this is a prerequisite rather than a nice-to-have

Apache Iceberg exists largely because Hive-style tables behaved badly on S3, and several
of its design choices only make sense there. Files are immutable because object stores do
not support in-place modification. A snapshot lists its files explicitly so a reader never
has to list a directory, which on an object store is slow. A commit is an atomic swap of
the metadata pointer, and because object stores have no atomic rename, the catalog exists
to provide that swap.

So #388 depends on this. An Iceberg reader without object storage can read a table
somebody copied to local disk, and not a table as deployed.

The existing note understates the work

design/PHASE_G_EXTERNAL_PARQUET_PLAN.md records the decision:

File access scope: DECIDED (2026-07-23) -- local filesystem first. Object storage
(S3 and S3-compatible such as MinIO, plus GCS/Azure) is a future todo behind the same
path/URL option: an s3://bucket/key path resolves through an object-store reader
while /path/file.parquet reads the local FS. The scan core is unchanged either way
because both just hand it bytes.

The first half is right and the reader really is agnostic. The last sentence is the part
to argue with. A local read is a microsecond-scale pread. An S3 read is a network round
trip of tens of milliseconds. Treating the two as interchangeable byte sources produces
something that works and is unusably slow.

What actually differs:

  • Ranged GETs. Read the footer, then only the column chunks the projection needs.
    Parquet's footer-at-the-end layout suits this, but the reader has to ask for byte
    ranges rather than stream a file.
  • Concurrency. Latency per request dominates, so chunk fetches want to be in flight
    together. The local path never needed that.
  • Retries and backoff. Transient failure is normal, not exceptional.
  • Caching. Refetching a footer per query is wasteful in a way it never was locally.
  • Credentials. FDW options land in pg_foreign_server in plain text, readable by
    anyone who can select from it. That is a design question before it is a coding one.

Suggested shape

  1. A byte-source abstraction behind the reader with the local path as the first
    implementation, so the seam is proven before any network code exists.
  2. An S3 and S3-compatible implementation with ranged GETs, retries and concurrency.
    MinIO makes this testable in CI without a cloud account.
  3. Credentials, deliberately, with the plaintext problem addressed rather than inherited.
  4. GCS and Azure after the seam has carried one implementation.

Steps 1 and 2 are the ones that decide whether this is viable.

Not in scope here

Writing to object storage is a separate issue, because multipart upload and its failure
semantics are a different problem from ranged reads.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions