Symfony + HHVM + MongoDB + CouchDB + Varnishに負荷がかかると猛烈な勢いで





今日は、システムがどのように構築されたかについてお話したいと思います。現在、1日あたり100万人を超えるユニークビジター(APIリクエストを除く)がアクセスしています。 行こう...





ソースデータ



システムはSymfony 2.3上で実行され、 DigitalOceanドロップレット上で実行され、コメントなしで活発に動作します。





symfony



symfonyには素晴らしいkernel.terminateイベントがあります。 ここでは、バックグラウンドで、クライアントがサーバーから応答を受信した後、すべてのハードワークが行われます(ファイルへの書き込み、キャッシュへのデータの保存、データベースへの書き込み)。



ご存知のように、ロードされたすべてのSymfonyバンドルは何らかの形でメモリ消費を増加させます。 したがって、システムの各コンポーネントに対して、必要なバンドルのセットのみをロードします(たとえば、フロントエンドには管理バンドルは必要なく、APIには管理バンドルとフロントエンドバンドルなどは必要ありません)。 この例のロードされたバンドルのリストは、単純化のために縮小されていますが、実際には、もちろんもっと多くのバンドルがあります。



クラス/app/BaseAppKernel.php
<?php use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Config\Loader\LoaderInterface; class BaseAppKernel extends Kernel { protected $bundle_list = array(); public function registerBundles() { //     $this->bundle_list = array( new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), new Symfony\Bundle\SecurityBundle\SecurityBundle(), new Symfony\Bundle\TwigBundle\TwigBundle(), new Symfony\Bundle\MonologBundle\MonologBundle(), new Symfony\Bundle\AsseticBundle\AsseticBundle(), new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(), new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), new Doctrine\Bundle\MongoDBBundle\DoctrineMongoDBBundle() ); //   ,     if ($this->needLoadAllBundles()) { // Admin $this->addBundle(new Sonata\BlockBundle\SonataBlockBundle()); $this->addBundle(new Sonata\CacheBundle\SonataCacheBundle()); $this->addBundle(new Sonata\jQueryBundle\SonatajQueryBundle()); $this->addBundle(new Sonata\AdminBundle\SonataAdminBundle()); $this->addBundle(new Knp\Bundle\MenuBundle\KnpMenuBundle()); $this->addBundle(new Sonata\DoctrineMongoDBAdminBundle\SonataDoctrineMongoDBAdminBundle()); // Frontend $this->addBundle(new Likebtn\FrontendBundle\LikebtnFrontendBundle()); // API $this->addBundle(new Likebtn\ApiBundle\LikebtnApiBundle()); } return $this->bundle_list; } /** * ,     . *     dev-  text-     prod-, *     */ public function needLoadAllBundles() { if (in_array($this->getEnvironment(), array('dev', 'test')) || $_SERVER['SCRIPT_NAME'] == 'app/console' || strstr($_SERVER['SCRIPT_NAME'], 'phpunit') ) { return true; } else { return false; } } /** *      */ public function addBundle($bundle) { if (in_array($bundle, $this->bundle_list)) { return false; } $this->bundle_list[] = $bundle; } public function registerContainerConfiguration(LoaderInterface $loader) { $loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml'); } }
      
      







クラス/app/AppKernel.api.php
 <?php require_once __DIR__.'/BaseAppKernel.php'; class AppKernel extends BaseAppKernel { public function registerBundles() { parent::registerBundles(); $this->addBundle(new Likebtn\ApiBundle\LikebtnApiBundle()); return $this->bundle_list; } }
      
      







スニペット/web/app.php
 //        //  -    , //      $_SERVER['REQUEST_URI'] if (strstr($_SERVER['HTTP_HOST'], 'admin.')) { //  require_once __DIR__.'/../app/AppKernel.admin.php'; } elseif (strstr($_SERVER['HTTP_HOST'], 'api.')) { // API require_once __DIR__.'/../app/AppKernel.api.php'; } else { //  require_once __DIR__.'/../app/AppKernel.php'; } $kernel = new AppKernel('prod', false);
      
      







トリックは、すべてのバンドルをdev環境でのみロードし、prod環境でキャッシュがクリアされた瞬間にロードする必要があることです。





モンゴッド



Compose.io上のMongoDBがメインデータベースとして使用されます。 データベースをメインサーバーと同じデータセンターに配置します。Compose を使用すると、データベースをDigitalOcean に配置できます



ある時点で、クエリ全体が遅くなり、システム全体のパフォーマンスが低下し始めました。 問題は、適切にコンパイルされたインデックスの助けを借りて解決されました。 MongoDBのインデックスの作成に関するほとんどすべてのマニュアルでは、クエリが範囲選択操作($ in、$ gtまたは$ lt)を使用する場合、そのようなクエリではどのような状況でもインデックスは使用されません。

 {"_id":{"$gt":ObjectId('52d89f120000000000000000')},"ip":"140.101.78.244"}
      
      





したがって、これは完全に真実ではありません。 値の範囲を選択してクエリのインデックスを使用できるようにする、インデックスを作成するための汎用アルゴリズムを次に示します(アルゴリズムがそのような理由で、 ここで読むことができます )。

  1. まず、特定の値が選択されるインデックスにフィールドが含まれます。
  2. 次に、ソートするフィールド。
  3. そして最後に、範囲の選択に関係するフィールド。


そして出来上がり:









Couchdb



統計データをCouchDBに保存し、承認なしでJavaScriptを使用してクライアントに直接提供することが決定されました。再びサーバーをプルすることはありません。 以前は、このデータベースでは機能しませんでした。「CouchDBはWeb用に特別に設計されています」というフレーズが賄われていました。



すべてがすでにセットアップされ、負荷テストの時間が来たとき、書き込み要求のストリームでは、CouchDBが単に詰まっていることがわかりました。 ほとんどすべてのCouchDBマニュアルでは、頻繁に更新されるデータに直接使用することを推奨していませんが、もちろん、私たちはそれを信じず、信頼していました。 Memcachedでのデータの蓄積はすぐに行われ、短い間隔でCouchDBに転送されました。



CouchDBには、ドキュメントのリビジョンを保存する機能もあり、通常のツールを使用して無効にすることはできません。 彼らは急いで遅すぎるときにこれについて学びました。 特定の条件が発生したときに開始される圧縮手順は、古いリビジョンを削除しますが、それにもかかわらず、リビジョンのメモリを消費します。







FutonはCouchDB Web管理者で、匿名ユーザーを含むすべてのユーザーが/ _utils /で利用できます。 誰でも見つけられるデータベースを見ることができないようにする唯一の方法は、[httpd_db_handlers]セクションの次のCouchDB構成エントリを削除することです(管理者はドキュメントのリストを表示する機能も失います)。

 _all_docs ={couch_mrview_http, handle_all_docs_req} _changes ={couch_httpd_db, handle_changes_req}
      
      





一般に、CouchDBはリラックスすることを許可しませんでした。





Hhvm



メインコンテンツを準備するバックエンドはHHVM上で回転します。この場合、これは以前に使用されていたPHP-FPM + APCバンドルよりも何倍も高速で安定して動作します。 Symfony 2.3の利点は、HHVM と100%互換性があることです。 Debian 8に問題なくHHVMをインストールします。



HHVMがMongoDBベースとやり取りできるように、C ++で半分、PHPで半分実装されているMongofill for HHVM拡張機能が使用されます。 小さな バグが原因で、データベースクエリの実行時にエラーが発生した場合、次のものが除外されます。
致命的なエラー:クラスは未定義:MongoCursorException
ただし、これにより、拡張機能が本番環境で正常に機能することを妨げません。





ワニス



コンテンツをキャッシュして直接配信するには、ワニスモンスターを使用します。 ワニスが何らかの理由で定期的に子供を殺したという事実に問題がありました。 次のようになりました。



 varnishd[23437]: Child (23438) not responding to CLI, killing it. varnishd[23437]: Child (23438) died signal=3 varnishd[23437]: Child cleanup complete varnishd[23437]: child (3786) Started varnishd[23437]: Child (3786) said Child starts
      
      





これにより、キャッシュがクリアされ、システム全体の負荷が急激に増加しました。 判明したように、この動作の理由は非常に多く、治療のヒントと処方箋です。 最初は、/ etc / default /ニスの-p cli_timeout=30s



パラメーターで罪を犯しましたが、それはポイントではありませんでした。 一般に、非常に長い実験とパラメーターの列挙の後、ワニスが新しいアイテムを配置するためにキャッシュからアイテムを積極的に削除し始めた瞬間にこれが起こっていたことが確立されました。 経験的に、私たちのシステムでは、default.vclのberesp.ttlパラメーターが選択されました。これは、アイテムがキャッシュに保存された時間を担当し、状況は通常に戻りました。



 sub vcl_fetch { /* Set how long Varnish will keep it*/ set beresp.ttl = 7d; }
      
      





beresp.ttlパラメーターは、新しい要素がキャッシュ内のスペース(ヌードオブジェクト)を使い果たす前に、古い要素がキャッシュから削除される(期限切れのオブジェクト)ように設定する必要がありました。







キャッシュヒットの割合は、 91%前後で安定しています。







設定の変更を有効にするには、ワニスを再起動する必要があります。 リブートすると、すべての結果を伴うキャッシュがクリアされます。 Varnishを再起動してキャッシュを失うことなく、新しい構成オプションをロードできるトリックを次に示します。



 varnishadm -T 0.0.0.0:6087 -S /etc/varnish/secret vcl.load config01 /etc/varnish/default.vcl vcl.use config01 quit
      
      





config01-新しい構成の名前。たとえば、newconfig、reloadなど、任意に指定できます。





クラウドフレア



CloudFlareはこのすべてをカバーし、静的をキャッシュし、同時にSSL証明書を提供します。



一部のクライアントは、APIへのアクセスに問題がありました-チャレンジパッセージキャプチャを入力するリクエストを受け取りました。 判明したように、CloudFlareはProject Honey Potおよび他の同様のサービスを使用してサーバー(潜在的なスパマー)を追跡し、警告が出されました。 CloudFlareのテクニカルサポートは長い間、わかりやすいソリューションを提供できませんでした。 最終的に、CloudFlareパネルでセキュリティレベルを基本的にオフに切り替えるだけで、次のことが可能になりました。









おわりに



今のところすべてです。 プロジェクトの負荷は急速に増大し、分析とソリューションの検索には最小限の時間しかかからなかったため、現在の状態になっています。 誰かが上記の問題を解決するためのよりエレガントな方法を提供してくれたら感謝します。



All Articles