BLOG

  • トップ
  • ブログ
  • Solr9.7負荷テストレポート:SIMD最適化によるセマンティック検索パフォーマンス向上の評価

Solr9.7負荷テストレポート:SIMD最適化によるセマンティック検索パフォーマンス向上の評価

著者: Mingchun Zhao

     

投稿日: 2024年10月08日  更新日: 2024年10月16日

 
  • Solr
  • セマンティック検索
  • SIMD最適化
  • ベクトル検索
  • パフォーマンス

PDFをダウンロード

PDFをダウンロード

目次

はじめに

2024年9月9日に公開されましたApache Solr 9.7.0にはセマンティック検索(ベクトル検索)におけるパフォーマンス向上が含まれています。詳細は、弊社ホームページの記事Apache Solr 9.7.0が公開されました!からご確認いただけます。

Apache Luceneが9.11.1にアップグレードされ、Java 21を使用したセマンティック検索などで大幅なパフォーマンス向上を実現しました。

このパフォーマンス向上は、Incubating Panama Vector APIの統合により実現され、Java 20とJava 21でベクトル計算におけるSIMD最適化を可能にしています。この機能をデフォルトで有効にするため、SolrのJavaコマンドにオプション--add-modules jdk.incubator.vectorが追加されました。

SIMD最適化とは

Single Instruction, Multiple Data(単一命令で複数データを処理)の略で、同じ操作を複数データに同時に適用することで、計算効率を高める手法です。SIMDは科学技術計算や画像音声処理など、大量データを一度に処理する必要があるタスクにおいて効果を発揮します。

SIMD最適化のポイント

  • ハードウェアのサポート
    • SIMDの専用命令セットをサポートするCPUやGPUが必要となります(IntelのSSE、AVX、ARMのNEONなど)
  • アルゴリズムやデータ構造のベクトル化
    • SIMDを利用するためデータをベクトル化し効率的に並列処理を行えるようにする必要があります
    • これにより実装やデバッグの難易度が高くなる場合があります
  • データ並列処理によるパフォーマンス向上
    • 同じ演算を複数データに対し同時実行することで、処理時間を短縮しパフォーマンスが大幅に向上します
    • システムリソースを効率的に活用し、アプリケーションの性能を向上させるための強力な手法です

SolrでSIMD最適化を利用するメリット

Apache Solrにおいて、SIMD最適化は検索パフォーマンスを大幅に向上でき、特にセマンティック検索において効果を発揮します。SIMD最適化により、複雑なクエリ処理や大規模データセットに対し高速かつ効率的な検索を実現できます。

  • テキスト解析の性能向上
    • トークナイズや正規化プロセスが高速化され、全体的なパフォーマンスが向上します
  • クエリの性能向上
    • ベクトル計算が高速化されセマンティック検索のパフォーマンスが向上します
    • スコアリングやフィルタリングなどを並列処理でき、検索クエリのレスポンスタイムが短縮されます
  • スケーラビリティの向上
    • SIMDに適した大規模データセットにより性能が改善され、スケーラビリティが向上します
  • ユーザー体験の向上
    • パフォーマンス向上により、迅速に関連性の高い結果を受け取ることができ、ユーザ満足度が向上します

Solrベクトル計算におけるSIMD最適化の導入に関するチケット

セマンティック検索とその設定方法に関しましては、弊社クラウド型検索エンジンサービスKanadaSearchのドキュメントセマンティック検索とはをご参照ください。

目的

ベクトル計算性能におけるSIMD最適化の効果を評価します

特にSIMD最適化の有効化と無効化のパフォーマンス比較を目的としたため、ワークロードとして大規模データセットを使った長時間検証ではなく、短時間で実行可能なテストケースを使用し、より多くの検証パターンでパフォーマンス比較できるようにしました。具体的には、以下の視点からテストケースを準備しました。

  • Java 20とJava 21でSIMD最適化を適用した場合、Solrベクトル計算のパフォーマンスがどれだけ向上するか?
  • Java 11からJava 21にバージョンアップした場合、Solrベクトル計算のパフォーマンスは向上するか?

テスト方法は、Solrへの同時実行数とクエリ負荷を増やしながら、レスポンスタイムとリソース使用率(CPU/MEMORY/DISK I/O)を測定し、測定結果を用いてパフォーマンス比較を定量化、可視化します。

結論

セマンティック検索のパフォーマンスを以下の3つのJavaバージョンを使ってそれぞれ計測し、その結果を比較しました。

  • ケース1:Java 11でSolrを実行します
  • ケース2:Java 21(ベクトル最適化無効)でSolrを実行します
    • Javaコマンドから--add-modules jdk.incubator.vectorオプションを削除します
  • ケース3:Java 21(ベクトル最適化有効)でSolrを実行します
    • Solrデフォルト設定でJavaコマンドに--add-modules jdk.incubator.vectorオプションが付与されています

その結果、

  • Solrのセマンティック検索でSIMD最適化を無効から有効に変更した場合、10%〜30%のパフォーマンス向上が確認できました
  • Solrが使用するJavaをJava 11からJava 21にアップグレードした場合、パフォーマンス向上を確認できませんでした

検証に使用したAmazon EC2インスタンススペックとSolrヒープサイズ

EC2 instance Processor vCPU Memory Storage Network performance SOLR_JAVA_MEM On-Demand hourly rate
t4g.small ArmベースのAWS Graviton2(Armベース) 2 2 GiB EBS Only Up to 5 Gigabit "-Xms512M -Xmx925M" $0.0168
t4g.medium ArmベースのAWS Graviton2(Armベース) 2 4 GiB EBS Only Up to 5 Gigabit "-Xms512M -Xmx1920M" $0.0336
t4g.large ArmベースのAWS Graviton2(Armベース) 2 8 GiB EBS Only Up to 5 Gigabit "-Xms512M -Xmx3909M" $0.0672
t4g.xlarge ArmベースのAWS Graviton2(Armベース) 4 16 GiB EBS Only Up to 5 Gigabit "-Xms4G -Xmx8G" $0.1344
c7g.large AWS Graviton3(コンピューティング最適化のARMベース) 2 4 GiB EBS Only Up to 12500 Megabit "-Xms512M -Xmx1920M" $0.0723
c7g.xlarge AWS Graviton3(コンピューティング最適化のARMベース) 4 8 GiB EBS Only Up to 12500 Megabit "-Xms512M -Xmx3909M" $0.1445

レスポンスタイム(ResponseTime): リクエストを発行してからレスポンスが返却されるまでかかった時間(秒)

  • 以下の条件で検証を行いました

    • セマンティック検索クエリの入力ベクトルは7367種類
    • JMeterのループカウントを7367で検証
    • JMeterの同時ユーザ数を20まで検証
    • topKは1000
  • SIMD最適化有効時とSIMD最適化無効時のレスポンスタイム比較

ComparisonofResponseTimes(sec)withSIMDOnorOffforAllEC2Instances

  • 全EC2インスタンスタイプでレスポンスタイムをソートした結果

ResponsSortingofResponseTimes(sec)forAllEC2InstanceswithSIMDOnorOffeTimeReductionRateByOptimization

  • 考察

    • CPUコア数が多いほど、ベクトル計算の同時実行数が増えるためセマンティック検索性能が向上しました
      • 同じリクエスト数においてレスポンスタイムが一番短く検索が速かったのはコンピューティング最適化インスタンスc7g.xlargeでした
      • t4g.xlarge(vCPU4/MEM16GiB)をt4g.large(vCPU2/MEM8GiB)と比べた場合、レスポンスタイムが半分に短縮されました
    • EC2のコンピューティング最適化インスタンスが一般用途向けと比べプロセッサーパフォーマンスが優れていました
      • c7g.large(vCPU2/MEM4GiB)をt4g.large(vCPU2/MEM8GiB)と比べた場合、レスポンスタイムが27%短縮されました。CPUコア数が同じで、メモリはt4gが倍ですが、プロセッサー性能が優れているc7g.largeが速くなりました
        • ネットワークはc7g.largeがt4gより倍速いですが、今回ワークロードのネットワーク転送量から影響しません
      • c7g.xlarge(vCPU4/MEM8GiB)をt4g.xlarge(vCPU4/MEM16GiB)と比べた場合、レスポンスタイムが17%短縮されました
    • メモリ容量とSolrヒープサイズもセマンティック検索性能に寄与しているが、CPUほどではありませんでした
      • t4g.large(vCPU2/MEM8GiB)をt4g.medium(vCPU2/MEM4GiB)と比べた場合、レスポンスタイムが2%しか速くならなかったです
      • t4g.medium(vCPU2/MEM4GiB)をt4g.small(vCPU2/MEM2GiB)と比べた場合、レスポンスタイムが15%速くなりましたが、これはクエリ負荷を処理するのにメモリ2GiB、Solrヒープ1GiBでは不十分であることを示唆しています
  • SIMD最適化によるレスポンスタイム短縮率の比較

ComparisonoftheRateofResponseTimeReductionAchievedbySIMD

  • 考察

    • SIMD最適化の適用によりベクトル計算性能の向上を確認できました
      • Java 21(最適化有効)をJava 21(最適化無効)と比べた場合、レスポンスタイムが10%〜30%短縮されました(平均で16%短縮)
  • topKによるレスポンスタイムの比較

ComparisonofResponseTimes(sec)withTopK

  • 考察

    • セマンティック検索のtopK値が大きくなるにつれ、レスポンスタイムが伸びていました
      • セマンティック検索でtopK値によりベクトル計算量が増えることを示唆しています
  • 同時実行数によるレスポンスタイムの比較

ComparisonofResponseTimes(sec)withConcurrency

  • 考察
    • 同時ユーザ数が増えるにつれ、パフォーマンス向上幅が大きくなりました
      • t4g.xlarge(vCPU4/MEM16GiB)で1ユーザー実行時のレスポンスタイム短縮率は10%、5ユーザー同時実行時は15%でした
        • topコマンドのSolrプロセスのCPU使用率を比較すると、1ユーザーでは1.5のvCPUしか使えておらず、5ユーザーでは4vCPUを使い切っていました
        • 1ユーザーと比べ5ユーザーでベクトル計算の同時実行数が増えたため、パフォーマンス向上幅もアップしたと考えられます

QPS(Quries/sec): 1秒間に処理したリクエスト件数

  • 以下の条件で検証を行いました

    • セマンティック検索クエリの入力ベクトルは7367種類
    • JMeterのループカウントを7367で検証
    • JMeterの同時ユーザ数を20で検証
    • topKは1000
  • SIMD最適化有効時とSIMD最適化無効時のQPS比較

ComparisonofQPSwithSIMDOnorOffforAllEC2Instances

  • 全EC2インスタンスタイプでQPSをソートした結果

SortingofQPSforAllEC2InstanceswithSIMDOnorOff

  • 考察
    • 同時実行数が増えるにつれQPSが増えていきますが、最大値に達したらその後は変化しませんでした

Javaバージョンによるセマンティック検索性能の違い

  • Javaバージョンによるベクトル計算性能の違いは確認できませんでした
    • Java 21(ベクトル最適化無効)をJava 11と比べた場合、レスポンスタイムはほぼ同じでした

システムリソース

CPU使用率

  • SIMD最適化の有効化/無効化によるCPU使用率の比較

ComparisonofCPUUsageswithSIMDOnorOff

  • 考察

    • SIMD最適化によるCPU使用率上昇はほとんど見られませんでした
  • 同時実行数によるCPU使用率の比較

ComparisonofCPUUsageswithConcurrency

  • 考察
    • 同時実行数が1の場合、Solrは最大1.5のvCPUしか使っていませんでした
    • 同時実行数が増えていくにつれ、CPU使用率が上昇し、同時実行数がvCPU数に達したら100%近くまで上昇しました
    • SolrプロセスのCPU使用率が100%に達している場合、ベクトルの並列計算にすべてのCPUコアがフルで使用されていることを示唆します

メモリ使用率

  • SIMD最適化の有効化/無効化によるメモリ使用率の比較

ComparisonofMemoryUsageswithSIMDOnorOff

  • 考察

    • SIMD最適化によるメモリ使用率上昇はほとんど見られませんでした
  • 同時実行数によるメモリ使用率の比較

ComparisonofMemoryUsageswithConcurrency

  • 考察
    • 同時実行数によるメモリ使用率上昇はほとんど見られませんでした
    • メモリ不足によりスワップアウトが発生するとパフォーマンスが著しく低下するため、負荷テスト全般でOSバッファキャッシュが枯渇しないようにワークロードを調整しました

Disk I/O使用率

  • 考察
    • 負荷テスト全般において、ディスクビジーによるI/O待ち状態は発生せず、I/O負荷が検索性能のボトルネックになることはありませんでした

考慮事項

検証サーバーのハードウェアとOSカーネルがSIMD最適化をサポートするか確認

今回の検証ではAmazon EC2のt4g.smallインスタンスを使用したため、t4g.smallを例にハードウェアとOSカーネルのレベルでSIMD最適化のサポートを確認した手順を共有します。

ハードウェアがSIMD最適化をサポートするか確認

  • プロセッサ情報をlscpuコマンドを使って確認しました
    • Cpu architecture: aarch64
    • Vendor ID: ARM
    • Model name: Neoverse-N1
    • Flags: asimd(Advanced SIMD)がセットされていました
  • ARMドキュメントAbout-the-Advanced-SIMD-and-floating-point-supportからNeoverse-N1モデルがSIMDをサポートすることを確認しました
    The Neoverse™ N1 core supports the Advanced SIMD and scalar floating-point
    instructions in the A64 instruction set and the Advanced SIMD and 
    floating-point instructions in the A32 and T32 instruction sets.
    

OS(Kernel)がSIMD最適化をサポートするか確認

float配列の積算を行うCサンプルプログラムを、gccのコンパイルオプション-O3(最適化レベル)と-S(アセンブリコード生成)を使ってコンパイルしました。その結果、生成されたアセンブリコードからベクトルタイプのSIMD積算インストラクションを確認できました。

fmul    v2.4s, v2.4s, v6.4s
... ...
fmul    v0.4s, v0.4s, v4.4s

セマンティック検索のクエリで使用する入力ベクトルセットについて

入力ベクトルとクエリの事前用意

ベクトル計算におけるSIMD最適化によるSolrセマンティック検索パフォーマンス向上を正確に評価するため、負荷測定時に関連しない他の処理がリソースを使用しないように工夫が必要です。

セマンティック検索クエリに含まれる入力ベクトル7367種類をあらかじめ計算(セマンティック検索時にオンザフライで計算するのではなく)し、JMeter負荷掛け用クエリとして使用します。

クエリで使用する入力ベクトル配列のバリエーション

ベクトルデータセットのバリエーションやデータ分布はSIMD最適化の効果に大きく影響します。データセットが均等に分布している場合、並列処理の効果が最大限に発揮されます。逆に、データ分布が偏っている場合、特定の要素に処理が集中するため、全体の性能向上が難しくなる場合があります。

試しに100種類のベクトル(768次元)のみ用意し、セマンティック検索で繰り返し使用してみたところ、パフォーマンス測定結果が不安定で評価が難しかったため、最終的には7367種類のベトクル(768次元)を負荷テストで使用しました。

テストの実行環境と実行方法

  • 負荷掛けツールJMeterとSolrをAmazon VPCの同じサブネットにある二つのインスタンスに配置し、ネットワーク負荷による影響を抑えました
    • セマンティック検索中に大量ベクトルデータがネットワーク経由でSolrに送信され、ネットワーク帯域幅がボトルネックとなりQPSやResponseTimeなどパフォーマンス指標に影響する可能性があるためです
  • 複数テストケースの実行順序を変えながら負荷テストを行いました
    • 実行順序がパフォーマンス測定に影響を与える可能性があるためです
  • テストケースを実行するたびにSolrを再起動しました
    • セマンティック検索時Solrキャッシュの再利用などがパフォーマンス評価の妨げにならないようにするためです
  • Amazon EC2のバーストパフォーマンスインスタンス使用時の注意点
    • バーストパフォーマンスインスタンスには一定のCPUバーストクォータが存在するため、高負荷によりCPU周波数がベースライン周波数より高くなる時間帯が敷居を超えると、CPU動作が遅くなります
      • 検証途中にCPUクレジットが底をついてしまうと、バースト(ベースラインを超えたCPU使用率が使用できる状況)が機能せずSolrの検索性能が低下する可能性があります
    • EC2インスタンスのCPUクレジットの使用率とバランスを監視しながら、バランスが枯渇しないか確認する必要があります
      • CPU credit usage: The number of CPU credits spent during the measurement period.
      • CPU credit balance: The number of CPU credits that an instance has accrued. This balance is depleted when the CPU bursts and CPU credits are spent more quickly than they are earned.
    • バーストパフォーマンスインスタンスのCPUクレジットモードをUnlimitedモードにする方法もあります
      • Unlimitedモードにより、CPUクレジットが枯渇した状態でもバースト状態を維持できますが、vCPU時間あたり追加コストが発生します

テスト結果

結果マトリックスで各種項目の説明

  • Users: JMeterのNum_threads値で、同時実行数(同時ユーザ数)
  • QPS: JMeterのQPS値で、単位時間に処理されるクエリ数
  • ResponseTime: クエリ発行からレスポンスが返却されるまでかかった時間(秒)
  • %CPU: システムのCPU使用率(vmstatコマンドで計測)
  • %MEM: システムのメモリ使用率(vmstatコマンドで計測)
  • %I/O: システムのDisk I/O使用率(iostatコマンドで計測)

検索性能

負荷測定方法

  • JMeterクエリに7367種類のベクトル値を使用した際の計測結果です
  • JMeterクエリのループカウントは7367固定にしました
  • 異なる同時実行数とJavaVerにおける負荷測定を2回ずつ実行し平均値を計算しました
  • 検証パターンは72件です

EC2インスタンス/Javaバージョン/同時実行数 におけるパフォーマンス比較(リソース使用率とレスポンスタイム短縮率)

Id EC2 Instance JavaVer SIMD Optimization Concurrency QPS ResponseTime(sec) Total %CPU Max Solr %CPU Max Total %MEM Max Solr %MEM Max Total %I/O Max ResponseTimeReductionRate(%) By Number Of Concurrent Users ResponseTimeReductionRate(%) By Vector Optimization ResponseTimeReductionRate(%) By Ec2 Instance with small ResponseTimeReductionRate(%) By Ec2 Instance with CPU cores ResponseTimeReductionRate(%) By Java Version
1 t4g.small Java11 off 1 103 72 75 74 57 40 1 0 0 0 0 0
2 t4g.small Java11 off 5 174 212 100 99 60 44 1 41 0 0 0 0
3 t4g.small Java11 off 10 175 421 100 99 60 44 1 42 0 0 0 0
4 t4g.small Java11 off 20 179 823 100 99 61 45 4 43 0 0 0 0
5 t4g.small Java21 off 1 102 73 71 70 63 44 1 0 0 0 0 -1
6 t4g.small Java21 off 5 171 215 100 99 64 44 1 41 0 0 0 -1
7 t4g.small Java21 off 10 179 413 100 99 63 44 2 43 0 0 0 2
8 t4g.small Java21 off 20 180 821 100 99 65 44 15 44 0 0 0 0
9 t4g.small Java21 on 1 111 67 68 67 60 44 0 0 9 0 0 0
10 t4g.small Java21 on 5 201 184 100 99 60 44 0 45 15 0 0 0
11 t4g.small Java21 on 10 209 353 100 99 60 43 1 47 15 0 0 0
12 t4g.small Java21 on 20 215 686 100 100 63 45 48 48 16 0 0 0
13 t4g.medium Java11 off 1 110 67 79 79 28 20 1 0 0 7 7 0
14 t4g.medium Java11 off 5 173 214 100 99 29 21 0 36 0 -1 -1 0
15 t4g.medium Java11 off 10 177 418 100 99 31 23 0 38 0 1 1 0
16 t4g.medium Java11 off 20 177 834 100 99 33 25 1 38 0 -1 -1 0
17 t4g.medium Java21 off 1 108 69 77 76 29 21 2 0 0 5 5 -3
18 t4g.medium Java21 off 5 172 214 100 99 29 21 2 38 0 0 0 0
19 t4g.medium Java21 off 10 176 420 100 100 29 21 2 39 0 -2 -2 0
20 t4g.medium Java21 off 20 176 838 100 100 29 21 1 39 0 -2 -2 0
21 t4g.medium Java21 on 1 134 55 75 73 29 21 0 0 20 17 17 0
22 t4g.medium Java21 on 5 231 160 100 99 29 21 0 42 25 13 13 0
23 t4g.medium Java21 on 10 247 299 100 99 30 21 0 46 29 15 15 0
24 t4g.medium Java21 on 20 253 583 100 99 30 21 0 47 30 15 15 0
25 t4g.large Java11 off 1 127 58 77 77 15 11 4 0 0 19 13 0
26 t4g.large Java11 off 5 202 182 100 99 16 12 1 37 0 14 15 0
27 t4g.large Java11 off 10 209 353 100 99 17 12 1 39 0 16 16 0
28 t4g.large Java11 off 20 214 687 100 99 23 19 1 41 0 17 18 0
29 t4g.large Java21 off 1 130 57 76 75 14 11 1 0 0 22 17 2
30 t4g.large Java21 off 5 199 185 100 99 16 12 1 35 0 14 14 -2
31 t4g.large Java21 off 10 204 361 100 100 17 12 7 37 0 13 14 -2
32 t4g.large Java21 off 20 215 684 100 100 16 12 26 40 0 17 18 0
33 t4g.large Java21 on 1 143 52 76 75 15 10 1 0 9 22 5 0
34 t4g.large Java21 on 5 241 154 100 100 16 12 1 41 17 16 4 0
35 t4g.large Java21 on 10 247 299 100 100 16 12 77 43 17 15 0 0
36 t4g.large Java21 on 20 259 570 100 100 17 12 80 45 17 17 2 0
37 t4g.xlarge Java11 off 1 126 59 38 37 22 20 1 0 0 18 -2 0
38 t4g.xlarge Java11 off 5 401 92 100 99 22 20 1 69 0 57 49 0
39 t4g.xlarge Java11 off 10 417 177 100 100 22 20 1 70 0 58 50 0
40 t4g.xlarge Java11 off 20 443 333 100 100 23 20 1 72 0 60 52 0
41 t4g.xlarge Java21 off 1 126 59 39 38 31 29 1 0 0 19 -4 0
42 t4g.xlarge Java21 off 5 405 91 100 99 31 29 0 69 0 58 51 1
43 t4g.xlarge Java21 off 10 425 174 100 100 31 29 0 71 0 58 52 2
44 t4g.xlarge Java21 off 20 431 343 100 100 31 29 0 71 0 58 50 -3
45 t4g.xlarge Java21 on 1 140 53 40 39 31 29 0 0 10 20 -2 0
46 t4g.xlarge Java21 on 5 482 77 99 99 31 29 0 71 15 58 50 0
47 t4g.xlarge Java21 on 10 506 146 100 100 31 29 0 72 16 59 51 0
48 t4g.xlarge Java21 on 20 518 285 100 100 31 29 1 73 17 58 50 0
49 c7g.large Java11 off 1 187 39 100 99 33 25 3 0 0 46 33 0
50 c7g.large Java11 off 5 322 115 100 99 28 21 1 41 0 46 37 0
51 c7g.large Java11 off 10 324 227 100 99 34 28 1 42 0 46 36 0
52 c7g.large Java11 off 20 336 439 100 99 34 28 1 44 0 47 36 0
53 c7g.large Java21 off 1 73 46 72 71 29 21 82 0 0 37 19 -18
54 c7g.large Java21 off 5 312 118 100 100 28 21 1 49 0 45 36 -3
55 c7g.large Java21 off 10 337 219 100 100 28 21 1 52 0 47 39 4
56 c7g.large Java21 off 20 344 429 100 100 29 21 1 53 0 48 37 2
57 c7g.large Java21 on 1 187 39 70 69 30 21 1 0 15 41 25 0
58 c7g.large Java21 on 5 348 106 100 99 30 21 1 46 10 42 31 0
59 c7g.large Java21 on 10 373 198 100 100 30 21 1 49 10 44 34 0
60 c7g.large Java21 on 20 383 385 100 100 30 22 2 51 10 44 32 0
61 c7g.xlarge Java11 off 1 203 37 39 38 16 12 1 0 0 49 37 0
62 c7g.xlarge Java11 off 5 625 59 99 99 16 12 1 68 0 72 36 0
63 c7g.xlarge Java11 off 10 677 109 100 99 16 12 1 71 0 74 38 0
64 c7g.xlarge Java11 off 20 708 208 99 99 18 15 1 72 0 75 38 0
65 c7g.xlarge Java21 off 1 206 36 99 99 17 13 1 0 0 51 39 3
66 c7g.xlarge Java21 off 5 638 58 99 99 18 13 2 68 0 73 36 2
67 c7g.xlarge Java21 off 10 669 110 100 99 17 13 2 69 0 73 37 -1
68 c7g.xlarge Java21 off 20 693 213 100 99 18 13 1 70 0 74 38 -2
69 c7g.xlarge Java21 on 1 231 32 39 38 15 11 7 0 11 52 40 0
70 c7g.xlarge Java21 on 5 766 48 99 99 18 14 5 70 17 74 38 0
71 c7g.xlarge Java21 on 10 812 91 100 99 18 14 4 72 17 74 38 0
72 c7g.xlarge Java21 on 20 849 174 100 99 18 14 4 73 18 75 39 0

検証環境

Amazon EC2インスタンス

JMeterとSolrがリソースを奪い合わないように、JMeterをSolrと分けて別サーバーにインストールします。 ただし、ネットワーク負荷が検証結果への影響を抑えるため、JMeterとSolrを同じVPCサブネットのEC2インスタンスに配置します。

  • JMeterサーバー用のEC2インスタンス

    • t4g.small
  • Solrサーバー用のEC2インスタンス

    • t4g.small
    • t4g.medium
    • t4g.large
    • t4g.xlarge
    • c7g.large
    • c7g.xlarge
  • OS: Ubuntu 20.04.6 LTS

検証手順

環境準備

SolrインスタンスにJava11, Java21をインストールし、切り替え可能に設定

$ wget -O- https://apt.corretto.aws/corretto.key | sudo apt-key add -
sudo add-apt-repository 'deb https://apt.corretto.aws stable main'

$ sudo apt-get update; sudo apt-get install -y java-11-amazon-corretto-jdk
$ wget -O - https://apt.corretto.aws/corretto.key | sudo gpg --dearmor -o /usr/share/keyrings/corretto-keyring.gpg && \
echo "deb [signed-by=/usr/share/keyrings/corretto-keyring.gpg] https://apt.corretto.aws stable main" | sudo tee /etc/apt/sources.list.d/corretto.list

$ sudo apt-get update; sudo apt-get install -y java-21-amazon-corretto-jdk
  • Java Versionの切り替え
$ sudo update-alternatives --config java

There are 3 choices for the alternative java (providing /usr/bin/java).

  Selection    Path                                           Priority   Status
------------------------------------------------------------
* 0            /usr/lib/jvm/java-21-amazon-corretto/bin/java   12100004  auto mode
  1            /usr/lib/jvm/java-11-amazon-corretto/bin/java   11100024  manual mode
  2            /usr/lib/jvm/java-21-amazon-corretto/bin/java   12100004  manual mode

Press <enter> to keep the current choice[*], or type selection number: 1
update-alternatives: using /usr/lib/jvm/java-11-amazon-corretto/bin/java to provide /usr/bin/java (java) in manual mode

$ java --version
openjdk 11.0.24 2024-07-16 LTS
OpenJDK Runtime Environment Corretto-11.0.24.8.1 (build 11.0.24+8-LTS)
OpenJDK 64-Bit Server VM Corretto-11.0.24.8.1 (build 11.0.24+8-LTS, mixed mode)

Solrインスタンスにiostatコマンドをインストール

$ sudo apt install sysstat

負荷テストツールJMeterをSolrとは別サーバーにインストール

  • JMeterをダウンロード
$ wget https://dlcdn.apache.org//jmeter/binaries/apache-jmeter-5.6.3.tgz
  • 解凍

    $ tar zxvf apache-jmeter-5.6.3.tgz
    
  • 試験的に、jmeterコマンドをレポート付きで実行してみます

    ./apache-jmeter-5.6.3/bin/jmeter -n -t test.jmx -Jusers=$1 -l jmeter-query.log -e -o ./jmeter-report
    
  • 試験的に、jmeterコマンドをレポートなしで実行してみます

    ./apache-jmeter-5.6.3/bin/jmeter -n -t test.jmx -Jusers=$1 -j jmeter-`date +'%Y%m%d%H%M%S'`.log
    

Solrの設定

セマンティック検索を行うための設定

Solrスキーマ定義にセマンティック検索用フィールドタイプとフィールドを追加する必要があります。

solr.DenseVectorFieldクラスの768次元ベクトルのフィールドタイプと当該フィールドタイプのフィールドを二つ追加しました。

  <fieldType name="knn_vector" class="solr.DenseVectorField" vectorDimension="768" similarityFunction="dot_product"/>
  <field name="body_vector" type="knn_vector" indexed="true" stored="true"/>
  <field name="title_vector" type="knn_vector" indexed="true" stored="true"/>

なお、クエリで使用する入力ベクトルの種類もベクトルのSIMD最適化の効果に影響するため、あらかじめ7367種類のベクトル値を計算し、それを入力ベクトルとするクエリを7367件用意しておきました。

セマンティック検索に必要なSolr設定の詳細については、弊社クラウド型検索エンジンサービスKanadaSearchのドキュメントApache Solrのセマンティック検索機能をご参照ください。

Solrのヒープサイズ

検証サーバーのスペックに合わせ、Solrのヒープサイズを以下のように設定しました。

SOLR_JAVA_MEM="-Xms512M -Xmx925M"

Solrログ設定

負荷テストで大量ログが出力されるとシステム負荷となるため、必要最小限のログサイズに変更しました。

  • ローテーションサイズと世代数を小さく設定します: /var/solr/log4j2.xml

    • <SizeBasedTriggeringPolicy size="1 MB"/>
    • <DefaultRolloverStrategy max="1"/>
  • ログレベルをエラーに変更、リクエストログを無効にします: /var/solr/solr.in.sh

    • Change log level: SOLR_LOG_LEVEL=ERROR
    • Disable request logging: SOLR_REQUESTLOG_ENABLED=false

ベクトルインデクシングの実施とセマンティック検索の試験

インデクシングに使用するデータを準備

KandaSearch拡張機能ライブラリーで提供している Livedoorニュースコーパス(embeddings)のデータを使用します。7367件のドキュメントにはすでに768次元ベクトルが付与されています。

Livedoorニュースコーパス(embeddings)を使ったセマンティック検索の詳細については、弊社クラウド型検索エンジンサービスKanadaSearchのドキュメントKandaSearchのセマンティック検索機能をご参照ください。

Solrコレクションを作成

KandaSearch拡張機能ライブラリーで提供している Livedoorニュースコーパス(embeddings)のコンフィグ を使って、Solrコレクションを作成します。

ベクトルデータのインデクシング

ベクトル付き Livedoorニュースコーパス(embeddings)のデータ をSolrへPOSTします。

$ /opt/solr/bin/solr post -c livedoor ./data/livedoor_embeddings.json

Posting files to [base] url http://localhost:8983/solr/livedoor/update...
Entering auto mode. File endings considered are xml,json,jsonl,csv,pdf,doc,docx,ppt,pptx,xls,xlsx,odt,odp,ods,ott,otp,ots,rtf,htm,html,txt,log
POSTing file livedoor_embeddings.json (application/json) to [base]/json/docs
1 files indexed.
COMMITting Solr index changes to http://localhost:8983/solr/livedoor/update...
WARNING: URLs provided to this tool needn't include Solr's context-root (e.g. "/solr"). Such URLs are deprecated and support for them will be removed in a future release. Correcting from [http://localhost:8983/solr] to [http://localhost:8983/].
Time spent: 0:01:30.047

ベクトル付きクエリの用意

セマンティック検索クエリに用いる入力ベクトルは上述 Livedoorニュースコーパス(embeddings)のデータ の全ドキュメントから768次元ベクトルを7367件すべて抽出します(クエリ作成に数時間かかる場合があります)。

  • livedoor_embeddings.jsonから7367件のbody_vector値を抽出します
% for i in {0..7366}; do command="jq -r '[.[] | .body_vector] | nth($i)' ../livedoor_embeddings.json > vector-$i.json" && eval $command ;done
  • 上記ベクトルをqパラメータに付与し、7367件のクエリを作成します
for i in {0..7366}; do echo "{\!knn f=body_vector topK=10}"$(cat vector-$i.json | tr -d '\n' | tr -d ' ') >> vector-query-7367.csv ;done
  • 作成されたクエリのサンプルです
{!knn f=body_vector topK=10}[-0.02621307782828808,-0.06952571868896484,-0.0034800975117832422,...(768次元)]

セマンティック検索が正常に行えるか確認します

  • ベクトルクエリを発行します
$ curl -g 'http://localhost:8983/solr/livedoor/query?q={!knn%20f=body_vector%20topK=10}[-0.02621307782828808,-0.06952571868896484,-0.0034800975117832422,...(768次元)]'
  • Solrから以下のレスポンスが返されます
    {
    "responseHeader":{
      "status":0,
      "QTime":11,
      "params":{
        "q":"{!knn f=body_vector topK=10}[-0.02621307782828808,-0.06952571868896484,...
    
      }
    },
    "response":{
      "numFound":10,
      "start":0,
      "numFoundExact":true,
      "docs":[{
        "id":"movie-enter-5978741.txt",
        "url":"http://news.livedoor.com/article/detail/5978741/",
        "category":"movie-enter",
        "title":"【DVDエンター!】... ...",
        "title_exact":"【DVDエンター!】... ...",
        "title_2g":"【DVDエンター!】......",
    ... ...
    

セマンティック検索の負荷測定を開始します

  • JMeterから異なる同時ユーザ数とループカウントを指定し負荷を掛けながら、QPS、ResponseTime、システムリソース使用率などパフォーマンスを測定します

  • Javaバージョンは、Java 11、Java 21(ベクトル最適化無効)、Java 21(ベクトル最適化有効)の三つに対しそれぞれ負荷計測を行います。

  • Java 21(ベクトル最適化無効)は、Solr起動コマンド/opt/solr/bin/solrから--add-modules jdk.incubator.vectorオプションをコメントアウトすることで実現しました

    #  if [[ "$JAVA_VER_NUM" -ge "20" ]] && [[ "$JAVA_VER_NUM" -le "21" ]] ; then
    #    SCRIPT_SOLR_OPTS+=("--add-modules" "jdk.incubator.vector")
    #    echo "Java $JAVA_VER_NUM detected. Incubating Panama Vector APIs have been enabled"
    # fi
    
  • 前回のテストケース実行が今回に影響しないように(リソース消費やSolrキャッシュなにより影響など)、新しくテストケース開始時はSolrを再起動します

テスト結果を確認

JMeterのパフォーマンス測定結果

vmstat/iostat/topコマンドのリソース測定結果

Solrログを確認

負荷テスト期間中にエラーが発生しなかったか確認します。特にメモリ不足によるOOM(OutOfMemory)エラーやプロセスkillが発生していないか確認します。

Solrログのエラー確認例です。

$ sudo grep -i err /var/solr/logs/* | grep -v " INFO "

システムログを確認

負荷テスト期間中にエラーが発生しなかったか確認します。特にメモリ不足によるOOM(OutOfMemory)エラーやプロセスkillが発生していないか確認します。

syslogのエラー確認例です。

$ sudo grep -i err /var/log/syslog

おわりに

Apache Solr 9.7.0を使った負荷テストを実施し、SIMD最適化によりセマンティック検索(ベクトル検索)のパフォーマンスが大幅に向上できたことを確認できました。 なお、セマンティック検索の負荷テストにおける検証手順や注意点、考察や気づきなども共有させていただきました、少しでも皆様のご業務の一助となれたら幸いです。

お見積もり・詳細は KandaSearch チームに
お気軽にお問い合わせください。

お問い合わせ