Găsirea vinovatului utilizării ridicate a procesorului în serverul nostru WordPress

Publicat: 2020-05-07

În urmă cu câteva zile, am primit un e-mail de la furnizorul nostru de găzduire (SiteGround) prin care ne anunță că site-ul nostru avea deja „90% din utilizarea lunară permisă a procesorului” și că, odată ce depășim 100%, „serviciul web ar fi limitat” și este posibil să avem „probleme la accesarea acestuia”. Destul de înfricoșător, nu? Cu siguranță a fost o situație total nedorită pe care a trebuit să o reparăm cât mai curând posibil. Dar... de unde să încep?

Statistici site-ului nostru în timpul episodului de utilizare ridicată a procesorului
Statistici site-ului nostru în timpul episodului de utilizare ridicată a procesorului.

Astăzi am vrut să vă împărtășim experiența noastră cu o problemă destul de comună pe site-uri web, explicând ce am făcut pentru a identifica vinovatul și cum am rezolvat problema. În acest fel, dacă vă confruntați cu o problemă similară, veți avea câteva idei despre cum să începeți...

Motive pentru care puteți avea o utilizare mare a procesorului

WordPress este un sistem de management al conținutului scris în PHP. Aceasta înseamnă că conținutul pe care îl servește este generat dinamic de un set de scripturi PHP: de fiecare dată când un vizitator ajunge pe site-ul tău web, WordPress procesează cererea (care este ceva de genul „te rog trimite-mi pagina ta de pornire”) și generează un răspuns (în în acest caz, trimite pagina de pornire). În mod clar, răspunsul la o solicitare implică o anumită utilizare a resurselor serverului: trebuie să se uite la cererea în sine, să stabilească ce dorește vizitatorul să acceseze, să o preia din baza de date, să genereze răspunsul HTML și așa mai departe.

Unul dintre motivele pentru care un sistem cache accelerează timpul de încărcare a site-ului dvs. ar trebui să fie destul de evident acum: practic economisește acest timp de procesare. Când o anumită solicitare sosește pentru prima dată („trimite-mi pagina ta de pornire”), WordPress pornește și generează răspunsul. Dacă există un cache, acesta stochează răspunsul înainte de a fi trimis efectiv vizitatorului. În acest fel, cererile viitoare către aceeași resursă (în exemplul nostru, o pagină de pornire) nu mai necesită ca WordPress să proceseze nimic; memoria cache poate trimite înapoi copia salvată anterior, economisind astfel timp și resurse.

Având în vedere această performanță, nu este dificil să ne imaginăm care sunt motivele pentru care putem vedea o utilizare ridicată a CPU pe serverul nostru:

  • Primești prea multe cereri. Dacă o mulțime de utilizatori vin pe site-ul dvs. în același timp, sau primiți multe solicitări nelegitime (probabil că cineva vă atacă serverul), WordPress va trebui să proceseze toate acele solicitări și, prin urmare, utilizarea resurselor serverului va crește.
  • Solicitările sunt lent de rezolvat. Dacă aveți o mulțime de pluginuri instalate sau unele dintre acestea sunt ineficiente din orice motiv, toate solicitările pe care le primiți vor dura mai mult decât este necesar, deoarece WordPress va rula mult cod ineficient.

Deci, se pare că un cache este o bună protecție împotriva acestor probleme, nu? Și într-adevăr este. Cu toate acestea, rețineți că memoria cache nu „rezolvă” problema; pur și simplu o „ascunde”. Și acest lucru este important de reținut, deoarece există funcționalități WordPress care nu pot fi stocate în cache și, prin urmare, vor necesita întotdeauna rularea WordPress:

  • Sarcini programate folosind WP-Cron. WP-Cron este un mecanism WordPress pentru programarea sarcinilor care vor rula în viitor. De exemplu, WordPress îl folosește pentru a publica postări programate.
  • API-ul REST al WordPress. API-ul REST este o interfață pe care o puteți utiliza din aplicații terțe pentru a interacționa cu un site WordPress prin trimiterea și primirea de obiecte JSON. Unele solicitări API REST pot fi stocate în cache (și anume, solicitările GET), dar altele nu sunt (POST și PUT). Prin urmare, este ceva pe care în general nu îl puteți stoca în cache...
  • Solicitări AJAX în WordPress. Înainte de a avea API-ul REST în WordPress, a trebuit să folosim API-ul AJAX pentru a crea site-uri web dinamice. Acest API este destul de similar cu API-ul REST, deoarece îl putem folosi și pentru a trimite și a primi informații de la server. Este un sistem diferit, dar este supus acelorași limitări ca și API-ul REST.

Analizând problema

Mai întâi trebuie să identificăm de ce utilizarea procesorului a crescut pe site-ul nostru web. A crescut numărul de solicitări către site-ul nostru? Este acum mai lent pentru a servi cererile individuale? Pentru a răspunde la aceste întrebări avem pe serverul nostru un instrument foarte util: jurnalul de acces .

Jurnalul de acces este un fișier text în care serverul înregistrează fiecare cerere pe care o primește împreună cu informații utile despre acestea. Mai exact, jurnalul de acces ne spune când a fost primită o solicitare (data și oră), cine a făcut-o (un IP), ce resursă a solicitat (o adresă URL), dacă cererea a avut succes etc. Iată un exemplu de pe serverul nostru :

 66.249.83.82 - - [22/Apr/2020:14:04:59 +0200] "GET /es/blog/imagenes-gratuitas-para-tu-blog/ HTTP/1.0" 200 22325 "-" "Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 5 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko; googleweblight) Chrome/38.0.1025.166 Mobile Safari/535.19" 66.249.83.84 - - [22/Apr/2020:14:05:02 +0200] "GET /es/wp-content/uploads/sites/3/2018/07/aziz-acharki-549137-unsplash-540x350.jpg HTTP/1.0" 200 10566 "https://neliosoftware.com/es/blog/imagenes-gratuitas-para-tu-blog/" "Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 5 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko; googleweblight) Chrome/38.0.1025.166 Mobile Safari/535.19" 66.249.83.82 - - [22/Apr/2020:14:05:02 +0200] "GET /es/wp-content/uploads/sites/3/2018/07/Screenshot-of-Unsplah-website-768x520.png HTTP/1.0" 200 399577 "https://neliosoftware.com/es/blog/imagenes-gratuitas-para-tu-blog/" "Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 5 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko; googleweblight) Chrome/38.0.1025.166 Mobile Safari/535.19" ... 188.79.17.218 - - [22/Apr/2020:14:06:14 +0200] "GET /es/blog/problemas-mas-comunes-de-wordpress/ HTTP/1.0" 200 110741 "https://www.google.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.2 Safari/605.1.15" 188.79.17.218 - - [22/Apr/2020:14:06:16 +0200] "GET /es/wp-content/plugins/nelio-ab-testing/assets/dist/js/alternative-loader.js?version=52b0ff65c68ab39896d47d6ff673fd59 HTTP/1.0" 200 2763 "https://neliosoftware.com/es/blog/problemas-mas-comunes-de-wordpress/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.2 Safari/605.1.15" 188.79.17.218 - - [22/Apr/2020:14:06:16 +0200] "GET /es/wp-includes/css/dist/block-library/style.min.css?ver=5.4 HTTP/1.0" 200 7627 "https://neliosoftware.com/es/blog/problemas-mas-comunes-de-wordpress/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.2 Safari/605.1.15" ...

Să aruncăm o privire mai atentă la el:

  • 66.249.83.82 . Acesta este IP-ul dispozitivului care a făcut cererea.
  • 22/Apr/2020:14:04:59 +0200 . Aceasta este data și ora exactă a cererii.
  • GET /es/blog/imagenes-gratuitas-para-tu-blog/ HTTP/1.0 . Apoi vedem că vizitatorul a solicitat ( GET ) o postare de pe blogul nostru (în spaniolă).
  • Mozilla/5.0 (Linux; Android ... Acesta este User-Agent al browserului și ne oferă câteva informații despre dispozitivul și sistemul de operare care a făcut solicitarea.

Observați cum serverul nostru a primit mai multe solicitări de la același IP ( 66.249.83.82 ) după prima. Acesta ar putea părea un atac, dar de fapt nu este: paginile web includ de obicei mai multe elemente (imagini, scripturi, stiluri) și este complet normal ca un vizitator care accesează o anumită pagină web a site-ului dvs. să efectueze mai multe solicitări pentru a le recupera pe toate. .

Ei bine, în cazul nostru am putut să verificăm că, într-adevăr, am avut un număr neobișnuit de mare de solicitări. Extrem de sus, de fapt. Și știam asta pentru că fișierul jurnal era mult mai mare decât de obicei.

O posibilă explicație ar fi că a existat un vârf de vizite din anumite motive... dar conform Google Analytics, nu a fost cazul. Deci ceva diferit se întâmpla.

O analiză mai detaliată a jurnalului de acces ne-a permis să identificăm următorul fapt: peste 15% din toate solicitările pe care le primim proveneau de la aceeași IP. Și (rularea tobei) acel IP era propriul nostru server web!

Gif care arată un bărbat care are o idee grozavă

Identificarea vinovatului

În acest moment, am știut în sfârșit că propriul nostru server a fost cel care a făcut atât de multe solicitări încât a generat un vârf în utilizarea procesorului său. Dar de ce? De ce sa întâmplat asta? Cine genera acele cereri? Acestea sunt întrebări mai dificile de răspuns.

Mai întâi ne-am uitat din nou la jurnalele noastre, filtrănd după IP și încercând să identificăm un model care ar putea arunca puțină lumină asupra problemei în cauză:

 35.214.244.124 - - [22/Apr/2020:14:06:08 +0200] "GET /es HTTP/1.0" 301 - "https://neliosoftware.com/es" "php-requests/1.7-3470169" 35.214.244.124 - - [22/Apr/2020:14:06:08 +0200] "GET /es?..." "php-requests/1.7-3470169" 35.214.244.124 - - [22/Apr/2020:14:06:18 +0200] "GET /es?..." "php-requests/1.7-3470169" 35.214.244.124 - - [22/Apr/2020:14:07:21 +0200] "GET /es?..." "php-requests/1.7-3470169" 35.214.244.124 - - [22/Apr/2020:14:07:24 +0200] "GET /es?..." "php-requests/1.7-3470169" ...

și am găsit unul: User-Agent al tuturor acestor solicitări anormale a fost php-requests/1.7-3470169 . Interesant!

WordPress are mai multe funcții cu care să declanșeze cereri: wp_remote_request . Dacă aruncați o privire la codul sursă al acestor funcții, veți vedea că ele înglobează, practic, câteva metode dintr-o clasă numită WP_Http . Această clasă este interesantă deoarece, implicit, toate solicitările pe care le face setează User-Agent la „WordPress/versiune”, așa că era posibil ca acest lucru să aibă ceva de-a face cu problema noastră. Dar nu... încă.

Dacă continuăm să inspectăm codul sursă al WordPress, vedem că WP_Http folosește o altă clasă WordPress în interior pentru a face efectiv solicitările: Requests . Și băiat este această clasă interesantă: la începutul definiției ei vedem că definește o constantă numită VERSION a cărei valoare este 1.7-3470169 . Și puțin mai târziu, folosește această constantă pentru a construi User-Agent pe care l-am găsit în jurnalele noastre: php-requests/1.7-3470169 .

Sclipitor! Am confirmat acum că toate aceste solicitări ciudate pe care le primim provin de la site-ul nostru WordPress. Acest lucru înseamnă probabil că vinovatul este un plugin... dar care?

Ideea pe care am avut-o să ne dăm seama a fost destul de simplă: dacă modificăm User-Agent astfel încât să includă numele pluginului care folosește clasa Requests , vom vedea numele pluginului în jurnalul de acces al serverului nostru. Și acest lucru este de fapt destul de ușor de realizat. Tot ce am făcut a fost să edităm funcția get_default_options din Requests cu următorul fragment:

 $trace = debug_backtrace(); $files = []; foreach ( $trace as $log ) { if ( false !== strpos( $log['file'], '/wp-content/plugins/' ) ) { $files[] = $log['file']; } } if ( empty( $files ) ) { $debug = 'no-plugin'; } else { $plugins = array_map( function( $x ) { return preg_replace( '/.*\/wp-content\/plugins\/([^\/]+)\/.*/', '$1', $x ); }, $files ); $plugins = array_unique( $plugins ); $debug = implode( ' ', $plugins ); } $defaults['useragent'] .= " (NelioDebug {$debug})";

Să vedem ce face pas cu pas:

  • Mai întâi obținem stiva de execuție cu debug_backtrace . Aceasta este o funcție PHP care generează un backtrace, dezvăluind toate funcțiile care au fost apelate pentru a ajunge la cea curentă.
  • Pentru fiecare element din stiva de execuție avem informații precum funcția care a fost invocată, fișierul și linia în care este definită, argumentele cu care a fost apelat etc. Pe ce vrem să ne concentrăm, totuși, este fișier în care a fost definită funcția: dacă este în wp-content/plugins , știm sigur că este o funcție definită într-un plugin.
  • Odată ce am procesat toate elementele din stivă, trebuie pur și simplu să obținem numele (dacă există) ale tuturor pluginurilor pe care le-am găsit și să le includem în variabila useragent .

După ce am extins WordPress așa cum este descris, am început rapid să vedem cine a fost vinovat :

 35.214.244.124 - - [23/Apr/2020:10:59:08 +0200] "GET /es HTTP/1.0" 301 - "https://neliosoftware.com/es" "php-requests/1.7-3470169 (NelioDebug culprit)" 35.214.244.124 - - [23/Apr/2020:10:59:20 +0200] "GET /?..." "php-requests/1.7-3470169 (NelioDebug culprit)" 35.214.244.124 - - [23/Apr/2020:10:59:36 +0200] "GET /?..." "php-requests/1.7-3470169 (NelioDebug culprit)" 35.214.244.124 - - [23/Apr/2020:11:00:01 +0200] "GET /es?..." "php-requests/1.7-3470169 (NelioDebug culprit)" 35.214.244.124 - - [23/Apr/2020:11:05:21 +0200] "GET /es?..." "php-requests/1.7-3470169 (NelioDebug culprit)" ...

Rezolvarea problemei

Odată ce am știut numele pluginului ofensator, am contactat pur și simplu dezvoltatorul acestuia și am cerut ajutor. Ei au răspuns și ne-au spus cum să depășim problema în timp ce lucrează la o soluție adecvată. Din fericire pentru noi, lucrurile s-au îmbunătățit rapid:

Statistici de pe site-ul nostru după corectarea unei probleme cu utilizarea procesorului
Statistici de pe site-ul nostru după ce ai cerut ajutor.

După cum puteți vedea, unul dintre lucrurile grozave despre software-ul liber este că putem explora codul sursă al aplicațiilor pe care le folosim și îl putem adapta nevoilor noastre. În acest caz, am reușit să modificăm WordPress în sine, astfel încât să poată dezvălui unele informații de care aveam nevoie.

Sper că v-a plăcut trucul de a folosi debug_backtrace pentru a afla cine execută o anumită funcție. Sigur, nu este o metodă ortodoxă, dar se implementează rapid și, până acum, s-a dovedit întotdeauna a fi extrem de utilă.

Imagine prezentată de Michal Mancewicz pe Unsplash.