web-dev-qa-db-ja.com

PostgreSQLテーブル行のサイズを測定する

PostgreSQLテーブルがあります。 select *は非常に遅いのに対し、select idは素晴らしく、迅速です。行のサイズが非常に大きく、転送に時間がかかるか、または他の要因が考えられます。

すべてのフィールド(またはほとんどすべてのフィールド)が必要なので、サブセットのみを選択するのは簡単な修正ではありません。必要なフィールドの選択がまだ遅い。

これが私のテーブルスキーマから名前を除いたものです:

integer                  | not null default nextval('core_page_id_seq'::regclass)
character varying(255)   | not null
character varying(64)    | not null
text                     | default '{}'::text
character varying(255)   | 
integer                  | not null default 0
text                     | default '{}'::text
text                     | 
timestamp with time zone | 
integer                  | 
timestamp with time zone | 
integer                  | 

テキストフィールドのサイズは任意のサイズにすることができます。しかし、それでも、最悪のケースでは数キロバイトにすぎません。

ご質問

  1. これについて「クレイジー非効率」と叫ぶものはありますか?
  2. これをデバッグするのに役立つPostgresコマンドラインでページサイズを測定する方法はありますか?
94
Joe

Q2:way to measure page size

PostgreSQLは Database Object Size Functions を多数提供しています。このクエリで最も興味深いものをまとめ、下部に Statistics Access Functions を追加しました。 (追加モジュール pgstattuple は、さらに便利な機能を提供します。)

これは、「行のサイズ」を測定するさまざまな方法が非常に異なる結果をもたらすことを示します。それはすべて、正確に測定したいものに依存します。

このクエリにはPostgres 9.3以降が必要です。古いバージョンについては、以下を参照してください。

VALUES式をLATERALサブクエリで を使用して、すべての行の計算をスペルアウトしないようにします。

置換public.tblは、オプションでスキーマ修飾したテーブル名を使用して、収集された行サイズ統計のコンパクトビューを取得します。これを繰り返し使用するためにplpgsql関数にラップし、テーブル名をパラメーターとして渡し、EXECUTE ...

SELECT l.metric, l.nr AS "bytes/ct"
     , CASE WHEN is_size THEN pg_size_pretty(nr) END AS bytes_pretty
     , CASE WHEN is_size THEN nr / NULLIF(x.ct, 0) END AS bytes_per_row
FROM  (
   SELECT min(tableoid)        AS tbl      -- = 'public.tbl'::regclass::oid
        , count(*)             AS ct
        , sum(length(t::text)) AS txt_len  -- length in characters
   FROM   public.tbl t                     -- provide table name *once*
   ) x
 , LATERAL (
   VALUES
      (true , 'core_relation_size'               , pg_relation_size(tbl))
    , (true , 'visibility_map'                   , pg_relation_size(tbl, 'vm'))
    , (true , 'free_space_map'                   , pg_relation_size(tbl, 'fsm'))
    , (true , 'table_size_incl_toast'            , pg_table_size(tbl))
    , (true , 'indexes_size'                     , pg_indexes_size(tbl))
    , (true , 'total_size_incl_toast_and_indexes', pg_total_relation_size(tbl))
    , (true , 'live_rows_in_text_representation' , txt_len)
    , (false, '------------------------------'   , NULL)
    , (false, 'row_count'                        , ct)
    , (false, 'live_tuples'                      , pg_stat_get_live_tuples(tbl))
    , (false, 'dead_tuples'                      , pg_stat_get_dead_tuples(tbl))
   ) l(is_size, metric, nr);

結果:

              メトリック|バイト/ ct | bytes_pretty | bytes_per_row 
 ----------------------------------- + -------- -+ -------------- + --------------- 
 core_relation_size | 44138496 | 42 MB | 91 
 visibility_map | 0 | 0バイト| 0 
 free_space_map | 32768 | 32 kB | 0 
 table_size_incl_toast | 44179456 | 42 MB | 91 
インデックス_サイズ| 33128448 | 32 MB | 68 
 total_size_incl_toast_and_indexes | 77307904 | 74 MB | 159 
 live_rows_in_text_representation | 29987360 | 29 MB | 62 
 --------------------------- | 
 row_count | 483424 | | 
 live_tuples | 483424 | | 
 dead_tuples | 2677 | |

古いバージョン(Postgres 9.2またはそれ以前):

WITH x AS (
   SELECT count(*)               AS ct
        , sum(length(t::text))   AS txt_len  -- length in characters
        , 'public.tbl'::regclass AS tbl      -- provide table name as string
   FROM   public.tbl t                       -- provide table name as name
   ), y AS (
   SELECT ARRAY [pg_relation_size(tbl)
               , pg_relation_size(tbl, 'vm')
               , pg_relation_size(tbl, 'fsm')
               , pg_table_size(tbl)
               , pg_indexes_size(tbl)
               , pg_total_relation_size(tbl)
               , txt_len
             ] AS val
        , ARRAY ['core_relation_size'
               , 'visibility_map'
               , 'free_space_map'
               , 'table_size_incl_toast'
               , 'indexes_size'
               , 'total_size_incl_toast_and_indexes'
               , 'live_rows_in_text_representation'
             ] AS name
   FROM   x
   )
SELECT unnest(name)                AS metric
     , unnest(val)                 AS "bytes/ct"
     , pg_size_pretty(unnest(val)) AS bytes_pretty
     , unnest(val) / NULLIF(ct, 0) AS bytes_per_row
FROM   x, y

UNION ALL SELECT '------------------------------', NULL, NULL, NULL
UNION ALL SELECT 'row_count', ct, NULL, NULL FROM x
UNION ALL SELECT 'live_tuples', pg_stat_get_live_tuples(tbl), NULL, NULL FROM x
UNION ALL SELECT 'dead_tuples', pg_stat_get_dead_tuples(tbl), NULL, NULL FROM x;

同じ結果。

Q1:anything inefficient?

列の順序を最適化して、行ごとに数バイトを節約することができます。

integer                  | not null default nextval('core_page_id_seq'::regclass)
integer                  | not null default 0
character varying(255)   | not null
character varying(64)    | not null
text                     | default '{}'::text
character varying(255)   | 
text                     | default '{}'::text
text                     |
timestamp with time zone |
timestamp with time zone |
integer                  |
integer                  |

これにより、1行あたり8〜18バイトが節約されます。それを "column tetris"と呼びます。詳細:

次の点も考慮してください。

105

[〜#〜] toast [〜#〜] された内容を含む行のサイズの概算は、行全体のTEXT表現の長さをクエリすることで簡単に取得できます。

SELECT octet_length(t.*::text) FROM tablename AS t WHERE primary_key=:value;

これは、実行時にクライアント側で取得されるバイト数とほぼ同じです。

SELECT * FROM tablename WHERE primary_key=:value;

...クエリの呼び出し元がテキスト形式で結果を要求していると仮定します。これは、ほとんどのプログラムが行うことです(バイナリ形式も可能ですが、ほとんどの場合、問題になることはありません)。

同じ手法を適用して、Ntablename "biggest-in-text"行を見つけることもできます。

SELECT primary_key, octet_length(t.*::text) FROM tablename AS t
   ORDER BY 2 DESC LIMIT :N;
40
Daniel Vérité

起こり得ることがいくつかあります。一般的に、長さが近位の問題であるとは思えません。代わりに、長さに関する問題があると思います。

あなたはテキストフィールドが数kまで取得できると言います。行はメインストレージで8kを超えることはできず、大きなテキストフィールドが TOASTed になっているか、メインストレージから別のファイルの拡張ストレージに移動されている可能性があります。これにより、メインストレージは高速になります(したがって、アクセスするディスクページが少ないため、select idは実際には高速になります)が、ランダムI/Oが多いため、select *は遅くなります。

行の合計サイズがまだ8k未満の場合は、ストレージ設定を変更してみてください。ただし、メインストレージに特大の属性を挿入すると、問題が発生する可能性があるので、必要がない場合はこれに触れないようにしてください。そうしない場合は、チェック制約を使用して適切な制限を設定してください。したがって、輸送だけが可能性があるわけではありません。ランダムな読み取りを必要とする多くのフィールドを照合している可能性があります。多数のランダム読み取りもキャッシュミスを引き起こす可能性があり、結合が存在する場合(およびTOASTが関係する場合は1つ存在する場合)、大量のメモリがディスク上に実体化され、大量の広い行が必要になる場合があります。結合パターンなど.

私が最初に行うことは、選択する行を少なくして、それが役立つかどうかを確認することです。それが機能する場合は、サーバーにRAMを追加することもできますが、最初に計画の変更とキャッシュミスのためにパフォーマンスが低下し始める場所を確認します。

14
Chris Travers

上記の データベースオブジェクトサイズ関数 の使用:

SELECT primary_key, pg_column_size(tablename.*) FROM tablename;