【追記アリ】Golangで1行1行がJSONのログを効率的にパースする
この記事でも書いたけど、ISUCON用のロガーを作ったので、そのログをパースするツールを書こうと思った。
少しパフォーマンスを意識して書いてみたけど、きっともっとパフォーマンスが良くなる気がするのでマサカリウェルカムです。
続きを読むGo専用対ISUCON兵器 「graqt」を書いた
はじめに
この記事でも書いたけど、最近ISUCONの面白さに気づいた。 serinuntius.hatenablog.jp
社内ISUCONはRubyで書かれていたけど、本家ISUCONでは使用言語が選択できる。 そうなったら、今なら迷わずにGoを選ぶ。
それは、何故かと言うとGoが速いとかそういう理由よりも、Goland
というIDEのおかげである。
こいつの補完やらなんやらがとても良くできているので、ソースを読むのも書くのも爆速になるからである。
RubyにもJetbrains製の Rubymine
というのがあるけれど、動的言語だからか完全にはコードジャンプできない。
同名のメソッドがあったときには、選択肢が出てユーザにどのメソッドですか?みたいな感じで問いかける感じ。
けど、Golandは型があるからか100%の精度でコードジャンプできる。 そのおかげで、爆速コードリーディングが可能になる。
Golangが最高という話はおいておいて、Go用のクエリのロガーとアクセスログを出すミドルウェアを書いたので、その説明をしたいと思う。
graqtとは
go-request-and-query-loggerです。*1
発音は、 グラクトか、ガラクタでお願いします。
僕はガラクタって読んでるw*2
Go用のアクセスログとクエリのログを記録するためのhttp handlerミドルウェアです。*3
主にISUCONで使うだろうな〜と思って書いたけど、全然普通のプロジェクトでも使えば便利そう。
alpとpt-query-digestを合体させたものが作りたいな〜というのが、この構想の始まり。
どんなログが出るの?
access.log
には 主に time
, request_id
, path
, method
が記録される。
{"level":"info","ts":1525082389.1337767,"caller":"graqt/middleware.go:25","msg":"","time":0.011566198,"request_id":"5914e629-6746-42c5-b342-702f224f48e1","path":"/user","method":"POST"} {"level":"info","ts":1525082389.2157433,"caller":"graqt/middleware.go:25","msg":"","time":0.003945689,"request_id":"4e743b2a-59be-4692-b36b-3b6a9ba78b01","path":"/user","method":"POST"} {"level":"info","ts":1525082389.2983494,"caller":"graqt/middleware.go:25","msg":"","time":0.014426681,"request_id":"f1d68fb3-5450-48f3-9f46-1ea5ad2370fb","path":"/user","method":"POST"} {"level":"info","ts":1525082389.3756797,"caller":"graqt/middleware.go:25","msg":"","time":0.004002398,"request_id":"e727db2b-df12-410c-91a4-4dd3117b9452","path":"/user","method":"POST"} {"level":"info","ts":1525082389.47504,"caller":"graqt/middleware.go:25","msg":"","time":0.005889232,"request_id":"57df4708-9705-4401-98b2-fad4bacfe585","path":"/user","method":"POST"} {"level":"info","ts":1525082389.554212,"caller":"graqt/middleware.go:25","msg":"","time":0.016009073,"request_id":"58957516-c7b2-4f64-8889-b39496e6597f","path":"/user","method":"POST"}
query.log
には 主にquery
,args
, time
, request_id
が記録される。
- query => クエリ
- args => prepareステートメントだと、その引数が入る
- time => そのクエリにかかった時間
- request_id => さっき↑で出てきた、リクエストごとに一意のid。これのおかげで、どのリクエストでどんだけのクエリが呼ばれるかを調べるという寸法
{"level":"info","ts":1525082389.1336403,"caller":"graqt/tracer.go:20","msg":"Exec","query":"INSERT INTO `user` (email,age) VALUES (?, ?)","args":[{"Name":"","Ordinal":1,"Value":"hoge1525082389130688404@hoge.com"},{"Name":"","Ordinal":2,"Value":57}],"time":0.001157627,"request_id":"5914e629-6746-42c5-b342-702f224f48e1"} {"level":"info","ts":1525082389.215664,"caller":"graqt/tracer.go:20","msg":"Exec","query":"INSERT INTO `user` (email,age) VALUES (?, ?)","args":[{"Name":"","Ordinal":1,"Value":"hoge1525082389213632765@hoge.com"},{"Name":"","Ordinal":2,"Value":37}],"time":0.002013919,"request_id":"4e743b2a-59be-4692-b36b-3b6a9ba78b01"} {"level":"info","ts":1525082389.2982852,"caller":"graqt/tracer.go:20","msg":"Exec","query":"INSERT INTO `user` (email,age) VALUES (?, ?)","args":[{"Name":"","Ordinal":1,"Value":"hoge1525082389287262677@hoge.com"},{"Name":"","Ordinal":2,"Value":74}],"time":0.011005944,"request_id":"f1d68fb3-5450-48f3-9f46-1ea5ad2370fb"} {"level":"info","ts":1525082389.37562,"caller":"graqt/tracer.go:20","msg":"Exec","query":"INSERT INTO `user` (email,age) VALUES (?, ?)","args":[{"Name":"","Ordinal":1,"Value":"hoge1525082389372293507@hoge.com"},{"Name":"","Ordinal":2,"Value":64}],"time":0.003297617,"request_id":"e727db2b-df12-410c-91a4-4dd3117b9452"} {"level":"info","ts":1525082389.474928,"caller":"graqt/tracer.go:20","msg":"Exec","query":"INSERT INTO `user` (email,age) VALUES (?, ?)","args":[{"Name":"","Ordinal":1,"Value":"hoge1525082389470871128@hoge.com"},{"Name":"","Ordinal":2,"Value":73}],"time":0.001937477,"request_id":"57df4708-9705-4401-98b2-fad4bacfe585"} {"level":"info","ts":1525082389.5541484,"caller":"graqt/tracer.go:20","msg":"Exec","query":"INSERT INTO `user` (email,age) VALUES (?, ?)","args":[{"Name":"","Ordinal":1,"Value":"hoge1525082389550824448@hoge.com"},{"Name":"","Ordinal":2,"Value":39}],"time":0.001194376,"request_id":"58957516-c7b2-4f64-8889-b39496e6597f"}
使い方
変更しなくちゃいけないだろうな〜というところにコメントしておいた。簡単に移行できるはず。
package main import ( "database/sql" "fmt" "math/rand" "net/http" "os" "time" _ "github.com/go-sql-driver/mysql" // 2つパッケージを追加してください "github.com/justinas/alice" "github.com/serinuntius/graqt" ) var ( // 2つ変数を追加してください traceEnabled = os.Getenv("GRAQT_TRACE") driverName = "mysql" db *sql.DB ) func init() { rand.Seed(time.Now().UnixNano()) } func main() { // ifを追加して 環境変数 GRAQT_TRACEが1のときだけログを吐きます。 // ISUCONで本番ベンチのときは オフにしておいたほうがいいと思います。 // しかし、パフォーマンスには結構気を使ったので、そんなに低下はしないと思います。 if traceEnabled == "1" { // driverNameは絶対にこれでお願いします。 driverName = "mysql-tracer" // もし、ログを吐く場所を変えたかったら以下のメソッドを呼んで下さい。 // デフォルトは、 query.logとrequest.logが このGoファイル(main.go)と同じディレクトリに出ると思います。 // graqt.SetQueryLogger("log/query.log") // graqt.SetRequestLogger("log/request.log") } var err error // ここを先程の変数に変更してください db, err = sql.Open(driverName, "root:@/graqt") if err != nil { panic(err) } defer db.Close() mux := http.NewServeMux() mux.HandleFunc("/user", createUser) // ミドルウェアを追加します。 var chain alice.Chain if traceEnabled == "1" { chain = alice.New(graqt.RequestId) } else { chain = alice.New() } // ミドルウェアを渡します http.ListenAndServe(":8080", chain.Then(mux)) } func createUser(w http.ResponseWriter, r *http.Request) { // contextを使用する方のメソッドに変えてください。 ctx := r.Context() stmt, _ := db.PrepareContext(ctx, "INSERT INTO `user` (email,age) VALUES (?, ?)") t1 := time.Now().UnixNano() age := rand.Intn(80) stmt.ExecContext(ctx, fmt.Sprintf("hoge%d@hoge.com", t1), age) w.Write([]byte(`ok`)) }
仕組み(ミドルウェア側)
まずはhttp handlerのミドルウェアの説明から。
http.Handler
を返す 関数を作ればそれをミドルウェアとして登録できる。
今回いろいろなミドルウェアを追加するときに面倒かな〜と思って、alice
を利用した。
http.Handlerを返す関数はこんな感じ↓。 時間を測って、uuid-v4のrequestIDを生成してcontextに突っ込む。 next.ServeHTTPで本来の処理をして、終わってからログを吐くという形。
func RequestId(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t1 := time.Now() id := newRequestID() ctx := setRequestID(r.Context(), id) next.ServeHTTP(w, r.WithContext(ctx)) // 終わったあとにログを吐く RLogger.Info("", zap.Duration("time", time.Since(t1)), zap.String("request_id", id), zap.String("path", r.RequestURI), zap.String("method", r.Method), ) }) }
仕組み(クエリロガー側)
shogo82148さんの go-sql-proxy
がめちゃくちゃ便利でした。*4
github.com
こんな感じで、Execする前に何をするか、Execした後に何をするかっていうのを書けるので、好みの処理を書くだけ。 今回は、クエリごとの時間を計測することと、クエリのログを吐くっていうのが目的なので、以下のような感じにしている。
sql.Register("mysql-tracer", proxy.NewProxyContext(&mysql.MySQLDriver{}, &proxy.HooksContext{ PreExec: func(_ context.Context, _ *proxy.Stmt, _ []driver.NamedValue) (interface{}, error) { return time.Now(), nil }, PostExec: func(c context.Context, ctx interface{}, stmt *proxy.Stmt, args []driver.NamedValue, _ driver.Result, _ error) error { QLogger.Info("Exec", zap.String("query", stmt.QueryString), zap.Any("args", args), zap.Duration("time", time.Since(ctx.(time.Time))), zap.String("request_id", c.Value(RequestIDKey).(string)), ) return nil }, ... }))
go-sql-proxyにもデフォルトでほぼ同じ機能のロガーがあるのだけど、今回のポイントはリクエストごとに一意になるIDを付け加えたいというのと、構造化されたログ(json)を吐きたいというところなので、手を加えさせて貰った。
構造化ログを吐くのにパフォーマンスがいいと謳われている、zap
を利用した。
github.com
パフォーマンスをそんなに気にしなくていいと言ったのはこのため。 ~実際wrkでベンチしたけど、ログを入れたからっといって有意な差はあまりなかった。~
追記
流石に雑すぎるので、次の項目でベンチ結果を貼る。
パフォーマンス
100query/recのアプリでログあり
$ wrk http://localhost:8080/user Running 10s test @ http://localhost:8080/user 2 threads and 10 connections Thread Stats Avg Stdev Max +/- Stdev Latency 181.55ms 44.71ms 346.20ms 70.86% Req/Sec 27.58 10.54 50.00 63.02% 549 requests in 10.06s, 63.26KB read Requests/sec: 54.56 Transfer/sec: 6.29KB
100query/reqのアプリでログなし
$ wrk http://localhost:8080/user Running 10s test @ http://localhost:8080/user 2 threads and 10 connections Thread Stats Avg Stdev Max +/- Stdev Latency 168.48ms 40.87ms 308.79ms 68.35% Req/Sec 29.59 11.74 50.00 78.87% 594 requests in 10.09s, 68.45KB read Requests/sec: 58.89 Transfer/sec: 6.79KB
1query/reqのアプリでログあり
wrk http://localhost:8080/user Running 10s test @ http://localhost:8080/user 2 threads and 10 connections Thread Stats Avg Stdev Max +/- Stdev Latency 8.74ms 14.81ms 270.78ms 95.19% Req/Sec 780.89 156.08 1.19k 69.65% 15628 requests in 10.10s, 1.76MB read Requests/sec: 1547.08 Transfer/sec: 178.28KB
1query/reqのアプリでログなし
wrk http://localhost:8080/user Running 10s test @ http://localhost:8080/user 2 threads and 10 connections Thread Stats Avg Stdev Max +/- Stdev Latency 7.55ms 7.72ms 107.14ms 89.51% Req/Sec 811.16 187.08 1.55k 69.65% 16235 requests in 10.10s, 1.83MB read Requests/sec: 1607.31 Transfer/sec: 185.22KB
まぁ、遅くなるけど、本気ベンチじゃないときにこれぐらいのスコア下がってもまぁ良いよねって感じ。
TODO
- このリクエストやクエリのログを読み込んで、ボトルネックをわかりやすくするための「graqtクライアント」作成*5
- mysql固定で書いたけど、ドライバーの登録部分を変えるだけで他のドライバーにもできるはずなので、そこを調整する
- context対応してないorm?だったらどうしよう?
- ISUCONで使用されたorm?はsqlxかdatabase/sqlだけなのでcontext対応しているので大丈夫!
まとめ
対ISUCON兵器とか釣りっぽく書いてしまったけど、まだログを吐けるだけでボトルネックは簡単には探せないw
graqtクライアントを書いたときに真価が問われる。
graqtクライアントはこんな感じで考えている。
リクエストモード
全体的に俯瞰して、どのリクエストが重いかを調べるモード。
- COUNT => そのリクエストの回数
- AVG => 平均リクエスト時間
- MIN => 最小リクエスト時間
- MAX => 最大リクエスト時間
- PATH => リクエストパス
- METHOD => メソッド
- QueryCount => 1リクエストのクエリの数
- DB Rate => クエリの合計時間/リクエストの時間 (これで、1リクエストでどれだけDBが支配してるかわかる)
COUNT | AVG | MIN | MAX | PATH | METHOD | QueryCount | DB Rate |
---|---|---|---|---|---|---|---|
300 | 20 | 13 | 30 | / | GET | 30 | 0.39 |
300 | 20 | 20 | 40 | /user | POST | 49 | 0.79 |
300 | 20 | 30 | 50 | /hoge | GET | 59 | 0.38 |
300 | 20 | 50 | 80 | /fuga | GET | 492 | 0.58 |
300 | 20 | 70 | 90 | /fuga | GET | 543 | 0.33 |
クエリモード
重いリクエストがわかったら、そのリクエストを選んでそのリクエストで飛んでるクエリが閲覧出来るモード。
- QueryTime => 1クエリの時間
- Count => そのクエリが1リクエストで呼ばれた数
- Args => Prepareステートメントの引数(サンプル1つだけ)
- Caller => どこでそのクエリが発行されてるかを示す(今はない)
QueryTime | Count | Query | Args | Caller |
---|---|---|---|---|
300ms | 30 | SELECT * FROM USER WHERE id = ? | 3 | main.go:23 |
30ms | 1 | INSERT INTO user (email,age) VALUES (?, ?) |
["hoge@hoge", 30] | main.go:43 |
たぶん初めて自分のOSS書いたけど、浸透するまで時間かかりそう。(別にしなくてもいいけどw)
一番の浸透させる方法は、これを使って(または、使った人が)ISUCONに優勝することだろうな〜。 (けど、DBの負荷を減らすのなんて決勝に出る人達はこんなツールがなくても余裕でやってのけるんだろうな〜とかw)
ISUCON7優勝者と社内ISUCONに出て学んだ12のこと
git blameとpecoとhubで特定行のGitHubのPRを一発で開くのを実現するgit alias
tl;dr
大規模なプロジェクトでコード書いてると、「なんでこういう設計になってるのか」とか「当時はどういう仕様だったのか」みたいなのを知りたいことがよくある。
そういったときに、プルリクエストを見ることで知りたい情報を手に入れられることがある。
この行のプルリクエストをみたいな〜と思ったときに、参考にあるCommit Hashから調べる方法で解決していたのだけど、Commit Hashを調べてコピーして git openpr <commit hash>
ってやるのが手間だな〜と思ったので一発でこれができるコマンド(blamepr
)を作った。
依存ツール
両方brewで入る
設定
~/.gitconfig
に↓を追記する。
別にblamepr
という名前じゃなくてもいい。自分で好きな名前にしてください。
[alias] openpr = "!f() { hub browse -- `git log --merges --oneline --reverse --ancestry-path $1...master | grep 'Merge pull request #' | head -n 1 | cut -f5 -d' ' | sed -e 's%#%pull/%'`; }; f" blamepr = "!f() { git blame $1 |peco| cut -f 1 -d ' ' | xargs -I@ git openpr @; };f"
使い方
とても簡単で、 git blamepr <file_name>
とやるとpecoにgit blameされたものが出てくるので該当行を絞り込んで、Enterを押すだけ。
参考
新卒が入社して一年で学んだこと
あっという間に1年が過ぎ、入社して2年目になってしまった。
MacBookに乗る実家の猫ちゃん
丁度良い機会なので、自分が1年でどんなことを学び、どんなことを学んで行きたいかを書こうかと思う。
志
今までと圧倒的に違うのは、プロ(?)としてお金を貰って仕事をしているわけだから、 学生の頃みたいに、飽きたとか、やる気でないとか、テスト書きたくないとか、わからんで終わらしてはいけなくなった。
わからんで終わらせないためにも、英語のドキュメントも読むし、 場合によってはライブラリの中やミドルウェアの中のソースも読まないといけない。
これは別に自分一人で解決しなくちゃならない訳ではない。
自分で解決出来なけれ先輩に聞くとかして、とりあえず解決に導かないといけない。
なんとかして解決できなければ、いつまでも学生だ。*1
ソースコードリーディング
会社に入ってエンジニアとして働くことになって、他人のコードを読む時間が学生の頃と比べると100倍以上に増えた。*2
まれによくクソコードだなって思ったり、クソコードだと思ってたけど理解したらこうやって書くのもやむなしかみたいに思うこともある。*3
おかげさまで他人のコードを読むということに前以上に抵抗がなくなったので、 疑問に思ったりしたら、わりとラフにライブラリとかミドルウェアとかの中のソースを読むようになった。
この習慣を手に入れたのはデカい。
テストとCI
この2つの重要性に気づかされた1年だった。
サービスとかゲームとか長期運用前提で改修も入れていく系で、 テストが全くない or テストが死んでいる のは超ツラいってわかったので、異動、転職のときは
「テストありますか?」
って聞いてみようって思った。 テストないってことは、
そういう文化がない or アプリケーションの寿命が短い
ってことだと思うので、プロジェクトの特性がどんなものなのかがわかる気がする。
テストがないって、ヘルスチェックをサボるロードバランサーみたなもんなんだよな〜
AWS
学生の頃は、さくらとかのVPS借りてゴニョゴニョとかHerokuにPushみたいなことしかやったことなかったけど、 仕事でAWSとか使うようになって、マネージドなDBとか、LBとか、CDN、サーバレスとか触るようになって世界が広がった。
触れたサービスはこのぐらいかな〜
負荷試験
学生のことはabぐらいしか、負荷試験の仕方を知らなかったけど、 JMeterで負荷試験するようになって、ちゃんと負荷をかけることができるようになってボトルネックの調査ができるようになった。
infrastructure as code
Chef & Capistrano
案件で自分が実際に使うことはなかったけど、ちょっと使えるぐらいにはなった
Docker
ゲームの部署に異動して、docker-compose.yamlが用意してあって初めてのDockerだったけど感動した。
GoとDockerの相性が最高に良いな〜と感じている今日このごろ。
シングルバイナリっていうのと、マルチステージビルドでビルドしたバイナリだけをalpineとかにコピーしたら Imageの容量めっちゃ減らせる。
Kubernetes
Dockerに入門して、勢い余ってKubernetesにまでツッコんでしまった。
まだ、趣味の領域でしかないけど、 GKEでk8sクラスター作って便利だな〜と感動した。
早くAWSにEKSが来て欲しい。 自前でクラスターを運用する気はあまりない。
言語
今思うと、言語なんてわりとどうでも良くって、
- ある程度読める
- ある程度書く人が世の中にいる
- ある程度生産性がある(パフォーマンスじゃなくて)
この条件が満たせるならある程度なんでも良い気がしてきた。*5
で、パフォーマンスが必要な場合は硬めの言語で書いたらいい。
たぶん日本語とか英語と違って、ベースのロジックとかアルゴリズムさえ理解してれば、 プログラミング言語乗り換えるなんてそんなに難しいことじゃないし、必要になれば勉強すればいい。
Ruby
研修が終わって、配属されたWebの受託の部署で8ヶ月ほど書いた。
mapとかの存在を知って、良い感じに書けるようになった。
やっぱり、自分の中で一番サクッと書けるので、ちょっとしたツールとかをサクッと作りたいときに使うかな。
Node.js
コールバックの仕組みが苦手でずっと避けていた言語だった。
自分が理解できない仕組みがあって、理解できない自分を認めるのが怖くて、 〇〇クソみたいなこと言いがちだけど、ほんと辞めようと思った。
AWS LambdaやAPI GatewayやDynamoDBを利用したサーバーレスの構成のときに非常に便利なので頑張って勉強した。
Promiseの仕組みがわかって書けるようになれば、特に躓くことはなかったかな〜。
Serverless Frameworkめっちゃ便利で好きだったな〜。
Perl
ゲームの部署に異動になり、Perlを書くようになった。歴は3ヶ月ほどかな。
あんなに書きたくないと思っていたPerlだけど、*6、ちゃんと書いてみるとそんなにダメな言語だとは思いませんでした。
周りがDisり過ぎてるから、そういうレッテルを貼ってしまっていた感じがする。
少なくとも今僕が携わっているプロジェクトの中では、Perlだからといって困ることはない。 いろいろな書き方ができると言われているPerlで、ここまで体裁を整えたソースコードを残してくれた先輩方に感謝ですかね〜。
Go
Ruby→Perl->Goって学んだから、ポインタとかが分かりやすかった気がする。
GolandっていうIDEで書いてるけど、コードを追いやすくて神。
困ったら⌘ + B
MySQL
学生の頃は、インデックスとか全く気にしたことなかったし、クエリの数とかもあまり気にしたことなかった。
技術部研修のときに、DBの講座があったんだけど、あのときは
「なんで先輩はそこにインデックス張るってわかるんや」
みたいな気持ちで一杯だったけど、今は少しどこにインデックス張るべきかわかった気がする
今年は
GoとDB(MySQL)とインフラに強くなりたいな〜と。
個人的なプロジェクトのk8sの運用も引き続きやってくぞ
まとめ
まさかの1年もせずに異動したりで、学んだことが多かった。
リストアップするとめっちゃあってびっくりした。 1年濃すぎやろ〜
今年もいろいろ学んでいくぞ!!!!!1
SlideShareで日本語が化けるので連番PNGをPDFに変換する
背景
本当はSpeakerDeckに資料を上げたかったんだけど、変換が1日経っても終わらなかったりでどうなっているんだという感じだった。
仕方がないので、SlideShareに上げることにした。 けど、今度はフォントの問題とかがあって、日本語が表示されていない。
ちなみに、スライドのテーマはazusa-colorsを使わせて貰った。
解決策
1. Keynoteのイメージ書き出しで連番のPNGを書き出す。
2. 連番のPNGをPDFに変換する
ImageMagickのconvert
コマンドで変換する。
ImageMagickを入れてない人は適当にググって入れて欲しい。
Keynoteで書き出したディレクトリを見ると、こんな感じのディレクトリになっていると思う。
そこでおもむろに
$ convert *.png out.pdf
と打てば、連結されたPDFが出来上がる。楽だ。
こんなことをせずとも、素直にSpeakerDeckにアップロードしたい人生だった。
YAPC::OkinawaでベストLT賞を貰った若者が得たもの
YAPC::Okinawa 2018でベストLT賞に選んで頂いた
1ヶ月前までRuby書いてたのに、気づいたら沖縄でPerlのLTをしていた
そんな感じ。紆余曲折を経てこうなった。
この記事は、自分が発表者として書いた記事で、聞いた側の記事はこちらに書いた。
資料
www.slideshare.net
LTをして得たもの
LTに参加していろいろなものを得たので紹介したい。
Kindle Oasis
唯一の物理枠。スポンサーのCOLSIS様からKindle Oasisを頂いた。
1ヶ月程前にKindle PaperWhiteを買って、「超いいぞ」ってなってたのでめっちゃ嬉しい。COLSIS様、ありがとうございます。
承認欲求の満たされた感
懇親会でお話させていただいたり、Twitterやブログを観ていると、「面白かった」とか「良かったよ」という声が多かった。
関西人としてこれほど嬉しいことはないだろう。(別に関西人じゃなくても嬉しいと思うけどw)
たった5分喋るだけでこんなに満たされた感があるのすごくコスパ良いw
ベストLTをした人という肩書
この肩書は一時的なものとはいえ、懇親会とかで「ベストLTの人」という肩書のおかげで、学生の方やおじさま方に声をかけて頂いたりした。
自分から話に行くのが苦手な僕にとってはすごくありがたかった。
LTをしてないとそうやって声をかけて頂くことなんて無いわけだから、
LTするのはイイぞ!
度胸
あのでかいホールで、何百人相手にプレゼンする機会って早々ないだろう。 だいぶ度胸付いた気がする。
実は普通のトークセッションよりもLTの方が聴衆は多いのだ。
知見
LTとかってそのコミュニティで有名なLTおじさんみたいなめっちゃ面白い人がいて、ベストLTを取るみたいなイメージがあった。
けど僕みたいな*.pm
とか、その他勉強会とかも行かないコミュニティ新参者でも、ベストLT賞を取ることが出来た。
これは正直意外だった!YAPCコミュニティの若者への優しさを感じた。
YAPC::Tokyoも(行くぞ|出るぞ)という気持ち
次の開催地はTokyoらしいので、横浜に住んでいる僕としてはとても行きやすいので特に障壁がない。 普通のトークセッションはしないかもだけど、最低でもLTはしたいな。 (こうやってブログに書いておくことで、おいお前出るって言ってたよなっていう状況を作り出す)
まとめ
- YAPCはいいぞ
- LTはいいぞ
- ブログ書くのもいいぞ
謝辞
いろいろと準備してくださったスタッフの皆様、スポンサーの皆様ありがとうございました!