Skip to content

Latest commit

 

History

History
429 lines (299 loc) · 11.1 KB

File metadata and controls

429 lines (299 loc) · 11.1 KB

PostgreSQL Migration Guide

Overview

This project has been migrated from in-memory conversation persistence to PostgreSQL with Entity Framework Core for production-ready, persistent conversation storage.

What Changed

1. Database Provider Migration

Before:

  • SQL Server for tasks
  • In-memory storage for conversations (ConcurrentDictionary)

After:

  • PostgreSQL for both tasks and conversations
  • Entity Framework Core with JSON support

2. New Features

  • Persistent conversation threads across server restarts
  • JSON storage for efficient querying with property order preservation
  • Indexed queries for fast conversation retrieval
  • Metadata tracking (title, preview, message count, timestamps)
  • Production-ready with scoped service lifetime

3. Files Created/Modified

New Files:

  • TaskAgent.Domain/Entities/ConversationThreadEntity.cs - Domain entity
  • TaskAgent.Infrastructure/Services/PostgresThreadPersistenceService.cs - EF Core implementation
  • TaskAgent.Infrastructure/Migrations/20251114223728_AddConversationThreads.cs - Database migration

Modified Files:

  • Directory.Packages.props - Added Npgsql.EntityFrameworkCore.PostgreSQL (9.0.2)
  • TaskAgent.Infrastructure/TaskAgent.Infrastructure.csproj - Package reference
  • TaskAgent.Infrastructure/Data/TaskDbContext.cs - Added ConversationThreads DbSet + JSON config
  • TaskAgent.Infrastructure/InfrastructureServiceExtensions.cs - Changed to PostgreSQL + scoped service
  • appsettings.json - Updated connection string
  • appsettings.Development.json - Updated connection string

Prerequisites

Install PostgreSQL

Windows:

  1. Download PostgreSQL 15+ from https://www.postgresql.org/download/windows/
  2. Run installer and set password for postgres user
  3. Default port: 5432

macOS:

brew install postgresql@15
brew services start postgresql@15

Linux (Ubuntu/Debian):

sudo apt update
sudo apt install postgresql postgresql-contrib
sudo systemctl start postgresql
sudo systemctl enable postgresql

Create Database

# Connect to PostgreSQL
psql -U postgres

# Create database
CREATE DATABASE "TaskAgentDb";

# Verify
\l

# Exit
\q

Configuration

Connection String Format

{
  "ConnectionStrings": {
    "TasksConnection": "Server=localhost;Database=TaskAgentDb;Trusted_Connection=true;Encrypt=False;",
    "ConversationsConnection": "Host=localhost;Port=5432;Database=TaskAgentDb;Username=postgres;Password=your_password"
  }
}

Parameters for ConversationsConnection (PostgreSQL):

  • Host - Database server (default: localhost)
  • Port - PostgreSQL port (default: 5432)
  • Database - Database name (TaskAgentDb)
  • Username - PostgreSQL user (default: postgres)
  • Password - User password (set during installation)

Environment Variables (Alternative)

For production, use environment variables instead of hardcoded credentials:

# PostgreSQL for conversations
export ConnectionStrings__ConversationsConnection="Host=your-host;Port=5432;Database=TaskAgentDb;Username=your_user;Password=your_password"

# SQL Server for tasks
export ConnectionStrings__TasksConnection="Server=your-server;Database=TaskAgentDb;Trusted_Connection=true;Encrypt=True;"

Database Migration

Apply Migrations

The application automatically applies migrations on startup via Program.cs:

await app.ApplyDatabaseMigrationsAsync();

Manual Migration (if needed)

# Navigate to backend services directory
cd src/backend/services/TaskAgent/src

# Apply migrations manually
dotnet ef database update --project TaskAgent.Infrastructure --startup-project TaskAgent.WebApp

# View migration history
dotnet ef migrations list --project TaskAgent.Infrastructure --startup-project TaskAgent.WebApp

# Drop database (for reset)
dotnet ef database drop --project TaskAgent.Infrastructure --startup-project TaskAgent.WebApp

Database Schema

ConversationThreads Table

CREATE TABLE "ConversationThreads" (
    "ThreadId" character varying(100) PRIMARY KEY,
    "SerializedThread" jsonb NOT NULL,
    "CreatedAt" timestamp with time zone NOT NULL,
    "UpdatedAt" timestamp with time zone NOT NULL,
    "MessageCount" integer NOT NULL DEFAULT 0,
    "IsActive" boolean NOT NULL DEFAULT true,
    "Title" character varying(200) NULL,
    "Preview" character varying(500) NULL
);

-- Indexes for performance
CREATE INDEX "IX_ConversationThreads_UpdatedAt" ON "ConversationThreads" ("UpdatedAt" DESC);
CREATE INDEX "IX_ConversationThreads_IsActive" ON "ConversationThreads" ("IsActive");
CREATE INDEX "IX_ConversationThreads_CreatedAt" ON "ConversationThreads" ("CreatedAt" DESC);

JSON Benefits

PostgreSQL's JSON type provides:

  • Property order preservation - Critical for Agent Framework deserialization
  • Text-based storage - Maintains exact JSON structure as written
  • Flexible schema - No rigid structure required
  • Native operators - JSON path queries, field extraction
  • Validation - Ensures valid JSON on insert

Why JSON instead of JSONB?

The Microsoft Agents Framework uses System.Text.Json with polymorphic deserialization, which requires the $type discriminator property to appear first. JSONB reorders properties alphabetically, breaking this requirement. The json type preserves the exact property order.

Example queries:

-- Find threads with specific content
SELECT * FROM "ConversationThreads"
WHERE "SerializedThread"::jsonb @> '{"messages": [{"role": "user"}]}';

-- Extract specific field
SELECT "ThreadId", "SerializedThread"::jsonb->'messages'->0->'content'
FROM "ConversationThreads";

Testing the Migration

1. Start PostgreSQL

Ensure PostgreSQL is running:

# Windows
# Check Windows Services for "postgresql-x64-15"

# macOS
brew services list | grep postgresql

# Linux
sudo systemctl status postgresql

2. Update Connection String

Edit appsettings.Development.json:

{
  "ConnectionStrings": {
    "TasksConnection": "Server=localhost;Database=TaskAgentDb;Trusted_Connection=true;Encrypt=False;",
    "ConversationsConnection": "Host=localhost;Port=5432;Database=TaskAgentDb;Username=postgres;Password=YOUR_PASSWORD_HERE"
  }
}

3. Run Application

# From repository root - with Aspire
dotnet run --project src/TaskAgent.AppHost

# Or standalone
dotnet run --project src/backend/services/TaskAgent/src/TaskAgent.WebApp

4. Test Conversation Persistence

  1. Open browser: https://localhost:5001
  2. Send a message: "Create a high priority task to review Q4 reports"
  3. Note the threadId in the response
  4. Restart the application
  5. Send another message with the same threadId
  6. Verify conversation history is preserved

5. Query Database

psql -U postgres -d TaskAgentDb

-- View all threads
SELECT "ThreadId", "Title", "MessageCount", "CreatedAt"
FROM "ConversationThreads";

-- View specific thread JSON
SELECT "SerializedThread"
FROM "ConversationThreads"
WHERE "ThreadId" = 'your-thread-id';

-- Count active threads
SELECT COUNT(*) FROM "ConversationThreads" WHERE "IsActive" = true;

Architecture Benefits

Before (In-Memory)

❌ Lost on restart
❌ Single server only
❌ No persistence
❌ Memory constraints

After (PostgreSQL)

✅ Persistent storage
✅ Multi-server ready (with shared DB)
✅ Scalable (millions of conversations)
✅ JSON queries (fast and flexible, preserves order)
✅ Production-ready
✅ Backup/restore support

Performance Considerations

Indexing Strategy

The migration creates 3 indexes:

  1. UpdatedAt DESC - For "recent conversations" queries
  2. IsActive - For filtering active/archived threads
  3. CreatedAt DESC - For chronological sorting

Query Performance

Typical queries:

  • List threads: ~10ms (with indexes)
  • Get thread by ID: ~5ms (primary key lookup)
  • Save thread: ~15ms (JSON serialization + write)

Optimization tips:

  • Use .AsNoTracking() for read-only queries
  • Batch updates when possible
  • Consider pagination (already implemented: 20 threads/page)

Troubleshooting

Error: "Could not connect to server"

Solution: Ensure PostgreSQL is running and connection string is correct.

# Test connection
psql -U postgres -h localhost -p 5432

Error: "database 'TaskAgentDb' does not exist"

Solution: Create the database manually:

psql -U postgres
CREATE DATABASE "TaskAgentDb";

Error: "password authentication failed"

Solution: Verify password in connection string matches PostgreSQL user password.

# Reset password
psql -U postgres
ALTER USER postgres PASSWORD 'new_password';

Error: "JSON type not supported"

Solution: Ensure PostgreSQL version is 9.2+ (JSON was introduced in 9.2, JSONB in 9.4).

psql -U postgres -c "SELECT version();"

Docker Setup (Optional)

For development, you can use Docker to run PostgreSQL:

# Run PostgreSQL container
docker run --name taskagent-postgres \
  -e POSTGRES_PASSWORD=postgres \
  -e POSTGRES_DB=TaskAgentDb \
  -p 5432:5432 \
  -d postgres:15

# Connect to container
docker exec -it taskagent-postgres psql -U postgres -d TaskAgentDb

# Stop container
docker stop taskagent-postgres

# Start container
docker start taskagent-postgres

Production Deployment

Azure Database for PostgreSQL

Connection string format:

Host=your-server.postgres.database.azure.com;Port=5432;Database=TaskAgentDb;Username=your_user@your-server;Password=your_password;SslMode=Require

AWS RDS PostgreSQL

Connection string format:

Host=your-instance.region.rds.amazonaws.com;Port=5432;Database=TaskAgentDb;Username=your_user;Password=your_password;SslMode=Require

Environment-Specific Settings

{
  "ConnectionStrings": {
    "TasksConnection": "Server=localhost;Database=TaskAgentDb;Trusted_Connection=true;Encrypt=False;",
    "ConversationsConnection": "Host=localhost;Port=5432;Database=TaskAgentDb;Username=postgres;Password=dev_password"
  }
}

Rollback Plan

If you need to revert to in-memory storage:

  1. Restore original service registration:
// InfrastructureServiceExtensions.cs
services.AddSingleton<IThreadPersistenceService, InMemoryThreadPersistenceService>();
  1. Remove migration:
dotnet ef migrations remove --project TaskAgent.Infrastructure --startup-project TaskAgent.WebApp
  1. Restore connection string to SQL Server in appsettings.json

Next Steps

  • Implement conversation archiving (soft delete)
  • Add conversation search by content (JSONB queries)
  • Implement conversation export (JSON download)
  • Add conversation analytics (message count trends)
  • Optimize JSONB queries with GIN indexes

Resources