CPUに負荷のかかるタスクが多すぎる:DXP 7.0に共通するパフォーマンス上の問題点

この記事は、Liferay DXP 7.0内でToo Many CPU Intensive Tasksを実行することに関連する一般的なパフォーマンスの問題を文書化したものです。 この問題を解決するために必要な情報と、その方法についてご紹介します。

以下に、よくある原因と解決策を紹介します:

  1. ミニチュア
  2. ストリップフィルター
  3. ジージップフィルタ
  4. パスワードハッシュ化

解決策

CPUに負荷のかかるタスクが多数同時に実行された場合、一部の加入者にパフォーマンスの問題が発生することがあります。 これは、様々なユースケースから生まれる一般的なパターンです。 スレッドダンプには、直面している問題の深刻さに応じて、そのようなタスクに取り組んでいる数十から数百のスレッドが含まれている可能性があります。 お客様にはCPU使用率の上昇やレスポンスの低下が見られ、まれにLiferayプラットフォームが無反応のように見えることもあるようです。

以下に、よくある原因とその解決策を示しますので、ご確認ください。

ミニチュア

Minifierは、CSSとJavascriptのリソースをブラウザに送信する前に、最小化を試みます。 この操作により、ファイルが小さくなるため、ネットワーク帯域を節約することができます。 ただし、CPUの性能劣化を伴います。

Minifier はデフォルトで有効ですが、 portal-ext.properties で以下のプロパティを設定することで OFF にすることができます。: minifier.enabled=false.

スレッドダンプでは、次のような文字列が何十回、場合によっては何百回と表示されていれば、この問題に気付くでしょう:

com.liferay.portal.util.MinifierUtil._minifyCss

"http-/0.0.0.0:8280-336" daemon prio=10 tid=0x00007f8bfc282ea0 nid=0x2c0d runnable [0x00007f8aa6d27000]
   java.lang.Thread.State: RUNNABLE
    at java.util.regex.Pattern$CharProperty$1.isSatisfiedBy(Pattern.java:3689)
    at java.util.regex.Pattern$7.isSatisfiedBy(Pattern.java:5171)
    at java.util.regex.Pattern$7.isSatisfiedBy(Pattern.java:5171)
    at java.util.regex.Pattern$7.isSatisfiedBy(Pattern.java:5171)
    at java.util.regex.Pattern$CharProperty.match(Pattern.java:3694)
    at java.util.regex.Pattern$Curly.match0(Pattern.java:4158)
    at java.util.regex.Pattern$Curly.match(Pattern.java:4132)
    at java.util.regex.Pattern$Start.match(Pattern.java:3408)
    at java.util.regex.Matcher.search(Matcher.java:1199)
    at java.util.regex.Matcher.find(Matcher.java:592)
    at java.util.regex.Matcher.replaceAll(Matcher.java:902)
    at java.lang.String.replaceAll(String.java:2162)
    at com.yahoo.platform.yui.compressor.CssCompressor.compress(CssCompressor.java:345)
    at com.liferay.portal.util.MinifierUtil._minifyCss(MinifierUtil.java:59)
    at com.liferay.portal.util.MinifierUtil.minifyCss(MinifierUtil.java:38)

Spotifyの Online Thread Dump Analyzerを使って、同じスレッドダンプから別の事象を紹介します:
スポティファイ・パフォーマンス-02.png

このような場合、正確なスタックトレースが異なる可能性があることに注意してください。 これらのスレッドはすべて実行されているため、それぞれ異なる段階にあります。すべてのスレッドがCPU時間を待って進行するため、リソースの枯渇を招きます。

これを解決するには、CSSやJavascriptの最小化が必要かどうかを評価する必要があります。 ミニフィケーションが必要な場合は、さらなるリソースが必要な場合があります。 加入者は、プラットフォーム利用のクラスタリングや、ウェブサーバーによるキャッシュやCDNサーバーへの静的コンテンツのオフロードなど、追加リソースの利用を検討する必要があります。 最小化が不要な場合は、前述のminifier.enabledプロパティでオフにするだけで、CPUへの負担が軽減されるはずです。

ストリップフィルター

StripFilterはMinifierと似ていて、CPU使用量と引き換えに最終的にファイルを小さくする。 Liferay Portal 6.2 のコメントを参照: ストリップフィルターは、出力されたコンテンツから空白行を削除します。
com.liferay.portal.servlet.filters.strip.StripFilter=true

CPUにかかるコストと比較して実益がないため、最初からオフにしておくことをお勧めします。 レスポンスから新しい行を削除することで、スペースを節約することができるはずです。 しかし、コンテンツをgzipすることは非常に効率的であり、ファイル内に改行があるかどうかは(極端なエッジケースを除いて)あまり重要ではありません。 さらに、ブラウザがページを読み込むのにかかる時間はほんのわずかで、ページを読み込むのにかかる時間の大半は、リソースのダウンロードを待つのに費やされます。

つまり、ネットワークにとってもエンドユーザーにとっても、これを行うメリットはなく、しかもCPUにパフォーマンスの影響を与えるということです。

このようなスレッドを見つけるには、 StripFilter.stripという文字列を検索すればよい。 - 任意のスレッドダンプで5つ以上の発生がある場合、そのために応答速度が低下していることを認識してください。 実際にStripFilterを実行しているスタックトレースの先頭の下に含まれています:

"http-/0.0.0.0:8280-186" daemon prio=10 tid=0x00007f796c343250 nid=0x175e runnable [0x00007f7854134000]
   java.lang.Thread.State: RUNNABLE
    at java.nio.Buffer.checkIndex(Buffer.java:537)
    at java.nio.CharBuffer.charAt(CharBuffer.java:1238)
    at com.liferay.portal.kernel.util.KMPSearch.search(KMPSearch.java:218)
    at com.liferay.portal.kernel.util.KMPSearch.search(KMPSearch.java:200)
    at com.liferay.portal.servlet.filters.strip.StripFilter.processInput(StripFilter.java:410)
    at com.liferay.portal.servlet.filters.strip.StripFilter.strip(StripFilter.java:668)
    at com.liferay.portal.servlet.filters.strip.StripFilter.processFilter(StripFilter.java:399)

ジージップフィルタ

GZipFilterの仕事は、ブラウザに戻るレスポンスを圧縮して帯域幅を節約し、最終的にページのロード時間を短縮することです。
com.liferay.portal.servlet.filters.gzip.GZipFilter=true以下のプロパティで制御します。

GZipFilterはStripFilterの影響を説明する際に紹介しましたが、その中で、レスポンスをgzip化する方が有益であるため、StripFilterを有効にする必要はないと述べられています。 GZipFilter の代わりに gzipping という言葉を選んだのは意図的です。可能な限り、Liferay はコンテンツを gzipping する別の方法を推奨しています。 例えば、Apacheのmod_deflateは、別の箱にあることで、別のサーバーのCPUがレスポンスの圧縮に使えるという利点があります。 GZipFilterは、レスポンスの準備が整った時点ですべてを圧縮するのが主な仕事なので、捕まえるのは難しいです。 そのため、最後にGZip関連のJavaクラスが実行され、 GZipResponse$1 オブジェクトがロックされるなど、実行プロセスが見えるようになっています:

"http-/0.0.0.0:8280-324" daemon prio=10 tid=0x00007f8bfc26f460 nid=0x2c01 runnable [0x00007f8aa7933000]
   java.lang.Thread.State: RUNNABLE
    at java.util.zip.Deflater.deflateBytes(Native Method)
    at java.util.zip.Deflater.deflate(Deflater.java:430)
    - locked <0x00000006dd9bf380> (a java.util.zip.ZStreamRef)
    at java.util.zip.Deflater.deflate(Deflater.java:352)
    at java.util.zip.DeflaterOutputStream.deflate(DeflaterOutputStream.java:251)
    at java.util.zip.DeflaterOutputStream.write(DeflaterOutputStream.java:211)
    at java.util.zip.GZIPOutputStream.write(GZIPOutputStream.java:146)
    - locked <0x00000006dd9bf2f8> (a com.liferay.portal.servlet.filters.gzip.GZipResponse$1)
    at com.liferay.portal.kernel.servlet.ServletOutputStreamAdapter.write(ServletOutputStreamAdapter.java:54)

前述の通り、GZipFilterに起因する、あるいはGZipFilterに関連するパフォーマンスの問題が現在ない場合でも、可能であればGZipFilterをオフにして他のサービスに依存することを推奨します。

パスワードハッシュ化

パスワードの比較は、ユーザーがフォームで指定したパスワードをハッシュ化し、データベースにあるすでにハッシュ化された(保存されている)パスワードと比較することによって行われます。 デフォルトでは、Liferayプラットフォームは、計算量の多いアルゴリズムを使用しています: PBKDF2 with HMAC SHA1 160ビットのハッシュと128,000ラウンドを使用しています。

以下は、暗号化アルゴリズムを設定するポータルのプロパティです。
passwords.encryption.algorithm=PBKDF2WithHmacSHA1/160/128000

CPUに負担をかけるのは、その仕組みに原因がある。 入力テキストに擬似ランダム関数とソルトを加えて-bitハッシュ(デフォルト:160)を作成し、それを何度も繰り返す(デフォルト:128,000)ものです。 その結果、多くのユーザーが同時にログインすると、CPUがすべてのリクエストを処理できるようになるまで、ポータルがしばらくロックされる可能性があります。 以下は、ユーザーログインに関わるスレッドのスタックトレースの先頭部分です:

"ajp--127.0.0.1-8009-198" daemon prio=10 tid=0x00007fcef1575600 nid=0xfe4d runnable [0x00007fced4138000]
   java.lang.Thread.State: RUNNABLE
    at com.sun.crypto.provider.HmacSHA1.engineReset(HmacSHA1.java:118)
    at javax.crypto.Mac.doFinal(Mac.java:547)
    at javax.crypto.Mac.doFinal(Mac.java:589)
    at com.sun.crypto.provider.PBKDF2KeyImpl.deriveKey(PBKDF2KeyImpl.java:178)
    at com.sun.crypto.provider.PBKDF2KeyImpl.(PBKDF2KeyImpl.java:121)
    at com.sun.crypto.provider.PBKDF2HmacSHA1Factory.engineGenerateSecret(PBKDF2HmacSHA1Factory.java:71)
    at javax.crypto.SecretKeyFactory.generateSecret(SecretKeyFactory.java:335)
    at com.liferay.portal.security.pwd.PBKDF2PasswordEncryptor.doEncrypt(PBKDF2PasswordEncryptor.java:77)

このスレッドのように、パスワードの暗号化に取り組んでいる複数のスレッドを見ることは、Liferayプラットフォームが苦しんでおり、ユーザーがログインしようとするときに長い待ち時間を報告する(またはすでに報告している)ことを示すものです。

この問題には、いくつかの解決策があります。

  1. PBKDF2アルゴリズムをチューニングする:
    1. 生成されるハッシュのサイズを下げるか、ラウンド数を下げるかのどちらかです。
    2. ただし、いずれもパスワードの暗号化強度が低下するため、その点も考慮する必要があります。
    3. ユーザーパスワードの強制リセットが必要な場合があります。
  2. 全く別のアルゴリズムを使用する:
    1. PBKDF2 から、より計算量の少ない別のアルゴリズムに移行することで、CPUへの影響も緩和されます。
    2. この場合、安全性の低い暗号に移行してしまうというデメリットもあります。
    3. ユーザーパスワードの強制リセットが必要な場合があります。
  3. LDAPやSSOソリューションのようなサードパーティーの認証サービスを使用する。
    1. 認証のプロセスはオフロードされ、Liferayプラットフォームは単に認証情報の送信やセッションの管理など、サードパーティーのサービスとのネゴシエーションを引き受けます。
この記事は役に立ちましたか?
2人中2人がこの記事が役に立ったと言っています