For years, the Databricks lakehouse had a conspicuous gap: operational workloads. You could build world-class analytics pipelines, train ML models, and govern petabytes of data — but the moment you needed a transactional database for your application, you were back to provisioning an external Postgres, managing credentials, and stitching together CDC pipelines to keep things in sync.
Lakebase changes that. It’s a fully managed, Postgres-compatible OLTP database running inside the Databricks platform. Same wire protocol. Same SQL dialect. Same psql you’ve used for a decade. But with lakehouse-native superpowers: autoscaling, scale-to-zero, database branching, point-in-time recovery, and built-in Unity Catalog integration.
In this article, we’ll go beyond the marketing and get hands-on: create a project, connect, build tables, branch your database, register it in Unity Catalog, and wire up pgvector for semantic search — all with real commands you can run today.
Before Lakebase, the typical architecture for a data + AI application on Databricks looked like this:
1
2
3
4
5
6
7
[Application] --> [External Postgres (RDS/Cloud SQL)]
|
CDC Pipeline (Debezium, Fivetran, etc.)
|
[Delta Lake in Lakehouse]
|
[Analytics / ML / AI]
That’s a lot of moving parts. Every CDC pipeline is a failure point. Every external database is a credential to manage, a network path to secure, and a bill to optimize separately.
With Lakebase, this collapses:
1
2
3
4
5
6
7
[Application] --> [Lakebase (managed Postgres)]
|
Lakehouse Sync (built-in CDC via wal2delta)
|
[Delta Lake in Unity Catalog]
|
[Analytics / ML / AI]
One platform. One governance model. One bill.
Lakebase runs real Postgres 17 — not a Postgres-like API or a compatibility layer. Your existing ORMs, drivers, and psql scripts work unchanged. It also ships with pgvector for vector similarity search out of the box.
In the Databricks sidebar, navigate to Lakebase and click New project.
Give your project a name (e.g., ai-app-backend)
Select Postgres 17
Click Create
Your project spins up with:
A production branch (the main database)
A default databricks_postgres database
Autoscaling compute attached to the branch
Scale-to-zero is enabled by default. If nobody connects for 5 minutes, compute suspends entirely — you pay nothing. The next connection wakes it up in seconds.
You should see the familiar Postgres prompt. Let’s verify:
1
SELECTversion();
1
2
3
4
version
------------------------------------------------------------------------------------------------------------
PostgreSQL 17.4 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 13.2.0, 64-bit
(1 row)
This is real Postgres. Your existing tools — pgAdmin, DBeaver, SQLAlchemy, Prisma, Django ORM — all connect the same way.
-- Conversation tracking for an AI agent
CREATETABLEconversations(idSERIALPRIMARYKEY,user_idVARCHAR(64)NOTNULL,agent_idVARCHAR(64)NOTNULL,started_atTIMESTAMPTZDEFAULTnow(),statusVARCHAR(20)DEFAULT'active');CREATETABLEmessages(idSERIALPRIMARYKEY,conversation_idINTEGERREFERENCESconversations(id),roleVARCHAR(20)NOTNULL,-- 'user', 'assistant', 'system'
contentTEXTNOTNULL,tokens_usedINTEGER,created_atTIMESTAMPTZDEFAULTnow());-- Insert some sample data
INSERTINTOconversations(user_id,agent_id)VALUES('user-001','support-bot-v2'),('user-002','support-bot-v2'),('user-003','onboarding-agent');INSERTINTOmessages(conversation_id,role,content,tokens_used)VALUES(1,'user','How do I reset my password?',12),(1,'assistant','You can reset your password by clicking...',45),(2,'user','What are your pricing plans?',10),(2,'assistant','We offer three tiers: Starter, Pro, and Enterprise...',68),(3,'user','Help me get started with the API',14),(3,'assistant','Welcome! Let me walk you through the setup...',52);
Good news — the essentials are there, including pgvector. We’ll use it in Step 7.
Step 4: Database Branching — Your New Best Friend
#
This is where Lakebase goes beyond standard managed Postgres. Branching creates an instant, isolated copy of your database using copy-on-write — no full data duplication.
In the Databricks UI, navigate to your project and click Create branch from the production branch. Name it dev-new-schema.
Your dev-new-schema branch is an exact copy of production — same data, same schema — but completely isolated. Any changes you make here don’t touch production.
Connect to the branch (it gets its own connection string) and make your schema changes:
1
2
3
4
5
6
7
8
9
10
11
-- Add embedding support to messages (on the dev branch)
ALTERTABLEmessagesADDCOLUMNembeddingvector(1536);-- Add a feedback table
CREATETABLEfeedback(idSERIALPRIMARYKEY,message_idINTEGERREFERENCESmessages(id),ratingSMALLINTCHECK(ratingBETWEEN1AND5),commentTEXT,created_atTIMESTAMPTZDEFAULTnow());
Verify the schema change on the branch:
1
\dmessages
1
2
3
4
5
6
7
8
9
10
Table "public.messages"
Column | Type | Collation | Nullable | Default
-----------------+--------------------------+-----------+----------+--------------------------------------
id | integer | | not null | nextval('messages_id_seq'::regclass)
conversation_id | integer | | |
role | character varying(20) | | not null |
content | text | | not null |
tokens_used | integer | | |
created_at | timestamp with time zone | | | now()
embedding | vector(1536) | | |
The embedding column is live on the branch, but production is untouched. Test thoroughly, run your integration tests against the branch. When you’re confident, use schema diff in the UI to review changes before applying them to production.
Accidentally dropped a table on production at 10:23 AM? Create a branch from 10:22 AM:
Click Create branch on the production branch
Select Point in time
Set the timestamp to just before the incident
Connect to the recovery branch and extract the missing data
No need to restore the entire database. No downtime. This alone is worth the switch for many teams.
Copy-on-write means branches are nearly instant regardless of database size. A 100 GB production database branches in seconds, not hours. You only pay for the incremental storage of changes made on the branch.
Your Lakebase database is invisible to the rest of Databricks until you register it in Unity Catalog. Once registered, it becomes a read-only catalog that the entire platform can query.
Once registered, anyone with the right permissions can query your operational data from SQL Warehouses, notebooks, or dashboards:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- Query live operational data from a SQL Warehouse
SELECTagent_id,COUNT(*)astotal_conversations,AVG(message_count)asavg_messages_per_conversationFROM(SELECTc.agent_id,c.id,COUNT(m.id)asmessage_countFROMchatbot_app.public.conversationscJOINchatbot_app.public.messagesmONm.conversation_id=c.idGROUPBYc.agent_id,c.id)subGROUPBYagent_id;
This is the real power — joining OLTP data with your lakehouse in a single query:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- Combine live conversation data with historical user analytics
SELECTc.user_id,c.agent_id,m.contentaslast_message,u.subscription_tier,u.lifetime_valueFROMchatbot_app.public.conversationscJOINchatbot_app.public.messagesmONm.conversation_id=c.idJOINmain.user_analytics.usersuONc.user_id=u.user_idWHEREc.started_at>=current_date-INTERVAL1DAYORDERBYc.started_atDESC;
1
2
3
4
5
user_id | agent_id | last_message | subscription_tier | lifetime_value
----------+------------------+-------------------------------------------+-------------------+----------------
user-001 | support-bot-v2 | You can reset your password by clicking...| pro | 1250.00
user-002 | support-bot-v2 | We offer three tiers: Starter, Pro, ... | enterprise | 45000.00
user-003 | onboarding-agent | Welcome! Let me walk you through the... | starter | 0.00
No ETL. No CDC pipeline. Just SQL across operational and analytical data, governed by Unity Catalog.
Step 6: Lakehouse Sync — CDC Without the Pipeline
#
While the Unity Catalog registration gives you read access to live Lakebase data, Lakehouse Sync goes further: it continuously replicates your Postgres tables into Delta tables using built-in Change Data Capture.
Lakehouse Sync is powered by wal2delta, a Postgres extension that runs inside the Lakebase compute. It uses logical decoding to capture WAL (Write-Ahead Log) changes and writes them directly to Delta tables in Unity Catalog.
1
2
3
4
5
Lakebase Postgres
└── WAL (Write-Ahead Log)
└── wal2delta (logical decoding)
└── Delta tables in Unity Catalog
└── SCD Type 2 history
Key facts:
No external compute required — it runs inside Lakebase
No pipelines or jobs to manage — it’s a native feature
SCD Type 2 history — every change is appended, giving you full audit trail
Enable it from the Lakebase project settings. Choose which tables to sync and the target catalog/schema. The sync starts immediately and runs continuously.
Once synced, you can query the history:
1
2
3
4
5
-- See the full change history for conversations
SELECT*FROMmain.lakebase_sync.lb_conversations_historyORDERBY_lb_commit_timestampDESCLIMIT20;
Every row change is captured with the operation type (_lb_op) and commit timestamp. This gives you a complete audit trail of every INSERT, UPDATE, and DELETE — without writing a single line of pipeline code.
-- Agent performance over time (from Delta, not hitting Postgres)
SELECTdate_trunc('hour',_lb_commit_timestamp)ashour,count(CASEWHEN_lb_op='INSERT'THEN1END)asnew_conversations,count(CASEWHEN_lb_op='UPDATE'ANDstatus='closed'THEN1END)asresolvedFROMmain.lakebase_sync.lb_conversations_historyWHERE_lb_commit_timestamp>=current_dateGROUPBY1ORDERBY1;
This runs on Delta Lake — zero load on your production Postgres. Your analysts can query freely without impacting application performance.
Lakebase ships with pgvector pre-installed, making it a natural fit for AI applications that need vector similarity search alongside traditional relational data.
CREATETABLEknowledge_base(idSERIALPRIMARYKEY,titleVARCHAR(255)NOTNULL,contentTEXTNOTNULL,embeddingvector(1536),-- OpenAI ada-002 dimensions
categoryVARCHAR(64),created_atTIMESTAMPTZDEFAULTnow());-- Create an index for fast similarity search
CREATEINDEXONknowledge_baseUSINGivfflat(embeddingvector_cosine_ops)WITH(lists=100);
-- Find the 5 most relevant knowledge base articles
-- (assuming you've generated an embedding for the query)
SELECTtitle,content,1-(embedding<=>$1::vector)assimilarityFROMknowledge_baseWHEREcategory='product-docs'ORDERBYembedding<=>$1::vectorLIMIT5;
importpsycopg2frompsycopg2.extrasimportRealDictCursorimportopenaiimportjson# --- Config ---DB_CONN="postgresql://your_role:[email protected]/databricks_postgres?sslmode=require"oai=openai.OpenAI()defget_embedding(text:str)->list[float]:"""Generate embedding using OpenAI ada-002."""resp=oai.embeddings.create(input=text,model="text-embedding-ada-002")returnresp.data[0].embeddingdefseed_knowledge_base(conn):"""Insert sample knowledge base articles with embeddings."""articles=[("Password Reset","To reset your password, go to Settings > Security > Reset Password. You'll receive an email with a reset link."),("Pricing Plans","We offer Starter ($9/mo), Pro ($49/mo), and Enterprise (custom). Pro includes API access and priority support."),("API Rate Limits","Free tier: 100 req/min. Pro: 1000 req/min. Enterprise: custom. Rate limit headers are X-RateLimit-Remaining."),("Data Export","Export your data from Settings > Data > Export. Formats: CSV, JSON, Parquet. Exports over 1GB are async."),]withconn.cursor()ascur:fortitle,contentinarticles:embedding=get_embedding(content)cur.execute("""INSERT INTO knowledge_base (title, content, embedding, category)
VALUES (%s, %s, %s::vector, 'product-docs')
ON CONFLICT DO NOTHING""",(title,content,str(embedding)))conn.commit()defretrieve_context(conn,query:str,top_k:int=3)->list[dict]:"""Semantic search — find most relevant knowledge base articles."""query_embedding=get_embedding(query)withconn.cursor(cursor_factory=RealDictCursor)ascur:cur.execute("""
SELECT title, content,
1 - (embedding <=> %s::vector) as similarity
FROM knowledge_base
WHERE category = 'product-docs'
ORDER BY embedding <=> %s::vector
LIMIT %s """,(str(query_embedding),str(query_embedding),top_k))returncur.fetchall()defsave_message(conn,conversation_id:int,role:str,content:str):"""Persist message to conversation history."""withconn.cursor()ascur:cur.execute("""INSERT INTO messages (conversation_id, role, content, tokens_used)
VALUES (%s, %s, %s, %s)""",(conversation_id,role,content,len(content.split())))conn.commit()# --- Main flow ---conn=psycopg2.connect(DB_CONN)seed_knowledge_base(conn)# User asks a questionuser_question="How do I export my data as Parquet?"# 1. Retrieve relevant context from pgvectorcontext_docs=retrieve_context(conn,user_question)print("Retrieved context:")fordocincontext_docs:print(f" [{doc['similarity']:.3f}] {doc['title']}")# 2. Build prompt with retrieved contextcontext_text="\n\n".join(f"### {d['title']}\n{d['content']}"fordincontext_docs)response=oai.chat.completions.create(model="gpt-4o",messages=[{"role":"system","content":f"Answer using this context:\n\n{context_text}"},{"role":"user","content":user_question}])answer=response.choices[0].message.content# 3. Persist the conversation (ACID-guaranteed)save_message(conn,1,"user",user_question)save_message(conn,1,"assistant",answer)print(f"\nAgent: {answer}")conn.close()
1
2
3
4
5
6
7
8
Retrieved context:
[0.932] Data Export
[0.841] API Rate Limits
[0.823] Pricing Plans
Agent: You can export your data as Parquet by going to Settings > Data > Export
and selecting "Parquet" as the format. Note that exports over 1GB will be
processed asynchronously — you'll receive a notification when it's ready.
This is a full RAG pipeline — retrieval, generation, and persistence — running against a single Lakebase instance. No separate vector database. No external state store.
If you’re using LangGraph for agent orchestration, Lakebase works directly as a Postgres checkpointer:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fromlanggraph.checkpoint.postgresimportPostgresSaver# Lakebase IS Postgres — LangGraph's native checkpointer just workscheckpointer=PostgresSaver.from_conn_string(DB_CONN)checkpointer.setup()# Creates checkpoint tables automatically# Build your agent graph with persistent statefromlanggraph.graphimportStateGraphgraph=StateGraph(...)# ... define nodes and edges ...app=graph.compile(checkpointer=checkpointer)# Each thread_id gets its own conversation state, persisted in Lakebaseconfig={"configurable":{"thread_id":"user-001-session-42"}}result=app.invoke({"messages":[("user","Check order status")]},config)# Resume later — state is loaded from Lakebase automaticallyresult=app.invoke({"messages":[("user","Now cancel it")]},config)
Every LangGraph interaction is stored at the thread level in Lakebase. Your agent can resume investigations across sessions, and the entire conversation state is available in Delta Lake via Lakehouse Sync for analytics.
# app.py — deploy as a Databricks AppfromfastapiimportFastAPI,HTTPExceptionfrompydanticimportBaseModelimportpsycopg2frompsycopg2.extrasimportRealDictCursorimportosapp=FastAPI(title="AI Chatbot API")defget_db():returnpsycopg2.connect(host=os.environ["LAKEBASE_HOST"],port=5432,dbname="databricks_postgres",user=os.environ["LAKEBASE_USER"],password=os.environ["LAKEBASE_PASSWORD"],sslmode="require")classMessageRequest(BaseModel):conversation_id:introle:strcontent:str@app.post("/conversations")defcreate_conversation(user_id:str,agent_id:str="default-agent"):conn=get_db()try:withconn.cursor(cursor_factory=RealDictCursor)ascur:cur.execute("INSERT INTO conversations (user_id, agent_id) VALUES (%s, %s) RETURNING *",(user_id,agent_id))result=cur.fetchone()conn.commit()returnresultfinally:conn.close()@app.get("/conversations/{conv_id}/messages")defget_messages(conv_id:int):conn=get_db()try:withconn.cursor(cursor_factory=RealDictCursor)ascur:cur.execute("SELECT role, content, tokens_used, created_at FROM messages ""WHERE conversation_id = %s ORDER BY created_at",(conv_id,))returncur.fetchall()finally:conn.close()@app.post("/messages")defpost_message(msg:MessageRequest):conn=get_db()try:withconn.cursor(cursor_factory=RealDictCursor)ascur:cur.execute("INSERT INTO messages (conversation_id, role, content, tokens_used) ""VALUES (%s, %s, %s, %s) RETURNING *",(msg.conversation_id,msg.role,msg.content,len(msg.content.split())))result=cur.fetchone()conn.commit()returnresultfinally:conn.close()@app.get("/stats")defagent_stats():"""Real-time agent performance — straight from Lakebase."""conn=get_db()try:withconn.cursor(cursor_factory=RealDictCursor)ascur:cur.execute("""
SELECT c.agent_id,
count(DISTINCT c.id) as conversations,
count(m.id) as total_messages,
round(avg(m.tokens_used), 1) as avg_tokens
FROM conversations c
JOIN messages m ON m.conversation_id = c.id
GROUP BY c.agent_id
""")returncur.fetchall()finally:conn.close()
Lakebase is a paradigm shift for the Databricks platform. For the first time, you can run operational workloads — your application’s transactional database — alongside analytical and AI workloads on a single platform, governed by a single catalog.
The practical implications are significant:
Fewer moving parts — eliminate external databases and CDC pipelines
Faster development — database branching and scale-to-zero change the development workflow
Unified governance — one set of access controls for operational and analytical data
AI-native — pgvector + relational storage in one place for agent state and RAG
If you’re building AI applications on Databricks, Lakebase should be your default starting point for the transactional layer. The days of “Databricks for analytics, something else for OLTP” are numbered.
Lakebase is GA on AWS with Autoscaling (since March 2026) and in public preview on Azure. Check the Databricks documentation for the latest availability and feature updates.
Author
Mariusz Derela
Cyber Security Specialist | DevSecOps | AI/ML
Databricks Deep Dive -
This article is part of a series.