How to Ingest Multiple Tables Efficiently

There are a few important considerations when ingesting many tables:

  • Avoid loading all tables into memory at once, if possible.

  • Ensure additions are atomic. If anything fails, the final state should be the same as the initial state.

Setup

The input data are in CSV files. Each file contains a timestamp column and one value column per device.

from datetime import datetime, timedelta

from chronify import (
    ColumnDType,
    CsvTableSchema,
    DatetimeRange,
    Store,
    TableSchema,
)

store = Store.create_in_memory_db()
time_config = DatetimeRange(
    time_column="timestamp",
    start=datetime(2020, 1, 1, 0),
    length=8784,
    resolution=timedelta(hours=1),
)
src_schema = CsvTableSchema(
    time_config=time_config,
    column_dtypes=[
        ColumnDType(name="timestamp", dtype="datetime"),
        ColumnDType(name="device1", dtype="float"),
        ColumnDType(name="device2", dtype="float"),
        ColumnDType(name="device3", dtype="float"),
    ],
    value_columns=["device1", "device2", "device3"],
    pivoted_dimension_name="device",
)
dst_schema = TableSchema(
    name="devices",
    time_config=time_config,
    value_column="value",
    time_array_id_columns=["device"],
)

Automated through chronify

Chronify will manage the database connection and errors.

store.ingest_from_csvs(
    (
        "/path/to/file1.csv",
        "/path/to/file2.csv",
        "/path/to/file3.csv",
    ),
    src_schema,
    dst_schema,
)

Self-Managed

Wrap the additions in a backend transaction. Any tables or views created within the block are automatically dropped if an exception is raised.

with store.backend.transaction():
    store.ingest_from_csv("/path/to/file1.csv", src_schema, dst_schema)
    store.ingest_from_csv("/path/to/file2.csv", src_schema, dst_schema)
    store.ingest_from_csv("/path/to/file3.csv", src_schema, dst_schema)

Note

Real database transaction semantics depend on the backend. The DuckDB and SQLite backends issue a real BEGIN / COMMIT / ROLLBACK around the block, so partial inserts to existing tables are rolled back on failure. The Spark backend does not support transactions; the context manager falls back to best-effort cleanup that drops any tables or views created inside the block when an exception is raised, but rows appended to pre-existing tables cannot be rolled back.