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

この記事では、Liferay DXP 7.0 内で実行している CPU を集中的に使用するタスクが多すぎる場合に発生する一般的なパフォーマンスの問題について説明します。この問題に関する必要な情報とその解決方法については、引き続きお読みください。

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

  1. Minifier
  2. StripFilter
  3. GZipFilter
  4. Password Hashing

解決

加入者によっては、CPUに負荷のかかる多くのタスクが同時に実行されると、パフォーマンスの問題が発生する場合があります。これは一般化されたパターンであり、様々なユースケースから発生する可能性があります。直面している問題の深刻度に応じて、スレッドダンプには、そのようなタスクで動作している数十から数百のスレッドが含まれることがあります。顧客はCPU使用率が高くなり、応答時間が遅くなり、まれにLiferayプラットフォームが応答しなくなることがあります。

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

Minifier

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

Minifier はデフォルトで有効ですが、 portal-ext.propertiesで以下のプロパティを設定することで、無効にできます: 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への負担が軽減されるはずです。

SpritFilter

StripFilter は、CPU 使用率と引き換えに、最終結果としてファイルが小さくなるという点で Minifier に似ています。Liferay Portal 6.2 のコメントを参照してください。StripFilterは、出力されたコンテンツから空白行を削除します。これにより、ダイヤルアップ接続しているユーザーのページ レンダリングが高速化されます。
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

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をオフにして他のサービスに依存することを推奨します。

Password Hashing

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

以下は、暗号化アルゴリズムを設定するポータルのプロパティです。
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人がこの記事が役に立ったと言っています