Mihai Surdeanu

Idei de optimizare a unui feature de paginare

Short story: Să spunem că lucrezi la o aplicație care expune niște date dintr-o bază de date, prin intermediul unei interfețe UI simple. Ți se cere să implementezi un feature de paginare a tuturor comenzilor făcute în sistem. Cu alte cuvinte, trebuie să implementezi un feature de paginare. Nu există niciun mecanism de caching la nivelul aplicației tale. Să zicem că soluția bazei de date este MySQL. Dar până la urmă nici nu contează cu ce bază de date relațională avem tangență.

Cum implementăm acest feature? Primul pas este să ne ducem la business și să întrebăm câte elemente se doresc a fi prezente pe o pagină. Business-ul cel mai probabil o să zică 10. De ce? Pentru că asta e valoarea cea mai folosită, în general, în practică.

Dacă este să ne gândim mai bine, toată complexitatea acestui feature se reduce la query-ul pe care îl vom executa în bază. De acolo luăm datele până la urmă… O primă idee ar fi ceva de genul, dacă dorim să luăm elementele de pe a treia pagina:

SELECT * FROM orders OFFSET 20 LIMIT 10

Interogarea de mai sus o putem interpreta astfel: sunt selectate 10 comenzi din tabela orders, asta după ce sunt sărite primele 20, care vor face parte din primele 2 pagini.

Dar care este problema cu această implementare? Atât timp cât sunt puține comenzi, nu este nicio problemă. Oare este o problemă atunci când avem de-a face cu milioane de comenzi? Ca să ne dăm seama că este o problemă de performanță, trebuie să cunoaștem foarte bine cum funcționează OFFSET-ul. El este problema…

De fiecare dată când query-ul de mai sus este executat, acesta determină un Full Table Scan (Sequential Scan). Asta dacă serverul MySQL nu are atât de multă memorie încât să stocheze totul în cache și să optimizeze interogarea. De dragul acestui articol să spunem că nu are foarte multă memorie la dispoziție și acest cache nu există. În acest caz, dacă valoarea OFFSET-ului este foarte mare, până să ajungă interogarea să îmi returneze cele 10 elemente din pagină, va trece ceva timp. De ce? Pentru că se va scana tabela până când vor fi sărite un număr de OFFSET elemente. În acest fel, dacă valoarea OFFSET-ului este de 20000, vor fi scanate 20010 elemente pentru a servi o pagină. Nu sună bine deloc!

Ce alte variante există? Am putea folosi un cursor pentru a simula o paginare. Adică ceva de genul:

SELECT * FROM orders WHERE id > 20 LIMIT 10

În acest caz, id-ul poate fi un AUTO_INCREMENT și chiar cheia primară a tabelei. Cum această coloană este indexată, baza de date va ști exact de unde să înceapă să extragă informația. Așadar, partea aceea de scanare a tabelei nu mai este necesară, timpul de execuție a SELECT-ului se va reduce considerabil.

Mihai

Pasionat de IT. Pasionat de viață. Pasionat de tot ceea ce înseamnă a face o viață mai bună, plină de înțelegere, ajutor reciproc și iubire de aproape.

1 comentariu

Arhive

Arhiva personală