Како оптимизовати ПХП Ларавел веб апликацију за високе перформансе?

Ларавел је много ствари. Али брза није једна од њих. Хајде да научимо неке занатске трикове како бисмо убрзали!

Ниједан ПХП програмер није нетакнут Ларавел ових дана. Они су или млади програмери или програмери средњег нивоа који воле брзи развој који Ларавел нуди, или су старији програмери који су приморани да уче Ларавел због притиска тржишта.

У сваком случају, не може се порећи да је Ларавел ревитализовао ПХП екосистем (ја бих, сигурно, одавно напустио ПХП свет да Ларавел није био тамо).

Делић (донекле оправдане) самохвале од Ларавела

Међутим, пошто се Ларавел сагиње уназад да би вам олакшао ствари, то значи да испод њега ради тоне и тоне посла како би се осигурало да имате угодан живот као програмер. Све „магичне“ карактеристике Ларавел-а које само делују имају слојеве кода који треба да се додају сваки пут када се нека функција покрене. Чак и једноставан изузетак прати колико је дубока зечја рупа (запазите где почиње грешка, све до главног кернела):

За оно што изгледа као грешка у компилацији у једном од погледа, постоји 18 позива функција за праћење. Лично сам наишао на 40, а лако би могло бити и више ако користите друге библиотеке и додатке.

Поента је да, подразумевано, овај слој на слојевима кода чини Ларавел спорим.

Колико је Ларавел спор?

Искрено, немогуће је одговорити на ово питање из неколико разлога.

Прво, не постоји прихваћен, објективан, разуман стандард за мерење брзине веб апликација. Брже или спорије у поређењу са чим? Под којим условима?

Друго, веб апликација зависи од толико много ствари (база података, систем датотека, мрежа, кеш, итд.) да је једноставно глупо говорити о брзини. Веома брза веб апликација са веома спором базом података је веома спора веб апликација. 🙂

Али ова неизвесност је управо разлог зашто су мерила популарна. Иако не значе ништа (види ово и ово), пружају неки референтни оквир и помажу нам да не полудимо. Стога, са неколико прстохвата соли спремних, хајде да добијемо погрешну, грубу представу о брзини међу ПХП оквирима.

Идемо на овај прилично респектабилан ГитХуб изворево како се ПХП оквири пореде у поређењу:

Можда нећете ни приметити Ларавел овде (чак и ако јако жмирите) осим ако не баците свој случај до краја репа. Да, драги пријатељи, Ларавел долази последњи! Наравно, већина ових „оквира“ није баш практична или чак корисна, али нам говори колико је Ларавел тром у поређењу са другим популарнијим.

Обично се ова „спорост“ не појављује у апликацијама јер наше свакодневне веб апликације ретко достижу високе бројеве. Али када то ураде (рецимо, више од 200-500 истовремено), сервери почињу да се гуше и умиру. Време је када чак ни бацање више хардвера на проблем не решава проблем, а рачуни за инфраструктуру расту тако брзо да се ваши високи идеали рачунарства у облаку срушавају.

Али хеј, разведри се! Овај чланак није о томе шта се не може учинити, већ о томе шта се може учинити. 🙂

Добра вест је да можете учинити много да ваша Ларавел апликација ради брже. Неколико пута брзо. Да, не шалим се. Можете учинити да иста кодна база постане балистичка и уштедите неколико стотина долара на рачунима за инфраструктуру/хостинг сваког месеца. Како? Хајдемо до тога.

Четири врсте оптимизације

По мом мишљењу, оптимизација се може извршити на четири различита нивоа (када су у питању ПХП апликације, тј.):

  • Ниво језика: Ово значи да користите бржу верзију језика и избегавате специфичне карактеристике/стилове кодирања на језику који чине ваш код спорим.
  • Ниво оквира: Ово су ствари које ћемо покрити у овом чланку.
  • Инфраструктурни ниво: Подешавање вашег ПХП менаџера процеса, веб сервера, базе података итд.
  • Хардверски ниво: Прелазак на бољег, бржег, моћнијег добављача хардверског хостинга.

Све ове врсте оптимизација имају своје место (на пример, ПХП-фпм оптимизација је прилично критична и моћна). Али фокус овог чланка биће оптимизације чисто типа 2: оне које се односе на оквир.

Узгред, нема образложења иза нумерације и то није прихваћен стандард. Управо сам ово измислио. Молим вас, немојте ме никада цитирати и рећи: „Треба нам оптимизација типа 3 на нашем серверу“, или ће вас вођа тима убити, пронаћи ме, а затим и мене. 😀

И сада, коначно, стижемо у обећану земљу.

Будите свесни н+1 упита базе података

Проблем са упитом н+1 је уобичајен када се користе ОРМ-ови. Ларавел има свој моћни ОРМ који се зове Елокуент, који је толико леп, тако згодан, да често заборављамо да погледамо шта се дешава.

  Како уметнути тачке у Екцел табелу

Размотрите веома уобичајен сценарио: приказивање листе свих поруџбина које је дала листа купаца. Ово је прилично уобичајено у системима е-трговине и свим интерфејсима за извештавање уопште где треба да прикажемо све ентитете који се односе на неке ентитете.

У Ларавелу можемо замислити функцију контролера која обавља посао овако:

class OrdersController extends Controller 
{
    // ... 

    public function getAllByCustomers(Request $request, array $ids) {
        $customers = Customer::findMany($ids);        
        $orders = collect(); // new collection
        
        foreach ($customers as $customer) {
            $orders = $orders->merge($customer->orders);
        }
        
        return view('admin.reports.orders', ['orders' => $orders]);
    }
}

Свеет! И што је још важније, елегантно, лепо. 🤩🤩

Нажалост, то је катастрофалан начин за писање кода у Ларавел-у.

Ево зашто.

Када тражимо од ОРМ-а да потражи дате купце, генерише се СКЛ упит попут овог:

SELECT * FROM customers WHERE id IN (22, 45, 34, . . .);

Што је тачно како се очекивало. Као резултат, сви враћени редови се чувају у колекцији $цустомерс унутар функције контролера.

Сада прелазимо преко сваког купца једног по једног и добијамо њихове поруџбине. Ово извршава следећи упит. . .

SELECT * FROM orders WHERE customer_id = 22;

. . . онолико пута колико има купаца.

Другим речима, ако треба да добијемо податке о поруџбини за 1000 купаца, укупан број извршених упита базе података биће 1 (за преузимање свих података купаца) + 1000 (за преузимање података о поруџбини за сваког купца) = 1001. Ово одакле долази име н+1.

Можемо ли боље? Сигурно! Коришћењем онога што је познато као нестрпљиво учитавање, можемо натерати ОРМ да изврши ЈОИН и врати све потребне податке у једном упиту! Овако:

$orders = Customer::findMany($ids)->with('orders')->get();

Добијена структура података је, наравно, угнежђена, али подаци о наруџбини се могу лако издвојити. Резултирајући појединачни упит, у овом случају, је отприлике овако:

SELECT * FROM customers INNER JOIN orders ON customers.id = orders.customer_id WHERE customers.id IN (22, 45, . . .);

Један упит је, наравно, бољи од хиљаду додатних упита. Замислите шта би се десило када би било 10.000 купаца за обраду! Или не дај Боже да бисмо и ми хтели да прикажемо ставке садржане у свакој наруџбини! Запамтите, назив технике је жељно учитавање и скоро увек је добра идеја.

Кеширајте конфигурацију!

Један од разлога за Ларавелову флексибилност је мноштво конфигурационих датотека које су део оквира. Желите да промените како/где се слике чувају?

Па, само промените датотеку цонфиг/филесистемс.пхп (барем у тренутку писања). Желите да радите са више драјвера у реду чекања? Слободно их опишите у цонфиг/куеуе.пхп. Управо сам пребројао и открио да постоји 13 конфигурационих датотека за различите аспекте оквира, осигуравајући да нећете бити разочарани без обзира шта желите да промените.

С обзиром на природу ПХП-а, сваки пут када дође нови веб захтев, Ларавел се буди, покреће све и анализира све ове конфигурационе датотеке да би открио како да ствари уради другачије овог пута. Осим што је глупо ако се ништа није променило последњих дана! Поновна израда конфигурације на сваки захтев је губитак који се може (у ствари, мора) избећи, а излаз је једноставна команда коју Ларавел нуди:

php artisan config:cache

Оно што ово ради је да комбинује све доступне конфигурационе датотеке у једну и кеш је негде за брзо преузимање. Следећи пут када буде веб захтева, Ларавел ће једноставно прочитати овај појединачни фајл и кренути.

Међутим, кеширање конфигурације је изузетно деликатна операција која вам може експлодирати. Највећа невоља је да када једном издате ову команду, енв() функција позива свуда осим конфигурационих датотека ће вратити нулл!

Има смисла када размислите о томе. Ако користите кеширање конфигурације, говорите оквиру: „Знате шта, мислим да сам добро поставио ствари и 100% сам сигуран да не желим да се мењају.“ Другим речима, очекујете да окружење остане статичко, чему служе .енв датотеке.

Уз то речено, ево неких гвоздених, светих, нераскидивих правила кеширања конфигурације:

  • Урадите то само на производном систему.
  • Урадите то само ако сте заиста, заиста сигурни да желите да замрзнете конфигурацију.
  • У случају да нешто крене наопако, поништите подешавање помоћу пхп артисан цацхе:цлеар
  • Молите се да штета учињена послу није значајна!
  • Смањите услуге које се аутоматски учитавају

    Да би био од помоћи, Ларавел учитава гомилу услуга када се пробуди. Они су доступни у датотеци цонфиг/апп.пхп као део кључа поља ‘провидерс’. Хајде да погледамо шта имам у мом случају:

    /*
        |--------------------------------------------------------------------------
        | Autoloaded Service Providers
        |--------------------------------------------------------------------------
        |
        | The service providers listed here will be automatically loaded on the
        | request to your application. Feel free to add your own services to
        | this array to grant expanded functionality to your applications.
        |
        */
    
        'providers' => [
    
            /*
             * Laravel Framework Service Providers...
             */
            IlluminateAuthAuthServiceProvider::class,
            IlluminateBroadcastingBroadcastServiceProvider::class,
            IlluminateBusBusServiceProvider::class,
            IlluminateCacheCacheServiceProvider::class,
            IlluminateFoundationProvidersConsoleSupportServiceProvider::class,
            IlluminateCookieCookieServiceProvider::class,
            IlluminateDatabaseDatabaseServiceProvider::class,
            IlluminateEncryptionEncryptionServiceProvider::class,
            IlluminateFilesystemFilesystemServiceProvider::class,
            IlluminateFoundationProvidersFoundationServiceProvider::class,
            IlluminateHashingHashServiceProvider::class,
            IlluminateMailMailServiceProvider::class,
            IlluminateNotificationsNotificationServiceProvider::class,
            IlluminatePaginationPaginationServiceProvider::class,
            IlluminatePipelinePipelineServiceProvider::class,
            IlluminateQueueQueueServiceProvider::class,
            IlluminateRedisRedisServiceProvider::class,
            IlluminateAuthPasswordsPasswordResetServiceProvider::class,
            IlluminateSessionSessionServiceProvider::class,
            IlluminateTranslationTranslationServiceProvider::class,
            IlluminateValidationValidationServiceProvider::class,
            IlluminateViewViewServiceProvider::class,
    
            /*
             * Package Service Providers...
             */
    
            /*
             * Application Service Providers...
             */
            AppProvidersAppServiceProvider::class,
            AppProvidersAuthServiceProvider::class,
            // AppProvidersBroadcastServiceProvider::class,
            AppProvidersEventServiceProvider::class,
            AppProvidersRouteServiceProvider::class,
    
        ],

    Још једном сам пребројао, а на листи је 27 услуга! Сада, можда ће вам требати све, али је мало вероватно.

      Како користити претраживач Пале Моон на Линуку

    На пример, тренутно правим РЕСТ АПИ, што значи да ми не треба добављач услуга сесије, добављач услуга прегледа итд. И пошто радим неколико ствари на свој начин и не пратим подразумеване поставке оквира , такође могу да онемогућим добављача услуге аутентификације, добављача услуге пагинације, добављача услуге превођења и тако даље. Све у свему, скоро половина њих је непотребна за мој случај употребе.

    Погледајте дуго, пажљиво своју апликацију. Да ли су му потребни сви ови провајдери услуга? Али за име бога, немојте слепо коментарисати ове услуге и гурајте се у производњу! Покрените све тестове, проверите ствари ручно на машинама за развој и постављање и будите веома параноични пре него што повучете окидач. 🙂

    Будите мудри са стековима средњег софтвера

    Када вам је потребна прилагођена обрада долазног веб захтева, решење је креирање новог међувера. Сада је примамљиво да отворите апп/Хттп/Кернел.пхп и убаците средњи софтвер у веб или АПИ стек; на тај начин постаје доступан у целој апликацији и ако не ради нешто наметљиво (као што је евидентирање или обавештавање, на пример).

    Међутим, како апликација расте, ова колекција глобалног међувера може постати тихо оптерећење за апликацију ако су сви (или већина) присутни у сваком захтеву, чак и ако за то нема пословног разлога.

    Другим речима, пазите где додајете/примените нови међуверски софтвер. Може бити згодније додати нешто глобално, али је казна перформанси веома висока на дужи рок. Знам колико бисте морали да трпите ако бисте селективно применили средњи софтвер сваки пут када дође до нове промене, али то је бол који бих радо прихватио и препоручио!

    Избегавајте ОРМ (повремено)

    Иако Елокуент многе аспекте ДБ интеракције чини пријатним, то долази по цену брзине. Будући да је мапер, ОРМ не само да мора да преузима записе из базе података, већ и да инстанцира објекте модела и да их хидрира (попуни) подацима колоне.

    Дакле, ако урадите једноставан $усерс = Усер::алл() и постоји, рецимо, 10.000 корисника, оквир ће преузети 10.000 редова из базе података и интерно урадити 10.000 нових Усер() и испунити њихова својства релевантним подацима . Ово је огроман посао који се обавља иза кулиса, и ако база података тамо где се налазите постаје уско грло, понекад је добра идеја заобићи ОРМ.

    Ово посебно важи за сложене СКЛ упите, где бисте морали да прескачете много обруча и пишете затварања по затварањима, а да ипак завршите са ефикасним упитом. У таквим случајевима, пожељно је извршити ДБ::рав() и ручно написати упит.

    Иде по ово студија перформанси, чак и за једноставне уметке Елокуент је много спорији како број записа расте:

    Користите кеширање што је више могуће

    Једна од најбоље чуваних тајни оптимизације веб апликација је кеширање.

    За неупућене, кеширање значи претходно израчунавање и чување скупих резултата (скупо у смислу употребе ЦПУ-а и меморије) и једноставно враћање када се исти упит понови.

    На пример, у продавници е-трговине може се наићи на 2 милиона производа, већину времена људи су заинтересовани за оне који су свеже залихе, унутар одређеног распона цена и за одређену старосну групу. Упити у базу података за ове информације је расипнички – пошто се упит не мења често, боље је да ове резултате ускладиштите негде где можемо брзо да приступимо.

    Ларавел има уграђену подршку за неколико типова кеширање. Поред коришћења драјвера за кеширање и изградње система за кеширање од самог почетка, можда ћете желети да користите неке Ларавел пакете који олакшавају кеширање модела, кеширање упитаитд.

    Али имајте на уму да осим одређеног поједностављеног случаја употребе, унапред изграђени пакети за кеширање могу изазвати више проблема него што их решавају.

    Преферирајте кеширање у меморији

    Када нешто кеширате у Ларавел-у, имате неколико опција где да ускладиштите резултујуће израчунавање које треба кеширати. Ове опције су такође познате као кеш драјвери. Дакле, иако је могуће и савршено разумно користити систем датотека за чување резултата кеша, то заправо није оно што кеширање треба да буде.

    У идеалном случају, желите да користите кеш меморију (који у потпуности живи у РАМ-у) као што је Редис, Мемцацхед, МонгоДБ, итд., тако да под већим оптерећењима, кеширање служи виталној употреби, а не да само по себи постаје уско грло.

    Можда мислите да је поседовање ССД диска скоро исто као и коришћење РАМ стицка, али није ни близу. Чак и неформално мерила показују да РАМ надмашује ССД 10-20 пута када је у питању брзина.

    Мој омиљени систем када је у питању кеширање је Редис. Његово смешно брзо (100.000 операција читања у секунди је уобичајено), а за веома велике кеш системе, може се развити у кластер лако.

    Кеширајте руте

    Баш као и конфигурација апликације, руте се не мењају много током времена и идеалан су кандидат за кеширање. Ово је посебно тачно ако не подносите велике фајлове као ја и на крају поделите своје веб.пхп и апи.пхп на неколико фајлова. Једна Ларавел команда пакује све доступне руте и држи их при руци за будући приступ:

    php artisan route:cache

    А када на крају додате или промените руте, једноставно урадите:

    php artisan route:clear

    Оптимизација слике и ЦДН

    Слике су срце и душа већине веб апликација. Случајно, они су и највећи потрошачи пропусног опсега и један од највећих разлога за споре апликације/веб локације. Ако једноставно ускладиштите отпремљене слике наивно на сервер и пошаљете их назад у ХТТП одговорима, пропуштате велику прилику за оптимизацију.

      Поправите Мицрософт продавница која не инсталира апликације

    Моја прва препорука је да не складиштите слике локално — постоји проблем губитка података који треба решити, а у зависности од тога у ком географском региону се налази ваш клијент, пренос података може бити болно спор.

    Уместо тога, потражите решење као што је Цлоудинари који аутоматски мења величину и оптимизује слике у ходу.

    Ако то није могуће, користите нешто попут Цлоудфларе-а за кеширање и сервирање слика док су ускладиштене на вашем серверу.

    А ако чак ни то није могуће, мало подесити софтвер вашег веб сервера да компримује средства и усмери претраживач посетиоца да кешује ствари, чини велику разлику. Ево како би исечак Нгинк конфигурације изгледао:

    server {
    
       # file truncated
        
        # gzip compression settings
        gzip on;
        gzip_comp_level 5;
        gzip_min_length 256;
        gzip_proxied any;
        gzip_vary on;
    
       # browser cache control
       location ~* .(ico|css|js|gif|jpeg|jpg|png|woff|ttf|otf|svg|woff2|eot)$ {
             expires 1d;
             access_log off;
             add_header Pragma public;
             add_header Cache-Control "public, max-age=86400";
        }
    }

    Свестан сам да оптимизација слике нема никакве везе са Ларавел-ом, али то је тако једноставан и моћан трик (и који се често занемарује) да си нисам могао помоћи.

    Оптимизација аутолоадера

    Аутоматско учитавање је згодна, не тако стара функција у ПХП-у која је вероватно спасила језик од пропасти. Међутим, процес проналажења и учитавања релевантне класе дешифровањем датог низа простора имена захтева време и може се избећи у производним применама где су високе перформансе пожељне. Још једном, Ларавел има решење са једном командом за ово:

    composer install --optimize-autoloader --no-dev

    Дружите се са редовима

    Редови су начин на који обрађујете ствари када их има много, а за сваку од њих је потребно неколико милисекунди да се заврши. Добар пример је слање е-поште — широко распрострањен случај употребе у веб апликацијама је слање неколико порука е-поште са обавештењима када корисник изврши неке радње.

    На пример, у новом производу, можда бисте желели да руководство компаније (око 6-7 адреса е-поште) буде обавештено кад год неко изврши поруџбину изнад одређене вредности. Под претпоставком да ваш мрежни пролаз за е-пошту може да одговори на ваш СМТП захтев за 500 мс, говоримо о добрих 3-4 секунде чекања за корисника пре него што почне потврда поруџбине. Заиста лош део корисничког искуства, сигуран сам да ћете договорити се.

    Лек је да складиштите послове чим стигну, кажете кориснику да је све прошло добро и обрадите их (неколико секунди) касније. Ако постоји грешка, послови у реду чекања могу се поново покушати неколико пута пре него што се прогласи неуспелим.

    Заслуге: Мицрософт.цом

    Док систем чекања мало компликује подешавање (и додаје неке додатне трошкове надгледања), он је неопходан у модерној веб апликацији.

    Оптимизација средстава (Ларавел микс)

    За све фронт-енд средства у вашој Ларавел апликацији, уверите се да постоји цевовод који компајлира и минимизира све датотеке средстава. Они који су задовољни системом пакета као што је Вебпацк, Гулп, Парцел, итд., не морају да се труде, али ако то већ не радите, Ларавел Мик је солидна препорука.

    Мик је лаган (и диван, искрено!) омот око Вебпацк-а који води рачуна о свим вашим ЦСС, САСС, ЈС, итд. фајловима за производњу. Типична .мик.јс датотека може бити тако мала и још увек чини чуда:

    const mix = require('laravel-mix');
    
    mix.js('resources/js/app.js', 'public/js')
        .sass('resources/sass/app.scss', 'public/css');

    Ово аутоматски води рачуна о увозу, минификацији, оптимизацији и целом послу када сте спремни за производњу и покренете нпм рун продуцтион. Мик брине не само о традиционалним ЈС и ЦСС датотекама, већ ио Вуе и Реацт компонентама које можда имате у току рада апликације.

    Више информација овде!

    Закључак

    Оптимизација перформанси је више уметност него наука – важно је знати како и колико радити него шта треба да се ради. Уз то, нема краја колико и шта све можете да оптимизујете у Ларавел апликацији.

    Али шта год да урадите, желео бих да вам дам савет за растанак — оптимизацију треба обавити када постоји солидан разлог, а не зато што звучи добро или зато што сте параноични у погледу перформанси апликације за 100.000+ корисника док сте у стварности има само 10.

    Ако нисте сигурни да ли треба да оптимизујете своју апликацију или не, не морате да разбијате пословично гнездо стршљена. Радна апликација која делује досадно, али ради управо оно што мора, десет пута је пожељнија од апликације која је оптимизована у мутантну хибридну супермашину, али с времена на време падне у воду.

    А да би новајлија постао мајстор Ларавела, погледајте ово онлајн курс.

    Нека ваше апликације раде много, много брже! 🙂