みなさん、こんにちは。
キャッシュは好きですか?私はもちろん、Cashは好きですが、Cacheも結構好きです。
一般的なシステムでも処理の効率化のために利用されるキャッシュ機構が、Winter'16からApexやVisualforceで利用可能となりました。それがプラットフォームキャッシュです。
プラットフォームキャッシュは非常に強力な処理効率化の手段ですが、その特徴や制限を把握した上で、上手に活用する必要があります(Cashもそうですかね)。そこで今回は、読者の皆様が立派なキャッシュ使いになれるよう、プラットフォームキャッシュの利用方法や制限、利用に適したシーンなどをご紹介したいと思います。
プラットフォームキャッシュとは
簡単に言えば、組織レベルまたはユーザセッションレベルで、Key-Value型で任意の値を保持することが出来る機能です。Valueにはプリミティブ型の値のみならず、各種Collectionやオブジェクトのインスタンスを持つことができます!!すごい!
組織レベルのキャッシュを組織キャッシュ、ユーザセッションレベルのキャッシュをセッションキャッシュと言います。そのままですね。
なお、組織キャッシュ、セッションキャッシュそれぞれでLeast Recently Used (LRU)アルゴリズムが適用されます。すなわち、「使われてから最も長い時間が経ったもの」からキャッシュから消される訳です。
Salesforceの従来の機能でも、キャッシュのような仕組みを持つことはできました。
- ビューステート
Visualforceにおいてページに<apex:form> コンポーネントが含まれる場合、ページ要求間でデータベースの状態を維持するために必要な情報は、暗号化されたビューステートとして保存されます。ただし、135KBという制限があるため、苦悶した経験のある方も多いのではないでしょうか。また、サーバ要求時に毎回送受信が行われるため、ネットワーク的にも負荷が掛かります。もちろん、組織キャッシュは実現できません。 - Cookie
Salesforceに限った機能ではありませんが(Apexでも扱えます)、古典的な手法ですね。セッションキャッシュに近いことができますが、ブラウザ設定で利用できない場合があります。またブラウザによりサイズ制限も発生するため、あまり大きな値を入れることはできませんし、ネットワーク負荷にもなります。また、組織キャッシュは実現できません。 - カスタム設定
SOQLを使用せずにカスタム設定メソッドでアクセスすれば、アプリケーションキャッシュとして利用可能です。こちらは、ユーザの状態を持つセッションキャッシュは実現できません。また、カスタム設定という名前の通り、通常はシステムの動作設定を共通的に持つための仕組みであり、動的にキャッシュを保持する目的に適しているとは言えません。 - カスタムオブジェクトに書き込んでおく
使い方次第で組織キャッシュにもセッションキャッシュにもできます。確実ではありますが、毎回SOQLでクエリする必要があり、パフォーマンスに問題があります。特にセッションキャッシュとして利用すると、大量のDMLが発行されますね。また、プリミティブ型ではない任意の構造をキャッシュしたい場合には、かなりの力技か、専用のカスタムオブジェクトが必要でしょう。
そういう訳で、プラットフォームキャッシュは従来の仕組みとは異なる、組織キャッシュとセッションキャッシュを簡単に利用できる、革新的な機能であることがお分かり頂けたでしょうか。
使ってみましょう
プラットフォームキャッシュを利用するためには、少なくとも1つのパーティションが必要です。パーティションは[設定] - [開発] - [プラットフォームキャッシュ]から作成・編集・削除が可能です。
パーティションとは、キャッシュを区分する枠と理解してください。パーティションごとに、キャッシュに利用する容量を設定可能です。現状Salesforce画面ではパーティションと区分という言葉が両方使われていて混乱しますが、本記事ではパーティションとして統一して呼びます。
さっそく1つパーティションを作ってみましょう。
この組織では10MBのキャッシュが利用可能でしたので、組織キャッシュに5MB、セッションキャッシュに5MBずつ割り当てて、デフォルトパーティションとして指定しました。Name(パーティション名)はこの後使うので、組織で一意の値を指定しましょう。名前空間プレフィクスは、独自の名前空間を付けているdeveloper組織の場合はその名前、そうでない場合はlocalが自動設定されます。
パーティションは必要に応じて複数作成することができます。ですが、現状では組織キャッシュとセッションキャッシュには0MBまたは5MBより大きい値を指定しないと作成することができません。つまり、10MBが利用可能な組織であれば、パーティション1とパーティション2を作りたい場合には、片方は組織キャッシュが5MBでセッションキャッシュは0MB、もう片方は組織キャッシュが0MBでセッションキャッシュが5MBにするより他にありません。
5MB以上の制限を掛けているのは何故でしょうか・・・。
ともあれ、1つキャッシュパーティションができれば、プラットフォームキャッシュを利用する準備ができました!
セッションキャッシュ
ユーザセッションが続く間保持されるキャッシュです。最大8時間で期限切れとなりますが、time-to-live値を指定した場合には、8時間を経過せずとも期限切れとなります。
値をセットする際には、Cache.Sessionクラスのput、値を取得する際にはCache.Sessionクラスのgetを使用します。非常に簡単なコードですので、まずはサンプルを見てみましょう。
// orderDateキーに値を格納し、参照するサンプル DateTime dt = DateTime.parse('10/31/2015 11:46 AM'); // 名前空間.パーティション名.キー で指定して値を格納する Cache.Session.put('local.Cache1.orderDate', dt); if (Cache.Session.contains('local.Cache1.orderDate')) { // オブジェクト型で返ってくるのでキャストが必要 DateTime cachedDt = (DateTime)Cache.Session.get('local.Cache1.orderDate') } // 同一名前空間のデフォルトパーティションならば、名前空間とパーティション名を省略可能 Cache.Session.put('orderDate', dt); if (Cache.Session.contains('orderDate')) { DateTime cachedDt = (DateTime)Cache.Session.get('orderDate'); } // 色々オプションを指定して値をセットするサンプル // Time to Live 値は3600秒 // あらゆる名前空間から参照可能「Cache.Visibility.ALL」 // 他の名前空間からは書き換え不可「true」 Cache.Session.put('SampleNameSpace.Cache1.totalSum', '500', 3600, Cache.Visibility.ALL, true);
キャッシュは名前空間.パーティション名.キーで指定します。
名前空間「local」はコードが実行中の現在の組織を表します。管理パッケージのApexクラスでlocalを指定すると、管理パッケージの名前空間ではなく実行組織を指定することになりますので、ご注意ください。
また、呼び出し元クラスと同一名前空間のデフォルトパーティションならば、名前空間とパーティションを省略し、キーのみ指定することも可能です。VisibilityはALLではない場合、「Visibility.名前空間」でアクセスを許可する個別の名前空間を指定することができます。
なお、1つのパーティションのみ利用する場合には、Cache.SessionPartitionクラスを利用すると便利です。
// 最初に名前空間とパーティション名を指定して、対象のパーティションを特定 Cache.SessionPartition sessionPart = Cache.Session.getPartition('myNs.myPartition'); // SessionPartitionのインスタンスに対して操作可能 if (sessionPart.contains('BookTitle')) { String cachedTitle = (String)sessionPart.get('BookTitle'); }
サンプルで紹介したメソッドの他に、Cache.Sessionクラスには以下のメソッドが用意されています。
メソッド | 用途 |
getCapacity() | セッションキャッシュの利用率を取得 |
getKeys() | 現在の名前空間で利用可能なキーを取得 |
isAvailable() | セッションキャッシュが使えるか判定 |
remove(key) | キーを指定してキャッシュ削除 |
セッションキャッシュが利用できない場合にはSessionCacheNoSessionExceptionが発生するようですので、isAvailableで確認するようにしましょう。※上記サンプルではチェックしていません(^-^;
Visualforceでセッションキャッシュを参照
Visualforceから$Cache.Sessionでセッションキャッシュを参照可能です。
<apex:outputText value="{!$Cache.Session.myNamespace.myPartition.key1}"/> <apex:outputText value="{!$Cache.Session.local.myPartition.numbersList.size}"/> <apex:outputText value="{!$Cache.Session.local.myPartition.myData.value}"/>
Apexと異なり、デフォルトパーティションであっても名前空間やパーティション名の省略はできず、キーを完全修飾名で指定する必要があります。名前空間の「local」は利用可能です。
また、キャッシュ値がメソッドやプロパティを持つ構造である場合には、.指定でメソッドやオブジェクトのプロパティにアクセス可能です。上記の例ではリスト型のキャッシュに対してList.size()を取得したり、myDataインスタンスのキャッシュに対してプロパティにアクセスしています。
組織キャッシュ
ユーザセッションとは関係なく、組織全体でアクセス可能なキャッシュです。time-to-live値により期限切れとなります。また、最大期限はセッションキャッシュと同じ8時間です。
利用方法はCache.Orgクラスを利用することを除いて、セッションキャッシュとほとんど同じです。特定のパーティションを利用する場合にはCache.OrgPartitionクラスが利用可能です。Chache.OrgクラスにはisAvailableがないことと、複数キーのSetで存在チェックが可能なcontains(keys)があることがメソッド上の異なる点です。
セッションキャッシュ、組織キャッシュのどちらも、利用方法はとても簡単ですね!
なお、セッションキャッシュでも組織キャッシュでも、Apexトランザクションが異常終了した場合には、そのトランザクション内でのキャッシュ操作はすべてロールバックされます。
キャッシュ利用が適切なシーン
プラットフォームキャッシュは決してパフォーマンス向上の万能ツールではありません。利用が適しているシーンについて考察してみましょう。
- セッションの間繰り返し利用する
セッションが続く間に同じクエリやデータ生成を幾度も行う場合、セッションキャッシュに格納しておくのが適しています。どのようなクエリやデータの種類であるかをキー値に指定すれば、一度実行した結果を繰り返し利用可能であるため、処理効率は飛躍的に向上するでしょう。 - 静的な値(変化の頻度が少ない)
キャッシュは特定時点の値を保存する性質のものであるため、情報の鮮度が重要な場合には適していません。情報の鮮度を意識するにはtime-to-live値の指定がありますが、厳密に最新の状態を常に利用するのであれば、キャッシュは向いていません。 - 抽出/計算にリソースを多く消費する
抽出や計算にリソースを多く消費するケースでは、再計算の必要がない場合にキャッシュ値を利用するようにロジックを組めば、大幅な処理効率の向上につながるでしょう。逆に、毎回計算しても大した負荷にならないものでしたら、キャッシュ化する必要はないかもしれません。バランスを検討しましょう。
この他にも様々な観点はあると思いますが、キャッシュを何にでも適用するのではなく、適切な用途であるか判断して使いましょう。
キャッシュ利用における注意点
キャッシュ利用を検討する場合に重要となる留意点が幾つかあります。
- キャッシュは永続するものではなく、必ず存在するものではない
期待するキャッシュ値が必ず存在すると信じているような、キャッシュに強く依存するコードを組んではいけません。あったら利用する、なければ自分で作って利用後キャッシュに格納しておく、というのがキャッシュに対する正しいスタンスです。これは本当に重要です。 - Visualforce Controllerのコンストラクタでは、キャッシュを操作不可
- キャッシュは同時操作を想定していない
例えば同一ユーザが複数ブラウザを開いた場合、セッションキャッシュはそれぞれのブラウザの操作で上書きしあうことになります。 - 匿名ブロックではサポートされない
例えば開発者コンソールでExecute Anonymousで実行しても、エラーになってしまいます。
最後に
Winter'16 により、我々は簡単で強力なキャッシュ機能を手に入れることができました。
キャッシュ使いとして十分な知識を得た皆様が、神速システムを目指してキャッシュを上手に活用されることをお祈りいたします。
ところで、ApexやVisualforceのみならず、数式項目でも利用できるような話がどこかでありました。次のリリースで実現されるかもしれませんね・・・。