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


83

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コマンドラインでページサイズを測定する方法はありますか?

実際には...列の1つは11 MBです。それで説明できると思います。だからlength(*)、ただよりもする方法がありlength(field)ますか?私はそれがバイトではなく文字であることを知っていますが、必要なのはおおよその値だけです。
ジョー

回答:


101

Q2: way to measure page size

PostgreSQLは、多くのデータベースオブジェクトサイズ関数を提供します。このクエリで最も興味深いものを詰め、下部にいくつかの統計アクセス関数を追加しました。(追加のモジュールpgstattupleは、さらに便利な機能を提供します。)

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

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

副照会VALUESLATERALを使用して、すべての行の計算を綴らないようにします。

public.tbl必要に応じてスキーマ修飾されたテーブル名で(2回)を置き換えて、行のサイズについて収集された統計のコンパクトなビューを取得します。これを繰り返し使用するために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
 indexs_size | 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バイト節約できます。私はそれを「列テトリス」と呼びます。詳細:

以下も検討してください。


テーブルが空の場合、9.3より前のスニペットはゼロによる除算をスローします。実際に9.3+バージョンを使用したかったのですが、間違ったバージョンを誤って選択し、数時間かけて修正する必要がありました。置き換え, unnest(val) / ct, (LEAST(unnest(val), unnest(val) * ct)) / (ct - 1 + sign(ct))、それはスローされません。理由は、いつ、ということでctある0valと置き換えられます0ct置き換えられます1
GuiRitter

1
@GuiRitter:指摘してくれてありがとう。ただし、より簡単な修正を適用しました。また、いくつかの一般的な更新もありますが、クエリは同じままです。
アーウィンブランドステッター

35

TOASTされたコンテンツを含む行のサイズの概算は、行全体のTEXT表現の長さを照会することで簡単に取得できます。

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

これは、実行時にクライアント側で取得されるバイト数に近い近似です。

SELECT * FROM tablename WHERE primary_key=:value;

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

同じ手法を適用して、以下のN「テキスト内で最大の」行を見つけることができますtablename

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

ビッグデータを操作するときにいくつかの推定値をすばやく取得する優れた方法(たとえば、行サイズの大部分はトーストに格納された可変長列にあります)。
fgblomqvist

結果はバイトですか?
Akmal Salikhov

14

発生する可能性のあることがいくつかあります。一般的に、長さが近位の問題であるとは思いません。代わりに、長さに関連する問題があると思われます。

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

行の合計サイズがまだ8kを下回っている場合は、ストレージ設定を変更してみてください。ただし、オーバーサイズの属性を主記憶域に挿入すると、悪いことが起こる可能性があるため、チェック制約を使用して適切な制限を設定する必要がない場合はこれに手を触れないことをお勧めします。そのため、輸送が唯一のものではない可能性があります。ランダム読み取りが必要な多くのフィールドを照合している可能性があります。大量のランダム読み取りもキャッシュミスを引き起こす可能性があり、大量のメモリが必要な場合は、結合が存在する場合(およびTOASTが関与する場合は存在する場合)、ディスクおよび多数の広い行で物事を具体化する必要があります結合パターンなど

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


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.