r/ETL

▲ 18 r/ETL+1 crossposts

From Kafka to Postgres in under 50ms with Spark RTM

I’ve been building a lot with Spark RTM lately and have been pleasantly surprised with the results of my experiments so far. I built an app that shows trading volumes in real time and pushed some market data into it from a Kafka topic. The end-to-end latency is quite impressive: p50 at 41ms, p90 at 60ms and p99 at 71ms. Much lower than what I was seeing with continuous or for each batch in Spark before, where it was hard to go below 1s latency. And this is the end to end latency, which includes significant latency from serving layer (lakebase) and the browser.

I’m happy to share more about my experience and how to create real time apps with Spark RTM and Lakebase if folks are interested.

reddit.com
▲ 27 r/ETL+1 crossposts

New to Databricks, question about unit testing SQL pipelines

I’m currently new to Databricks, and I’m writing unit tests for a pipeline that is entirely in SQL, with Bronze, Silver, and Gold tables.

I asked how I should approach unit testing, and I was told that since unit tests are typically written in Python, I could wrap the pipeline logic in Python files and then use pytest to test those files.

But I’m confused about how this would work in a real production environment.

For example, if we push everything to GitHub and later make a change to the SQL pipeline, we would also have to make the same change in the corresponding Python files so that the unit tests continue to work. That means maintaining the same logic in two places, which doesn’t seem like a good production practice.

So what is the recommended approach for unit testing a SQL-based Databricks pipeline? Is there a way to test the actual SQL transformations directly without duplicating the pipeline logic in Python?

I’m still learning Databricks, so I’d really appreciate some guidance on the proper production approach.

reddit.com
u/WolfscAr1 — 2 days ago
▲ 3 r/ETL

How do I test SCD Type 2

I was given the task to perform type 2 validation check. Can I know the possible approaches

reddit.com
u/BugSquare4344 — 2 days ago
▲ 8 r/ETL

How do you handle schema changes without breaking ETL pipelines?

What practices or tools have helped you manage source changes safely in production?

reddit.com
u/Effective_Ocelot_445 — 2 days ago
▲ 9 r/ETL

When a batch of 1,000 transactions has 5 bad rows, do you isolate the whole batch or dead-letter just the 5 rows?

Hey folks,

If an ingestion batch of 1,000 payment records arrives where 995 rows are completely valid and 5 rows have missing critical fields (like receiver_account is NULL):

Do your pipelines usually:

  1. Reject/Hold the entire batch to preserve batch atomicity and ordering?
  2. Accept the 995 valid rows and route the 5 bad rows to a Dead-Letter Queue (DLQ)?

What are the trade-offs you run into with transactional integrity versus keeping pipeline throughput moving

reddit.com
u/No_Many1887 — 3 days ago
▲ 8 r/ETL

Interlace — SQL and Python in one graph

Author here, so treat this accordingly. Not selling anything, MIT licensed, no company behind it, no hosted tier planned.

Background: I'm a CTO at a small UK fintech and I've spent the last few years assembling the same stack over and over. dbt/sqlmesh for transformation, something for orchestration, something else for ingestion or reverse ETL. Multiple deployments, multiple failure modes, and the seams between them are where time is lost.

The specific thing that annoyed me enough to build something was Python models. In dbt they're a second-class citizen that needs a cloud warehouse with a Python runtime. In SQLMesh they're better but still feel bolted on. I wanted a .py model to sit mid-DAG with SQL either side, in both directions, and for the planner to not care which I'd written.

Obviously I'm aware dbt and SQLMesh have both been bought by FiveTran, i cover that in this article.

So that's the core of it:

python

# models/enriched_events.py
@model()                  # param name IS the dependency
def enriched_events(raw_events):
  for batch in raw_events.reader():     # Arrow in, Arrow out, bounded memory 
    yield add_revenue(batch)

sql

-- models/event_summary.sql — SQL straight over the Python
SELECT country, count(*) FILTER (WHERE is_conversion) AS conversions
FROM enriched_events GROUP BY country

The Python model is a plain function. You can call it in a test with no warehouse and no session.

The rest of the design, briefly:

  • IR is a sqlglot AST, not Jinja templates. Dependencies come from parsing the SQL, not from ref(). No pandas in core, everything moves as Arrow RecordBatchReader.
  • By default models builds into a fingerprinted physical table and environments are just views over those. A dev environment reuses prod's tables for free, promotion is an atomic view swap, and rollback is the same swap backwards.
  • plan / apply in the terraform sense. Changes classify as breaking / non-breaking / forward-only, and column-level lineage impact analysis proves when a downstream output is unchanged so it gets reused rather than rebuilt.
  • Streams are durable. POST an event, it's fsynced before the 200, deduplicated by idempotency key, and the materialiser commits data and watermark in the same warehouse transaction. Exactly-once without distributed coordination.
  • One process. interlace serve is the web UI, HTTP API, scheduler and stream ingestion. No Airflow, no broker.
  • DuckDB by default, DuckLake one config line away, Postgres natively over ADBC.

Where it's weak, and I'd rather you heard it from me:

  • Single maintainer. That's the honest risk with any tool like this and I'm not going to pretend otherwise.
  • Snowflake, BigQuery, Redshift and MotherDuck adapters are wired and dialect-correct but have not been run against a live account. Alpha, and labelled as such.
  • Developed on Linux, CI is Linux only. Nothing in the codebase is platform-specific and every dependency ships mac/Windows wheels, so both should work, but neither is tested.
  • Spark is beta.
  • It's new. Real production mileage is limited to my own use.

I've written up a full jaffle_shop migration (a real dbt project, end to end) if you want to see what moving something across actually looks like rather than taking my word for it.

What I'm after here is criticism rather than stars. Specifically:

  1. If you run dbt today, what would actually stop you trialling this on one pipeline? I suspect the answers are "single maintainer" and "my warehouse is Snowflake", but I'd rather know than guess.
  2. Does the fingerprinted-table-plus-view-swap model break in a way I haven't hit yet? I'm particularly interested in whether anyone's tried this at a scale where the number of snapshots becomes a catalog problem.
  3. Anyone doing durable ingestion in-process like this rather than via Kafka/Kinesis? Interested in what bit it, if so.

Repo: github.com/interlace-sh/interlace
Comparison against dbt and SQLMesh, including where they're ahead: interlace.sh/why

interlace.sh
u/ma1f — 5 days ago
▲ 3 r/ETL

I helped a client migrate from Fivetran to Ingestr, cutting their cost by 10x

I wrote a whole article about it, link in comments, but I just want share a quick summary here.

This client had over 30 data sources they were ingesting from, it looked something like this:

  • posgresql -> snowflake ~100m/rows/mo (some spikes to 1b/rows/mo)
  • all other data sources -> snowflake ~100m/rows/mo (some spikes to 1b/rows/mo)

This was costing them almost $100k per year (~$8.5k/month) and it was especially hard to justify because the spikes would normally happen during busy seasons and they already had tight margins.

They had already started using ingestr (the free open source version) to offload some of their smaller jobs and running the jobs on an EC2 instance.

Once they migrated over their production database to snowflake job, they cut the cost significantly - the total server cost came to less than $500/month.

If this resonates to anyone, and you want to put on your resume "helped reduce ingestion cost by x amount", then check out ingestr repo and docs.

reddit.com
u/uncertainschrodinger — 6 days ago
▲ 3 r/ETL

What’s the most common reason ETL pipelines fail in production?

Is it bad source data, schema changes, performance issues, monitoring gaps, or something else?

reddit.com
u/Effective_Ocelot_445 — 10 days ago
▲ 45 r/ETL+2 crossposts

lazyflow – a kdash-style TUI for Apache Airflow 3

The Airflow 3 web UI is painful enough that I avoid opening it, so I built lazyflow.

Airflow 3 only (built on the v2 REST API). Single static Go binary.

u/heekey1 — 12 days ago
▲ 3 r/ETL

Free alternative to Snowflake for a long-running dbt thesis project?

Snowflake's free trial (30 days/$400 credits) is too short for a university thesis running several months – need something that doesn't expire mid-project. What are people using instead? Also curious if anyone knows of a longer free tier/subscription (student program, extended trial, etc.) for Snowflake specifically before I give up on it.

reddit.com
u/roksolana_shendiukh — 14 days ago
▲ 8 r/ETL+1 crossposts

[Hiring] - AI / GenAI / Python / Snowflake Roles

Hey everyone! 👋

I came across a few openings at my company and thought I’d share them here in case anyone is looking for a switch or a new opportunity.

Here are the roles available:

📍 PAN India

AI Engineer: Exp: 5-8 yrs

Python + GenAI: Exp: 8-10 yrs

📍 Pune

MS Azure AI/ML Exp: 5-8 yrs

Python + GenAI : Exp: 8-10 yrs

📍 Hyderabad

Data Scientist: Exp: 5-8 yrs

AI Cognitive + Snowflake + Python: Exp: 8-10yrs

📍 Bengaluru

Data Scientist: Exp: 5-8 yrs

Gen AI

📍 Chennai: Exp: 8-10 yrs

Data Scientist

💰 Salary: Not specified in the job posting / Depends on experience and role.

If you're interested in any of these roles, feel free to DM me or directly send me your updated resume at:

📧 dkviswa889@gmail.com

To make things easier, please include:

First Name:

Last Name:

Mobile Number:

Gmail ID:

Country:

Updated Resume: Attached

📌 Email subject / resume name:

Job_Title_FirstName_LastName

Example:

Python_GenAI_Rahul_Kumar

I'll try my best to refer suitable profiles. 😊

Good luck to everyone who's looking! 🤝

If you've any queries on the job role, pls feel free to DM! I'll share the skill stack. So you'll get an idea 💡.

reddit.com
u/Ghost_8897 — 13 days ago
▲ 18 r/ETL

riko: 10 years of evolving a Python ETL library

Ten years ago I x-posted riko: a Python stream-processing library inspired by Yahoo! Pipes here. Admittedly, the r /Python post garnered a lot more attention, so this time around I'm posting it here first :).

Since then, riko has seen sporadic development with a burst of activity in 2016/17, and again in 2020. I recently revamped riko over the past month and wanted to share how it's evolved. The underlying idea has (and always will be) streams of Python records flowing through composable pipes. But here's whats changed:

Twisted -> async/await

I built the original async implementation on Twisted. I've now migrated it to native async/await backed by AnyIO. The new API is briefly:

from riko import AsyncPipe, get_path, run

async def main():
    flow = AsyncPipe("fetch", conf={"url": get_path("feed.xml")})

    async for item in flow:
        print(item["title"])
        break

run(main)
# Donations

The broader implication is that async is a first-class citizen at full feature parity with the sync model. You can now run any pipeline four ways: synchronously (inline), asynchronously (concurrent), or in parallel with a local synchronous thread or process pool.

Fluent, chainable pipes

Early riko code often involved importing individual pipe implementations directly. The public API now removes that boilerplate.

from riko import SyncPipe, get_path

flow = (
    SyncPipe("fetch", conf={"url": get_path("feed.xml")})     
    .filter(conf={"rule": {"field": "title", "op": "contains", "value": "q"}}) 
    .sort(conf={"rule": {"field": "title"}}) 
)

next(flow)
# "FAQ"

Refined looping

Over the years, I went back and forth about loops. I finally settled on implicit looping by default, with an escape hatch for explicit loops when they're actually needed. For example, even though strreplace operates on an individual item, it can consume a source stream:

from riko import get_path
from riko.modules.fetch import pipe as fetch
from riko.modules.strreplace import pipe as strreplace

replace_conf = {"rule": {"find": "Donations", "replace": "Bike Donations"}}
stream = fetch(conf={"url": get_path("feed.xml")})
replaced = strreplace(stream, conf=replace_conf, field="title")
next(replaced)["strreplace"]
# "Bike Donations"

Note: This uses the private riko.modules.* imports just to showcase the new behavior. SyncPipe and AsyncPipe always performed implicit looping.

Pub/sub / fan-out

This is my most exciting addition. riko already had a split module for sending one stream to multiple consumers. But it eagerly materializes the stream in memory. The new send / receive pub/sub model is push based and lazy, so it fans items out as the source is consumed. A very basic example...

from riko import SyncPipe

items = [{"title": "Gravity paper"}, {"title": "Breaking: riko 4.0"}]
receiver = SyncPipe("receive", conf={"name": "receiver"})
next(receiver)  # prime it

sender = SyncPipe("send", items, others=["receiver"])
_ = list(sender)  # Consuming the sender drives delivery

# Drain the receiver independently
[item["title"] for item in receiver if "title" in item]
# ["Gravity paper", "Breaking: riko 4.0"]

The sync and async implementations are backed by generator-based coroutines and AnyIO channels, respectively.

Internals

I really modernized things under the hood too: riko is now fully typed (and ships py.typed); has 86% test coverage (including Python examples in the RST documentation); and leverages uv, pytest, Ruff, Pyright, tox, and GitHub Actions.

GitHub: https://github.com/nerevu/riko

I'd love to get feedback from the community. Is riko something you would use? If so, how? What changes/additions do you think riko needs?

u/reubano — 13 days ago