Akatsuki Hackers Lab | 株式会社アカツキ(Akatsuki Inc.)

Akatsuki Hackers Labは株式会社アカツキが運営しています。

株式会社アカツキのインターンでお世話になった話

こんにちは。 東京工科大学のコンピュータサイエンス学部というところの3年生をやっております(就職するなら23卒ということになります)。 宮川です。

この度、アカツキのインターンを受けましたのでその記録を残します。 ブログ執筆経験が浅くつたない文章になると思いますがご容赦下さい。

エントリーと選考

アカツキのインターンを知ったのは就活サイトがきっかけです。

プログラミングのインターンを受けたい。でも1day仕事体験とかではなく数週間にわたってやりたい。あとお給料出ると嬉しい。そんな欲張りな条件で探した結果アカツキのインターンに出会いました。 ゲーム開発のインターンということで、私はゲーム開発に興味があるので良さげと思いました。

でもUnityなんだろうな...。私はゲーム制作に興味はあるもののUnityはあまり得意ではないのです。 ですが募集要項を見てみると..... Cocos2d-x & C++!? 私はそれに一目惚れして、アカツキのインターン選考が非常に厳しいらしいことも知らず、募集人数1人程度の「ヤバいインターン」にダメ元でエントリーしました。

────そして何故か選考に合格してしまったのです。(めちゃくちゃ嬉しかったです。)

やったこと

9月。ついに始まった人生初のインターン。 様々なオリエンテーションに出席しつつ、メンターさんと今後やることを相談しました。 そして最初に出された課題がオンボーディングも兼ねた練習問題です。

練習問題

私の配属されたプロジェクトには、開発者用メニューというものがあります。 各々の開発者の名前が登録されており、開発者が試したい機能を自分の開発者用メニューで試すことができるというものです。 ...言葉で説明するのは難しいですが、開発者の名前が書かれた「扉」がズラっと並んでいて、それぞれの「部屋」で好き勝手に機能を試せるといった感じです。

そこに自分の「扉」と「部屋」を作り、その中に「メインメニューに戻るボタン」を作るというのが今回の練習問題です。

コードを見てみると「アカツキ太郎」さんの部屋がいかにもテンプレートっぽく置いてあったので、安直にコピペして必要な箇所を変更して....。 最初は当たり前のようにやってしまいましたが、これでは駄目ですね。

今回のように簡単な問題ならまだしも、こうやってコピペする癖がついてしまうのは良くない。既存のコードをコピーする時は1行1行理解しながら写す必要があるのです。

じゃあそうしないとどうなるか?コピペして表面上問題なく動作したからといって、別の箇所に影響範囲が出ないとも限らないのです。そして、ただコピペしたコードについて「この関数はなんですか?」と聞かれても答えられないといよいよまずいのです。「おまじないです」なんて言うわけには行きません。

だからコードをちゃんと理解しながら書くことが大事なんですね。至極当然のことですが、私はこんな基礎のことまでできていなかったということです...。 巨大なプロジェクトにcommitするというのはこういうことです。

さて、何だかんだコードが完成し動作確認も済んだのでPRを発行する時が来ました。 git statusしてみると色々なファイルが変更されている。これはIDEのプロジェクトファイルっぽいし必要なんだろう。とりあえず git add -A。 結果は...当然駄目です。addする必要のあるファイルもありますが、ほとんどはaddする必要の無いファイルでした。 そう、大規模なプロジェクトにおいてはどのファイルをサーバーにアップロードするかも見極める必要があるのです。

このように、個人開発では適当にやっていたことでも、細かな配慮をもってやらなければならないので中々大変な作業でした。 ただ、やっとのことでPRが承認されたときの達成感は大きかったです。

クライアント改善課題

ここからがインターンの本番です。 私のインターン課題は「開発メンバーが喜ぶデバッグ機能を開発する」です。 これを開発者用機能の改修というタスクをもって達成します。 クライアント改善というと顧客満足度向上のような響きですが、「サーバークライアントモデル」における「クライアント」というニュアンスです。

メンターさんが事前に改善依頼を集めてくださったので、そこから1つ選び、依頼者にヒアリングを行い、そして実装するという流れです。 私が取り組んだのは、ゲームを効率的にすすめるデバッグ機能の改修でした。 デバッグ機能をONにしてもアプリ再起動によってOFFに戻ってしまうのが不便なので、最初からONにしてほしいという依頼内容です。

まずは依頼者にヒアリングを行い、依頼者の目的(なぜその機能が欲しいのか)を聞き出しました。 これは非常に重要な工程で、依頼者と実装者の間に認識のズレがあると良くない結果を招いてしまいます。 例えば、完成したものを依頼者に見せても「なんかこれじゃない...」といった反応をされてしまったり、「そういう目的なら別のアプローチがあったのに」と後から気づいてしまったり...。 このようなトラブルで手戻りをしないようにするため、依頼者から目的を聞き出すことが重要なのです。

ヒアリングの結果、他の開発メンバー(検証・運用さん等)にも確認を取る必要が出てきました。 そこで、我々のプロジェクトメンバー全員が入っているSlackチャンネルに文章を送ることになりました。 この時はまだ文章作成に2営業日費やすとは思ってもみなかったのです...。

Slackに投稿する前にメンターさんにFBをもらうことになっていたのですが、最初に作成した文章は

  • 文字がびっしり
  • 用語の表記ゆれがある
  • 参考図が無い

という中々ひどい有様でした。 言ってしまえば見るだけで億劫になる活字の塊です。 これでも不足なく、分かりやすく説明しようと頑張ったんですけどね...。

数回のFBを受けてようやく文章と3つの参考図が完成しました。 ここで如何に最初の文章が酷かったのかを認識することになりました。 そして、自分の書く文章の悪い癖も知ることができました。

そう、私は分かりやすくて不足なく書こうとして長ったらしい文章を作ってしまいましたが、真に分かりやすい文章とは「簡潔で」「視覚的に整理されていて」「必要に応じて図で補足する」ようなものだったのです。

そしてついに300人以上のチャンネルにメッセージを投下し、なんとすぐにFBが届きました。 インターン生の私を相手にして下さったことが嬉しかったし、何よりメンターさんでも気づけなかった意見を得ることができて驚きでした。 依頼者のみが得をするのではなく、他のメンバーが損をしてしまうことも考える。 つまり、一人の主観で決めるのではなく他者の意見も取り入れる必要があるということですね。 考えてみればこれも至極当然のことですが、実際一人の意見だけ過信するとかやってしまいそうなので肝に命じておきたいです。

ヒアリング、全体への確認も終わりいよいよ方向性が決まりました。 あとは実装するだけです。

実装は割と簡単でした。 C++の専門知識がある程度要求されたので楽しかったですが、基本的には既存の仕組みを利用するものでした。 あっさりと実装も終わり、PR発行。 一回目のようなトラブルも起きず、素直に承認されました。 大分ここでのGitHubの扱いについても慣れてきていたと思います。

新規機能実装

インターンのメイン課題が終わった後は、一週間ほど別のインターンに行っていました。 祝日というのもあり、アカツキに来るのは久々でした。

メインの課題は達成しましたが、成果発表会の準備をするにはまだ時間が余っていたので、新機能の実装を体験させて頂くことになりました。

当たり前ですが、私はインターン生でプロジェクトに関する知識が浅い為、新しいことをするには多くのキャッチアップが必要になります。 しかし、インターンが始まってからずっとそうでしたが、アカツキの構造化されたドキュメントは非常に便利です。 検索機能を使えば欲しい情報は割とすぐ見つかるし、ビルド待ちの時間などには階層構造をなす項目から興味深い記事を見つけることができます。 このきっちりしたドキュメントは素晴らしいですし、今後の企業研究の際にも基準の一つとして考えたいと思いました。

新規機能の実装はモブプログラミングによって行いました。 これは、調べごとをしながら指示を出す「ナビゲーター」とナビゲーターに従ってコードを打つ「ドライバー」に分かれて共同作業をする手法です。 これは1~複数人のナビゲーターと1人のドライバーで構成されます。

一般的には一定時間でドライバーとナビゲーターを交代するようですが、インターンということでずっと私がドライバーを務めました。 Zoomの画面共有を使用しているとはいえ、ずっと誰かに見られながら作業していることになるので、始終落ち着けませんでした。 しかしモブプログラミングにはその弱点を超える数々の利点がありました。

まず、分からなかったらすぐナビゲーターに聞ける事。これによって、コーディング中にブラウザを開いて検索して...という手間がなくなります。 今回はかなり前からこのプロジェクトにいるベテランの方がナビゲーターをして下さったので、調べるどころか大体即答です。効率がえぐいです。 そしてプログラミング以外のスキルも継承できること。 例えばIDEの知らなかった機能や会社の知識など。 コーディングの片手間に知識が共有できるので便利です。

このように、モブプログラミングにはたくさんの利点がある為、いい加減誰かに見られながらの作業も克服したいなと思いました...。

モブプログラミングはPRの作成においても威力を発揮します。 (この場合はモブワークに該当しそうですね。) PRを作成している最中にコードレビューが大体できてしまうようなものですから、非常に合理的です。 また、大きな事故になりかねないような誤操作も起きにくいので安全とも言えます。

安全に作業できる環境、素晴らしいですよね。

何を得たか

今回のインターンを通して学んだこと、つまりどのようなスキルを入手したかについてまとめさせて頂きます。

コミュニケーションスキル

ヒアリングやSlackでの文章作成、そしてモブプログラミング...。 日常的な会話などに加えて上記を含め様々なコミュニケーションが要求される職業。それがエンジニアでした。 コーディングと同じくらい、もしかしたらコーディング以上に開発現場のコミュニケーションについて知ることができました。

作業を管理するスキル

インターン中は常に別画面でGoogleカレンダーを開きながら作業をしていました。 「〇〇時までにこの作業を終わらせる必要がある」という都合や、そもそも定時というものが存在するので、しっかりと作業を管理する必要がありました。 というかそうしないとやっていけません。

あとこれは私が勝手に始めたことですが行動ログというものを付けてみました。 何時にアレを初めて、何時にコレが終わったという記録を延々付けるだけのものです。 これを作ると何が良いかというと、後々振り返りをする時に役に立ちます。 また副次的にですが、「14:03 〇〇を開始」と打った後は脱線せず作業を遂行できることが多いです。

行動ログを付けていることをメンターさんに言ってみたところ、なんとメンターさんも同じようなものを作っていました。 行動ログと違うところは、一日の始まりにざっくりとした予定を考え、それを遵守できたかどうかも含めて後々振り返りをしているところでした。 私も真似しようとしましたが、如何せんインターンでやることは初めてのことだらけなので作業時間の目処がつかない...。 なので一日の始めにTODOリストを作るに留まりましたが、今後は作業時間の目処を立てることを意識していきたいです。

目的を意識するということ

これは今回のインターンにおいて最も重要な教訓です。 目的を意識することは、あらゆる仕事・作業において役に立ちます。

例えば今回私も行ったヒアリングについて。 ヒアリングで「依頼者は何故これを実現して欲しいのか?」つまり「依頼者の目的は何か?」を追求することで、課題解決に向けて共通の認識を持つことができます。

逆に依頼者の目的をよく聞かずに実装を進めてしまうとどうなってしまうか。 実装した結果「欲しかったのはこれじゃない」と言われてしまい、実装をやり直すという手戻りのコストが掛かってしまう可能性があります。

目的を意識することは最適解に近づく為の非常に有効な手段といえます。

また、目的を考えることは「あれ?今何やってるんだっけ?」と道に迷ってしまった場合にも有効です。 何の目的でこの仕事をしているのかを考えることで、目的が見つかれば作業に集中できますし、あるいは別の方法を検討することも可能です。

このように、「目的を意識すること」は私にとって万能な教訓となりました。 今後も日常的に意識していきたいと思います。

感想

今回のインターンが人生上初となりましたが、アカツキでのインターンが初で本当に良かったです。 エンジニアのインターンということで、プログラミングのスキルも当然身につきましたが、それ以上に身についたのは上に挙げたような「エンジニアに必要なスキル」の数々です。 そしてこれらのスキルの多くはポータブルスキルであり、今後の他のインターンや就活、実際に就職した後でさえ約に立ちます。

昨日メンターさんに「この枠にどのくらいエントリーがあったんですか?」と勇気を出して聞いてみたところ、腰を抜かしました(メンターさんも具体的な数は把握していませんでしたが、恐らく私にとっては信じられない数です)。 私は悪魔のような技術力をもった人材ではありません。 では何故採用してくれたのか聞いたところ、採用側とエントリーした側の「こうなりたい」というビジョンが一致したからと教えて下さいました。

このような理由で採用して下さったことがとても嬉しかったです。 同時に、今後就活をする上でも「私が将来どうなりたいか」というビジョンを大切にしたいと思いました。 これも「目的」ですね。

まとめると、非常に実践的なスキルも身につく、かつ将来を見据えたものの考え方まで身についてしまう、やはり「ヤバいインターン」でした。 毎(営業)日新しい発見ばかりでとっても楽しかったです。

メンターの伊藤さん、そしてインターン生の私を暖かく受け入れて下さったアカツキの皆様、本当に有り難うございました!!

初めてのRuby on Railsでユーザ数が大規模なゲームのAPIを高速化した話

自己紹介

はじめまして、大阪工業大学 情報科学部  情報知能学科  3年 伊地知翔也と申します。  
普段は、チームや個人でWEBアプリ開発をしたり画像処理についての研究をしています。  
このサマーインターンに参加する前までは、
私のサーバサイドで経験のある言語は、

  • Golang
  • Python(Flask)

しかありませんでした。
しかしながら、ゲームのサーバサイドに携わってみたいという想いから経験のないRuby on Railsを使用する本インターンを志望しました。

 

事前準備

事前の準備として、Ruby on Railsのチュートリアルを概ね完走しました。

といっても、他の言語でWEB開発の経験あったので、この機能を実現するにはこのメソッドを使えば良いなど、飛ばし飛ばし大まかに学び、準備していました。

 

インターンの内容

今回、高速化に取り組んだゲームは八月のシンデレラナインというゲームです。

f:id:megumimorishima:20211006171712p:plain

今回取り組んだのは主に2つで、

  • ゲームクライアント側でとある情報の表示、非表示を制御できるようにする

  • とあるAPIの高速化

を行いました。

 

今回は、高速化の方を取り上げたいと思います。

 

高速化前の状況把握

 

ハチナイのイベントやストーリ選択する際に叩かれる重要なエンドポイントが遅いので高速化しましょうというのが課題です。

 

前任者の調査結果と私の調査により以下2つの問題があることが判明しました。

  1. とあるデータの参照において、N+1問題が発生している。
  2. キャッシュサーバとの通信回数が多すぎる。

 

まず、N+1の解決だ!

 

高速化前では、N+1問題という、Ruby on Railsでやってしまいがちな問題が発生していました。しかしながら、どうやら、何かミスでN+1が起きないように処理できる機構はありましたが、それを利用せずに逐次処理で実装されていました。よって、その機構を利用するよう修正することでこの問題は解消しました。

結果、

f:id:aktsk-ijichi:20210924181955p:plain

N+1の解消結果

こんな感じで、1.1倍の高速化を達成しました。

表の結果は私の開発環境の値であり、本番サーバの速度ではありません。ただし、計算量の評価を行うためには有用です。(倍率で見れば良い)

ところで N+1問題って何?

N+1問題は単純で、とあるユーザIDがあったとして、ユーザに関するデータを

A,B,C,Dというデータの集団(テーブル)に格納するデータベースがあったとします。

各データの集団は

Aのデータの集団では、{ユーザID,何かの情報}が複数格納されています。

B,C,Dも同じように、{ユーザID,何かの情報}が複数格納されていて、

A,B,C,Dには各々別々のデータが入っています。

 

データベースは一般にアプリケーションサーバからは独立した別のコンピュータで動作しているので、アプリケーションサーバからデータベースに対して検索命令を行う必要があります。

 

そうしたときに、ユーザIDだけで、ABCDの各データの集団をまとめ、情報を処理したいときが多々あります。

普通、こうした時は、AのユーザID、BのユーザID、CのユーザID、DのユーザIDで情報を結合して、一挙にして結果を得ますが、高速化前ではこういう仕様にはなっておらず、AからとあるユーザIDの検索を行って、結果を処理し、BからとあるユーザIDの検索、結果の処理....という逐次処理が行われていました。

この逐次処理方式では、一回ごとにデータベースサーバに検索するよう命令するため、

  • データベースとの通信処理
  • データベースの内部処理(SQL命令解釈処理など)

がA,B,C,Dなどのデータ集団の数回=N回行われることとなり、低速の原因となります。

また、逆の場合(ユーザIDがたくさんあって、Aというデータ集団に対してユーザIDごとに検索命令することなど)もあります。

この問題を一般にN+1問題と呼んでいます。

インターン選考ではここが主に聞かれるので注意してください、、、これから受ける方。

これらをA,B ,C,Dに対して、一括で検索するように処理を変えることで対策できます。

また、A,B,C,Dの全データを取得し、データベースサーバではなくて、アプリケーションサーバ側で検索することでも解決できます。(メモリを圧迫する問題がありますが)

 

キャッシュサーバへの問い合わせが多い問題を解消

これはストーリごとの情報をキャッシュサーバに格納していたためです。

ストーリは何十個もあるので、ストーリごとにキャッシュサーバへの問い合わせを行い、逐次処理を行うため、上記のN+1問題に似ている状況が発生します。

解決は簡単で、

  1. 一気に複数のストーリ情報を受け取るよう問い合わせ行う
  2. ストーリを複数に束ねて、情報の格納、問い合わせを行う

方法は2つありますが、結果的に実験してよかった後者を選択し、実装を行いました。

 

f:id:aktsk-ijichi:20210924193300p:plain

後者の実装方法のイメージ

 

 

最終的な速度の計測結果

上のN+1問題の解決とキャッシュサーバ問い合わせ問題の解決を行った実験結果を下記に示します。

 

f:id:aktsk-ijichi:20210924183416p:plain

最終的な高速化の成果

最終的に1.3倍の高速化を達成しました。

この実装を適用することにより、

  • 各アプリケーションサーバ、データベースサーバ、キャッシュサーバの負荷が減少しレスポンスタイムが高速になる
  • さまざまなリソース消費量(メモリ、ネットワーク、CPUなど)が減少する。

ことが期待されます。

 

インターンの感想など

私は今まで趣味でサーバを勉強していたので、業務に携わるのは初めてで、少々不安もあったのですが、メンターの方にどうしてもわからないところがあればビデオ通話で質問するなど親切にしていただき、なんとかこれらの成果を出すことができました。

圧倒的成長...!!!

このゲームの開発プロジェクトのチームの方々にも暖かく見守っていただき、コードのチェック(レビュー)していただくことで、圧倒的成長することができました。

そして、レビューに対して、自分の意見を述べて、レビューした方とコードに関する議論を交わせたことは生涯の糧になると考えています。

 

人生観を変えてくれたサマーインターン

業務でプログラムを書きチームに貢献するという貴重な体験は非常に面白くキャリア観、人生観を揺るがすほどでした。

 

最後に

メンターさん並びにハチナイ開発プロジェクトの皆様、人事の方々、そのほか私に関わってくださった皆様、誠に感謝申し上げます。

まだインターンに参加していない方は、ぜひインターンに参加してみてください!

きっと人生を変える経験ができると思います。

 

 

 

ユーザー数が大規模なゲームで負荷試験と改善をした話

はじめまして、7月5日から7月30日までアカツキのサーバーサイドでインターンをさせていただいた光枝と申します。 本記事では私が今回のインターンで取り組んだことについて共有させていただきます。

自己紹介

神戸大学理学部の学部3年生です。 普段は主にWebアプリケーションの開発をやっていて、サーバーは Ruby on Rails、フロントは React/Next (TypeScript) を書くことが多いです。 最近は Go や Kubernetes の勉強などもしています。

インターンの題材

今回のインターンで取り組んだ題材は「負荷試験」です。

私が携わらせていただいたサービスはユーザー数が非常に多いゲームでイベントや新しいガチャが実装された際にはサーバーにかなりの負荷がかかります。

そんな中で当ゲームはメジャーバージョンアップを控えています。 その際に

  • 今までの機能が重くなっていないか
  • 新機能が重くないか

を検証する必要があり、そのタスクにアサインいただきました。

検証方法

検証にはAWS上に構築した本番環境の数十分の1スケール(以後stress環境と呼びます)を使用しました。 手順は以下の通りです。

1. シナリオの準備

負荷試験には LOCUST という負荷試験ツールを使用しました。 LOCUST は locustfile.py というファイルにシナリオを書くことで、その通りに負荷をかけてくれるツールです。 (他にもアクセスするユーザー数や Hatch rate を設定し、結果を確認するためのGUIを提供しています。)

シナリオは例えば

ログイン → お知らせ確認 → ログインボーナス取得 → ...

のようにユーザーの動きを再現するように、各APIを順番に叩く処理を記述します。

バージョンアップに際して今回着目した新APIを叩く処理をシナリオに追記しました。 (ここではそのエンドポイントを POST /hoge として置きます。)

2. ローカル環境での実行

まず新バージョンがすでに動いている環境のデータベースをコピーしてきて、LOCUST をローカルで実行します。 ここで失敗し続けているAPIがないか確認します。

(例えば特定のAPIが失敗していた場合、そこで処理が止まってしまい想定していた負荷をかけることができなくなったり、その後に続く処理が実行されなくなってしまうのでここで確認する必要があります。)

3. AWS の stress環境に反映して実行

手元で確認が取れたら実際にstress環境で実行します。 stress環境にかかる負荷は

インフラの負荷 → Amazon CloudWatch

アプリケーションの負荷 → New Relic, LOCUST

で確認します。

実行結果

上記の 1 ~ 3 で負荷試験を実施してデータをみたところ、新APIに問題が見つかりました。

平均レスポンスタイム (ms)
POST /hoge 2090
1リクエストあたりのSQL実行回数
ActiveRecord Model2 find 396
ActiveRecord Model3 find 396
ActiveRecord Model4 find 18.3

これは N + 1 問題が起きていると考えられます。 なのでこれを修正して再度負荷試験を行い、どの程度改善されたかを計測します。

実は負荷試験を実施するまでにもいくつか苦労したところ (e.g. 「外部API のモック。これをやらないと外部サービスに対して負荷をかけてしまう」) がありましたが、今回は負荷試験の結果と改善に焦点を当ててお話させていただきます。

発生している問題

まず 「N + 1 問題とは何か」を簡単に説明します。

例えばユーザー(User)というモデルがあり、ユーザーがユーザーHoge (UserHoge)というモデルと関連があるとします。 また1ユーザーにつき、通常複数の UserHoge が関連しているとすると以下の図のような関係になります。

f:id:itsuki_mi:20210730180407p:plain

これらのデータが全て必要な場合にUserHoge が N 個あるとすると、このデータを取得するためにデータベースに N + 1 回 (1回は最初に User を取ってくるため) 問い合わせてしまうことを N + 1 問題と呼びます。

これらは本来、工夫することで 2 回の問い合わせですべてのデータを取得することができます。

今回の POST /hoge の問題も N + 1 問題だと予想されます。

調査

まず実際にローカル環境で API を実行して発生したクエリを再現し、該当コード部を読みました。 その結果以下のような構造になっていることが判明しました。

f:id:itsuki_mi:20210730181502p:plain

User が複数の Model1 と関連していて、Model1 は複数の Model2 と Model3 と関連があります。Model3 は 1 対 1 で Model4 と関連があります。 またこのサービスはデータベースを水平分散しており、「User, Model1, Model2, Model3」は同一のデータベース(Shard1)にありますが、Model4 だけは別のデータベース(Shard2)に保存されています。

これらのデータをすべて取得してくる必要があったため対策がない場合は

  • 1 ユーザーあたり N1 個の Model1 と関連
  • Model1 は N2 個の Model2・N3 個の Model3 と関連
  • Model3 は N4 個の Model4 と関連

としたとき合計で

N1 × (N2 + N3 + N4) 回

のSQLクエリを発行することになります。(ただし N1 個の Model1 はすでに取得済みとします。)

これが原因で POST /hoge は非常に重くなっていました。

修正に当たっての問題 その1

本サービスのサーバーサイドは Ruby on Rails で書かれています。 通常の N + 1 問題であれば includes メソッドを使うなどで解決できますが、今回の Model4 はシャードが異なるためワンライナーで取ってくることはできません。 Model3 から関連する Model4 を取る際にシャードを変更する必要があります。

この部分は以下のようにして解決しました。

Octoball.using(shard1) do
  ActiveRecord::Associations::Preloader.new.preload(model1s, [:model2s, :model3s])
end

model3s = model1s.flat_map(&:model3s)

Octoball.using(shard2) do
  ActiveRecord::Associations::Preloader.new.preload(model3s, :model4)
end

まず Octoball の部分でシャードを切り替えています。 以下のように記述することで #block の部分を指定の shard に接続した状態で実行することができます。

Octoball.using(shard) do
  # block
end

そこでまず Model2, Model3 まで shard1 に接続した状態で preload (ActiveRecord::Associations::Preloader.new.preload(model1s, [:model2s, :model3s])) しておきます。 そして preload した model3s だけ変数に参照渡しして、shard2 に切り替えてから残りの Model4 も ActiveRecord::Associations::Preloader.new.preload(model3s, :model4) で読みます。

このように記述することでシャードを跨いだ関連に対しても N + 1 を回避させることができます。

修正に当たっての問題 その2

全データの取得はこれで問題ありませんでしたが、それでも model4 だけ N + 1 問題が解決しませんでした。 理由としては、その後の model4 を使用する箇所で model3 のアソシエーションを使用していなかったためです。 実装は model4_id を用いて model4find_by するコードになっていました。

class Model3 < ApplicationRecord
  belongs_to :model4
.
.
.

  def model4
    unless defined? @model4
      @model4 = 
        if model4_id
          Model4.using(shard2).find_by(id: model4_id)
        end
    end
  end
end

なお model4 の関連付けがあるのに model4 メソッドが用意されているのは、毎回シャードを指定しなくてもよくするためです。 (また shard2 は動的に決まっています。shard2 は master/slave 構造を取っており、環境変数のAZを見て最適な slave を返すようになっています。)

上記の記述だとアソーシエーションを利用せずに model4 を取得するため preload したデータを使いません。 そこで以下のように条件を書き加えました。

class Model3 < ApplicationRecord
  belongs_to :model4
.
.
.

  def model4
    unless defined? @model4
      @model4 = 
        if model4_id
          if association(:model4).loaded?
            association(:model4).target
          else
            Model4.using(shard2).find_by(id: model4_id)
          end
        end
    end
  end
end

association(:model4).loaded? ですでに読み込まれているか確認して、true だった場合はその中身 association(:model4).target を返すようにしました。

改善後の結果

上記の改善を入れて、再度負荷試験を実施したところ以下のようになりました。

改善前の平均レスポンスタイム (ms) 改善後の平均レスポンスタイム (ms)
POST /hoge 2090 296
改善前の1リクエストあたりのSQL実行回数 改善後の1リクエストあたりのSQL実行回数
ActiveRecord Model2 find 396 1
ActiveRecord Model3 find 396 1
ActiveRecord Model4 find 18.3 1

完全に N + 1 が消えて、各 Model がそれぞれ1クエリで取れるようになったのがわかります。 また POST /hoge 自体もかなり高速化されました。

インターンでの学び & 感想

インターンに参加する前は負荷試験について言葉だけ知っている状態でした。

しかし実際に

負荷試験の準備(シナリオ作成、APIの失敗がないかのチェック及び修正、ローカル実行で確認)

stress環境で負荷試験実施

CloudWatch, New Relic, LOCUST でメトリクスなどを確認

問題があった場合は改善

再度負荷試験を実施して改善効果を検証

という一連の流れを体感することができました。

またシャードを跨いだ関連付けの取得やActiveRecord 内部の仕様についても理解を深めることができました。

実は事前面談で「インフラ系のタスクをやってみたい」とお話していてこのタスクを用意していただきました。

なので私がやりたいと言ったことをやらせていただき、また手厚いサポートのもとたくさんのことを学ばせていただきました。

約3週間本当にお世話になりました。ありがとうございました!!!

アカツキのサマーインターンに参加したお話

こんにちは。 7/5〜7/27までの3週間、アカツキのサマーインターンに参加させていただきました、あおやまと申します。 このインターンでは、「八月のシンデレラナイン」のサーバーサイドの機能改修・運用改善などの業務に携わりました。

ここでは、このインターンで取り組んだこと、学んだことについてまとめていこうと思います。

はじめに

自己紹介

通信制の大学に通う学部3年生です。 大学ではCS系のことを学んでいます。 普段はWebの開発をしており、インターン参加前はサーバーサイドというよりはフロントエンドよりのことをやっていました。

スペック

Ruby/Ruby on Railsは3週間ほどしか経験がなく、超基本的なことしか理解していない状態でした。(普段はPHPやTypeScriptを書いています)
自己紹介でも書きましたが、普段はWebの開発ばかりやっているので、ゲーム開発は初めてでした。

インターンで取り組んだこと

新規機能開発(断念)

雑談で上がっていた新規機能が面白そうだったので、ディレクターさんやプランナーさんにその機能を開発したいと相談しました。 その話し合いの中で、その機能を作る目的について聞かれました。 しかしながら、私はユーザーに対する目的しか浮かびませんでした。そこで、他にどのような目的が考えられるかを尋ねたところ以下のような回答をいただきました。

  • 実装に関するコスパが他のタスクを比較して良い(実装工数・検証工数が小さい)
  • キャンペーン・マーケティングに使える(実装タイミングでお祭り感を出せる)
  • 使える工数がある(そもそも手が空いてるのでなんかしたい)
  • チームの育成に役立つ(難易度が適切なので、チームの練度向上に使える)

「ほぇ〜〜」
実装に対するコスパ、マーケティング、チームの育成など今まで考えたことがないばかりだったのでとても学びになりました。

これを元にプランナーさんと話し合いをした結果、現状はこの機能は現実的ではないということで断念しました。

作戦変更

上記の新規機能開発は早い段階で断念する決断ができたため、その後は作戦を変更することにしました。

その作戦名は、

運用改善タスクを数こなしてみんなに感謝されよう作戦

です。 私が配属されたチームでは、手のつけられていない運用改善タスクが山ほどありました。そこで、それらを1つずつ片付けていきました。
運用改善タスクは、仕様が決まっているものもあれば、あやふやなものもありました。仕様があやふやな場合は依頼者に、「なぜこの機能が必要か」「この機能で検証したい部分はどこか」などの聞き込みが必要でした。そのような聞き込みをするのが初めてだったので、最初は何を聞いたら良いか分かりませんでしたが、複数タスクをこなすうちに、依頼者はどのような機能が欲しいかなどを感じたり、うまく聞き込みをしたりすることができるようになりました。(成長!)
結果、サーバーサイドの運用改善タスクはほぼやり切ることができ、数としては6つこなすことができました。

まとめ

良かった点

新規機能開発の一部を体験できたこと
断念してしまいましたが、話し合いを通じてたくさんの学びがありました。

運用改善でチームに貢献できたこと
運用改善チームの方から感謝されました^^

ゲームのサーバーサイドの中身を知ることができた
機能改修タスクや一部の運用改善タスクはゲームのロジックの理解が必要だったので、そこを理解しレビュアーとしっかり議論することができました。

エンジニアとして技術以外のことをたくさん学ぶことができた
ただ作るだけでなくなぜ作るかやどう作るかという考えや、エンジニア以外の方とのコミュニケーションなど個人では体験できないことを実際に体験し、そこからたくさんのことを学ぶことができた。

自走力が成長した
いい意味でメンターさんが私を放置してくれたので、自分からエンジニアやディレクター、プランナーさんたちにコミュニケーションを仕掛けたりと自走する力が成長しました。

今後の展望

オーナーシップを育てる
今回のインターンでは、自分がタスクのオーナー(それについて一番詳しい)になることが多々ありました。しかし、まだまだ自分がオーナーだという自覚が足りない場面がありました。これからたくさんの経験を積んで1つのプロジェクトを見れるくらいのオーナーシップを育てていきたいと思います。

リアルタイムのゲーム開発
参加前はゲームはするのは好きだけど、自分で開発するということは考えていませんでした。しかし、自分の好きなもの(ゲーム)を開発することはとても楽しかったので、もっとゲーム開発をしたいと思うようになりました。次はリアルタイムでやりとりが行われるようなゲームの開発もしてみたいです。

最後に

参加前はフルリモートで若干不安でしたが、参加してみると毎日メンターさんが1on1を開いてくれたり、朝会・夕会があったり、定期的に社内の方とランチ会があったりととても楽しくインターンを過ごすことができました。
とても充実した3週間を過ごすことができました!!

アカツキで「ハチナイ」のサーバーサイドインターンを体験しました。

はじめまして!ハチナイのサーバーサイドエンジニアとしてインターンを体験させていただきました、堀崎と申します。7/5〜7/27の15日間お世話になりました。

今回のインターンで開発したものとそこから得た学びを書いていこうと思います。 

自己紹介

名古屋大学に通う学部3年生です。

アプリ開発サークルに所属しており、普段はWebアプリを開発しています。サーバーサイドとしてRails、フロントエンドとしてVueを主に使っています。

今回はRailsでの開発がメインでしたが、普段使っていることもあり技術に対する不安はそこまでなかったです。

やったこと

管理画面の開発

最初は簡単なタスクとして、管理画面の機能追加を行いました。管理画面はActive AdminというGemを利用して作られており、初めて触りましたがすでにあるコードを参考にして難なく実装が出来ました。

ゲーム機能の改善

管理画面のタスクを終えてコードの全容が見えてきたところで、ゲームAPIの速度改善のタスクを引き受けました。改善点が明らかになっていなかったため、まずはログの調査から始めて改善策を練ることにしました。

ログの調査結果

以下の問題が判明しました。

  • N+1問題
  • 大量のdeleteクエリ

N+1問題に対する対策としてeager_loadすることにしました。そしてdeleteクエリに関しては一括で削除するメソッドを自前で用意することになりました。

改善結果

改善前はレスポンスを返すまでに約5.5秒かかっていた処理を約1秒まで短縮することができ、約80%の速度改善に成功しました。

運用改善

最後のタスクとしてプルリク作成時にファイル名をチェックするCIを追加しました。こちらはGithub Actionsに新たにjobを追加するだけだったので比較的速くこなすことができました。

インターンを終えて

3週間という短い期間でしたが、色々なことを学ばさせていただきました。

技術的な成長

今回のインターンでは、調査タスクを多く経験させていただきました。その中で公式のドキュメントを読み込んだり、GithubのIssueを探索したりとコードを深く追っていく力を養うことができました。

また、これまでに触れたことのないGemやCI/CD関連のツールにも触れることができ、保守性の高いシステムに関しても新たに学びを得ることができました。

チーム開発におけるコミュニケーションの難しさ

このインターンで一番苦しんだのがコミュニケーションだと言ってしまっても過言ではありません。これまで小規模の開発しか経験してこなかった僕にとって、自分の思っている実装方法を人に伝えることは非常に困難に感じました。言葉足らずで自分の意思が正確に伝わらず、メンターさんを困惑させてしまうことが何度かありました。自分で改善することが難しかったコミュニケーション能力に関して、ご指導してくださったメンターさんにはとても感謝しています。まさかエンジニアインターンでコミュニケーション能力が養われるとは思ってもいませんでした。

おわりに

15日間あっという間に過ぎてしまいましたが、非常に有意義な時間を過ごすことができました。メンターさんをはじめ、たくさんのサポートを本当にありがとうございました。

アカツキ社内の雰囲気について、皆さんが楽しそうに話されている姿からアカツキという会社がより一層魅力的に見えました!

短い期間でしたが、本当にお世話になりました!