Database Configuration
Configure and manage PostgreSQL database for Alita Robot.
Database Configuration
Alita Robot uses PostgreSQL as its primary database with GORM as the ORM layer. This guide covers database setup, migrations, connection pooling, and schema design.
Requirements
:::note PostgreSQL is the only supported database engine. MySQL, SQLite, and other databases are not supported. :::
- PostgreSQL 16 or higher
- UTF-8 encoding support
Connection String
Configure the database URL in your environment:
# Format
DATABASE_URL=postgres://username:password@host:port/database?sslmode=disable
# Example (local development)
DATABASE_URL=postgres://postgres:password@localhost:5432/alita_robot?sslmode=disable
# Example (Docker Compose)
DATABASE_URL=postgresql://alita:alita@postgres:5432/alita
# Example (production with SSL)
DATABASE_URL=postgres://user:pass@db.example.com:5432/alita?sslmode=require
SSL Modes
:::caution
Never use sslmode=disable in production. Always use require or stronger to protect data in transit.
:::
| Mode | Description |
|---|---|
disable |
No SSL (development only) |
require |
Require SSL but don’t verify certificate |
verify-ca |
Require SSL and verify server certificate |
verify-full |
Require SSL, verify certificate and hostname |
Automatic Migrations
Alita Robot supports automatic database migrations on startup, eliminating the need to manually run migration commands.
Enabling Auto-Migration
# Enable automatic migrations
AUTO_MIGRATE=true
# Optional: Continue running even if migrations fail (not recommended for production)
AUTO_MIGRATE_SILENT_FAIL=false
# Optional: Custom migration directory
MIGRATIONS_PATH=migrations
How Auto-Migration Works
:::tip Auto-migration is safe to enable on every startup for a single bot instance. If you run multiple replicas, serialize deployments so only one instance migrates at a time. :::
- Migration Files: SQL migrations are stored in the
migrations/directory - Version Tracking: Applied migrations and their SHA-256 checksums are tracked in the
schema_migrationstable - Immutable History: Changing an applied file causes startup to fail with a checksum mismatch
- Transactional: Each migration runs in a transaction for atomicity
- Auto-Cleaning: Supabase-specific SQL (GRANT statements, RLS policies) is automatically removed
Migration Process
When AUTO_MIGRATE=true, the bot will:
- Check for pending migrations in
migrations/ - Clean any Supabase-specific SQL commands automatically
- Apply migrations in alphabetical order (by filename)
- Track applied migrations in
schema_migrationstable - Log migration status and any errors
Example log output:
[Migrations] Starting automatic database migration...
[Migrations] Found N migration files
[Migrations] Applying 20250805200527_initial_migration.sql...
[Migrations] Successfully applied 20250805200527_initial_migration.sql
[Migrations] Migration complete - Applied: N, Skipped: M
Manual Migration Commands
If you prefer manual control, the repository’s migration script uses the same migrations/ source files, cleans Supabase-only SQL into a temporary directory, and applies each file transactionally.
Apply Migrations
# Set required environment variables
export PSQL_DB_HOST=localhost
export PSQL_DB_NAME=alita
export PSQL_DB_USER=postgres
export PSQL_DB_PASSWORD=password
export PSQL_DB_PORT=5432 # Optional, defaults to 5432
export PSQL_DB_SSLMODE=prefer # Optional
# Apply all pending migrations
make psql-migrate
Check Migration Status
make psql-status
The manual runner also verifies the checksum of every previously applied migration. Legacy rows without checksums are backfilled on their first check.
:::caution
Migrations are forward-only. Never edit an applied migration or insert/delete schema_migrations rows by hand. Add a new timestamped migration for every schema change.
:::
Additional Database Commands
# Validate database for orphaned data before major migrations
make validate-db
# Backup database before migrations
make backup-db
Output:
version | executed_at
-------------------------------------------------------+----------------------------
20250805200527_initial_migration.sql | 2025-08-05 20:05:27.000000
...
Reset Database (DANGEROUS)
:::danger[Irreversible Data Loss] This will drop ALL tables and delete ALL data. There is no undo. Make sure you have a verified backup before running this command. :::
This will drop all tables and recreate the schema:
make psql-reset
You will be prompted to confirm with yes before proceeding.
Connection Pool Configuration
Optimize database performance with connection pooling:
# Maximum idle connections in the pool
# Default: 50, Recommended: 30-80 depending on deployment size
DB_MAX_IDLE_CONNS=50
# Maximum open connections to the database
# Default: 200, Recommended: 150-400 depending on deployment size
DB_MAX_OPEN_CONNS=200
# Maximum connection lifetime in minutes
# Default: 240, Recommended: 120-480 minutes
DB_CONN_MAX_LIFETIME_MIN=240
# Maximum idle time in minutes
# Default: 60, Valid range: 1-60 minutes
DB_CONN_MAX_IDLE_TIME_MIN=60
Sizing Guidelines
:::tip[Performance Tuning] Start with the defaults and only adjust pool settings if you observe connection exhaustion or idle timeout issues in the logs. Over-provisioning connections wastes database resources. :::
| Deployment Size | MAX_IDLE_CONNS | MAX_OPEN_CONNS | Use Case |
|---|---|---|---|
| Small | 10-30 | 100 | < 50 groups |
| Medium | 30-50 | 200 | 50-500 groups |
| Large | 50-80 | 300-400 | 500+ groups |
Schema Design Patterns
Alita Robot uses a surrogate key pattern for application tables. The schema_migrations metadata table is keyed by migration filename instead.
Primary Keys
Each table has an auto-incremented id field as the primary key (internal identifier):
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY, -- Internal ID
user_id BIGINT NOT NULL UNIQUE, -- Telegram user ID
username VARCHAR(255),
name VARCHAR(255),
language VARCHAR(10) DEFAULT 'en',
created_at TIMESTAMP,
updated_at TIMESTAMP
);
Benefits
- Decoupling: Internal schema is independent of external systems (Telegram IDs)
- Stability: If external IDs change or new platforms are added, internal references remain stable
- Performance: Integer primary keys are faster for joins and indexing
- GORM Compatibility: Consistent integer primary keys simplify ORM operations
Business Keys
External identifiers like user_id (Telegram user ID) and chat_id (Telegram chat ID) are stored with unique constraints:
user_id BIGINT NOT NULL UNIQUE -- Prevents duplicates
chat_id BIGINT NOT NULL UNIQUE -- Ensures one row per chat
Exception: Join Tables
Chat membership is managed via the JSONB users column on the chats table, not a physical join table. The chat_users join table and its GORM model have both been removed.
Database Tables
Alita Robot creates the following tables:
| Table | Purpose |
|---|---|
users |
Telegram user data |
chats |
Chat/group information |
warns_settings |
Warning configuration per chat |
warns_users |
User warning records |
greetings |
Welcome/goodbye messages |
filters |
Chat filters |
notes |
Saved notes |
notes_settings |
Notes configuration |
rules |
Chat rules |
blacklists |
Blacklisted words |
locks |
Lock settings |
pins |
Pin settings |
admin |
Admin settings |
antiflood_settings |
Anti-flood configuration |
connection |
Connection settings |
connection_settings |
Chat connection config |
disable |
Disabled commands |
disable_chat_settings |
Per-chat disable settings |
report_chat_settings |
Report configuration |
report_user_settings |
User report preferences |
devs |
Developer settings |
channels |
Linked channels |
approved_users |
Approved users immune to anti-spam |
antiraid_settings |
Anti-raid configuration |
captcha_settings |
Captcha configuration |
captcha_attempts |
Active captcha attempts |
captcha_muted_users |
Users muted due to captcha failure |
stored_messages |
Messages stored during captcha |
reactions |
Per-chat keyword reactions |
schema_migrations |
Migration versions and checksums |
Backup and Restore
:::caution Always test your backup restoration process before relying on it in production. A backup that cannot be restored is worthless. :::
Backup
# Using pg_dump
pg_dump -h localhost -U postgres -d alita > backup.sql
# Compressed backup
pg_dump -h localhost -U postgres -d alita | gzip > backup.sql.gz
# Docker
docker compose exec -T postgres pg_dump -U alita -d alita > backup.sql
Restore
# From SQL file
psql -h localhost -U postgres -d alita < backup.sql
# From compressed file
gunzip -c backup.sql.gz | psql -h localhost -U postgres -d alita
# Docker
docker compose exec -T postgres psql -U alita -d alita < backup.sql
Troubleshooting
Connection refused
Failed to connect to database: connection refused
- Verify PostgreSQL is running
- Check host and port in
DATABASE_URL - Ensure firewall allows connections on port 5432
Authentication failed
password authentication failed for user
- Verify username and password in connection string
- Check
pg_hba.confauthentication settings
Too many connections
too many connections for role
- Reduce
DB_MAX_OPEN_CONNS - Increase PostgreSQL
max_connectionsinpostgresql.conf - Consider using connection pooling (PgBouncer)
Migration failed
Migration failed: column already exists
Each migration is transactional, so a failed file is not recorded as applied. Inspect the failing statement and reconcile the existing schema so the migration can run successfully. Do not bypass the failure with AUTO_MIGRATE_SILENT_FAIL or manually mark the file as applied; either can leave the application running against an incomplete schema.
Slow queries
Enable query logging in PostgreSQL:
ALTER SYSTEM SET log_min_duration_statement = 1000; -- Log queries > 1 second
SELECT pg_reload_conf();
Or enable debug mode in the bot:
DEBUG=true