tyoshikawa1106のブログ

- Force.com Developer Blog -

SFDC:【大量データ処理】SOQLクエリとパフォーマンス

クエリを実行した際にSalesforceに登録されたレコードが多すぎて結果が帰ってこない。そんな大規模データを扱う組織に関わったことは...ほとんどないのですが、そういったときにどのように対応すればいいかという話はいろんなところで聞くことができます。


インデックスやディビジョンを利用したり、ORDER BYの有無を考慮したり...
2013年4月頃にはベスト・プラクティスをまとめた「Salesforceでの大規模データの取り扱い」Webinarも行われ、動画やスライドが公開されています。


情報はいろいろ出ていて知識としては知ってはいるのですが、実際に試してみないと気づけないこともあると思うので今回は12万件のデータが登録された組織を使ってクエリの実行方法でどれだけパフォーマンスに影響がでるか確認してみることにしました。

f:id:tyoshikawa1106:20141128011300p:plain

f:id:tyoshikawa1106:20141128030437p:plain


さっそく次のクエリを実行して確認してみます。

f:id:tyoshikawa1106:20141128031453p:plain


おなじみのガバナ制限ですね。一度に取得できる件数は50000件までとなります。クエリを実行する際には理由がなければLIMITを指定するようにしましょう。

f:id:tyoshikawa1106:20141128030907p:plain


ではLIMITを指定してからクエリを実行してみます。

f:id:tyoshikawa1106:20141128031304p:plain


今度は無事に50000件取得することができました。

f:id:tyoshikawa1106:20141128031605p:plain


このときのクエリ実行時間は『408.15Millis』となりました。

f:id:tyoshikawa1106:20141128031826p:plain


次にORDER BYで取得項目をソートした場合です。

f:id:tyoshikawa1106:20141128032020p:plain


結果は『905.43Millis』です。

f:id:tyoshikawa1106:20141128032338p:plain

・・ORDER BYを付けるだけで元の倍近くになりました。改めて見てみると、ここまで違いがでるんですね。約10万件でこれなので100万件とか1000万件なんて組織で実行したらちょっと無理そうな感じです。


LIMIT 1で取得件数を絞ったときはどうなるのかなと思いちょっと確認してみました。

f:id:tyoshikawa1106:20141128033015p:plain


結果は『323.79』と、LIMIT 50000の時に比べて約半分の時間で取得することができました。取得件数が減っても案外クエリ実行時間には影響無いんじゃ..なんて思ったりしたのですが、そんなことはありませんでした。

f:id:tyoshikawa1106:20141128033428p:plain

f:id:tyoshikawa1106:20141128033339p:plain


ORDER BYがなければ『1.99』と更に早くなります。パフォーマンス向上のために取得件数を減らすというのは有効な手段みたいです。

f:id:tyoshikawa1106:20141128033727p:plain

f:id:tyoshikawa1106:20141128033736p:plain


では検索条件にインデックス項目を指定したケースを確認してみます。

IDを指定して検索 (LIMIT 50000)

f:id:tyoshikawa1106:20141128220820p:plain

f:id:tyoshikawa1106:20141128220809p:plain

IDを指定して検索 (LIMIT 1)

f:id:tyoshikawa1106:20141128220942p:plain

f:id:tyoshikawa1106:20141128220950p:plain

インデックス項目を条件にするとLIMIT 50000でもLIMIT 1でもほぼ同じ処理時間で検索を行うことができました。


続いてName項目を条件に検索してみます。

Nameを指定して検索 (LIMIT 50000)

f:id:tyoshikawa1106:20141128221543p:plain

f:id:tyoshikawa1106:20141128221832p:plain

Nameを指定して検索 (LIMIT 1)

f:id:tyoshikawa1106:20141128221726p:plain

f:id:tyoshikawa1106:20141128222006p:plain


Name項目をつかった検索もほぼ同じ時間で検索を実行できました。この検証行っていたときに思い出したのですが、同じ検索条件でも一回目の検索処理により二回目の検索の方が短時間での検索が可能になります。パフォーマンス測定の際には注意が必要です。

Nameを指定して検索 (初回検索時)

f:id:tyoshikawa1106:20141128222231p:plain


外部ID項目を条件に検索してみます。

f:id:tyoshikawa1106:20141128223746p:plain

LIMIT 50000件 - 1回目

f:id:tyoshikawa1106:20141128223829p:plain

LIMIT 50000件 - 2回目

f:id:tyoshikawa1106:20141128223856p:plain

LIMIT 1件 - 1回目

f:id:tyoshikawa1106:20141128223951p:plain

LIMIT 1件 - 2回目

f:id:tyoshikawa1106:20141128224016p:plain


初回の検索処理には時間がかかりましたがそれ以外はほぼ同じ時間となりました。外部ID項目もインデックスが貼られているため、検索条件に指定すると素早く検索が可能です。


ここまでインデックスが貼られた項目を対象としてきたので、次はインデックス対象外の項目を条件に指定したいと思います。

f:id:tyoshikawa1106:20141128225124p:plain

f:id:tyoshikawa1106:20141128225133p:plain:w300

1回目

f:id:tyoshikawa1106:20141128225234p:plain

2回目

f:id:tyoshikawa1106:20141128225303p:plain

3回目

f:id:tyoshikawa1106:20141128225329p:plain


インデックス項目を検索したとき、約5〜8だったのが約60と大きく差がでました。


検索条件が複数でインデックス有りの項目と無しの項目を条件に指定した場合です。

f:id:tyoshikawa1106:20141128225940p:plain

1回目

f:id:tyoshikawa1106:20141128230043p:plain

2回目

f:id:tyoshikawa1106:20141128230100p:plain


ID項目など一意の条件を指定すれば、インデックス無し項目を条件に指定してもパフォーマンスは悪くなりませんでした。


実際に50000件のレコードを取得する際に、『408.15Millis』の処理時間が必要でした。取得するのがレコード情報ではなく、count()で取得できる件数だった場合に処理時間に影響がでるかも確認してみます。

f:id:tyoshikawa1106:20141128230622p:plain

f:id:tyoshikawa1106:20141128230708p:plain:w300

1回目

f:id:tyoshikawa1106:20141128230745p:plain

2回目

f:id:tyoshikawa1106:20141128230905p:plain


測定方法を何か間違えたかなというぐらい初回検索時の処理に時間がかかっていました。件数を取得するだけのイメージでしたが、件数集計処理は時間がかかるみたいです。それからcount()で件数を取得する場合もレコード取得と同じで最大50000件までとなります。LIMITを指定せずに実行して上限を超えるとガバナ制限のエラーが発生します。

f:id:tyoshikawa1106:20141128231442p:plain:w300


この結果をみて、ひょっとしたらcount()ではなく普通にリストで取得してlist.size()で件数を取得した方がはやいんじゃないかとそちらも確認してみました。

f:id:tyoshikawa1106:20141128232021p:plain

1回目

f:id:tyoshikawa1106:20141128232054p:plain

2回目

f:id:tyoshikawa1106:20141128232131p:plain

1回目はcount()よりも短い時間で取得できました。しかし、2回目の処理時間はcount()よりも長い時間が必要になってしまいました。今回は検証のため、WHEREを特に指定せずに実行しましたが、インデックス項目を指定するなどきちんとした条件で検索を行えば、count()の方が効率が良さそうです。

まとめ

いろいろ検討することはあると思いますがクエリ実行時は以下のことに注意しておいた方がいいみたいです。

  • 検索条件にはインデックス項目を指定する。
  • 不要なORDER BYは指定しない。
  • LIMITで取得件数を必要最低限に絞る。
  • 件数の取得はcount()を使用する。

おまけ

ApexでSOQLクエリを実行したときに取得できる件数は最大50000件までです。しかし開発者コンソールのQuery Editorでクエリを実行した際には50000件を超えてレコードを取得できます。全件分画面に表示されているのかなと確認したところ、画面には2000件までしか表示されていませんでした。Query Editorの検索処理はレポートの検索処理と同じ条件で実行されるみたいです。

f:id:tyoshikawa1106:20141128233026p:plain