恥ずかしながら私はPromiseのthenとcatchの順番に意味があるとは知りませんでした

どういうこと?

catch => thenで書いたときとthen => catchで書いたときの挙動が異なります。

次のコードを用意しました。

ボタンを左から順番に押してみるとどういうことかわかると思います。

See the Pen then catchの順番によって意図しないコードになる by serinuntius (@serinuntius) on CodePen.

そうです。最後のボタンを押したときだけ2回alertが出ましたね。

最後のボタンは catch => thenの順序で描いて、条件的にcatchに入るパターン です。

使い分けると便利?かもしれません。おそらく大半のケースで then => catch という順序で書いた方が意図する実行結果になるということがわかりました。

今回はたまたまFirebaseのドキュメントを見ながら書いていて、catchが書いてあったからそこにthenを足したことで気づきましたw

Failed to load gRPC binary module because it was not installed for the current systemで怒られたときの対処法

なかなか解決しなかったので一応メモ。

環境

node: 10系

使用パッケージ

  • electron
  • firebase

対処法

1. nodeを9系にダウングレードする(あまり意味ないかも)

僕は ndenv でnodeをインストールしているので

$ ndenv install -l |less
$ ndenv install v9.11.2
$ ndenv global v9.11.2

node_modulesを吹っ飛ばす

$ rm -rf node_modules
$ npm i

これでもまだ僕の場合駄目でした。

2. rebuildする

$ npm rebuild --runtime=electron --target=2.0.11 --disturl=https://atom.io/download/electron --abi=57

これで僕の場合解決しました。

参考

stackoverflow.com

新卒で入社して1年半ぐらい勤めた面白法人Kayacを退職しました

はじめに

この記事は ex-KAYAC Advent Calendar 2018 の15日目の記事です。

おそらくこのアドベントカレンダーの中で僕は最新の辞めた人ですね。今までと今後について書きたいと思います。

今まで

受託チーム(CL)時代

プラコレという、結婚式をしたいカップルと結婚式場をマッチングさせてチャットで予約できるサービスや、両家のレシピという新婚のカップルの両親からレシピ本を聞き出し製本するサービスの開発に携わっていました。

CLでは珍しい社内やグループのサービスと携わっていました。CLはクライアントの広告をお手伝いする系の案件が多い中、今思うと自分はあまりそういった案件には関わらなかったですね。

pla-cole.wedding

prtimes.jp

去年の11月末ぐらいに異動しないかというお誘いがあり、SG(ソーシャルゲーム)事業部に異動しました。

ゲームチーム時代

数年運用しているゲームに配属されました。

中規模のチーム開発や、運用が長いタイトルの大変さを学びました。

10ヶ月ぐらいそのチームで過ごして、そろそろ他のことをしようかな〜と思ってた頃にブロックチェーンと出会いました。

ブロックチェーンと出会ってから

BuildersconでEthreumを知り、DAppsというものを知りました。

現在サーバサイドのエンジニアをやっていますが、自分の仕事がなくなる、もしくは置き換わって行くな〜という気がして、すぐさま勉強しようと思いました。

ビルコンの2週間後にBitcoinハッカソンがあるというをその前日ぐらいに気づいて、参加を決意しました。

そのハッカソンで優勝し、もっと実績を作りたいと思い、どんどんブロックチェーンハッカソンに参加します。

2ヶ月強でこれだけのハッカソンに参加しました。さまざまな人と出会い、いろいろなアイデアや、様々なチェーンの開発をしました。

これから

株式会社を登記するだけの費用を稼ぐことが出来たら、起業しようと思ってます。

とりあえず今はブロックチェーンに関係ない便利なアプリケーションの開発をしていますが、それが一段落すればブロックチェーンを使ったちゃんとユーザにメリットがあるサービスを作りたいですね。

ほしい物リスト

退職エントリーを書くときにはほしい物リストのリンクを貼るのが通例のようなのではらせていただきます。

全て書籍です。(Kindle本は不可能らしいので、ギフト券にしました。応援してくださる方はwasab1gjがIDのGmailドメインに送ってください)

http://amzn.asia/0ESqtgN

さいごに

会社を辞めたということをいうとよく聞かれるのが、「なぜやめたか?」なのですが、それは明確な答えが1つあります。

自分が成功しなかったことを会社のせいにしたくないからです。 100%の自由度で挑戦して、全て自己責任にしたかったのです。

会社のせいにするのってめっちゃダサいので・・・。

そんなこんなでこれから頑張っていきます。これからもよろしくお願いいたします。

GoのWebアプリケーションのSQLロガーとパフォーマンス分析ツールを作ってみた

はじめに

この記事は Go2 Advent Calendar 2018の記事です。

担当の10日より1時間ほど投稿が遅れてしまったことをお詫びします。

今回は、拙作のGo製のWebアプリケーションにSQLのクエリロガーを仕込めるOSS『graqt』 を紹介したいと思います。

過去に書いた記事を足して2で割ったような記事なので、graqtを知ってる方は読まなくてもいいと思います。*1

graqtとは

github.com

go-request-and-query-loggerです。

発音はガラクタでお願いします。

Go用のアクセスログとクエリのログを記録するためのhttp handlerミドルウェアで、そのログを解析するためのツールもついてます。

使い方

シンプルなWebアプリケーションの実装例

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`))
}

CLI部分

gocuiというパッケージを使って実装しています。

alpをリスペクトして作ったCLIの動作例*2

今はこれより進化してて、普通に全部の情報が正しく表示されるはずです。

まとめ

ある程度便利なの作ったはずなので、煮るなり焼くなりしてあげてください。

*1:あまり居ないと思いますが

*2:すみません、再度録画するのが面倒で使いまわしです・・・

#isucon 8 で 惨敗してきました

以下のレポートは書いてる途中で力尽きてしまい不完全なものです・・・。あらかじめご了承ください。

2018/09/15(土)にISUCON8に参加して、ボロ負けしてきたのでそのレポートです。

SlackでのやりとりやGitHubのコードをベースに、時間ごとに何をやったかをざっくり書いていきたいと思います。

メンバー

@serinuntius アプリケーション・エンジニア

@dozen (会社の同期) インフラ・エンジニア

@kazasiki (会社の先輩) アプリケーション・エンジニア

面倒なので、このレポートでは敬称略とさせていただきます。

10:00~10:30

ポータルログインして、レギュレーションを読む。同時にSSHのコンフィグを書いてました。

次のようなことを確認しました。

  • ベンチマーが側の帯域がかなりでかい!
  • PHPは動かすのにH2Oのコンフィグ書き換えなきゃいけないのか〜大変そうだな〜(まだ、このときはGo実装でもH2Oが動いてるとはしらず・・・。ちゃんと「PHP以外の実装」という言葉がでてきているのにね
  • GET /initialize は10秒以内に返す
  • なお、問題ページの内容はできるだけ最新の状態を反映させる必要がありますが、1秒以内の遅延は許されます。 非同期処理をおすすめするような文言
  • アプリケーションは全て、保存データを永続化する必要があります。 これも気になった。

あと、ポータルを確認すると、ベンチマーカのIPアドレスが1つしか設定できないので、ロードバランスしないといけないんだろうな〜という話はしていました。

僕が alp, pt-query-diegest,dstat,notify_slack を事前に用意してた温かみのあるコマンドの羅列を直接打って入れましたw

僕がGoの初期実装に変更して、ベンチ回すと1194でした。

f:id:serinuntius:20180917083831p:plain

topとかで負荷を確認したところ、すごくMySQLが支配的で、ループとかIndexやばそうだねっていう話をしました。

@kazasiki が、Redisを全台に入れてくれました。

10:30~

@kazasiki のMacがフリーズする

@dozen が ホスト名をつけてくれて、今何でログインしているかとか、通信もisu2とかでいけるようになる。

@dozenがスペックを貼ってくれる

スペック
CPU: 2コア
MEM: 1GB / swap 2GB

@serinuntius が alpの設定をNginxに入れようと思ったら、H2Oで動いていることに気がつく。

H2O全然わからんし、剥がしてNginx(open resty)で行こう!ってなった。

けど、この判断は結論からいうとミスだったかもしれない。 そもそも、H2Oはボトルネックではないし、ちゃんと動いてたし、なんならNginxよりパフォーマンスがいいという話を聞く。 alp形式のログにするのも、ちょっと調べてみるべきだった。

alpでボトルネックを調査して、初動を決めるというムーブが完全にできなくなってしまったので、レコード数の調査やアプリを読むことにした。 @dozenに、Nginx移行を進めてもらいながら、@serinuntius, @kazasikiはアプリを読むという割り振りになった。

MySQLのレコード数やIndexのサイズをパッと出してくれるSQLを、@serinuntiusが出す。

MariaDB [torb]> select
    ->     table_name, engine, table_rows as tbl_rows, avg_row_length as rlen,
    ->     floor((data_length+index_length)/1024/1024) as allMB, #総容量
    ->     floor((data_length)/1024/1024) as dMB, #データ容量
    ->     floor((index_length)/1024/1024) as iMB #インデックス容量
    ->     from information_schema.tables
    ->     where table_schema=database()
    ->     order by (data_length+index_length) desc;
+----------------+--------+----------+------+-------+------+------+
| table_name     | engine | tbl_rows | rlen | allMB | dMB  | iMB  |
+----------------+--------+----------+------+-------+------+------+
| reservations   | InnoDB |   192507 |   57 |    15 |   10 |    4 |
| users          | InnoDB |     5173 |  307 |     1 |    1 |    0 |
| sheets         | InnoDB |     1229 |   53 |     0 |    0 |    0 |
| administrators | InnoDB |      101 |  162 |     0 |    0 |    0 |
| events         | InnoDB |       21 |  780 |     0 |    0 |    0 |
+----------------+--------+----------+------+-------+------+------+
5 rows in set (0.01 sec)

11:00 ~ アプリを読んでみる

@kazasikiとわいわいしながら、「何これ糞すぎ」「なにこれヤバイ」といいながらアプリを読んでいると、いくつかこれは確実にボトルネックになるであろうヤバポイントがあった。

getEvent, getEventsの「N+1」

こんだけの箇所で、getEventが呼ばれていた↓。

f:id:serinuntius:20180917090542p:plain

これを直すだけでも、だいぶ良さそう。

SHA2 の計算をわざわざMySQLに問い合わせて計算してもらっている

それ自体はそんなに重くないかもだけど、回数が多くなるとレイテンシが乗るしかなり無駄。

@kazasiki がざっと読んで、こんなメモを残していた。(原文ママ

* [ループクエリ]func getEventsからfunc getEventを読んでいて、getEvent内でsheetsやreserveを引いてる

* 455行目totalPriceが毎回reserveをgroupbyしてsum()してるからまずい

* 385行目/509行目でpasswordのsha2変換をmysqlにやらせている

* 517行目でuserをもう一回引いてるけど無駄?

* 533行のsanitizeEventはオブジェクトコピーがいらない気がする

• 167行目のgetLoginUserが毎回ユーザ情報をdbから引いている

11:30〜

@dozenのNginxへの移行が終わり、やっとalpでの集計ができた。 ちなみにNginxへ移行してスコアが200点ほど落ちた。

こんな感じのコマンドを叩いてました。

[root@118-27-24-38 nginx]# cat access.log |alp --aggregates="/api/users/.*, /api/events/.*/sheets/.*/.*/reservation, /admin/api/events/.*/actions/edit"|less

f:id:serinuntius:20180917091955p:plain

/admin/api/users/:id/ が遅い。

予想通り、 getEvent & getEvents がネックぽいってなったので、@serinuntiusはそれを修正することにした。 @kazasikiはtotalPriceのSUMをどうにかすることになった。

12:30〜

N+1も酷いんだけど、SheetってUPDATEも走らないし、INSERTもDELETEも走らないので、cacheすれば良くない?と思って、 /initialize でGoのメモリにキャッシュすることにした。 *1

ただ、cacheするだけじゃだめで、ハマった。何回かFailしてやっと意味がわかった。 ポインタ型を渡してしまうと、いろいろな処理でゼロ埋めみたいな処理とかが走って駄目だったみたい。

ポインタじゃなくて、実態を渡すことで回避した。 @kazasikiにいろいろと助けてもらった。

ただ、これはスコアにほぼ影響せず、12:38に score: 963 でマージした。

初めてのアプリの修正が競技開始2時間半って、

「うわっ・・・俺らのチーム遅すぎ・・・?」

ほぼ同時刻ぐらいに、@kazasikiの修正も終わったので、ブランチ変えて試してたら、Index貼るスキーマ, がなかったりして、修正してマージした。

12:54 score:1125

この2つの修正後のalp。まだgetEvent修正してないので、そんなに変化はない。 f:id:serinuntius:20180917122831p:plain

13:00〜

@serinuntius が getEventの修正をする。

@dozenがSlowログ出す。

@kazasiki は レポート作成時の不要なソートをしないっていうのをやっていた。 report作成時にやってる不要なソートを消す by kazasiki · Pull Request #6 · dozen/isucon8 · GitHub

15:00~

書くのも辛いが、getEventの修正にめちゃくちゃ時間がかかってしまった。

マージできたのは15時になった。 全部の関数には適当しなかったので、スコアは 1608

いろいろ自分がポンコツ過ぎて、@kazasikiにめっちゃ助けてもらった。 今思うと、常にペアプロで正確にガシガシ修正して行ったほうがよかったかもしれないと思った。

15:40~

別の箇所でもgetEventsを適用した。

score: 2289

17:00~

@kazasikiが直近の予約の取得を僕が作った、N+1改善後の関数にしてくれて、ここで大きくはねた。

スコアは 12635

なんかめっちゃ時間かかってしまったな〜という感じ。

*1:Goのメモリっていう表現はなんか変だけど、Redisではないっていうこと。

builderscon 2018に行ってきたよ

builderscon.io

builderscon 2018 に 参加してきたので、レポート。

buildersconの良いところ

  • 知識欲を満たせる豊富なカテゴリのセッション!
  • その道に詳しい人によるハイレベルなセッション!
  • 「知らなかった!」を絶対聞ける!
  • 普段(使ってない|知らない)技術でも「ちょっと聞いてみよ」ができる
  • 開催してる場所が日吉駅で、個人的にそこそこアクセスが良い

buildersconのイマイチなところ

  • ステッカーいらない問題(後述します)
  • 人気のセッションが聞けなかったり、座れなかったりする(とはいえ、大体のセッションがYouTubeで公開されるはずなので、後で見れたりできるはず)
  • ゆえに、発表後のQ&Aを聞かずに待機列に並ぶ人も・・・

空間による制約のせいなので、全員VR上でカンファレンスすればいいのでは????(極端

自分のよかったところ

  • チケットの販売が始まった瞬間に、前売り券を買ったこと
  • 遅刻せずに(懇親会を覗く)全日程をこなした

自分の悪かったところ

  • スポンサーチケットを買わなかったところ(名札欲しいっていろいろなセッション聞いてめっちゃ思った・・・)
  • LTしたいな〜と思っていたら、いつの間にか登録終わってたみたい(というか、どこからどのタイミングでLT申し込みできたのかよくわからなかった。)
  • 懇親会に行ったりして、知らない人と話すみたいなムーブをしなかった

聞いたセッション

  • Envoy internals deep dive
  • ランチセッション (サイボウズ株式会社): Kubernetes で実現するインフラ自動構築パイプライン
  • Building and operating a service mesh at mid-size company
  • Webサービスにて200週連続で新機能をリリースする舞台裏
  • 実録!ある担当者がみた「謎ガジェット」開発1年史
  • JavaCardの世界
  • ブロックチェーン(DApp)で作る世界を変える分散型ゲームの世界
  • 次世代通信プロトコルにおけるセキュリティ・プライバシー保護・パフォーマンス (Security, privacy, performance of the upcoming transport protocols)

  • 高集積コンテナホスティングにおけるボトルネックとその解法

  • ブログサービスのHTTPS化を支えたAWSで作るピタゴラスイッチ

  • Webアプリケーションエンジニアが知るべきDNSの基本

  • なぜ私はキーボードを作るのか

私的ベストスピーカー

意見

完全に余談です。超個人的な意見ですが、主催者がフィードバックを求めているとおっしゃっていたので書きます。

企業ステッカーいらない問題

って書いたけど、よく考えたらスポンサー企業によって支えられてるから、受け取る義務があるのかな・・・。

各企業の方は、本当にステッカー需要あるか考えたほうがいいかも・・・。

少なくとも僕にとっては本当にいらないし、捨てるのもかわいそうだけど、全部捨てました・・・。

他のノベルティは大体嬉しいけど、ステッカー本当にいらない・・・。

まとめ

buildersconに関わるすべての皆様、ありがとうございました!

控えめにいって、これ以上最高のカンファレンスはないだろうっていうぐらい最高でした。

この記事を読んだ人で、buiidersconに参加したことない人は絶対1度参加してみることを強くオススメします!