Fix bin/publish: use correct .env path for rspade_system Fix bin/publish script: prevent grep exit code 1 from terminating script 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
445 lines
15 KiB
Plaintext
Executable File
445 lines
15 KiB
Plaintext
Executable File
NAME
|
|
model_indexes - Database index analysis and recommendation system
|
|
|
|
SYNOPSIS
|
|
Automatic detection of required database indexes from model relationships
|
|
and query patterns
|
|
|
|
DESCRIPTION
|
|
The RSX index analysis system automatically discovers which database
|
|
indexes are required by analyzing model relationships and query patterns
|
|
in your codebase. It generates optimized index recommendations using
|
|
covering index logic and produces reviewable migration files.
|
|
|
|
Unlike manual index management where developers must remember to create
|
|
indexes for every foreign key and query pattern, RSX automatically
|
|
detects missing indexes by analyzing:
|
|
|
|
1. Model relationships - Foreign key columns from #[Relationship] methods
|
|
2. Query patterns - AST analysis of Model::where() chains in /rsx/
|
|
|
|
Key differences from manual approaches:
|
|
- Manual: Developer creates indexes as they remember
|
|
- RSX: Automatic detection from code analysis
|
|
|
|
- Manual: No way to know if indexes are missing
|
|
- RSX: Command reports all missing indexes
|
|
|
|
- Manual: Risk of redundant indexes
|
|
- RSX: Covering index logic optimizes recommendations
|
|
|
|
Benefits:
|
|
- Never forget to index foreign keys
|
|
- Automatic detection of query patterns
|
|
- Optimized recommendations via covering logic
|
|
- Quota management prevents too many indexes
|
|
- Source attribution shows why each index is needed
|
|
|
|
COMMAND USAGE
|
|
Basic Analysis:
|
|
php artisan rsx:db:check_indexes
|
|
|
|
Shows summary of missing indexes and recommendations.
|
|
|
|
Generate Migration:
|
|
php artisan rsx:db:check_indexes --generate-migration
|
|
|
|
Creates migration file in database/migrations/ with DDL statements.
|
|
|
|
Detailed Analysis:
|
|
php artisan rsx:db:check_indexes --show-details
|
|
|
|
Shows verbose output with file counts and analysis progress.
|
|
|
|
Analyze Specific Table:
|
|
php artisan rsx:db:check_indexes --table=users
|
|
|
|
Limit analysis to single table.
|
|
|
|
Skip Relationship Analysis:
|
|
php artisan rsx:db:check_indexes --no-relationships
|
|
|
|
Only analyze query patterns, ignore relationship indexes.
|
|
|
|
Skip Query Pattern Analysis:
|
|
php artisan rsx:db:check_indexes --no-queries
|
|
|
|
Only analyze relationships, ignore query patterns.
|
|
|
|
RELATIONSHIP INDEX DETECTION
|
|
How It Works:
|
|
The system scans all models extending Rsx_Model_Abstract and finds
|
|
methods with #[Relationship] attribute. For each relationship, it
|
|
determines which table needs an index on which foreign key column.
|
|
|
|
Detected Relationships:
|
|
- hasMany() / hasOne() - Index on related table's foreign key
|
|
- belongsTo() - No index needed (we have the foreign key)
|
|
- belongsToMany() - Indexes on both pivot table foreign keys
|
|
- morphMany() / morphOne() - Index on related table's morph_id
|
|
|
|
Priority:
|
|
Relationship indexes have HIGHEST priority and are never cut due to
|
|
quota limits. Foreign keys must always be indexed for performance.
|
|
|
|
Example Detection:
|
|
// In User_Model.php
|
|
#[Relationship]
|
|
public function posts()
|
|
{
|
|
return $this->hasMany(Post_Model::class);
|
|
}
|
|
|
|
// Detected requirement: posts table needs index on user_id column
|
|
// Source attribution: rsx/models/user_model.php:45
|
|
|
|
QUERY PATTERN DETECTION
|
|
How It Works:
|
|
The system uses AST parsing to scan all PHP files in /rsx/ that
|
|
define classes. It finds Model::where() method chains and extracts
|
|
the column names in order.
|
|
|
|
Detected Patterns:
|
|
// Single where clause
|
|
User_Model::where('status', 'active')->get();
|
|
// Detected: Index on (status)
|
|
|
|
// Chained where clauses
|
|
User_Model::where('status', 'active')
|
|
->where('created_at', '>', '2024-01-01')
|
|
->get();
|
|
// Detected: Index on (status, created_at)
|
|
|
|
// Multiple columns preserve order
|
|
Order_Model::where('user_id', $id)
|
|
->where('status', 'pending')
|
|
->where('created_at', '>', $date)
|
|
->get();
|
|
// Detected: Index on (user_id, status, created_at)
|
|
|
|
Not Detected (Documented Limitations):
|
|
- Variable column names: where($column, 'value')
|
|
- Complex WHERE clauses: whereIn(), whereRaw(), whereExists()
|
|
- Join conditions (handled separately via relationship analysis)
|
|
- Runtime-determined queries
|
|
- orderBy() clauses (result set ordering, not filtering)
|
|
|
|
Priority:
|
|
Query pattern indexes have MEDIUM priority and are subject to quota
|
|
limits. Multi-column indexes are preferred over single-column.
|
|
|
|
Column Order:
|
|
The system preserves the exact order of columns from your where()
|
|
chain because MySQL index order matters for left-prefix matching.
|
|
|
|
COVERING INDEX OPTIMIZATION
|
|
Left-Prefix Matching Rule:
|
|
MySQL can use an index (a, b, c) to satisfy queries filtering on:
|
|
- (a) alone
|
|
- (a, b) together
|
|
- (a, b, c) together
|
|
|
|
But it CANNOT use the index for:
|
|
- (b) alone
|
|
- (c) alone
|
|
- (b, c) together
|
|
|
|
Optimization Strategy:
|
|
The system uses covering index logic to reduce redundant indexes:
|
|
|
|
Requirements: (status), (status, created_at), (status, role)
|
|
|
|
Analysis:
|
|
- (status, created_at) covers (status) via left-prefix
|
|
- (status, role) covers (status) via left-prefix
|
|
- Neither covers the other (different second columns)
|
|
|
|
Result: Create both (status, created_at) and (status, role)
|
|
Drop standalone (status) - it's covered
|
|
|
|
Reordering for Coverage:
|
|
If you need both (status) and (user_id, status), the system could
|
|
theoretically create (status, user_id) to cover both. However, this
|
|
would hurt performance for queries filtering on user_id alone.
|
|
|
|
Current Implementation: Does not reorder. Preserves query patterns.
|
|
|
|
INDEX QUOTA MANAGEMENT
|
|
MySQL Limits:
|
|
MySQL InnoDB has a hard limit of 64 indexes per table.
|
|
|
|
RSX Quota:
|
|
The system reserves 32 indexes per table for auto-generated indexes,
|
|
leaving 32 for user-defined custom indexes.
|
|
|
|
When Quota Exceeded:
|
|
If recommendations exceed available quota, the system prioritizes:
|
|
|
|
Priority Tier 1: Relationship indexes (never cut)
|
|
Priority Tier 2: Multi-column query indexes (2+ columns)
|
|
Priority Tier 3: Single-column query indexes (cut first)
|
|
|
|
The command displays a warning showing which indexes were cut and
|
|
suggests manual review for those that couldn't be auto-applied.
|
|
|
|
Example Warning:
|
|
⚠️ WARNING: Table 'users' exceeds index quota
|
|
|
|
Recommended indexes: 45
|
|
Available quota: 12 (64 max - 52 existing user indexes)
|
|
|
|
The following 33 indexes cannot be auto-applied:
|
|
- idx_auto_status (single-column, cut)
|
|
- idx_auto_email (single-column, cut)
|
|
...
|
|
|
|
Consider reviewing existing user-defined indexes for redundancy.
|
|
|
|
MIGRATION GENERATION
|
|
Generated Migration Format:
|
|
php artisan rsx:db:check_indexes --generate-migration
|
|
|
|
Creates: database/migrations/{timestamp}_auto_index_recommendations.php
|
|
|
|
Migration Content:
|
|
<?php
|
|
|
|
use Illuminate\Database\Migrations\Migration;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
return new class extends Migration
|
|
{
|
|
public function up()
|
|
{
|
|
// Table: users
|
|
// ============================================
|
|
|
|
// DROP: Obsolete auto-generated indexes
|
|
|
|
DB::statement('DROP INDEX IF EXISTS idx_auto_old_column ON users');
|
|
|
|
// CREATE: New recommended indexes
|
|
|
|
// Source: rsx/app/frontend/users_controller.php:45
|
|
// Priority: query (where)
|
|
DB::statement('
|
|
CREATE INDEX idx_auto_status_created_at
|
|
ON users (status, created_at)
|
|
');
|
|
|
|
// Source: rsx/models/user_model.php:67 (#[Relationship])
|
|
// Priority: relationship (hasMany)
|
|
DB::statement('
|
|
CREATE INDEX idx_auto_organization_id
|
|
ON users (organization_id)
|
|
');
|
|
}
|
|
};
|
|
|
|
Index Naming Convention:
|
|
Auto-generated indexes use pattern: idx_auto_{col1}_{col2}_{col3}
|
|
|
|
Examples:
|
|
- idx_auto_user_id
|
|
- idx_auto_status_created_at
|
|
- idx_auto_status_role_created_at
|
|
|
|
Source Attribution:
|
|
Each CREATE statement includes comments showing:
|
|
- Source file and line number where requirement detected
|
|
- Priority level (relationship or query)
|
|
- Relationship type or query pattern type
|
|
|
|
Applying Migration:
|
|
After reviewing the generated migration:
|
|
|
|
php artisan migrate
|
|
|
|
The migration creates new indexes and drops obsolete ones that
|
|
are no longer needed.
|
|
|
|
AUTO-GENERATED INDEX MANAGEMENT
|
|
Identification:
|
|
The system identifies auto-generated indexes by the idx_auto_ prefix.
|
|
|
|
Lifecycle:
|
|
- System creates indexes with idx_auto_ prefix
|
|
- On subsequent runs, system checks which are still needed
|
|
- Obsolete auto-generated indexes are dropped
|
|
- User-defined indexes are NEVER touched
|
|
|
|
User-Defined Indexes:
|
|
Any index NOT starting with idx_auto_ is considered user-defined
|
|
and is completely ignored by the system. This allows developers
|
|
to manually create custom indexes without interference.
|
|
|
|
Idempotency:
|
|
Running the command multiple times without code changes produces
|
|
no recommendations. The system only suggests changes when:
|
|
- New relationships are added
|
|
- New query patterns are detected
|
|
- Existing relationships are removed
|
|
- Existing query patterns change
|
|
|
|
OUTPUT EXAMPLES
|
|
Success Output:
|
|
Database Index Analysis
|
|
==================================================
|
|
|
|
Found 12 indexes to create and 4 to drop
|
|
|
|
Table: users
|
|
+ Create index on (status, created_at) - rsx/controllers/users.php:23
|
|
+ Create index on (organization_id) - rsx/models/user_model.php:45
|
|
- Drop index idx_auto_old_status on (old_status)
|
|
|
|
Table: posts
|
|
+ Create index on (user_id) - rsx/models/post_model.php:12
|
|
|
|
To generate migration:
|
|
php artisan rsx:db:check_indexes --generate-migration
|
|
|
|
All Indexes Up To Date:
|
|
Database Index Analysis
|
|
==================================================
|
|
|
|
✅ All indexes are up to date
|
|
|
|
With Detailed Analysis:
|
|
Database Index Analysis
|
|
==================================================
|
|
|
|
Analyzing model relationships...
|
|
Found 23 relationships
|
|
Analyzing query patterns...
|
|
Scanning 47 PHP files for query patterns
|
|
Query pattern analysis complete
|
|
Total requirements collected: 87
|
|
Optimizing index requirements...
|
|
Found 12 indexes to create and 4 to drop
|
|
...
|
|
|
|
LIMITATIONS WARNING
|
|
The command always displays limitations to set expectations:
|
|
|
|
⚠️ LIMITATIONS
|
|
|
|
This analysis cannot detect:
|
|
✗ Dynamic column names: where($variable, ...)
|
|
✗ Join conditions (separate relationship analysis)
|
|
✗ Complex WHERE clauses (whereRaw, whereIn, etc.)
|
|
✗ Runtime-determined query patterns
|
|
|
|
Always benchmark critical queries and add custom indexes as needed.
|
|
Use EXPLAIN to verify query performance in production.
|
|
|
|
Why These Limitations:
|
|
Static analysis cannot predict runtime behavior. The system can
|
|
only analyze code patterns visible in source files.
|
|
|
|
Recommendations:
|
|
- Use EXPLAIN on critical queries in production
|
|
- Benchmark slow queries and add custom indexes
|
|
- Review query logs for patterns not detected by AST
|
|
- Run command periodically as codebase evolves
|
|
|
|
WORKFLOW
|
|
Regular Development:
|
|
1. Write models with #[Relationship] methods
|
|
2. Write controllers with Model::where() queries
|
|
3. Run: php artisan rsx:db:check_indexes
|
|
4. Review recommendations
|
|
5. Run: php artisan rsx:db:check_indexes --generate-migration
|
|
6. Review generated migration file
|
|
7. Run: php artisan migrate
|
|
|
|
Before Deployment:
|
|
Always run the index analysis before deploying to production to
|
|
ensure all required indexes exist. Missing indexes can cause
|
|
severe performance issues in production.
|
|
|
|
Periodic Audits:
|
|
Run the command periodically to catch missing indexes as your
|
|
codebase evolves. New features often introduce new relationships
|
|
and query patterns.
|
|
|
|
EXAMPLES
|
|
Analyze All Tables:
|
|
php artisan rsx:db:check_indexes
|
|
|
|
Generate Migration:
|
|
php artisan rsx:db:check_indexes --generate-migration
|
|
|
|
Check Specific Table:
|
|
php artisan rsx:db:check_indexes --table=users
|
|
|
|
Relationships Only:
|
|
php artisan rsx:db:check_indexes --no-queries
|
|
|
|
Queries Only:
|
|
php artisan rsx:db:check_indexes --no-relationships
|
|
|
|
Verbose Output:
|
|
php artisan rsx:db:check_indexes --show-details
|
|
|
|
TROUBLESHOOTING
|
|
No Indexes Detected:
|
|
- Verify models extend Rsx_Model_Abstract
|
|
- Check #[Relationship] attributes present on relationship methods
|
|
- Ensure PHP files in /rsx/ define classes
|
|
- Run with --show-details to see file count
|
|
|
|
Missing Query Pattern Detection:
|
|
- Verify Model::where() uses string literals, not variables
|
|
- Check model class names end with _Model suffix
|
|
- Ensure AST parser can parse file syntax
|
|
- Complex queries may not be detected (use custom indexes)
|
|
|
|
Quota Warnings:
|
|
- Review existing user-defined indexes for redundancy
|
|
- Consider if all existing indexes are still needed
|
|
- Manually select which auto-indexes to apply
|
|
- Some tables genuinely need many indexes
|
|
|
|
Migration Fails:
|
|
- Check MySQL version supports CREATE INDEX IF NOT EXISTS
|
|
- Verify table names are correct
|
|
- Ensure sufficient MySQL privileges
|
|
- Review migration file for syntax errors
|
|
|
|
BEST PRACTICES
|
|
Relationship Indexes:
|
|
Always use #[Relationship] attribute on relationship methods.
|
|
This ensures the system can detect foreign key requirements.
|
|
|
|
Query Patterns:
|
|
Use string literals in where() clauses when possible:
|
|
- Good: where('status', 'active')
|
|
- Bad: where($column, 'active')
|
|
|
|
Column Order Matters:
|
|
Order your where() clauses by selectivity:
|
|
- Most selective columns first
|
|
- Example: where('user_id', $id)->where('status', 'active')
|
|
- Better than: where('status', 'active')->where('user_id', $id)
|
|
|
|
Custom Indexes:
|
|
For queries the system can't detect, create custom indexes:
|
|
- Use standard naming: idx_users_status_created_at
|
|
- Avoid idx_auto_ prefix (reserved for system)
|
|
- Document why the index is needed
|
|
|
|
Regular Audits:
|
|
Run index analysis as part of deployment checklist.
|
|
Missing indexes are easier to add before production.
|
|
|
|
Migration Review:
|
|
Always review generated migrations before applying.
|
|
Verify recommendations make sense for your data patterns.
|
|
|
|
SEE ALSO
|
|
model - RSX model system with relationships
|
|
model_normalization - Schema normalization command
|
|
migrations - Database migration system
|
|
coding_standards - Code quality and standards
|