PostgreSQL Antipatterns: navigācija pa reģistru

Å odien nebÅ«s nekādu sarežģītu gadÄ«jumu un muļķīgu algoritmu SQL. Viss bÅ«s ļoti vienkārÅ”i, Kapteiņa AcÄ«mredzamÄ«ba lÄ«menÄ« — darām notikumu reÄ£istra apskate sacensÄ«bām pa laiku.

Tātad Å”ajā datubāzē ir tabula events, un tai ir lauks ts — tieÅ”i tas pats laiks, pēc kura mēs vēlamies Å”os ierakstus kārtot parādÄ«t:

CREATE TABLE events(
  id
    serial
      PRIMARY KEY
, ts
    timestamp
, data
    json
);

CREATE INDEX ON events(ts DESC);

Ir skaidrs, ka ierakstu tur bÅ«s ne desmit, tādēļ mums kaut kādā veidā lappuÅ”u navigācija.

#0. «Я у мамы погроммист»

cur.execute("SELECT * FROM events;")
rows = cur.fetchall();
rows.sort(key=lambda row: row.ts, reverse=True);
limit = 26
print(rows[offset:offset+limit]);

PatiesÄ«bā tā nav pat joks — tas notiek reti, bet dažreiz sastopams brÄ«vā dabā. Dažreiz pēc darba ar ORM ir grÅ«ti pāriet uz "tieÅ”o" darbu ar SQL.

Bet pāriesim pie izplatītākām un mazāk acīmredzamām problēmām.

#1. OFFSET

SELECT
  ...
FROM
  events
ORDER BY
  ts DESC
LIMIT 26 OFFSET $1; -- 26 - ierakstu skaits lapā, $1 - lappuses sākums

Kāpēc Å”eit ir skaitlis 26? Tas ir aptuvenais ierakstu skaits, kas nepiecieÅ”ams viena ekrāna aizpildīŔanai. PrecÄ«zāk, 25 attēlotie ieraksti, plus 1, kas signalizē, ka tālāk izvēlē ir kaut kas cits, un ir jēga turpināt.

Protams, Å”o vērtÄ«bu varētu nepievienot jautājumam, bet nodot to kā parametru. Bet Å”ajā gadÄ«jumā PostgreSQL plānotājs nevarēs paļauties uz zināŔanām, ka ierakstu vajadzētu bÅ«t salÄ«dzinoÅ”i maz, — un bildētu neefektÄ«vu plānu.

Un kamēr lietojumprogrammas saskarnē notikumu reÄ£istra apskate tiek Ä«stenota kā pārslēgÅ”anās starp vizuālām "lappusēm", neviens ilgi nemanÄ«s neko aizdomÄ«gu. TieÅ”i lÄ«dz brÄ«dim, kad cīņā par ērtumu UI/UX nolemj pārveidot saskarni uz "bezgalÄ«go ritināŔanu" — tas ir, visi reÄ£istra ieraksti tiek parādÄ«ti vienā sarakstā, ko lietotājs var ritināt uz augÅ”u un uz leju.

Un, veicot kārtējo testēŔanu, jÅ«s sastopat ierakstu dublēŔanu reÄ£istrā. Kāpēc, ja tabulai ir normāls indekss (ts), uz kuru balstās jÅ«su pieprasÄ«jums?

TieÅ”i tāpēc, ka jÅ«s neesat ņēmuÅ”i vērā, ka ts nav unikāls atslēga Å”ajā tabulā. PatiesÄ«bā arÄ« tā vērtÄ«bas nav unikālas, kā jebkurā ā€œlaikÄā€ reālajos apstākļos — tāpēc viena un tā pati ieraksts divās blakus esoŔās pieprasÄ«jumos viegli ā€œlēcā€ no lapas uz lapu, pateicoties citam galÄ«gajam secÄ«bā tādas paÅ”as atslēgas vērtÄ«bas kārtÄ«bā.

PatiesÄ«bā Å”eit slēpjas vēl otra problēma, kuru pamanÄ«t ir daudz grÅ«tāk — daži ieraksti vispār netiks parādÄ«ti! Jo ā€œduplikātiā€ ieraksti aizņēma kāda cita vietu. Detalizētu skaidrojumu ar skaistām bildēm var izlasÄ«t Å”eit.

PaplaŔinām indeksu

ViltÄ«gs izstrādātājs saprot — jāpadara indeksa atslēga unikāla, un vienkārŔākais veids ir to paplaÅ”ināt ar iepriekÅ” noteiktu unikālu lauku, kā tam lieliski piemērots bÅ«s PK:

CREATE UNIQUE INDEX ON events(ts DESC, id DESC);

Un pieprasījums mutē:

SELECT
  ...
ORDER BY
  ts DESC, id DESC
LIMIT 26 OFFSET $1;

#2. ŠŸŠµŃ€ŠµŃ…Š¾Š“ на Ā«ŠŗŃƒŃ€ŃŠ¾Ń€Ń‹Ā»

Pēc kāda laika pie jums nāk DBA un ā€œpriecÄ“ā€, ka jÅ«su pieprasÄ«jumi brutāli noslogo serveri ar saviem milzÄ«gajiem OFFSET, un vispār, ir laiks pāriet uz navigāciju no pēdējā rādÄ«tā vērtÄ«ba. JÅ«su pieprasÄ«jums atkal mutē:

SELECT
  ...
WHERE
  (ts, id) < ($1, $2) -- pēdējās iegÅ«tās vērtÄ«bas iepriekŔējā solÄ«
ORDER BY
  ts DESC, id DESC
LIMIT 26;

Jūs atviegloti ieelpojat, līdz nebeidzās...

#3. Чистка инГексов

Tāpēc, ka kādu dienu jÅ«su DBA izlasÄ«ja rakstu par neefektÄ«vu indeksu meklēŔanu un saprata, ka ā€œne pēdējaisā€ timestamps — tas nav labi.Un atkal atnāca pie jums — tagad ar domu, ka Å”im indeksam tomēr vajadzētu atgriezties (ts DESC).

Bet ko darÄ«t ar sākotnējo problēmu ā€œlēcienaā€ ierakstiem starp lapām?.. Viss ir vienkārÅ”i — ir jāizvēlas bloki ar nefixētu ierakstu skaitu!

Vispār, kurÅ” mums aizliedz lasÄ«t ne ā€œprecÄ«zi 26ā€, bet ā€œne mazāk kā 26ā€? Piemēram, tā, lai nākamajā blokā bÅ«tu ieraksti ar noteikti citādām vērtÄ«bām ts — tad jau nebÅ«s arÄ« problēmu ar ā€œlēcieniemā€ ierakstiem starp blokiem!

Šādi to var panākt:

SELECT
  ...
WHERE
  ts = coalesce((
    SELECT
      ts
    FROM
      events
    WHERE
      ts < $1
    ORDER BY
      ts DESC
    LIMIT 1 OFFSET 25
  ), '-infinity')
ORDER BY
  ts DESC;

Kas Ŕeit vispār notiek?

  1. Izsoļojam par 25 ierakstiem ā€œuz lejuā€ un iegÅ«stam ā€œrobežuā€ vērtÄ«bu ts.
  2. Ja tur vairs nav neko, tad nomainām NULL vērtību uz -infinity.
  3. Izslēdzam visu vērtÄ«bu segmentu starp iegÅ«to vērtÄ«bu ts un nodoto no saskarnes parametru $1 (iepriekŔējo ā€œpēdējoā€ uzzÄ«mēto vērtÄ«bu).
  4. Ja, ja, blokam, kas atgriežas ar mazāk nekā 26 ierakstiem — tas ir pēdējais.

Vai tas paÅ”as parādās attēlā:
PostgreSQL Antipatterns: navigācija pa reģistru

Tagad mums ir paraugs nav noteikts "sākums", tāpēc nekas neļauj mums "pagriezt" Å”o pieprasÄ«jumu pretējā virzienā un realizēt dinamisko datu bloku ielādēŔanu no "atsauces punkta" abos virzienos — gan uz leju, gan uz augÅ”u.

Piezīme

  1. Jā, Ŕādā gadÄ«jumā mēs piekļūstam indeksam divreiz, taču viss "tÄ«ri pēc indeksa". Tāpēc iegultais pieprasÄ«jums radÄ«s tikai vienu papildu Index Only Scan.
  2. Pietiekami acÄ«mredzami, ka Ŕī metodika ir izmantojama tikai tad, ja jums ir vērtÄ«bas ts var pārsniegt tikai nejauÅ”i, un to ir maz. Ja jÅ«su tipisks gadÄ«jums ir "miljons ierakstu 00:00:00.000", tad tā nedrÄ«kst darÄ«t. Proti, nedrÄ«kst pieļaut Ŕādu gadÄ«jumu. Bet, ja tā nu ir sanācis, izmantojiet paplaÅ”inātā indikatora variantu.

Avots: habr.com

Iegādājieties uzticamu tÄ«mekļa hostingu ar DDoS aizsardzÄ«bu, VPS VDS serveriem šŸ”„ Iegādājieties uzticamu tÄ«mekļa hostingu ar DDoS aizsardzÄ«bu, VPS VDS serveriem | ProHoster