How Prefect (and a Beefy Hetzner Auction Box) Rules Our Price-Tracking Empire

●4 MIN READ

How Prefect (and a Beefy Hetzner Auction Box) Rules Our Price-Tracking Empire

I’ve talked multiple times about my goal to build a price-tracking empire. What started as a single, fragile app back in 2016 has evolved into a multi-region, multi-store data operation.

But when you’re dealing with millions of products across Bol.com, Walmart, and beyond, your code can quickly turn into a chaotic spaghetti monster of broken scripts. As we started scaling up, I brought in some talented freelancers to help speed up development. Best decision ever. One of them introduced Prefect to our stack, and it completely changed the game for our orchestration.

Here is exactly how we use Prefect to run our data pipeline, and the exact infrastructure making it happen for cheap.

The Infra: DigitalOcean Apps Meets a Hetzner Auction Beast

I used to worry about creeping infrastructure costs, but I’ve finally found the sweet spot between cloud flexibility and raw hardware power:

  • The Frontends: The actual customer-facing price tracking websites (like Prijzenvolger and Walltrack) are hosted on DigitalOcean Apps. It’s clean, isolated, and handles web traffic beautifully.
  • The Heavy Lifting: Everything else lives on a absolute beast of a server I scored in the Hetzner server auction. It came with a massively beefy memory setup and nvme setup, perfect for data crunching. And because it can handle all our memory intensive data flows and has enough storage to last us a 8 years, we’re set for a while
  • The Deployment: We use Ansible to manage the server configuration. Everything on this Hetzner machine; our databases, our queues, and our Prefect flows, is completely dockerized.

The Data Layer: Polyglot Persistence Done Right

Running dockerized on our Hetzner box is a polyglot data stack where every tool does what it’s best at:

  • PostgreSQL: Handles our relational data (product catalogs, user accounts, price watches).
  • ClickHouse: The ultimate OLAP machine. It handles our massive, high-volume time-series price history. Every single price tick goes here.
  • Vespa & Redis: Vespa handles semantic search, while Redis keeps track of runtime configs and rate limits.

Our codebase follows a strict rule: flows.py only contains high-level Prefect flow definitions. All actual business logic lives inside a tasks/ directory and is imported lazily inside each flow.

Let’s see how that looks like for Prijzenvogler:

1. The Core Pipeline: Fan-Out Data Ingestion

The heart of Prijzenvolger (our Bol.com tracker) is the handle_datafeed_flow. It runs a strict Fetch → Process → Store pipeline inside its Docker container.

  1. Fetch: Connects to Bol’s FTP server via FTPS, checks for duplicates, and downloads the gzipped data.
  2. Process: We use Pandas to crunch the pipe-delimited CSV. Because Bol handles both the Netherlands (NL) and Belgium (BE), we split the data into parallel tracks and convert prices to clean integer cents.
  3. Store: Prices get dumped into ClickHouse. Products get an UPSERT into Postgres.

The Postgres Partitioning Gotcha ⚠️

Because our Postgres product_product table is heavily partitioned for speed, it won’t expose xmax. That means the classic RETURNING (xmax = 0) trick to see what rows are genuinely new doesn’t work. Instead, we pre-query existing IDs per chunk to figure out what’s new. Only those brand-new products are then pushed to Vespa for search indexing.

The 25x Fan-Out

Instead of writing a massive, slow flow, we wrote one generic flow and fanned it out 25 times using flows.yaml. Each deployment targets a specific category (e.g., electronics, books) with staggered cron schedules every 2 to 6 hours. This spreads the database and FTP load perfectly across our Hetzner RAM.

2. Deal Finder & User Alerts

  • User Alerts (check_price_watches): Runs every 5 minutes. It grabs unfulfilled alerts, groups them by product so we only hit the live Bol API once per product, and fires off an email if the price drops.
  • “Speurder Prikkie” Deal Finder (update_hot_products): An hourly ClickHouse query compares today’s latest price against yesterday’s max. We only keep drops between 20% and 70% (under 20% is boring; over 70% is usually bad store data). It uses a unique index in Postgres to remain completely idempotent.

Bonus: My Personal “Agentic” Prefect Stack

I fell so hard for Prefect while building this empire that I didn’t stop there. Aside from the price tracking workflows, I have set up a completely separate, private Prefect stack on my infrastructure.

I use this secondary stack exclusively to run my own agentic flows—orchestrating AI agents, automated workflows, and LLM experiments. Having a reliable, UI-driven playground to monitor complex, multi-step AI tasks has been an absolute game changer.

Why This Setup Wins

By combining Prefect’s orchestration with a split Postgres/ClickHouse architecture running on an auctioned Hetzner box, we get zero data corruption, real-time price accuracy, and incredibly low overhead.

The empire is growing, the workflows are automated, and the data is clean. As someone who always wanted to be a solopreneur, it’s actually refreshing to work together with people on a project, and have them recommend tools that end up changing the business in a very positive way

Stay tuned for the next update!

More polaroids I want to share

CLICK OR DRAG THE TOP ONE ASIDE

You've reached the end of the shoebox. More polaroids and other posts live on my Instagram.

@GMJELLE →
Polaroid of a calm lake at dusk reflecting pink clouds and dark hills, at a wild campsite near Adana, Turkey
Polaroid of Josip Broz Tito's marble tomb seen through plant leaves in Belgrade, Serbia
Polaroid of a motorcycle parked at a Soviet-era bus stop decorated with a flower mosaic in Moldova
Polaroid of a large stone statue of a bearded man in a long coat on a hillside near Buzludzha, Bulgaria
Polaroid of the saucer-shaped Buzludzha Monument on a distant hilltop above the trees in Bulgaria
Polaroid of a man standing in front of the Buzludzha Monument and its tower in Bulgaria
Polaroid of a valley of pale, cone-shaped rock formations in Cappadocia, Turkey
Polaroid of hot air balloons silhouetted against the dawn sky in Cappadocia, Turkey
Polaroid of the brutalist Cosmos Hotel tower in Chisinau, Moldova
Polaroid of the Tomb of Cyrus the Great on its stepped stone base at Pasargadae, Iran
Polaroid of a close view of the Tomb of Cyrus the Great at Pasargadae, Iran
Polaroid of a converted red fire truck camper and a motorcycle at a campsite on the west coast of Turkey
Polaroid of a mosque minaret and domes silhouetted against a cloudy sky in Edirne, Turkey
Polaroid of the Mimar Sinan statue in front of the Selimiye Mosque in Edirne, Turkey
Polaroid of the courtyard of a historic house with a fountain and colored-glass windows in Isfahan, Iran
Polaroid of a man standing next to an Isfahan city emblem sign on a sidewalk in Isfahan, Iran
Polaroid of the tiled dome and entrance portal of the Sheikh Lotfollah Mosque in Isfahan, Iran
Polaroid of the tiled entrance portal and twin minarets of the Shah Mosque in Isfahan, Iran
Polaroid of a columned palace silhouetted at sunset behind a fountain pool on Naqsh-e Jahan Square, Isfahan, Iran
Polaroid of a dark interior lit through colorful stained-glass windows in a historic house in Isfahan, Iran
Polaroid of rocky ridges and rock formations under a cloudy sky near Goreme, Cappadocia, Turkey
Polaroid of a loaded motorcycle parked by a desert road with mountains behind it in Iran
Polaroid of a loaded motorcycle with a helmet on the mirror, parked on a coastal road near Izmir, Turkey
Polaroid of a Soviet-era mosaic of workers and a motorcyclist on a building wall in Lutsk, Ukraine
Polaroid of a Soviet-era mosaic of a crowned woman holding scales, framed by tree leaves in Lviv, Ukraine
Polaroid of the Lviv Opera House facade with a fountain in front in Lviv, Ukraine
Polaroid of a Soviet WWII memorial column with a stork's nest on top in northern Moldova
Polaroid of a large Soviet-era mural of figures on a wall beside a tree in Moldova
Polaroid of royal tombs carved into the cliff face at Naqsh-e Rustam near Persepolis, Iran
Polaroid of a stone relief of a Persian soldier holding a spear at Persepolis, Iran
Polaroid of the Gate of All Nations with its winged bull statues and columns at Persepolis, Iran
Polaroid of four men sharing dinner at a table in a traditional hostel in Shiraz, Iran
Polaroid of the tiled courtyard and reflecting pool of the Nasir al-Mulk (Pink) Mosque in Shiraz, Iran
Polaroid of two men smiling at a restaurant table in Tabriz, Iran
Polaroid of the mirrored hall of Golestan Palace lit by large chandeliers in Tehran, Iran
Polaroid of the bronze statue of Leonidas raising a spear at the Thermopylae monument, Greece
Polaroid of a man looking up at the wooden Trojan Horse from the Troy film in Canakkale, Turkey
Polaroid of a backgammon board on a table overlooking the sea at a campsite in Turkey
Polaroid of a Soviet-era mosaic of a woman in traditional dress under a Ukrainian coat of arms in Ukraine
Polaroid of a motorcycle parked at a bus stop with a mosaic of three knights in Ukraine
Polaroid of a man standing on a boardwalk in front of the stairway of the Ziggurat of Ur, Iraq
Polaroid looking up the brick stairway of the Ziggurat of Ur, Iraq
Polaroid of eroded mud-brick ruins with a small sign in front at Uruk, Iraq
Polaroid of a man and two boys crouching by a freshly slaughtered sheep on grass on the road to Van, Turkey
Polaroid looking up at the curved glass Zlota 44 skyscraper in Warsaw, Poland
Polaroid of the Palace of Culture and Science lit at dusk in Warsaw, Poland
Polaroid of the Zoroastrian Fire Temple with a round reflecting pool in Yazd, Iran