What happens in the milliseconds after you tap pay
Databricks shows that modern lakehouse architectures can achieve 27ms median latency for real-time fraud scoring by using route-optimized model serving.
Deep dive
- Databricks Model Serving route optimization minimizes network overhead for inference requests.
- Lakebase Postgres supports autoscaling and low-latency feature lookups for model inference.
- The benchmark achieved 27.2ms median latency over 5,000 requests.
- Feature lookups averaged 8.9ms using persistent connection pools to avoid TLS handshake overhead.
- OAuth token rotation is handled within the connection pool to maintain connectivity without long-lived credentials.
- The architecture processes both business rules and statistical inference from the same underlying database table.
Decoder
- Route Optimization: A networking feature in Databricks that shortens the path between the application and the inference container to reduce latency.
- Lakebase: A managed PostgreSQL service within the Databricks platform designed for transactional workloads.
- Lakehouse: A data architecture that combines the performance of a data warehouse with the flexibility of a data lake.
Original article
- A sample Databricks App (FastAPI + React) that scores credit card transactions for fraud in real time using Model Serving route optimization for low-latency inference and Lakebase Postgres for online feature and profile lookups.
- Fast inference alone is not enough. The app pairs route-optimized Model Serving with Lakebase, plus the connection pooling, OAuth token rotation, and autoscaling patterns that keep latency stable under load.
- Across 5,000 requests, the route-optimized endpoint returned in 27 ms at p50 and 37 ms at p95 end-to-end, with 8.9 ms median Lakebase feature lookups and 100% success, well inside checkout latency budgets.
You're standing at the register. You tap your card. A tiny spinner appears for maybe half a second, maybe less, and then it says Approved. Or it doesn't.
During that time, something had to decide whether this charge looks like you, or like someone who stole your card number in a data breach six months ago. It had to know things about you: your spending patterns, your daily limit, whether you even allow purchases from other countries. And it had to do all of that fast enough that you didn't notice it happened.
This post is about what that "something" looks like when you build it on Databricks. We'll walk through retail-app, a sample application (FastAPI backend, React frontend, deployed as a Databricks App) that brings two platform capabilities together:
- Model Serving with route optimization, a faster network path to your deployed model.
- Lakebase, a managed Postgres for the profile and feature data the model needs at prediction time, featuring autoscaling so the database scales with demand instead of becoming the new bottleneck.
The flow: what actually happens on a transaction
Before we look at code, here's the plain story of a single payment. Two checks run in sequence: a model scores the charge, then the app checks your profile rules. Either one can decline the transaction.
The model runs first because we want its latency numbers regardless of the outcome. Then the profile lookup (daily spending cap, international transaction toggle, country of residence) feeds a handful of if-statements. The response includes timing for every step so you can see exactly where the milliseconds went.
Updating your profile (changing your daily limit, toggling international transactions) is a separate action. You save changes to the database, and the next payment picks them up. No redeployment, no cache invalidation.
Route optimization: why the network path matters
When a model is deployed behind Databricks Model Serving, there's a network hop between your application and the inference container. For batch workloads, a few extra milliseconds per request is irrelevant. For a checkout experience, it's everything.
Route optimization shortens that network path. When you enable route optimization on an endpoint, Databricks Model Serving improves the network path for inference requests, resulting in faster, more direct communication between your client and the model. This optimized routing unlocks higher queries per second (QPS) compared to non-optimized endpoints and provides more stable and lower latencies for your applications.
You enable it when you create the endpoint, and you query through the data-plane flow using OAuth, not personal access tokens. You get lower latency and higher throughput for the same compute, which is exactly what an interactive fraud-scoring use case needs.
In the sample app, the endpoint is called fraud-detection-lakebase.
A few things to notice:
serving_endpoints_data_plane.query: This is the data-plane query path, which is what route optimization uses. The Databricks SDK handles the OAuth token exchange under the hood.asyncio.to_thread: The SDK's query method is synchronous. Wrapping it into_threadkeeps the FastAPI event loop free while the model runs.dataframe_records: The payload is a list of dictionaries (one per row). For fraud scoring, we send one transaction at a time.
The model itself returns fraud_probability, fraud_flag, and (crucially) its own internal timing: lookup_ms (how long the feature lookup inside the model container took), inference_ms (CatBoost prediction), and total_ms. The backend maps these through so the frontend can show a latency waterfall.
Lakebase: Postgres for the data the model needs
The fraud model doesn't just look at the transaction in isolation. It looks up the customer's historical features (average transaction amount, cross-border ratio, chargeback rate, velocity over the past 24 hours) using the first six digits of the credit card (the BIN) as the lookup key. Those features live in a Lakebase Postgres table called customer_features.
This is the same table the backend reads from for profile data (name, daily limit, international toggle). One table, two readers: the model container reads features for inference, the FastAPI app reads profile fields for business rules.
On the read side, the backend borrows a connection from the pool, runs a parameterized SELECT by user_id, and returns the connection in a finally block. On the write side, when a user changes their daily limit or toggles international transactions in the UI, the backend builds a dynamic UPDATE from an allowlist of editable columns.
Connection pooling and OAuth token rotation
Every transaction in this app hits Lakebase at least twice: once inside the model container for the feature lookup, and once in the backend for the profile check. Opening a fresh connection each time means a TCP handshake plus a TLS negotiation on every single request, which would easily add 20-50 ms of overhead per call. A connection pool keeps a handful of connections open and ready, so most requests just grab one and go.
The pool itself is a psycopg2.pool.ThreadedConnectionPool with 3–10 connections. Because Lakebase authenticates via OAuth, the app exchanges service principal credentials for an access token, then uses the client ID as the Postgres username and the token as the password.
The model's side: feature lookup inside the container
The fraud model itself (deployed as an MLflow pyfunc) does its own Lakebase lookup at prediction time. It extracts the card BIN, queries customer_features, assembles a feature vector, and runs CatBoost inference.
The model container maintains its own ThreadedConnectionPool to Lakebase, with background token refresh so the pool stays valid across long-running serving instances. This is the same pattern as the backend (pool + OAuth rotation), but running inside the model container rather than the FastAPI process.
Business rules: the profile check
After the model scores the transaction, the backend reads the customer's profile from Lakebase and runs two simple rules. First, it compares the transaction amount against the user's daily spending limit. Second, if the user has disabled international transactions, the backend checks whether the transaction's country matches the user's country of residence.
Lakebase autoscaling with scale-to-zero: handling demand
In production, your Postgres instance needs to handle both the model container's feature lookups and the backend's profile reads. Lakebase autoscales, adjusting compute within a configured min/max range so you're not paying for peak capacity at 3 AM, but you're also not dropping queries at noon. With scale-to-zero enabled, you also stop paying when no transactions are flowing.
Putting it together: end-to-end latency breakdown
| Measurement | What it captures |
|---|---|
| model_call_ms | Wall-clock time for the entire serving call |
| model_lookup_ms | Feature lookup inside the model container |
| model_interfere_ms | CatBoost prediction time |
| model_total_ms | Total time inside the model container |
| business_logic_ms | Profile read + rule evaluation |
| backend_total_ms | Wall-clock time from request start through fraud model call |
Results: How fast is it really?
| Metric | p50 | p95 |
|---|---|---|
| Feature lookup | 8.9 ms | 13.9 ms |
| Inference | 0.4 ms | 6.0 ms |
| Total model time | 9.5 ms | 17.6 ms |
| End-to-end round trip | 27.2 ms | 37.3 ms |
- End-to-end round trip is 27 ms at the median, 37 ms at p95. That's the full journey: caller → route-optimized data plane → model container → Lakebase lookup → CatBoost inference → response. Well within the latency budget for a checkout flow.
- Feature lookup is single-digit milliseconds at p50 (8.9 ms). The model's connection pool to Lakebase keeps connections warm, so most reads skip the TLS handshake entirely.
- Inference is essentially free. CatBoost prediction on a 12-feature vector takes 0.4 ms at the median.
- Network overhead is ~17 ms. Route optimization keeps this consistent: the p50-to-p95 spread is only 4 ms.
Try it yourself: deploy the app to your workspace
The app is built as a Databricks App (FastAPI backend, React frontend) using apx.
The key files:
src/retail_app/backend/router.py: Transaction endpoint, fraud check, business rulessrc/retail_app/backend/postgres.py: Lakebase connection pool, profile CRUDmodel_training/fraud_model.py: MLflow pyfunc with feature lookup and CatBoostapp.yml: Deployment config
Fresh dev news, AI-digested daily. Never miss a trend at devoured.fyi