ページの先頭行へ戻る
Enterprise Postgres 14 SP1 スケールアウト運用ガイド
FUJITSU Software

4.4.1 SQLチューニング

EXPLAINの使用

アプリケーションが中央管理ノードに接続し、データノード上のテーブルにアクセスするSQLを実行した場合、中央管理ノードのpostgres_scaleout_fdwは、データノードに対して、必要なデータを取得するSQLを内部的に実行します。postgres_scaleout_fdwが内部的に実行しているSQLは、EXPLAINコマンドやEXPLAIN ANALYZEコマンドにVERBOSEオプションをつけることで出力できます。下記は、データノード1上のテーブルpart1とデータノード2上のテーブルpart2を結合した場合の例です。

postgres=#  EXPLAIN VERBOSE SELECT * FROM part1, part2 WHERE part1.id = part2.id AND part1.id < 10;
                                    QUERY PLAN
-----------------------------------------------------------------------------------
 Hash Join  (cost=241.85..405.75 rows=3105 width=72)
   Output: part1.id, part1.value, part2.id, part2.value
   Hash Cond: (part2.id = part1.id)
   ->  Foreign Scan on public.part2  (cost=100.00..150.95 rows=1365 width=36)
         Output: part2.id, part2.value
         Remote SQL: SELECT id, value FROM public.part2
   ->  Hash  (cost=136.16..136.16 rows=455 width=36)
         Output: part1.id, part1.value
         ->  Foreign Scan on public.part1  (cost=100.00..136.16 rows=455 width=36)
               Output: part1.id, part1.value
               Remote SQL: SELECT id, value FROM public.part1 WHERE ((id < 10))
(11 行)

Remote SQLの箇所に、それぞれのテーブルのデータを取得するために内部的に実行されたSQLが表示されます。上記の場合、part1のデータを取得する際にはid < 10の条件が付与されていますが、part2のデータを取得する際には条件が付与されておらず、全てのデータを取得していることがわかります。ここで、part1.id = part2.idの条件があることからpart1.id < 10であれば、part2.id < 10であることがわかります。そのため、下記のようにpart2.id < 10の条件もSQLに追加することで、part2のデータを取得する際にも全ての行を取得する必要がなくなります。

postgres=#  EXPLAIN VERBOSE SELECT * FROM part1, part2 WHERE part1.id = part2.id AND part1.id < 10 AND part2.id < 10;
                                    QUERY PLAN
-----------------------------------------------------------------------------------
 Hash Join  (cost=241.85..315.66 rows=1035 width=72)
   Output: part1.id, part1.value, part2.id, part2.value
   Hash Cond: (part1.id = part2.id)
   ->  Foreign Scan on public.part1  (cost=100.00..136.16 rows=455 width=36)
         Output: part1.id, part1.value
         Remote SQL: SELECT id, value FROM public.part1 WHERE ((id < 10))
   ->  Hash  (cost=136.16..136.16 rows=455 width=36)
         Output: part2.id, part2.value
         ->  Foreign Scan on public.part2  (cost=100.00..136.16 rows=455 width=36)
               Output: part2.id, part2.value
               Remote SQL: SELECT id, value FROM public.part2 WHERE ((id < 10))
(11 行)

auto_explainの使用

上記のようにEXPLAINを使用することで、中央管理ノードからデータノードに内部的に実行されるSQLを出力できますが、そのSQLがデータノードで実行される際の実行計画は出力できません。

auto_explainを使用することで、データノードで実行される際の実行計画を出力できます。下記は、auto_explainにより実行計画を出力する例です。

  1. 業務1で使用しているapp1userでauto_explainの設定をします。この例では、app1userを使用している他のセッションにも影響があるため、業務実施中に設定する場合には、業務で使用しているユーザーと別のユーザーで実施するなど、業務への性能影響に注意してください。

    postgres=# SET pgx_ddl_target_node = 'ALLNODES';
    SET
    postgres=# ALTER USER app1user SET session_preload_libraries = 'auto_explain';
    ALTER ROLE
    postgres=# ALTER USER app1user SET auto_explain.log_min_duration = 0;
    ALTER ROLE
    postgres=# RESET pgx_ddl_target_node;
    RESET
  2. 中央管理ノードでEXPLAIN ANALYZEを実行します。なお、EXPLAIN ANALYZEは、実際にSQLが実行されるため、必要に応じてROLLBACKしてください。

    postgres=# EXPLAIN ANALYZE VERBOSE SELECT * FROM part1, part2 WHERE part1.id = part2.id AND part1.id < 10 AND part2.id < 10;
                                                             QUERY PLAN
    -----------------------------------------------------------------------------------------------------------------------------
     Hash Join  (cost=241.85..451.58 rows=1 width=72) (actual time=1.760..1.862 rows=9 loops=1)
       Output: part1.id, part1.value, part2.id, part2.value
       Hash Cond: (part1.id = part2.id)
       ->  Foreign Scan on public.part1  (cost=100.00..309.18 rows=9 width=36) (actual time=0.769..0.797 rows=9 loops=1)
             Output: part1.id, part1.value
             Remote SQL: SELECT id, value FROM public.part1 WHERE ((id < 10))
       ->  Hash  (cost=136.16..136.16 rows=455 width=36) (actual time=0.947..0.956 rows=9 loops=1)
             Output: part2.id, part2.value
             Buckets: 1024  Batches: 1  Memory Usage: 9kB
             ->  Foreign Scan on public.part2  (cost=100.00..136.16 rows=455 width=36) (actual time=0.878..0.906 rows=9 loops=1)
                   Output: part2.id, part2.value
                   Remote SQL: SELECT id, value FROM public.part2 WHERE ((id < 10))
     Planning Time: 0.336 ms
     Execution Time: 4.332 ms
    (14 行)
  3. データノードでのログを確認します。下記では、Index Scanが実施されていることがわかります。

    00000: 2022-01-24 15:39:39 JST [3885757]: [2-1] user = app1user,db =postgres,remote = ::1(56188) app = 3885754_for_psql LOG:  duration: 0.029 ms  plan:
            Query Text: DECLARE c1 CURSOR FOR
            SELECT id, value FROM public.part1 WHERE ((id < 10))
            Index Scan using part1_pkey on part1  (cost=0.29..8.48 rows=9 width=36)
              Index Cond: (id < 10)

pg_hint_planの使用

pg_hint_planでは、ヒントの指定方法として、下記の2つがあります。

これらのヒントは、ノード内の実行計画にのみ有効であり、中央管理ノードからデータノードに内部的に実行されるSQLの実行計画には効果がないことに注意してください。

中央管理ノードからデータノードに内部的に実行されるSQLの実行計画を指定する場合には、“EXPLAINの使用”を参照して内部的に実行されるSQLを調査し、そのSQLに対するヒントを、データノードのヒント用のテーブルに登録してください。

データの配置について

EXPLAINの使用”で説明したSQLの実行計画のように、データノード1のテーブルとデータノード2のテーブルを結合する場合、それぞれのデータノードのデータを中央管理ノード上に収集したのちに、中央管理ノード上で結合が実施されます。このように、データノードをまたいだSQLは、1つのノード内でのSQLに比べて、通信によるオーバーヘッドが発生します。

そのため、システム設計の段階で、なるべくノードをまたがないように設計したうえで、ノード間ネットワークは高速なネットワークを利用するようにしてください。