- Add validator_type field to scans and violations (architecture, security, performance) - Create security validator with SEC-xxx rules - Create performance validator with PERF-xxx rules - Add base validator class for shared functionality - Add validate_all.py script to run all validators - Update code quality service with validator type filtering - Add validator type tabs to dashboard UI - Add validator type filter to violations list - Update stats response with per-validator breakdown - Add security and performance rules documentation - Add chat-bubble icons to icon library 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
224 lines
7.3 KiB
YAML
224 lines
7.3 KiB
YAML
# Database Performance Rules
|
|
# ==========================
|
|
|
|
database_rules:
|
|
- id: "PERF-001"
|
|
name: "N+1 query detection"
|
|
severity: warning
|
|
description: |
|
|
Accessing relationships in loops causes N+1 queries.
|
|
For each item in a list, a separate query is executed.
|
|
|
|
Solutions:
|
|
- joinedload(): Eager load with JOIN
|
|
- selectinload(): Eager load with IN clause
|
|
- subqueryload(): Eager load with subquery
|
|
file_pattern: "**/service*.py|**/api/**/*.py"
|
|
anti_patterns:
|
|
- 'for\s+\w+\s+in\s+\w+\.all\(\):\s*\n[^}]*\.\w+\.\w+'
|
|
suggested_patterns:
|
|
- "joinedload|selectinload|subqueryload"
|
|
example_bad: |
|
|
orders = db.query(Order).all()
|
|
for order in orders:
|
|
customer_name = order.customer.name # N+1 query!
|
|
example_good: |
|
|
orders = db.query(Order).options(
|
|
joinedload(Order.customer)
|
|
).all()
|
|
for order in orders:
|
|
customer_name = order.customer.name # Already loaded
|
|
|
|
- id: "PERF-002"
|
|
name: "Eager loading for known relationships"
|
|
severity: info
|
|
description: |
|
|
When you always need related data, use eager loading
|
|
to reduce the number of database round trips.
|
|
file_pattern: "**/service*.py"
|
|
suggested_patterns:
|
|
- "joinedload|selectinload|subqueryload"
|
|
|
|
- id: "PERF-003"
|
|
name: "Query result limiting"
|
|
severity: warning
|
|
description: |
|
|
All list queries should have pagination or limits.
|
|
Unbounded queries can cause memory issues and slow responses.
|
|
file_pattern: "**/service*.py|**/api/**/*.py"
|
|
anti_patterns:
|
|
- '\\.all\\(\\)(?![^\\n]*limit|[^\\n]*\\[:)'
|
|
exclude_patterns:
|
|
- "# noqa: PERF-003"
|
|
- "# bounded query"
|
|
- ".filter("
|
|
suggested_patterns:
|
|
- "limit|offset|skip|paginate"
|
|
example_bad: |
|
|
all_products = db.query(Product).all()
|
|
example_good: |
|
|
products = db.query(Product).limit(100).all()
|
|
# Or with pagination
|
|
products = db.query(Product).offset(skip).limit(limit).all()
|
|
|
|
- id: "PERF-004"
|
|
name: "Index usage for filtered columns"
|
|
severity: info
|
|
description: |
|
|
Columns frequently used in WHERE clauses should have indexes:
|
|
- Foreign keys (vendor_id, customer_id)
|
|
- Status fields
|
|
- Date fields used for filtering
|
|
- Boolean flags used for filtering
|
|
file_pattern: "**/models/database/*.py"
|
|
suggested_patterns:
|
|
- "index=True|Index\\("
|
|
|
|
- id: "PERF-005"
|
|
name: "Select only needed columns"
|
|
severity: info
|
|
description: |
|
|
For large tables, select only the columns you need.
|
|
Use .with_entities() or load_only() to reduce data transfer.
|
|
file_pattern: "**/service*.py"
|
|
suggested_patterns:
|
|
- "with_entities|load_only|defer"
|
|
example_good: |
|
|
# Only load id and name columns
|
|
products = db.query(Product).options(
|
|
load_only(Product.id, Product.name)
|
|
).all()
|
|
|
|
- id: "PERF-006"
|
|
name: "Bulk operations for multiple records"
|
|
severity: warning
|
|
description: |
|
|
Use bulk operations instead of individual operations in loops:
|
|
- bulk_insert_mappings() for inserts
|
|
- bulk_update_mappings() for updates
|
|
- add_all() for ORM inserts
|
|
file_pattern: "**/service*.py"
|
|
anti_patterns:
|
|
- 'for\\s+\\w+\\s+in\\s+\\w+:\\s*\\n[^}]*db\\.add\\s*\\('
|
|
- 'for\\s+\\w+\\s+in\\s+\\w+:\\s*\\n[^}]*\\.save\\s*\\('
|
|
suggested_patterns:
|
|
- "bulk_insert_mappings|bulk_update_mappings|add_all"
|
|
example_bad: |
|
|
for item in items:
|
|
product = Product(**item)
|
|
db.add(product)
|
|
example_good: |
|
|
products = [Product(**item) for item in items]
|
|
db.add_all(products)
|
|
|
|
- id: "PERF-007"
|
|
name: "Connection pool configuration"
|
|
severity: info
|
|
description: |
|
|
Configure database connection pool for optimal performance:
|
|
- pool_size: Number of persistent connections
|
|
- max_overflow: Additional connections allowed
|
|
- pool_pre_ping: Check connection health
|
|
- pool_recycle: Recycle connections periodically
|
|
file_pattern: "**/database.py|**/config*.py"
|
|
suggested_patterns:
|
|
- "pool_size|pool_pre_ping|pool_recycle|max_overflow"
|
|
|
|
- id: "PERF-008"
|
|
name: "Use EXISTS for existence checks"
|
|
severity: info
|
|
description: |
|
|
Use EXISTS or .first() is not None instead of count() > 0.
|
|
EXISTS stops at first match, count() scans all matches.
|
|
file_pattern: "**/service*.py"
|
|
anti_patterns:
|
|
- '\\.count\\(\\)\\s*>\\s*0'
|
|
- '\\.count\\(\\)\\s*>=\\s*1'
|
|
- '\\.count\\(\\)\\s*!=\\s*0'
|
|
suggested_patterns:
|
|
- "exists\\(\\)|scalar\\(exists"
|
|
example_bad: |
|
|
if db.query(Order).filter_by(customer_id=id).count() > 0:
|
|
example_good: |
|
|
exists_query = db.query(exists().where(Order.customer_id == id))
|
|
if db.scalar(exists_query):
|
|
|
|
- id: "PERF-009"
|
|
name: "Batch updates instead of loops"
|
|
severity: warning
|
|
description: |
|
|
Use .update() with filters instead of updating in a loop.
|
|
One UPDATE statement is faster than N individual updates.
|
|
file_pattern: "**/service*.py"
|
|
anti_patterns:
|
|
- 'for\\s+\\w+\\s+in\\s+\\w+:\\s*\\n[^}]*\\w+\\.\\w+\\s*='
|
|
suggested_patterns:
|
|
- "\\.update\\(\\{"
|
|
example_bad: |
|
|
for product in products:
|
|
product.is_active = False
|
|
db.add(product)
|
|
example_good: |
|
|
db.query(Product).filter(
|
|
Product.id.in_(product_ids)
|
|
).update({"is_active": False}, synchronize_session=False)
|
|
|
|
- id: "PERF-010"
|
|
name: "Avoid SELECT * patterns"
|
|
severity: info
|
|
description: |
|
|
When you only need specific columns, don't load entire rows.
|
|
This reduces memory usage and network transfer.
|
|
file_pattern: "**/service*.py"
|
|
|
|
- id: "PERF-011"
|
|
name: "Use appropriate join strategies"
|
|
severity: info
|
|
description: |
|
|
Choose the right join strategy:
|
|
- joinedload: Few related items, always needed
|
|
- selectinload: Many related items, always needed
|
|
- subqueryload: Complex queries, many related items
|
|
- lazyload: Rarely accessed relationships
|
|
file_pattern: "**/service*.py"
|
|
|
|
- id: "PERF-012"
|
|
name: "Transaction scope optimization"
|
|
severity: warning
|
|
description: |
|
|
Keep transactions short and focused:
|
|
- Don't hold transactions during I/O
|
|
- Commit after bulk operations
|
|
- Use read-only transactions when possible
|
|
file_pattern: "**/service*.py"
|
|
|
|
- id: "PERF-013"
|
|
name: "Query result caching"
|
|
severity: info
|
|
description: |
|
|
Consider caching for:
|
|
- Frequently accessed, rarely changed data
|
|
- Configuration tables
|
|
- Reference data (categories, statuses)
|
|
file_pattern: "**/service*.py"
|
|
suggested_patterns:
|
|
- "@cache|@lru_cache|redis|memcache"
|
|
|
|
- id: "PERF-014"
|
|
name: "Composite indexes for multi-column filters"
|
|
severity: info
|
|
description: |
|
|
Queries filtering on multiple columns benefit from composite indexes.
|
|
Order columns by selectivity (most selective first).
|
|
file_pattern: "**/models/database/*.py"
|
|
suggested_patterns:
|
|
- "Index\\([^)]*,[^)]*\\)"
|
|
|
|
- id: "PERF-015"
|
|
name: "Avoid correlated subqueries"
|
|
severity: info
|
|
description: |
|
|
Correlated subqueries execute once per row.
|
|
Use JOINs or window functions instead when possible.
|
|
file_pattern: "**/service*.py"
|