JSON Field, Cache, Index - Sistemi Yavaşlatan Her Şeyi Analiz Ediyoruz!
Kullanıcı anasayfayı açtığında sistem şunu yapıyor:
JSON içinde arama yapmak normal sütunda aramaya göre daha yavaş.
WHERE sector_id = 1 → Çok hızlı (index var)WHERE JSON_CONTAINS(display_rules, '1', '$.sectors') → Yavaş (index yok)Her koleksiyon için ayrı ayrı veritabanına gidip içeriği çekmek.
Hazır sonuçları sakla. Her seferinde hesaplama! 60 saniye cache = 60 kat daha hızlı!
Veritabanına "kitap içindekiler" ekle. Aramayı 100 kat hızlandır!
Tüm ilişkili verileri tek seferde çek. 101 query → 2 query!
SELECT * FROM muzibu_content_collections
WHERE JSON_CONTAINS(display_rules, '1', '$.sectors')
AND JSON_EXTRACT(display_rules, '$.time_range.start') <= '09:00'
AND JSON_EXTRACT(display_rules, '$.time_range.end') >= '09:00'
AND is_active = 1;
// Problem: JSON fonksiyonları index kullanamaz!
// 100 satır için ~50-100ms
// 1 query: Koleksiyonları çek
$collections = ContentCollection::where('is_active', true)->get();
foreach ($collections as $collection) {
// +100 query: Her koleksiyon için ayrı query!
$items = $collection->items()->get();
}
// Toplam: 1 + 100 = 101 query!
// Her query ~5ms = 505ms 😱
// Eager loading ile 2 query!
$collections = ContentCollection::with([
'items.playlist',
'items.album',
'items.radio'
])
->where('is_active', true)
->get();
// Query 1: Collections çek
// Query 2: Tüm items + ilişkili data (tek seferde!)
// Toplam: 2 query = 10ms ⚡
Her sayfa yüklemesinde tüm koleksiyonların kuralları PHP'de kontrol ediliyor:
foreach ($collections as $collection) {
// Saat kontrolü
if (!$this->checkTimeRange($collection->display_rules)) continue;
// Gün kontrolü
if (!$this->checkDays($collection->display_rules)) continue;
// Sektör kontrolü
if (!$this->checkSectors($collection->display_rules, $currentSector)) continue;
// Kullanıcı koşulları
if (!$this->checkVisibility($collection->visibility_conditions, $user)) continue;
}
// 100 koleksiyon × 4 kontrol × 5ms = 2000ms (2 saniye!) 😱
// collection_items tablosu
item_type: 'playlist', item_id: 15
item_type: 'album', item_id: 8
item_type: 'radio', item_id: 3
// Laravel şunu yapar:
SELECT * FROM muzibu_playlists WHERE id IN (15, 42, 88, ...);
SELECT * FROM muzibu_albums WHERE id IN (8, 25, 67, ...);
SELECT * FROM muzibu_radios WHERE id IN (3, 19, 44, ...);
// 3 ayrı tablo = 3 ayrı query + JOIN maliyeti
Cache::remember(
'collections.home.sector_1',
60, // 60 saniye
fn() => Collection::with('items')
->where('is_active', true)
->get()
);
// İlk yükleme: 100ms
// Cache hit: 2ms ⚡
@cache('home.collections', 60)
@foreach($collections as $col)
<div>{{ $col->name }}</div>
@endforeach
@endcache
// İlk render: 50ms
// Cache hit: 1ms ⚡
// ResponseCache package
Route::get('/', HomeController)
->middleware('cache.response:60');
// İlk yükleme: 200ms
// Cache hit: 5ms ⚡
// 40x hızlanma!
// Migration: Virtual columns ekle
Schema::table('muzibu_content_collections', function($table) {
// JSON'dan virtual column oluştur
$table->time('display_time_start')
->virtualAs("JSON_UNQUOTE(JSON_EXTRACT(display_rules, '$.time_range.start'))");
$table->time('display_time_end')
->virtualAs("JSON_UNQUOTE(JSON_EXTRACT(display_rules, '$.time_range.end'))");
$table->json('display_sectors_cached')
->virtualAs("JSON_EXTRACT(display_rules, '$.sectors')");
// Virtual column'a index ekle! ⚡
$table->index(['is_active', 'display_time_start', 'display_time_end']);
});
// Sık kullanılan kombinasyonları index'le
$table->index(['is_active', 'priority', 'created_at'], 'collections_active_priority_idx');
$table->index(['type', 'is_active'], 'collections_type_active_idx');
// collection_items için
$table->index(['collection_id', 'position'], 'items_collection_position_idx');
$table->index(['item_type', 'item_id'], 'items_morphable_idx');
// Koleksiyon isimlerinde arama için
DB::statement('ALTER TABLE muzibu_content_collections
ADD FULLTEXT INDEX collections_search_idx (name)');
$collections = Collection::all();
// 101 query!
$collections = Collection::with([
'items.playlist.sectors',
'items.album.artists',
'items.radio'
])->get();
// 4-5 query!
Collection::all();
// Tüm sütunlar çekiliyor
// JSON parse overhead!
Collection::select([
'id', 'name', 'slug',
'icon', 'color', 'type'
])->get();
// Sadece gerekli sütunlar!
// 10000 koleksiyon varsa, 100'er 100'er işle
Collection::with('items')
->chunk(100, function($collections) {
foreach ($collections as $collection) {
// Process...
}
});
// Memory: 10 MB yerine 1 MB ⚡
// Scheduled Job: Her 5 dakikada bir çalış
// app/Console/Kernel.php
$schedule->job(new PrecomputeCollectionVisibility)
->everyFiveMinutes();
// Job: Tüm koleksiyonları değerlendir, cache'e yaz
class PrecomputeCollectionVisibility
{
public function handle()
{
$now = now();
$currentHour = $now->format('H:i');
$currentDay = $now->dayName;
// Her sayfa için ayrı cache
foreach (['home', 'sector_show'] as $page) {
foreach (Sector::all() as $sector) {
$visibleCollections = Collection::all()
->filter(function($col) use ($currentHour, $currentDay, $sector) {
return $this->checkRules($col, $currentHour, $currentDay, $sector);
});
// Cache'e yaz (5 dakika)
Cache::put(
"collections.{$page}.sector_{$sector->id}",
$visibleCollections,
300 // 5 dakika
);
}
}
}
}
// Controller'da sadece cache'den oku!
public function home()
{
$collections = Cache::get('collections.home.sector_' . auth()->user()->sector_id);
return view('home', compact('collections'));
}
// Sayfa yüklemesi: 0ms (sadece cache read!) ⚡⚡⚡
Hemen uygulanabilir, büyük etki!
Index'ler ve virtual columns
Multi-layer cache yapısı
Pre-computation ile ultra hız