{"slug":"databases-postgresql","title":"PostgreSQL Database Instances","description":"Complete guide to creating and managing PostgreSQL database instances on DanubeData.","section":"Features","url":"https://docs.danubedata.ro/databases-postgresql","markdown_url":"https://docs.danubedata.ro/databases-postgresql.md","breadcrumbs":[{"title":"Features","slug":null},{"title":"Databases","slug":"databases-overview"},{"title":"PostgreSQL","slug":"databases-postgresql"}],"headings":[{"level":1,"title":"PostgreSQL Database Instances","id":"postgresql-database-instances"},{"level":2,"title":"Overview","id":"overview"},{"level":2,"title":"Supported Versions","id":"supported-versions"},{"level":3,"title":"Version Selection","id":"version-selection"},{"level":3,"title":"Upgrading Versions","id":"upgrading-versions"},{"level":2,"title":"Creating a PostgreSQL Instance","id":"creating-a-postgresql-instance"},{"level":3,"title":"Step-by-Step","id":"step-by-step"},{"level":3,"title":"Configuration Tips","id":"configuration-tips"},{"level":2,"title":"Connecting to PostgreSQL","id":"connecting-to-postgresql"},{"level":3,"title":"Connection Information","id":"connection-information"},{"level":3,"title":"psql Command Line","id":"psql-command-line"},{"level":3,"title":"Connection String","id":"connection-string"},{"level":3,"title":"From Applications","id":"from-applications"},{"level":2,"title":"Two sets of credentials (managed PostgreSQL)","id":"two-sets-of-credentials-managed-postgresql"},{"level":2,"title":"Database Management","id":"database-management"},{"level":3,"title":"Create Databases","id":"create-databases"},{"level":3,"title":"Create Users","id":"create-users"},{"level":3,"title":"Create Tables","id":"create-tables"},{"level":2,"title":"PostgreSQL-Specific Features","id":"postgresql-specific-features"},{"level":3,"title":"PostGIS (One-Click Enable)","id":"postgis-one-click-enable"},{"level":3,"title":"JSONB Support","id":"jsonb-support"},{"level":3,"title":"Full-Text Search","id":"full-text-search"},{"level":3,"title":"Array Types","id":"array-types"},{"level":3,"title":"Window Functions","id":"window-functions"},{"level":3,"title":"Common Table Expressions (CTEs)","id":"common-table-expressions-ctes"},{"level":2,"title":"Performance Optimization","id":"performance-optimization"},{"level":3,"title":"Indexes","id":"indexes"},{"level":3,"title":"Query Optimization","id":"query-optimization"},{"level":3,"title":"Connection Pooling","id":"connection-pooling"},{"level":2,"title":"Monitoring & Metrics","id":"monitoring-metrics"},{"level":3,"title":"Key Metrics","id":"key-metrics"},{"level":3,"title":"Database Statistics","id":"database-statistics"},{"level":2,"title":"Backup & Recovery","id":"backup-recovery"},{"level":3,"title":"Automated Backups","id":"automated-backups"},{"level":3,"title":"Manual Snapshots","id":"manual-snapshots"},{"level":3,"title":"Export Database","id":"export-database"},{"level":3,"title":"Import Database","id":"import-database"},{"level":2,"title":"Read Replicas","id":"read-replicas"},{"level":3,"title":"Create Replica","id":"create-replica"},{"level":3,"title":"Benefits","id":"benefits"},{"level":3,"title":"Using Replicas","id":"using-replicas"},{"level":2,"title":"Security","id":"security"},{"level":3,"title":"SSL/TLS Connections","id":"ssltls-connections"},{"level":3,"title":"User Permissions","id":"user-permissions"},{"level":2,"title":"Troubleshooting","id":"troubleshooting"},{"level":3,"title":"Connection Issues","id":"connection-issues"},{"level":3,"title":"Performance Issues","id":"performance-issues"},{"level":2,"title":"Best Practices","id":"best-practices"},{"level":3,"title":"Schema Design","id":"schema-design"},{"level":3,"title":"Query Optimization","id":"query-optimization"},{"level":3,"title":"Maintenance","id":"maintenance"},{"level":2,"title":"Next Steps","id":"next-steps"}],"format":"markdown","word_count":2647,"content":"# PostgreSQL Database Instances\n\nComplete guide to creating and managing PostgreSQL database instances on DanubeData.\n\n## Overview\n\nPostgreSQL is a powerful, open-source object-relational database system. DanubeData provides fully managed PostgreSQL instances with:\n\n- **Automated backups** - Daily snapshots with 3-day retention, plus continuous backups with 30-day retention\n- **High availability** - Optional synchronized standby with automatic failover\n- **Performance monitoring** - Real-time metrics and alerts\n- **Easy scaling** - Vertical scaling, read replicas, and optional [storage autoscaling](https://docs.danubedata.ro/databases-overview#storage-autoscaling)\n- **In-place upgrades** - Move to a newer PostgreSQL version without recreating\n- **Security** - Encrypted connections and data\n\nEvery managed PostgreSQL instance also includes [SQL Studio](https://docs.danubedata.ro/databases-sql-studio) — an in-browser data studio for browsing, editing, and managing your schema, with no client to install.\n\n## Supported Versions\n\n- **PostgreSQL 18** - Latest, recommended\n- **PostgreSQL 17** - Current stable\n- **PostgreSQL 16** - Previous stable\n- **PostgreSQL 15** - Mature release\n\n### Version Selection\n\n**PostgreSQL 18** (Recommended):\n- Latest features\n- Asynchronous I/O (io_uring)\n- Virtual generated columns\n- Native OAuth support (requires manual configuration)\n\n**PostgreSQL 17**:\n- Stable and mature\n- Improved partitioning\n- Better JSON support\n\n**PostgreSQL 16**:\n- Wide adoption\n- Logical replication improvements\n\n**PostgreSQL 15**:\n- Very stable\n- Good for production\n- Mature ecosystem\n\n### Upgrading Versions\n\nYou can move an existing PostgreSQL instance to a newer version in place — no need to recreate it or dump and reload:\n\n1. Open the **Updates** tab on your instance\n2. Choose an available target version\n3. Start the upgrade — a snapshot is taken automatically first, so you can roll back if needed\n4. Track progress live; the instance shows an **Updating** status while it runs\n\nTake a manual snapshot first if you want an extra restore point.\n\n## Creating a PostgreSQL Instance\n\n### Step-by-Step\n\n1. Navigate to **Databases**\n2. Click **Create Database Instance**\n3. Select **PostgreSQL** as engine\n4. Choose version (18 recommended)\n5. Select resource profile\n6. Configure settings:\n   - Instance name\n   - Database name\n   - Master username\n   - Master password\n   - Region\n7. Optional: Parameter group, firewall\n8. Click **Create Database**\n\n**Creation Time**: 2-3 minutes\n\n### Configuration Tips\n\n**Instance Name**: `prod-postgres-users`  \n**Database Name**: `production` (initial database)  \n**Master Username**: `postgres` (default) or custom  \n**Master Password**: Auto-generated or custom (min 8 chars)  \n\n## Connecting to PostgreSQL\n\n### Connection Information\n\nAfter creation:\n- **Endpoint**: `postgres-abc123.danubedata.ro`\n- **Port**: `5432`\n- **Username**: Your master username\n- **Password**: Master password\n\n### psql Command Line\n\n```bash\npsql -h postgres-abc123.danubedata.ro \\\n     -p 5432 \\\n     -U postgres \\\n     -d production\n\n# Or with full connection string\npsql \"postgresql://postgres:password@postgres-abc123.danubedata.ro:5432/production\"\n```\n\n### Connection String\n\n```\npostgresql://username:password@host:port/database\npostgresql://postgres:mypassword@postgres-abc123.danubedata.ro:5432/production\n```\n\n### From Applications\n\n**Python (psycopg2)**:\n```python\nimport psycopg2\n\nconn = psycopg2.connect(\n    host=\"postgres-abc123.danubedata.ro\",\n    port=5432,\n    database=\"production\",\n    user=\"postgres\",\n    password=\"your_password\"\n)\n\ncur = conn.cursor()\ncur.execute(\"SELECT version()\")\nversion = cur.fetchone()\nprint(f\"PostgreSQL version: {version[0]}\")\n\ncur.close()\nconn.close()\n```\n\n**Python (SQLAlchemy)**:\n```python\nfrom sqlalchemy import create_engine\n\nengine = create_engine(\n    'postgresql://postgres:password@postgres-abc123.danubedata.ro:5432/production'\n)\n\nwith engine.connect() as conn:\n    result = conn.execute(\"SELECT 1\")\n    print(result.fetchone())\n```\n\n**Node.js (pg)**:\n```javascript\nconst { Client } = require('pg');\n\nconst client = new Client({\n  host: 'postgres-abc123.danubedata.ro',\n  port: 5432,\n  database: 'production',\n  user: 'postgres',\n  password: 'your_password',\n});\n\nclient.connect();\n\nclient.query('SELECT NOW()', (err, res) => {\n  console.log(err ? err.stack : res.rows[0]);\n  client.end();\n});\n```\n\n**Java (JDBC)**:\n```java\nimport java.sql.*;\n\npublic class PostgreSQLExample {\n    public static void main(String[] args) {\n        String url = \"jdbc:postgresql://postgres-abc123.danubedata.ro:5432/production\";\n        String user = \"postgres\";\n        String password = \"your_password\";\n        \n        try {\n            Connection conn = DriverManager.getConnection(url, user, password);\n            System.out.println(\"Connected to PostgreSQL\");\n            \n            Statement stmt = conn.createStatement();\n            ResultSet rs = stmt.executeQuery(\"SELECT version()\");\n            \n            while (rs.next()) {\n                System.out.println(rs.getString(1));\n            }\n            \n            conn.close();\n        } catch (SQLException e) {\n            e.printStackTrace();\n        }\n    }\n}\n```\n\n**PHP (PDO)**:\n```php\n<?php\n$host = 'postgres-abc123.danubedata.ro';\n$port = 5432;\n$dbname = 'production';\n$user = 'postgres';\n$password = 'your_password';\n\ntry {\n    $dsn = \"pgsql:host=$host;port=$port;dbname=$dbname\";\n    $pdo = new PDO($dsn, $user, $password);\n    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);\n    \n    $stmt = $pdo->query('SELECT version()');\n    $version = $stmt->fetchColumn();\n    echo \"PostgreSQL version: $version\\n\";\n} catch (PDOException $e) {\n    echo \"Error: \" . $e->getMessage();\n}\n?>\n```\n\n**Go**:\n```go\npackage main\n\nimport (\n    \"database/sql\"\n    \"fmt\"\n    _ \"github.com/lib/pq\"\n)\n\nfunc main() {\n    // A public *.danubedata.ro hostname is reached through the shared load\n    // balancer, where this instance has its own port — copy it from the\n    // dashboard (or the API's connection_port), it is not 5432. Use 5432 only\n    // with the private-network hostname.\n    connStr := \"host=postgres-abc123.danubedata.ro port=YOUR_PUBLIC_PORT user=postgres password=your_password dbname=production sslmode=require\"\n    \n    db, err := sql.Open(\"postgres\", connStr)\n    if err != nil {\n        panic(err)\n    }\n    defer db.close()\n    \n    var version string\n    err = db.QueryRow(\"SELECT version()\").Scan(&version)\n    if err != nil {\n        panic(err)\n    }\n    \n    fmt.Println(\"PostgreSQL version:\", version)\n}\n```\n\n## Two sets of credentials (managed PostgreSQL)\n\nFor managed PostgreSQL clusters (currently PostgreSQL 18+), the dashboard\nexposes **two distinct credentials**:\n\n| Role | Database | Use for | Privileges |\n|---|---|---|---|\n| `pguser` | `pgdb` | Day-to-day application traffic | Standard owner of `pgdb` — full access inside it |\n| `postgres` | `postgres` | Admin tasks: `CREATE DATABASE`, installing extensions, role management | Full PostgreSQL superuser |\n\nMost applications should use `pguser` / `pgdb`. Connect as `postgres` only\nwhen you need cluster-wide admin capabilities.\n\n> ⚠️ **Do not rotate the `postgres` password via SQL.** Statements like\n> `ALTER ROLE postgres PASSWORD '…'` will break automated failover and\n> backups managed by the DanubeData platform. Contact support if you need\n> to rotate this credential.\n\n## Database Management\n\n### Create Databases\n\n```sql\n-- Create database\nCREATE DATABASE myapp_production\n    WITH ENCODING 'UTF8'\n    LC_COLLATE = 'en_US.UTF-8'\n    LC_CTYPE = 'en_US.UTF-8'\n    TEMPLATE template0;\n\n-- Use database\n\\c myapp_production\n\n-- List databases\n\\l\n\n-- Drop database\nDROP DATABASE old_database;\n```\n\n### Create Users\n\n```sql\n-- Create user\nCREATE USER appuser WITH PASSWORD 'strong_password';\n\n-- Grant privileges\nGRANT ALL PRIVILEGES ON DATABASE myapp_production TO appuser;\n\n-- Grant specific privileges\nGRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO appuser;\n\n-- Grant on future tables\nALTER DEFAULT PRIVILEGES IN SCHEMA public\nGRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO appuser;\n\n-- List users\n\\du\n\n-- Drop user\nDROP USER olduser;\n```\n\n### Create Tables\n\n```sql\n-- Create table\nCREATE TABLE users (\n    id SERIAL PRIMARY KEY,\n    username VARCHAR(50) UNIQUE NOT NULL,\n    email VARCHAR(100) UNIQUE NOT NULL,\n    password_hash VARCHAR(255) NOT NULL,\n    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\n\n-- Create index\nCREATE INDEX idx_users_email ON users(email);\nCREATE INDEX idx_users_username ON users(username);\n\n-- List tables\n\\dt\n\n-- Describe table\n\\d users\n\n-- Show create statement\n\\d+ users\n```\n\n## PostgreSQL-Specific Features\n\n### PostGIS (One-Click Enable)\n\nTurn a managed PostgreSQL database into a full geospatial database without any manual extension setup:\n\n1. Open the **Connectivity** tab on your PostgreSQL instance\n2. Use the **Enable PostGIS** card\n\nOne click sets up the PostGIS extension for geometry types, spatial indexes, and GIS functions. It's available on **PostgreSQL 15 through 18**. Your data and settings are preserved — single-node instances restart briefly, while high-availability clusters roll with near-zero downtime.\n\n```sql\n-- After enabling, PostGIS is ready to use\nSELECT PostGIS_Version();\n\nCREATE TABLE places (\n    id   SERIAL PRIMARY KEY,\n    name TEXT,\n    geom GEOGRAPHY(Point, 4326)\n);\n\nCREATE INDEX idx_places_geom ON places USING GIST (geom);\n```\n\n### JSONB Support\n\n```sql\n-- Create table with JSONB\nCREATE TABLE events (\n    id SERIAL PRIMARY KEY,\n    data JSONB NOT NULL,\n    created_at TIMESTAMP DEFAULT NOW()\n);\n\n-- Insert JSON data\nINSERT INTO events (data) VALUES \n    ('{\"user_id\": 123, \"action\": \"login\", \"ip\": \"192.168.1.1\"}');\n\n-- Query JSON\nSELECT * FROM events WHERE data->>'action' = 'login';\n\n-- JSON indexing\nCREATE INDEX idx_events_action ON events ((data->>'action'));\n\n-- GIN index for full JSON search\nCREATE INDEX idx_events_data ON events USING GIN (data);\n```\n\n### Full-Text Search\n\n```sql\n-- Add tsvector column\nALTER TABLE articles ADD COLUMN search_vector tsvector;\n\n-- Update search vector\nUPDATE articles \nSET search_vector = to_tsvector('english', title || ' ' || content);\n\n-- Create GIN index\nCREATE INDEX idx_articles_search ON articles USING GIN(search_vector);\n\n-- Search\nSELECT * FROM articles \nWHERE search_vector @@ to_tsquery('english', 'postgresql & database');\n\n-- Rank results\nSELECT *, ts_rank(search_vector, query) AS rank\nFROM articles, to_tsquery('english', 'postgresql') query\nWHERE search_vector @@ query\nORDER BY rank DESC;\n```\n\n### Array Types\n\n```sql\n-- Create table with array\nCREATE TABLE products (\n    id SERIAL PRIMARY KEY,\n    name VARCHAR(100),\n    tags TEXT[]\n);\n\n-- Insert with array\nINSERT INTO products (name, tags) \nVALUES ('Laptop', ARRAY['electronics', 'computers', 'portable']);\n\n-- Query array\nSELECT * FROM products WHERE 'electronics' = ANY(tags);\n\n-- Array contains\nSELECT * FROM products WHERE tags @> ARRAY['computers'];\n```\n\n### Window Functions\n\n```sql\n-- Rank employees by salary\nSELECT \n    name,\n    department,\n    salary,\n    RANK() OVER (PARTITION BY department ORDER BY salary DESC) as rank\nFROM employees;\n\n-- Running total\nSELECT \n    date,\n    amount,\n    SUM(amount) OVER (ORDER BY date) as running_total\nFROM sales;\n```\n\n### Common Table Expressions (CTEs)\n\n```sql\n-- Recursive CTE for hierarchy\nWITH RECURSIVE org_chart AS (\n    -- Base case\n    SELECT id, name, manager_id, 1 as level\n    FROM employees\n    WHERE manager_id IS NULL\n    \n    UNION ALL\n    \n    -- Recursive case\n    SELECT e.id, e.name, e.manager_id, oc.level + 1\n    FROM employees e\n    JOIN org_chart oc ON e.manager_id = oc.id\n)\nSELECT * FROM org_chart ORDER BY level, name;\n```\n\n## Performance Optimization\n\n### Indexes\n\n```sql\n-- B-tree index (default)\nCREATE INDEX idx_users_email ON users(email);\n\n-- Partial index\nCREATE INDEX idx_active_users ON users(email) WHERE active = true;\n\n-- Composite index\nCREATE INDEX idx_orders_user_date ON orders(user_id, created_at DESC);\n\n-- GIN index for full-text search\nCREATE INDEX idx_articles_content ON articles USING GIN(to_tsvector('english', content));\n\n-- List indexes\n\\di\n\n-- Index usage stats\nSELECT schemaname, tablename, indexname, idx_scan, idx_tup_read, idx_tup_fetch\nFROM pg_stat_user_indexes\nORDER BY idx_scan DESC;\n```\n\n### Query Optimization\n\n```sql\n-- Analyze query plan\nEXPLAIN ANALYZE SELECT * FROM users WHERE email = 'user@example.com';\n\n-- Analyze table\nANALYZE users;\n\n-- Vacuum table\nVACUUM ANALYZE users;\n\n-- Full vacuum (requires maintenance window)\nVACUUM FULL users;\n```\n\n### Connection Pooling\n\nInstances with the connection proxy enabled include built-in pgBouncer endpoints — you don't need to run your own pooler:\n\n- `<instance>-proxy-rw` — routes to the primary (writes)\n- `<instance>-proxy-ro` — routes to read replicas only (HA instances)\n- `<instance>-proxy-r` — routes to any instance (primary or replica)\n\nThe pools run in **transaction mode** with prepared-statement support enabled (`max_prepared_statements = 200`). Pooled server connections are recycled after at most one hour (`server_lifetime = 3600`, `server_idle_timeout = 120`).\n\n**Transaction pooling semantics:** consecutive transactions from one client connection may execute on different server connections, so session state — `LISTEN/NOTIFY`, advisory locks held across statements, `SET`, temporary tables — does not carry over between transactions. Connect to the direct instance endpoint instead of the proxy if you need session-level features.\n\n**Prepared statements and DDL migrations:** named prepared statements are cached on the pooler's *server* connections, which outlive your application's connections. DDL that changes a statement's result shape — an `ALTER TABLE ... ADD COLUMN` when a cached `SELECT *` targets that table, or an `ALTER COLUMN ... TYPE` for any cached statement returning that column — causes subsequent executions of the cached statement to fail with `cached plan must not change result type (SQLSTATE 0A000)`. Restarting your application does **not** clear this: the affected connections belong to the pooler, not your app, and recycle naturally within at most one hour. To avoid it:\n\n- Run result-shape-changing migrations using the simple query protocol, or with your driver's prepared-statement cache disabled.\n- Avoid `SELECT *` in hot query paths, so an added column cannot change a cached result shape.\n- If you hit `0A000` errors after a migration, contact support — we can recycle the pooler connections immediately rather than waiting for the automatic turnover. Prepared-statement support can also be disabled per instance on request.\n\n**Application-level pooling** (complements the proxy):\n```python\nfrom sqlalchemy import create_engine\nfrom sqlalchemy.pool import QueuePool\n\nengine = create_engine(\n    'postgresql://postgres:password@postgres-abc123.danubedata.ro:5432/production',\n    poolclass=QueuePool,\n    pool_size=10,\n    max_overflow=20,\n    pool_pre_ping=True\n)\n```\n\n## Monitoring & Metrics\n\n### Key Metrics\n\nMonitor in dashboard:\n- **CPU Usage**\n- **Memory Usage**\n- **Connections** (active/max)\n- **Queries per second**\n- **Cache hit ratio**\n- **Replication lag**\n\n### Database Statistics\n\n```sql\n-- Database size\nSELECT pg_size_pretty(pg_database_size('production'));\n\n-- Table sizes\nSELECT \n    schemaname,\n    tablename,\n    pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size\nFROM pg_tables\nORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC\nLIMIT 10;\n\n-- Connection stats\nSELECT count(*), state \nFROM pg_stat_activity \nGROUP BY state;\n\n-- Long-running queries\nSELECT pid, now() - pg_stat_activity.query_start AS duration, query\nFROM pg_stat_activity\nWHERE state = 'active'\nAND now() - pg_stat_activity.query_start > interval '5 minutes'\nORDER BY duration DESC;\n\n-- Cache hit ratio\nSELECT \n    sum(heap_blks_read) as heap_read,\n    sum(heap_blks_hit) as heap_hit,\n    sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read)) as ratio\nFROM pg_statio_user_tables;\n```\n\n## Backup & Recovery\n\n### Automated Backups\n\n- Daily at 2 AM UTC\n- 3-day retention for automated snapshots\n- Continuous backups with 30-day retention\n- Point-in-time recovery\n- No performance impact\n\n### Manual Snapshots\n\nCreate on-demand:\n1. Go to database page\n2. **Snapshots** tab\n3. **Create Snapshot**\n4. Name and create\n\n### Export Database\n\n```bash\n# Export entire database\npg_dump -h postgres-abc123.danubedata.ro \\\n        -U postgres \\\n        -d production \\\n        -F custom \\\n        -f backup.dump\n\n# Export with compression\npg_dump -h postgres-abc123.danubedata.ro \\\n        -U postgres \\\n        -d production \\\n        | gzip > backup.sql.gz\n\n# Export schema only\npg_dump -h postgres-abc123.danubedata.ro \\\n        -U postgres \\\n        -d production \\\n        --schema-only \\\n        -f schema.sql\n\n# Export specific tables\npg_dump -h postgres-abc123.danubedata.ro \\\n        -U postgres \\\n        -d production \\\n        -t users -t orders \\\n        -f tables.sql\n```\n\n### Import Database\n\n```bash\n# Import from custom format\npg_restore -h postgres-abc123.danubedata.ro \\\n           -U postgres \\\n           -d production \\\n           -F custom \\\n           backup.dump\n\n# Import from SQL\npsql -h postgres-abc123.danubedata.ro \\\n     -U postgres \\\n     -d production \\\n     < backup.sql\n\n# Import compressed\ngunzip < backup.sql.gz | psql -h postgres-abc123.danubedata.ro \\\n                               -U postgres \\\n                               -d production\n```\n\n## Read Replicas\n\n### Create Replica\n\n1. Go to database page\n2. Click **Add Replica**\n3. Select node and profile\n4. Click **Create**\n\n### Benefits\n\n- Scale read operations\n- Reporting without impacting primary\n- Load balancing via the reader endpoint\n- High availability\n\n### Using Replicas\n\n```python\n# Primary for writes\nprimary_engine = create_engine('postgresql://postgres:pass@primary-host:5432/db')\n\n# Replica for reads\nreplica_engine = create_engine('postgresql://postgres:pass@replica-host:5432/db')\n\n# Write to primary\nwith primary_engine.connect() as conn:\n    conn.execute(\"INSERT INTO users ...\")\n\n# Read from replica\nwith replica_engine.connect() as conn:\n    users = conn.execute(\"SELECT * FROM users\").fetchall()\n```\n\n## Security\n\n### SSL/TLS Connections\n\nAlways use SSL:\n\n```bash\n# psql with SSL\npsql \"postgresql://postgres:password@postgres-abc123.danubedata.ro:5432/production?sslmode=require\"\n```\n\n**Connection modes**:\n- `sslmode=require` - Require SSL\n- `sslmode=verify-ca` - Verify certificate\n- `sslmode=verify-full` - Full verification\n\n### User Permissions\n\n```sql\n-- Application user\nCREATE USER app_user WITH PASSWORD 'strong_pass';\nGRANT CONNECT ON DATABASE production TO app_user;\nGRANT USAGE ON SCHEMA public TO app_user;\nGRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_user;\n\n-- Read-only user\nCREATE USER readonly WITH PASSWORD 'strong_pass';\nGRANT CONNECT ON DATABASE production TO readonly;\nGRANT USAGE ON SCHEMA public TO readonly;\nGRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;\n```\n\n## Troubleshooting\n\n### Connection Issues\n\n```sql\n-- Check max connections\nSHOW max_connections;\n\n-- Check current connections\nSELECT count(*) FROM pg_stat_activity;\n\n-- Kill idle connections\nSELECT pg_terminate_backend(pid)\nFROM pg_stat_activity\nWHERE state = 'idle'\nAND now() - state_change > interval '10 minutes';\n```\n\n### Performance Issues\n\n```sql\n-- Find slow queries\nSELECT query, calls, total_time, mean_time\nFROM pg_stat_statements\nORDER BY mean_time DESC\nLIMIT 10;\n\n-- Table bloat\nSELECT schemaname, tablename, \n       pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size\nFROM pg_tables\nORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;\n\n-- Run VACUUM\nVACUUM ANALYZE;\n```\n\n## Best Practices\n\n### Schema Design\n1. Use appropriate data types\n2. Add foreign keys\n3. Create necessary indexes\n4. Normalize when appropriate\n5. Use constraints\n\n### Query Optimization\n1. Use EXPLAIN ANALYZE\n2. Add indexes for frequent queries\n3. Avoid SELECT *\n4. Use LIMIT for large results\n5. Optimize JOINs\n\n### Maintenance\n1. Regular VACUUM ANALYZE\n2. Monitor index usage\n3. Review slow queries\n4. Update statistics\n5. Monitor disk space\n\n## Next Steps\n\n- [SQL Studio](https://docs.danubedata.ro/databases-sql-studio)\n- [Database Replicas](https://docs.danubedata.ro/databases-replicas)\n- [Parameter Groups](https://docs.danubedata.ro/databases-parameters)\n- [MySQL Guide](https://docs.danubedata.ro/databases-mysql)\n- [Backups](https://docs.danubedata.ro/databases-backups)\n\nNeed help? Contact support through the dashboard.\n\n","prev":{"title":"MySQL","slug":"databases-mysql","url":"https://docs.danubedata.ro/databases-mysql","markdown_url":"https://docs.danubedata.ro/databases-mysql.md","json_url":"https://docs.danubedata.ro/databases-mysql.json"},"next":{"title":"SQL Studio","slug":"databases-sql-studio","url":"https://docs.danubedata.ro/databases-sql-studio","markdown_url":"https://docs.danubedata.ro/databases-sql-studio.md","json_url":"https://docs.danubedata.ro/databases-sql-studio.json"},"index_url":"https://docs.danubedata.ro/index.json"}