How to Replicate Databricks Lakebase to Snowflake with Sling
Last updated: July 2026
Databricks Lakebase is a serverless Postgres database, built on the Neon engine Databricks acquired in 2025 and generally available since February 2026. It holds operational, transactional data: the reads and writes an application or an AI agent makes in real time, kept separate from the analytical tables in your lakehouse or warehouse. The idea is to keep the OLTP store fast and small and move data out to a warehouse when you need to run analytics across it.
Snowflake is a common destination for that analytics half, which raises an obvious question. How do you get data out of Lakebase and into Snowflake without standing up a separate ETL platform to do it?
Here’s the thing that makes it easy: Lakebase is Postgres. Not Postgres-like, actual Postgres, running version 17 and speaking the standard wire protocol on port 5432. Anything that connects to Postgres connects to Lakebase. That includes Sling, which treats Lakebase as an ordinary postgres source and moves it to Snowflake with a few lines of YAML.
This guide walks through that move end to end. The row counts, timings, and type mappings below come from a real replication run: a Postgres source loaded with a demo e-commerce schema, replicated into a live Snowflake warehouse. Because Lakebase presents as standard Postgres, the mechanics don’t change whether the source is a self-hosted Postgres or a Lakebase endpoint. Only the host and the credentials change.
Installation
Sling is a single binary with no runtime dependencies. Install it however suits your setup:
# macOS / Linux
curl -fsSL https://slingdata.io/install.sh | bash
# Windows
irm https://slingdata.io/install.ps1 | iex
# Python
pip install sling
Confirm it’s on your path:
sling --version
Connecting Sling to Lakebase
A Lakebase instance exposes a standard Postgres connection: a hostname (Databricks generates one that looks like ep-<id>.databricks.com), port 5432, and a default database named databricks_postgres. SSL is required — Lakebase rejects unencrypted connections — so the connection string carries sslmode=require.
The one decision worth making up front is authentication. Lakebase supports two methods, and they behave very differently for a data-movement tool. You can pass an OAuth token as the password, which is convenient for poking around interactively, but the token expires after an hour, so a scheduled replication that runs overnight will fail when it goes stale. Or you can use a native Postgres role and password, which doesn’t expire and works with connection pooling. Use the second one for Sling.
Create a Postgres role in Lakebase, give it read access to the tables you’re replicating, and use that role’s credentials. Then register the connection with Sling. Since Lakebase is Postgres, the connection type is postgres:
sling conns set lakebase type=postgres \
host=ep-your-instance.databricks.com port=5432 \
user=your_role password=your_password \
database=databricks_postgres sslmode=require
Or as a connection string:
sling conns set lakebase \
url="postgres://your_role:[email protected]:5432/databricks_postgres?sslmode=require"
Connecting Sling to Snowflake
Snowflake authenticates with an account identifier, a user, and either a password or a key pair. The Sling user needs USAGE on the warehouse, database, and target schema, plus CREATE TABLE on the schema so Sling can create the destination tables.
sling conns set snowflake type=snowflake \
account=your-account user=your-user password=your-password \
database=analytics warehouse=compute_wh role=your-role
Test both connections
sling conns test lakebase
sling conns test snowflake
8:41AM INF success!
If the Lakebase test hangs before failing, the instance may have scaled its compute to zero while idle — Lakebase spins compute down when nothing is connected and back up on demand. Give it a moment and test again. If it fails outright, check that sslmode=require is present and that your role has login rights.
The source tables
The demo source is a small e-commerce schema, the kind of transactional data that lives in an OLTP store: 15,000 customers, 3,000 products, and 90,000 orders. The orders table carries an ordered_at timestamp, which matters for incremental loads later, plus a nullable promo_code column so the type-handling examples have something to point at.
-- on the Lakebase source
select 'customers' as t, count(*) as c from demo_lakebase_snowflake.customers
union all select 'products', count(*) from demo_lakebase_snowflake.products
union all select 'orders', count(*) from demo_lakebase_snowflake.orders;
t c
customers 15000
products 3000
orders 90000
Full refresh: the first load
A replication is one YAML file. The defaults block sets the mode and how targets are named; streams lists what to move. The {stream_table} token maps each source table to a Snowflake table of the same name.
# replication.yaml
source: lakebase
target: snowflake
defaults:
mode: full-refresh
object: demo_lakebase_snowflake.{stream_table}
streams:
demo_lakebase_snowflake.customers:
demo_lakebase_snowflake.products:
demo_lakebase_snowflake.orders:
Run it:
sling run -r replication.yaml
INF Sling Replication [3 streams] | lakebase -> snowflake
INF [1 / 3] running stream demo_lakebase_snowflake.customers
INF writing to target database [mode: full-refresh]
INF created table "DEMO_LAKEBASE_SNOWFLAKE"."CUSTOMERS_TMP"
INF created table "DEMO_LAKEBASE_SNOWFLAKE"."CUSTOMERS"
INF inserted 15000 rows into "DEMO_LAKEBASE_SNOWFLAKE"."CUSTOMERS" in 15 secs [993 r/s] [1.3 MB]
...
INF [3 / 3] running stream demo_lakebase_snowflake.orders
INF created table "DEMO_LAKEBASE_SNOWFLAKE"."ORDERS_TMP"
INF inserted 90000 rows into "DEMO_LAKEBASE_SNOWFLAKE"."ORDERS" in 13 secs [6,814 r/s] [5.5 MB]
INF execution succeeded
INF Sling Replication Completed in 40s | lakebase -> snowflake | 3 Successes | 0 Failures
Three tables, 108,000 rows, 40 seconds. Two details in that log are worth noticing. First, Sling writes to a _TMP table and swaps it in once the load is clean, so a failed run leaves the existing table untouched rather than half-loaded. Second, Sling stages the data and loads it into Snowflake in bulk with COPY INTO rather than inserting row by row — that’s why 90,000 rows land in 13 seconds.
You don’t have to pre-create the tables. Sling creates them on first run and adjusts them on later runs when the schema changes.
A note on branching
Lakebase inherits Neon’s copy-on-write branching: you can create an instant, zero-copy clone of your database. That’s a useful trick here. Instead of pointing the replication at your production instance and adding read load to the same compute your application uses, create a branch and replicate from that. The branch is a consistent snapshot, the read traffic hits separate compute, and you tear the branch down when the load finishes. Nothing about the Sling side changes — you just point the host at the branch’s endpoint.
Verification
Trust the load, but check it. Run a count and a min/max against the target:
sling conns exec snowflake \
"select count(*) as orders, min(ordered_at) as first_order, max(ordered_at) as last_order from demo_lakebase_snowflake.orders"
ORDERS FIRST_ORDER LAST_ORDER
90000 2025-01-01 00:02:00 +0000 UTC 2025-03-04 12:01:00 +0000 UTC
And a sample:
sling conns exec snowflake \
"select order_id, customer_id, quantity, amount, promo_code, ordered_at from demo_lakebase_snowflake.orders order by order_id limit 5"
ORDER_ID CUSTOMER_ID QUANTITY AMOUNT PROMO_CODE ORDERED_AT
1 2 2 10.740000 PROMO1 2025-01-01 00:02:00 +0000 UTC
2 3 3 17.220000 PROMO2 2025-01-01 00:03:00 +0000 UTC
3 4 4 24.440000 PROMO3 2025-01-01 00:04:00 +0000 UTC
4 5 5 32.400000 2025-01-01 00:05:00 +0000 UTC
5 6 1 6.850000 PROMO5 2025-01-01 00:06:00 +0000 UTC
Row count matches the source, the timestamps survived the trip, and the decimal amounts kept their precision. Notice row 4: the promo_code is empty because it was null in the source, and that nullability carried across intact.
Type mapping
Postgres and Snowflake don’t share a type system, so Sling maps between them. Read the target schema to see the result:
sling conns exec snowflake \
"select column_name, data_type from information_schema.columns
where table_schema='DEMO_LAKEBASE_SNOWFLAKE' and table_name='ORDERS'
order by ordinal_position"
COLUMN_NAME DATA_TYPE
ORDER_ID NUMBER
CUSTOMER_ID NUMBER
PRODUCT_ID NUMBER
QUANTITY NUMBER
AMOUNT NUMBER
PROMO_CODE TEXT
ORDERED_AT TIMESTAMP_NTZ
Postgres integers become Snowflake NUMBER, and numeric(10,2) also becomes NUMBER with its precision preserved, so the order amounts keep full decimal precision instead of collapsing into a float. varchar and text become TEXT, and timestamp becomes TIMESTAMP_NTZ. When a clean mapping isn’t available, Sling renders the value as a string rather than dropping precision silently, and you can override any column with a columns: block in the stream if you need a specific Snowflake type.
Incremental loads: only what changed
Full refresh is fine for a first load or a small dimension table. For anything that grows, you want to move only the new rows. Switch the mode to incremental and tell Sling which column tracks change and which column identifies a row:
# replication-incremental.yaml
source: lakebase
target: snowflake
defaults:
mode: incremental
object: demo_lakebase_snowflake.{stream_table}
primary_key: [order_id]
update_key: ordered_at
streams:
demo_lakebase_snowflake.orders:
Say 2,500 new orders land in Lakebase. Re-run with the incremental file:
sling run -r replication-incremental.yaml
INF Sling Replication | lakebase -> snowflake | demo_lakebase_snowflake.orders
INF getting checkpoint value (ordered_at)
INF created table "DEMO_LAKEBASE_SNOWFLAKE"."ORDERS_TMP"
INF inserted 2500 rows into "DEMO_LAKEBASE_SNOWFLAKE"."ORDERS" in 10 secs [245 r/s] [152 kB]
INF execution succeeded
The line that does the work is getting checkpoint value (ordered_at). Sling reads the maximum ordered_at already in the Snowflake target, then pulls only source rows newer than that. There’s no separate state file to manage — the target table is the checkpoint. The primary_key lets Sling merge, so a row that was updated rather than inserted is upserted instead of duplicated.
Run it again with nothing new on the source and you get a clean no-op:
INF getting checkpoint value (ordered_at)
WRN no data or records found in stream. Nothing to do.
INF inserted 0 rows into "DEMO_LAKEBASE_SNOWFLAKE"."ORDERS" in 7 secs [0 r/s]
INF execution succeeded
Zero rows, no error. The warning is Sling telling you there was nothing past the checkpoint, which is exactly what you want from a scheduled job that fires on a cron whether or not there’s anything to move.
A final count confirms the math:
sling conns exec snowflake "select count(*) as orders, max(ordered_at) as last_order from demo_lakebase_snowflake.orders"
ORDERS LAST_ORDER
92500 2025-03-06 05:41:00 +0000 UTC
90,000 from the first load plus 2,500 from the incremental run, with the high-water mark advanced to the newest order.
Replicating many tables at once
Listing every table by hand doesn’t scale past a handful. Use a wildcard in streams and the {stream_table} token in the target object to move a whole schema:
source: lakebase
target: snowflake
defaults:
mode: incremental
object: demo_lakebase_snowflake.{stream_table}
primary_key: [id]
update_key: updated_at
streams:
demo_lakebase_snowflake.*:
Every table in the schema gets replicated, each into a Snowflake table of the same name. You can still override a single table by listing it explicitly below the wildcard — the explicit entry wins. This is the pattern to reach for when you’re mirroring a whole Lakebase database into the warehouse.
Scheduling
Once the YAML works, scheduling is a cron entry:
0 * * * * cd /path/to/configs && sling run -r replication-incremental.yaml >> /var/log/sling.log 2>&1
Because incremental mode tracks its checkpoint against the target, each run is idempotent — if one fails or is skipped, the next picks up exactly where the last left off. One thing to keep in mind on a schedule: use a native Postgres password for the Lakebase role, not an OAuth token. Tokens expire after an hour, and a job that runs overnight will hit a stale one.
If you’d rather not babysit cron, the Sling Platform handles scheduling, alerting, and run history, while keeping the data movement itself on your own infrastructure — nothing routes through a vendor control plane.
Conclusion
Because Lakebase is real Postgres, moving data out of it is not a special case. You register it as a postgres connection, point Sling at Snowflake, and write two YAML files: full-refresh for the first load, incremental for everything after. Sling loads into Snowflake in bulk, maps the types sensibly by default, and keeps the checkpoint in the target table so there’s no separate state store to lose.
The numbers here are from a real run: 108,000 rows on the first load in 40 seconds, 2,500 rows on the incremental in 10, a clean no-op when there’s nothing to move. Point Sling at your own Lakebase endpoint, ideally a branch, and you’ll get the same shape.
Related guides
The Lakebase-to-Snowflake path is the standard-Postgres-to-Snowflake path, so these companion walkthroughs apply directly:
- Export PostgreSQL to Snowflake — the same replication against a self-hosted Postgres, with more on transformations and advanced options
- Snowflake to Postgres — the reverse direction, when Snowflake is the source of truth
- Postgres to DuckDB — a local, zero-cloud target for quick analysis
- Extract databases into DuckLake — Lakebase to a lakehouse table format instead of a warehouse
If you’re weighing Sling against a managed connector for this job, the Sling vs Fivetran comparison covers the tradeoffs. For the reasoning behind Sling’s single-binary, no-control-plane design, see the Sling blog.
Frequently asked questions
Does Sling need a special Lakebase connector?
No. Lakebase speaks the standard Postgres wire protocol on port 5432, so Sling connects to it with the ordinary postgres connection type. There’s nothing Lakebase-specific to configure beyond the hostname, credentials, and sslmode=require.
Should I use an OAuth token or a password for the Lakebase connection?
Use a native Postgres role and password. Lakebase’s OAuth tokens expire after an hour, which breaks scheduled or long-running replications. A native password doesn’t expire and works with connection pooling, so it’s the right fit for a data-movement tool.
Will replicating add load to my production Lakebase instance?
It can, since the read query runs against your instance’s compute. To avoid competing with application traffic, use Lakebase’s copy-on-write branching to create a zero-copy clone and replicate from the branch. The read load hits separate compute, and you delete the branch when the run finishes.
How are Postgres numeric, jsonb, and uuid columns handled in Snowflake?
numeric maps to Snowflake NUMBER with precision preserved, so decimal amounts don’t get flattened to floats. jsonb lands in a VARIANT column and stays queryable with : and FLATTEN(). uuid maps to TEXT by default; override it with a columns: block if you want a different type.
Can I do incremental Lakebase → Snowflake with deletes?
Sling’s incremental mode propagates inserts and updates via the update_key and merges on the primary_key, but it does not detect physical deletes in the source. For delete-aware syncs, soft-delete with a deleted_at column and filter in queries, run mode: full-refresh on small enough tables, or use mode: snapshot to keep historical versions.
Does the source have to be Lakebase specifically?
No — this exact configuration works against any Postgres 16 or 17 instance. Lakebase is just Postgres with serverless compute and branching, so if you later move the transactional store somewhere else, the replication YAML doesn’t change; only the connection host does.


