Skip to main content

database-patterns

DB schema design and query tuning: normalization, indexing, N+1, transactions, EXPLAIN. Triggers: schema, index, slow query, N+1, PostgreSQL, MySQL, EXPLAIN, deadlock, query plan.

معلومات المصدر

المستودع
softspark/ai-toolkit
آخر نشاط في المصدر
٦ مايو ٢٠٢٦ في ٠٩:٤٨
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
١٧٧
التفرعات
٢١

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
database-patterns
description
DB schema design and query tuning: normalization, indexing, N+1, transactions, EXPLAIN. Triggers: schema, index, slow query, N+1, PostgreSQL, MySQL, EXPLAIN, deadlock, query plan.
effort
medium
user-invocable
false
allowed-tools
Read
# Database Patterns Skill ## ORM Selection | Scenario | ORM | |----------|-----| | Node.js, type-safe | Prisma | | Node.js, SQL-first | Drizzle | | Python, async | SQLAlchemy 2.0 | | Python, simple | SQLModel | | PHP | Doctrine, Eloquent | --- ## Schema Design ### Naming Conventions ```sql -- Tables: plural, snake_case CREATE TABLE user_profiles (...); -- Columns: snake_case user_id, created_at, is_active -- Indexes: idx_{table}_{columns} CREATE INDEX idx_users_email ON users(email); -- Foreign keys: fk_{table}_{ref_table} CONSTRAINT fk_orders_users FOREIGN KEY (user_id) REFERENCES users(id) ``` ### Common Patterns #### Soft Delete ```sql ALTER TABLE users ADD COLUMN deleted_at TIMESTAMP NULL; -- Query active records SELECT * FROM users WHERE deleted_at IS NULL; ``` #### Audit Columns ```sql created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, created_by UUID REFERENCES users(id), updated_by UUID REFERENCES users(id) ``` #### UUID vs Serial | Use Case | Type | |----------|------| | Internal only | SERIAL/BIGSERIAL | | External/distributed | UUID | | Human readable | SERIAL with prefix | --- ## Index Strategies ### When to Index - Foreign keys (always) - Columns in WHERE clauses - Columns in ORDER BY - Columns in JOIN conditions ### Index Types | Type | Use Case | |------|----------| | B-tree | Equality, range (default) | | Hash | Equality only | | GIN | Arrays, JSONB, full-text | | GiST | Geometric, full-text | | BRIN | Large sequential data | ### Composite Index Order ```sql -- Good: matches query pattern CREATE INDEX idx_orders_user_date ON orders(user_id, created_at); SELECT * FROM orders WHERE user_id = 1 AND created_at > '2024-01-01'; -- Index used for: -- WHERE user_id = 1 -- WHERE user_id = 1 AND created_at > ... -- Index NOT used for: -- WHERE created_at > '2024-01-01' (missing leading column) ``` --- ## Query Optimization ### Explain Analyze ```sql EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@example.com'; ``` ### Common Issues | Issue | Solution | |-------|----------| | Seq Scan on large table | Add index | | High row estimate | Update statistics | | Nested Loop on large sets | Consider hash join | | Sort in memory | Increase work_mem | ### N+1 Prevention ```python # Bad: N+1 for user in users: print(user.orders) # Query per user # Good: Eager loading users = User.query.options(joinedload(User.orders)).all() ``` --- ## Migration Best Practices ### Safe Migrations ```sql -- Add column (safe) ALTER TABLE users ADD COLUMN phone VARCHAR(20); -- Add NOT NULL column (safe pattern) ALTER TABLE users ADD COLUMN phone VARCHAR(20); UPDATE users SET phone = '' WHERE phone IS NULL; ALTER TABLE users ALTER COLUMN phone SET NOT NULL; -- Rename column (use application-level) -- 1. Add new column -- 2. Copy data -- 3. Update application -- 4. Remove old column ``` ### Migration Checklist - [ ] Tested on production-like data - [ ] Rollback script ready - [ ] No long locks on large tables - [ ] Indexes created concurrently - [ ] Application handles both states --- ## Connection Pooling ### PgBouncer Settings ```ini [pgbouncer] pool_mode = transaction max_client_conn = 1000 default_pool_size = 20 ``` ### Application Settings | Framework | Pool Size Formula | |-----------|------------------| | General | (cores * 2) + disk spindles | | Read-heavy | cores * 4 | | Write-heavy | cores * 2 | --- ## Vector Database (Qdrant) Patterns ### Client Setup ```python from qdrant_client import QdrantClient from qdrant_client.models import Distance, VectorParams, PointStruct # Sync client client = QdrantClient(host="localhost", port=6333) # Async client from qdrant_client import AsyncQdrantClient async_client = AsyncQdrantClient(host="localhost", port=6333) ``` ### Collection Management ```python # Create collection (single vector) client.create_collection( collection_name="documents", vectors_config=VectorParams(size=384, distance=Distance.COSINE) ) # Create collection (multi-vector) from qdrant_client.models import VectorParams client.create_collection( collection_name="multimodal", vectors_config={ "text": VectorParams(size=384, distance=Distance.COSINE), "image": VectorParams(size=512, distance=Distance.EUCLID), } ) ``` ### Upserting Vectors ```python # Single upsert client.upsert( collection_name="documents", points=[ PointStruct( id=1, vector=[0.1, 0.2, 0.3, ...], # 384-dim vector payload={"title": "Doc 1", "category": "tech"} ) ] ) # Batch upsert points = [ PointStruct(id=i, vector=vectors[i], payload=payloads[i]) for i in range(len(vectors)) ] client.upsert(collection_name="documents", points=points, batch_size=100) ``` ### Searching Vectors ```python from qdrant_client.models import Filter, FieldCondition, MatchValue # Basic search results = client.search( collection_name="documents", query_vector=[0.1, 0.2, ...], limit=10 ) # Search with filter results = client.search( collection_name="documents", query_vector=[0.1, 0.2, ...], query_filter=Filter( must=[ FieldCondition(key="category", match=MatchValue(value="tech")) ] ), limit=10, with_payload=True, score_threshold=0.7 ) # Search with range filter from qdrant_client.models import Range results = client.search( collection_name="documents", query_vector=query_vector, query_filter=Filter( must=[ FieldCondition(key="price", range=Range(gte=10, lte=100)) ] ), limit=10 ) ``` ### Payload Indexing ```python # Create payload index for faster filtering client.create_payload_index( collection_name="documents", field_name="category", field_schema="keyword" # or "integer", "float", "bool" ) ``` ### Best Practices | Aspect | Recommendation | |--------|----------------| | Batch Size | 100-1000 points per upsert | | Vector Dim | Match your embedding model (384, 768, 1536) | | Filters | Index frequently filtered fields | | Distance | COSINE for normalized, EUCLID for raw | | Sharding | Use for >1M vectors | ### Distance Metrics | Metric | Best For | Normalized | |--------|----------|------------| | COSINE | Text embeddings | Yes | | EUCLID | Image embeddings | No | | DOT | When vectors pre-normalized | Yes | ## Common Rationalizations | Excuse | Why It's Wrong | |--------|----------------| | "We'll add indexes later when it's slow" | Missing indexes on production tables cause outages, not slowdowns — index from design | | "The ORM handles performance" | ORMs generate queries, they don't optimize them — always check the query plan | | "NoSQL is faster" | NoSQL trades consistency for speed — if you need joins, use a relational DB | | "We don't need migrations, we'll update the schema directly" | Direct schema changes are irreversible and untestable — migrations are the safety net | | "One big table is simpler" | Denormalization without measurement creates update anomalies — normalize first, denormalize with data | ## Rules - **MUST** profile queries with `EXPLAIN (ANALYZE, BUFFERS)` before adding an index — indexes chosen by intuition miss the real hot path half the time - **MUST** design the schema around the dominant access pattern, not the logical entity graph — storage follows queries, not the other way round - **NEVER** write to production with raw SQL when a migration file fits — ad-hoc changes break rollback and audit - **NEVER** add a `SELECT *` in a loop — N+1 is the most common performance regression in code review - **CRITICAL**: every foreign key has an index on the referencing column. Postgres does not create one automatically, and `ON DELETE CASCADE` without the index causes full-table scans on delete. - **MANDATORY**: numeric IDs use `bigint` (or `bigserial`) in new tables unless there is a stated reason to cap at 2^31. Integer overflow on a growing table is a late, painful surprise. ## Gotchas - `EXPLAIN` without `ANALYZE` shows the planner's estimate, not the actual execution. A query plan that "looks good" with `EXPLAIN` can still be slow in practice — always use `ANALYZE` for real diagnosis. - ORM-generated queries often look efficient in one row but emit N+1 at scale. `prisma`, `sequelize`, `activerecord` all have "eager loading" switches that must be explicit — the default is lazy and bites under load. - Postgres transactions hold **row locks** until commit or rollback. A long-running transaction that reads rows another writer needs blocks progress silently. Investigate `pg_stat_activity` for `state=idle in transaction` when writes stall. - Index-only scans require both the query columns AND the filter to be in the index (or in the visibility map for heap tuples). Adding a single column to `WHERE` can demote an index-only scan to an index scan with a 10× slowdown. - MySQL implicit collation on JOIN across tables with different `utf8mb4` collations forces a row-by-row collation conversion — a 100× slowdown that shows as a full scan in the plan. Align collations during schema design. ## When NOT to Load - For **schema evolution** (zero-downtime, expand-contract, backfill) — use `/migration-patterns` - For running migrations as a task — use `/migrate` - For query-plan profiling and the four golden signals — use `/performance-profiling` - For vector/embedding-specific schema — this skill covers the mechanics; use `/rag-patterns` for retrieval design - For observability of DB metrics (slow query log, connection pool saturation) — use `/observability-patterns`
عرض على GitHub