23 β Caching va performance¶
β¬ οΈ Oldingi: 22 β Testing (Pest va PHPUnit) Β· π README Β· Keyingi: 24 β Deploy va yakuniy loyiha β‘οΈ
Bu bobda: ilova foydalanuvchi ko'payganda nega sekinlashishini va uni tezlashtirishning amaliy yo'llarini o'rganamiz. Cache (
Cache::remember,get,put,forget,flush) bilan og'ir hisob-kitobni bir marta bajarib qayta ishlatishni, file/database/Redis driverlarni, productiondaconfig:cache/route:cache/view:cacheva bittaphp artisan optimizebuyrug'ini ko'ramiz. So'ng asosiy sekinlik manbai β N+1 muammosini eager loading bilan yo'q qilamiz,Model::preventLazyLoadingbilan uni dasturlash bosqichida ushlaymiz, database indeksini migratsiyada qo'shamiz, faqat kerakli ustunniselectqilishni, katta jadvalnichunk/lazybilan kichik bo'laklarda qayta ishlashni vapaginatebilan butun jadvalni xotiraga olmaslikni o'rganamiz. Oxirida Redis'ni cache + session + queue uchun ulaymiz va Telescope bilan so'rovlarni "ko'z bilan" profil qilamiz.
Muammo¶
Do'koningiz ishga tushdi. Birinchi haftada hammasi yashin tez: bosh sahifa darrov ochiladi, buyurtmalar ro'yxati bir zumda chiqadi. Mahsulotlar 50 ta, buyurtmalar 200 ta.
Olti oydan keyin do'kon o'sdi: 8000 mahsulot, 120 000 buyurtma, kuniga minglab tashrif. Va birdan shikoyatlar keladi β "sayt sekin ochilyapti", "buyurtmalar sahifasi 5 sekund kutadi". Kod o'zgarmadi-ku, nega sekinlashdi?
Sabab oddiy: kichik ma'lumotda sezilmaydigan samarasizlik, katta ma'lumotda halokatga aylanadi. Masalan, har so'rovda bazadan og'ir hisobotni qayta hisoblash, yoki har bir buyurtma uchun mijozini alohida-alohida so'rash (N+1), yoki butun jadvalni xotiraga yuklash. 50 qatorda bu hech narsa, 120 000 qatorda β sayt qotadi.
Bu bobda birinchi qoidani o'rganamiz: tezlashtirishdan oldin o'lchang. Qaysi so'rov sekin, nechta so'rov yuborilayotganini bilmasdan optimallashtirish β qorong'uda o'q otish. Keyin esa tezlashtirishning ikki katta yo'lini ko'ramiz: takror ishni cache'lash (bir marta hisoblab saqlash) va ortiqcha ishni yo'q qilish (N+1, ortiqcha ustun, butun jadvalni yuklash). Boshlaymiz.
π Birinchi maslahat oltin qiymatga ega: erta optimallashtirmang. Sekinligi isbotlanmagan kodni "ehtimol kerak bo'lar" deb cache'lab tashlash faqat bug keltiradi (eskirgan ma'lumot ko'rsatish). Avval ishlaydigan, to'g'ri kod yozing; sekinlik paydo bo'lsa β o'lchang, sekin joyni toping, faqat o'shani tuzating.
Cache nima va Cache::remember¶
Tasavvur qiling, bosh sahifada "eng ko'p sotilgan 10 mahsulot" ko'rsatasiz. Buni hisoblash uchun barcha buyurtma qatorlarini guruhlab, sanab, saralash kerak β og'ir so'rov. Lekin bu ro'yxat har sekundda o'zgarmaydi: bir soatda bir marta yangilansa ham yetadi.
Demak, har tashrifda qayta hisoblash β isrof. Bir marta hisoblab, natijani tez joyga (xotira yoki fayl) qo'yib, keyingi so'rovlarda o'sha tayyor natijani qaytarsak β ulkan tejamkorlik. Mana shu cache.
Laravel'da eng ko'p ishlatiladigan metod β Cache::remember:
use Illuminate\Support\Facades\Cache;
use App\Models\Mahsulot;
$top = Cache::remember('top_mahsulotlar', 3600, function () {
return Mahsulot::query()
->withCount('buyurtmaQatorlari')
->orderByDesc('buyurtma_qatorlari_count')
->take(10)
->get();
});
remember uch narsa oladi: kalit ('top_mahsulotlar'), necha sekund saqlash (3600 = 1 soat), va og'ir ishni bajaradigan closure. Mantiq:
- Cache'da bu kalit bor bo'lsa (hit) β closure umuman ishlamaydi, saqlangan natija darrov qaytadi.
- Cache'da bu kalit yo'q bo'lsa (miss) β closure ishlaydi, natijasi cache'ga yoziladi va qaytariladi.
Ya'ni og'ir so'rov soatda bir marta ishlaydi, qolgan minglab tashrif tayyor natijani oladi. Closure ichidagi kod faqat miss bo'lganda ishga tushadi β bu juda muhim: cache to'la bo'lsa, bazaga umuman borilmaydi.
π‘ Kalit nomi noyob va ma'noli bo'lsin. Ma'lumotga bog'liq cache'da odatda parametrni kalitga qo'shamiz: bitta foydalanuvchi profili uchun "user_{$id}_profil", sahifa bo'yicha "mahsulotlar_sahifa_{$page}". Aks holda har xil ma'lumotni bitta kalit ostiga aralashtirib yuborasiz.
Cache'ning boshqa metodlari: get, put, has, forget, flush¶
remember β eng qulay yorliq, lekin ostida oddiy "yoz/o'qi" metodlar turibdi:
// Yozish (3-argument β sekundlar; bermasangiz "abadiy"ga yaqin):
Cache::put('valyuta_kursi', 12650, 600); // 10 daqiqa saqla
// O'qish (yo'q bo'lsa null yoki 2-argumentdagi default):
$kurs = Cache::get('valyuta_kursi'); // null bo'lishi mumkin
$kurs = Cache::get('valyuta_kursi', 12000); // yo'q bo'lsa 12000
// Bormi?
if (Cache::has('valyuta_kursi')) {
// ...
}
// Bittasini o'chirish:
Cache::forget('valyuta_kursi');
// Abadiy saqlash (muddatsiz) va keyin majburan tozalash:
Cache::forever('sayt_sozlamalari', $sozlamalar);
π Cache::remember aslida shu uchtaning birikmasi: has bilan tekshiradi, bo'lsa get, bo'lmasa closure'ni ishga tushirib put qiladi. Ya'ni quyidagi ikki blok bir xil ishni qiladi β lekin remember toza va xatosiz:
// β Qo'lda β uzun va xatoga moyil:
if (Cache::has('top_mahsulotlar')) {
$top = Cache::get('top_mahsulotlar');
} else {
$top = Mahsulot::query()->take(10)->get();
Cache::put('top_mahsulotlar', $top, 3600);
}
// β
remember bilan β bir qatorda, xuddi shu mantiq:
$top = Cache::remember('top_mahsulotlar', 3600, fn () => Mahsulot::take(10)->get());
π‘ Hech qachon eskirmasligi kerak bo'lmagan, lekin ma'lumot o'zgarganda yangilansin desangiz β Cache::rememberForever('kalit', $closure) ishlating va ma'lumot o'zgarganda Cache::forget('kalit') qiling. Buni keyinroq "cache'ni qachon tozalash" bo'limida ko'ramiz.
Cache invalidatsiya β eng qiyin qism¶
Dasturlashda mashhur hazil bor: "Kompyuter fanidagi ikki qiyin narsa β cache invalidatsiya va nomlarni tanlash". Cache'ning xavfi shu: ma'lumot o'zgardi, lekin cache eski qiymatni saqlab turibdi. Mahsulot narxini o'zgartirdingiz, lekin bosh sahifa hali eskisini ko'rsatyapti.
Yechim: ma'lumotni o'zgartirgan joyda cache'ni ham yangilang. Eng oson β bog'liq kalitni forget qilish:
public function update(MahsulotYangilashRequest $request, Mahsulot $mahsulot)
{
$mahsulot->update($request->validated());
// Narx o'zgardi β top ro'yxat cache'i endi eski. Tozalaymiz:
Cache::forget('top_mahsulotlar');
return redirect()->route('mahsulotlar.show', $mahsulot);
}
π Eng sof yondashuv β cache'ni Model eventiga bog'lash, shunda qaysi joydan o'zgartirsangiz ham cache avtomatik tozalanadi. Buni Mahsulot modelida booted orqali qilish mumkin:
protected static function booted(): void
{
static::saved(fn () => Cache::forget('top_mahsulotlar'));
static::deleted(fn () => Cache::forget('top_mahsulotlar'));
}
π‘ Hamma narsani bir zarbada tozalash β Cache::flush(). Lekin ehtiyot bo'ling: u butun cache'ni (boshqa kalitlarni ham) o'chiradi. Faqat zarur bo'lganda (masalan, deploy paytida) ishlating, oddiy update'da emas.
Cache driverlari: file, database, Redis¶
Cache'ni qayerga saqlash kerak? Buni driver belgilaydi. .envda bitta qator:
| Driver | Qayerda saqlaydi | Qachon |
|---|---|---|
file |
storage/framework/cache ichidagi fayllar |
Lokal ishlash, kichik bitta serverli sayt. Default. |
database |
cache jadvali |
Redis yo'q bo'lsa, sodda joyda saqlash kerak bo'lsa |
redis |
Redis serveri (xotirada) | Production, ko'p server, eng tez. Tavsiya etiladi. |
array |
Joriy so'rov xotirasida (so'rov tugashi bilan yo'qoladi) | Faqat test uchun |
database driveri uchun avval cache jadvali kerak. Laravel'da tayyor migratsiya bor:
π array driveri testlarda juda qulay: har test boshida cache toza bo'ladi, fayl/Redis kerak emas. phpunit.xml yoki .env.testingda CACHE_STORE=array qo'ying.
π‘ Eng tez variant β Redis. U cache'ni diskka emas, operativ xotiraga saqlaydi va shu sababli o'qish/yozish chaqmoqday tez. Ko'p serverli ilovada esa file driver umuman yaramaydi (har server o'z faylini ko'radi), Redis esa hammaga umumiy bo'ladi. Redis'ni bob oxirida ulaymiz.
Asosiy sekinlik manbai: N+1 muammosi (takror)¶
Ko'p hollarda sayt sekinligining asosiy aybdori β cache yetishmasligi emas, balki bitta sahifa yuzlab keraksiz so'rov yuborishi. Bu N+1 muammosi. 9- va 10-boblarda ko'rgan edik, lekin u shu qadar muhimki, performance bobida takror eslatmasdan bo'lmaydi.
Buyurtmalar ro'yxatini, har birining mijozi bilan ko'rsatamiz:
{{-- β Blade'da har buyurtma uchun mijoz alohida so'raladi: --}}
@foreach ($buyurtmalar as $buyurtma)
<li>#{{ $buyurtma->id }} β {{ $buyurtma->mijoz->ism }}</li>
@endforeach
$buyurtma->mijoz birinchi marta chaqirilganda Laravel bazaga SELECT * FROM mijozlar WHERE id = ? so'rovini yuboradi. Sikl 100 marta aylanadi β demak 100 ta qo'shimcha so'rov, ustiga boshidagi 1 ta. Jami 101 so'rov. 1000 buyurtmada 1001 so'rov!
Yechim β eager loading: bog'lanishni oldindan, with() bilan yuklash:
// β
with() bilan β mijozlar oldindan, bitta so'rovda yuklanadi:
$buyurtmalar = Buyurtma::with('mijoz')->latest()->take(100)->get();
Endi Laravel atigi 2 so'rov yuboradi: biri buyurtmalar uchun, ikkinchisi SELECT * FROM mijozlar WHERE id IN (...) β barcha kerakli mijozni bir zarbada. 100 ham, 10 000 ham bo'lsin β doim 2 so'rov.
π Bir nechta munosabatni birga yuklash mumkin, ichma-ich ham:
// Buyurtma + mijozi + har buyurtmaning qatorlari + ularning mahsuloti:
$buyurtmalar = Buyurtma::with(['mijoz', 'qatorlar.mahsulot'])->get();
π‘ Faqat sanog'i kerak bo'lsa β butun munosabatni yuklamang, withCount ishlating. "Har mijozning nechta buyurtmasi bor" uchun mijozlarning hamma buyurtmasini yuklash isrof:
$mijozlar = Mijoz::withCount('buyurtmalar')->get();
// endi $mijoz->buyurtmalar_count mavjud β qo'shimcha so'rovsiz
N+1'ni dasturlashda ushlash: preventLazyLoading¶
N+1'ning yomon tomoni β u jim ishlaydi. Sahifa to'g'ri ko'rinadi, faqat sekin. Kichik ma'lumotda umuman sezmaysiz, productionda ma'lumot o'sgach portlaydi. Buni qanday oldindan ushlash mumkin?
Laravel'da ajoyib himoya bor: lazy loading'ni butunlay taqiqlash. Agar kodingiz oldindan yuklamagan munosabatga murojaat qilsa β Laravel jim so'rov yubormaydi, balki xato tashlaydi. Shunda N+1'ni darhol ko'rasiz.
Buni AppServiceProviderning boot metodida yoqamiz:
use Illuminate\Database\Eloquent\Model;
public function boot(): void
{
Model::preventLazyLoading(! $this->app->isProduction());
}
! isProduction() β ya'ni faqat lokal/test muhitda yoqiladi, productionda yoqilmaydi (real foydalanuvchiga xato chiqmasligi uchun). Endi with()siz munosabatga tegsangiz, lokal ishlashda darrov xato olasiz:
Illuminate\Database\LazyLoadingViolationException:
Attempted to lazy load [mijoz] on model [App\Models\Buyurtma]
but lazy loading is disabled.
π Bu xato β sizning do'stingiz. U "shu yerda with('mijoz') qo'yishni unutding" deb to'g'ridan-to'g'ri aytadi. Dasturlash bosqichida ushlangan N+1 β productionda sekinlikka aylanmaydi.
π‘ Ba'zan munosabatni vaqtincha qo'lda yuklash kerak bo'ladi (oldindan yuklamagan bo'lsangiz) β load(). Bu N+1 emas, chunki butun to'plamga bir marta ishlaydi:
$buyurtmalar = Buyurtma::latest()->get();
// ... shartga qarab kerak bo'lsa:
$buyurtmalar->load('mijoz'); // hammasiga bitta so'rov, N+1 emas
Database indeks β sekin so'rovni tezlashtirish¶
Cache va eager loading yetmaganda, keyingi qadam β bazaning o'zini tezlashtirish. Eng kuchli vosita β indeks.
Tasavvur qiling, 120 000 qatorli buyurtmalar jadvalida WHERE mijoz_id = 5 qidiryapsiz. Indekssiz baza har bir qatorni ko'zdan kechiradi (full table scan) β 120 000 tekshiruv. Indeks esa kitobning orqasidagi alifbo ko'rsatkichiga o'xshaydi: bazaga mijoz_id = 5ni darrov topishga yordam beradi, hamma qatorni ko'rmasdan.
Qoida: WHERE, ORDER BY yoki JOINda tez-tez ishlatadigan ustunga indeks qo'ying. Foreign key ustunlari (mijoz_id, mahsulot_id) deyarli har doim indeks talab qiladi.
Migratsiyada indeksni shunday qo'shamiz:
public function up(): void
{
Schema::table('buyurtmalar', function (Blueprint $table) {
$table->index('mijoz_id'); // oddiy indeks
$table->index(['holat', 'created_at']); // birikma indeks
});
}
π foreignId('mijoz_id')->constrained() bilan ustun yaratganingizda Laravel FK uchun indeksni avtomatik qo'shadi β alohida index() shart emas. Lekin oddiy unsignedBigInteger('mijoz_id') bilan qo'lda yaratsangiz, indeksni o'zingiz qo'shasiz.
π‘ Indeks tekin emas: u o'qishni tezlatadi, lekin har INSERT/UPDATEda yangilanishi kerak β ya'ni yozishni biroz sekinlashtiradi va joy egallaydi. Shuning uchun "har ehtimolga qarshi hamma ustunga indeks" β xato. Faqat haqiqatan tez-tez qidiriladigan ustunga qo'ying. Qaysi so'rov sekinligini bilish uchun keyingi bo'limdagi profil vositalaridan foydalaning.
Faqat kerakli ustunni oling: select¶
Default holatda Eloquent SELECT * qiladi β barcha ustunni. Agar jadvalda og'ir ustunlar bo'lsa (uzun matn, json, blob), lekin sizga faqat id va nomi kerak bo'lsa β qolganini tortib kelish isrof:
// β Hammasini oladi (og'ir tavsif ustuni ham keladi):
$mahsulotlar = Mahsulot::all();
// β
Faqat kerakligini:
$mahsulotlar = Mahsulot::select(['id', 'nomi', 'narx'])->get();
π Munosabat bilan ishlatganda ehtiyot bo'ling: selectda foreign key ustunini qoldirmang, aks holda munosabat ulanmaydi. Masalan mijoz munosabatini yuklamoqchi bo'lsangiz, mijoz_id ustuni selectda bo'lishi shart:
// mijoz munosabati uchun mijoz_id kerak:
Buyurtma::select(['id', 'mijoz_id', 'summa'])->with('mijoz')->get();
π‘ Faqat bitta ustun ro'yxati kerak bo'lsa β pluck undan ham yengil, butun model obyektini yasamaydi:
$nomlar = Mahsulot::pluck('nomi'); // ['Olma', 'Non', ...]
$narxlar = Mahsulot::pluck('narx', 'id'); // [1 => 5000, 2 => 8000, ...]
Katta ma'lumotni bo'lib qayta ishlash: chunk va lazy¶
Endi tasavvur qiling, 200 000 mahsulotning hammasiga biror amal qilish kerak (masalan, narxni qayta hisoblash yoki hisobot eksporti). Bunday yozish β falokat:
// β 200 000 modelni BIRDAN xotiraga yuklaydi β xotira tugaydi:
foreach (Mahsulot::all() as $mahsulot) {
// ...
}
all() butun jadvalni xotiraga oladi. 200 000 model obyekti β yuzlab megabayt RAM, "Allowed memory size exhausted" xatosi. Yechim β bo'laklab ishlash. chunk ma'lumotni N tadan olib keladi, har bo'lakdan keyin xotirani bo'shatadi:
// β
500 tadan oladi, har bo'lakni qayta ishlab keyingisiga o'tadi:
Mahsulot::chunk(500, function ($mahsulotlar) {
foreach ($mahsulotlar as $mahsulot) {
// har biri bilan ishlash
}
});
π Agar sikl ichida qayta ishlayotgan ustunni o'zgartirsangiz (masalan, holatni WHERE holat = 'yangi' bo'yicha filtrlay turib yangilasangiz), chunk qatorlarni o'tkazib yuborishi mumkin. Bunday holda chunkById ishlating β u id bo'yicha barqaror saralaydi va xavfsiz:
Mahsulot::where('holat', 'yangi')->chunkById(500, function ($mahsulotlar) {
foreach ($mahsulotlar as $mahsulot) {
$mahsulot->update(['holat' => 'korildi']);
}
});
π‘ Yana toza variant β lazy(). U LazyCollection qaytaradi: tashqaridan oddiy foreachday ko'rinadi, lekin ichida ma'lumotni bo'laklab oladi, butun jadvalni xotiraga olmaydi:
Pagination β butun ro'yxatni yuklamaslik¶
Foydalanuvchiga 50 000 buyurtmani bitta sahifada ko'rsatmaysiz β hech kim 50 000 qatorni skroll qilmaydi va brauzer ham qotadi. Faqat bir sahifalik (masalan 20 ta) ko'rsatib, qolganiga "keyingi sahifa" havolasi berasiz. Bu β pagination, va u performance uchun ham muhim: baza har safar faqat 20 qator oladi.
// Controllerda β get() emas, paginate():
$buyurtmalar = Buyurtma::with('mijoz')->latest()->paginate(20);
{{-- Blade'da ro'yxat + havolalar: --}}
@foreach ($buyurtmalar as $buyurtma)
<li>#{{ $buyurtma->id }} β {{ $buyurtma->mijoz->ism }}</li>
@endforeach
{{ $buyurtmalar->links() }}
paginate(20) orqada LIMIT 20 OFFSET ... qo'shadi β bazadan faqat 20 qator keladi, butun jadval emas. links() esa "1 2 3 ... Keyingi" tugmalarini chizadi.
π Juda katta jadvalda (millionlab qator) oddiy paginate "jami nechta sahifa" uchun COUNT(*) so'rovi yuboradi, bu ham sekin bo'lishi mumkin. Agar "jami soni" kerak bo'lmasa β simplePaginate(20) ishlating: u faqat "Oldingi/Keyingi" beradi, COUNT so'rovini qilmaydi, demak tezroq.
Productionda config, route va view cache¶
Yuqoridagilar β kodingizdagi sekinlikni tuzatadi. Endi deploy paytidagi bepul tezlik: Laravel productionda har so'rovda config fayllarni o'qiydi, route'larni tahlil qiladi, Blade shablonlarni PHP'ga aylantiradi. Bularni oldindan bir marta keshlab qo'yib, har so'rovda qayta o'qimaslik mumkin.
php artisan config:cache # barcha config'ni bitta faylga birlashtiradi
php artisan route:cache # route'larni keshlaydi
php artisan view:cache # Blade shablonlarni oldindan kompilyatsiya qiladi
php artisan event:cache # event-listener xaritasini keshlaydi
To'rttasini alohida yozish o'rniga bitta buyruq hammasini qiladi:
π Faqat productionda! config:cacheni lokal ishlashda qilsangiz, .envdagi o'zgarishlar "muzlab" qoladi: faylni o'zgartirasiz, lekin sayt eski qiymatni ko'rsatib sizni chalg'itadi. Lokal ishlashda bu keshlarni umuman yoqmang.
π‘ Keshlarni tozalash β teskari buyruqlar. Hammasini bir zarbada:
β config:cache yoqilgan bo'lsa, kodda env('BIROR_NARSA')ni to'g'ridan-to'g'ri ishlatmang β config keshlanganda env() null qaytaradi. Doim config('xizmat.kalit') orqali o'qing, .env qiymatini esa faqat config/*.php fayllari ichida env() bilan oling. Bu Laravel'ning qat'iy qoidasi.
// β Controllerda yoki modelda β config:cache bilan buziladi:
$key = env('TOLOV_KALIT');
// β
config/services.php ichida env() o'qiladi, kodda config() ishlatiladi:
$key = config('services.tolov.kalit');
Redis β cache, session va queue uchun tez¶
Productionda eng katta umumiy tezlik β Redisga o'tish. Redis β operativ xotirada ishlaydigan tez ma'lumot ombori. Uni uch joyda ishlatish mumkin: cache, session va queue. Hammasi diskga emas, xotiraga boradi β natijada chaqmoqday tez.
Avval PHP uchun Redis mijozi kerak. Laravel'ning sof PHP varianti β Predis paketi:
So'ng .envda driverlarni Redis'ga o'tkazasiz:
CACHE_STORE=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis
REDIS_CLIENT=predis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=null
Tamom β kodingizda hech narsa o'zgarmaydi. Cache::remember(...) bir xil, lekin endi natija Redis xotirasiga saqlanadi. Sessiya ham, navbatdagi job'lar ham (20-bobdan) Redis orqali ketadi.
π Nega bularning hammasi Redis'da? Cache β tez o'qish kerak. Session β ko'p serverli ilovada hamma server bir xil sessiyani ko'rishi kerak (file driver buni eplay olmaydi). Queue β database driver navbatni jadvaldan "so'rab-so'rab" oladi (polling), Redis esa ancha tez va yengil. Uchchalasi uchun Redis β to'g'ri tanlov.
π‘ Redis o'rnatilmagan bo'lsa, lokal ishlashda Docker bilan bir buyruqda ko'tarish mumkin: docker run -p 6379:6379 redis. Yoki Laravel Sail ishlatsangiz, Redis allaqachon konteynerlar orasida keladi.
So'rovlarni "ko'z bilan" ko'rish: Debugbar va Telescope¶
Bularning hammasini qildik β lekin qaysi sahifa nechta so'rov yuborayotganini qanday bilamiz? Taxmin qilmaymiz β o'lchaymiz. Ikki mashhur vosita bor.
Laravel Debugbar β sahifa pastida panel chiqaradi: nechta so'rov yuborildi, har biri qancha vaqt oldi, qaysi biri takrorlangan. N+1'ni darrov ko'rsatadi (bir xil so'rov 100 marta β qizil bayroq):
--dev muhim: bu faqat dasturlash vositasi, productionga hech qachon o'rnatilmasin.
Laravel Telescope β kuchliroq panel (/telescope sahifasi): har so'rov, har baza so'rovi, job, mail, xato β hammasini yozib boradi va ko'rsatadi:
π Telescope ham asosan lokal/staging uchun. Productionda yoqsangiz, faqat ruxsatli adminlarga ochiq qilib (TelescopeServiceProviderdagi gate) va ma'lumotni muntazam tozalab turing (telescope:prune) β aks holda jurnal jadvali shishadi.
π‘ Optimallashtirishning to'g'ri tartibi: (1) o'lchang (Debugbar/Telescope bilan sekin so'rovni toping) β (2) tuzating (N+1'ni with bilan, sekin so'rovni indeks bilan, takror hisobni cache bilan) β (3) qayta o'lchang (haqiqatan tezlashganiga ishonch hosil qiling). Taxminga emas, o'lchovga tayaning.
Yakuniy xulosa β tezlashtirish ketma-ketligi¶
Sayt sekinlashganda shu tartibda harakat qiling:
- O'lchang. Debugbar/Telescope bilan qaysi sahifa, qaysi so'rov sekinligini aniqlang. Taxmin qilmang.
- N+1'ni yo'q qiling. Eng ko'p uchraydigan muammo.
with()qo'ying,preventLazyLoadingbilan oldini oling. - Indeks qo'shing.
WHERE/ORDER BY/JOINdagi sekin ustunga migratsiyada indeks. - Ortiqcha ma'lumotni kesing.
selectbilan kerakli ustun,paginatebilan sahifalash, katta amalgachunk. - Takror og'ir ishni cache'lang.
Cache::remember, o'zgargandaforget. - Productionda
php artisan optimizeva imkon bo'lsa Redis.
π Eng muhim dars: optimallashtirish β bu "hamma joyga cache tashlash" emas. Bu o'lchash, sekin joyni topish va aniq tuzatish jarayoni. Toza, to'g'ri kod β birinchi; tezlik β o'lchovga asoslangan ikkinchi qadam.
Keyingi va oxirgi bobda hamma o'rganganlarimizni birlashtirib, ilovani real serverga deploy qilamiz va yakuniy loyihani yig'amiz.
23-bob mashqlari¶
Quyidagi mashqlarni o'zingiz bajaring. Aksariyati avvalgi boblardagi do'kon (mahsulotlar, mijozlar, buyurtmalar) loyihasida ishlaydi β agar hali ko'p ma'lumot yo'q bo'lsa, 11-bobdagi factory/seeder bilan minglab qator yarating, shunda sekinlikni va tezlikni o'z ko'zingiz bilan ko'rasiz.
Cache::rememberbilan bosh sahifadagi "eng so'nggi 10 mahsulot" ro'yxatini 10 daqiqaga keshlang. Closure ichigalogger('bazaga borildi')qo'ying va sahifani bir necha marta yangilab, log faqat birinchi marta yozilishini tasdiqlang.- Bitta valyuta kursini
Cache::putbilan 600 sekundga saqlang,Cache::getbilan o'qing,Cache::hasbilan borligini tekshiring, so'ngCache::forgetbilan o'chiring. Cache::rememberishlatadigan kodni faqatget/has/putbilan qo'lda qayta yozing, so'ng teskarisini β qo'lda yozilgannirememberga qisqartiring. Ikkalasi bir xil natija berishiga ishonch hosil qiling.- Mahsulot narxini o'zgartiradigan
updatemetodida tegishli cache kalitiniforgetqiling. Narxni o'zgartirgach, keshlangan ro'yxat darrov yangilanishini tekshiring. Mahsulotmodelidabooted()orqalisavedvadeletedeventlariga cache tozalashni bog'lang. Endi qaysi joydan o'zgartirsangiz ham cache yangilanishini sinab ko'ring..envdaCACHE_STOREnifiledandatabasega o'tkazing.make:cache-tablevamigrateqilib, cache'ningcachejadvalida saqlanishini tekshiring.- Buyurtmalar ro'yxatini har birining mijozi bilan,
with()siz ko'rsatadigan sahifa yozing. Debugbar yokiDB::getQueryLog()bilan yuborilgan so'rovlar sonini sanang. - Xuddi shu sahifaga
with('mijoz')qo'shing va so'rovlar soni 2 ga tushganini tasdiqlang. So'rovlar sonidagi farqni yozib qo'ying. - Ichma-ich munosabatni eager yuklang:
Buyurtma::with(['mijoz', 'qatorlar.mahsulot']). Blade'da hammasini ko'rsating va N+1 yo'qligini tekshiring. - "Har mijozning nechta buyurtmasi bor"ni avval munosabatni to'liq yuklab (yomon), so'ng
withCount('buyurtmalar')bilan (yaxshi) yozing. So'rovlar sonidagi farqni o'lchang. AppServiceProvider::bootdaModel::preventLazyLoading(! $this->app->isProduction())ni yoqing.with()siz munosabatga teging vaLazyLoadingViolationExceptionchiqishini ko'ring, so'ngwith()qo'shib tuzating.- Oldindan yuklamagan to'plamga
->load('mijoz')ishlatib, uning N+1 emasligini (butun to'plamga bitta so'rov) DB so'rov logi bilan tasdiqlang. buyurtmalarjadvaliningmijoz_idustuniga migratsiyadaindex()qo'shing.foreignId(...)->constrained()allaqachon indeks yaratishini va qo'lda yaratilgan FK indekssiz qolishini taqqoslang.- Ko'p ishlatiladigan ikki ustunli filtr uchun birikma indeks qo'shing:
$table->index(['holat', 'created_at']). Indeksdan oldin va keyin so'rov tezligini taxminan solishtiring. Mahsulot::all()niMahsulot::select(['id', 'nomi', 'narx'])->get()ga o'zgartiring. So'ngwith('kategoriya')qo'shgandakategoriya_idniselectga kiritishni unutib, munosabat ulanmay qolishini ataylab ko'ring va tuzating.- Faqat mahsulot nomlari ro'yxati kerak bo'lganda
pluck('nomi')ishlating; so'ngpluck('narx', 'id')bilanid => narxmassivini oling. - Katta jadvalga
Mahsulot::all()bilanforeachyozib, xotira muammosini his qiling (ko'p ma'lumotda), so'ng unichunk(500, ...)ga o'tkazing. - Sikl ichida filtrlangan ustunni yangilaydigan amalni
chunkbilan yozib, ba'zi qator o'tkazib yuborilishini ko'ring, so'ngchunkByIdga o'tkazib muammoni hal qiling. - Buyurtmalar ro'yxatiga
paginate(20)va Blade'da{{ $buyurtmalar->links() }}qo'shing; so'ng "jami soni" kerak bo'lmagan holatdasimplePaginate(20)ga o'tib,COUNTso'rovi yo'qolganini tekshiring. - Laravel Telescope'ni
--devbilan o'rnating,/telescopeochib eng sekin so'rovni toping va uni shu bobdagi usullardan biri (indeks,with,selectyoki cache) bilan tezlashtiring; oldin va keyingi vaqtni yozib qo'ying.