Move whole tables between databases at wire speed, in bounded memory
Project description
apitap
Move whole tables between databases at wire speed, in bounded memory.
apitap is the open-source transfer engine behind apitap cloud — a Rust core with Python bindings, in the spirit of Polars. It moves data the way the databases themselves would: raw wire-format streams, parallel range pipes, atomic swaps, and memory that stays flat no matter how big the table is.
pip install apitap
import apitap
report = apitap.transfer(
"postgres://user:pass@src-host/db",
"postgres://user:pass@dst-host/db",
table="public.events",
)
print(f"{report.rows:,} rows in {report.elapsed_ms} ms over {report.parallel} pipes")
Routes
The URL schemes pick the route; each pair negotiates the fastest wire format both sides speak:
| route | how it moves |
|---|---|
postgres:// → postgres:// |
raw binary COPY passthrough — no row decode at all |
postgres:// → clickhouse:// |
binary COPY transcoded in-flight to RowBinary |
mysql:// → clickhouse:// |
binary wire protocol decoded straight into RowBinary |
mysql:// → postgres:// |
wire decode → binary COPY (exact decimals up to DECIMAL(65,30)) |
Every transfer stages into <table>__apitap_staging and swaps in atomically —
readers never see a partial table, an empty source never wipes a good one, and a
mid-run failure leaves the previous table untouched.
How fast?
10M rows, every tool capped at 16 vCPU / 4 GB, auto settings, stock Docker
databases — apitap measured from this exact published wheel (pip install apitap),
every number checksum-validated across engines:
| route | apitap | ingestr 1.0.75 | dlt 1.x (default) | dlt + pyarrow |
|---|---|---|---|---|
| Postgres → Postgres | 20.2 s | 500 s | 2 604 s | 708 s |
| Postgres → ClickHouse | 9.9 s | 111 s | 1 893 s | 360 s |
| MySQL → ClickHouse | 10.4 s | 97 s | 2 231 s | failed¹ |
| MySQL → Postgres | 22.5 s | 481 s | 2 899 s | failed¹ |
¹ dlt's pyarrow backend refuses MySQL DOUBLE without hand-written schema hints;
its connectorx backend was OOM-killed on all four routes at the same 4 GB cap apitap
runs in. Full methodology, validation queries, and honest caveats:
benchmarks/README.md.
In a 0.5 vCPU / 256 MB container apitap still completes every route (28–68 s) — the pipe count auto-derives from the cgroup's CPU and memory, so it never OOM-kills itself.
API
apitap.transfer(
src, dst, table, *,
dest_table=None, # defaults to `table`
parallel=None, # auto: CPU- and memory-aware; explicit value never overridden
cursor=None, # auto: integer PK; PK-less Postgres tables use TID ranges
chunk_bytes=None, # per-send coalescing, default 4 MiB
durable=True, # False = UNLOGGED staging on Postgres dests (~-30% wall,
# table stays unlogged until ALTER TABLE … SET LOGGED)
) -> TransferReport # .rows, .elapsed_ms, .parallel
mode="append" loads only rows past the destination's current max(cursor) —
the watermark is the destination data itself, so there are no state files to manage
and any failed run is safely re-runnable. mode="merge" (Postgres destinations)
upserts the delta by the destination's primary key. A 1M-row delta lands on a
10M-row table in ~10 s — cost is proportional to the delta, not the table. See the
usage guide for the semantics that stateless cursor incremental implies.
The GIL is released for the whole transfer. Errors are ValueError for bad input
(unknown table, unsupported type — always at probe time, never mid-copy) and
RuntimeError for transfer failures.
Full usage guide — connection URLs, per-route type mappings, durability semantics, troubleshooting: docs/usage.md.
Roadmap
- Postgres → Postgres · Postgres → ClickHouse · MySQL → ClickHouse · MySQL → Postgres
- Incremental sync —
mode="append"/mode="merge"(stateless watermark) -
read_postgres()→ Arrow / Polars - Snowflake / BigQuery destinations
- aarch64 + macOS wheels
License
MIT. Source: github.com/apitap/apitap-lib. The managed cloud (scheduling, always-on per-tenant workers, monitoring, a UI) is apitap.dev.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file apitap-0.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: apitap-0.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 2.8 MB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
36d6eb6158f49b11c510c5449ac8715ed7be96a844623a04c9a5025abf5a1fef
|
|
| MD5 |
47660d32e5046b41ee4b4f4e7a3397ca
|
|
| BLAKE2b-256 |
99eed2b9673fa8e1814ec7c4ad9e9301b6b142db2a7638275774c62d0e3fc895
|
Provenance
The following attestation bundles were made for apitap-0.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
publish.yml on apitap/apitap-lib
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
apitap-0.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
36d6eb6158f49b11c510c5449ac8715ed7be96a844623a04c9a5025abf5a1fef - Sigstore transparency entry: 2152045183
- Sigstore integration time:
-
Permalink:
apitap/apitap-lib@e9d1052bc68e0832f238873e2b7ef9a109f71b0f -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/apitap
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e9d1052bc68e0832f238873e2b7ef9a109f71b0f -
Trigger Event:
push
-
Statement type: