En hvað ef svo óþægileg tegund (langtíma OLAP álag á OLTP gagnagrunn) er enn til? Hvernig hreint virkan skiptiborð umkringdur löngum fyrirspurnum og ekki stíga á hrífu?
Að leggja út hrífuna
Fyrst skulum við ákveða hvert vandamálið sem við viljum leysa er og hvernig það getur komið upp.
Venjulega gerist þetta ástand á tiltölulega litlu borði, en þar sem það kemur fyrir miklar breytingar. Venjulega þetta eða öðruvísi metrar/upplag/einkunnir, þar sem UPDATE er oft keyrt, eða biðminni-röð til að vinna úr einhverjum stöðugum straumi atburða, þar sem skrár eru stöðugt INSERT/DELETE.
Við skulum reyna að endurskapa valkostinn með einkunnum:
CREATE TABLE tbl(k text PRIMARY KEY, v integer);
CREATE INDEX ON tbl(v DESC); -- по этому индексу будем строить рейтинг
INSERT INTO
tbl
SELECT
chr(ascii('a'::text) + i) k
, 0 v
FROM
generate_series(0, 25) i;
Og samhliða, í öðru sambandi, byrjar löng, löng beiðni, safna flóknum tölfræði, en hefur ekki áhrif á borðið okkar:
SELECT pg_sleep(10000);
Nú uppfærum við verðgildi eins af teljara mörgum, mörgum sinnum. Fyrir hreinleika tilraunarinnar skulum við gera þetta
DO $$
DECLARE
i integer;
tsb timestamp;
tse timestamp;
d double precision;
BEGIN
PERFORM dblink_connect('dbname=' || current_database() || ' port=' || current_setting('port'));
FOR i IN 1..10000 LOOP
tsb = clock_timestamp();
PERFORM dblink($e$UPDATE tbl SET v = v + 1 WHERE k = 'a';$e$);
tse = clock_timestamp();
IF i % 1000 = 0 THEN
d = (extract('epoch' from tse) - extract('epoch' from tsb)) * 1000;
RAISE NOTICE 'i = %, exectime = %', lpad(i::text, 5), lpad(d::text, 5);
END IF;
END LOOP;
PERFORM dblink_disconnect();
END;
$$ LANGUAGE plpgsql;
NOTICE: i = 1000, exectime = 0.524
NOTICE: i = 2000, exectime = 0.739
NOTICE: i = 3000, exectime = 1.188
NOTICE: i = 4000, exectime = 2.508
NOTICE: i = 5000, exectime = 1.791
NOTICE: i = 6000, exectime = 2.658
NOTICE: i = 7000, exectime = 2.318
NOTICE: i = 8000, exectime = 2.572
NOTICE: i = 9000, exectime = 2.929
NOTICE: i = 10000, exectime = 3.808
Hvað gerðist? Hvers vegna jafnvel fyrir einföldustu UPDATE á einni skrá framkvæmdartími styttist um 7 sinnum — frá 0.524ms til 3.808ms? Og einkunn okkar er að byggjast meira og hægar.
Þetta er allt MVCC að kenna.
Þetta snýst allt um
VACUUM VERBOSE tbl;
INFO: vacuuming "public.tbl"
INFO: "tbl": found 0 removable, 10026 nonremovable row versions in 45 out of 45 pages
DETAIL: 10000 dead row versions cannot be removed yet, oldest xmin: 597439602
Ó, það er ekkert að þrífa! Samhliða Rekstrarbeiðnin truflar okkur - þegar allt kemur til alls gæti hann einhvern tíma viljað snúa sér að þessum útgáfum (hvað ef?), og þær ættu að vera honum aðgengilegar. Og þess vegna mun jafnvel VACUUM FULL ekki hjálpa okkur.
„Hruna saman“ borðið
En við vitum fyrir víst að sú fyrirspurn þarf ekki töfluna okkar. Þess vegna munum við samt reyna að skila afköstum kerfisins í fullnægjandi mörk með því að útrýma öllu óþarfa af borðinu - að minnsta kosti "handvirkt", þar sem VACUUM gefst upp.
Til að gera það skýrara skulum við skoða dæmið um tilfelli biðminnistöflu. Það er, það er mikið flæði af INSERT/DELETE og stundum er borðið alveg tómt. En ef það er ekki tómt verðum við vista núverandi innihald þess.
#0: Að meta ástandið
Það er ljóst að þú getur reynt að gera eitthvað við töfluna jafnvel eftir hverja aðgerð, en það þýðir ekki mikið - viðhaldskostnaðurinn verður greinilega meiri en afköst markmiðsfyrirspurnanna.
Við skulum móta viðmiðin - „það er kominn tími til að bregðast við“ ef:
- VACUUM var hleypt af stokkunum fyrir nokkuð löngu síðan
Við gerum ráð fyrir miklu álagi, svo látum það vera 60 sekúndur frá síðasta [sjálfvirku]VACUUM. - líkamleg borðstærð er stærri en markmiðið
Við skulum skilgreina það sem tvöfaldan fjölda síðna (8KB blokkir) miðað við lágmarksstærð - 1 blk fyrir hrúgu + 1 blk fyrir hverja vísitölu - fyrir hugsanlega tómt borð. Ef við gerum ráð fyrir því að ákveðið magn af gögnum verði alltaf í biðminni „venjulega“, er sanngjarnt að fínstilla þessa formúlu.
Staðfestingarbeiðni
SELECT
relpages
, ((
SELECT
count(*)
FROM
pg_index
WHERE
indrelid = cl.oid
) + 1) << 13 size_norm -- тут правильнее делать * current_setting('block_size')::bigint, но кто меняет размер блока?..
, pg_total_relation_size(oid) size
, coalesce(extract('epoch' from (now() - greatest(
pg_stat_get_last_vacuum_time(oid)
, pg_stat_get_last_autovacuum_time(oid)
))), 1 << 30) vaclag
FROM
pg_class cl
WHERE
oid = $1::regclass -- tbl
LIMIT 1;
relpages | size_norm | size | vaclag
-------------------------------------------
0 | 24576 | 1105920 | 3392.484835
#1: Enn Tómarúm
Við getum ekki vitað fyrirfram hvort samhliða fyrirspurn trufli okkur verulega - nákvæmlega hversu margar færslur hafa orðið „úreltar“ síðan hún hófst. Þess vegna, þegar við ákveðum að vinna úr töflunni á einhvern hátt, ættum við í öllum tilvikum fyrst að framkvæma á henni VACUUM - ólíkt VACUUM FULL, truflar það ekki samhliða ferli sem vinna með les- og skrifa gögn.
Á sama tíma getur það strax hreinsað út flest það sem við viljum fjarlægja. Já, og síðari fyrirspurnir á þessari töflu munu fara til okkar eftir "hot cache", sem mun draga úr tímalengd þeirra - og þar af leiðandi heildartíma þess að loka fyrir aðra með þjónustuviðskiptum okkar.
#2: Er einhver heima?
Við skulum athuga hvort það sé eitthvað í töflunni:
TABLE tbl LIMIT 1;
Ef það er ekki ein skrá eftir, þá getum við sparað mikið í vinnslu með því einfaldlega að keyra
Það virkar eins og skilyrðislaus DELETE skipun fyrir hverja töflu, en er miklu hraðari þar sem hún skannar ekki töflurnar. Þar að auki losar það strax um pláss, svo það er engin þörf á að framkvæma VACUUM aðgerð á eftir.
Hvort þú þarft að endurstilla töfluraðarteljarann (RESTART IDENTITY) er þitt að ákveða.
#3: Allir - skiptast á!
Þar sem við vinnum í mjög samkeppnisumhverfi, á meðan við erum hér að athuga hvort engar færslur séu í töflunni, gæti einhver þegar skrifað eitthvað þar. Við ættum ekki að missa þessar upplýsingar, hvað svo? Það er rétt, við þurfum að passa að enginn geti skrifað það niður með vissu.
Til að gera þetta þurfum við að virkja Raðhæft-einangrun fyrir viðskiptin okkar (já, hér byrjum við viðskipti) og læstu borðinu „þétt“:
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
LOCK TABLE tbl IN ACCESS EXCLUSIVE MODE;
Þetta stig lokunar ræðst af aðgerðunum sem við viljum framkvæma á því.
#4: Hagsmunaárekstrar
Við komum hingað og viljum „læsa“ merkinu - hvað ef einhver væri virkur á því á því augnabliki, til dæmis að lesa úr því? Við munum „hanga“ og bíða eftir að þessi kubb verði gefin út og aðrir sem vilja lesa munu rekast á okkur...
Til að koma í veg fyrir að þetta gerist munum við „fórna okkur“ - ef við gátum ekki náð lás innan ákveðins (viðunandi stutts) tíma, þá fáum við undanþágu frá stöðinni, en við munum að minnsta kosti ekki trufla okkur of mikið af öðrum.
Til að gera þetta skaltu stilla lotubreytuna
SET statement_timeout = ...;LOCK TABLE ...;
Til þess að þurfa ekki að takast á við að endurheimta „gamla“ gildi breytunnar síðar notum við formið SETJA LOKALT, sem takmarkar umfang stillingarinnar við núverandi viðskipti.
Við munum að statement_timeout á við um allar síðari beiðnir þannig að viðskiptin geta ekki teygt sig upp í óviðunandi gildi ef það er mikið af gögnum í töflunni.
#5: Afritaðu gögn
Ef taflan er ekki alveg tóm verður að vista gögnin aftur með því að nota bráðabirgðatöflu:
CREATE TEMPORARY TABLE _tmp_swap ON COMMIT DROP AS TABLE tbl;
Undirskrift ON COMMIT DROP þýðir að á því augnabliki sem færslunni lýkur mun bráðabirgðataflan hætta að vera til og það er engin þörf á að eyða henni handvirkt í tengingarsamhenginu.
Þar sem við gerum ráð fyrir að það sé ekki mikið af „lifandi“ gögnum ætti þessi aðgerð að fara fram nokkuð hratt.
Jæja, það er allt! Ekki gleyma eftir að hafa lokið viðskiptum
Að setja saman lokahandritið
Við notum þetta „gervi-python“:
# собираем статистику с таблицы
stat <-
SELECT
relpages
, ((
SELECT
count(*)
FROM
pg_index
WHERE
indrelid = cl.oid
) + 1) << 13 size_norm
, pg_total_relation_size(oid) size
, coalesce(extract('epoch' from (now() - greatest(
pg_stat_get_last_vacuum_time(oid)
, pg_stat_get_last_autovacuum_time(oid)
))), 1 << 30) vaclag
FROM
pg_class cl
WHERE
oid = $1::regclass -- table_name
LIMIT 1;
# таблица больше целевого размера и VACUUM был давно
if stat.size > 2 * stat.size_norm and stat.vaclag is None or stat.vaclag > 60:
-> VACUUM %table;
try:
-> BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
# пытаемся захватить монопольную блокировку с предельным временем ожидания 1s
-> SET LOCAL statement_timeout = '1s'; SET LOCAL lock_timeout = '1s';
-> LOCK TABLE %table IN ACCESS EXCLUSIVE MODE;
# надо убедиться в пустоте таблицы внутри транзакции с блокировкой
row <- TABLE %table LIMIT 1;
# если в таблице нет ни одной "живой" записи - очищаем ее полностью, в противном случае - "перевставляем" все записи через временную таблицу
if row is None:
-> TRUNCATE TABLE %table RESTART IDENTITY;
else:
# создаем временную таблицу с данными таблицы-оригинала
-> CREATE TEMPORARY TABLE _tmp_swap ON COMMIT DROP AS TABLE %table;
# очищаем оригинал без сброса последовательности
-> TRUNCATE TABLE %table;
# вставляем все сохраненные во временной таблице данные обратно
-> INSERT INTO %table TABLE _tmp_swap;
-> COMMIT;
except Exception as e:
# если мы получили ошибку, но соединение все еще "живо" - словили таймаут
if not isinstance(e, InterfaceError):
-> ROLLBACK;
Er ekki hægt að afrita gögnin í annað sinn?Í grundvallaratriðum er það mögulegt ef horn borðsins sjálfs er ekki bundið við neina aðra starfsemi frá BL hlið eða FK frá DB hlið:
CREATE TABLE _swap_%table(LIKE %table INCLUDING ALL);
INSERT INTO _swap_%table TABLE %table;
DROP TABLE %table;
ALTER TABLE _swap_%table RENAME TO %table;
Við skulum keyra handritið á upprunatöflunni og athuga mælikvarðana:
VACUUM tbl;
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SET LOCAL statement_timeout = '1s'; SET LOCAL lock_timeout = '1s';
LOCK TABLE tbl IN ACCESS EXCLUSIVE MODE;
CREATE TEMPORARY TABLE _tmp_swap ON COMMIT DROP AS TABLE tbl;
TRUNCATE TABLE tbl;
INSERT INTO tbl TABLE _tmp_swap;
COMMIT;
relpages | size_norm | size | vaclag
-------------------------------------------
0 | 24576 | 49152 | 32.705771
Allt gekk upp! Taflan hefur dregist saman um 50 sinnum og allar uppfærslur ganga hratt aftur.
Heimild: www.habr.com