The LTAP Lakegarden

A live, multiplayer garden. Plant and water plots with everyone else in real time — state lives in Lakebase, a timer in the Go server grows & wilts the crops by rules, and CDC captures every change into the lakehouse. See how it's built →

● connecting…
👀 view-only — enter a name to plant, water & harvest
● connecting…

Architecture

The garden runs on this. Four layers, two engines, one governed dataset — click a surface to trace how it reaches the garden data and read the tradeoffs — or press Play to watch a row travel bronze → gold → surface → client.

Trace a path:

Access matrix — which surface reads which source

Access surfaceWire / protocolPath it callsReachesSource objectGoverned by
Unity CatalogREST (HTTPS)/api/2.1/unity-catalog/*metadata onlypublic catalog → bronze volume + gold tablesUnity Catalog
JDBCPostgres wire (5432)Lakebase Postgres garden.*point read/writegarden.v_leaderboard · garden.v_plots (Postgres/OLTP)Unity Catalog + Postgres roles
HTTPREST (HTTPS)/api/2.0/sql/statementsSQL executionpublic.00_public_gold.garden_audit (Delta)Unity Catalog
GraphQLHTTP resolvers → both engines/graphqlfederated player readpublic.00_public_gold.garden_audit (Delta) + garden.* (Postgres)Unity Catalog + Postgres roles
LakebasePostgres wire (5432)psql / pg / psycopgpoint read/writegarden.players · garden.plots · garden.eventsUnity Catalog + Postgres roles + OAuth

Unity Catalog lakehouse governance

Mental model: UC is IAM + a data catalog combined — one place that both describes your data (catalog → schema → table → column) and controls who can touch it. Everything below is live UC REST metadata, the same thing every other surface is governed by.

Catalogs
Schemas
Tables / columns

Gaps & best practice

What's enforced. This app authenticates as a service principal with USE CATALOG on public + SELECT on gold only — private catalogs (e.g. dev_appian_poc) are genuinely blocked, not hidden.
Best practice. Avoid ALL PRIVILEGES to account users; grant to groups with least privilege; classify with tags; apply column masks + row filters; use on-behalf-of-user auth so each user's own grants apply.

The OLTP schema Unity Catalog governs — Lakebase garden

This is the transactional half of the dataset: the Postgres garden schema behind the live game. Unity Catalog governs it alongside the Delta lakehouse tables above — same discovery, lineage and permission plane, one dataset in two physical shapes.

How Unity Catalog governs Lakebase — especially from a Databricks App

UC-governed access, not a raw password. A Databricks App reaches Lakebase through a UC-governed managed identity (M2M) or a per-user on-behalf-of (OBO) tokennot a raw Postgres password. So UC applies catalog / schema / table grants, tags and lineage to the OLTP store the same way it does to Delta: one policy plane across transactional + analytical.
Why this matters. Because the App authenticates as a UC identity, each user's own grants, column masks and row filters follow them into the operational store — the same enforcement that already governs the lakehouse, now covering the live garden.* tables below.
Contrast — the raw Postgres-wire path. A psql / pg / GraphQL client connecting over the native wire authenticates with native Postgres roles, which live outside Unity Catalog. That path still works, but it's governed by Postgres role grants, not UC — which is exactly the governance-boundary point this POC makes.
ref_plot_states
state_id
key
ref_crops
crop_id
key
ref_actions
action_id
key
players
player_id
name
email masked
plots
plot_id
row · col
state_id →
crop_id →
stage · watered
planted_by →
events append-only log
event_id
plot_id →
player_id →
action_id →
crop_id →
stage · created_at
rabbits
rabbit_id
plot_id →
satiety
core tables reference tables PK primary key foreign key
Foreign keys: plots.state_id → ref_plot_states plots.crop_id → ref_crops plots.planted_by → players events.plot_id → plots events.player_id → players events.action_id → ref_actions events.crop_id → ref_crops rabbits.plot_id → plots

Alternative Access Patterns Lakebase-first

How the app actually reaches its data. LTAP means most application access is transactional — point reads and writes against Lakebase (Postgres/OLTP). The lakehouse (Delta/OLAP) is the analytics surface you reach through the Statement API, and GraphQL is the one typed layer that federates both. Pick a surface below — every example is read-only and fixed (you run it; you can't edit the SQL that's sent).

Surface:

JDBC Lakebase · Postgres wire

The workhorse driver path — but pointed where an app spends most of its time: the operational store. A JDBC/ODBC client (BI tool, notebook, JVM app, ORM) holds a session against Lakebase Postgres and runs indexed, millisecond point reads over the live garden schema. This is the OLTP half of LTAP.

SELECT name, plants, waters, harvests, score
FROM garden.v_leaderboard
ORDER BY score DESC LIMIT 10
Read-only, fixed query against Lakebase Postgres (garden.v_leaderboard / v_plots). The live game writes these tables transactionally as people garden.

When to reach for JDBC

Tableau/Power BI, dbt, Spark, ORMs, any tool with a JDBC/ODBC slot. Against Lakebase you get true OLTP — indexes, transactions, sub-ms point reads. Session reuse keeps chatty app traffic cheap. Downside: needs a driver + a live connection.

Tradeoffs & what UC governance you sacrifice

Fastest OLTP path. The raw Postgres wire gives you indexes, transactions and sub-ms point reads/writes — nothing beats it for chatty operational traffic.
You sacrifice query-time UC enforcement. The raw Postgres-wire connection authenticates with native Postgres roles OUTSIDE Unity Catalog. So on this traffic you give up UC column masks, row filters, per-user OBO, and unified lineage/audit — UC still catalogs the instance, but doesn't enforce policy on the wire. Governance falls back to Postgres GRANTs.
Better host: Databricks Apps. An App gets first-class Lakebase resource bindings + managed credentials + OBO, instead of shelling out to generate OAuth tokens on a VM (as this POC does).
Best practice. Dedicated least-privilege Postgres role on the garden schema; map Databricks identities to Postgres roles via identity federation; manage synced tables through UC so lineage + access flow through one plane.

Lakebase Postgres · OLTP

The operational system of record: managed Postgres 17, speaking the native wire protocol. Millisecond point reads and transactional writes — what the lakehouse is deliberately bad at. Any Postgres client (psql, JDBC, pg, ORMs) connects unchanged; auth is a short-lived Databricks OAuth token as the password (this app auto-refreshes it).

SELECT name, plants, waters, harvests, score
FROM garden.v_leaderboard
ORDER BY score DESC LIMIT 10
Native Postgres SQL over the garden schema (players · plots · events). Read-only, fixed query; the live game writes these tables transactionally as people garden.

When to reach for Lakebase

App state, user sessions, agent memory, feature serving, anything needing an index and a sub-ms write. Pair it with the lakehouse via synced tables so analytics and serving share one governed dataset.

How Unity Catalog applies here

Lakebase lives under UC too. The Postgres instance and its synced tables are registered in Unity Catalog, so UC provides discovery, lineage and (increasingly) permission management over the operational store — not just the lakehouse.
Best practice. Create a dedicated least-privilege Postgres role on the garden schema, map Databricks identities to Postgres roles via identity federation, and manage synced tables through UC so lineage + access flow through one plane.
Better host: Databricks Apps. An App gets first-class Lakebase resource bindings + managed credentials, instead of shelling out to generate OAuth tokens on a VM (as this POC does).

The transactional store doesn't live in isolation — below is its change feed (how writes reach the lakehouse and back) and its branching (fork the whole database in seconds). Same Lakebase, two more capabilities.

Explore Lakebase:

Change feed Delta CDF operational CDC

The two engines don't drift apart — changes flow between them. Lakehouse → Lakebase ships curated analytics down to the operational store for serving; Lakebase → Lakehouse streams operational writes up for analytics. Both are live below.

Lakehouse → Lakebase Delta CDF

serve curated analytics into the operational store
gold Delta Change Data Feed synced table Postgres

Delta Change Data Feed exposes every insert/update/delete on the gold table as rows tagged with a _change_type. A synced table (or a stream) reads that feed and upserts into Lakebase — no full reloads.

The app reads the feed read-only (SELECT on gold). Writes to gold come from a pipeline identity — the app never mutates the analytics tables.

Lakebase → Lakehouse Lakebase CDF

stream operational writes up for analytics
Postgres Lakebase CDF (off the WAL) Delta · lakebase_cdf

Operational writes (a player acting on a plot) are captured by native Lakebase Change Data Feed straight off the Postgres write-ahead log — batched to Delta every ~15s as lb_<table>_history tables under the lakebase_cdf catalog, each row tagged with _pg_change_type, LSN and commit timestamp. No triggers, no Debezium, no polling.

The action writes garden.events in Postgres — the exact insert Lakebase CDF captures off the WAL and lands in the lakehouse.

How change capture works — Lakebase CDF

Native path: Lakebase Change Data Feed. Databricks captures Postgres changes straight off the write-ahead log (WAL) and flushes them into Delta — no triggers, no Debezium, no log listeners, and no per-row polling. See docs.databricks.com/oltp/projects/lakebase-cdf.
The lakehouse side is UC-governed end to end. The captured lb_<table>_history tables land in the lakebase_cdf catalog (external storage on R2) with full Unity Catalog lineage — grants and tags follow the data.
Better host: Databricks Apps + Lakeflow. Run the sync as a managed Lakeflow pipeline (or Lakebase CDF) and the app as a Databricks App bound to both engines — no CDC glue on a VM.

LTAP — Lakehouse Transactional / Analytical Processing

LTAP runs your operational app and your analytics on one governed lakehouse. Two purpose-built engines — Lakebase (Postgres) for transactions, the Lakehouse (Delta) for analytics — sit under one platform, one governance plane (Unity Catalog), with zero-ETL sync so they behave like a single system. No separate warehouse to stand up, no nightly copy job, one set of permissions and lineage across both.

The problem: the two-system tax

What teams do today

You run an OLTP database for the app (users, sessions, orders, state) and a separate warehouse / lakehouse for analytics — glued together by brittle batch ETL. Two engines, two bills, and a pipeline in the middle you have to build and babysit.

What that tax costs you

Copies drift — the warehouse is a stale snapshot of the app. Freshness lags the last ETL run. And you govern two systems with two policy models — separate permissions, separate lineage, separate audit — with nothing that sees the whole dataset.

What LTAP collapses it into

One governed dataset, two physical shapes. Lakebase Postgres OLTP for the app + Delta OLAP in the lakehouse for analytics — kept coherent by Change Data Feed / synced tables, no hand-written pipeline.
One governance plane. Both halves live under a single Unity Catalog — one set of grants, tags, lineage and audit across the transactional and analytical stores. No drift, no second policy model, no ETL to babysit.

The analogy

Forcing one database to do both is like one chef doing prep, plating and dishes — versatile but a bottleneck; bolting a warehouse on the side means nightly copies and stale data. LTAP is a proper line: a fast station for live plot actions (OLTP) and a batch/prep station for volume analytics (OLAP), sharing one kitchen and one manager — Unity Catalog.

What makes it "one system"

Synced tables + Change Data Feed keep the two engines coherent with no hand-written ETL, and Unity Catalog governs both — so it reads like a single governed dataset with two physical shapes.

When LTAP wins

You need real analytics (ML, big joins, BI) and sub-ms serving, independent scaling and cost per engine, and one lineage + permission plane across operational and analytical.

The Difference: OLTP vs OLAP

Two workload types, two engine shapes. LTAP doesn't blur them — it runs a best-of-breed engine for each and governs both as one dataset. Below, the same table of garden events, read two different ways. Watch how each engine reaches into memory.

LakebaseOLTP · row-orientedread one ROW ▸
Managed Postgres. Grabs or updates one whole record in a single seek — plant, water, harvest plot 12 — in sub-millisecond, at high concurrency. Answers "what is the current state of this one thing?" Every click you made on the garden was a Lakebase transaction.
LakehouseOLAP · columnarscan one COLUMN ▸
Delta on the lakehouse. Scans one column across all history — every crop ever planted — to aggregate in seconds with Spark / Photon / ML / BI. Answers "what's true across all of it?" The Garden History requery and Genie both run here.
👆 Click Lakebase or Lakehouse above to watch how it reaches into memory auto-cycling ▸

Lakebase / row-store cost

Cheap: single-row read & write, indexed point lookups, high concurrency — the OLTP hot path.
Expensive: full scans & aggregations — it has to walk every row to answer "across all of it."

Lakehouse / columnar cost

Cheap: scans, aggregations, ML / BI, adding columns — read only the columns you need.
Expensive: single-row lookups, frequent small row updates/deletes — rewrites files, not built for OLTP write rates.

Why you historically needed BOTH — and what LTAP changes

The asymmetry. Row stores are cheap to write & to fetch one thing, expensive to scan everything; column stores are the opposite — that asymmetry is why teams historically ran two systems, and what LTAP resolves under one Unity Catalog.
LTAP gives you both under one governance plane. Keep the best engine for each workload — Lakebase for the point reads/writes, the lakehouse for the scans — and let Unity Catalog govern them as a single dataset, with Change Data Feed / synced tables keeping them coherent.

Real World Use Cases

Where the two-system tax actually hurts — and how one governed dataset (Lakebase OLTP + lakehouse OLAP under Unity Catalog) removes it.

AI agent state & memory AI Realtime

Problem → Agents need sub-ms read/write of session, memory & tool state every turn, but teams also need to analyze agent behavior, cost & quality at scale — usually two systems plus a brittle pipeline.
LTAP → Agents persist state in Lakebase; the same rows are analytics-ready in the lakehouse for eval / cost / behavioral analytics; one Unity Catalog over both. (This is literally how this garden works.)

Online feature serving for ML ML Realtime

Problem → Real-time inference needs low-latency feature lookups while training needs the same features computed offline over history — keeping them in sync is the classic training/serving skew problem.
LTAP → Serve online features from Lakebase, compute & train on Delta, one governed feature definition — no skew, no separate feature-store infra.

Serving computed insights Data Near Real Time

Problem → The lakehouse produces great aggregates (scores, recommendations, leaderboards) but apps can't hit a warehouse per page-load, so teams build custom reverse-ETL to a separate OLTP store.
LTAP → Publish lakehouse results into Lakebase via UC-governed synced tables for low-latency app reads — no bespoke sync code.

Government case / permit management Gov/SLED Near Real Time

Problem → Case / permit / benefits systems need transactional updates (citizen submits, caseworker edits) and reporting, audit & compliance analytics — traditionally an OLTP app plus nightly ETL to a warehouse.
LTAP → Transactional case state in Lakebase; reporting, audit trails & analytics on the lakehouse; Unity Catalog governs lineage + access for both — one compliance boundary.

Fraud / anomaly detection FinServ Realtime

Problem → Detecting fraud needs transactional writes and real-time + historical scoring, with decisions fed back into the operational flow — latency and governance both matter.
LTAP → Transactions land in Lakebase, streaming + batch models score on the lakehouse, decisions written back — one plane, one governance model.

Operational app backend App Batch

Problem → User profiles, sessions & orders live in the app's OLTP DB; getting that to the lakehouse for BI means yet another ETL pipeline to build and babysit.
LTAP → The app runs on Lakebase, the same data is analytics-ready in the lakehouse with no separate pipeline; Unity Catalog governs both.

The Garden History ad-hoc lakehouse query

TL;DR — every transactional write to Lakebase is captured into Delta by native Lakebase Change Data Feed. This tab pulls that change history live from the lakehouse on demand — one governed OLAP query per click, round-trip latency shown.

No pre-synced mirror and no cached snapshot: Requery from lakehouse runs a fresh SELECT over the captured change history through the SQL warehouse (round-trip latency shown), then the slider replays those changes on the grid — reconstructing exactly what was planted where, one committed change at a time. The pull is analytical (governed, columnar, seconds); the reconstruction happens in the browser.

Callouts and Considerations Free Edition & Databricks Apps

The honest ledger, ordered by what matters most. First the real architectural tradeoff you're making with LTAP, then why governance is the differentiator — and only then the Databricks Free Edition limitations and the workaround each one forced in this POC. On a paid workspace, the Free-Edition workarounds disappear; the tradeoff at the top is inherent to the pattern.

1 · The honest tradeoff — LTAP is not one ACID engine

Sync latency is real. There's sync latency between the transactional and analytical copies — a write to Lakebase appears in the lakehouse after the feed / synced table catches up, sub-second to minutes by config. If you need a single strictly-consistent transaction spanning operational + analytical, that's a single-engine DB's job, at the cost of analytical depth.
What you trade for. Best-of-breed depth on each side, independent scale + cost, one Unity Catalog governance / lineage plane — instead of compromising both workloads on a single engine.

2 · Governance is the differentiator

What makes this more than "two databases" is Unity Catalog: one place that governs, tags and traces lineage across the transactional (Lakebase) and analytical (Delta) halves. Two engines under one policy plane is the whole point — remove UC and you're back to the two-system tax.

3 · Free Edition & Databricks Apps — limitations and workarounds

AreaFull-featured DatabricksLimitation hereWorkaround in this POC
App hostingRun as a Databricks App — managed identity, OBO auth, resource bindings, secrets, autoscaling, no servers to run.Go server on a VM behind nginx; it holds a service-principal token in env.
Realtime + game loopDatabricks Apps run a long-lived container — increasingly viable for persistent WebSocket connections + background loops, and Databricks is pushing near-real-time app patterns (Zerobus Ingest + Lakebase).Now that the growth loop is in-process (a goroutine, not an external cron), the whole thing is one Go process — so it could deploy as a Databricks App. It runs on a VM here mostly by history, not necessity.
Lakebase → lakehouse CDCLakebase Change Data Feed captures Postgres changes straight off the write-ahead log and flushes to Delta — no triggers, no Debezium, no polling.Lakebase CDF needs Postgres 17 + a UC destination with default storage disabled.Both provisioned. Migrated the instance to PG17 (lakebase-poc17) and created a default-storage-disabled catalog lakebase_cdf (on R2 external storage), so native Lakebase CDF is now the Postgres→Delta path (lb_<table>_history Delta tables off the WAL). The old hand-rolled event-log replay is retired to a one-flag fallback (CDF_NATIVE).
Scale-to-zero & branchingLakebase Autoscale scales compute to zero when idle and supports instant database branching.Free Edition Lakebase is now on the Autoscale (Neon) compute model — a real production branch + native scale-to-zero — but the idle suspend is fixed at 24 h (the suspend_timeout edit is rejected on Free Edition), the branch/CU-tuning APIs aren't in the public SDK/CLI, and the legacy provisioned start/stop field is dead (rejected with min_cu == max_cu).The garden's on/off button + a 5-min auto-off toggle the endpoint's spec.disabled flag via the /api/2.0/postgres API to reclaim credits well before the 24 h native suspend (the old stopped field is dead). An in-memory idle-gate also skips the growth tick after 5 min of no play, so it issues zero Lakebase + zero warehouse queries. With Lakebase CDF on, the feed needs the endpoint live — so the auto-off pauses CDF while idle and it resumes on wake (no writes happen while off, so nothing is missed); set LAKEBASE_ALWAYS_ON=1 for a continuous feed at the cost of steady credit use. The branching tree below is an interactive illustration.
Identity / authM2M OAuth service-principal credentials, and per-user OBO so UC enforces each user's grants.Account-level OAuth (M2M) secrets aren't available on Free Edition.An OBO token for a least-privilege service principal, scoped to catalog public.
Warehouse tuningSet auto-stop, size and serverless scaling per workload.Editing the SQL warehouse is blocked for free users.Stuck at the 10-min default auto-stop — the idle-gate keeps it from being kept warm.
Governance hygieneLeast-privilege groups, row filters + column masks, no all-users grants.Pre-existing catalogs are granted to account users, so any principal (incl. the SP) can see them.The UC tab app-filters the catalog list to public — enforcement is real at the query layer, the filter is just presentation.
Sync & pipelinesSynced tables (declarative reverse-ETL) and Lakeflow managed pipelines move data both ways, UC-governed.Synced tables (Delta→Lakebase) not exercised on Free Edition.Postgres→Delta now runs on native Lakebase CDF (not app code). Delta→Lakebase is still illustrated rather than run as a managed synced table.
Genie / AI-BIGenie natural-language dashboards over UC tables answer questions about both engines.Built — the "Ask the Garden — Genie" panel on the Garden page runs a real Genie space over the gold garden tables (garden_audit). Lakebase data reaches it via synced tables.
JDBC driverThe Go SQL driver databricks-sql-go (Thrift + Arrow) for the JDBC path.The Go backend routes the JDBC surface through the HTTP Statement Execution API to keep the build lean.