絶品ゆどうふのタレ

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

CarthageでGithubのAPI rate limitに対処する

Carthageって結構ごりごりGithubからcheckoutしてくるので、大勢でやってると

"API rate limit exceeded for xxx.xxx.xxx.xxx. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)"

みたいなことを言われてしまう時があります。

対処法としては Homebrewなどと同様に Github APIAccess Tokenを使う事になるんですが、Carthageではこのtokenを GITHUB_ACCESS_TOKEN という環境変数に指定することで利用できます。

export GITHUB_ACCESS_TOKEN=xxxxxxxxxxxx
carthage checkout

なお、Githubのtokenは生成時に1度だけしか表示されないので、 .*rcとかの中のコミットしないどこかにこっそり書いておくと良いと思います。

この辺で実装されたようで、利用できるのはcarthage 0.8以上っぽいです。

そんな小ネタ。

iOSシミュレータでの通信状態のテストにNetwork Link Conditionerが便利だった

や、僕が知らなかっただけで世の中では常識の可能性もありますが。めっちゃ便利だった。

ネットワーク悪環境の場合のテスト。。。

iOSのネットワーク環境が悪い場合のテストって前から悩ましいと思ってて、MacWifiをOffにするとか開発中のアプリを実機に入れて建物の隅っこに行くとか涙ぐましい感じでやってたんですけど。

ちゃんとネットワーク状態を調整してくれるツールあった。

Network Link Conditioner

Apple Developer Center から Hardware IO Tools っていうユーティリティ群をダウンロードすると、その中に入ってる。 他に同梱されてるアプリも便利そうな名前が色々。。。

f:id:Yudoufu:20160528120815p:plain

起動すると、再現したいネットワーク状況が色々選べて、スイッチをONにするとMac上の通信が制限される。

f:id:Yudoufu:20160528120835p:plain

他にも、自分で通信状態を細かく指定したカスタム環境も作成できる。 up/downそれぞれのLoss率と速度などなど。

f:id:Yudoufu:20160528120850p:plain

なお、操作できるのはパケットロスの度合いと通信速度までで、ネットワークON/OFFののテストしたいときは直接MacwifiをON/OFFしてやることになりそう。

100% Lossとか使うとTimeoutのテストが手軽にできる。 めっちゃ良くて捗りそう。今まで知らなかったのは悔やまれる。

いまさら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

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