ISUCON9予選惨敗録
こんにちはnasaです。
ISUCON9で惨敗したので来年に向けて筆を執りました。
事前準備
特になにかやった感は無いですが、alpやpt-query-digestのインストールスクリプト、nginxのアクセス解析、mysqlのスロークエリ出力の方法をまとめたり、をやっておきました。
あとはチーム内で最初に何をやるかを話し合っておきました。
チームメンバーはインフラ担当akidukiさんと、インターンで会った強いお方kazさんです。
惨敗当日
とりあえずボクの知ってる範囲でやったことを書いていきます。
マニュアル読み読み
kazさんがインスタンスを立てている間にボクとakiさんでマニュアルを読みました。
ISUCARIは椅子を売りたい人/買いたい人をつなげるフリマアプリです。
ここでボクとakiさんはクスクス笑ってました、、、 kazさん邪魔してごめん、、、
今回は負荷レベルを自分で上げていくんだな〜というのがマニュアルを読んで分かりました。
Git管理
ここでコードをローカルで開発出来るようになりました。 go buildすると特にハマりどころ無くビルド出来て楽でした。
ここで最初にgit add
する時に余分なファイルもgit管理してしまい、他のインスタンスを立ててmaster pullすると、エラーを吐いてしまい、pull出来ない状態になってしまいました。
結果として、要らないファイルを消せば行けたので、最初から余分なファイルはaddしないようにしたいと思いました。
アクセスログ、スロークエリを見る。
kazさんとakiさんがやってくれました。
ここでベンチを回して、ログを見ます。 あとhtopも(初期スコア2000くらい)
htopを見るとmysqlの負荷が100%になっていました。合わせてスロークエリもpt-query-digestで集計してみると、クエリ発行数が偉いことになっていて、n+1いっぱいありそう。と思いながらコードを読み始めました。
getNewItemsのN+1をこの世から消し去る
itemsの数だけ、categoryやsellerをselectしていたので、これを一括で持ってくるようにしました。
この時itemsのselectとsellerのselectを別で書いてましたが、今思うとjoinして持ってくればよかったなーと、、、 反省です。
categoryの方は変更がないデータだと分かったのでkazさんがキャッシュしてくれました。
ここで3000イスコインくらいになった気がする。
indexはりはり。
itemsのseller_id、buyer_idにはりはり。 あとは、(status, category_id, created_at)にはりはりしました。
これでスコアが、3600くらいになった気がする。 (正直スコアについてはあまり覚えてない)
DBをmariadbに + nginx confをいい感じに
akiさんがやってくれました。 これらに関するconfファイルはgit管理されてなかったので、どんな設定をしたか知らない状態ですが、スコアは4000位になりました。
何したんだろ。
N + 1を駆逐する。
getTransactionsのN + 1を駆逐して回りました。
さっきも言いましたが、joinを使えばシンプルに終わったのに、2つのselectに分けて、goで一緒のstructの詰める実装をしてました。 アホの所業ですが、スコアはこの時5000くらい。
kazさんやakiさんの変更も入ってるので、純粋にこれだけで5000に行ったかは分かりません、、、
終焉ノ刻
ここから地獄だった気がします、、、
何も出来ず終わってしまいました。
スコアの推移はこんな感じです。 一瞬6000に行きましたが、それ以降上げることは出来ず。
反省
来年に向けて反省を書いておきます。
デプロイの自動化
毎回git pull
して、go build
してsystemctl restart
してってやってました。
時間の無駄すぎるし、go build忘れてて変更が反映されてない!という状況が何度もありました、(systemclt restart忘れも)
最初に簡単なスクリプトを書いておくべきでしたね。
git pull origin msater # ここ他のブランチにも対応したい cd webapp/go make build cd ../../ sudo systemctl restart isucari.golang....
雑に書いてもこんな感じでしょうか? 10秒もかからなかったです。
これをやっておけば無駄な時間を過ごさなくてよかったです、、、、
conf系のgit管理
my.cnfやnginx.cnfをgit管理するようにしたかったです。
そして、cp my.cnf /etc/my.cnf
のように反映する処理を.shに書いておけば楽に反映、バージョン管理出来たはずなので。
git管理したかった一番の理由はどんな変更をしたのか共有しづらいというのがあります。 akiさんがばんばん変更してくれていたのですが、「ん?スコア上がったけど何したん。failしたけど何したん?」という状態でした。
git管理して、PR出してくれればどういった変更が入ったのか共有できるし、ミスが有った時に誰かが気づく可能性が高くなるので、来年はそうしたいと思いました。 (ブログにもどういった変更をしたのか書けますしね!)
開発環境の整備
akiさんが開発環境をdockerに乗せてくれたんですが、使い方がマジでわからなかったです。 (すみませんakiさん!無駄にしました、、、)
これをうまく使えてればローカルでゴリゴリ開発していけたんだろうと思いますが、ボクは実際にインスタンスにsshしてgit pullして動作確認をしてました、、、
スコアを確認したいときはこの運用でいいんでしょうけど、今の状態だと本番環境で開発しているのと変わらない状態だったので、非効率だったなーと思います、、 (本番デバッグだめ!絶対!)
本番で開発するならするで、インスタンスをチーム内で振り分けておくべきでした。
「いまから1番使いまーす」や「2番今誰か使ってます?」のような確認を取ることが本当に多かったです。
あとは、ベンチマークをテスト代わりにしてたのも良くなかったです(主にボクが) target serverを切り替える手間もありますし、アプリケーションにアクセスして挙動を見れば済む話だったなーと今は思います。
反省まとめ
今回の反省は主に開発の進め方に合ったと思います。
正直、getTransactnionsの外部APIが遅いことや、post buyのトランザクション周りを改善するというのは時間がアレば行けたのではないかなーと思っていて、すぐに終わらせるべきだったn+1改善に無駄に時間を取られてしまいました。
ローカル(または本番で)素直に開発して、デバッグ出来ればぱぱっと終わる作業だったので。
楽に開発できる環境を作らなかったことが一番の敗因だと思いました。
感想
ISUCON楽しかったですが、悔いしかありません。
タラレバを言っても仕方ないですが、デバッグを楽に出来る様になっていれば、もっと 計測 < - > 実装 を繰り返し出来たし、外部APIの並列化やpost buyの改善のための時間と取れた気がしてなりません、、、
いや、言っても仕方ないんだけど、、、
チームメンバーがその気ならまた同じメンツでやりたいなーと思います。
akiさん、kazさん来年もよろしくです!!
VOYAGE GROUPの夏インターンTreasureに参加した
どうもnasaです。 8月の12 ~ 31日までの3週間でVOYAGE GROUPのインターンに行ってきたので、振り返り、感想を書いていこうと思います。
参加したインターンの紹介
今回行ったインターンはVOYAGE GROUPの夏インターン"Treasure"です.
Treasureとは
期間 : 三週間
前半 (7日間) : 講義、後半 (8日間) : チーム開発
参加人数 : 30人
報酬 : 12万円
遠方からの参加の場合東京までの交通費支給
自己紹介
軽く自己紹介しときます。
nasaと言います。 今は福岡に居てB3です。
普段は主にRailsやRustを使って、WEBアプリケーションやコマンドラインツールの開発をやっています。あとは細々とRustのウェブアプリケーションフレームワークにPR投げたり、ライブラリのbug fixしたり、リファクタPR投げたりしてます (ほんとに細々と)
詳しくはこちらを
参加の経緯
とりあえず。なんで参加しようと思ったのか、どこで知ったのかを書いていこうと思います。
Treasureを知ったのはあずにゃんペロペロ先輩の紹介です。 最高のインターンだった!っていうのを聞いていてB3になったら参加してみようと思っていました。
(社員さんにペロペロ先輩がどんな人だったのかを聞いたら、内定者の作ったアプリをドヤ顔で攻撃して、それを社内LTで話して、とにかく問題児だったらしいです、、、)
ぼくは福岡にいるのですが、周りに同じようなことをやってる学生が居ない(あまり外に出ないので知らない) ため、技術的な話ができる相手が少ないので、友達を作りに参加しました! Treasureに参加する人は技術的に強い人が多いと聞いていたので。(実際強かった)
他には、講義パートではsecurityやデータモデリングなどがありweb開発に必要な知識で自分が苦手なところを学べると思い、技術的にも圧倒的成長と遂げるべく参加しました。
最後に、後半にチーム開発をやると聞いて参加を決めました。ぼくはチーム開発(ハッカソン的な)がすごく苦手で、ぼく以外の人でアイディア出しが進んでいき、何も話すこと無く作るものが決まってる。とか、チームメンバーの議論のスピードについていけず、気づいたら他のこと話してる!みたいな状況になってしまいます、、、
なので苦手克服のためにもTreasureに参加しよう!と思いました。
選考について
Treasureは特に技術課題とかはなく、普通にESを送って、面接して終わりです。
ただ、インターンの面接なのに3人と面接しました。 1対3ではなく 1対1を3回しました。 長っ!って思いますよね〜? (まあ、実際長かった、、1人 20~30分くらい?たぶん)
まあ、面接と言っても楽なもんで、雑談してたら気づいたら30分立ってる!みたいな感じで進んでいきました。
前半の講義について
カリキュラムとしては以下のような感じです。
- Go 1.5日
- フロント 1.5日
- DB 1日
- 中間課題 1日 、実際3日(金曜に課題が出て月曜発表。なるほど。土日は?)
- セキュリティ 1日
- 認証周りの話 0.5日
- インフラ 0.5日
これ、とにかく一個一個のスピードが早かったです。
Golang
1.5日で「GoでAPI余裕っす!」になるべく頑張りました。 多分なったはず! API余裕っす!
講義と言っても、ずっと説明ではなく「じゃあ、今説明した内容でこれやってみようか! 30分で」という感じで課題がちょいちょい挟まってました。
この制限時間が結構おかしくて、GoでのCLIツールの作り方、httpリクエストの送り方を説明されたあとに「じゃあ、30分でcurl書こうか!」と言われ、「30分だって! カタカタカタ...」と急いで作りました。 マジで時間設定おかしい。
Golangの後半は記事投稿APIを作っていきました。(これも制限時間おかしかった)
ここではペアプロを始めてやりました。 隣の方がツヨツヨの方で、ぼくの実装のおかしいところをすぐに指摘してくれました。
エラーでハマる時間が極端に少なかったし、分からないところはすぐ聞けるのでめちゃめちゃ勉強になった。
JS
ここでは最近流行りのReactやVueをメインでやるのでは無く、fetchやPromise,classのthis、クロージャなど、基礎的なところをメインにやりました。
この時よく言われていたセリフが「迷ったら、苦労する方へ」でした。 (迷わなかったら楽していいよ!)
前日にGoで作ったapiをfetchで叩きまくりました。 この時タイムアウトを設定したり、複数リクエストを投げ両方の結果を待つ。など、fetch apiとsetTimeoutなどをうまく組み合わせて、実装していきました。 最後に聞いた話ですが、今回のJS講義でやったfetchの応用はaxiosを使えば一瞬でできるらしいです、、、、(oh)
けど、色々楽に出来るライブラリを使い続けるというのは危険だと思っていて、中で何をやっているか分からない + 勉強にならないと思います。 普段はRailsを書いているのですが、configをちょっといじるだけでいい感じになってしまうのでGoでゴリゴリとAPIを書いていくのは楽しかったし、仕組みを理解した上で書いているので、すべてを把握した上で書くことが出来ました。
気づいたらフロント関係ない話になってますが、この講義ではシンプルなものを組み合わせて(応用して)使えるのがプログラマーとしてツヨツヨになる秘訣なのでは?と思いました。
DB
データモデリング講義はマジで勉強になった!
普段はなんとなーくでテーブル設計をしてますが、今回は推奨されている手順に従ってテーブル設計をしました。
1.論理モデルの作成 1.1.ユースケースを考える 1.2.エンティティ抽出 1.3.リレーションシップを張る 1.4.主要属性の定義 1.5.主キーの選定 1.6.正規化 2.論理モデルから物理モデルへの変換
ここでは実際のアプリケーションに近いユースケースが与えられて、じゃあ、テーブル設計して。と言われ設計しました。
中間課題
中間課題はテーマがあり「とにかく面白いものを作れ」でした。
とにかく3日しかなかったのと、土日は多少休みたかったのでアイディア出しは雑に終わらせてぱぱっと実装することにしました。
作ったのはCodeHub(仮)というwebサービスで、go playgroundはGolangしか動かせないという欠陥を修正するモノになります。
具体的にはGo以外が動くplaygroundを作ったよ。という話です。 それだけじゃ面白くないので、自分の書いたコードを保存できたり、人の書いたコードが見れたり、それに対してコメントできたり。というアプリケーションです。
テーマの「とにかく面白いものを作れ」を達成できてるか?と聞かれれば、していないと答えるしか無いですね、、、、
けど、3日でよく作れたな〜と自分では満足しています。 タイムアタックみたいな感じで、実装がめちゃめちゃ楽しかったです。
残りの講義は略、、、、 書くの疲れた
後半のチーム開発について
後半はインターン生5人。VOYAGE GROUPの社員さん3人の8人で進めていきました。
サポーターの3人はずっといるわけではなく、ちょくちょくKPTしたり、アドバイスしてくれたりといった立ち位置です。
チーム名とソウル決め
チーム開発開始初日にチームメイトソウルを決めました。
ソウルって何やねん?という話ですが、チームの中での一つの価値観、共通の指標です。例えばあるアプリケーションのアイディアが出た時にそれはチームのソウルにあってるのか?と問いかけることが出来ます。
他にも、どっちかの機能を優先させないといけない状況になったときに、どっちがソウルにあってるか?を問いかけることで決まったりします。
チームで話していくうちに「誰かを幸せにしてぇ!」という話になり、幸せにも色々あるよね、新発見による幸せ、幸せに気づいてないだけだったり。と話が広がっていきました。
そこで、それらをひっくるめて「HAPPs」と呼ぶことにしました。そしてチーム名をHAPPs、ソウルを「happening HAPPS」にしました。
ソウルは、たくさんの幸せを届けるぞい!という意味です。
最初は、ソウルって何やねん。と思っていましたが、2つのアプリケーションどっちで行くか?となったときにより「HAPPs」な方を選べたりしたのであってよかったな。と思います。
HAPPsって言葉を最初に誰が言い出したか忘れたけど、ありがとう!誰か!
アイディア出し
これは難しかったですね。
アイディアに新規性がなかったり、逆に新しすぎたりしました。
講義中にアイディアを出すフレームワークを紹介してもらったのですが、忘れてしまいました、、、、 (講義資料も見つからなかった) 悔しい。
その手順に沿ってどんどん出していきました。
チームメンバーがどんどんアイディアを出していく中、ぼくは「いやー。これもうあるしな〜」と考えてしまい、あまり出せなかったです。
この時に、今は「どんどん出すフェーズ」なのか「出たものを吟味するフェーズ」なのかを意識しておけばよかったと思います。
面白そうなアイディアが出たときはチーム内で盛り上がって話していたのですが、ぼくは前提知識がないからピンとこなかったりでシーンとしていました、、、(結構サポーターの方に心配されていた気がします。ヒシヒシと感じておりました。)
チームHAPPsでは「カジクエストオンライン2」というサービスを作ることにしました。
これは家族内の家事の偏りを減らすことを目的としていて、ゲーミフィケーションを取り入れることによって、楽しく家族内の家事をこなすアプリケーションです。(詳細は割愛!)
実装
このフェーズに入ってから、ぼくの口数がめちゃめちゃ増えたらしいです(自覚ない)
技術以外興味ない!という考えではないのですが、外からはそう見えていたかも知れませんね。
初日はDB設計をみんなでして、5人でモブプロをしました。
チームで一番強い人が、教えつつAPIのエンドポイント1つとそれに関するマイグレーション、フロント1画面を実装するという流れでした。
早いうちにタスクを振り分けて個々で作業する、それかペアプロするというのが時間的には良さそうでしたが、チーム内でスキルに差があったのでみんなが1人でバックもフロントもマイグレも出来るようにするという長期的に見た作戦でした。
これはチーム内で大好評で、「自信なかったけど、出来るようになったわー!」と言ってる人が多かったです。
そして2日目からはタスクを振り分けてどんどん開発していきました。
あとはゴリゴリ実装していくだけなので書くことないですね。
チーム開発感想
最終的には優秀賞をもらえたので、わりと満足でしたが、今となって振り返れば、もっとやれた感があります。
まず、最初にやったモブプロですが、自分で言うのもアレなんですが、バックエンドとマイグレーション周りならぼくも教えながら出来たと思います。 なので、2手に分かれてやっていれば進みは良かったのかな。と思います。
あとは、詰まった時に速攻でサポータの方に聞けばよかったです。マジで1分調べて分からんかったら聞く感じでいいと思います。それくらい時間なかったので。
あとは、1日でどこまで進めるかの確認はしていたんですが、全日程を見ての進捗ぎめ、確認が出来ていなかったのかな〜と思います。なのでいろんな機能を削る羽目になった。
とまあ、反省点は割とあるんですが、チーム開発はめちゃめちゃ楽しかったですね。これからはハッカソンとか出てみようと思いました。
感想
とにかく3週間最高でした! オススメなので機会があればTreasureに参加してみてください。
ツヨツヨな人と三週間一緒に居られるので技術的な学びも多いですし、何より圧倒的刺激をもらえた気がします。
チームメンバーとルームメイトはとにかく技術的にも、人間的?にも凄い人たち揃いで、肩身の狭い思いをしながら3週間過ごしたんですが、「こうなりたいなー」と思う機会がめちゃめちゃ多かったです。 マジでこれから頑張ります。
VOYAGE GROUPについて
// あとから追記する
まとめ
Treasure参加しろ!
(長々と書いて疲れたので今日はこれくらいで、書き足りたい所あるので、あとから追記します。たぶん)
他の参加者のも読んで!
ぼくが観測したやつをやっときます。
ルームメイトへ
Wantedlyのインターンでも一緒の部屋になる人がいるので一言。
寝るときはエアコンの温度上げるか、快眠にしような! ボクが風邪引くぞ!
(Wantedlyでもよろしくお願いします!)
CSSアニメーションに入門するのでメモ
りゅうおうのおしごとの空銀子先生の誕生日を調べたら9月9日だったので、何かやりたいと思い、yui540さんが由比ヶ浜結衣の誕生日サイトを作ってたのを思い出した。
これを見て我も作りたい!と思ったので、CSSアニメーションにとりあえず入門するぞい。
とりあえずMDN読んどけばいいだろうということで、ここをヨミヨミしてメモをこれから書きます。
CSS アニメーションの使用 - CSS: カスケーディングスタイルシート | MDN
余談ですが、CSS アニメーションでググるとドキュメントより先にQiitaが出てSEO強っ!って思いました。
CSSアニメーション概要
CSSアニメーションは2種類の要素で構成されている
- アニメーションについて記述するスタイル
- アニメーションの始めとと終わりのCSSスタイルを示すキーフレーム
animationプロパティ
CSSアニメーションの流れを作成するには要素にanimation
プロパティを設定する。
animation
プロパティは外見を指定するのもではなく、アニメーションをどのようにすすめるかを設定するためのものです。
animation
プロパティは値を複数指定して、アニメーションのシーケンスの詳細を指定しますが、それぞれにどのような意味があるのかを覚えるのが大変そうです。
なので、animation-name
やanimation-duration
などのサブプロパティを見て、どのような流れを指定できるのかを知るのが良いと思います。
一つ一つの説明をこのブログで書くのは無駄なので公式ドキュメントをぱぱっと見てください。1分位で読めるかと
キーフレーム
アニメーションの外見を定義しなければ、animationプロパティで指定してきたことを無駄です!なので学んでいきましょう
これは例を見ればどのようなものか分かると思います。 pタグが画面を横切るようなアニメーションです。
p { animation-duration: 3s; animation-name: slidein; } @keyframes slidein { from { margin-left: 100%; width: 300%; } to { margin-left: 0%; width: 100%; } }
これの応用で頑張ればいろいろなアニメーションが表現できるかと思います!
ポートフォリオサイトにアニメーションを組み込んで遊んでみたので良かったら見てください。 https://k-nasa.me/
非同期プログラミング on Rust のために知っときたいこと [メモ]
非同期プログラミング on Rust
このメモは「非同期? なんそれ?」っていう初心者がメモ用で書いたものです。 用語の誤用、誤った記述があると思います。ので、コメントで間違ってるところを教えていただけると嬉しいです。
Rustのstableにasync/awaitがもうすぐ入るようなので、非同期プログラミング,Futureについて勉強していこうと思います。 それのメモがこれです。
非同期プログラミング
非同期プログラミングとは?みたいなところから書いてきます。
非同期プログラミングは複数タスクを経公的に処理(実行)するための技法。
Webサーバーの例でいうと、1つのクライアントとの通信が終わるのを待たずに、他のクライアントの処理を開始する。これが非同期
Future
FutureトレイとはRustの非同期プログラミングの主役的存在です。 jsのPromiseみたいなイメージかな
以下のような定義になってます。
pub trait Future { type Output; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> } pub enum Poll<T> { Ready(T), Pending } pub struct Context<'a> { /* fields omitted */ } impl<'a> Context<'a> pub fn from_waker(waker: &'a Waker) -> Context<'a>; pub fn waker(&self) -> &'a Waker; } pub struct Waker { /* fields omitted */ } impl Waker { pub fn wake(self); pub fn wake_by_ref(&self); pub fn will_wake(&self, other: &Waker) -> bool; pub unsafe fn from_raw(waker: RawWaker) -> Waker; }
このような実行モデルは、ポーリングモデルと呼ばれています。
具体的には、pollメソッドはself
とコンテキストcx
を受け取り、Poll<Self::Output>
型を返します。
このPollっていうのはReady
とPending
の2つの状態のどちらかを表すことができます。
Ready
バリアントは計算完了を表し、計算結果の値を持ちます。
Pending
は呼んでのごとく、完了していないことを表します。
少し戻りますが、pollの引数が&mut self
ではなくself: Pin<&mut self>
になっています。 これは自己参照構造体を実現するためです。 詳しくはボクも分かってないのでここでは説明できません。雑に記事を貼っておきます。
https://qiita.com/ubnt_intrepid/items/df70da960b21b222d0ad
Poll実行タイミング
Futureの所有者はいつpollを呼び出すのか?という問題があるかと思います。
pollを呼び出してPending
が返ってきたとします。そのようなFutureに対しまたすぐにpollを呼び出すのではCPUを無駄に使用する可能性が高いですね。
なぜなら、Pending
が返ってきているので、計算が終了するまでには、もう少し時間がかかる可能性が高いので。
ここで登場するのが先程しれっと書いたWaker
なのですが詳細な話は省こうと思います。
とりあえずWaker
のwakeメソッドを呼び出すことでランタイムにタスクの再開通知ができます。
まとめ
一旦終わりです。 肝心のAsync/awaitには入ってないですが、基本的なところはこんな感じなので。
あとから、ジェネレーターやAsync/awaitの話を追記しようと思いますが、とりあえずリリースしようぜの精神でpostします。