Symfonyのキャッシュ。 HTMLキャッシングのイデオロギー。 コンポーネントとパーシャル

symfonyを使用して2.5年以上、symfonyプログラマーによるhtmlキャッシュのアイデアの誤解の問題に常に対処しなければなりません。 この投稿の目的は、symfony開発者の頭脳にパーシャルとコンポーネントを使用するパラダイムの実現を伝えることです。







それでは、どのような問題について話しているのでしょうか。 フレームワーク、特にシンフォニーのアイデアを理解するようになった人は、少なくとも最適化について聞いたことがあります。 プロジェクト開発者が時期尚早な最適化を望んでいるため、symfonyキャッシュシステムが構築されているすべてのアイデアが失われます。



例として、最近立ち上げたプロジェクトを使用しますが、感情はまだ冷めていません。 プロジェクトの本質:燃えている安いチケットの販売。 多くの場合、更新された情報はRSSフィードに集約され、サイトのメインページに表示されます。 メインページは、サイトのメインインターフェイスであるフィルターを備えたリストです。最小限のリソースで、できるだけ早く機能する必要があります。



情報は非常に頻繁に更新されます。オファーは数日間関連しており、それらの情報は頻繁に変更される場合があります。 このような公開ブログは、特別に訓練された人々によって書かれていることが判明しました:)



簡単な実装に移りましょう。



例:旅行パッケージがあります-表A、ツアーオペレーターに関する情報があります-表B、ホテルに関する情報があります-表C。まだたくさんありますが、私たちにとっては重要ではありません。



PropelをORMとして使用します。



タスク:製品リストページを作成します。 リストには、観光客が行くホテルやサービスプロバイダー(ツアーオペレーター)に関する簡単な情報を表示する必要があります。 冷蔵庫や電話、または他に販売できるものをリストするときも同じです。メーカーに関する情報、製品カテゴリーに関する情報、サプライヤーに関する情報、配送に関する情報などです。最終行も同じです。データベースから多くのエンティティを選択する必要があります。 とにかくではありませんが、できればORMパラダイムを使用してください。



頭に浮かぶ標準的な解決策は、一意のエンティティキーをインデックスとして使用して、テーブルA、B、Cからオブジェクトのインデックス付き配列を選択することです。 そして、リストフローで制御フローが転送されるメインアクションで選択を行います。



モデルには、A、B、C、APeer、BPeer、CPeerのクラスがあると考えています。



リストは特定のフィルターで行われますが、その微妙な点はまったく気にしません。



  1. <?php
  2. class indexActions extends sfActions {
  3. パブリック関数executeListing(sfWebRequest $ r){
  4. $ f = new AFormFilter();
  5. $ f-> bind($ r-> getParameter( 'filter'));
  6. if($ f-> isValid()){
  7. $ c = $ f-> buildCriteria();
  8. $ this-> array_of_a = APeer :: doSelect($ c);
  9. $ this-> array_of_b = BPeer :: retrieveBySelectedA($ this-> array_of_a);
  10. $ this-> array_of_c = CPeer :: retrieveBySelectedA($ this-> array_of_a);
  11. } else {
  12. $ this-> getUser()-> setFlash( 'error'、 'データ取得中のエラー');
  13. }
  14. }
  15. }




ここでやったこと:リクエストから受け取ったフィルターに従って、sfFormFilterオブジェクトのAFormFilterインスタンスからCriteriaオブジェクトを返しました。 次に、テーブルAから選択されたすべてのエンティティのID、インデックス付きなどにより、テーブルBおよびCから必要なレコードを選択しました。 そして、BPeerクラスとCPeerクラスで作成したretrieveBySelectedAメソッドでこれをすべて行いました。 データベースへの3つのターゲット呼び出し。 悪くない。



次に、/ actions / index / templates / listingSuccess.phpを作成し、そこに要素のリストを表示します。



もちろん、最大限の抽象化のためにコードを非表示にすることはできますが、これは行いません。アイデアは私たちにとって重要です。



キャッシュをオンにします。 最初に直面するのは、パラメーター(GETまたはPOST要求)がある場合、アクションがキャッシュされないことです。 OK、何をすべきか:コンポーネント内のデータベースにリクエストを行い、到着したパラメータの配列のみを渡します。



リストページがなく、通常のインデックスページがある場合。 /actions/index/config/cache.ymlで次のパラメーターを指定することにより、レイアウトですべてをキャッシュできます。



  1. リスト:
  2. 有効:オン
  3. with_layout:true




これは、鉄筋コンクリートの静的ページに適しています。 しかし、承認がある場合、ヘッダーはユーザーごとに明らかに異なります。 ページ全体をキャッシュすることはできません。 結論-すべてをパーシャルとkmコンポーネントに配置する。



Symphonyは、PartialHelperを介して追加されたすべてのページ要素(include_partialおよびinclude_component)をキャッシュするという点で注目に値します。 同時に、一般にすべての要素をキャッシュします。親要素のキャッシュがクリアされている場合、子要素のキャッシュは保存されます。



それで、リストページに戻ってください。 別のコンポーネントで選択を取り出します。



  1. <?php
  2. / * actions.class.php * /
  3. class indexActions extends sfActions {
  4. パブリック関数executeListing(sfWebRequest $ r){
  5. $ this-> filterParams = $ r-> getParameter( 'filter');
  6. }
  7. }
  8. / * actions.class.phpの終わり* /
  9. / * components.class.php * /
  10. class IndexComponents extends sfComponents {
  11. パブリック関数executeListingBlock(){
  12. $ f = new AFormFilter();
  13. $ f-> bind($ this-> filterParams);
  14. if($ f-> isValid()){
  15. $ c = $ f-> buildCriteria();
  16. $ this-> array_of_a = APeer :: doSelect($ c);
  17. $ this-> array_of_b = BPeer :: retrieveBySelectedA($ this-> array_of_a);
  18. $ this-> array_of_c = CPeer :: retrieveBySelectedA($ this-> array_of_a);
  19. } else {
  20. $ this-> getUser()-> setFlash( 'error'、 'データ取得中のエラー');
  21. }
  22. }
  23. }
  24. / * components.class.phpの終わり* /




  1. / * ListingSuccess.php * /
  2. <?php include_component( 'index'、 'listingBlock'、 array ( 'filterParams' => $ filterParams))?>
  3. / * ListingSuccess.phpの終わり* /




さて、同じフィルターパラメーターに対して、キャッシュされたテンプレートをプルアップするため、ターゲットリクエストの数は0です。新しいパラメーターの場合は、まだ3です。



新しい製品が追加されるまで、すべては問題ありません。 適切な人として、管理パネルの保存アクションに次のコードをハングアップします。



  1. <?php
  2. $ configuration = ProjectConfiguration :: getApplicationConfiguration( 'frontend'、 'prod'、false);
  3. sfContext :: createInstance($ configuration、 'frontend');
  4. $ cacheManager = sfContext :: getInstance( 'frontend')-> getViewCacheManager();
  5. $ cacheManager-> remove( '@ sf_cache_partial?module = index&action = _listingBlock&sf_cache_key = *');




キャッシュがクリアされます。 すべてのリクエストについて、再びデータベースへの3つの呼び出し。



システムは安全に動作しています。 もちろん、実際にはすべてがより複雑です。 最初に遭遇した問題は、ORMが大量のメモリを消費していたことです。 彼らは、必要な値のみを選択して、通常のSQLクエリに変換することで最適化を試みました。 抽象化の欠如に問題がありました-私はまだフレームワークによって課せられたコード標準に賛成であり、自転車を発明するのは好きではありません。 教科書とまったく同じようにすべてが判明しました。人件費が高い一人が書いたコードは、他の開発者によって修正されました。 適切に抽象化されたORMを使用したかったのです。



当然、提案ごとに個別のコンポーネントで追加リクエストを行いながら、各提案のキャッシュに到達しました。



  1. / * components.class.php * /
  2. class IndexComponents extends sfComponents {
  3. パブリック関数executeListingBlock(){
  4. $ f = new AFormFilter();
  5. $ f-> bind($ this-> filterParams);
  6. if($ f-> isValid()){
  7. $ c = $ f-> buildCriteria();
  8. $ this-> array_of_a = APeer :: doSelect($ c);
  9. } else {
  10. $ this-> getUser()-> setFlash( 'error'、 'データ取得中のエラー');
  11. }
  12. }
  13. パブリック関数executeOfferItem(){
  14. $ this-> b = $ this-> a-> getBRelatedByB();
  15. $ this-> c = $ this-> a-> getCRelatedByC();
  16. }
  17. }
  18. / * components.class.phpの終わり* /




  1. / * _listingBlock.php * /
  2. <? foreach($ array_of_a as $ a):?>
  3. <? include_component( 'index'、 'offerItem'、 array ( 'a' => $ a))?>
  4. <? endforeach?>












はい、一目で犯罪に行きました。 私は不注意に各要素についてデータベースに2回アクセスします。そのため、システム全体の規模について考えたくない同僚を繰り返し批判しています。



すべてを超簡単にしましょう:



5つのフィルターがあり、それぞれに3つのオプションがあり、平均サンプルは100の結果を返します。 更新は5分ごとに行われます(1レコードの追加/変更)。 このページには1日あたり5万のホストがあり、1時間あたり2000のホストの偶数アカウントがあります。



最初のオプションを覚えていますか? -オプションごとに3つのリクエスト。 すべてのオプションを網羅するために、45〜= 50件のリクエストがあります。 その後、5分間で最大150台のホストがある場合、データベースへの最大50件のリクエストとキャッシュからの100回の読み取りを考慮します(もちろん、5分ごとにすべてのオプションがカバーされることを考えると、非常に誇張します)。 1日の場合:50 * 12 * 24〜=ベースからのターゲット読み取り値の15k。



1つの文をキャッシュする2番目のオプション:

満たす必要があるすべてのオプションをカバーするために:15オプション*(1リクエスト+ 100オファー* 2追加リクエスト)= 3015のターゲット測定値。 5分ごとに変更が発生します。

3リクエスト* 12 * 24〜= 900リクエスト。 1日あたりの合計では、データベースから約4kの対象読み取り値があります。



この非自明な方法で、リクエストの数を4倍減らしました。 ユーザーが絶えず情報を更新するようなブログがある場合-更新期間が大幅に短縮され、より詳細なキャッシングを使用するメリットが顕著になり、すでに注文に移行しています。



ページ生成の時間について話すのが怖いです、それはすべて設定に依存します。 このプロジェクトでは、ViewCacheにXCacheを使用しました。



  1. / * factories.yml * /
  2. view_cache:
  3. クラス:sfXCacheCache
  4. param:
  5. automaticCleaningFactor:0
  6. storeCacheInfo:true




もちろん、標準のsfFileCacheを使用する場合のページ生成時間は、比較にならないほど長くなります。 しかし、システム構成の問題は別としておきましょう。



データベースへの対象となる呼び出しの数の増加に加えて、非常に明白な透過的なロジックと最大の抽象化を備えたコードがありますが、その利点を説明する価値はないと思います。



symphonyでORMを使用する場合、リクエスト自体キャッシュはデフォルトでは提供されません。 これは、開発者がバカだからではなく、ここでは異なるレベルのキャッシュが使用されているためです。 別のアイデア。



このアプローチの短所には、キャッシュからアイテムを削除する非常に複雑なロジックが含まれることがあります。 ユーザーがアップロードしたビデオに関するデータを変更するとしますが、リストページ(すべてのオプション)、詳細ページ、rss-feedの要素がある場合はその要素などで、このビデオのすべての要素のキャッシュをクリアする必要があります。 これらすべてについて考え、適切なタイミングで覚えておく必要があるため、推奨事項として、キャッシュを考慮してシステムをすぐに設計することをお勧めします。



このプロジェクトでは、管理パネルは物理的に別のコンテナに配置されているため、キャッシュの管理が難しくなっています。 したがって、このために単純なXMLRPCインターフェイスを作成する必要がありました。



コストのかかる操作を個別のコンポーネントに削除することが、HTMLキャッシングの主なアイデアです。 車輪を再発明する必要はありません。すべてがすでに機能しており、非常にシンプルで機能的です。 私のチームの全員がこれを理解したら、私は彼らとの誓いをやめるかもしれません:)



All Articles