Build and Query with Azure Database for PostgreSQL
Introduction
3 minAzure Database for PostgreSQL is a fully managed PostgreSQL service β Microsoft handles patching, backups, HA, and connection pooling. You get full PostgreSQL compatibility including JSONB, extensions (pgvector for vector search), and transactional guarantees. Perfect for AI agents storing conversation history, task state, and structured context alongside vector embeddings.
Explore Azure Database for PostgreSQL
7 minPostgreSQL Connection Architecture: App β Entra token β PgBouncer (port 6432) β PostgreSQL
1. Compute Tiers
| # | Tier | VM Series | CPU Burst | PgBouncer | Use For |
|---|---|---|---|---|---|
| 1 | Burstable | B-series | Yes | β No | Dev/test, small apps, proof-of-concept |
| 2 | General Purpose | D-series | No | β Yes | Production APIs, steady workloads |
| 3 | Memory Optimized | E-series | No | β Yes | Complex queries, large AI working sets, caching |
2. Managed Capabilities
- Automatic backups β 7β35 day retention, point-in-time restore to any second within the window. AES-256 encrypted at rest.
- High Availability β zone-redundant HA with automatic failover. Standby replica in a different AZ.
- Extensions β enable with
CREATE EXTENSION:pgvectorβ vector similarity search (cosine, L2, inner product)pg_trgmβ fuzzy text matching for AI document searchuuid-osspβ UUID generationhstoreβ key-value pairs in a column
Connect to PostgreSQL
10 min1. Connection Parameters
- Endpoint:
servername.postgres.database.azure.com - Direct port: 5432
- PgBouncer port: 6432
- Username format (Entra):
username@servername
2. Entra ID Authentication (Recommended)
Short-lived OAuth2 tokens instead of passwords. Works with managed identities. No stored credentials.
from azure.identity import DefaultAzureCredential
credential = DefaultAzureCredential()
token = credential.get_token("https://ossrdbms-aad.database.windows.net/.default")
# Use token.token as the password in your psycopg2 connection string https://ossrdbms-aad.database.windows.net/.default. Memorize this URL β the exam tests it. Tokens auto-refresh; no manual rotation needed. 3. TLS / SSL Modes
- disable β no encryption. Azure rejects this for managed PostgreSQL.
- require β encrypts connection but does NOT validate server certificate.
- verify-ca β validates the certificate authority.
- verify-full β validates CA AND hostname. Recommended for production.
verify-full. The exam asks "which mode ensures the client is connecting to the correct server?" β answer: verify-full. 4. PgBouncer β Connection Pooling
AI applications make many short-lived DB calls (one per inference request). Each new TCP connection is expensive. PgBouncer pools connections β the app creates a connection to PgBouncer, which reuses existing DB connections.
az postgres flexible-server parameter set \\
--resource-group rg --server-name myserver \\
--name pgbouncer.enabled --value true
# Connect via PgBouncer on port 6432
postgresql://[email protected]:6432/mydb?sslmode=require Create and Manage Schemas
10 min1. Hierarchy: Server β Database β Schema β Table
Default schema is public. Use separate databases for full isolation; separate schemas for logical grouping with cross-schema JOINs possible.
2. AI-Relevant Data Types
- JSONB β binary JSON with GIN indexing. Use for flexible metadata, model parameters, nested structures.
- TIMESTAMPTZ β always use over TIMESTAMP for global apps. Stores UTC, displays per session timezone.
- BIGSERIAL β auto-incrementing 64-bit integer PK. Use over SERIAL (32-bit overflows on high-volume tables).
- TEXT β unbounded string. Same performance as VARCHAR in PostgreSQL. No reason to use VARCHAR unless enforcing max length.
- UUID β universally unique ID. Use
DEFAULT gen_random_uuid(). Good for distributed, globally-unique identifiers.
3. Conversation History Table Example
CREATE TABLE conversations (
id BIGSERIAL PRIMARY KEY,
session_id UUID NOT NULL DEFAULT gen_random_uuid(),
user_id VARCHAR(255) NOT NULL,
started_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
metadata JSONB DEFAULT '{}'::jsonb
);
CREATE TABLE messages (
id BIGSERIAL PRIMARY KEY,
conversation_id BIGINT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
role VARCHAR(50) NOT NULL CHECK (role IN ('user', 'assistant', 'system')),
content TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
); 4. Indexes
CREATE INDEX idx_messages_conversation_id ON messages(conversation_id);
CREATE INDEX idx_messages_conv_created ON messages(conversation_id, created_at); Indexes speed reads but slow writes. Add indexes when query profiling reveals slow queries β not preemptively on every column.
5. Transactional DDL (PostgreSQL Superpower)
BEGIN;
ALTER TABLE conversations ADD COLUMN category VARCHAR(100);
CREATE INDEX idx_conversations_category ON conversations(category);
COMMIT; -- or ROLLBACK on failure Query Data
10 min1. JSONB Queries
-- Access JSONB field
SELECT metadata->>'model' AS model_name FROM conversations;
-- Filter on JSONB (containment operator @>)
SELECT * FROM conversations WHERE metadata @> '{"tier": "premium"}'::jsonb;
-- Check JSONB key existence
SELECT * FROM conversations WHERE metadata ? 'user_preferences'; 2. Upserts with ON CONFLICT
INSERT INTO conversations (session_id, user_id) VALUES ($1, $2)
ON CONFLICT (session_id)
DO UPDATE SET user_id = EXCLUDED.user_id, started_at = CURRENT_TIMESTAMP; 3. Keyset Pagination (Avoid OFFSET)
OFFSET gets slower as the offset grows. Keyset pagination is O(log n) with the right index:
-- Get next 50 messages after last seen id
SELECT * FROM messages
WHERE conversation_id = $1 AND id > $last_id
ORDER BY id ASC LIMIT 50; 4. INSERT RETURNING (Avoid Extra SELECT)
INSERT INTO messages (conversation_id, role, content)
VALUES ($1, 'user', $2)
RETURNING id, created_at; -- Get generated values without extra round-trip β‘ PostgreSQL Master Cheatsheet
https://ossrdbms-aad.database.windows.net/.defaultExercise β Conversation History Store
30 min- Create a PostgreSQL server (General Purpose tier)
- Enable PgBouncer and verify port 6432
- Obtain Entra token and connect with psycopg2
- Create conversations and messages tables with JSONB metadata
- Insert messages, query with JSONB filter, implement keyset pagination
- Wrap an ALTER TABLE + CREATE INDEX in a transaction
Knowledge Check
5 min- Q: Which tier supports PgBouncer? A: General Purpose and Memory Optimized (not Burstable)
- Q: SSL mode that validates both CA and hostname? A: verify-full
- Q: AI app needs flexible metadata alongside relational data. Data type? A: JSONB
- Q: Why keyset pagination instead of OFFSET? A: OFFSET performance degrades as offset grows; keyset is O(log n) with the right index
- Q: Entra token resource URL for PostgreSQL? A: https://ossrdbms-aad.database.windows.net/.default
Summary
2 minChoose General Purpose or higher for production (PgBouncer access). Use Entra authentication with token-based access. Always enforce verify-full TLS. Design schemas using TIMESTAMPTZ and BIGSERIAL. Use JSONB for flexible AI metadata. Enable PgBouncer on port 6432 for high-concurrency AI services. Wrap schema changes in transactions.
π§ Memory Tricks
"BuGM" β Burstable (no PgBouncer), General Purpose (PgBouncer β ), Memory Optimized (PgBouncer β ). Want Bouncer? Go General or above.
TLS modes order: disable (blocked) β require (encrypt only) β verify-ca (+ CA check) β verify-full (+ hostname check = best)
Exam Summary Card
2 min| Scenario | Answer |
|---|---|
| Connection pooling needed | General Purpose or Memory Optimized tier (port 6432) |
| Secure Entra auth token resource | https://ossrdbms-aad.database.windows.net/.default |
| Validates CA AND hostname | sslmode=verify-full |
| Flexible metadata type | JSONB |
| Vector similarity search | pgvector extension |
| Large dataset pagination | Keyset (WHERE id > last_id LIMIT N) |
| Schema change that auto-rollbacks on failure | ALTER TABLE inside BEGIN/COMMIT transaction |
| UUID primary key | DEFAULT gen_random_uuid() |
Azure Database for PostgreSQL
π Key Facts
- PgBouncer tier β General Purpose or Memory Optimized ONLY (not Burstable)
- PgBouncer port β 6432 (vs 5432 direct)
- Entra token resource β https://ossrdbms-aad.database.windows.net/.default
- Best TLS mode β verify-full β validates CA AND hostname
- JSONB β Binary JSON with GIN indexing β use for flexible AI metadata
- BIGSERIAL β Auto-increment 64-bit PK (use over SERIAL which overflows)
- TIMESTAMPTZ β Always over TIMESTAMP β stores UTC, timezone-aware
- Transactional DDL β Wrap ALTER TABLE in BEGIN...COMMIT β rolls back on failure
π» Commands & Patterns
-- Create AI messages table
CREATE TABLE messages (
id BIGSERIAL PRIMARY KEY,
session_id UUID NOT NULL DEFAULT gen_random_uuid(),
role VARCHAR(50) CHECK (role IN ('user','assistant')),
content TEXT NOT NULL,
metadata JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Keyset pagination (not OFFSET)
SELECT * FROM messages
WHERE session_id=$1 AND id > $last_id
ORDER BY id ASC LIMIT 50;
-- Transactional DDL
BEGIN;
ALTER TABLE messages ADD COLUMN tokens INT;
CREATE INDEX idx ON messages(tokens);
COMMIT; Vector Search with pgvector and RAG Patterns
Introduction to pgvector
3 minpgvector is a PostgreSQL extension that adds a vector data type and vector similarity operators. Azure Database for PostgreSQL Flexible Server ships with pgvector pre-installed β enabling RAG (Retrieval-Augmented Generation) directly in your existing Postgres database.
Setup and Store Embeddings
8 minEnable pgvector and Create Table
-- Enable extension (once per database)
CREATE EXTENSION IF NOT EXISTS vector;
-- Create table with embedding column
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
content TEXT NOT NULL,
embedding vector(1536), -- matches text-embedding-3-small
category TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- HNSW index for fast approximate search
CREATE INDEX ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m=16, ef_construction=64);
-- Insert with Python
import psycopg2
cur.execute(
"INSERT INTO documents (title, content, embedding) VALUES (%s, %s, %s)",
(title, content, embedding) # embedding = list of floats
) Similarity Search and RAG
10 minVector Search + RAG Pipeline
import psycopg2
import openai
def rag_answer(user_question):
# 1. Embed the question
q_emb = oai.embeddings.create(
input=user_question,
model="text-embedding-3-small"
).data[0].embedding
# 2. Find top-5 similar docs (cosine distance)
cur.execute("""
SELECT content, title,
1 - (embedding <=> %s::vector) AS score
FROM documents
ORDER BY embedding <=> %s::vector
LIMIT 5
""", [str(q_emb), str(q_emb)])
chunks = cur.fetchall()
context = "\n\n".join(
f"[{title}]: {content}"
for content, title, score in chunks
)
# 3. Generate answer with context
response = oai.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content":
f"Answer using context:\n{context}"},
{"role": "user", "content": user_question}
]
)
return response.choices[0].message.content <=> = cosine distance (use for text). <-> = L2/Euclidean. 1 - distance = cosine similarity (0β1). Always cast to ::vector when passing from Python. HNSW vs IVFFlat Indexes
6 min| Index | Recall | Build Speed | Memory | Use When |
|---|---|---|---|---|
| HNSW | Higher | Slower | Higher | Production, few million rows |
| IVFFlat | Lower | Faster | Lower | Large datasets, memory constrained |
vector_cosine_ops for text embeddings. Summary
2 minpgvector: CREATE EXTENSION vector β vector(1536) column β HNSW index with vector_cosine_ops β <=> cosine distance in queries β RAG pipeline: embed question, find top-K, pass context to LLM. Azure PostgreSQL Flexible Server includes pgvector pre-installed. HNSW = better recall; IVFFlat = lower memory. Always use 1 - distance for similarity score.