10 KiB
🚀 Optymalizacja wydajności - Dokumentacja
Implementowane zmiany dla obsługi setek tysięcy rekordów
⚡ Zaimplementowane optymalizacje
1. Fast Approximate Count z Cachingiem (Kluczowa optymalizacja)
Problem: COUNT(*) na tabeli z 500k+ rekordów może trwać 5-10 sekund
Rozwiązanie:
- Limit COUNT do 100,000 rekordów
- Powyżej limitu wyświetla "100,000+"
- Cache w sesji na 5 minut
- Wydajność: ~50-100ms zamiast 5000ms+
Pliki:
Działanie:
// Cache hit: natychmiastowy zwrot (0ms)
// Cache miss z limitem: ~50-100ms
// Stary sposób: 5000-10000ms
2. Query Timeouts
Problem: Zapytania bez timeoutów mogą zawiesić serwer Rozwiązanie:
- Connection timeout: 10 sekund
- Query timeout: 30 sekund
- PHP execution: 30 sekund
Implementacja:
[
PDO::ATTR_TIMEOUT => 10,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET SESSION MAX_EXECUTION_TIME=30000"
]
3. Composite Indexes (Krytyczne!)
Problem: Pojedyncze indeksy nie optymalizują złożonych zapytań Rozwiązanie: Utworzono 10+ composite indexes
Najważniejsze indeksy:
| Index | Kolumny | Użycie |
|---|---|---|
idx_matches_status_start_time |
Status, StartTime | Filtr + sortowanie (80% zapytań) |
idx_matches_platform_type_status |
Platform, MatchType, Status | Filtrowanie 3-kolumnowe |
idx_matches_covering_count |
Status, Platform, MatchType, StartTime, ID | COUNT optimization |
idx_users_role_verified_created |
role, email_verified, created_at | Panel admina users |
Wydajność:
- Przed: Full table scan 500k rows = 2-5s
- Po: Index scan = 10-50ms
- Przyspieszenie: 50-500x
Plik: database_optimization_indexes.sql
4. Usunięcie LEFT JOIN z loadUsers.php
Problem: LEFT JOIN user_stats przy każdym zapytaniu Rozwiązanie: Lazy loading - pobieraj tylko gdy potrzebne
Oszczędność:
- Mniej IO operations
- Mniejsze zapytania
- ~20-30% szybsze ładowanie listy użytkowników
5. Archiwizacja starych rekordów
Problem: Tabela rośnie w nieskończoność Rozwiązanie:
- Automatyczna archiwizacja meczów > 6 miesięcy
- Tabela
matches_archive - Automatyczny cronjob co tydzień
- Widok
matches_alldla dostępu do wszystkich
Cel: Utrzymać tabelę główną < 100k rekordów
Plik: database_archivization.sql
Użycie:
-- Manualne uruchomienie
CALL archive_old_matches();
-- Przywrócenie meczu
CALL restore_match_from_archive(12345);
-- Wyłączenie auto-archiwizacji
ALTER EVENT weekly_match_archivization DISABLE;
📊 Wydajność - Porównanie
| Operacja | Przed optymalizacją | Po optymalizacji | Przyspieszenie |
|---|---|---|---|
| COUNT(*) 500k rows | 5-10s | 50-100ms (cache: 0ms) | 50-100x |
| Lista meczów (filtr+sort) | 2-5s | 10-50ms | 40-200x |
| Lista użytkowników | 800ms-2s | 100-300ms | 8-10x |
| Paginacja (page 100+) | 3-8s | 50-150ms | 30-80x |
🎯 Testowanie wydajności
Test 1: Bez cache
# Pierwsze wywołanie
curl "http://localhost/api/getMatches.php?page=1" -w "\nTime: %{time_total}s\n"
# Oczekiwany czas: 50-200ms
Test 2: Z cache
# Drugie wywołanie (w ciągu 5 minut)
curl "http://localhost/api/getMatches.php?page=1" -w "\nTime: %{time_total}s\n"
# Oczekiwany czas: 10-30ms
Test 3: Z filtrami
curl "http://localhost/api/getMatches.php?status=live&platform=PC" -w "\nTime: %{time_total}s\n"
# Oczekiwany czas: 20-80ms (dzięki composite index)
🔧 Instalacja
- Wykonaj skrypty SQL:
# Indeksy (wykonaj NAJPIERW!)
mysql -u togethere_cloud -p togethere_cloud < database_optimization_indexes.sql
# Archiwizacja (opcjonalne)
mysql -u togethere_cloud -p togethere_cloud < database_archivization.sql
- Sprawdź utworzone indeksy:
SHOW INDEX FROM matches;
SHOW INDEX FROM users;
- Włącz event scheduler (dla archiwizacji):
SET GLOBAL event_scheduler = ON;
SHOW VARIABLES LIKE 'event_scheduler';
- Test wydajności:
EXPLAIN SELECT * FROM matches
WHERE Status = 'live' AND Platform = 'PC'
ORDER BY StartTime DESC
LIMIT 50;
-- Sprawdź czy używa idx_matches_status_platform_time
⚠️ Monitoring
Sprawdzenie rozmiaru indeksów:
SELECT
TABLE_NAME,
INDEX_NAME,
ROUND(INDEX_LENGTH / 1024 / 1024, 2) AS 'Size_MB'
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = 'togethere_cloud'
AND TABLE_NAME IN ('matches', 'users')
GROUP BY TABLE_NAME, INDEX_NAME;
Sprawdzenie statystyk archiwizacji:
SELECT
'Active' as type, COUNT(*) as count FROM matches
UNION ALL
SELECT
'Archived' as type, COUNT(*) as count FROM matches_archive;
🎮 Integracja gry (Ping-Pong) z backendem
Kluczowe pliki
- private_html/api/matches_sync.php – endpoint do tworzenia, aktualizacji i synchronizacji meczów.
- private_html/api/match_service.php – logika CRUD + walidacja danych.
- private_html/api/game-validator.php – serwerowa walidacja wyniku (opcjonalna, wywoływana przy statusie
end). - Test: private_html/tests/matches_sync_test.php (smoke test przepływu create → update → sync).
Konfiguracja połączenia DB (driver MySQL)
- Ustaw host/bazę/login w private_html/administration/includes/config.php (aktualnie
localhost,togethere_cloud). - Endpointy używają PDO z
charset=utf8mb4,ERRMODE_EXCEPTION. - Dostęp wymaga sesji:
$_SESSION['logged_in'] === truei$_SESSION['user_id'](ustawiane podczas logowania).
API – szybki start
- Tworzenie meczu:
POST /api/matches_sync.php{ "team1_id": 1, "team2_id": 2, "startTime": "2026-01-27 12:00:00", "platform": "PC", "matchType": "league", "status": "live", "participants": [1,2] } - Aktualizacja wyniku:
PUT /api/matches_sync.php?id=123{ "status": "end", "score": "10:8", "endTime": "2026-01-27 12:10:00", "gameData": { "playerScore": 10, "botScore": 8, "gameDuration": 420, "difficulty": "normal", "sessionToken": "..." } } - Odczyt zmian (polling/real-time):
GET /api/matches_sync.php?since=2026-01-27%2012:00:00&status=live&limit=50
Walidacja danych
- Dozwolone statusy:
planned,live,end. - Wynik
scorew formacieX:Y(np.10:8). startTime/endTime– dowolny parsowalny datetime; zapisywany jakoY-m-d H:i:s.participantszapisywane w kolumnieParticipantsjako JSON array ID użytkowników.- Przy statusie
endmożesz przekazać blokgameData; zostanie zweryfikowany w private_html/api/game-validator.php. Błędna walidacja zwróci400.
Synchronizacja danych
- Model pull: klient gry przechowuje ostatni znacznik
syncedAti wywołujeGET /api/matches_sync.php?since=<znacznik>co 5–15s (lub po zakończeniu meczu). - Spójność po meczu: ustaw
status=end,score,endTime; endpoint automatycznie ustawiEndTimegdy brak wartości. - Archiwizacja: po 6 miesiącach rekord trafi do
matches_archiveprzez istniejący cron (private_html/cron/archive_matches.php).
Testy
- Smoke test CLI (nie wymaga serwera HTTP):
Test tworzy mecz, aktualizuje wynik, pobiera ostatnie zmiany i usuwa rekord testowy.php private_html/tests/matches_sync_test.php
Informacja dla graczy
- Zapisywane dane: ID drużyn, czas start/koniec, status (
planned/live/end), wynikX:Y, platforma, typ meczu, lista uczestników. - Podgląd wyników: gry mogą odpytywać
GET /api/matches_sync.phpz parametremsince, aby pobierać zmienione mecze bez pełnego odświeżania listy.
Monitoruj slow queries:
-- Włącz slow query log
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- queries > 1s
-- Sprawdź logi
SHOW VARIABLES LIKE 'slow_query_log_file';
🎯 Limity i skalowanie
Obecne limity:
- COUNT: 100,000 rekordów (powyżej pokazuje "100k+")
- Cache: 5 minut
- Timeout: 30 sekund
- Archiwizacja: 6 miesięcy
Przy > 1M rekordów rozważ:
- Redis cache zamiast session
- Read replicas dla separacji read/write
- Partitioning tabeli po dacie
- ElasticSearch dla advanced search
📝 Checklist wdrożenia
- Zoptymalizowano getMatches.php (COUNT + timeout)
- Zoptymalizowano loadUsers.php (COUNT + timeout + usunięto JOIN)
- Utworzono composite indexes
- Utworzono archiwizację
- Wykonano SQL: database_optimization_indexes.sql
- Wykonano SQL: database_archivization.sql (opcjonalne)
- Przetestowano wydajność
- Włączono event_scheduler (jeśli archiwizacja)
- Skonfigurowano monitoring
🆘 Troubleshooting
Problem: "Unknown column in 'field list'" Rozwiązanie: Sprawdź czy struktura tabeli ma wszystkie kolumny (EndTime, created_at, updated_at)
Problem: Event scheduler nie działa Rozwiązanie:
SET GLOBAL event_scheduler = ON;
SHOW PROCESSLIST; -- sprawdź czy event scheduler jest aktywny
Problem: Indeksy nie są używane Rozwiązanie:
ANALYZE TABLE matches;
ANALYZE TABLE users;
-- Wymusza przeliczenie statystyk
Problem: Za wolne mimo optymalizacji Rozwiązanie:
-- Sprawdź czy indeksy są używane
EXPLAIN SELECT ... ;
-- Zwiększ buffer pool
SET GLOBAL innodb_buffer_pool_size = 2147483648; -- 2GB
📚 Dodatkowe zasoby
- MySQL Index Optimization: https://dev.mysql.com/doc/refman/8.0/en/optimization-indexes.html
- Query Cache: https://dev.mysql.com/doc/refman/5.7/en/query-cache.html
- InnoDB Buffer Pool: https://dev.mysql.com/doc/refman/8.0/en/innodb-buffer-pool.html