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ě.




