絶品ゆどうふのタレ

ふと気づいたことを綴るだけのメモ

いまさらGAE/Goに入門してみる

最近周囲がGAEづいていて、やったこともなかったので入門してみた。 今の状況的にはやっぱりGoかなと思うので、GAE/Goで。

GCPに登録

まずは登録

https://console.cloud.google.com

初めての人は、60日間有効な$300クレジットがあるのでそれで遊ぶどいいよ (2016/05/21) 登録時にクレカの登録を求められるけど、無料期間が終わっていきなり請求されるわけではないらしい。

プロジェクトを作る

何故か最初からAPI ProjectができていたがApp Engineがほしいので、新しいプロジェクトを作る。特に選択肢なくせずにGAEのプロジェクトができた。 プロジェクト名はご自由に。

AppEngineのページに行くと、言語ごとのチュートリアルも読むことができる。 すごく良く出来てて基本的な使い方を理解できるので、正直やっとくといいよ。

自分はGoのチュートリアルをやった。 チュートリアルを実行すると、リポジトリにサンプルアプリが配置されるので、実行テストにはそれを使いまわすと楽。

App Engine SDK のセットアップ

ちょっと前のドキュメントだとGoogle Platform SDK の gcloudコマンドが統合管理するよ!って感じだったけど、どうやらGAEのSDKはそこからstandaloneになった様子。

https://cloud.google.com/appengine/downloads

ここからpackageを取ってくる。

% curl -O https://storage.googleapis.com/appengine-sdks/featured/go_appengine_sdk_darwin_amd64-1.9.37.zip
% unzip go_appengine_sdk_darwin_amd64-1.9.37.zip

.*shrc 的なものにPATHを通す。

export PATH="/path/to/go_appengine:$PATH"

.*shrcを読みなおしてgoappコマンドにパスが通っている事が確認できればOK。

ローカルでサンプルを動作させてみる

この辺は作りたいものを作ってく感じだけど、とりあえずまずGAEの操作を覚えるのでサンプルのまま。

gae-sample
├── app.yaml
└── hello
    └── hello.go
package hello

import (
    "fmt"
    "net/http"
)

func init() {
    http.HandleFunc("/", handler)
}

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World!")
}
version: 1
runtime: go
api_version: go1

handlers:
- url: /.*
  script: _go_app

上記を作成し、対象ディレクトリをgoapp serve する。

% goapp serve gae-sample
INFO     2016-05-21 04:45:56,681 devappserver2.py:769] Skipping SDK update check.
WARNING  2016-05-21 04:45:56,885 simple_search_stub.py:1146] Could not read search indexes from /var/folders/r8/gnn5k3_j4qq1rjmd3k3x1b4r0000gn/T/appengine.None.yudoufu/search_indexes
INFO     2016-05-21 04:45:56,891 api_server.py:205] Starting API server at: http://localhost:58354
INFO     2016-05-21 04:45:56,897 dispatcher.py:197] Starting module "default" running at: http://localhost:8080
INFO     2016-05-21 04:45:56,901 admin_server.py:116] Starting admin server at: http://localhost:8000

これでローカルにアプリが起動するので、http://localhost:8080/ で動作をチェックできたら完了。 http://localhost:8000/でアプリケーションの状況もチェックできる。

余談だが、最初yamlを書く際にGCP上のサンプルを真似してversion: 1無しで作ったら、GAEにdeployする際に A version or backend parameter is required. と怒られてしまった。 その辺、参考にしたサンプル次第で色々エラーに見舞われると思うのでご注意をば。

GAE上に配置する

今のサンプルをGAE上に配置してみる。

% goapp deploy -application [project id] gae-sample 

project idは、作成したGAEプロジェクトのダッシュボード左上あたりに書かれてるので、それを引っ張ってくる。 実行すると、ブラウザ上で認証を求められるので許可すると、デプロイされる。

完了したらhttp://[project id].appspot.comにアクセスすると、動作が確認できる。

これでひとまず、GAEアプリケーションを開発してく準備が整った感じ。

fluent-plugin-elasticsearch(elasticsearch-ruby)で負荷分散してる場合のリトライ設定

結論的にはマニュアル以上の情報はないです。個人的メモに近い感じ。

この辺は気軽にテスト/確認しづらいレイヤだと思ってるので、具体的な実装を知らないと怖かったので調べてみました。

背景

  • fluentdからelasticsearchクラスタにログを投入したい
  • サービスから参照されるのでクラスタの一部が死んでもサービスに影響がないようにしたい

世にある記事だと、elasticsearchサーバのローカルにfluentd receiverを置いて、そこからesに投入する形式が多く見られた。

が、それだとreceiverが生きててesプロセスだけが死んだ場合にデータの投入が部分的に止まるよねってことで、送信元のfluentdからesに直接接続して投入することにした。

その場合fluent-plugin-elasticsearchが分散処理を行うので、その部分のfailure/retryの扱いやパラメータを調査した。

ポイント

先に、設定項目とかのまとめだけ。

  • fluent-plugin-elasticsearchは内部的にelasticsearch-ruby(elasticsearch-transport)を利用していて、そこが接続管理をしている。

  • 設定を検討した方がいいelatissearch-rubyの項目は以下

    • reload_connections
      • コネクション先サーバリストの更新可否と、その閾値trueでdefault 10000リクエスト毎。
      • とりあえずtrueで。クラスタの構成変更の頻度次第で増減させるといいかも。
    • reload_on_failure
      • 接続失敗した場合にサーバリストを更新するか否か。
      • これはtrueにするのが良さそうだが、サービスの監視体制等次第かも。正常にクラスタから外れないまま壊れたサーバが出るとどうなるだろう。。。
      • reload_connectionsの長さとのバランスにもよる。
    • retry_on_failure
      • サーバがunreachableだった場合のretry可否と試行回数。true指定だとdefaultの3が使われる。
      • fluent-plugin-elasticsearchでは5で固定。まぁそんもん感。
    • retry_on_status
      • サーバエラーが返ってきた時にretryするかどうか。
      • クラスタのうちの1台だけが異常になった場合とか想定すると、trueしたい。
        • ...だが、これはfluent-plugin-elasticsearchでは指定できない。
  • 環境次第だがWARNのログも閾値設けて監視の対象とした方が安全そう

    • ここを見ないとLANポート・ケーブル故障とかネットワーク異常系の場合に気づきにくそう
  • reload_connectionsをしていれば無停止でクラスタ構成変更に対応できるが、confの書き換えは忘れず行う必要があるので手順漏れがないよう注意

  • 以下項目は、関連するがdefaultのままでもたぶん支障はない

    • resurrect_after
      • default 60
    • resurrect_timeout
      • fluent-plugin-elasticsearchにはない
    • sniffer_timeout
      • fluent-plugin-elasticsearchにはない

以下詳細

まぁほぼソースコードの解説なので。。。

アクセスエラー処理の流れ

対象となるソースコードこのあたりにまとまっている。 とくにBase#perform_requestが中心。

  • 指定された選択方式(RRとか)で接続先サーバを決め、リクエストを送る
  • retry対応が走るのは以下のパターンだけで、それ以外は問答無用で死ぬ

unreachableだった場合(Timeout / ConnectionFailed)

  • 対象サーバは死亡判定を受ける
    • reload_on_failureパラメータがあれば、接続不能なサーバにあたった時点で即座にサーバリストのリロードが走る
    • retry_on_failureパラメータが設定されていると、改めてretry
      • max回数を超えるまでは失敗してもWARNしか吐かないので、WARNログをチェックしたほうが良さそう
      • [#{e.class}] Attempt #{tries} connecting to #{connection.host.inspect}

ServerErrorだった場合

  • retry_on_statusパラメータがあるなら、無条件でretry
    • エラーを返したサーバに接続ペナルティはない(dead判定もfailure判定もされない)ので、調子悪いサーバが対象から外れることはない。
    • WARNで出る [#{e.class}] Attempt #{tries} to get response from #{url}のログをちゃんと監視しておくと良いのかもしれない。
    • max失敗するとFatal。default回数は3。retry_on_failureパラメータに数字を渡すと変えられる。
  • パラメータがないならそのまま失敗する!!

なお、fluent-plugin-elasticsearchにはretry_on_statusがないので、この処理は通らない。

死亡判定と復帰判定

サーバの死亡判定はunreachable時、復帰の判定は接続先サーバの選定時に走る。

死亡判定

unreachableになると一旦死亡判定される

  • 死亡判定をされると、メモリ上に死亡フラグと失敗回数インクリメント、死亡時刻が記録される
    • reload_connectionsパラメータがセットされてると、指定リクエスト回数毎にサーバリストのリロードが走る
  • メモリがクリアされると死亡判定は消えちゃうよ
  • 死亡判定されると一旦接続対象から外されて、復帰判定を待つ

復帰判定

unreachableになったサーバにも再度接続のチャンスを与えるために、一定時間経過の後で復帰判定が行われる

  • resurrect_afterパラメータが関与
  • 最後のアクセスからresurrect_after秒後に復活チェック(default: 60sec)
  • 死亡判定を受けたConnectionが、死亡時刻からresurrect_timeout x (2 ** 失敗回数-1) 経っていたら復帰させる

復帰のあと、接続に成功した場合

復帰後にちゃんと接続に成功したら、正常に戻ったと判定されて失敗回数がクリアされる

対象サーバリストのリロード

  • 実際にクラスタにnode情報を取りに行って、対象サーバのリストを更新する
    • その際のtimeoutは別途sniffer_timeoutで決められる(default: 1)
  • この機能を使うことで、設定ファイルのhostsリストを編集・リロードすることなく、クラスタ群の追加削除に対応できる
  • ただし情報はメモリ上にロードされるだけなので、設定ファイルの更新は併せて行う必要がある

だいたいこんな感じ。そんじゃーね。

Kyobashi.swift #1 参加したメモ

http://kyobashi-swift.connpass.com/event/23712/

第1回めだったので、雰囲気も含めてメモメモ

まとめ

  • おにくおいしいです。 リクルートマーケティングパートナーズ さん++
  • 会場が大変にしゃれおつ!
  • drinkup的に、お酒飲みながら。ゆるふわ。
  • みんな軽いLTな感じで、ハードルの上がっている昨今では発表しやすいかも。

以下、聞きながらとったメモ。スライドは上のイベントページを参照のこと


既存プロジェクトにSwiftLintを導入した話

  • Githubが公開してるSwiftのコーディングスタイルガイドに沿っているかチェックしてくれる
  • 導入するのは簡単

  • 今回はチームでいかに導入したかの話

    • チームで合意形成、まずはdisableにしてから徐々に。
    • auto-correctがそこそこやってくれる

ExtraView

  • StoryboardのViewControllerの外側に作れるViewのこと

    • View単体で作れる
  • たとえば: SectionHeader

    • コードで作ったり
    • xibで作ったり
    • やりづらい
  • せっかくならすべての要素を1つのStoryboardで管理したい
    • 整理がついてイメージが作りやすいので、めっちゃ便利

ReSwift

OSSから学ぶSwift実践テクニック

  • Alamofire
    • URLStringConvertible
      • String風に見えるけどじつは違う
      • NSURLでもStringでもなんでも渡せる型
    • extensionをつかってProtocol毎にグルーピングして書くとわかりやすい
    • メソッドチェーンでリクエストを使いやすく
      • 大量のオプションでも見やすく書ける

Nearby Messages API

  • Wifi, BLE, 超音波などで近距離通信できる
  • 100kB以内のデータをやり取りできる
    • (超音波使うのはトークンの交換ぐらいっぽい)

NSError

  • Swift 2.0から導入された新しいエラーハンドリング方式
    • Exceptiionてきなもの
  • Cocoaフレームワーク内のエラーもどんどんこれに置き換わっている

  • NSErrorに存在した詳細なエラー情報がない

  • 定義は空っぽ

  • FriendlyErrorType

  • NSErrorとの共存

DI in Swift

  • DIとは

  • 依存オブジェクトをコンストラクタから差し込む

  • 配線の問題

    • 何かのオブジェクトがほしい時、依存するインスタンスも作らなければいけない
  • Swinject

    • 動的DI
  • Swiftにおける静的なDI
    • Cake Patternっぽいものを使ったDI
    • Protocol Extensionを使ってやってる
    • 読むのはややこしくなる。。。

Swiftは静的言語でも動的言語でもない、という話

swift-evolutionのMLを読んでいたら面白いメールがあったので個人的にメモメモ。 https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151207/001948.html

Protocol Extensionのmethod dispatchがわかりにくいからdynamicにすべきだ、という提案から徐々に話がそれてSwiftはdynamicかstaticか、いやこうあるべきだどーのこーのと話が揉めてきたところ、Swift作者のChris Lattnerが降臨してSwiftの設計思想についての話を書いていた。

前半は各言語のパラダイムや思想について話していて、その上で後半でSwiftがどういう立ち位置を目指しているのかを説明してる。

スレの流れも含めて要約すると、こんな感じの事言ってるっぽい。


  • Swiftは動作的にはdynamicにもstaticにも属しているわけではなく、予測可能なパフォーマンスとなるモデルにしている。
    • = JIT任せで何でもdynamic、ってのは目指してない。
  • 両者イイトコどりの"opportunitistic" language (日和見的言語)だとしている。

  • 言語的には、基本的にダイナミックだが、それをstatic compilerを使ってJITなしに高いパフォーマンスを実現するように作っている。

    • たとえばnon-publicなクラスをfinalとして解釈することによって、dynamic dispatchのお世話にならずに高速に動作させている
    • memo: Whole Module Optimization とかにも絡む話をしているんだと思わるる
  • 旧来のstaticかdynamicかという話はSwiftでは重要ではなくて、本当に実現したいモデルはProgrammer model

    • Programmer model = メンタルモデルとしてのプログラム言語モデルプログラマが理解するように動く、というもの。

個人的メモなんで、あんまり正確な書き方になってない。。。(;´▽`A``英語弱いから誤解してるかもだけど、こんな話みたい。間違ってたら誰か突っ込んでw

なお、個人的には元々の提案の方に興味があって見てたんだが、明確な結論は出てないようだった。 話的には、Swift3でそこら辺のdispatchの関係が「もっと理解しやすくなる」のかな。

負荷テストツールGatlingを触ってみた

負荷ツールとしてGatlingのことを少し前から耳にする機会が増えたので、利用してみることにした。

色々既出だとは思うが、公式のQuickstartに従って試してみたのでメモ。

GUIが必要だったので、今回は手元のMacで実行。

Gatlingとは

Java/Scala製の負荷テストツール

JMaterと似た感じのツールではあるが、

  • ハイパフォーマンス
  • 見やすいレポートHTML
  • developerフレンドリーなシナリオファイル

というのをウリとして謳っている。

たぶん、3項目とも対JMater(重い・レポート見づらい・XMLのシナリオつらい)を意識したメリットだろうなー。

なお、シナリオファイルは。。。

Gatling simulation scripts are written in Scala, but don’t panic!

わろた。

というわけで、触ってみる

Install

JavaとGalingをインストールする。Gatlingはunzipするだけ。

% brew cask install java
% wget https://repo1.maven.org/maven2/io/gatling/highcharts/gatling-charts-highcharts-bundle/2.1.6/gatling-charts-highcharts-bundle-2.1.6-bundle.zip
% unzip gatling-charts-highcharts-bundle-2.1.6-bundle.zip

シナリオの作成

テストシナリオ

まず最初に、テストシナリオを決める。チュートリアルに従って以下のとおり。

Gatling Recorderによるシナリオの記録

シナリオの作成には、Gatling Recorderを使う。

Gatling Recorderは、proxyを通してブラウザアクセスして記録を取ことで、Scalaで書かれたシナリオファイルを自動生成してくれるツール

もちろん、自分でScalaを書いてシナリオ作りをしても良いが、今回は手軽にできるproxy式の手法をとる。

起動と設定

Gatling Recorderを起動する。

% cd gatling-charts-highcharts-bundle-2.1.6
% bin/recorder.sh

ツールが起動したら、初期設定として以下のように項目を埋める。

f:id:Yudoufu:20150613170429p:plain

この状態で、WebプロキシとしてGatling Recorderを設定する。

f:id:Yudoufu:20150613170522p:plain

ブラウザアクセスでシナリオを作る

Start を押すと設定に従ってproxyが起動するので、ブラウザでアクセスしながら記録を取る。

シナリオ作成時のTipsとして、変なpluginなどの影響がないようにsecret windowでやるとよさ気。 また、Gatling Recorderにはログの間にTAGを差し込む機能があり、シナリオの記録が綺麗になるのでそれも活用するといい。

ということで、以下の流れで操作を記録する。

f:id:Yudoufu:20150613170548p:plain

ここまで実行し終わったら、Stop & Saveを押すと、user-files/simulations/computerdatabase/BasicSimulation.scalaにシナリオファイルが保存される。

内容は以下のようになった。

package computerdatabase

import scala.concurrent.duration._

import io.gatling.core.Predef._
import io.gatling.http.Predef._
import io.gatling.jdbc.Predef._

class BasicSimulation extends Simulation {

    val httpProtocol = http
        .baseURL("http://computer-database.herokuapp.com")
        .inferHtmlResources(BlackList(""".*\.css""", """.*\.js""", """.*\.ico"""), WhiteList())
        .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
        .acceptEncodingHeader("gzip, deflate, sdch")
        .acceptLanguageHeader("ja,en-US;q=0.8,en;q=0.6")
        .userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.39 Safari/537.36")



    val uri1 = "http://computer-database.herokuapp.com"

    val scn = scenario("BasicSimulation")
        // Search
        .exec(http("request_0")
            .get("/"))
        .pause(5)
        .exec(http("request_1")
            .get("/computers?f=macbook"))
        .pause(2)
        .exec(http("request_2")
            .get("/computers/6"))
        .pause(10)
        // Browse
        .exec(http("request_3")
            .get("/"))
        .pause(2)
        .exec(http("request_4")
            .get("/computers?p=1")
            .resources(http("request_5")
            .get(uri1 + "/computers?p=2"),
            http("request_6")
            .get(uri1 + "/computers?p=3")))
        .pause(2)
        .exec(http("request_7")
            .get("/computers/72"))
        .pause(1)
        .exec(http("request_8")
            .get("/"))
        .pause(1)
        .exec(http("request_9")
            .get("/computers?p=1")
            .resources(http("request_10")
            .get(uri1 + "/computers?p=2")))
        .pause(3)
        .exec(http("request_11")
            .get("/computers/70"))
        .pause(10)
        // Edit
        .exec(http("request_12")
            .get("/"))
        .pause(1)
        .exec(http("request_13")
            .get("/computers/new"))

    setUp(scn.inject(atOnceUsers(1))).protocols(httpProtocol)
}

以上でシナリオの作成は完了。

テストの実行

今のシナリオを元に、Gatlingを起動する

% bin/gatling.sh

しばし待つと、実行候補となるシナリオの一覧が出てくるので、対象を選ぶ。(今回は0) なお、BasicSimulation以外の候補は、元々含まれているサンプル。

その他の質問もとりあえずEnterして実行する。

Choose a simulation number:
     [0] computerdatabase.BasicSimulation
     [1] computerdatabase.advanced.AdvancedSimulationStep01
     [2] computerdatabase.advanced.AdvancedSimulationStep02
     [3] computerdatabase.advanced.AdvancedSimulationStep03
     [4] computerdatabase.advanced.AdvancedSimulationStep04
     [5] computerdatabase.advanced.AdvancedSimulationStep05
0
Select simulation id (default is 'basicsimulation'). Accepted characters are a-z, A-Z, 0-9, - and _

Select run description (optional)

Simulation computerdatabase.BasicSimulation started...

するとテストが実行され、最終的に結果が出力される

================================================================================
---- Global Information --------------------------------------------------------
> request count                                         18 (OK=18     KO=0     )
> min response time                                    191 (OK=191    KO=-     )
> max response time                                    414 (OK=414    KO=-     )
> mean response time                                   217 (OK=217    KO=-     )
> std deviation                                         64 (OK=64     KO=-     )
> response time 50th percentile                        195 (OK=195    KO=-     )
> response time 75th percentile                        197 (OK=197    KO=-     )
> mean requests/sec                                  0.441 (OK=0.441  KO=-     )
---- Response Time Distribution ------------------------------------------------
> t < 800 ms                                            18 (100%)
> 800 ms < t < 1200 ms                                   0 (  0%)
> t > 1200 ms                                            0 (  0%)
> failed                                                 0 (  0%)
================================================================================

Reports generated in 0s.
Please open the following file: results/basicsimulation-1434181826508/index.html

結果の確認

results/basicsimulation-1434181826508/index.htmlにレポートが出力されたようなので、見てみる

% open results/basicsimulation-1434181826508/index.html

f:id:Yudoufu:20150613170606p:plain

思った以上にリッチなレポートが出力されていて、全体のrpsの状況や成功失敗のグラフ、各リクエストごとの詳細な情報、パーセンタイル値などが出た。

f:id:Yudoufu:20150613170622p:plain

f:id:Yudoufu:20150613170634p:plain

f:id:Yudoufu:20150613170721p:plain

今回のテストは動作チェック程度なので大したグラフになっていないが、これはまじめに負荷テストやるとすごく良さそう。 機会があったら、プロダクト側で使ってみたい。

Embulkではてなブログの記事をparseしてElasticsearchに入れてみる

Embulk良さそうだね、と言いつついじったことなかったので。 先日ちょっと近所で話題に上がったので、仕事中に息抜きでやってみた。on mac

先にまとめ

  • input / parser / execute / outputと、細かく分けてpluginになっており、一般的な形式ならほぼ組み合わせていける
    • 異常に楽
  • 一旦input側を作ってstdoutに出す -> outputを作る(もしくは逆順)、とステップを分割して作っていけるので、普通に作るよりハマりにくそう
    • sampleのinputデータも自動生成してくれるので、outputを先に作っても楽だったと思う(今回はやらなかったけど)
  • pluginを取り扱うのにembulk gemコマンドとか用意されてて地味に便利
  • 関係ないけどbrewすげぇ

Embulkのinstall

手元のMacでの実験なのでこんな感じ

% brew cask install java
% brew install embulk

RSSを取得してSTDOUTに出力させる

一気にやるとわからなくなるので、とりあえずRSSから取得してstdoutに出してみる

参考にするのは以下のあたり

Pluginをinstall

% embulk gem install embulk-input-http
% embulk gem install embulk-parser-xml

config.ymlの作成

  • inputhttpparserとしてxmlを使う
in:
  type: http
  url: http://yudoufu.hatenablog.jp/rss
  params: ~
  parser:
    type: xml 
    root: rss/channel/item
    schema:
      - { name: title, type: string }
      - { name: link, type: string }
      - { name: pubDate, type: string }
  method: get 
out:
  type: stdout
  • とりあえず標準出力に出す
  • schemaは、descriptionを含めちゃうと

実行

% embulk run config.yml
2015-06-09 16:12:06.611 +0900: Embulk v0.6.5
2015-06-09 16:12:08.400 +0900 [INFO] (transaction): {done:  0 / 1, running: 0}
2015-06-09 16:12:08.681 +0900 [INFO] (task-0000): GET "http://yudoufu.hatenablog.jp/rss"
Norikra meetup #2 に参加してきた,http://yudoufu.hatenablog.jp/entry/2015/06/03/235131,Wed, 03 Jun 2015 23:51:31 +0900
AWS Summit 2015 - Day2 行ってきたまとめ,http://yudoufu.hatenablog.jp/entry/2015/06/03/232347,Wed, 03 Jun 2015 23:23:47 +0900
AWS Summit 2015 - Day2 - 新サービス解説セッション EFS と ML,http://yudoufu.hatenablog.jp/entry/2015/06/03/223904,Wed, 03 Jun 2015 22:39:04 +0900
AWS Summit 2015 - Day2 - クラウドを活用したIoT/M2Mソリューション,http://yudoufu.hatenablog.jp/entry/2015/06/03/223701,Wed, 03 Jun 2015 22:37:01 +0900
AWS Summit 2015 - Day2 - AWS セキュアデザイン(IAM) Deep Dive,http://yudoufu.hatenablog.jp/entry/2015/06/03/223637,Wed, 03 Jun 2015 22:36:37 +0900
AWS Summit 2015 - Day2 - AWS System Operation Deep Dive,http://yudoufu.hatenablog.jp/entry/2015/06/03/223605,Wed, 03 Jun 2015 22:36:05 +0900
AWS Summit 2015 - Day2 - ネットワークDeep Dive,http://yudoufu.hatenablog.jp/entry/2015/06/03/223523,Wed, 03 Jun 2015 22:35:23 +0900
2015-06-09 16:12:09.064 +0900 [INFO] (transaction): {done:  1 / 1, running: 0}
2015-06-09 16:12:09.079 +0900 [INFO] (main): Committed.
2015-06-09 16:12:09.080 +0900 [INFO] (main): Next config diff: {"in":{},"out":{}}
  • ひとまず目的のデータは出た

Elasticsearchに入れてみる

以下の記事やREADMEを参考に、書いてみる

ElasticsearchのInstall

% brew install elasticesearch
  • brewで入れるとcluster_nameelasticsearch_usernameになってるので、必要なら適当に変える
% vi /usr/local/opt/elasticsearch/config/elasticsearch.yml
cluster.name: elasticsearch_yudoufu
% elasticsearch --config=/usr/local/opt/elasticsearch/config/elasticsearch.yml
  • とりあえずこれで、http://127.0.0.1:9200/にnodeが立つ
  • 9300がノード間通信のportで、こっちを使って接続するっぽい

Embulk pluginを入れる

% embulk gem install embulk-output-elasticsearch

config.ymlのoutputを作る

  • out部分を修正
    • 事前にindexを作っておく必要は特にないので、cluster_namenodesの情報だけ合わせておく
out:
  type: elasticsearch
  cluster_name: elasticsearch_yudoufu
  nodes:
    - { host: 127.0.0.1, port: 9300 }
  index: embulk_yudoufulog_rss
  index_type: embulk

実行

% embulk run config.yml
2015-06-09 17:05:01.632 +0900: Embulk v0.6.5
2015-06-09 17:05:03.559 +0900 [INFO] (transaction): [Dominus] loaded [], sites []
2015-06-09 17:05:04.391 +0900 [INFO] (transaction): {done:  0 / 1, running: 0}
2015-06-09 17:05:04.400 +0900 [INFO] (task-0000): [Siena Blaze] loaded [], sites []
2015-06-09 17:05:04.686 +0900 [INFO] (task-0000): GET "http://yudoufu.hatenablog.jp/rss"
2015-06-09 17:05:05.137 +0900 [INFO] (task-0000): Execute 7 bulk actions
2015-06-09 17:05:05.735 +0900 [INFO] (elasticsearch[Siena Blaze][transport_client_worker][T#5]{New I/O worker #22}): 7 bulk actions succeeded
2015-06-09 17:05:05.745 +0900 [INFO] (transaction): {done:  1 / 1, running: 0}
2015-06-09 17:05:05.761 +0900 [INFO] (main): Committed.
2015-06-09 17:05:05.762 +0900 [INFO] (main): Next config diff: {"in":{},"out":{}}

データの確認

  • データ入ったのを確認!!\(^o^)/
% curl -X GET http://127.0.0.1:9200/embulk_yudoufulog_rss/embulk/_search\?q\=AWS\&pretty
{
  "took" : 10,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 6,
    "max_score" : 0.26516503,
    "hits" : [ {
      "_index" : "embulk_yudoufulog_rss",
      "_type" : "embulk",
      "_id" : "AU3XWfGno1wqzcGPiVrl",
      "_score" : 0.26516503,
      "_source":{"title":"AWS Summit 2015 - Day2 - AWS System Operation Deep Dive","link":"http://yudoufu.hatenablog.jp/entry/2015/06/03/223605","pubDate":"Wed, 03 Jun 2015 22:36:05 +0900"}
    }, {
      "_index" : "embulk_yudoufulog_rss",
      "_type" : "embulk",
      "_id" : "AU3XWfGno1wqzcGPiVrh",
      "_score" : 0.111475274,
      "_source":{"title":"AWS Summit 2015 - Day2 行ってきたまとめ","link":"http://yudoufu.hatenablog.jp/entry/2015/06/03/232347","pubDate":"Wed, 03 Jun 2015 23:23:47 +0900"}
    }, {
      "_index" : "embulk_yudoufulog_rss",
      "_type" : "embulk",
      "_id" : "AU3XWfGno1wqzcGPiVrm",
      "_score" : 0.111475274,
      "_source":{"title":"AWS Summit 2015 - Day2 - ネットワークDeep Dive","link":"http://yudoufu.hatenablog.jp/entry/2015/06/03/223523","pubDate":"Wed, 03 Jun 2015 22:35:23 +0900"}
    }, {
      "_index" : "embulk_yudoufulog_rss",
      "_type" : "embulk",
      "_id" : "AU3XWfGno1wqzcGPiVrk",
      "_score" : 0.081366636,
      "_source":{"title":"AWS Summit 2015 - Day2 - AWS セキュアデザイン(IAM) Deep Dive","link":"http://yudoufu.hatenablog.jp/entry/2015/06/03/223637","pubDate":"Wed, 03 Jun 2015 22:36:37 +0900"}
    }, {
      "_index" : "embulk_yudoufulog_rss",
      "_type" : "embulk",
      "_id" : "AU3XWfGno1wqzcGPiVrj",
      "_score" : 0.057534903,
      "_source":{"title":"AWS Summit 2015 - Day2 - クラウドを活用したIoT/M2Mソリューション","link":"http://yudoufu.hatenablog.jp/entry/2015/06/03/223701","pubDate":"Wed, 03 Jun 2015 22:37:01 +0900"}
    }, {
      "_index" : "embulk_yudoufulog_rss",
      "_type" : "embulk",
      "_id" : "AU3XWfGno1wqzcGPiVri",
      "_score" : 0.057534903,
      "_source":{"title":"AWS Summit 2015 - Day2 - 新サービス解説セッション EFS と ML","link":"http://yudoufu.hatenablog.jp/entry/2015/06/03/223904","pubDate":"Wed, 03 Jun 2015 22:39:04 +0900"}
    } ]
  }
}

最終的なconfig.yml

備忘録としてまとめておく

in:
  type: http
  url: http://yudoufu.hatenablog.jp/rss
  params: ~
  parser:
    type: xml
    root: rss/channel/item
    schema:
      - { name: title, type: string }
      - { name: link, type: string }
      - { name: pubDate, type: string }
  method: get
out:
  type: elasticsearch
  cluster_name: elasticsearch_yudoufu
  nodes:
    - { host: 127.0.0.1, port: 9300 }
  index: embulk_yudoufulog_rss
  index_type: embulk

Norikra meetup #2 に参加してきた

  • https://atnd.org/events/65969

  • Norikraをつかいたいなーという気持ちを主張するためにとりあえずmeetupに参加してきた。

    • 画面黄色かった
    • だいたいみんな監視系で使ってた
    • だいたいみんなSPOFで困ってた
    • やっぱ便利そう
    • 使いたい
  • AWS Summitからのはしごだったのでさすがに疲れた。。。

メルカリでのNorikraの活用、Mackerelを添えて

  • kazeburoさん
  • Mercari

  • いかに早くスムーズにサイクルを回すか

  • Zabb....?

    • いいことはいっぱい
    • 煩雑だしめんどくさい
  • DevとOpsで情報を共有

  • MetricsをDevと共有
  • これを何とかするのに、fluentd経由でkibana / bigquery / tdなどでやっている

  • 何かあった時にすぐ知りたい

  • Norikra + mackerel

  • mackerel

    • はてな提供のサーバ管理ツール
    • Serverにagentを入れると、それをMackerelに送る
    • 直接クライアントを叩くことも可能
  • Norikra にSQLを投入するとmackerelにグラフを作られるようにしてみた

    • そこにalert用の閾値設定
  • メルカリでのNorikra構成

    • 各サーバからfluentd中継サーバへ、そこからnorikra
    • access_logとerror_logが対象
    • 全件処理してる
    • jolokiaを有効にしてKuradoでJVMのmonitoring
  • Norikraが落ちる問題

    • GCの途中で落ちてるっぽい
    • 正直分からない

設定とクエリ

  • Basic count()

    • Norikraで取ってきて、mackerelに投げてる
    • mackerelのいいところ
      • グラフの特定の値の表示を消すことができる
      • わずかしか出ない数値だけを表示したいときに楽
    • 特定のUAからのアクセスが「ある」場合をグラフ化
      • 閾値以下になった場合には、アラート発砲
  • percentile

    • アクセスのレスポンスタイムのパーセンタイルを取りたい
    • 全件やるとクエリが重そうだったので、1分間の最初の五万件だけを対象に絞った
    • percentilesのプラグインが返してくるのがオブジェクトの形になってしまうので、これをフラットにするためにもう一つplugin
    • レスポンスタイムの監視は、ほとんどの速いレスポンスに引きづられるので平均は意味が無い
    • なので、パーセンタイルや中央値などを使って監視するのが正しい
  • Q: kibanaと比べてどう?

    • データを一杯入れると重いし、グラフを多人数で見るのが重たい
    • リアルタイムにエラーログを見たい、とかはkibana使ってる
  • Q: メモリとかGCオプション

    • memory 64Gのマシンで、max 40Gで使ってる
    • メモリが少ないと圧縮、とかのオプションは外してる
    • フルGCで泊まる時間はNorikraでは問題ないので、そんなに気にしてない
  • Q: クエリのタイムウィンドウはどれくらいで運用してる?

    • 1分ぐらい
    • 特別重くかかるものの時だけは5分ぐらい
  • Q: 冗長化はしてる?

    • してない

Norikra to realtime log analytics

  • harukasan

  • 昔はsshでログ取ってくるとかhogehoge

  • 今はfluentd
    • そのままTDやHDFSやElasticsearchとか
  • さらにそれをCloudforecastとか、kibanaとかtableauとか

  • fluentdがあるおかげで、ここを通せばだいたいどうとでもなる

  • でも、そこから先の解析はまだよしなにやってくれるわけじゃない

解析の方式

  • Batch processing
    • daily / monthly...
    • PVとか
  • Ad-hoc analysis
    • Kibata & Elasticsearch
    • BI Tool: Tablau
  • Offline Analysis

  • Batch processは重すぎる

    • 5分毎の・・・1分ごとの。。。というのを知りたいときには困る
    • エラー時の情報をすぐに通知して欲しい
  • こういうのに向いてる方式

  • Stream Processing

    • データはずっと流れていて、そこにtime windowを切る
    • そのデータに対して処理をする
    • Norikra
  • Norikra

    • schema less
    • SQL like query
  • fluentdから送る

    • fluent-plugin-norikra
      • target_map_tag にしておくと便利
  • じゃあその結果を他に流すか?

    • fluentd -> Norikra -> fluentd
    • sweep でタグを使って投げる

Norikra Deployment

  • Norikra / GrowthforecastあたりはSPOFで困ってる

  • Hardware

    • Norikraにはメモリいっぱい(8GB以上
  • JVM 1.7

  • jruby でbuild
  • daemonize はsupervisord

  • Q: Norikra自身の監視は?

    • 特にしてない
  • Q: どのくらいの数のクエリ入れてますか?

    • 5~6こ
    • そんなに多くはならない
  • Q: time windowどのくらいにしてる?

    • 1分にしてる
    • でかくするとすごいメモリを食う

NorikraでWebサービスを守る

  • fujiwaraさん
  • HTTP status code / response timeを書いたり
  • fluentd-plugin-(datacounter|numeric-monitor)を置き換えた

  • r3.xlarge ( 4core 30GBのメモリ )

    • メモリは10Gから多い時で20Gいくかどうか
  • クエリ数はちょっと多い

    • VirtualHost毎にカウントしてる
    • なんだかんだ40クエリぐらいかけている
  • スパマーが来た

    • Web版を出したら、APIとかが見えるので、POSTをしてきたりされた
  • APIにPOSTメソッドにモリモリ送ってきものをクエリで抽出
  • twitter経由でのログインも抽出
  • さらに10分に何回、1時間に何回、という閾値も設定
  • これで引っかかったものをfluentdの自作プラグインでフックして、memcachedに投げ込んでspam-reportが発行される

  • まとめ

    • 簡単にクエリをカスタマイズできて、自作plugin書くと色々できる

Gunosy AdのNorikra事例

  • ユースケース

    • Adでターゲティング広告みたいなことしてる
    • ユーザーの好みと外れてるものはできるだけ早く外したい
    • イケてる広告画像をできるだけ早く洗い出したい
  • 環境

    • AWS
    • アドサーバはGolangにリプレース
  • c4.large 1台あたり

    • 1000req/sec
    • 2000~2500log/sec
  • 元々はRedshift+独自の集計

    • だるい
  • ログ量多くてkibana4に生ログ突っ込むと死ぬ

    • Speak streaming 使ってもしょうがない
  • そこでNorikra

  • 最終段のところでKibana4につっこむ

  • OpsWorksでポチポチサーバ構築して多段構成を何とかしてる

    • クエリ情報は全部custom jsonにくくりだしてる
  • 監視

  • datadogはdashbordにiframe埋め込めて便利

Norikra Recent update

  • (スライド見つからなかった)

  • tagomorisさん

  • 1年間で約17リリース

  • Suspend Queries

    • なんかの理由でちょっと止めたい
    • 登録はするけど一時的に止める、ということが可能に
    • suspendしたクエリはstats fileに吐き出されないので注意
  • Nullable fields

    • 同じようなデータなんだけど複数の場所から送られてくる
    • 新しいパラメータには存在するけど、古い方にはない。。。けど集計にはどっちも含めたい!という場合
    • NULLABLE()で囲むと、 項目がnullのイベントも抽出される
  • Listener

    • クエリのグループを指定するできる
      • LOOPBACK(target)
      • STDOUT() -> 結果が出たら、メモリプールに行かずにNorikraのログに吐かれる
  • Listener plugin

    • Listernerを自分で作れる
    • これでfluentd-pluginがなくても色々できる!
    • sync listener vs async listener
  • Dynamic plugin reloading

    • SIGHUPで読み込まれるようになった
    • restartなしでよくなった
  • その他にも色々