[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6ENXRJY0Q74AX7D6VG2FR99":3,"skill-same:01M6ENXRJY0Q74AX7D6VG2FR99":64},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":30,"linkOnly":31,"markdownContent":32,"markdownBody":33,"frontMatter":34,"refName":35,"commitSha":36,"skillDirectory":37,"forksCount":38,"repositoryHtmlUrl":39,"repositoryHomepageUrl":40,"ownerHtmlUrl":41,"ownerBlogUrl":6,"ownerDescription":42,"updatedAt":43,"install":44,"resources":57},"01M6ENXRJY0Q74AX7D6VG2FR99","postgres-performance",null,"High-performance PostgreSQL patterns. Use when optimizing queries, designing for scale, or debugging performance issues.","ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",67,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fcjharmath\u002Fpostgres-performance\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29],"performance","postgres","postgres performance","debugging","issues","postgresql","use","queries","patterns","high-performance","scale","2026-11-10 10:12:30",false,"---\nname: postgres-performance\ndescription: High-performance PostgreSQL patterns. Use when optimizing queries, designing for scale, or debugging performance issues.\n---\n\n# PostgreSQL Performance Engineering\n\n## Problem Statement\n\nPerformance problems compound. A query that takes 50ms at 1K rows takes 5s at 100K rows. This skill covers patterns for building performant database interactions from the start and fixing performance issues.\n\n---\n\n## Pattern: Query Optimization Workflow\n\n### Step 1: Identify Slow Queries\n\n```sql\n-- Enable pg_stat_statements (if not already)\nCREATE EXTENSION IF NOT EXISTS pg_stat_statements;\n\n-- Find slowest queries\nSELECT \n    query,\n    calls,\n    round(mean_exec_time::numeric, 2) as avg_ms,\n    round(total_exec_time::numeric, 2) as total_ms,\n    rows\nFROM pg_stat_statements\nWHERE calls > 10\nORDER BY mean_exec_time DESC\nLIMIT 20;\n```\n\n### Step 2: Analyze Query Plan\n\n```sql\nEXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)\nSELECT * FROM assessments \nWHERE user_id = 'abc-123' \nORDER BY created_at DESC \nLIMIT 10;\n```\n\n**What to look for:**\n\n| Warning Sign | Problem | Solution |\n|--------------|---------|----------|\n| Seq Scan on large table | Missing index | Add index |\n| High `loops` count | N+1 in join | Rewrite query, add index |\n| Sort with high cost | No index for ORDER BY | Covering index |\n| Hash\u002FMerge Join with high rows | Large intermediate result | Filter earlier, better indexes |\n| Buffers: shared read high | Data not cached | More RAM, or query less data |\n\n### Step 3: Fix and Verify\n\n```sql\n-- Add index\nCREATE INDEX CONCURRENTLY ix_assessments_user_created \nON assessments (user_id, created_at DESC);\n\n-- Verify improvement\nEXPLAIN (ANALYZE, BUFFERS)\nSELECT * FROM assessments \nWHERE user_id = 'abc-123' \nORDER BY created_at DESC \nLIMIT 10;\n-- Should now show \"Index Scan\" instead of \"Seq Scan\"\n```\n\n---\n\n## Pattern: Covering Indexes (Index-Only Scans)\n\n**Problem:** Query reads index, then fetches rows from table (heap fetch).\n\n```sql\n-- Query\nSELECT id, title, status FROM assessments WHERE user_id = ?;\n\n-- Regular index: requires heap fetch\nCREATE INDEX ix_assessments_user ON assessments (user_id);\n-- Plan: Index Scan + Heap Fetches\n\n-- ✅ Covering index: all columns in index\nCREATE INDEX ix_assessments_user_covering \nON assessments (user_id) \nINCLUDE (id, title, status);\n-- Plan: Index Only Scan (no heap fetch, much faster)\n```\n\n**When to use:**\n- Frequently run queries\n- Queries selecting few columns\n- Tables with many columns (heap fetch is expensive)\n\n---\n\n## Pattern: Pagination at Scale\n\n```sql\n-- ❌ SLOW: OFFSET-based pagination\nSELECT * FROM events ORDER BY created_at DESC LIMIT 20 OFFSET 10000;\n-- Must scan and discard 10,000 rows!\n\n-- ✅ FAST: Cursor-based (keyset) pagination\nSELECT * FROM events \nWHERE created_at \u003C '2024-01-15T10:30:00Z'  -- Last seen timestamp\nORDER BY created_at DESC \nLIMIT 20;\n-- Jumps directly to the right place via index\n\n-- For compound cursor (when duplicates possible):\nSELECT * FROM events \nWHERE (created_at, id) \u003C ('2024-01-15T10:30:00Z', 'last-id')\nORDER BY created_at DESC, id DESC \nLIMIT 20;\n```\n\n**In SQLAlchemy:**\n```python\n# Cursor-based pagination\nasync def get_events_page(\n    session: AsyncSession,\n    cursor_time: datetime | None,\n    cursor_id: UUID | None,\n    limit: int = 20,\n) -> list[Event]:\n    query = select(Event).order_by(Event.created_at.desc(), Event.id.desc())\n    \n    if cursor_time and cursor_id:\n        query = query.where(\n            tuple_(Event.created_at, Event.id) \u003C (cursor_time, cursor_id)\n        )\n    \n    result = await session.execute(query.limit(limit))\n    return result.scalars().all()\n```\n\n---\n\n## Pattern: Batch Processing\n\n```sql\n-- ❌ SLOW: One huge query\u002Fupdate\nUPDATE events SET processed = true WHERE processed = false;\n-- Locks millions of rows, times out\n\n-- ✅ FAST: Batch processing\nDO $$\nDECLARE\n    batch_size INT := 10000;\n    rows_affected INT;\nBEGIN\n    LOOP\n        UPDATE events \n        SET processed = true \n        WHERE id IN (\n            SELECT id FROM events \n            WHERE processed = false \n            LIMIT batch_size\n            FOR UPDATE SKIP LOCKED\n        );\n        \n        GET DIAGNOSTICS rows_affected = ROW_COUNT;\n        \n        IF rows_affected = 0 THEN\n            EXIT;\n        END IF;\n        \n        COMMIT;\n        PERFORM pg_sleep(0.1);  -- Brief pause to let other queries through\n    END LOOP;\nEND $$;\n```\n\n**In Python:**\n```python\nasync def process_in_batches(session: AsyncSession, batch_size: int = 10000):\n    while True:\n        result = await session.execute(\n            text(\"\"\"\n                UPDATE events SET processed = true\n                WHERE id IN (\n                    SELECT id FROM events \n                    WHERE processed = false \n                    LIMIT :batch_size\n                    FOR UPDATE SKIP LOCKED\n                )\n                RETURNING id\n            \"\"\"),\n            {\"batch_size\": batch_size}\n        )\n        \n        updated = result.fetchall()\n        await session.commit()\n        \n        if len(updated) == 0:\n            break\n        \n        await asyncio.sleep(0.1)\n```\n\n---\n\n## Pattern: Efficient Aggregations\n\n```sql\n-- ❌ SLOW: Count with complex WHERE\nSELECT COUNT(*) FROM events WHERE user_id = ? AND status = 'active';\n-- Scans all matching rows\n\n-- ✅ FAST: Approximate count (for large tables)\nSELECT reltuples::bigint AS estimate\nFROM pg_class\nWHERE relname = 'events';\n\n-- ✅ FAST: Maintain counter cache\n-- Add column: assessments.answer_count\n-- Update on INSERT\u002FDELETE to answers\n\n-- ✅ FAST: Materialized view for complex aggregations\nCREATE MATERIALIZED VIEW user_stats AS\nSELECT \n    user_id,\n    COUNT(*) as total_assessments,\n    AVG(rating) as avg_rating\nFROM assessments\nGROUP BY user_id;\n\n-- Refresh periodically\nREFRESH MATERIALIZED VIEW CONCURRENTLY user_stats;\n```\n\n---\n\n## Pattern: Connection Pool Tuning\n\n```python\n# Async SQLAlchemy with proper pool settings\nfrom sqlalchemy.ext.asyncio import create_async_engine\nfrom sqlalchemy.pool import NullPool, AsyncAdaptedQueuePool\n\n# For serverless\u002FLambda (no persistent connections)\nengine = create_async_engine(\n    DATABASE_URL,\n    poolclass=NullPool,  # New connection per request\n)\n\n# For long-running servers\nengine = create_async_engine(\n    DATABASE_URL,\n    poolclass=AsyncAdaptedQueuePool,\n    pool_size=10,           # Base connections\n    max_overflow=20,        # Extra connections under load\n    pool_timeout=30,        # Wait for connection\n    pool_recycle=1800,      # Recycle connections every 30 min\n    pool_pre_ping=True,     # Test connection before use\n)\n```\n\n**PostgreSQL side:**\n```sql\n-- Check max connections\nSHOW max_connections;  -- Default 100\n\n-- See current connections\nSELECT count(*) FROM pg_stat_activity;\n\n-- Connection per application\nSELECT application_name, count(*) \nFROM pg_stat_activity \nGROUP BY application_name;\n```\n\n---\n\n## Pattern: Read Replicas\n\n```python\n# Route reads to replica, writes to primary\nfrom sqlalchemy import create_engine\nfrom sqlalchemy.orm import Session\n\nprimary_engine = create_async_engine(PRIMARY_URL)\nreplica_engine = create_async_engine(REPLICA_URL)\n\nclass RoutingSession(Session):\n    def get_bind(self, mapper=None, clause=None):\n        if self._flushing or self.is_modified():\n            return primary_engine.sync_engine\n        return replica_engine.sync_engine\n```\n\n---\n\n## Pattern: Denormalization for Read Performance\n\n```sql\n-- ❌ SLOW: Joining 4 tables for common query\nSELECT \n    a.id, a.title, u.name as user_name, \n    COUNT(q.id) as question_count,\n    AVG(ans.value) as avg_score\nFROM assessments a\nJOIN users u ON a.user_id = u.id\nJOIN questions q ON q.assessment_id = a.id\nLEFT JOIN answers ans ON ans.question_id = q.id\nGROUP BY a.id, a.title, u.name;\n\n-- ✅ FAST: Denormalized columns\nALTER TABLE assessments ADD COLUMN user_name VARCHAR(100);\nALTER TABLE assessments ADD COLUMN question_count INT DEFAULT 0;\nALTER TABLE assessments ADD COLUMN avg_score NUMERIC(3,2);\n\n-- Update via triggers or application code\n-- Query becomes simple:\nSELECT id, title, user_name, question_count, avg_score FROM assessments;\n```\n\n**Tradeoffs:**\n- ✅ Much faster reads\n- ❌ More complex writes (must update denormalized data)\n- ❌ Potential for stale data\n\n---\n\n## Pattern: Partitioning Large Tables\n\n```sql\n-- Partition events by month\nCREATE TABLE events (\n    id UUID PRIMARY KEY,\n    user_id UUID NOT NULL,\n    event_type VARCHAR(50),\n    created_at TIMESTAMPTZ NOT NULL\n) PARTITION BY RANGE (created_at);\n\n-- Create partitions\nCREATE TABLE events_2024_01 PARTITION OF events\n    FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');\nCREATE TABLE events_2024_02 PARTITION OF events\n    FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');\n\n-- Query specific partition (fast)\nSELECT * FROM events WHERE created_at >= '2024-01-15' AND created_at \u003C '2024-02-01';\n\n-- Drop old data instantly\nDROP TABLE events_2023_01;  -- Much faster than DELETE\n```\n\n---\n\n## Pattern: Caching Strategy\n\n```python\n# Cache frequently-read, rarely-changed data\nimport redis.asyncio as redis\nimport json\n\ncache = redis.from_url(REDIS_URL)\n\nasync def get_user_stats(user_id: UUID) -> UserStats:\n    cache_key = f\"user_stats:{user_id}\"\n    \n    # Try cache first\n    cached = await cache.get(cache_key)\n    if cached:\n        return UserStats.model_validate_json(cached)\n    \n    # Query database\n    async with get_session() as session:\n        stats = await calculate_user_stats(session, user_id)\n    \n    # Cache for 5 minutes\n    await cache.setex(cache_key, 300, stats.model_dump_json())\n    \n    return stats\n\n# Invalidate on write\nasync def update_user_assessment(user_id: UUID, ...):\n    # ... update database ...\n    await cache.delete(f\"user_stats:{user_id}\")\n```\n\n---\n\n## Performance Monitoring Queries\n\n```sql\n-- Table bloat (needs VACUUM)\nSELECT \n    schemaname, relname,\n    n_dead_tup as dead_tuples,\n    n_live_tup as live_tuples,\n    round(n_dead_tup::numeric \u002F NULLIF(n_live_tup, 0) * 100, 2) as dead_pct\nFROM pg_stat_user_tables\nWHERE n_dead_tup > 10000\nORDER BY n_dead_tup DESC;\n\n-- Index bloat\nSELECT\n    indexrelname as index,\n    pg_size_pretty(pg_relation_size(indexrelid)) as size,\n    idx_scan as scans\nFROM pg_stat_user_indexes\nWHERE idx_scan = 0  -- Unused indexes\nORDER BY pg_relation_size(indexrelid) DESC;\n\n-- Cache hit ratio (should be > 99%)\nSELECT \n    sum(blks_hit) * 100.0 \u002F sum(blks_hit + blks_read) as cache_hit_ratio\nFROM pg_stat_database;\n\n-- Long-running queries\nSELECT \n    pid,\n    now() - query_start as duration,\n    query\nFROM pg_stat_activity\nWHERE state = 'active' \n    AND query NOT LIKE '%pg_stat%'\n    AND now() - query_start > interval '30 seconds';\n```\n\n---\n\n## Performance Checklist\n\nBefore deploying:\n\n- [ ] Slow queries identified and optimized\n- [ ] Indexes match query patterns\n- [ ] Covering indexes for frequent queries\n- [ ] Pagination uses cursor-based (not OFFSET)\n- [ ] Large tables partitioned if > 10M rows\n- [ ] Connection pool sized appropriately\n- [ ] Cache layer for hot data\n- [ ] Monitoring in place for slow queries\n- [ ] VACUUM and ANALYZE scheduled\n","# PostgreSQL Performance Engineering\n\n## Problem Statement\n\nPerformance problems compound. A query that takes 50ms at 1K rows takes 5s at 100K rows. This skill covers patterns for building performant database interactions from the start and fixing performance issues.\n\n---\n\n## Pattern: Query Optimization Workflow\n\n### Step 1: Identify Slow Queries\n\n```sql\n-- Enable pg_stat_statements (if not already)\nCREATE EXTENSION IF NOT EXISTS pg_stat_statements;\n\n-- Find slowest queries\nSELECT \n    query,\n    calls,\n    round(mean_exec_time::numeric, 2) as avg_ms,\n    round(total_exec_time::numeric, 2) as total_ms,\n    rows\nFROM pg_stat_statements\nWHERE calls > 10\nORDER BY mean_exec_time DESC\nLIMIT 20;\n```\n\n### Step 2: Analyze Query Plan\n\n```sql\nEXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)\nSELECT * FROM assessments \nWHERE user_id = 'abc-123' \nORDER BY created_at DESC \nLIMIT 10;\n```\n\n**What to look for:**\n\n| Warning Sign | Problem | Solution |\n|--------------|---------|----------|\n| Seq Scan on large table | Missing index | Add index |\n| High `loops` count | N+1 in join | Rewrite query, add index |\n| Sort with high cost | No index for ORDER BY | Covering index |\n| Hash\u002FMerge Join with high rows | Large intermediate result | Filter earlier, better indexes |\n| Buffers: shared read high | Data not cached | More RAM, or query less data |\n\n### Step 3: Fix and Verify\n\n```sql\n-- Add index\nCREATE INDEX CONCURRENTLY ix_assessments_user_created \nON assessments (user_id, created_at DESC);\n\n-- Verify improvement\nEXPLAIN (ANALYZE, BUFFERS)\nSELECT * FROM assessments \nWHERE user_id = 'abc-123' \nORDER BY created_at DESC \nLIMIT 10;\n-- Should now show \"Index Scan\" instead of \"Seq Scan\"\n```\n\n---\n\n## Pattern: Covering Indexes (Index-Only Scans)\n\n**Problem:** Query reads index, then fetches rows from table (heap fetch).\n\n```sql\n-- Query\nSELECT id, title, status FROM assessments WHERE user_id = ?;\n\n-- Regular index: requires heap fetch\nCREATE INDEX ix_assessments_user ON assessments (user_id);\n-- Plan: Index Scan + Heap Fetches\n\n-- ✅ Covering index: all columns in index\nCREATE INDEX ix_assessments_user_covering \nON assessments (user_id) \nINCLUDE (id, title, status);\n-- Plan: Index Only Scan (no heap fetch, much faster)\n```\n\n**When to use:**\n- Frequently run queries\n- Queries selecting few columns\n- Tables with many columns (heap fetch is expensive)\n\n---\n\n## Pattern: Pagination at Scale\n\n```sql\n-- ❌ SLOW: OFFSET-based pagination\nSELECT * FROM events ORDER BY created_at DESC LIMIT 20 OFFSET 10000;\n-- Must scan and discard 10,000 rows!\n\n-- ✅ FAST: Cursor-based (keyset) pagination\nSELECT * FROM events \nWHERE created_at \u003C '2024-01-15T10:30:00Z'  -- Last seen timestamp\nORDER BY created_at DESC \nLIMIT 20;\n-- Jumps directly to the right place via index\n\n-- For compound cursor (when duplicates possible):\nSELECT * FROM events \nWHERE (created_at, id) \u003C ('2024-01-15T10:30:00Z', 'last-id')\nORDER BY created_at DESC, id DESC \nLIMIT 20;\n```\n\n**In SQLAlchemy:**\n```python\n# Cursor-based pagination\nasync def get_events_page(\n    session: AsyncSession,\n    cursor_time: datetime | None,\n    cursor_id: UUID | None,\n    limit: int = 20,\n) -> list[Event]:\n    query = select(Event).order_by(Event.created_at.desc(), Event.id.desc())\n    \n    if cursor_time and cursor_id:\n        query = query.where(\n            tuple_(Event.created_at, Event.id) \u003C (cursor_time, cursor_id)\n        )\n    \n    result = await session.execute(query.limit(limit))\n    return result.scalars().all()\n```\n\n---\n\n## Pattern: Batch Processing\n\n```sql\n-- ❌ SLOW: One huge query\u002Fupdate\nUPDATE events SET processed = true WHERE processed = false;\n-- Locks millions of rows, times out\n\n-- ✅ FAST: Batch processing\nDO $$\nDECLARE\n    batch_size INT := 10000;\n    rows_affected INT;\nBEGIN\n    LOOP\n        UPDATE events \n        SET processed = true \n        WHERE id IN (\n            SELECT id FROM events \n            WHERE processed = false \n            LIMIT batch_size\n            FOR UPDATE SKIP LOCKED\n        );\n        \n        GET DIAGNOSTICS rows_affected = ROW_COUNT;\n        \n        IF rows_affected = 0 THEN\n            EXIT;\n        END IF;\n        \n        COMMIT;\n        PERFORM pg_sleep(0.1);  -- Brief pause to let other queries through\n    END LOOP;\nEND $$;\n```\n\n**In Python:**\n```python\nasync def process_in_batches(session: AsyncSession, batch_size: int = 10000):\n    while True:\n        result = await session.execute(\n            text(\"\"\"\n                UPDATE events SET processed = true\n                WHERE id IN (\n                    SELECT id FROM events \n                    WHERE processed = false \n                    LIMIT :batch_size\n                    FOR UPDATE SKIP LOCKED\n                )\n                RETURNING id\n            \"\"\"),\n            {\"batch_size\": batch_size}\n        )\n        \n        updated = result.fetchall()\n        await session.commit()\n        \n        if len(updated) == 0:\n            break\n        \n        await asyncio.sleep(0.1)\n```\n\n---\n\n## Pattern: Efficient Aggregations\n\n```sql\n-- ❌ SLOW: Count with complex WHERE\nSELECT COUNT(*) FROM events WHERE user_id = ? AND status = 'active';\n-- Scans all matching rows\n\n-- ✅ FAST: Approximate count (for large tables)\nSELECT reltuples::bigint AS estimate\nFROM pg_class\nWHERE relname = 'events';\n\n-- ✅ FAST: Maintain counter cache\n-- Add column: assessments.answer_count\n-- Update on INSERT\u002FDELETE to answers\n\n-- ✅ FAST: Materialized view for complex aggregations\nCREATE MATERIALIZED VIEW user_stats AS\nSELECT \n    user_id,\n    COUNT(*) as total_assessments,\n    AVG(rating) as avg_rating\nFROM assessments\nGROUP BY user_id;\n\n-- Refresh periodically\nREFRESH MATERIALIZED VIEW CONCURRENTLY user_stats;\n```\n\n---\n\n## Pattern: Connection Pool Tuning\n\n```python\n# Async SQLAlchemy with proper pool settings\nfrom sqlalchemy.ext.asyncio import create_async_engine\nfrom sqlalchemy.pool import NullPool, AsyncAdaptedQueuePool\n\n# For serverless\u002FLambda (no persistent connections)\nengine = create_async_engine(\n    DATABASE_URL,\n    poolclass=NullPool,  # New connection per request\n)\n\n# For long-running servers\nengine = create_async_engine(\n    DATABASE_URL,\n    poolclass=AsyncAdaptedQueuePool,\n    pool_size=10,           # Base connections\n    max_overflow=20,        # Extra connections under load\n    pool_timeout=30,        # Wait for connection\n    pool_recycle=1800,      # Recycle connections every 30 min\n    pool_pre_ping=True,     # Test connection before use\n)\n```\n\n**PostgreSQL side:**\n```sql\n-- Check max connections\nSHOW max_connections;  -- Default 100\n\n-- See current connections\nSELECT count(*) FROM pg_stat_activity;\n\n-- Connection per application\nSELECT application_name, count(*) \nFROM pg_stat_activity \nGROUP BY application_name;\n```\n\n---\n\n## Pattern: Read Replicas\n\n```python\n# Route reads to replica, writes to primary\nfrom sqlalchemy import create_engine\nfrom sqlalchemy.orm import Session\n\nprimary_engine = create_async_engine(PRIMARY_URL)\nreplica_engine = create_async_engine(REPLICA_URL)\n\nclass RoutingSession(Session):\n    def get_bind(self, mapper=None, clause=None):\n        if self._flushing or self.is_modified():\n            return primary_engine.sync_engine\n        return replica_engine.sync_engine\n```\n\n---\n\n## Pattern: Denormalization for Read Performance\n\n```sql\n-- ❌ SLOW: Joining 4 tables for common query\nSELECT \n    a.id, a.title, u.name as user_name, \n    COUNT(q.id) as question_count,\n    AVG(ans.value) as avg_score\nFROM assessments a\nJOIN users u ON a.user_id = u.id\nJOIN questions q ON q.assessment_id = a.id\nLEFT JOIN answers ans ON ans.question_id = q.id\nGROUP BY a.id, a.title, u.name;\n\n-- ✅ FAST: Denormalized columns\nALTER TABLE assessments ADD COLUMN user_name VARCHAR(100);\nALTER TABLE assessments ADD COLUMN question_count INT DEFAULT 0;\nALTER TABLE assessments ADD COLUMN avg_score NUMERIC(3,2);\n\n-- Update via triggers or application code\n-- Query becomes simple:\nSELECT id, title, user_name, question_count, avg_score FROM assessments;\n```\n\n**Tradeoffs:**\n- ✅ Much faster reads\n- ❌ More complex writes (must update denormalized data)\n- ❌ Potential for stale data\n\n---\n\n## Pattern: Partitioning Large Tables\n\n```sql\n-- Partition events by month\nCREATE TABLE events (\n    id UUID PRIMARY KEY,\n    user_id UUID NOT NULL,\n    event_type VARCHAR(50),\n    created_at TIMESTAMPTZ NOT NULL\n) PARTITION BY RANGE (created_at);\n\n-- Create partitions\nCREATE TABLE events_2024_01 PARTITION OF events\n    FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');\nCREATE TABLE events_2024_02 PARTITION OF events\n    FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');\n\n-- Query specific partition (fast)\nSELECT * FROM events WHERE created_at >= '2024-01-15' AND created_at \u003C '2024-02-01';\n\n-- Drop old data instantly\nDROP TABLE events_2023_01;  -- Much faster than DELETE\n```\n\n---\n\n## Pattern: Caching Strategy\n\n```python\n# Cache frequently-read, rarely-changed data\nimport redis.asyncio as redis\nimport json\n\ncache = redis.from_url(REDIS_URL)\n\nasync def get_user_stats(user_id: UUID) -> UserStats:\n    cache_key = f\"user_stats:{user_id}\"\n    \n    # Try cache first\n    cached = await cache.get(cache_key)\n    if cached:\n        return UserStats.model_validate_json(cached)\n    \n    # Query database\n    async with get_session() as session:\n        stats = await calculate_user_stats(session, user_id)\n    \n    # Cache for 5 minutes\n    await cache.setex(cache_key, 300, stats.model_dump_json())\n    \n    return stats\n\n# Invalidate on write\nasync def update_user_assessment(user_id: UUID, ...):\n    # ... update database ...\n    await cache.delete(f\"user_stats:{user_id}\")\n```\n\n---\n\n## Performance Monitoring Queries\n\n```sql\n-- Table bloat (needs VACUUM)\nSELECT \n    schemaname, relname,\n    n_dead_tup as dead_tuples,\n    n_live_tup as live_tuples,\n    round(n_dead_tup::numeric \u002F NULLIF(n_live_tup, 0) * 100, 2) as dead_pct\nFROM pg_stat_user_tables\nWHERE n_dead_tup > 10000\nORDER BY n_dead_tup DESC;\n\n-- Index bloat\nSELECT\n    indexrelname as index,\n    pg_size_pretty(pg_relation_size(indexrelid)) as size,\n    idx_scan as scans\nFROM pg_stat_user_indexes\nWHERE idx_scan = 0  -- Unused indexes\nORDER BY pg_relation_size(indexrelid) DESC;\n\n-- Cache hit ratio (should be > 99%)\nSELECT \n    sum(blks_hit) * 100.0 \u002F sum(blks_hit + blks_read) as cache_hit_ratio\nFROM pg_stat_database;\n\n-- Long-running queries\nSELECT \n    pid,\n    now() - query_start as duration,\n    query\nFROM pg_stat_activity\nWHERE state = 'active' \n    AND query NOT LIKE '%pg_stat%'\n    AND now() - query_start > interval '30 seconds';\n```\n\n---\n\n## Performance Checklist\n\nBefore deploying:\n\n- [ ] Slow queries identified and optimized\n- [ ] Indexes match query patterns\n- [ ] Covering indexes for frequent queries\n- [ ] Pagination uses cursor-based (not OFFSET)\n- [ ] Large tables partitioned if > 10M rows\n- [ ] Connection pool sized appropriately\n- [ ] Cache layer for hot data\n- [ ] Monitoring in place for slow queries\n- [ ] VACUUM and ANALYZE scheduled\n",{"name":5,"description":7},"main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002Faiskillstore\u002Fmarketplace\u002Fcjharmath\u002Fpostgres-performance",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":8,"skillName":5,"commands":45,"prompt":55,"zipAvailable":56},[46,49,52],{"tool":47,"command":48},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill postgres-performance",{"tool":50,"command":51},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill postgres-performance",{"tool":53,"command":54},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill postgres-performance","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fcjharmath\u002Fpostgres-performance\u002FSKILL.md",true,[58],{"path":59,"resourceType":60,"resourceTypeDesc":61,"sizeBytes":62,"sourceHtmlUrl":63},"skill-report.json","REFERENCE","参考文档",10421,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fcjharmath\u002Fpostgres-performance\u002Fskill-report.json",[65,79,89,98,104,111],{"skillNo":66,"name":67,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":68,"categories":69,"keywords":75,"publishedAt":30},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[70],{"categoryNo":71,"categoryName":72,"taxonomyType":73,"taxonomyTypeDesc":74},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[76,77,78],"model","model hierarchy","model hierarchy skill",{"skillNo":80,"name":81,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":82,"categories":83,"keywords":85,"publishedAt":30},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[84],{"categoryNo":71,"categoryName":72,"taxonomyType":73,"taxonomyTypeDesc":74},[86,87,88],"MCP","zoom","zoom mcp",{"skillNo":90,"name":91,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":92,"categories":93,"keywords":94,"publishedAt":30},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[95,96,97,87],"sdk","zoom apps sdk","apps",{"skillNo":99,"name":100,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":101,"categories":102,"keywords":103,"publishedAt":30},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[100],{"skillNo":105,"name":106,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":107,"categories":108,"keywords":110,"publishedAt":30},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[109],{"categoryNo":71,"categoryName":72,"taxonomyType":73,"taxonomyTypeDesc":74},[106],{"skillNo":112,"name":113,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":114,"categories":115,"keywords":116,"publishedAt":30},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[113]]