OpenStats - partitioning

Podobně jako v případě projektu PGMon, i při sběru statistik návštěvnosti mohou vznikat velké objemy dat, které však poměrně rychle zastarávají - ve většině případů není třeba pracovat s daty staršími než 1 měsíc. S rychlým nárůstem objemu dat jsou spojeny dva výrazné problémy - pokles výkonu dotaz a problémy při údržbě (zálohování a odmazání zastaralých dat).

Není překvapivé že stejně jako v případě projektu PGMon je elegantním řešením těchto problémů partitioning hlavních tabulek (Sessions, Actions a Parameters) partitioning. Zatímco v případě projektu PGMon bylo nasazení partitioningu poměrně jednoduchou záležitostí, v tomto případě je situace vzhledem k provázanosti hlavních tabulek cizími klíči výrazně složitější.

Partitioning závislých tabulek

Uvažujme dvě tabulky, provázané cizím klíčem:

CREATE TABLE A ( id INTEGER PRIMARY KEY );
CREATE TABLE B ( id INTEGER PRIMARY KEY, a_id INTEGER );

ALTER TABLE B ADD FOREIGN KEY (a_id) REFERENCES A (id);

Pokud bychom obě tabulky rozdělili dle parametru "id" (tj. A.id a B.id) na dvě části například takto:

CREATE TABLE A_1 ( CHECK id <  1000000 ) INHERITRS (A);
CREATE TABLE A_2 ( CHECK id >= 1000000 ) INHERITRS (A);

CREATE TABLE B_1 ( CHECK id <  1000000 ) INHERITRS (B);
CREATE TABLE B_2 ( CHECK id >= 1000000 ) INHERITRS (B);

CREATE RULE A_insert_1 AS ON INSERT TO A WHERE (id <  1000000)
                          DO INSTEAD INSERT INTO A_1 (NEW.*);
CREATE RULE A_insert_2 AS ON INSERT TO A WHERE (id >= 1000000)
                          DO INSTEAD INSERT INTO A_2 (NEW.*);

CREATE RULE B_insert_1 AS ON INSERT TO B WHERE (id <  1000000)
                          DO INSTEAD INSERT INTO B_1 (NEW.*);
CREATE RULE B_insert_2 AS ON INSERT TO B WHERE (id >= 1000000)
                          DO INSTEAD INSERT INTO B_2 (NEW.*);

budou sice vytvoženy "partitions" obou tabulek, ale nelze korektně definovat referenční integritu. Pro libovolný řádek v tabulce B_2 nelze odhadnout zda řádek tabulky "A" odkazovaný sloupcem "a_id" je umístěn v partition A_1 nebo A_2. Finta s navázáním referenční integrity z partitions B_1 a B_2 přímo na tabulku A bohužel nefunguje neboť při ověřování referenční integrity nejsou podřízené tabulky prohledávány.

Tento problém lze vyřešit třemi způsoby:

  • smířit se s neexistencí referenční integrity (pro mne nepřijatelné - na integritě dat mi záleží)
  • napsat si triggery které budou referenční integritu kontrolovat (velmi náročné na korektní implementaci a optimalizaci)
  • rozdělit tabulky "stejným způsobem" (dle stejných parametrů) tj. tak aby bylo možno navázat referenční integritu mezi jednotlivými partišnami

Nemusím asi zdůrazňovat že mým favoritem je třetí možnost - takové rozdělení na partitions aby bylo možno definovat referenční integritu přímo mezi těmito částmi. Například ve výše uvedeném příkladě s tabulkami A a B toto znamená rozdělení tabulky B dle sloupce B.a_id namísto B.id. Definice částí tabulky B by tedy měla být

CREATE TABLE B_1 ( CHECK a_id <  1000000 ) INHERITRS (B);
CREATE TABLE B_2 ( CHECK a_id >= 1000000 ) INHERITRS (B);

CREATE RULE B_insert_1 AS ON INSERT TO B WHERE (a_id <  1000000)
                          DO INSTEAD INSERT INTO B_1 (NEW.*);
CREATE RULE B_insert_2 AS ON INSERT TO B WHERE (a_id >= 1000000)
                          DO INSTEAD INSERT INTO B_2 (NEW.*);

Důsledkem této drobné úpravy je že obě tabulky jsou rozděleny "stejně" tj. je jisté že řádky v části B_1 odkazují pouze řádky v tabulce A_1, a řádky v tabulce B_2 odkazují pouze řádky v tabulce A_2. Lze tedy definovat následující cizí klíče:

ALTER TABLE B_1 ADD FOREIGN KEY (a_id) REFERENCES A_1 (id);
ALTER TABLE B_2 ADD FOREIGN KEY (a_id) REFERENCES A_2 (id);

Výše uvedený postup lze shrnout do následujícího pravidla:

Je-li tabulka B závislá na tabulce A, tj. sloupec B.a_id je cizím klíčem odkazujícím na sloupec A.id, potom je třeba partitioning obou tabulek třeba prováděd právě dle tohoto sloupce, tj. tabulka A musí být dělena dle sloupce A.id a tabulka B dle sloupce B.a_id.

Aplikace na tabulky Sessions, Actions a Parameters

Aplikujme nyní výše uvedené pravidlo na tabulky které jsou jádrem projektu OpenStats - Sessions, Actions a Parameters. Právě kvůli němu je sloupec Sessions.id (poněkud uměle) propagován až do tabulky Parameters.

Tabulku Sessions tedy rozdělme dle sloupce "id":

CREATE TABLE Sessions_1 ( CHECK id =< 100000 ) INHERITS (Sessions);
CREATE TABLE Sessions_2 ( CHECK id > 100000 AND id =< 200000 ) INHERITS (Sessions);
CREATE TABLE Sessions_3 ( CHECK id > 200000 AND id =< 300000 ) INHERITS (Sessions); 
...

CREATE RULE Sessions_insert_1 AS ON INSERT TO Sessions
                              WHERE (id =<  100000)
                                 DO INSTEAD INSERT INTO Sessions_1 (NEW.*);

CREATE RULE Sessions_insert_2 AS ON INSERT TO Sessions
                              WHERE (id =<  200000 AND id > 100000)
                                 DO INSTEAD INSERT INTO Sessions_1 (NEW.*);

CREATE RULE Sessions_insert_3 AS ON INSERT TO Sessions
                              WHERE (id =<  300000 AND id > 200000)
                                 DO INSTEAD INSERT INTO Sessions_1 (NEW.*);
...

Tabulku Actions rozdělme odpovídajícím dle sloupce "session_id":

CREATE TABLE Actions_1 ( CHECK session_id =< 100000 ) INHERITS (Actions);
CREATE TABLE Actions_2 ( CHECK session_id > 100000
                           AND session_id =< 200000 ) INHERITS (Actions);
CREATE TABLE Actions_3 ( CHECK session_id > 200000
                           AND session_id =< 300000 ) INHERITS (Actions); 
...

CREATE RULE Actions_insert_1 AS ON INSERT TO Actions
                    WHERE (session_id =<  100000)
                                 DO INSTEAD INSERT INTO Actions_1 (NEW.*);

CREATE RULE Actions_insert_2 AS ON INSERT TO Actions
                     WHERE (session_id =<  200000 AND session_id > 100000)
                                 DO INSTEAD INSERT INTO Actions_1 (NEW.*);

CREATE RULE Actions_insert_3 AS ON INSERT TO Actions
                     WHERE (session_id =<  300000 AND session_id > 200000)
                                 DO INSTEAD INSERT INTO Actions_1 (NEW.*);
...

No a konečně tabulku Parameters rozdělme stejným způsobem jako tabulku Actions:

CREATE TABLE Parameters_1 ( CHECK session_id =< 100000 ) INHERITS (Parameters);
CREATE TABLE Parameters_2 ( CHECK session_id > 100000
                           AND session_id =< 200000 ) INHERITS (Parameters);
CREATE TABLE Parameters_3 ( CHECK session_id > 200000
                           AND session_id =< 300000 ) INHERITS (Parameters);
...

CREATE RULE Parameters_insert_1 AS ON INSERT TO Parameters
                    WHERE (session_id =<  100000)
                                 DO INSTEAD INSERT INTO Parameters_1 (NEW.*);

CREATE RULE Parameters_insert_2 AS ON INSERT TO Parameters
                     WHERE (session_id =<  200000 AND session_id > 100000)
                                 DO INSTEAD INSERT INTO Parameters_1 (NEW.*);

CREATE RULE Parameters_insert_3 AS ON INSERT TO Parameter
                     WHERE (session_id =<  300000 AND session_id > 200000)
                                 DO INSTEAD INSERT INTO Parameters_1 (NEW.*);
...

Tím je partitioning víceméně hotov - je samozřejmě třeba průběžně vytvářet další partitions, ať již automaticky nebo ručně.

Komentáře

K tomuto článku zatím žádné komentáře neexistují (nebo čekají na schválení).

Nový komentář

Všechny komentáře podléhají schválení - mezi odesláním komentáře a jeho zobrazením na této stránce tedy může být prodleva. Vyplníte-li e-mailovou adresu, budete o schválení či neschválení komentáře informováni.

V titulku ani v textu nejsou povoleny HTML tagy - budou automaticky odstraněny. Odstavec ukončíte prázdným řádkem.

(nepovinné)