2026-01-03 13:14:27 +00:00
|
|
|
-- Migration 003: Add composite and covering indexes for performance
|
|
|
|
|
-- Date: 2025-12-14
|
|
|
|
|
-- Description: Create optimized indexes for frequent queries
|
|
|
|
|
|
|
|
|
|
-- =====================================================
|
|
|
|
|
-- 1. User-Pack relationship indexes
|
|
|
|
|
-- =====================================================
|
|
|
|
|
|
|
|
|
|
-- Composite index for "get user packs" query
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_user_packs_composite
|
|
|
|
|
ON user_packs(user_id, pack_id)
|
|
|
|
|
INCLUDE (granted_at, grant_type);
|
|
|
|
|
|
|
|
|
|
-- Reverse index for "which users have this pack"
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_user_packs_pack_composite
|
|
|
|
|
ON user_packs(pack_id, user_id)
|
|
|
|
|
WHERE grant_type != 'admin';
|
|
|
|
|
|
|
|
|
|
-- =====================================================
|
|
|
|
|
-- 2. Card-Pack relationship indexes
|
|
|
|
|
-- =====================================================
|
|
|
|
|
|
|
|
|
|
-- Composite index for "get pack cards ordered"
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_card_pack_cards_ordered
|
|
|
|
|
ON card_pack_cards(pack_id, "order")
|
|
|
|
|
INCLUDE (card_id);
|
|
|
|
|
|
|
|
|
|
-- Index for cards
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_game_cards_pack
|
|
|
|
|
ON game_cards(pack_id)
|
|
|
|
|
INCLUDE (original, translation)
|
|
|
|
|
WHERE is_deleted = false;
|
|
|
|
|
|
|
|
|
|
-- =====================================================
|
|
|
|
|
-- 3. User subscriptions indexes
|
|
|
|
|
-- =====================================================
|
|
|
|
|
|
|
|
|
|
-- Active subscriptions (most common query)
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_user_subscriptions_active
|
|
|
|
|
ON user_subscriptions(user_id, finish)
|
|
|
|
|
WHERE finish > NOW();
|
|
|
|
|
|
|
|
|
|
-- Index for subscription expiration checks
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_user_subscriptions_expiring
|
|
|
|
|
ON user_subscriptions(finish)
|
|
|
|
|
WHERE finish BETWEEN NOW() AND NOW() + INTERVAL '7 days';
|
|
|
|
|
|
|
|
|
|
-- =====================================================
|
|
|
|
|
-- 4. Payments indexes
|
|
|
|
|
-- =====================================================
|
|
|
|
|
|
|
|
|
|
-- Covering index for user payments list
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_payments_user_covering
|
|
|
|
|
ON payments(user_id, date DESC)
|
|
|
|
|
INCLUDE (amount, currency, status, payment_system);
|
|
|
|
|
|
|
|
|
|
-- Index for pending/processing payments (for cron jobs)
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_payments_pending
|
|
|
|
|
ON payments(status, date DESC)
|
|
|
|
|
WHERE status IN ('pending', 'processing', 'created');
|
|
|
|
|
|
|
|
|
|
-- Index for successful payments analytics
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_payments_succeeded
|
|
|
|
|
ON payments(date DESC)
|
|
|
|
|
WHERE status = 'succeeded';
|
|
|
|
|
|
|
|
|
|
-- =====================================================
|
|
|
|
|
-- 5. Study sessions indexes
|
|
|
|
|
-- =====================================================
|
|
|
|
|
|
|
|
|
|
-- User sessions ordered by time
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_study_sessions_user_time
|
|
|
|
|
ON study_sessions(user_id, start_time DESC)
|
|
|
|
|
INCLUDE (words_learned, tests_completed, accuracy);
|
|
|
|
|
|
|
|
|
|
-- Active sessions (no end_time yet)
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_study_sessions_active
|
|
|
|
|
ON study_sessions(user_id, session_id)
|
|
|
|
|
WHERE end_time IS NULL;
|
|
|
|
|
|
|
|
|
|
-- =====================================================
|
|
|
|
|
-- 6. Authentication indexes
|
|
|
|
|
-- =====================================================
|
|
|
|
|
|
|
|
|
|
-- Covering index for token lookup
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_tokens_token_covering
|
|
|
|
|
ON tokens(token)
|
|
|
|
|
INCLUDE (user_id, expires, external_user_id);
|
|
|
|
|
|
|
|
|
|
-- Valid tokens only
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_tokens_valid
|
|
|
|
|
ON tokens(user_id, expires DESC)
|
|
|
|
|
WHERE expires > NOW();
|
|
|
|
|
|
|
|
|
|
-- Refresh tokens covering index
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_jti_covering
|
|
|
|
|
ON refresh_tokens(jti)
|
|
|
|
|
INCLUDE (user_id, expires_at, is_blacklisted);
|
|
|
|
|
|
|
|
|
|
-- Active refresh tokens
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_active
|
|
|
|
|
ON refresh_tokens(user_id, expires_at DESC)
|
|
|
|
|
WHERE is_blacklisted = false AND expires_at > NOW();
|
|
|
|
|
|
|
|
|
|
-- =====================================================
|
|
|
|
|
-- 7. Users indexes
|
|
|
|
|
-- =====================================================
|
|
|
|
|
|
|
|
|
|
-- Email lookup with common fields
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_users_email_covering
|
|
|
|
|
ON users(email)
|
|
|
|
|
INCLUDE (id, name, admin, external_user_id)
|
|
|
|
|
WHERE email IS NOT NULL AND is_deleted = false;
|
|
|
|
|
|
|
|
|
|
-- External user ID lookup
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_users_external_covering
|
|
|
|
|
ON users(external_user_id)
|
|
|
|
|
INCLUDE (id, name, email, admin);
|
|
|
|
|
|
|
|
|
|
-- =====================================================
|
|
|
|
|
-- 8. Promo codes indexes
|
|
|
|
|
-- =====================================================
|
|
|
|
|
|
|
|
|
|
-- Code lookup
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_promo_codes_code_covering
|
|
|
|
|
ON promo_codes(code)
|
|
|
|
|
INCLUDE (campaign_id, user_id, is_used)
|
|
|
|
|
WHERE is_used = false;
|
|
|
|
|
|
|
|
|
|
-- Campaign promo codes
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_promo_codes_campaign
|
|
|
|
|
ON promo_codes(campaign_id, is_used);
|
|
|
|
|
|
|
|
|
|
-- =====================================================
|
|
|
|
|
-- 9. Tests indexes
|
|
|
|
|
-- =====================================================
|
|
|
|
|
|
|
|
|
|
-- Test pack relation
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_test_pack_relations_pack
|
|
|
|
|
ON test_pack_relations(pack_id)
|
|
|
|
|
INCLUDE (test_id);
|
|
|
|
|
|
|
|
|
|
-- Test questions ordered
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_test_questions_test
|
|
|
|
|
ON test_questions(test_id, "order");
|
|
|
|
|
|
|
|
|
|
-- =====================================================
|
|
|
|
|
-- Analyze tables after creating indexes
|
|
|
|
|
-- =====================================================
|
|
|
|
|
ANALYZE users;
|
|
|
|
|
ANALYZE user_packs;
|
|
|
|
|
ANALYZE card_pack_cards;
|
|
|
|
|
ANALYZE game_cards;
|
|
|
|
|
ANALYZE payments;
|
|
|
|
|
ANALYZE user_subscriptions;
|
|
|
|
|
ANALYZE study_sessions;
|
|
|
|
|
ANALYZE tokens;
|
|
|
|
|
ANALYZE refresh_tokens;
|
|
|
|
|
ANALYZE promo_codes;
|
|
|
|
|
ANALYZE tests;
|
|
|
|
|
|
|
|
|
|
-- =====================================================
|
|
|
|
|
-- Проверка созданных индексов
|
|
|
|
|
-- =====================================================
|
|
|
|
|
SELECT
|
|
|
|
|
schemaname,
|
|
|
|
|
tablename,
|
|
|
|
|
indexname,
|
|
|
|
|
indexdef
|
|
|
|
|
FROM pg_indexes
|
|
|
|
|
WHERE schemaname = 'public'
|
|
|
|
|
AND indexname LIKE 'idx_%composite%'
|
|
|
|
|
OR indexname LIKE 'idx_%covering%'
|
|
|
|
|
ORDER BY tablename, indexname;
|
|
|
|
|
|
|
|
|
|
-- =====================================================
|
|
|
|
|
-- Оценка размера индексов
|
|
|
|
|
-- =====================================================
|
|
|
|
|
SELECT
|
|
|
|
|
schemaname,
|
|
|
|
|
tablename,
|
|
|
|
|
indexname,
|
|
|
|
|
pg_size_pretty(pg_relation_size(indexrelid)) as index_size
|
|
|
|
|
FROM pg_stat_user_indexes
|
|
|
|
|
WHERE schemaname = 'public'
|
|
|
|
|
ORDER BY pg_relation_size(indexrelid) DESC
|
|
|
|
|
LIMIT 20;
|
|
|
|
|
|
|
|
|
|
PRINT 'Migration 003 completed successfully!';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-08 13:02:47 +00:00
|
|
|
|
2026-01-24 12:31:47 +00:00
|
|
|
|