dimhold.by
← Артыкулы

Другая старонка паўтарыла радок з першай, і нічога пры гэтым не зламалася

Мне паведамілі, што пры гартанні спіса адна і тая ж картка паказалася двойчы. У логах нічога, запыт звычайны, таму я патраціў паўдня на пошук памылкі ў праграме. Памылка ў самім спосабе гартаць, і ніякай драмы з канкурэнтнасцю для яе не трэба: хапае адной устаўкі.

Стэнд гэта табліца на мільён радкоў з індэксам па ключы сартавання, старонкі па 20. Першая старонка вяртае ідэнтыфікатары з 1 па 20. Далей прыходзіць 1 радок, які па парадку становіцца раней за ўсіх, а гэта звычайны выпадак, калі нехта заднім чыслом дадае запіс. І далей запытваецца другая старонка звычайным спосабам.

першая старонка была з 1 па 20, потым уставілі радок перад усімі старонка 2 праз OFFSET 20 21 22 23 ... 38 39 20 ужо была на першай старонка 2 па ключы 21 22 23 24 ... 39 40 нічога не паўторана OFFSET лічыць пазіцыі ў спісе, які пад ім памяняўся ключ лічыць ад радка, які чытач сапраўды бачыў апошнім радкоў паказана двойчы: 1 праз OFFSET, 0 па ключы
Абодва запыты даюць слушны адказ на тое пытанне, якое ім задалі. OFFSET просіць радкі з 21 па 40 спіса, якім ён стаў зараз. Чытач жа глядзеў на спіс, якім ён быў імгненне таму.

Старонка праз OFFSET пачынаецца з 20, то бок з радка, які чытач ужо бачыў. Радок 40 не з’яўляецца ўвогуле. Старонка па ключы вяртае з 21 па 40. Вінаватага няма: запыт з OFFSET сумленна аддае радкі з 21 па 40 бягучага парадку, а бягучы парадак на 1 радок даўжэйшы ў пачатку, чым быў, калі малявалася першая старонка.

Менавіта гэта і з’ела мае паўдня. Я шукаў гонку, слой кэшавання, няслушнае сартаванне. Шукаць няма чаго. У самой схемы ёсць уласцівасць: усё, што ўставілі або выдалілі наперадзе тваёй пазіцыі, зрушвае ўсё, што ззаду. Чытач, які гартае павольна, бачыць паўторы пасля ўставак і моўчкі прапускае радкі пасля выдаленняў.

Хуткасць, пра якую звычайна і кажуць замест гэтага

медыяна з 5 прагонаў, 1 старонка ў 20 радкоў, мілісекунды 0 35 70 OFFSET: 69.485 па ключы: 1.519 на той жа глыбіні 0 1000 10000 100000 500000 900000 на якой глыбіні стаіць старонка
Лініі перасякаюцца недзе каля 10000 радкоў глыбіні. Ніжэй за гэта OFFSET хутчэйшы, і якраз таму звычка жыве.

У пачатку спіса запыт з OFFSET аказваецца хутчэйшым: 1.483 мілісекунды супраць 1.774. Гэта варта сказаць уголас, бо менавіта таму ніхто нічога не мяняе: на тых старонках, якія людзі рэальна адкрываюць, просты варыянт выйграе.

На глыбіні 100000 ён каштуе ўжо 10.8 мілісекунды, а на 900000 каштуе 69.5, пры тым што запыт па ключы так і не зрушыўся з 1.5. План тлумачыць гэта адным радком: каб вярнуць 20 радкоў, база чытае 900020 і выкідвае ўсё, што ідзе да зрушэння. Запыт па ключы чытае роўна 20.

Абыход усёй табліцы робіць форму відавочнай. Старонкамі па 20 іх выходзіць 50000, і версія з OFFSET на кожнай наступнай чытае на 20 радкоў больш, чым на папярэдняй. У суме гэта каля 25 мільярдаў прачытаных радкоў на табліцы ў мільён. Версія па ключы чытае па 20 радкоў на старонку і заканчвае на мільёне. Гэта арыфметыка, а не замер, і гэта роўна тая ж арыфметыка, якую план паказаў на адной старонцы: цана старонкі роўная яе пазіцыі.

Выгрузка даных зробленая менавіта так. Яна праходзіць усе старонкі па разе, ветліва, цыклам, які нехта напісаў за паўдня. І ператварае абыход табліцы ў квадратычны, прычым ніводны радок таго кода не выглядае няслушным.

Чаго я не правяраў

Гартанню па ключы патрэбны ўнікальны давершнік. У мяне гэта першасны ключ побач з адзнакай часу. Без яго радкі з аднолькавым значэннем сартавання лягуць па абодва бакі мяжы старонкі. Гэтага я не мераў, хоць чакаю, што паўтор адтворыцца іншым спосабам.

Люстэрка гэтай памылкі, то бок выдаленне наперадзе пазіцыі, з-за якога радок моўчкі знікне, я не праганяў, а разважыў. І ў гартання па ключы ёсць сапраўдная цана: перайсці адразу на 57 старонку нельга, толькі наперад і назад. Значыць любы інтэрфейс з нумараванымі старонкамі сам просіць тую схему, якая памыляецца ў ліку.

Вузкае сцвярджэнне пра тое, што чыніць першым. Аргумент пра хуткасць тычыцца глыбіняў, да якіх большасць праграм не даходзіць. Аргумент пра карэктнасць працуе на другой старонцы.