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 APIのAccess 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のネットワーク環境が悪い場合のテストって前から悩ましいと思ってて、MacのWifiをOffにするとか開発中のアプリを実機に入れて建物の隅っこに行くとか涙ぐましい感じでやってたんですけど。
ちゃんとネットワーク状態を調整してくれるツールあった。
Network Link Conditioner
Apple Developer Center から Hardware IO Tools っていうユーティリティ群をダウンロードすると、その中に入ってる。 他に同梱されてるアプリも便利そうな名前が色々。。。
起動すると、再現したいネットワーク状況が色々選べて、スイッチをONにするとMac上の通信が制限される。
他にも、自分で通信状態を細かく指定したカスタム環境も作成できる。 up/downそれぞれのLoss率と速度などなど。
なお、操作できるのはパケットロスの度合いと通信速度までで、ネットワークON/OFFののテストしたいときは直接Macのwifiを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)で負荷分散してる場合のリトライ設定
結論的にはマニュアル以上の情報はないです。個人的メモに近い感じ。
この辺は気軽にテスト/確認しづらいレイヤだと思ってるので、具体的な実装を知らないと怖かったので調べてみました。
背景
世にある記事だと、elasticsearchサーバのローカルにfluentd receiverを置いて、そこからesに投入する形式が多く見られた。
が、それだとreceiverが生きててesプロセスだけが死んだ場合にデータの投入が部分的に止まるよねってことで、送信元のfluentdからesに直接接続して投入することにした。
その場合fluent-plugin-elasticsearch
が分散処理を行うので、その部分のfailure/retryの扱いやパラメータを調査した。
ポイント
先に、設定項目とかのまとめだけ。
fluent-plugin-elasticsearch
は内部的にelasticsearch-ruby
(elasticsearch-transport
)を利用していて、そこが接続管理をしている。設定を検討した方がいい
elatissearch-ruby
の項目は以下reload_connections
reload_on_failure
- 接続失敗した場合にサーバリストを更新するか否か。
- これは
true
にするのが良さそうだが、サービスの監視体制等次第かも。正常にクラスタから外れないまま壊れたサーバが出るとどうなるだろう。。。 reload_connections
の長さとのバランスにもよる。
retry_on_failure
- サーバがunreachableだった場合のretry可否と試行回数。
true
指定だとdefaultの3
が使われる。 fluent-plugin-elasticsearch
では5
で固定。まぁそんもん感。
- サーバがunreachableだった場合のretry可否と試行回数。
retry_on_status
- サーバエラーが返ってきた時にretryするかどうか。
- クラスタのうちの1台だけが異常になった場合とか想定すると、
true
したい。- ...だが、これは
fluent-plugin-elasticsearch
では指定できない。
- ...だが、これは
環境次第だがWARNのログも閾値設けて監視の対象とした方が安全そう
- ここを見ないとLANポート・ケーブル故障とかネットワーク異常系の場合に気づきにくそう
reload_connections
をしていれば無停止でクラスタ構成変更に対応できるが、confの書き換えは忘れず行う必要があるので手順漏れがないよう注意以下項目は、関連するがdefaultのままでもたぶん支障はない
resurrect_after
- default
60
- default
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)
- その際のtimeoutは別途
- この機能を使うことで、設定ファイルの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
- Fluxフレームワーク
- 一方向のデータフロー
- Reduxにインスパイアされた
OSSから学ぶSwift実践テクニック
- Alamofire
- URLStringConvertible
- String風に見えるけどじつは違う
- NSURLでもStringでもなんでも渡せる型
- extensionをつかってProtocol毎にグルーピングして書くとわかりやすい
- メソッドチェーンでリクエストを使いやすく
- 大量のオプションでも見やすく書ける
- URLStringConvertible
Nearby Messages API
NSError
- Swift 2.0から導入された新しいエラーハンドリング方式
- Exceptiionてきなもの
NSErrorに存在した詳細なエラー情報がない
定義は空っぽ
FriendlyErrorType
- NSErrorとの共存
DI in Swift
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
個人的メモなんで、あんまり正確な書き方になってない。。。(;´▽`A``英語弱いから誤解してるかもだけど、こんな話みたい。間違ってたら誰か突っ込んでw
なお、個人的には元々の提案の方に興味があって見てたんだが、明確な結論は出てないようだった。 話的には、Swift3でそこら辺のdispatchの関係が「もっと理解しやすくなる」のかな。
負荷テストツールGatlingを触ってみた
負荷ツールとしてGatlingのことを少し前から耳にする機会が増えたので、利用してみることにした。
色々既出だとは思うが、公式のQuickstartに従って試してみたのでメモ。
Gatlingとは
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
シナリオの作成
テストシナリオ
まず最初に、テストシナリオを決める。チュートリアルに従って以下のとおり。
測定対象とするのは http://computer-database.herokuapp.com/ というページ
- これは、いろんなPC等コンピュータのモデル情報を登録するCGM的なサイト
- Play framework製!
- 自分のローカルで動かしてもいい https://github.com/playframework/playframework/tree/2.2.x/samples/scala/computer-database
リアルなユーザーのシナリオとして、以下の様な想定でテストする
- ユーザーがアプリケーションにアクセス
-
mackbook
で検索 - 関連するモデルを開く
- homeページに戻る
- これを繰り返す
- ユーザーが新しいモデルを登録する
Gatling Recorderによるシナリオの記録
シナリオの作成には、Gatling Recorderを使う。
Gatling Recorderは、proxyを通してブラウザアクセスして記録を取ことで、Scalaで書かれたシナリオファイルを自動生成してくれるツール。
もちろん、自分でScalaを書いてシナリオ作りをしても良いが、今回は手軽にできるproxy式の手法をとる。
起動と設定
Gatling Recorderを起動する。
% cd gatling-charts-highcharts-bundle-2.1.6 % bin/recorder.sh
ツールが起動したら、初期設定として以下のように項目を埋める。
この状態で、WebプロキシとしてGatling Recorderを設定する。
ブラウザアクセスでシナリオを作る
Start
を押すと設定に従ってproxyが起動するので、ブラウザでアクセスしながら記録を取る。
シナリオ作成時のTipsとして、変なpluginなどの影響がないようにsecret windowでやるとよさ気。 また、Gatling Recorderにはログの間にTAGを差し込む機能があり、シナリオの記録が綺麗になるのでそれも活用するといい。
ということで、以下の流れで操作を記録する。
- Recorder側で
Search
タグを追加 - ブラウザでhttp://computer-database.herokuapp.com/ のトップに移動
mackbook
で検索Macbook Pro
をBrowse
タグを追加- http://computer-database.herokuapp.com/ のトップに戻る
- 何回か
Next
ボタンで遷移 - いくつかページを開いてみる
Edit
タグを追加- http://computer-database.herokuapp.com/ のトップに戻る
Add new computer
を押す- (チュートリアルではもっとあるけど、ここまでにした)
ここまで実行し終わったら、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
思った以上にリッチなレポートが出力されていて、全体のrpsの状況や成功失敗のグラフ、各リクエストごとの詳細な情報、パーセンタイル値などが出た。
今回のテストは動作チェック程度なので大したグラフになっていないが、これはまじめに負荷テストやるとすごく良さそう。 機会があったら、プロダクト側で使ってみたい。