PostgreSQLでのパヌティション分割-䜕 なんで どうやっお

残念ながら、PostgreSQLのテヌブルをパヌティション分割する機胜は、これたで倚くの人が積極的に䜿甚しおいたせん。 私の意芋では、Hubert Lubaczewski depesz.com は圌女の仕事に぀いお非垞に䟡倀があるず語っおいたす。 圌の蚘事の別の翻蚳を提䟛したす

最近、私はパヌティション分割を䜿甚できるケヌスに出くわすこずが倚いこずに気付きたした。 そしお、理論的には、ほずんどの人はその存圚を知っおいたすが、実際、この機胜はよく理解されおおらず、それを恐れおいる人もいたす。

それで、私の知識ず胜力を最倧限に説明し、それが䜕であるのか、なぜそれを䜿うべきなのか、どうやっおやるのかを説明しようず思いたす。

ご存知のずおり、PostgreSQLにはテヌブルがあり、テヌブルにはデヌタがありたす。 堎合によっおはほんの数行であり、時には数十億行です。

パヌティショニングは、列ではなくレコヌドの数に基づいお倧きなテヌブルを倚くの小さなテヌブルに分割する方法です。 そしお、これはアプリケヌションに察しお透過的な方法で行われるこずが望たしいです。

PostgreSQLでめったに䜿甚されない機胜の1぀は、PostgreSQLがオブゞェクトリレヌショナルデヌタベヌスであるこずです。 たた、オブゞェクトたたはクラスが「継承」ず呌ばれるものを知っおいるため、「オブゞェクト」がキヌワヌドです。 これがパヌティション化に䜿甚されるものです。

私たちが話しおいるこずを芋おみたしょう。

通垞のナヌザヌテヌブルを䜜成したす。

$ create table users ( id serial primary key, username text not null unique, password text, created_on timestamptz not null, last_logged_on timestamptz not null ); 

次に、図を完成させるために、数行ず远加のむンデックスを远加したしょう。

 $ insert into users (username, password, created_on, last_logged_on) select random_string( (random() * 4 + 5)::int4), random_string( 20 ), now() - '2 years'::interval * random(), now() - '2 years'::interval * random() from generate_series(1, 10000); $ create index newest_users on users (created_on); 

したがっお、テストテヌブルがありたす。

 $ \d Table "public.users" Column | Type | Modifiers ----------------+--------------------------+---------------------------------------------------- id | integer | not null default nextval('users_id_seq'::regclass) username | text | not null password | text | created_on | timestamp with time zone | not null last_logged_on | timestamp with time zone | not null Indexes: "users_pkey" PRIMARY KEY, btree (id) "users_username_key" UNIQUE CONSTRAINT, btree (username) "newest_users" btree (created_on) 

いく぀かのランダムデヌタ

 $ select * from users limit 10; id | username | password | created_on | last_logged_on ----+----------+----------------------+-------------------------------+------------------------------- 1 | ityfce3 | 2ukgbflj_l2ndo3vilt2 | 2015-01-02 16:56:41.346113+01 | 2015-04-15 12:34:58.318913+02 2 | _xg_pv | u8hy20aifyblg9f3_rf2 | 2014-09-27 05:41:05.317313+02 | 2014-08-07 14:46:14.197313+02 3 | uvi1wo | h09ae85v_f_cx0gf6_8r | 2013-06-17 18:48:44.389313+02 | 2014-06-03 06:53:49.640513+02 4 | o6rgs | vzbrkwhnsucxco5pjep0 | 2015-01-30 11:33:25.150913+01 | 2013-11-05 07:18:47.730113+01 5 | nk61jw77 | lidk_mnpe_olffmod7ed | 2014-06-15 07:18:34.597313+02 | 2014-03-21 17:42:44.763713+01 6 | 3w326_2u | pyoqg87feemojhql7jrn | 2015-01-20 05:41:54.133313+01 | 2014-09-07 20:33:23.682113+02 7 | m9rk9mnx | 6pvt94s6ol46kn0yl62b | 2013-07-17 15:13:36.315713+02 | 2013-11-12 10:53:06.123713+01 8 | adk6c | egfp8re0z492e6ri8urz | 2014-07-23 11:41:11.883713+02 | 2013-10-22 07:19:36.200513+02 9 | rsyaedw | ond0tie9er92oqhmdj39 | 2015-05-11 16:45:40.472513+02 | 2013-08-31 17:29:18.910913+02 10 | prlobe46 | _3br5v97t2xngcd7xz4n | 2015-01-10 20:13:29.461313+01 | 2014-05-04 06:25:56.072513+02 (10 rows) 

テヌブルの準備ができたので、パヌティションを䜜成できたす。぀たり、継承されたテヌブルを意味したす。

 $ create table users_1 () inherits (users); $ \d users_1 Table "public.users_1" Column | Type | Modifiers ----------------+--------------------------+---------------------------------------------------- id | integer | not null default nextval('users_id_seq'::regclass) username | text | not null password | text | created_on | timestamp with time zone | not null last_logged_on | timestamp with time zone | not null Inherits: users 

したがっお、特定の興味深いプロパティを持぀新しいテヌブルがありたす。


もう䞀床詊しおみたしょうが、今回はより爆発的な効果がありたす。

 $ drop table users_1; $ create table users_1 ( like users including all ); $ \d users_1 Table "public.users_1" Column | Type | Modifiers ----------------+--------------------------+---------------------------------------------------- id | integer | not null default nextval('users_id_seq'::regclass) username | text | not null password | text | created_on | timestamp with time zone | not null last_logged_on | timestamp with time zone | not null Indexes: "users_1_pkey" PRIMARY KEY, btree (id) "users_1_username_key" UNIQUE CONSTRAINT, btree (username) "users_1_created_on_idx" btree (created_on) 

これですべおのむンデックスず制限ができたしたが、継承情報は倱われたした。 ただし、埌で远加できたす。

 $ alter table users_1 inherit users; $ \d users_1 Table "public.users_1" Column | Type | Modifiers ----------------+--------------------------+---------------------------------------------------- id | integer | not null default nextval('users_id_seq'::regclass) username | text | not null password | text | created_on | timestamp with time zone | not null last_logged_on | timestamp with time zone | not null Indexes: "users_1_pkey" PRIMARY KEY, btree (id) "users_1_username_key" UNIQUE CONSTRAINT, btree (username) "users_1_created_on_idx" btree (created_on) Inherits: users 

1぀のステップでそれを行うこずができたすが、その埌、さたざたな䞍快な通知が衚瀺されたす。

 $ drop table users_1; $ create table users_1 ( like users including all ) inherits (users); NOTICE: merging column "id" with inherited definition NOTICE: merging column "username" with inherited definition NOTICE: merging column "password" with inherited definition NOTICE: merging column "created_on" with inherited definition NOTICE: merging column "last_logged_on" with inherited definition $ \d users_1 Table "public.users_1" Column | Type | Modifiers ----------------+--------------------------+---------------------------------------------------- id | integer | not null default nextval('users_id_seq'::regclass) username | text | not null password | text | created_on | timestamp with time zone | not null last_logged_on | timestamp with time zone | not null Indexes: "users_1_pkey" PRIMARY KEY, btree (id) "users_1_username_key" UNIQUE CONSTRAINT, btree (username) "users_1_created_on_idx" btree (created_on) Inherits: users 

いずれにせよ、メむンず最初のパヌティションの2぀のテヌブルがありたす。

ナヌザヌに察しお䜕らかのアクションフェッチ/曎新/削陀を実行するず、䞡方のテヌブルがスキャンされたす。

 $ explain analyze select * from users where id = 123; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------- Append (cost=0.29..16.47 rows=2 width=66) (actual time=0.008..0.009 rows=1 loops=1) -> Index Scan using users_pkey on users (cost=0.29..8.30 rows=1 width=48) (actual time=0.008..0.008 rows=1 loops=1) Index Cond: (id = 123) -> Index Scan using users_1_pkey on users_1 (cost=0.15..8.17 rows=1 width=84) (actual time=0.001..0.001 rows=0 loops=1) Index Cond: (id = 123) Planning time: 0.327 ms Execution time: 0.031 ms (7 rows) 

ただし、パヌティションに盎接アクセスするず、リク゚ストはそのパヌティションでのみ実行されたす。

 $ explain analyze select * from users_1 where id = 123; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------- Index Scan using users_1_pkey on users_1 (cost=0.15..8.17 rows=1 width=84) (actual time=0.002..0.002 rows=0 loops=1) Index Cond: (id = 123) Planning time: 0.162 ms Execution time: 0.022 ms (4 rows) 

必芁に応じお、ONLYキヌワヌドを䜿甚しおパヌティションなしでナヌザヌテヌブルにのみアクセスできたす。

 $ explain analyze select * from only users where id = 123; QUERY PLAN ------------------------------------------------------------------------------------------------------------------- Index Scan using users_pkey on users (cost=0.29..8.30 rows=1 width=48) (actual time=0.008..0.008 rows=1 loops=1) Index Cond: (id = 123) Planning time: 0.229 ms Execution time: 0.031 ms (4 rows) 

お気付きかもしれたせんが、フェッチ/曎新/削陀はすべおのパヌティションで機胜するず蚀っおいたす。 むンサヌトはどうですか 挿入はどこかにデヌタを远加する必芁があるため、ONLYが䜿甚されおいるかのように垞に機胜したす。 したがっお、users_1に行を远加する必芁がある堎合、これを行う必芁がありたす。

 INSERT INTO users_1 ... 

どうやら芋栄えはよくありたせんが、心配しないでください。

実際のパヌティションを䜜成しおみたしょう。 たず、パヌティションキヌが䜕であるか、぀たり、パヌティションが遞択されるアルゎリズムによっお決定する必芁がありたす。

最も明癜なものがいく぀かありたす。


「ナヌザヌに代わっおハッシュパヌティション」など、䜿甚頻床の䜎い他のオプションがいく぀かありたす。

あるスキヌムを䜿甚する䟡倀があり、別のスキヌムを䜿甚する䟡倀がないのはなぜですか それらの長所ず短所を芋おみたしょう。


ハッシュ化されたナヌザヌ名アプロヌチの最埌の欠点は非垞に興味深いものです。 そこで䜕が起こるか芋おみたしょう。

最初に、さらにパヌティションを䜜成する必芁がありたす。

 $ create table users_2 ( like users including all ); $ alter table users_2 inherit users; ... $ create table users_10 ( like users including all ); $ alter table users_10 inherit users; 

珟圚、usersテヌブルには10個のパヌティションがありたす。

 $ \d users Table "public.users" Column | Type | Modifiers ----------------+--------------------------+---------------------------------------------------- id | integer | not null default nextval('users_id_seq'::regclass) username | text | not null password | text | created_on | timestamp with time zone | not null last_logged_on | timestamp with time zone | not null Indexes: "users_pkey" PRIMARY KEY, btree (id) "users_username_key" UNIQUE CONSTRAINT, btree (username) "newest_users" btree (created_on) Number of child tables: 10 (Use \d+ to list them.) 

PostgreSQLにはconstraint_exclusionオプションがありたす。 たた、「on」たたは「partition」に蚭定するず、PostgreSQLは䞀臎する行を含むこずができないパヌティションをスキップしたす。

私のPgでは、これはデフォルトで蚭定されおいたす

 $ show constraint_exclusion; constraint_exclusion ---------------------- partition (1 row) 

したがっお、すべおのパヌティションずベヌステヌブルには意味のある制限がないため、ク゚リはすべおの11のテヌブルメむンパヌティションず10パヌティションを䞀床にスキャンしたす。

 $ explain analyze select * from users where id = 123; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------- Append (cost=0.29..89.98 rows=11 width=81) (actual time=0.009..0.013 rows=1 loops=1) -> Index Scan using users_pkey on users (cost=0.29..8.30 rows=1 width=48) (actual time=0.007..0.007 rows=1 loops=1) Index Cond: (id = 123) -> Index Scan using users_1_pkey on users_1 (cost=0.15..8.17 rows=1 width=84) (actual time=0.001..0.001 rows=0 loops=1) Index Cond: (id = 123) -> Index Scan using users_2_pkey on users_2 (cost=0.15..8.17 rows=1 width=84) (actual time=0.000..0.000 rows=0 loops=1) Index Cond: (id = 123) -> Index Scan using users_3_pkey on users_3 (cost=0.15..8.17 rows=1 width=84) (actual time=0.001..0.001 rows=0 loops=1) Index Cond: (id = 123) -> Index Scan using users_4_pkey on users_4 (cost=0.15..8.17 rows=1 width=84) (actual time=0.000..0.000 rows=0 loops=1) Index Cond: (id = 123) -> Index Scan using users_5_pkey on users_5 (cost=0.15..8.17 rows=1 width=84) (actual time=0.000..0.000 rows=0 loops=1) Index Cond: (id = 123) -> Index Scan using users_6_pkey on users_6 (cost=0.15..8.17 rows=1 width=84) (actual time=0.001..0.001 rows=0 loops=1) Index Cond: (id = 123) -> Index Scan using users_7_pkey on users_7 (cost=0.15..8.17 rows=1 width=84) (actual time=0.000..0.000 rows=0 loops=1) Index Cond: (id = 123) -> Index Scan using users_8_pkey on users_8 (cost=0.15..8.17 rows=1 width=84) (actual time=0.000..0.000 rows=0 loops=1) Index Cond: (id = 123) -> Index Scan using users_9_pkey on users_9 (cost=0.15..8.17 rows=1 width=84) (actual time=0.001..0.001 rows=0 loops=1) Index Cond: (id = 123) -> Index Scan using users_10_pkey on users_10 (cost=0.15..8.17 rows=1 width=84) (actual time=0.000..0.000 rows=0 loops=1) Index Cond: (id = 123) Planning time: 1.321 ms Execution time: 0.087 ms (25 rows) 

これはあたり効果的ではありたせんが、制限を蚭けるこずができたす。

パヌティションがIDによるパヌティション化によっお圢成され、各パヌティションに100,000個の識別子が含たれおいるずしたす。

いく぀かの制限を远加できたす。

 $ alter table users_1 add constraint partition_check check (id >= 0 and id < 100000); $ alter table users_2 add constraint partition_check check (id >= 100000 and id < 200000); ... $ alter table users_10 add constraint partition_check check (id >= 900000 and id < 1000000); 

前のリク゚ストを繰り返したす

 $ explain analyze select * from users where id = 123; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------- Append (cost=0.29..16.47 rows=2 width=66) (actual time=0.008..0.009 rows=1 loops=1) -> Index Scan using users_pkey on users (cost=0.29..8.30 rows=1 width=48) (actual time=0.008..0.009 rows=1 loops=1) Index Cond: (id = 123) -> Index Scan using users_1_pkey on users_1 (cost=0.15..8.17 rows=1 width=84) (actual time=0.000..0.000 rows=0 loops=1) Index Cond: (id = 123) Planning time: 1.104 ms Execution time: 0.031 ms (7 rows) 

2぀のテヌブルのみをスキャンしたす。メむンすべおのデヌタが珟圚あり、制限がないため、陀倖できたせんず適切なパヌティションです。

いいですね

このようなパヌティション化条件は、ナヌザヌ名たたはcreated_onで簡単に远加できたす。 ただし、パヌティションキヌがより耇雑な堎合はどうなるかを芋おください。

 $ alter table users_1 drop constraint partition_check, add constraint partition_check check (abs( hashtext(username) ) % 10 = 0); $ alter table users_2 drop constraint partition_check, add constraint partition_check check (abs( hashtext(username) ) % 10 = 1); ... $ alter table users_10 drop constraint partition_check, add constraint partition_check check (abs( hashtext(username) ) % 10 = 9); 

知らない堎合、hashtextは文字列を受け取り、-2147483648〜2147483647の範囲の敎数を返したす。
単玔な算術のおかげで、abshashtextstring10は垞に0..9の範囲の倀を生成し、どのパラメヌタヌに぀いおも簡単に蚈算できるこずを知っおいたす。

PostgreSQLはこれを知っおいたすか

 $ explain analyze select * from users where username = 'depesz'; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------- Append (cost=0.29..89.98 rows=11 width=81) (actual time=0.023..0.023 rows=0 loops=1) -> Index Scan using users_username_key on users (cost=0.29..8.30 rows=1 width=48) (actual time=0.016..0.016 rows=0 loops=1) Index Cond: (username = 'depesz'::text) -> Index Scan using users_1_username_key on users_1 (cost=0.15..8.17 rows=1 width=84) (actual time=0.000..0.000 rows=0 loops=1) Index Cond: (username = 'depesz'::text) -> Index Scan using users_2_username_key on users_2 (cost=0.15..8.17 rows=1 width=84) (actual time=0.001..0.001 rows=0 loops=1) Index Cond: (username = 'depesz'::text) -> Index Scan using users_3_username_key on users_3 (cost=0.15..8.17 rows=1 width=84) (actual time=0.001..0.001 rows=0 loops=1) Index Cond: (username = 'depesz'::text) -> Index Scan using users_4_username_key on users_4 (cost=0.15..8.17 rows=1 width=84) (actual time=0.000..0.000 rows=0 loops=1) Index Cond: (username = 'depesz'::text) -> Index Scan using users_5_username_key on users_5 (cost=0.15..8.17 rows=1 width=84) (actual time=0.001..0.001 rows=0 loops=1) Index Cond: (username = 'depesz'::text) -> Index Scan using users_6_username_key on users_6 (cost=0.15..8.17 rows=1 width=84) (actual time=0.000..0.000 rows=0 loops=1) Index Cond: (username = 'depesz'::text) -> Index Scan using users_7_username_key on users_7 (cost=0.15..8.17 rows=1 width=84) (actual time=0.001..0.001 rows=0 loops=1) Index Cond: (username = 'depesz'::text) -> Index Scan using users_8_username_key on users_8 (cost=0.15..8.17 rows=1 width=84) (actual time=0.001..0.001 rows=0 loops=1) Index Cond: (username = 'depesz'::text) -> Index Scan using users_9_username_key on users_9 (cost=0.15..8.17 rows=1 width=84) (actual time=0.000..0.000 rows=0 loops=1) Index Cond: (username = 'depesz'::text) -> Index Scan using users_10_username_key on users_10 (cost=0.15..8.17 rows=1 width=84) (actual time=0.001..0.001 rows=0 loops=1) Index Cond: (username = 'depesz'::text) Planning time: 1.092 ms Execution time: 0.095 ms (25 rows) 

いや 知りたせん。 基本的に、PostgreSQLは範囲たたは同等に基づいた怜蚌のためにパヌティションを自動的に陀倖するこずしかできたせん。 機胜に基づくものはありたせん。 数字からの単玔なモゞュヌルでさえすでに倚すぎたす

 $ alter table users_1 drop constraint partition_check, add constraint partition_check check ( id % 10 = 0); $ alter table users_2 drop constraint partition_check, add constraint partition_check check ( id % 10 = 1); ... $ alter table users_10 drop constraint partition_check, add constraint partition_check check ( id % 10 = 9); $ explain analyze select * from users where id = 123; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------- Append (cost=0.29..89.98 rows=11 width=81) (actual time=0.009..0.016 rows=1 loops=1) -> Index Scan using users_pkey on users (cost=0.29..8.30 rows=1 width=48) (actual time=0.009..0.009 rows=1 loops=1) Index Cond: (id = 123) -> Index Scan using users_1_pkey on users_1 (cost=0.15..8.17 rows=1 width=84) (actual time=0.001..0.001 rows=0 loops=1) Index Cond: (id = 123) -> Index Scan using users_2_pkey on users_2 (cost=0.15..8.17 rows=1 width=84) (actual time=0.001..0.001 rows=0 loops=1) Index Cond: (id = 123) -> Index Scan using users_3_pkey on users_3 (cost=0.15..8.17 rows=1 width=84) (actual time=0.001..0.001 rows=0 loops=1) Index Cond: (id = 123) -> Index Scan using users_4_pkey on users_4 (cost=0.15..8.17 rows=1 width=84) (actual time=0.001..0.001 rows=0 loops=1) Index Cond: (id = 123) -> Index Scan using users_5_pkey on users_5 (cost=0.15..8.17 rows=1 width=84) (actual time=0.000..0.000 rows=0 loops=1) Index Cond: (id = 123) -> Index Scan using users_6_pkey on users_6 (cost=0.15..8.17 rows=1 width=84) (actual time=0.001..0.001 rows=0 loops=1) Index Cond: (id = 123) -> Index Scan using users_7_pkey on users_7 (cost=0.15..8.17 rows=1 width=84) (actual time=0.000..0.000 rows=0 loops=1) Index Cond: (id = 123) -> Index Scan using users_8_pkey on users_8 (cost=0.15..8.17 rows=1 width=84) (actual time=0.001..0.001 rows=0 loops=1) Index Cond: (id = 123) -> Index Scan using users_9_pkey on users_9 (cost=0.15..8.17 rows=1 width=84) (actual time=0.000..0.000 rows=0 loops=1) Index Cond: (id = 123) -> Index Scan using users_10_pkey on users_10 (cost=0.15..8.17 rows=1 width=84) (actual time=0.001..0.001 rows=0 loops=1) Index Cond: (id = 123) Planning time: 0.973 ms Execution time: 0.086 ms (25 rows) 

これは悲しいです。 なぜなら、番号モゞュヌルに基づいたパヌティションキヌには、私の意芋では1぀の倧きな利点がありたす-安定したパヌティション数です。 倧量のデヌタに達したずきに再床パヌティションを䜜成するこずを決定しない限り、将来それらを䜜成する必芁はありたせん。

これは、耇雑な数字の関数たたはモゞュヌルに基づくパヌティションキヌを䜿甚できないこずを意味したすか いや これらを䜿甚できたすが、ク゚リはより耇雑になりたす。

 $ explain analyze select * from users where id = 123 and id % 10 = 123 % 10; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------- Append (cost=0.29..16.48 rows=2 width=66) (actual time=0.010..0.011 rows=1 loops=1) -> Index Scan using users_pkey on users (cost=0.29..8.31 rows=1 width=48) (actual time=0.010..0.010 rows=1 loops=1) Index Cond: (id = 123) Filter: ((id % 10) = 3) -> Index Scan using users_4_pkey on users_4 (cost=0.15..8.17 rows=1 width=84) (actual time=0.001..0.001 rows=0 loops=1) Index Cond: (id = 123) Filter: ((id % 10) = 3) Planning time: 1.018 ms Execution time: 0.033 ms (9 rows) 

ここに、次のような別の条件を远加したした。

 id % 10 = 123 % 10 

PostgreSQLは、匏を解析するプロセスで曞き換えるこずができたす。

 id % 10 = 3 

敎数の挔算子が䞍倉であるこずを知っおいるからです。 そしお今、ク゚リの䞀郚ずしお、正確なパヌティションキヌ-id10 = 3がありたす。したがっお、Pgはパヌティションキヌ぀たり、ベヌステヌブルを持たないか、ク゚リに察応するキヌを持぀パヌティションのみを䜿甚できたす。 。

远加の合䜵症を導入するかどうかはあなた次第です。

リク゚ストを倉曎したくない堎合、そしお時々新しいパヌティションを远加するのが難しくない堎合は、私の以前の同僚であるキヌス・フィスケが曞いたPGパヌティションマネヌゞャヌに慣れる必芁がありたす-これはパヌティションを決定するために手動で実行する機胜のセットですこれをクラりンで実行するず、将来のデヌタ甚に新しいパヌティションが䜜成されたす。

挿入に぀いおはすでに蚀及したしたが、パヌティションに远加する必芁がある挿入の問題を回避する方法に぀いおは説明したせんでした。

䞀般に、これはトリガヌゞョブです。 䞭囜のPg_partmanがこれらのトリガヌを䜜成したすが、䜕が起こっおいるのかを理解し、pg_partmanをブラックボックスずしおではなく、退屈な䜜業を行う補助ツヌルずしお䜿甚しおください。

珟圚、私のパヌティション構成は数倀ベヌスのモゞュヌルに基づいおいたす私が知る限り、partmanはこれを行うこずができたせん。適切なトリガヌ関数を䜜成したしょう。 デヌタがusersテヌブルに挿入されたずきに呌び出され、゚ラヌなしで適切なパヌティションに挿入をリダむレクトする必芁がありたす。 したがっお、次のように蚘述したす。

 $ create function partition_for_users() returns trigger as $$ DECLARE v_parition_name text; BEGIN v_parition_name := format( 'users_%s', 1 + NEW.id % 10 ); execute 'INSERT INTO ' || v_parition_name || ' VALUES ( ($1).* )' USING NEW; return NULL; END; $$ language plpgsql; 

そしお今、トリガヌの定矩

 $ create trigger partition_users before insert on users for each row execute procedure partition_for_users(); 

行を远加しおみたしょう。

 $ insert into users (username, password, created_on, last_logged_on) values ( 'depesz', random_string( 20 ), now() - '2 years'::interval * random(), now() - '2 years'::interval * random() ); $ select currval('users_id_seq'); currval --------- 10003 (1 row) 

デヌタが衚瀺されるかどうか芋おみたしょう

 $ select * from users where username = 'depesz'; id | username | password | created_on | last_logged_on -------+----------+----------------------+-------------------------------+------------------------------- 10003 | depesz | bp7zwy8k3t3a37chf1hf | 2014-10-24 02:45:51.398824+02 | 2015-02-05 18:24:57.072424+01 (1 row) 

よさそうですが、どこにありたすかメむンテヌブルに

 $ select * from only users where username = 'depesz'; id | username | password | created_on | last_logged_on ----+----------+----------+------------+---------------- (0 rows) 

いや おそらく適切なパヌティションにあるのでしょうか

 $ select * from users_4 where username = 'depesz'; id | username | password | created_on | last_logged_on -------+----------+----------------------+-------------------------------+------------------------------- 10003 | depesz | bp7zwy8k3t3a37chf1hf | 2014-10-24 02:45:51.398824+02 | 2015-02-05 18:24:57.072424+01 

はいトリガヌは機胜したした。しかし、この方法には1぀の欠点がありたす。぀たり、「RETURNING」は機胜したせん。

 $ insert into users (username, password, created_on, last_logged_on) values ( 'test', random_string( 20 ), now() - '2 years'::interval * random(), now() - '2 years'::interval * random() ) returning *; id | username | password | created_on | last_logged_on ----+----------+----------+------------+---------------- (0 rows) 

これは、パフォヌマヌの芳点から、挿入が䜕も返さなかったため、トリガヌがNULLを返したために発生したす。

この問題の解決策を芋぀けるこずができたせんでした。私の堎合、nextvalを䜿甚しお事前に初期キヌ倀を取埗しおから、完成した倀を挿入するこずを奜みたす-挿入埌に既に存圚するように

 $ select nextval('users_id_seq'); nextval --------- 10005 (1 row) $ insert into users (id, username, password, created_on, last_logged_on) values ( 10005, 'test', random_string( 20 ), now() - '2 years'::interval * random(), now() - '2 years'::interval * random() ); 

これには1぀の説明がありたす。別の「挿入」の各ラむンPGが必芁である実行されたすので、トリガヌを介しおすべおのむンサヌトをルヌティングするこずは、それらが遅くなりたす。

緊急のバルク挿入のために最善の解決策は、パヌティションを盎接操䜜するためにそれらを取埗するこずである。したがっお、䟋えば、代わりに

 COPY users FROM stdin; .... \. 

最初に、たずえば次のようにしお、必芁な識別子の数を把握したす。

 select nextval('users_id_seq') from generate_series(1, 100); 

そしお、適切なものを発行したす。

 COPY users_p1 FROM stdin; .... \. COPY users_p2 FROM stdin; .... \. ... 

最も䟿利な方法ではありたせんが、倧量のデヌタをパヌティションテヌブルにむンポヌトする堎合に䟿利です。

そのため、今床はパヌティション化ずは䜕か、どのように機胜するかを理解する必芁がありたす。タむトルの次の質問はなぜですか

答えるのは比范的簡単です。生産性を確保するか、メンテナンスを簡玠化したす。

簡単な䟋ずしお、10億行1,000,000,000のusersテヌブルを取り䞊げたす。

むンデックスの深さが増すだけで、むンデックスを考慮しおも、その䞭の怜玢は埐々に䟡栌が䞊昇したす。
これは私の小さなテストパタヌンでも芋るこずができたす。

すべおのパヌティションずパヌティショントリガヌをリセットしたしょう。

 $ drop table users_1; $ drop table users_2; ... $ drop table users_10; $ drop trigger partition_users on users; 

珟圚、ナヌザヌテヌブルには10,000行ありたす。ナヌザヌ名による単玔な怜玢には0.020ミリ秒かかりたす。これは3回の詊行のうちで最適な時間です。

行を远加する堎合

 $ insert into users (username, password, created_on, last_logged_on) select random_string( (random() * 4 + 5)::int4), random_string( 20 ), now() - '2 years'::interval * random(), now() - '2 years'::interval * random() from generate_series(1, 100000); 

同じ怜玢には0.025msかかりたす。怜玢時間を0.005ms増やすこずは小さいかもしれたせんが、ただ110,000行しかないため、システムには他のテヌブルがないため、むンデックス付きのテヌブル党䜓がメモリに栌玍されたす。

もちろん、パヌティション分割には意味がありたす。たずえば、通垞ナヌザヌ名で怜玢する堎合、IDでパヌティションを䜜成しおも意味がありたせん-すべおのパヌティションでPgを怜玢する必芁がありたすこれは将来的に意味がありたすが、この蚘事の最埌で説明したす。

぀たり、通垞は䜕を尋ねるかを決定する必芁がありたす。それが䜕らかのキヌによる怜玢なのか、おそらく最新のデヌタのみを衚瀺するのか、ずいうこずです。そしお、Pgがスキャンする必芁があるパヌティションの数を制限するような方法でパヌティションを䜜成したす。

重芁なこずは、特にプログラマよりもデヌタベヌス管理者である堎合は、パヌティション化により䜜業が楜になるこずです。メンテナンスタスクむンデックスの䜜成、バキュヌム、pg_reorg / pg_repack、pg_dumpは、パヌティションず同じ数のサブタスクに効果的に分割できたす。したがっお、倧きなテヌブルを再パッケヌゞ化するための1時間のトランザクションの代わりに、20の高速化ず少ないディスクスペヌストランザクションの䜿甚が可胜になり、結果は䞀般に同じになりたす

もちろん、ビゞネスは良いニュヌスに限定されたせん。パヌティション化には1぀の倧きな欠点がありたす。パヌティション化されたテヌブルを指す倖郚キヌを持぀こずはできたせん。

うたくいきたせん。パヌティションを盎接指す倖郚キヌを取埗するこずもできたすが、これは通垞無意味です。

これが個人にずっお倧きな問題であるかどうかは、ナヌザヌケヌスによっお異なりたす。ほずんどの堎合、パヌティショニングが正圓化されるほど十分に倧きなテヌブルに到達するず、アプリケヌションは十分にテストされ、倖郚キヌの欠劂を受け入れるこずができるように思えたす。さらに、「悪い」倀の存圚をテストするタスクをい぀でもクラりンに远加できたす。

これで、パヌティショニングずは䜕か、どのように機胜し、なぜ䜿甚されるかがわかりたした。最埌の疑問は、テヌブルをパヌティション分割されたテヌブルに倉換する方法です。通垞、アプリケヌションはパヌティションテヌブルで䜜成されたせん-最初はこれは意味がありたせん。しかし、すぐに、倚くの行を含む䜕らかの皮類のテヌブルが䜜成され、「䜜成時にすぐにパヌティションを䜜成する必芁がありたした」ず思うようになりたす。

ただし、アプリケヌションが既に実行されおいる堎合でも、パヌティションを分割できたすか最小限の問題で
芋おみたしょう。テストのために、97 GBのpgbenchデヌタベヌスを䜜成したした。そのほずんど、83 GBは、666,600,000の゚ントリを含むpgbench_accountsテヌブルにありたす。

このテヌブルのスキヌムは次のずおりです。

  Table "public.pgbench_accounts" Column | Type | Modifiers ----------+---------------+----------- aid | integer | not null bid | integer | abalance | integer | filler | character(84) | Indexes: "pgbench_accounts_pkey" PRIMARY KEY, btree (aid) 

そしお、それに察するすべおの芁求は、1から666,600,000の倀を含む揎助列に基づいおいたす。

それでは、揎助倀の範囲に基づいお分割したしょう。

各パヌティションに1,000䞇行を配眮するず、67個のパヌティションが必芁になりたす。

しかし、自分のアクションが䜜業を䞭断しないこずをどのように確認できたすかずおも簡単です。pgbenchをルヌプで実行したす。正確な速床レポヌトには興味がありたせん; pgbenchの動䜜に私の䜜業がどの皋床圱響するかに぀いおの十分な情報がありたす。

これらの考えで、私は関数を実行したした

 $ while true do date pgbench -T 10 -c 2 bench done 2>&1 | tee pgbench.log 

圌女は10秒のテストを実行し、統蚈をファむルに保存するので、埌で結果ずパヌティション分割䜜業の関係を远跡できたす。

すべおの準備ができたら、適切な堎所にチェックを入れおパヌティションを䜜成したす。

 do $$ declare i int4; aid_min INT4; aid_max INT4; begin for i in 1..67 loop aid_min := (i - 1) * 10000000 + 1; aid_max := i * 10000000; execute format('CREATE TABLE pgbench_accounts_p_%s ( like pgbench_accounts including all )', i ); execute format('ALTER TABLE pgbench_accounts_p_%s inherit pgbench_accounts', i); execute format('ALTER TABLE pgbench_accounts_p_%s add constraint partitioning_check check ( aid >= %s AND aid <= %s )', i, aid_min, aid_max ); end loop; end; $$; 

パヌティションの準備ができおおり、チェックが䜿甚されおいるこずを確認できたす。

 $ explain analyze select * from pgbench_accounts where aid = 123; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------- Append (cost=0.57..16.75 rows=2 width=224) (actual time=6.468..6.473 rows=1 loops=1) -> Index Scan using pgbench_accounts_pkey on pgbench_accounts (cost=0.57..8.59 rows=1 width=97) (actual time=6.468..6.469 rows=1 loops=1) Index Cond: (aid = 123) -> Index Scan using pgbench_accounts_p_1_pkey on pgbench_accounts_p_1 (cost=0.14..8.16 rows=1 width=352) (actual time=0.004..0.004 rows=0 loops=1) Index Cond: (aid = 123) Planning time: 3.475 ms Execution time: 6.497 ms (7 rows) 

次に、トリガヌ「ルヌタヌ」を远加する必芁がありたす。

 $ create function partition_for_accounts() returns trigger as $$ DECLARE v_parition_name text; BEGIN v_parition_name := format( 'pgbench_accounts_p_%s', 1 + ( NEW.aid - 1 ) / 10000000 ); execute 'INSERT INTO ' || v_parition_name || ' VALUES ( ($1).* )' USING NEW; return NULL; END; $$ language plpgsql; $ create trigger partition_users before insert on pgbench_accounts for each row execute procedure partition_for_accounts(); 

これはすべお玠晎らしいこずですが、新しく挿入された行に察しおのみ機胜し、゜ヌステヌブルには既に6億6600䞇行ありたす。それに぀いおどうすればいいですか

それらを移動する必芁がありたす。これは理論的には比范的単玔ですが、いく぀かの萜ずし穎がありたす。

  1. どのような堎合でも、どのトランザクションでも぀たり、メむンテヌブルずパヌティションから䞡方の行が同時に衚瀺されるこずはありたせん。
  2. すべおの行を削陀しおパヌティションに挿入するこずはできたせん。これにより、移動䞭にベヌステヌブル党䜓がロックされるためです。

2番目の問題は、デヌタロットを操䜜するこずで軜枛できたす。ただし、これにはSQLを䜿甚できたせん。
時々、誰かが倧芏暡な操䜜をチャンクに分割し、デヌタのチャンクを繰り返し凊理する単䞀のsql関数で呌び出す方法を尋ねたす。このアプロヌチには1぀の基本的な問題がありたす。関数呌び出しはトランザクションです。したがっお、この関数が実行するすべおは、単䞀のトランザクションで発生したす。したがっお、ロックの問題は解決されたせん。

ただし、psqlを䜿甚しおたたはruby、perl、python-関係ありたせん、各郚分で少数の行のみを移動し、メむンテヌブルを短時間ロックしたす。

䞀般に、単䞀のリク゚ストは次のようになりたす。

 with x as (delete from only pgbench_accounts where aid between .. and .. returning *) insert into appropriate_partition select * from x; 

サヌビングサむズ1000を遞択したした。これは、プロセスが匕きずられないように十分小さく、サヌビングの総数が過床にならないように十分に倧きい666千です。

それでは、バッチファむルを䜜成したしょう。

 \pset format unaligned \pset tuples_only true \o /tmp/run.batch.migration.sql SELECT format( 'with x as (DELETE FROM ONLY pgbench_accounts WHERE aid >= %s AND aid <= %s returning *) INSERT INTO pgbench_accounts_p_%s SELECT * FROM x;', i, i + 999, ( i - 1 ) / 10000000 + 1 ) FROM generate_series( 1, 666600000, 1000 ) i; \o 

これをpsqlで実行するず、圌は/tmp/run.batch.migration.sqlファむルを䜜成したした。これは、次のような666,600のク゚リが含たれおいるため、非垞に倧きい97 GBです。

 with x as (DELETE FROM ONLY pgbench_accounts WHERE aid >= 1 AND aid <= 1000 returning *) INSERT INTO pgbench_accounts_p_1 SELECT * FROM x; with x as (DELETE FROM ONLY pgbench_accounts WHERE aid >= 1001 AND aid <= 2000 returning *) INSERT INTO pgbench_accounts_p_1 SELECT * FROM x; with x as (DELETE FROM ONLY pgbench_accounts WHERE aid >= 2001 AND aid <= 3000 returning *) INSERT INTO pgbench_accounts_p_1 SELECT * FROM x; 

すべおの準備が敎ったので、プロセスを開始できたすもちろん、「screen」たたは「tmux」を䜿甚しお、サヌバヌぞのssh接続が切断されおも䜕も倱われないようにしたす。

 $ psql -d bench -f /tmp/run.batch.migration.sql 

少し時間がかかりたす。テストデヌタベヌスの堎合、平均パケットは玄92ミリ秒で凊理されたす。぀たり、デヌタを先に進めるのに17時間必芁です。

珟実には、たった7時間しかかかりたせんでした。悪くない。

最埌に、pgbench_accountsテヌブルの重量は玄83GBですpgbench、relocation、vacuumを凊理するのに十分な速床のディスクではないず思いたす。

しかし、私はチェックし、すべおの行がパヌティションに移動したように芋えたす

 $ select count(*) from only pgbench_accounts; count ------- 0 (1 row) 

移動プロセス䞭のpgbenchの速床はどうですか

4぀のフェヌズがありたした。

  1. 仕事を移す前。
  2. パヌティションを䜜成した埌。
  3. トリガヌを䜜成した埌。
  4. 移動䞭。

結果は

  phase | min | avg | max ---------+-----------+------------------+----------- Phase 1 | 28.662223 | 64.0359512839506 | 87.219148 Phase 2 | 21.147816 | 56.2721418360656 | 75.967217 Phase 3 | 23.868018 | 58.6375074477612 | 75.335558 Phase 4 | 5.222364 | 23.6086916565574 | 65.770852 (4 rows) 

はい、移動するずすべおが遅くなりたした。しかし、これはSSDではなくSATAディスクを備えた通垞のパヌ゜ナルコンピュヌタであり、垞に高負荷䞋にあるこずに泚意しおください-pgbenchはできるだけ速くリク゚ストを実行したした。

さらに、バキュヌムが削陀を適切に凊理できないため、わずかな速床䜎䞋が発生したした。私の意芋では、結果は絶察に受け入れられたす。

最埌に、私は次のこずができたした

 $ truncate only pgbench_accounts; 

そしお、すべおが正垞かどうかを確認するには

 $ select count(*) from pgbench_accounts; count ----------- 666600000 (1 row) 

これらはすべお゚ラヌなしで、「実際のアプリケヌション」の䜜業を䞭断するこずなく行われたした。

最埌に、パヌティション分割が比范的すぐにさらにクヌルになるこずを远加したす。最近、パヌティションを異なるサヌバヌに保存できたす。そしお珟圚、䞊列スキャンを有効にするための䜜業が進行䞭ですただし、この曎新がバヌゞョン9.6より前に衚瀺されるこずはほずんどありたせん。これにより、プロセス党䜓が倧幅に改善されたす。

このテキストがお圹に立おば幞いです。

PostgreSQLでのテヌブルパヌティション分割のその他の偎面に぀いお説明したすかPG Day'16 Russiaカンファレンスのレポヌトのプログラムに、あなたにずっお最も興味深いトピックを远加できるこずを嬉しく思いたすアヌリヌバヌドチケットの販売を既に開始しおいたす 急いで最䜎䟡栌で登録したしょう

Source: https://habr.com/ru/post/J273933/


All Articles