Skip to content

Object storage writes for the export functions, which is not the read path reversed #394

Description

@ChronicallyJD

The export functions write to a local path only. Since Parquet consumers generally read
from object storage, an export usually has to be copied somewhere else by hand before
anything can use it.

This is the write half of #393, and it is filed separately because it is a different
problem rather than the same one in reverse.

What exists today

All three take a plain path, and it is a local path:

pgcolumnar.export_arrow(rel regclass, path text)
pgcolumnar.export_parquet(rel regclass, path text)
pgcolumnar.parallel_export_parquet(target regclass, path text, workers int DEFAULT NULL)

The argument is already the right shape. If #393 introduces a URL-dispatched byte source,
these take a byte sink at the same seam, and no caller has to change.

Why it is not just #393 with the arrows reversed

Reads are ranged GETs of an object that already exists and does not change. Writes are
not symmetric with that.

  • Multipart upload. Large objects go up in parts, each acknowledged, then completed.
    That is a protocol with state, not a single request.
  • Failure leaves debris. An abandoned multipart upload can linger and bill until it
    is aborted, so the failure path has to clean up rather than just report.
  • No partial visibility. An object appears whole or not at all, which is a difference
    in what "the file so far" means to a concurrent reader.
  • parallel_export_parquet writes a directory of files from several workers. With
    N workers uploading concurrently, "the export succeeded" needs defining when some parts
    completed and others did not. Locally a half-written directory is at least inspectable.
    In a bucket it is a set of objects with no marker saying which run produced them.

That last one is the interesting design question and it does not exist on the read side.

Why it matters beyond convenience

Export is the interoperability story: it is how a columnar table becomes something Spark,
Trino, DuckDB or a warehouse can consume. Those consumers read from buckets. Making the
user stage a local file and copy it up is the step that makes the feature look like a
demo rather than a pipeline.

Suggested shape

Sequenced behind #393, because the credential handling, the retry policy and the URL
dispatch should be built once and shared.

  1. Single-object upload for export_parquet and export_arrow, multipart above a size
    threshold, with abort-on-failure.
  2. Decide and document what parallel_export_parquet guarantees when some workers fail.
  3. Only then consider whether a completion marker or manifest is wanted, which is also
    the point where this starts to touch Review what it would take to support Apache Iceberg #388.

Note

This is not currently recorded anywhere. design/PHASE_G_EXTERNAL_PARQUET_PLAN.md covers
object storage for reads and says nothing about writes, and design/ROADMAP.md does not
mention object storage in either direction. See #395 for that gap.

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