tech.kayac.com

スマートフォンのネイティブアプリをメインにサービス展開しているLobiですが、 Webブラウザからも利用することができます。

https://web.lobi.co

Webブラウザからでもチャットの投稿・閲覧が行えます。

このサイトは AngularJS を用いて実装されています。

AngularJSとは

AngularJSはGoogleが提供しているJavaScriptライブラリです。

https://angularjs.org/

Webページ内でのユーザーのアクションに対応するコンテンツ生成や表示変更などをJavaScriptで強力に実現します。

SEOとの相性

AnguraJSに限らず、JavaScriptで動的にコンテンツを生成するWebページには、 SEOとの相性が悪いという欠点があります。 これは検索エンジンのクローラーがJavaScriptの実行までは行わないことに起因しています。 真っ白なページにJavaScriptでコンテンツを追加していくような作りになっている場合、 クローラーからは「真っ白なページ」として認識されてしまいます。

これではせっかくコンテンツを用意しても、検索エンジンからは存在しないものとして扱われてしまいます。 LobiのWebブラウザ版(https://web.lobi.co/)はまさにその状態でした・・・。

最初期

当時LobiはiOSとAndroidのネイティブアプリとしてサービス提供を行っていました。 ユーザーからの「PC版はないのか?」という要望から、Webブラウザで動作するLobi(以下「Web版」と略記)を作ることになりました。

ネイティブアプリと同等の操作性を提供したいとの考えから、 AngularJSを用いたSingle Page Applicationとして開発が進められました。

その後、単なるチャットツールではなくコミュニティとしてのLobiを拡散する施策を行うにあたり、 Web版のSEOに着手したのですが・・・上記の通りAngularJSで動的にコンテンツを生成していたため、 クローラーからは「真っ白なページ」として認識されているという事実が大きな足かせになりました。

Prerender.IOでレンダリング

クローラーにコンテンツを認識してもらうためには、 サーバーでページをレンダリングするしかありません。 かといってAngularJSでフル実装されたWeb版を サーバーサイドレンダリング方式に実装し直すには大きなコストがかかってしまいます。

そこで「JavaScriptによるコンテンツ生成を維持したままサーバーでレンダリングする」という手段をとることにしました。 当時使用していたのはperenderというツールです。

https://prerender.io/

これでレンダリング済みのページを返すことでインデックスされるようにはなった・・・のですが、 prerenderのプロセスが頻繁にエラー終了してしまうため代替手段を用いる必要がありました。

暫定対応

暫定対応として、クローラーからのリクエストに対しては コンテンツとして重要な要素(チャットグループのタイトル、最新の発言数件、など)を サーバーでレンダリングして返すような実装を行っていました。

しかし、ユーザーが見るコンテンツとクローラーが見るコンテンツを出し分ける行為は、 「クローキング」というガイドライン違反にあたるため、早急に別の手を打つ必要がありました。

クローキング - Search Console ヘルプ

なお、Googleのクローラーから見たがWebページをどのように表示されているかは、 Search ConsoleのFetch as Googleを使用することで確認することができます。

ウェブサイト用 Fetch as Google を使用する - Search Console ヘルプ

phantomjsでレンダリング

ここからが本番。

prerenderと同じアプローチではありますが、JavaScriptを解釈し、 ブラウザで表示されるものと同じページレンダリングを行う手段として PhantomJSを採用することにしました。

http://phantomjs.org/

レンダリング処理

上述の通りPhantomJSでレンダリングを行なっているわけですが、 JavaScriptを解釈しサーバーでレンダリングするためには、 AngularJSで全コンテンツを動的生成している場合はとりわけ時間がかかります。

Lobiではこの描画処理をバックグラウンドのワーカーに行わせています。 リクエストからレスポンスまでのシーケンス図は以下のようになります。

sequence.png

  • クローラーからリクエストがあった場合、まずレンダリング結果のキャッシュを参照する
  • キャッシュがあればそれを返して終了

新規ページ等はキャッシュが作られていないので、レンダリング処理が実行されます。

  • バックエンドのアプリケーションがレンダリング用ワーカーを起動
  • nginxにはいったんX-Accel-Redircetヘッダーを付けてレスポンスを返す
  • X-Accel-Redirectには「一定時間おきにキャッシュ取得をくりかえす」locationが指定されている
  • キャッシュ取得を繰り返す処理にはngx_lua_moduleを使用
  • ワーカーによるレンダリングが完了し、nginxがキャッシュ取得に成功したらそれをレスポンスとして返す
  • 一定時間以上キャッシュが取得できなかった場合はRetry-Afterヘッダーをつけた上でstatus=503としてレスポンスを返す

X-Accel-Redirectはnginxが独自に解釈するヘッダーで、 upstreamからのレスポンスにこのヘッダーが指定されていた場合、 その値に対応するlocationに処理を引き継ぐことができます。

nginxの設定から該当箇所を抜粋すると以下のようになります。


upstream app {
    server 127.0.0.1:8080;
}

location / {
    if ($http_user_agent ~* "baiduspider|googlebot|bingbot") {
        set $cache_key "$cache_key_prefix$host$request_uri";
        memcached_pass 127.0.0.1:11211;
        error_page 404 503 = @fallback;
    }
}

location @fallback {
    proxy_pass http://app;
    # proxy先から X-Accel-Redirect: /wait_cache が返ってくる
}

location /fetch_cache {
    internal;
    include /etc/nginx/nginx.common.location.proxy.conf;
    memcached_pass 127.0.0.1:11211;
}

location /wait_cache {
    internal;
    default_type "text/html; charset=utf-8";
    memcached_gzip_flag 2;
    gunzip on;

    if ( $upstream_http_x_cache_key ) {
        set $cache_key $upstream_http_x_cache_key;
    }

    access_by_lua '
        while true do
            ngx.sleep( 2 );

            local res = ngx.location.capture( "/fetch_cached", {
                share_all_vars = true
            });

            if res.status == 200 then
                return;
            end

            if 30 < ngx.now() - ngx.req.start_time() then
                ngx.header.Retry_After = 60;
                ngx.header.Content_Type = nil;
                ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE);
                return;
            end
        end
    ';

    memcached_pass 127.0.0.1:11211;
}

詳しくはnginxのドキュメントを参照して下さい。

https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/

弊社サービスで実際に使われているngx_lua_moduleの活用方法については以下のスライドを参照下さい。

https://speakerdeck.com/fujiwara3/practical-nginx-lua-in-kayac

ストリーミングAPIの罠

LobiはチャットをメインコンテンツとするWebサービスです。 ブラウザでチャットグループを開いておくと新着メッセージが自動的に表示されるようになっています。 この仕組はクライアントとAPIサーバー間でHTTP接続を維持することで実現しています。 この接続は最長10分間継続します。

つまり、PhantomJSで何も考えずにグループチャットのページをレンダリングすると、 処理完了まで10分間かかることになります。

クローラー用のページではその時点で投稿されているメッセージが描画されていれば充分なので、 ストリーミングAPIを使用する必要がありません。 そこで該当APIのリクエストをキャンセルするようにしました。


var page = require('webpage').create();
page.onResourceRequested = function(requestData, networkRequest) {
    if (isStream.test(requestData['url'])) {
        networkRequest.abort();
    }

    ...
});

ストリーミングAPI以外の外部リクエスト(Google AnalyticsへのAPI送信など)についても、 表示されるコンテンツに影響しないものについてはリクエストをキャンセルすることで レンダリング速度を上げる工夫をしています。

(prerenderでレンダリングしていた際には時間が足りず調べきれなかったエラー終了の原因も、 ストリーミングAPIによるものである可能性が高いと考えています)

SEOを考えている場合AngularJSという選択は・・・

先のシーケンス図を見てもらえば分かる通り、AngularJSを使ったサイトできちんとSEOしようとすると かなり複雑な仕組みを用意しなければなりません。 しかもこれは「SEOするための準備」であって、SEOそのものではないのです・・・。

SEOを考えるのであれば、AngularJSをはじめJavaScriptでコンテンツを生成するライブラリの利用は諦めたほうが無難でしょう。

次回

次回はLobiにおけるConsulの活用事例について紹介します。

カヤックではSEOに興味があるエンジニアも募集しています!

どうも、フロントエンドエンジニアの田島です。

弊社カヤックでは、2016年の7月7日を以て3年ぶりのコーポレートサイトリニューアルを行いました。

https://www.kayac.com

今回のサイトの大きなテーマは、 5種類のデザインから自分の好きなテーマに切り替えて楽しめる ということです。

現在のカヤックのアイデンティティを示す5つのテーマ、 「Simple」「Manga」「Game」「Tech」「Kamakura」 に則り、それぞれのテーマに応じたデザイン・演出を実装しています。 375c834e-cec2-1836-54a9-585a2cae9667.jpg

本記事では、フロントエンド実装に当たって工夫した、

  • 開発環境
  • CSS/JavaScript の設計
  • 『餅は餅屋』 大作戦
  • その他Tips, 使用したライブラリ

というトピックについて、書いていきたいと思います。

開発環境

本サイトではRuby on Railsを採用しています。 フロントエンド側でもなるべくrailに乗っかるべく、gulp等のタスクランナーを使う場面は極力抑え、asset pipelineベースで開発を進めました。

JavaScriptはES6を使いたかったため、 browserify-rails + babelify という構成としました。 こちらの導入手法をおさらいしますと、

まずbrowserify-railsというgemをインストールし、

[Gemfile]
gem 'browserify-rails'

npm moduleであるbabel-preset-es2015とbabelifyをインストールした上で、

npm install babel-preset-es2015 babelify --save-dev

config/application.rb 内でbrowserify_railsのオプションとしてbabelifyを用いるように記述します。

[config/application.rb]
config.browserify_rails.commandline_options = "-t [ babelify --presets [ es2015 ] ]"

これで、asset pipeline上のJavaScriptはES6として記述していくことができます。importも使い放題ですね。

さらに、フロントエンドエンジニアとしてはどうしても欲しいlivereload機能ですが、今回はbrowser-syncを使うことで解決しました。

つまり、browser-syncをインストールした上で、

npm install browser-sync --save-dev

npm startコマンドでそれを立ち上げるようにします。この際、立ち上げるサーバーではrailsが立てられているlocalhost:3000をproxyしています。 こうすることで、railsのapp内でファイルが変更される度にブラウザを自動更新してくれます。

[package.json]
"scripts": {
    "start": "browser-sync start --proxy localhost:3000 --files 'app/assets/stylesheets/*.scss, app/assets/javascripts/**/*.js, app/views/**/*'"
}

開発時には、 bundle exec rails server でrailsサーバーを立ちあげてから、 npm start でbrowser-syncのサーバーを立てるかたちになります。 ブラウザ上ではlodalhost:3001で確認しながら開発を進めていきます。

CSSの設計

レスポンシブ対応

本サイトではPC用サイト / スマホ用サイトをレスポンシブで出し分けています。 出し分け方として、media queryを使って読み込むCSSファイル自体を変更する手法 をとりました。 大規模サイトで、特にsassの@extendを多用する設計になっている場合、PCサイトとスマホサイトのCSSを同時に記述していくと、同一セレクタ上での予期せぬstyleのバッティングが頻発するため、思い切ってファイル自体を分けるやり方はオススメです。

<link rel="stylesheet" media="print, screen and (min-width: 801px)" href="#{PC用サイトCSS}" />
<link rel="stylesheet" media="screen and (max-width: 800px)" href="#{スマホサイト用CSS}" />

テーマ切り替え

今回のサイトの核となる、テーマ切り替えのCSS設計は以下の様にbodyに対してdata属性を付与する手法をとりました。 テーマを切り替える際にはここだけ変更すれば良いので、管理は非常にシンプルとなります。

body[data-design="simple"]{
    @content;
}

sass上での設計方法としては、以下のようにテーマごとのmixinを予め用意しておいた上で、

@mixin simple-design{
    body[data-design="simple"]{
        @content;
    }
}

@mixin comic-design{
    body[data-design="comic"]{
        @content;
    }
}

@mixin game-design{
    body[data-design="game"]{
        @content;
    }
}

@mixin kamakura-design{
    body[data-design="kamakura"]{
        @content;
    }
}

@mixin tech-design{
    body[data-design="tech"]{
        @content;
    }
}

テーマ別にデザインの差異があるモジュール部分にて、下記のようにstyleを上書きしていくやり方をとりました。

// 基本style
.block{
    color: #fff;
}

// テーマ別のデザイン差異
@include simple-design{
    .block{
        color: #f00;
    }
}

@include comic-design{
    .block{
        color: #ff0;
    }
}

JavaScriptの設計

本サイトでは右下にある切り替えボタンから、サイトのテーマを変更することができます。 実装面では、CSSの変更だけでなく、演出を担っているJavaScriptも実行中の処理を変更しなければなりません。 04c8917e-1279-ee4d-3e3c-01d57f0b7e44.gif

テーマ切り替えのためのイベント管理にはEventEmitterを採用しました。

以下のようにDesignManagerというクラスを作っておき、デザイン切り替えボタンが押されるとsetDesignが呼ばれ、CSSの切り替え(bodyのdata-design属性変更)を行った上で、切り替えイベントを発行しています。 イベントの中身には、変更前・変更後のテーマ名が渡されます。

import EventEmitter from 'events';
const DATA_DESIGN = 'data-design';

class DesignManager extends EventEmitter{
    constructor(){
        super();
        this.currentDesign = null;
    }

    setDesign(design){
        const currentDesign = this.currentDesign;

        if (design === currentDesign) {
            return;
        }

        $('body').attr(DATA_DESIGN, design);
        this.currentDesign = design;

        // テーマ切り替えイベントを発行
        this.emit('change', {
            from: currentDesign,
            to: design
        });
    }
};

(※実際のプログラムから一部抜粋)

例えば、GAMEテーマの背景を動かしている演出は、下記のようにdesignManagerのイベントを監視しながら、

・切り替え先がGAMEである場合は演出開始 ・切り替え元がGAMEである場合は演出停止

という書き方をします。 これが、テーマごとに実行すべきJavaScriptの処理を制御するための基本設計です。

[GameBackground.js]
const gamecBackground = new GameBackground();
const GAME_NAME = 'game'; 

designManager.on('change', (e) => {
    if(e.to === GAME_NAME){
        gameBackground.start();
    }
    else if(e.from === GAME_NAME){
        gameBackground.stop();
    }
}

『餅は餅屋』 大作戦

今回のカヤックサイトの演出部分は、カヤック社員の持てるスキルを総動員する形で実装しました。 アニメーション制作を得意とする人、音制作を得意とする人、ジェネラティブなプログラミングを得意とする人、クイズを考えるのが得意な人など、様々な種類のクリエイターを抱えるカヤックならではの特徴を活かした、『餅は餅屋』的な作り方を意識しました。

具体例を紹介していきましょう。

GAMEテーマのフッターにいるキャラクター

GAMEテーマのフッターで流れているキャラクターの動きは、ゲームチームにいるアニメーターが制作し、フロントエンドエンジニアである私がコーディングに落としこむという流れをとりました。 例えば、Adobe Animateで作成したキーフレームアニメーションをCSSのkeyframesに書き起こしたり、 Spriterで作成した連番画像アニメーションをSprite化してJavaScriptで再生する、という作り方をしています。

dd94a2d0-db9e-3e2d-0988-668c0a187c2f.gif

採用フォームでのチャット風インターフェース

スマートフォンから採用フォーム画面に進むと、チャット風のインターフェースになる仕掛けを施しました。 チャット感覚でサクサクとエントリーできるだけでなく、会話相手となるBOTが時折気の利いたことを言うことで楽しませる工夫をしています。

例えば、誕生日を入れるとその日が何の日か教えてくれてたり、電話番号を入力すると、勝手に語呂合わせしてくれたり。 1c335e4d-7e5d-7c28-ab74-b79d79da84a3.jpg

それぞれの辞書データの作成は、弊社の謎かけを得意とするディレクターや、クイズ作りを得意とするプロデューサに頼んで手伝っていただきました。

TECHテーマのロゴ演出・背景演出

TECHテーマではWebGLを多く利用しています。

例えば、左上のロゴが3Dになっていたり、 92a3893a-46cb-f2b3-abb3-a89d7f6567af.jpeg

背景ではシェーダーを使ったジェネラティブな動きを取り入れています。 6b31694c-bde6-1966-f6f6-292e4e40b969.jpg

いずれも、Blenderでの3Dモデル制作を得意とするデザイナー、普段openFrameworksやUnityでシェーダーを書いているエンジニアに依頼して制作しています。

その他Tips, 使用したライブラリ

閲覧環境のスペックに応じて演出のレベルをコントロールする仕組み

本サイトでは、上記のように多くの演出を盛り込んでいますが、あくまでコーポレートサイトであるため幅広いユーザに閲覧されることが想定されます。 従って、比較的処理性能の低いマシンから閲覧された場合には演出のレベルを落として負荷を下げなければなりません。

対処方法として、requestAnimationFrameを使ってFPS値を監視し、 閾値を下回っていたら演出レベルを段階的に下げる という処理を行っています。 これは、マシンスペックを要する演出系サイトにおいては汎用的に使いまわすことのできる手法です。

音声はboombox.jsを採用

GAMEテーマとTECHテーマでは各所でサウンドエフェクトを実装しています。 音声再生のライブラリとして、boombox.jsを採用しました。

このライブラリを用いる主なメリットとして、 Audio Sprite機能が挙げられます。 特にSEを多用する場合では、音声データをバラバラに用意しておくと読み込むファイル数が膨れ上がり、フロントエンド的にもサーバーサイド的にも負荷が高くなります。 Audio Spriteを使えばファイル数は1つで済むため、リクエスト数の削減にもなりますし、フロントエンド側でプリロードの管理に意識を割かなくて良いため非常に楽になります。

CSSのmix-blend-mode

CSSプロパティとして採用したモダンなやつだと、mix-blend-modeがあります。 これは、CSSだけでphotoshopのようなレイヤー合成ができちゃうというスグレモノです。 今回は、乗算合成をkamakuraテーマのフッターにいるイラストに対して用いています。 こうしてON/OFFを比べて見ると違いがよくわかりますね。背景となじませたいときに今後も重宝するでしょう。 IEはまだ未サポートだったりしますが、必須ではない付加演出の場面であれば、積極的に使っていきたいです。

3c5fa55c-a9e1-5c29-5b24-98cf50beb7ed.jpg

フォント・アイコンデータ

テーマごとに全てフォントファミリーも変えています。 今回は、欧文フォントにGoogle Fontsを、日本語フォントにはType Squareを利用しています。 フォントデータを自前サーバーでホスティングする必要がないため楽です。

さらに、アイコン素材系のほとんどはFont Awesomeを利用しています。 矢印やSNSアイコンなどよく使われるやつは一通りそろっています。こちらも素材をホスティングする必要がなく、アイコンをフォントデータ的に使うことができて非常に便利でした。

おわりに

カヤックのコーポレートサイトはニュース記事・社員ページ含めると非常にたくさんのコンテンツ数を持っています。 そこに更に5種類のデザインテーマが選べる機能が加わったことにより、より多彩な顔を持つようになりました。 コンテンツとデザインの組み合わせの妙によっては、面白い画が生まれるという楽しみ方もあります。 ぜひ、気分によってデザインを切り替えながらお楽しみ下さい。

https://www.kayac.com

Lobiチームの長田です。

あらゆるWebサービスがそうであるように、Lobiでも日々大量のログが出力されています。 今回はこのログをどのように集約・解析しているかを紹介します。

TL;DR

  • アクセスログ・アプリログなど、毎秒10000行以上のログが生成されている
  • Fluentdを使用しログを集約
  • consul serviceを利用した集約サーバーの冗長化
  • ログ中のイベント検知・集約にはNorikraを使用
  • アクセスログの各種解析にはAmazon Redshiftを利用

ログの集約

aggregate.png

ログ収集エージェント

Lobiではログの集約にFluentdを利用しています。

ログファイルの集約にはfluent-agent-hydraを、Perlアプリケーション内からのログ送信にはFluent::Loggerモジュールをそれぞれ使用しています。

fluent-agent-hydraはGo言語で書かれたログ収集エージェントです。 Fluentdよりも高速・低リソースで動作します。 詳しくはfluent-agent-hydraのベンチマークディレクトリを参照して下さい。

同じくFluentdの代わりとなるエージェントとしてはPerl製のfluent-agent-liteが有名ですが、 これに対してfluent-agent-hydraはビルド済みのバイナリを配置するだけでセットアップが完了するというメリットがあります。

Fluent::LoggerからはPerlアプリケーションから同ホストのFluentdにログを送信、 更にそこからforwardしてログ集約サーバーのFluentdに送信しています。 この仕組はファイルとして保存されるアクセスログよりも粒度の細かいログを記録する場合に使用します。

ログ集約サーバーの冗長化

Lobiでは現在計4台のログ集約サーバーが稼働しており、それぞれにFluentdが起動しています。 各集約サーバーで集約されたログは1時間単位でファイルとして保存され、 最終的にはAmazon S3にアップロードされます。

各ホストのfluent-agent-hydra、Fluntedからの送信先はconsul service にログ集約サーバーを登録し、それをconsulが提供するDNS機能で名前解決することで決定しています。 consulによるログ集約サーバーのヘルスチェックが失敗した場合は名前解決の対象からは除外されるため、 常に正常稼働しているサーバーにログを送信することができます。 送信中にログ集約サーバーがダウンした場合はFluentdが再送処理を行うためログの集約漏れが発生することはありません。 Fluent::Loggerから直接ログ集約サーバーのFluentdにログ送信を行わないのはこの再送処理を利用するためです。

consulのDNSで送信先を決定する手法には、

  • ログ集約サーバーの切り離しをconsul maintだけで行える
  • ログ集約サーバーを追加する際にはconsul serviceの追加のみでよく、Fluentdの設定変更は不要

などのメリットもあります。

集約したログファイルのtail

集約サーバーが複数あることで冗長化されているのは安心感があるのですが、 ログファイルが複数のサーバーに分散するため「ちょっとログファイルをtailする」といった操作が行えません。 本番環境での動作を確認したい場合、これでは不便です。

この問題を解消するために、nsshというツールを使用しています。

nsshは複数のサーバーに対してssh経由でコマンド発行を行い、出力をまとめて表示するツールです。 たとえば全ての集約サーバーを対象に以下のようなコマンドを発行すれば、 全てのログに対してまとめてtailをかけることができます。


nssh -t host1 -t host2 -t ... tail -F /var/log/aggregated/api.log

毎回ホスト名を書くのは手間なので、 実際にはconsul membersと連携するラッパースクリプトを通して nsshコマンドを使用しています。

アクセスログの解析

Norikra

analyze.png

NorikraはSQLでログストリームを処理することができるアプリケーションです。

LobiではNorikraを以下のような各種イベントの検知や、

  • HTTPリクエスト処理時に発生したエラー
  • バックグラウンドで動作しているworkerプロセスのエラー
  • 不自然なチャットメッセージ連投
  • 不自然な連続アカウント作成
  • DoS攻撃

集計処理に利用しています。

  • HTTPリクエストステータスコード別集計

Lobiで生成されるログのほぼ全てがNorikraに渡されており、 その10000行/秒のログをNorikraのプロセスひとつで処理しています(動作ホストはAmazon EC2のm4.2xlarge)。

加工されたログストリームは、専用のFluentdが動作しているサーバーに再度送信されます。 そこで必要な物はファイルに保存され、蓄積・監視が必要な値はzabbixに送信され、迅速な対応が必要な物はSlack等に通知を行います。

以下の例ではアプリケーションのログを1分単位でクエリし、 500番台のエラーが現れたらSlackに通知しています。


# Norikra query
SELECT 'notification' AS host,
 'app_status_5xx' AS title,
 'warn' AS level,
 count(*) AS count,
 last(_hostname) AS _hostname,
 last(message) AS message
FROM app.win:time_batch(1 min)
WHERE
  message LIKE '%status:5%'

# Fluentd conf
<match norikra.notification.**>
  type slack
  webhook_url https://hooks.slack.com/services/***/******
  parse none
  channel lobi
  username nuko
  message_keys level,title,count,_hostname,message
  message ":%s: %s count:%s %s %s"
  flush_interval 1s
</match>

HTTPリクエストのステータスコード集計は、以前はCounter系のFluent Pluginを組み合わせて実現していましたが、

  • ログ流量増加に伴い集計処理の負荷に耐えられなくなった
  • 設定変更には設定ファイルの変更とfluentdの再起動が必要で、本番環境のデータを用いた試行錯誤が難しかった

などの理由からNorikra導入に至りました。 特に後者については、ちょっとした集計方法の改善や実験的な値取得がWebコンソール上から手軽に行えるため大変重宝しています。 もちろんNorikraで定常的に使用するクエリはコードとしてリポジトリ管理下に置き、誤操作などでクエリが失われてもすぐに復旧できるようになっています。

Amazon Redshift

DAU(Daily Active Users: 1日のアクティブユーザー数)の算出やユーザーの動向調査など、アクセスログの解析が必要になる場合は多々あります。 Lobiでは全てのアクセスログをAmazon Redshiftに投入し、それをクエリすることで解析を行っています。

Redshiftを使用する前はファイルとして保存した圧縮しても数GBあるアクセスログを、 1行ずつ走査して、パースして、メモリ上に中間結果を保持しつつ各項目を集計する、ということを行なっていました。 当然この処理には非常に長い時間とマシンパワーが必要で、末期には午前2時に始めた解析処理が昼ごろにようやく完了する状態でした。

アクセスログをRedshiftに投入し、クエリで集計結果を得るようにしてからは 1時間程度で同等の解析処理を行うことができています。

Redshiftへのログ投入はRinを使用しています。

15分おきに最新のログが投入されるよう設定しているため、 たとえば本番反映したAPI軽量化施策の効果を十数分後には集計結果として得られるようになっています。

Rinについては以下を参照下さい。

また、以前のtech.kayacの記事でも触れていますので、そちらも合わせてご覧下さい。

おわり

次回はAngularJSを使用しているLobi Web版のSEOについて紹介します。

カヤックではログの集約・活用に興味があるエンジニアも募集しています!