Summary
Parquet export and lakehouse blob paths encode parquet in a streaming fashion (temp NDJSON spill + row-group encoding), but upload still loads the entire finished file into memory before calling store.BlobStore.Put. Peak RAM at upload time is therefore O(compressed parquet file size), not bounded by row-group size.
This is documented as an intentional limitation in pkg/view/README.md, docs/parquet-on-fhir-interop.md, and docs/CHANGELOG-parquet-analytics.md.
Current behavior
- Parquet is written to a temp file on disk (
writeLakehouseParquetBlob, writeParquetExportFile).
- The full file is read back with
os.ReadFile.
- Bytes are passed to
BlobStore.Put via store.BlobObject.Data []byte.
Affected call sites:
pkg/analytics/lakehouse.go — writeLakehouseParquetBlob
pkg/view/export_service.go — writeParquetExportFile → ExportFileStore.Put
store.BlobStore is a whole-object API:
type BlobObject struct {
Key string
ContentType string
Size int64
Data []byte // full payload required today
}
type BlobStore interface {
Put(ctx context.Context, obj BlobObject) error
// ...
}
Why not fixed in Parquet-on-FHIR follow-ups
Encoding was optimized (single-pass spill, row groups, no CollectMatchingResources for large exports). Fixing upload memory requires a cross-cutting BlobStore contract change and updates to every implementation (S3/GCS adapters, SQLite chunk store, in-memory test fakes, export artifact stores).
Impact
| Scenario |
Risk |
| Moderate exports (tens of MB parquet) |
Acceptable in-process buffering |
| Multi-GB lakehouse blob uploads |
Risk of OOM; must use filesystem partitions (LakehouseConfig.RootDir) today |
$viewdefinition-export artifacts to blob-backed storage |
Same O(file) peak at Put |
Docs recommend ~2× compressed parquet size peak RAM at blob upload (file on disk + []byte for Put).
Proposed approach
- Extend
store.BlobStore (or add BlobStoreWithUpload) with a streaming put, e.g.:
PutStream(ctx, key, contentType, size int64, r io.Reader) error, or
PutFromPath(ctx, key, contentType, path string) error for backends that support file-based upload.
- Optionally support multipart/resumable upload for S3/GCS adapters.
- Update lakehouse blob and export artifact paths to stream from the temp parquet file without
os.ReadFile.
- Keep
Put(BlobObject) for small inline payloads; deprecate or document size limits.
Workaround (today)
Use filesystem lakehouse partitions (LakehouseConfig.RootDir) for large exports — parquet streams directly to the final path with no full-file RAM buffer.
References
Acceptance criteria
Summary
Parquet export and lakehouse blob paths encode parquet in a streaming fashion (temp NDJSON spill + row-group encoding), but upload still loads the entire finished file into memory before calling
store.BlobStore.Put. Peak RAM at upload time is therefore O(compressed parquet file size), not bounded by row-group size.This is documented as an intentional limitation in
pkg/view/README.md,docs/parquet-on-fhir-interop.md, anddocs/CHANGELOG-parquet-analytics.md.Current behavior
writeLakehouseParquetBlob,writeParquetExportFile).os.ReadFile.BlobStore.Putviastore.BlobObject.Data []byte.Affected call sites:
pkg/analytics/lakehouse.go—writeLakehouseParquetBlobpkg/view/export_service.go—writeParquetExportFile→ExportFileStore.Putstore.BlobStoreis a whole-object API:Why not fixed in Parquet-on-FHIR follow-ups
Encoding was optimized (single-pass spill, row groups, no
CollectMatchingResourcesfor large exports). Fixing upload memory requires a cross-cuttingBlobStorecontract change and updates to every implementation (S3/GCS adapters, SQLite chunk store, in-memory test fakes, export artifact stores).Impact
LakehouseConfig.RootDir) today$viewdefinition-exportartifacts to blob-backed storagePutDocs recommend ~2× compressed parquet size peak RAM at blob upload (file on disk +
[]byteforPut).Proposed approach
store.BlobStore(or addBlobStoreWithUpload) with a streaming put, e.g.:PutStream(ctx, key, contentType, size int64, r io.Reader) error, orPutFromPath(ctx, key, contentType, path string) errorfor backends that support file-based upload.os.ReadFile.Put(BlobObject)for small inline payloads; deprecate or document size limits.Workaround (today)
Use filesystem lakehouse partitions (
LakehouseConfig.RootDir) for large exports — parquet streams directly to the final path with no full-file RAM buffer.References
pkg/view/README.md(Parquet export sizing)docs/parquet-on-fhir-interop.md(Memory behavior table)Acceptance criteria
BlobStore(or companion interface) supports upload fromio.Readeror file path without requiring full in-memory[]bytewriteLakehouseParquetBloband export artifact upload use streaming putPut(BlobObject)callers for small blobs remain supported[]byteequal to file size (or documents backend-specific streaming behavior)