postgresqlでCPUが急上昇する問題を覚えておいてください

目次

I.はじめに

2.故障について

1.故障現象

2.トラブルシューティングプロセス

3.障害の原因の分析

4.この失敗から学んだこと

3.PGについて

1.キャッシュ

2.要点インデックス

4、フォローアップの最適化


I.はじめに

少し前まで、オンライン環境でpostgresqlのCPUが急上昇し、本番環境で障害が発生しました。CPUは95%に急上昇し、高いままでした。最後に、反復で変更されたSQLの一部が原因であることが判明しました。今まで、それを最適化する方法については考えていませんでした。または、最初にこの失敗と、この失敗から学んだpostgresqlのいくつかの知識ポイントを記録します。

2.故障について

1.故障現象

ほとんどの操作の読み込みが遅く、読み込みタイムアウトエラーの問題があります

2.トラブルシューティングプロセス

1.サーバーのアラームとログ情報を確認します。CPU使用率は上昇を続け、10:10に100%に近づきました。データベース接続の数が設定されたしきい値を超えたため、アプリケーションの応答が遅くなりました
。2 データベースSQLを分析し、複数を検出します。異常なSQL(20バール)、複数の手動キル異常なSQLは無効です
3.データベースSQLを分析し、待機中のSQLの多くが特定のサービスに関連するSQLであることを
確認します4.サーバーログを確認し、インターフェイス側の会社を見つけます従業員更新インターフェイスを頻繁に呼び出してサービスに応答するゆっくりと、運用および保守の同僚は、Ngnixインターフェイスのアクセス頻度を元の1秒あたり5から1分あたり5に
調整しました。5。Ngnixインターフェイスのアクセス頻度を調整した、データベースのCPUはまだ大幅に低下していません
。6。データベースは11:18に切り替えられました。インスタンスのマスタースレーブは、マスターアプリケーションを切り替えて再起動した後も、切り替え後もCPUが大幅に低下していません
。7。これで時間の経過とともに、アプリケーションコードのSQLのパフォーマンスの問題であると最初に判断されます。SQLの最後の反復での大きな変更は、承認クエリを保留しています
8、11:25その後、承認されるコードの部分をロールバックする準備をします。そして、のdevの環境でテストして。
9. 13:00のdevの環境でコードを検証した後、オンライン環境のパッケージ化と展開を開始。で
10は、13時10分、オンライン環境データベースは徐々に減少し、傾向にあります。安定したゆう

3.障害の原因の分析

障害が復元された後、基本的には保留中のSQLが原因であると判断され、次の側面から障害の原因が分析されます。

1.データベースの監視とアラームのページを表示します

図のこの部分から、CPU使用率が上昇し続け、メモリ使用率が上昇していることがわかりますが、この期間中にIOPSは急上昇せず、通常よりも低くなっています。 SQLの読み取りと書き込み用のディスクが通常より少なくなっています。この間、接続数も大幅に増加し、解放できませんでした。アプリケーションを再起動した後も、接続数が瞬時に急増し、保留中のこの部分を示しています。 SQLの実行が遅く、接続を解放できず、SQLのこの部分の主な理由が遅いです。これは、ディスクの負荷が高いためではなく、CPUとメモリの過度の負荷が原因でSQLの大部分が発生します。が待機状態にあり、SQLのこの部分がメモリに非常にストレスをかけていることを示しています。クエリがキャッシュにヒットし続けるか、SQL計算プロセスが長い可能性があります。

2.ログ管理で遅いログの詳細を表示します

CPUが通常から95%に急上昇する過程で、承認待ちの関連SQLの頻度が非常に高く、ほとんどが1秒または2秒であることがわかりました。高が高くなると、ここでの遅いSQLは実際には無意味です。 、そしてアプリケーション全体が遅れます。それらはすべて高いので、あらゆる種類の遅いSQLがあり、最初はこれに実際にだまされました。

3. SQLの前後を比較して、失敗の主な理由を見つけます

今回の主な問題は要点インデックスの使用であり、元のSQL自体がより複雑であるため、実行する必要のある計算が過剰になり、CPUが急上昇します。

最初に元のSQL(不完全)を見てみましょう:

select proc_task_id

from (
         (
             select t.id_ proc_task_id
             from act_ru_task t
                      join reimburse_data rd on rd.proc_inst_id = t.proc_inst_id_
                      left join act_ru_variable v_al on v_al.task_id_ = t.id_ and v_al.name_ = 'allocationCode'
                      left join reimburse_allocation ra on v_al.text_ = ra.allocation_code and rd.reimburse_data_code =
                                                                                               ra.reimburse_data_code

             where t.suspension_state_ = 1
               and t.category_ = 'APPROVAL'
               and t.assignee_ = 'UI2008051OZLZ8QO'
               and rd.flag != -1
               and t.create_time_ < (current_timestamp - interval '10 seconds')
         )

         union all

         (
             select t.id_ proc_task_id
             from act_ru_task t
                      join reimburse_data rd on rd.proc_inst_id = t.proc_inst_id_
                      left join act_ru_variable v_al on v_al.task_id_ = t.id_ and v_al.name_ = 'allocationCode'
                      left join reimburse_allocation ra on v_al.text_ = ra.allocation_code and rd.reimburse_data_code =
                                                                                               ra.reimburse_data_code

             where t.suspension_state_ = 1
               and t.category_ = 'APPROVAL'
               and t.assignee_ is null
               and t.create_time_ < (current_timestamp - interval '10 seconds')
               and exists(
                     select 1
                     from act_ru_identitylink i
                     where i.task_id_ = t.id_
                       and i.type_ = 'candidate'
                       and (
                             i.user_id_ = 'UI2008051OZLZ8QO'
                             OR i.group_id_ = 'UI2008051OZLZ8QO'
                             or
                             i.user_id_ = 'UG200902X4ZMDEE_'
                             OR i.group_id_ = 'UG200902X4ZMDEE_'
                             or
                             i.user_id_ = 'UG200714ZA49XXS_'
                             OR i.group_id_ = 'UG200714ZA49XXS_'
                             or
                             i.user_id_ = 'UG2008191VRRW9OS_'
                             OR i.group_id_ = 'UG2008191VRRW9OS_'
                         )
                 )
               and rd.flag != -1
         )
     ) reim

         left join form_ru_task ft
                   on reim.enterprise_code = ft.ent_code and reim.reimburse_data_code = ft.form_data_code and
                      proc_task_id = ft.task_id


where reim.enterprise_code = 'EC2002241IQ4WI9T'

  and (ft.auto_complete = 'N' or ft.auto_complete is null);

SQL自体のこの部分は、ビジネス関係のために非常に大きくなります。クエリャが複数のユーザーによって承認されると、SQL全体が複数のユニオン操作を実行し、各承認は承認者グループと承認グループに分割されます。ユーザーグループが多すぎる場合、moreまたは操作が生成されます。activitiは承認者と承認グループを別のテーブルに分離し、btreeインデックスを作成しているため、ユーザーグループのマッチングに大きな問題はなく、SQL全体が一意です。問題はディスクの読み取りです。書き込み圧力が大きい

以下のfaultsqlを見てみましょう。

select proc_task_id

from (
         (
             select t.task_id as proc_task_id
             from form_ru_task t
                      join reimburse_data rd on rd.proc_inst_id = t.proc_inst_id
                      left join reimburse_allocation ra
                                on t.allocation_code = ra.allocation_code and rd.reimburse_data_code =
                                                                              ra.reimburse_data_code

             where t.skip = 'N'
               and t.deleted = 'N'
               and t.is_current = 'Y'
               and t.valid = 'Y'
               and t.suspend_at is null

               and (t.auto_complete = 'N' or t.auto_complete is null)


               and (
                     t.assignee = 'UI1709212MG35TK' or (
                         t.assignee is null and (
                             t.valid_users like '%' || 'UI1709212MG35TK' || '%'
                             or (
                                     t.valid_groups like '%' || 'UGSG1911041C502M5W' || '%'
                                     or
                                     t.valid_groups like '%' || 'UG1811201ELR0J9C_' || '%'
                                     or
                                     t.valid_groups like '%' || 'UG17103010CTDBEO_' || '%'
                                     or
                                     t.valid_groups like '%' || 'UGSG1908131DH64C2F' || '%'
                                     or
                                     t.valid_groups like '%' || 'UI1709212MG35TK' || '%'
                                     or
                                     t.valid_groups like '%' || 'UGSG1909171EWS2HCP' || '%'
                                     or
                                     t.valid_groups like '%' || 'UGSG2005061HZUK0CD' || '%'
                                     or
                                     t.valid_groups like '%' || 'UGSG1908131DH64C1Z' || '%'
                                     or
                                     t.valid_groups like '%' || 'UG1908131DH64C1V_' || '%'
                                     or
                                     t.valid_groups like '%' || 'UGSG1909052OE7DXB' || '%'
                                     or
                                     t.valid_groups like '%' || 'UGSG1908131DH64C21' || '%'
                                 ))
                     )
                 )


               and t.task_type != 'TELLER'
               and t.task_type != 'FINANCE_AUDIT'
               and rd.flag != -1
               and t.created_at < (current_timestamp - interval '10 seconds')
         )
     ) reim

where reim.enterprise_code = 'EC1710251LQT24G1';

クエリのメインテーブルを切り替えた後、関連付けられている3つのテーブルが明らかに少なくなり、承認者と承認がクエリステートメントに結合されていることがわかりますが、承認がある場合でも、ビジネス関係のため、SQLには複数のユニオンすべての操作がありますロジックのこの部分は最適化できません。完全なファジー検索が必要なため、このSQLによって変更されたクエリ条件のvalid_usersフィールドのみが大幅に変更されていますが、pgのgistインデックスまたはginインデックスはこの種のファジークエリをサポートしています。このSQLは、元のSQLよりもパフォーマンスが優れています。パフォーマンスが向上する主な理由は、ディスクの読み取りと書き込みを減らすことですが、メモリとcpuの計算圧力を高めることです。このSQLでは、データブロックが次のようになっている必要があります。全体としてクエリされます(クエリステートメントのキャッシュ))古いSQLよりもはるかに低い

createindexステートメントは次のとおりです。

CREATE INDEX idx_frt_group_task_ent ON maycur.form_ru_task USING gist 
(valid_groups gist_trgm_ops, task_type, ent_code);

現在、私自身の理解では、システム自体が要点のインデックス作成を行っているため、これまでCPU圧力が継続的に増加することはありませんでした。以前の訪問数はそれほど多くなく、今回調整されたSQLの方が多いと推測されます。複雑なことに、pgに必要な計算は少し大きくなる可能性があります。さらに、gistのワードセグメンテーションインデックスアルゴリズムによってスキャンされるインデックスアイテムが多すぎ、ほとんどのインデックスアイテムがキャッシュに直接ヒットするため、メモリに直接アクセスされます。これにはより多くのCPU計算が必要です。PGはキャッシュスペースを共有するため、SQLのこの部分は継続的にメモリにアクセスし、メモリの負荷を高めます。

4.この失敗から学んだこと

1.今回のトラブルシューティングプロセスは遅く、SQLの障害を特定するプロセスも遅いです。CPUの急上昇の問題に遭遇するのは初めてであるため、以前のiopsが急上昇したため、主観的には次のように考えられます。以前の古い問題のあるサービスが原因であり、無駄です。長い間、障害のあるSQLを特定するときは、CPUが通常から95に急上昇するプロセス中に遅いSQLログが最も多く発生するSQLを調査する必要があります。 %。SQLのこの部分を分析する必要があります。
2. SQLの特定の部分を大幅に調整する場合は、SQLの元の部分を保持して、問題が発生したときにできるだけ早くロールバックを試みることをお勧めします。今回は、コードのロールバックも多くの時間を浪費するためです。 、障害全体が解決されます。遅い。
3.主要な調整のためのコードレビューは可能な限り行われるべきです。
4.データベースレベルで大きな変更があった場合、テストでは、ストレステストと複数の次元からの観察を実行し、並行性の量を増やし、iops、cpuなどのインジケーターの動作を観察し、1つのインジケーターが改善されないようにする必要があります。他の指標は貧弱です。

3.PGについて

1.キャッシュ

pgは、主にテーブルデータ、インデックス、クエリプランをキャッシュします(この部分はセッションが切断された後に解放されます)。キャッシュされたデータはメモリに存在します。メモリ領域のこの部分は、8kbブロック単位の配列と見なすことができます。 、つまり、最小割り当て単位は8kb(ページのサイズのみ)です。pgがディスク(主にテーブルとインデックス)からデータ(ページ)を取得する場合、最初にshared_buffersを検索し(ページメタデータに従って)、ページがshared_buffersにあるかどうかを確認します。存在する場合は、直接ヒットし、I / Oを回避するためにキャッシュされたデータを返します。

存在しない場合、pgはディスクにアクセスしてI / Oを介してデータを取得します(明らかに、shared_buffersから取得するよりもはるかに低速です)。

2.要点インデックス

要旨は一般的な探索木です。バランスの取れたツリー構造アクセスです。一般的なインデックスインターフェイスです。任意のインデックスモードを実装するために使用できます。主に、範囲が交差するかどうか、含まれるかどうか、ポイントとサーフェスの交差を解決します。地理的な場所、またはポイントごとに近くのポイントを検索するなど。

Gistは、ノードページで構成されるバランスの取れたツリーです。ノードはインデックス行で構成されます。リーフノードの各行には、いくつかの述語(ブール式、インデックスフィールド演算子)とテーブル行(TID)、インデックスデータ(キー)への参照が含まれます。この述語を満たす必要があります。内部ノードの各行(内部行)には、述語と子ノードへの参照も含まれており、サブツリーのすべてのインデックスデータはこの述語を満たす必要があります。つまり、内側の行の述語には、すべての子行の述語が含まれます。要旨インデックスのこの重要な機能は、Bツリーの単純な並べ替えに代わるものです。

ただし、要点インデックスは不可逆であるため、インデックスが誤った一致を生成する可能性があり、実際のテーブル行をチェックしてそのような誤った一致を排除する必要があるため(pgは必要に応じて自動的に実行されます)、SQLの部分は複雑です。ビジネスパフォーマンスの低下が大きくなり、CPU計算が多すぎる可能性があります。

gistインデックスを作成する前提は、gist拡張機能がコンパイルおよびインストールされていることです。その後、拡張機能をデータベースに直接作成できます。

create extension btree_gist;

一般的に、要点インデックスを作成する際には、trgmプラグイン(ワードセグメンテーションインデックス)が使用されます。このプラグインは、テキスト検索のパフォーマンスを向上させるのに非常に効果的です。データ量が約100万であるため、パフォーマンスがさらに向上します。 500回以上。pg_trgmを使用すると、文字列の前に2つのスペースが追加され、最後に1つのスペースが追加されます。3つの連続する文字列ごとにトークンとして分割され、トークンの要点インデックスが確立されます。次の単語は次のとおりです。 pg_trgmのセグメンテーション効果:

SELECT SHOW_TRGM('viid');

{  v, vi,id ,iid,vii}

pg_trgmはトークンとして3つの連続した文字を使用するため、クエリする単語が1つまたは2つである場合、効果は良くありません。したがって、少なくとも3つの文字をクエリすることをお勧めします。具体的なクエリは、次のような配列検索方法を使用することです。 %viid%を検索すると、対応するフィールドトークンに{v、vi、id、iid、vii}が含まれている必要があります。このとき、フィールドのデータ型はコレクションに属します。これは、RDツリーインデックスアルゴリズムがコレクションインデックス、インデックスアイテムに含まれる要素多くの場合、そのキーのサイズは急速に拡大します。したがって、pgの一部の要点演算子実装クラスでは、インデックスアイテムのキーはsignatureメソッドを使用して圧縮されます。コレクションタイプof pg_trgmはこれを要点インデックスアルゴリズムとして使用するため、おおよその手順は次のとおりです。pg_trgmモジュールによって提供されるgist_trgm_ops演算子は、最初にデータをいくつかの3項語に分割し、これらの3項語を圧縮して署名ベクトルの形式で格納します。リーフノードのインデックスエントリは、pg_trgmの3値ワードの配列(pg_trgmによって生成されます)を格納します。3項ワードの場合、3バイトは3項ワードを格納するために内部的に使用されます。3項ワードにマルチバイト文字が含まれている場合、次のように圧縮されます。 CRCを計算し、最初の3バイトを取得する)、非リーフノードのインデックス項目含まれるすべての3項ワードセットの署名ベクトルを格納します。署名ベクトルのサイズは96ビットです。 pg_trgmの要点インデックスは比較的小さいため、クエリごとにスキャンする必要のあるインデックスノードの数は必然的に多くなり、CPUの計算速度に大きく影響します。次のように拡張機能を作成します。

create extension pg_trgm;    

インデックスの作成も同様です。

create index idx_tbl_1 on tbl using gist(info gist_trgm_ops);    

gistインデックスとbtreeインデックスの違いの1つは、マルチフィールドインデックスでは、前者はインデックスフィールドのサブセットが含まれている限りインデックススキャンを使用し、後者は左端のプレフィックスの原則を満たす必要があることです。 btreeと比較した時間とインデックスサイズどちらも比較的大きいです。andやorなどの演算子の場合、要点インデックスのパフォーマンスはbtreeと比較してそれほど向上していません。

4、フォローアップの最適化

オンライン情報によると、完全なあいまい検索では、gin index + pg_trgmのパフォーマンスはgist + pg_trgmよりも優れています。テスト後、私のビジネスシナリオでは、さらに遅くなり、より多くのデータブロックにヒットします。次に、ginの使用もテストしました。完全なあいまい検索を置き換えるためのインデックス+配列マッチング。配列マッチングの使用は減少するか、理論的には優れているためですが、テストの結果、違いがないか、前者でさえわずかに優れていることがわかりました。

インデックスに一致する配列を作成します。

CREATE INDEX idx_frt_array_group_task ON form_ru_task USING gin (string_to_array(valid_groups, ','), task_type);

pgの&&オーバーラップ関数を使用します。pgの配列関数については、https//www.cnblogs.com/alianbog/p/5665411.htmlを参照してください。

2020.12.21は現在最適化を続けていないため、来年も上司と分析を続ける予定です。上司に経験があればアドバイスをお願いします。

おすすめ

転載: blog.csdn.net/m0_38001814/article/details/111325289