AdvancedTopics: partitioning.sql

File partitioning.sql, 9.0 KB (added by 231136, 5 days ago)
Line 
1-- напредна тема 1: партиционирање на song_streams по streamed_at
2
3-- 1. pravime nova shema za arhiviranite particii
4
5-- idealno ova bi bilo cuvano vo nekoj poevtin storage bidejki sakame da gi cuvame podatocite,
6-- no nema cesto da gi pristapuvame
7CREATE SCHEMA IF NOT EXISTS archive;
8
9
10-- 2. transakcija za migriranje na song_streams vo particionirana tabela
11BEGIN;
12
13
14-- 2.1 prvo pravime detach na sekvencata za posle da mozeme da ja prikacime kon novata tabela,
15-- inaku drop table bi ja izbrisala
16ALTER SEQUENCE song_streams_id_seq OWNED BY NONE;
17
18-- 2.2a se uste ne ja drop-nuvame tabelata bidejki ke ni treba za proverka na kraj. samo ja preimenuvame
19ALTER TABLE song_streams RENAME TO song_streams_old;
20
21-- 2.2b gi preimenuvame indeksite bidejki ovie iminja za indeksite ke ni trebaat za indeksite na novata relacija
22ALTER INDEX song_streams_pkey RENAME TO song_streams_old_pkey;
23ALTER INDEX idx_song_streams_streamed_at_song_id RENAME TO song_streams_old_streamed_at_song_id;
24ALTER INDEX idx_song_streams_user_id RENAME TO song_streams_old_user_id;
25
26
27
28-- 2.3 pravime nova particionirana parent tabela. istite koloni no so slozen PK koj sto go sodrzi i
29-- particioniot kluc (streamed_at) - ova e requirement od samiot postgres
30CREATE TABLE song_streams (
31 id bigint NOT NULL DEFAULT nextval('song_streams_id_seq'),
32 playback_session_id bigint NOT NULL,
33 song_id bigint NOT NULL,
34 streamed_at timestamp without time zone NOT NULL,
35 user_id bigint NOT NULL,
36
37 PRIMARY KEY (id, streamed_at),
38
39 FOREIGN KEY (playback_session_id) REFERENCES playback_sessions(id) ON DELETE CASCADE,
40 FOREIGN KEY (song_id) REFERENCES songs(id)
41) PARTITION BY RANGE (streamed_at);
42
43-- 2.4 ja prikacuvame sekvencata od prethodno
44ALTER SEQUENCE song_streams_id_seq OWNED BY song_streams.id;
45
46-- 2.5 gi rekreirame indeksite na parent-ot
47-- postgres avtomatski ke gi kreira i menadzira indeksite za site particii
48CREATE INDEX idx_song_streams_streamed_at_song_id ON song_streams (streamed_at, song_id);
49CREATE INDEX idx_song_streams_user_id ON song_streams (user_id);
50
51-- 2.6 gi kreirame inicijalnite particii za postoeckite podatoci i nekolku meseci unapred.
52-- vo idnina particiite ke bidat kreirani avtomatski, ova se koristi samo za inicijalniot bootstrap
53-- kreirame particii od 2025-11 do 2026-07
54CREATE TABLE song_streams_y2025m11 PARTITION OF song_streams FOR VALUES FROM ('2025-11-01') TO ('2025-12-01');
55CREATE TABLE song_streams_y2025m12 PARTITION OF song_streams FOR VALUES FROM ('2025-12-01') TO ('2026-01-01');
56CREATE TABLE song_streams_y2026m01 PARTITION OF song_streams FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
57CREATE TABLE song_streams_y2026m02 PARTITION OF song_streams FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');
58CREATE TABLE song_streams_y2026m03 PARTITION OF song_streams FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');
59CREATE TABLE song_streams_y2026m04 PARTITION OF song_streams FOR VALUES FROM ('2026-04-01') TO ('2026-05-01');
60CREATE TABLE song_streams_y2026m05 PARTITION OF song_streams FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');
61CREATE TABLE song_streams_y2026m06 PARTITION OF song_streams FOR VALUES FROM ('2026-06-01') TO ('2026-07-01');
62CREATE TABLE song_streams_y2026m07 PARTITION OF song_streams FOR VALUES FROM ('2026-07-01') TO ('2026-08-01');
63
64-- default particija za zapisi koj nemaat soodvetna particija
65CREATE TABLE song_streams_default PARTITION OF song_streams DEFAULT;
66
67-- 2.7 migracija na podatocite. insertot implicitno gi rutira site zapisi kon tocnata particija
68INSERT INTO song_streams (id, playback_session_id, song_id, streamed_at, user_id)
69SELECT id, playback_session_id, song_id, streamed_at, user_id
70FROM song_streams_old;
71
72-- 2.8 update na sekvencata
73SELECT setval('song_streams_id_seq', (SELECT max(id) FROM song_streams));
74
75-- 2.9 proverki za da vidime deka site podatoci se preneseni
76-- SELECT count(*) FROM song_streams;
77-- SELECT count(*) FROM song_streams_old;
78
79COMMIT;
80
81-- 2.10 drop na starata tabela (samo otkako sme utvrdile deka zavisnite views/matviews se postaveni kon novata tabela)
82-- DROP TABLE song_streams_old;
83
84-- 2.11 refresh na statistikite na planner-ot
85ANALYZE song_streams;
86
87
88
89-- 3. funkcija za kreiranje segasna + idni particii
90-- funkcijata e idempotentna - moze da se izvrsuva poveke pati vo mesecot bez neposakuvani side effects
91CREATE OR REPLACE FUNCTION create_song_streams_partitions(months_ahead integer DEFAULT 2)
92RETURNS void
93LANGUAGE plpgsql
94AS $$
95DECLARE
96 first_month date := date_trunc('month', current_date)::date;
97 m date;
98 part_name text;
99 from_ts timestamp;
100 to_ts timestamp;
101BEGIN
102 FOR i IN 0..months_ahead LOOP
103 m := (first_month + (i || ' months')::interval)::date;
104 part_name := format('song_streams_y%sm%s', to_char(m, 'YYYY'), to_char(m, 'MM'));
105 from_ts := m;
106 to_ts := (m + interval '1 month');
107
108 IF NOT EXISTS (
109 SELECT 1 FROM pg_class WHERE relname = part_name AND relkind = 'r'
110 ) THEN
111 EXECUTE format(
112 'CREATE TABLE %I PARTITION OF song_streams FOR VALUES FROM (%L) TO (%L)',
113 part_name, from_ts, to_ts
114 );
115 RAISE NOTICE 'Created partition % (% .. %)', part_name, from_ts, to_ts;
116 END IF;
117 END LOOP;
118END;
119$$;
120
121
122-- 4. funkcija za arhiviranje stari particii
123-- retention_months = kolku meseci sakame da cuvame. default vrednost e 12 (1 godina)
124
125CREATE OR REPLACE FUNCTION archive_song_streams_partitions(retention_months integer DEFAULT 12)
126RETURNS void
127LANGUAGE plpgsql
128AS $$
129DECLARE
130 cutoff_month date := (date_trunc('month', current_date)
131 - (retention_months || ' months')::interval)::date;
132 r record;
133 yr int;
134 mo int;
135 part_month date;
136 archived_name text;
137BEGIN
138 -- zamrznuvame brojaci za sekoj zatvoren mesec pred da pocneme
139 PERFORM seal_closed_song_streams_months();
140
141 FOR r IN
142 SELECT c.relname
143 FROM pg_inherits i
144 JOIN pg_class c ON c.oid = i.inhrelid
145 JOIN pg_class p ON p.oid = i.inhparent
146 WHERE p.relname = 'song_streams'
147 AND c.relname ~ '^song_streams_y\d{4}m\d{2}$' -- isklucuva default particija
148 LOOP
149 yr := substring(r.relname from 'y(\d{4})m')::int;
150 mo := substring(r.relname from 'm(\d{2})$')::int;
151 part_month := make_date(yr, mo, 1);
152
153 IF part_month < cutoff_month THEN
154 archived_name := r.relname || '_archived';
155
156 -- 1. otstranuvame particija od parent tabelata -> stanuva standalone tabela.
157 EXECUTE format('ALTER TABLE song_streams DETACH PARTITION %I', r.relname);
158
159 -- 2. ja pomestuvame vo archive shemata i ja preimenuvame soodvetno
160 EXECUTE format('ALTER TABLE %I SET SCHEMA archive', r.relname);
161 EXECUTE format('ALTER TABLE archive.%I RENAME TO %I', r.relname, archived_name);
162
163 RAISE NOTICE 'archived % -> archive.%', r.relname, archived_name;
164 END IF;
165 END LOOP;
166
167 -- sinhronizacija na full-history view so novite arhivirani particii (sekcija 6)
168 PERFORM rebuild_song_streams_all_view();
169END;
170$$;
171
172
173-- 5. job scheduling so pg_cron
174
175CREATE EXTENSION IF NOT EXISTS pg_cron;
176
177
178-- sozdava novi particii za mesecite sto sledat: se izvrsuva sekoj den vo 02:00.
179-- dnevno, namesto mesecno za propusten den da ne znaci deka nema da se sozdade novata particija
180SELECT cron.schedule(
181 'song_streams_create_partitions',
182 '0 2 * * *',
183 $$SELECT create_song_streams_partitions(2)$$
184);
185
186-- arhiviranje stari particii: se izvrsuva na vtoriot den od sekoj mesec vo 03:00.
187SELECT cron.schedule(
188 'song_streams_archive_partitions',
189 '0 3 2 * *',
190 $$SELECT archive_song_streams_partitions(12)$$
191);
192
193
194-- 6. funkcija za dinamicko kreiranje na view sto gi spojuva postoeckite song streams
195-- so site arhivirani particii
196CREATE OR REPLACE FUNCTION rebuild_song_streams_all_view()
197RETURNS void
198LANGUAGE plpgsql
199AS $$
200DECLARE
201 cols constant text := 'id, playback_session_id, song_id, streamed_at, user_id';
202 sql text;
203 r record;
204BEGIN
205 -- zapocni od postoeckata ("ziva") tabela
206 sql := format('SELECT %s FROM song_streams', cols);
207
208 -- dodavaj po edno UNION ALL za sekoja arhivirana particija, pocnuvajki od najstarata
209 FOR r IN
210 SELECT n.nspname, c.relname
211 FROM pg_class c
212 JOIN pg_namespace n ON n.oid = c.relnamespace
213 WHERE n.nspname = 'archive'
214 AND c.relkind = 'r'
215 AND c.relname ~ '^song_streams_y\d{4}m\d{2}_archived$'
216 ORDER BY c.relname
217 LOOP
218 sql := sql || format(
219 E'\n UNION ALL\n SELECT %s FROM %I.%I',
220 cols, r.nspname, r.relname
221 );
222 END LOOP;
223
224 EXECUTE format('CREATE OR REPLACE VIEW song_streams_all AS %s', sql);
225 RAISE NOTICE 'Rebuilt song_streams_all view';
226END;
227$$;
228
229-- SELECT rebuild_song_streams_all_view();