クエリを実行した際にSalesforceに登録されたレコードが多すぎて結果が帰ってこない。そんな大規模データを扱う組織に関わったことは...ほとんどないのですが、そういったときにどのように対応すればいいかという話はいろんなところで聞くことができます。
インデックスやディビジョンを利用したり、ORDER BYの有無を考慮したり...
2013年4月頃にはベスト・プラクティスをまとめた「Salesforceでの大規模データの取り扱い」Webinarも行われ、動画やスライドが公開されています。
情報はいろいろ出ていて知識としては知ってはいるのですが、実際に試してみないと気づけないこともあると思うので今回は12万件のデータが登録された組織を使ってクエリの実行方法でどれだけパフォーマンスに影響がでるか確認してみることにしました。
さっそく次のクエリを実行して確認してみます。
おなじみのガバナ制限ですね。一度に取得できる件数は50000件までとなります。クエリを実行する際には理由がなければLIMITを指定するようにしましょう。
ではLIMITを指定してからクエリを実行してみます。
今度は無事に50000件取得することができました。
このときのクエリ実行時間は『408.15Millis』となりました。
次にORDER BYで取得項目をソートした場合です。
結果は『905.43Millis』です。
・・ORDER BYを付けるだけで元の倍近くになりました。改めて見てみると、ここまで違いがでるんですね。約10万件でこれなので100万件とか1000万件なんて組織で実行したらちょっと無理そうな感じです。
LIMIT 1で取得件数を絞ったときはどうなるのかなと思いちょっと確認してみました。
結果は『323.79』と、LIMIT 50000の時に比べて約半分の時間で取得することができました。取得件数が減っても案外クエリ実行時間には影響無いんじゃ..なんて思ったりしたのですが、そんなことはありませんでした。
ORDER BYがなければ『1.99』と更に早くなります。パフォーマンス向上のために取得件数を減らすというのは有効な手段みたいです。
では検索条件にインデックス項目を指定したケースを確認してみます。
IDを指定して検索 (LIMIT 50000)
IDを指定して検索 (LIMIT 1)
インデックス項目を条件にするとLIMIT 50000でもLIMIT 1でもほぼ同じ処理時間で検索を行うことができました。
続いてName項目を条件に検索してみます。
Nameを指定して検索 (LIMIT 50000)
Nameを指定して検索 (LIMIT 1)
Name項目をつかった検索もほぼ同じ時間で検索を実行できました。この検証行っていたときに思い出したのですが、同じ検索条件でも一回目の検索処理により二回目の検索の方が短時間での検索が可能になります。パフォーマンス測定の際には注意が必要です。
Nameを指定して検索 (初回検索時)
外部ID項目を条件に検索してみます。
LIMIT 50000件 - 1回目
LIMIT 50000件 - 2回目
LIMIT 1件 - 1回目
LIMIT 1件 - 2回目
初回の検索処理には時間がかかりましたがそれ以外はほぼ同じ時間となりました。外部ID項目もインデックスが貼られているため、検索条件に指定すると素早く検索が可能です。
ここまでインデックスが貼られた項目を対象としてきたので、次はインデックス対象外の項目を条件に指定したいと思います。
1回目
2回目
3回目
インデックス項目を検索したとき、約5〜8だったのが約60と大きく差がでました。
検索条件が複数でインデックス有りの項目と無しの項目を条件に指定した場合です。
1回目
2回目
ID項目など一意の条件を指定すれば、インデックス無し項目を条件に指定してもパフォーマンスは悪くなりませんでした。
実際に50000件のレコードを取得する際に、『408.15Millis』の処理時間が必要でした。取得するのがレコード情報ではなく、count()で取得できる件数だった場合に処理時間に影響がでるかも確認してみます。
1回目
2回目
測定方法を何か間違えたかなというぐらい初回検索時の処理に時間がかかっていました。件数を取得するだけのイメージでしたが、件数集計処理は時間がかかるみたいです。それからcount()で件数を取得する場合もレコード取得と同じで最大50000件までとなります。LIMITを指定せずに実行して上限を超えるとガバナ制限のエラーが発生します。
この結果をみて、ひょっとしたらcount()ではなく普通にリストで取得してlist.size()で件数を取得した方がはやいんじゃないかとそちらも確認してみました。
1回目
2回目
1回目はcount()よりも短い時間で取得できました。しかし、2回目の処理時間はcount()よりも長い時間が必要になってしまいました。今回は検証のため、WHEREを特に指定せずに実行しましたが、インデックス項目を指定するなどきちんとした条件で検索を行えば、count()の方が効率が良さそうです。
まとめ
いろいろ検討することはあると思いますがクエリ実行時は以下のことに注意しておいた方がいいみたいです。
- 検索条件にはインデックス項目を指定する。
- 不要なORDER BYは指定しない。
- LIMITで取得件数を必要最低限に絞る。
- 件数の取得はcount()を使用する。
おまけ
ApexでSOQLクエリを実行したときに取得できる件数は最大50000件までです。しかし開発者コンソールのQuery Editorでクエリを実行した際には50000件を超えてレコードを取得できます。全件分画面に表示されているのかなと確認したところ、画面には2000件までしか表示されていませんでした。Query Editorの検索処理はレポートの検索処理と同じ条件で実行されるみたいです。