for Startups Tech blog

for Starupsのテックブログです

スモールチームのインフラ担当・SREとして入社し取り組んだことと失敗したこと

こんにちは。フォースタートアップス株式会社でインフラ・SREを担当している吉田です。昨年入社しました。 今回は、インフラ・SRE担当の一人目として入社してからこれまでの約1年間で取り組んできたこと、失敗してきたことや課題をお話します。

入社時の弊社の状況

入社したのは約1年前。当時はオーナーや開発メンバーなど含め社員3〜5人のチームが2つあり、それぞれ別のサービスを開発していました。 社員の他にも、チーム内にはパートナーやインターンの方々もいて、約十名が曜日で入れ替わりながら参加いただいていました。

組織の状況は以下でした。

  • 各チーム各サービスとも、開発も進み機能が徐々に増えてきた
  • より開発や運用の安定性をより高めていきたい
  • サービスレベルの目標設定もしたい

そんな組織の、インフラやSREの担当の一人目としてジョインしました。 入社前は2人チームとお話をもらっていましたが、諸事情で専任の一人目となり、インフラやSREの業務をゼロからスタートすることとなりました。

当時はこの2つのチームを横断して、インフラ関連業務やサービス開発と信頼性の向上に取り組むこととなりました。

入社してから取り組んだこと

直近の活動方針を決める

同じ業務をするメンバーもおらず、右も左もわからない状態からのスタートだったので、

  • まずは、メンバーとサービスを理解しよう
  • そこから、今困っていることを探して改善/整備しよう

と、ざっくり活動方針をたてて、以下の直近の活動内容をつくりロードマップをひきはじめました。

  • サービスの現状理解する
    • アラームやインシデントの対応と棚卸し
    • サービス目標と追いかける指標の策定
  • 整備と改善

上の内容に取り組む中で、良かった点と失敗した点がありました。

アラームやインシデントの対応と棚卸し

着任してから、まずは、

  • サービスの開発や運用の状況や構成を知ろう
  • 安定性を高めるためにはどこからなにをはじめるか、ポイントを探ろう
  • 今サービスの安定をおびやかす事象にどのようなものがあるのか知ろう

という考えのもと、

  • サービスを知るには、まずは日々のアラームや障害の対応にガンガン参加することだ!
  • チームが今、何を大事にして何を追っているのか、まずはつかむぞ!

という思いで動き出しました。

...というのは半分建前で、実はその裏では「早く馴染みたい、早く役に立ちたい」という焦りや不安がとても大きかったのです。

というのも、各チームは人数が少ないこともあってか、

  • チームの方針に則りながらも各メンバーが自律的に積極的に動いている
  • 誰かがアサインしなくても開発Issueがどんどん上から解決されていく
  • また、サービスの問題もチームで話し合い分析し、Issue化して協力して取り組むことができている

という、自律しながらも協力して取り組む文化のある、統率のとれたチームでした。

このチームとともに活動をしていくためには、

  • 自分も早くIssueを取れるようにならないといけない
  • 早く各チームのメンバーに、一員として認められたい
  • この無力感を脱したい

という焦りを感じていました。

そのためには「四の五の言わず、まずは現状把握と対応だ!🔥」という(勝手な)思いがありました。

失敗:入社直後はなかなか役に立てず空回り

まずは各チーム各サービスのアラームやインシデント対応に参加をはじめます。が、しかし早速つまづきます。

  • 発生するアラーム、発生する事象が、日々とても多い
  • 発生している事象が、どこの何が起因なのかがつかめない
  • サービスを構成するサービスが複数存在し、データストアの相互参照もあり
  • 対応を優先するべきアラームなのか、対応が不要なアラームなのかの判断がつかず、勝手に右往左往してしまう

既存のメンバーはそのアラームの過去の発生頻度や様子から、発生箇所の特定やおおよその原因を掴んで進んでいきます。

私はサービスの理解が浅いことも有り、問題の概要がつかめずに対応の初動まで時間がかかってしまいます。その間にまた別のアラームや事象が発生します。

  • アラームがなる、とりあえず確認する
  • これどこのなんのサービス?影響範囲は?前後の事象と関係がある?
  • これはなに?これは見るの?これは見なくていいの?
  • と思っていたら、別のアラームが...

入社して数日は素早い対応がなかなかできず、悔しい日々が続きました。

気づきと学び:まずは状況観察とヒアリングが大切

日々発生する事象の対応をしながら、少しずつですがサービスの概要や業務の流れを掴み始めました。

同時に、xxxの情報があったらもっと詳しい調査ができるのではないか、原因不明の事象もxxxのログから追えるのではないか、と、一歩引いた目線でインシデントを振り返る時間もできました。

最前線で対応することはもちろんですが、この小さいチームで「SREとして、対応者や門番の役割をさらに超えてチームと伴走し、よりサービスの信頼性をあげるには今何が必要なのだろうか」を考え始めました。

開発メンバーと同様に対応をはじめるだけでなく、別の観点やアプローチをつくることでチームとしてより早く問題解決ができるのではと感じ始めます。

対応人数が少ないからこそ、発生する事象が多いからこそ「まずは状況を観察し判断すること」、インシデントの対応にも言わばトリアージのようなものが一定は必要であると感じました。

実際複数のアラームやインシデントが同時に発生することは少なくなく、その場ですぐに優先度をつけることを求められる場面がありました。

複数の対応の優先度付けや対応箇所の判断ができるようになるためにも、今この初期のタイミングで観察や事象の整理をしなければいけないと気づきました。

このことから、インシデントの検知アラームや障害の対応と並行して、事象や通知を棚卸しをしながら、優先度付けや原因特定がしやすくなるように、以下に取り組みはじめました。

  • アラームや通知は精査をして不要なものは流さないこと
  • アラームや通知情報を意味ある情報にすること
  • 本当の関係者に通知すること(サービスも事象も多いが人数は少ないため、担当に注力できるようノイズを極力減らす)
  • 属人化しないよう、誰が見ても同じように状況を認識して動き出せるようになること

例えば、特定の指標の増減や稼働率低下などのSLOを満たしていない事象を通知する場合においても、少し極端な例ですが、


稼働率が下がってきた!やばい!
< 応答速度があがってきた!やばい!

と通知をしても、メンバーは


< 具体的に何が起きてるの?
< サービスのどこがおかしいの?

となり、対応の初動に時間がかかってしまうことが多いです。

なので、そうではなく、例えばですが、


<xx時にxxのエラー発生でユーザにエラー画面が見えている
<xx日からxxのAPI応答時間が延びている

と通知をすれば、メンバーは、


< xxのAPIだから、影響範囲はxxだね
< xx日ということは,もしかしてxxのリリースがきっかけかも...

と、起きている事象の理解や当てをつけることがしやすくなります。

必要な情報を取捨選択して、プロダクトやアプリケーションの詳細な出来事に落とし込み、事象解決につながる情報にして届けることが大切なのだと、当たり前のことではありますが再認識しました。

とすれば、アラームや通知内容は、目指すサービスレベルをおびやかす(SLO違反)警告となるものにするべきです。

今思えば、後述するサービスレベルや追いかける指標(SLI/SLO)の精査に、より早く着手するべきだったと反省しています。

またSLO違反の際に各自がどのように動き出すべきなのか、関係者同士である程度でも目的や方針などの認識をそろえておくともっとスムーズに動けるはずです。

ロードマップや目下の開発を進めることとのバランスもスコープに入れると、メンバーは動きやすくなりますし、チームの心理的安全性の向上にもつながるのではと思います。

良かったこと:現場だから生まれるコミュニケーション

前述のような失敗はありましたが、一方で入社直後で不慣れなことが多い中でも、アラームやインシデントの対応がきっかけでうまく進み始めたことも多くありました。

入社3日目と4日目に続けてインシデントが起きました。具体的には、Elasticsearchの高負荷やノードのダウンによる遅延やエラー応答が発生したのですが、これがきっかけで入社後すぐにチームのメンバーと仕様の深い話をし始めるきっかけができました。

自分 「ちなみにxxxってどうなってるんですか?」
メンバ「これはxxxとxxxでつかっているんだけど、負荷かかってるんだよねー」
自分 「そしたらxxxの部分を見てみますね、ちなみに普段どうやって確認されていますか?」

また、その会話をきっかけに、

「xxxの実行時間と頻度って変更できるものですか?」
「xxxのパラメータで改善できそうです、サービス影響を相談させてください。」
「xxxの作業するので、夜間作業に立ち会っていただけませんか?」

というように、一緒に作業をするきっかけも作ることができました。

対応者として前線に立つことによって、参加できる会話や見える課題もあるのだなと感じた瞬間でした。

サービスの目標と追いかける指標の策定

入社のタイミングで「各サービスの目標(稼働目標など)を定義したい」という課題があり、こちらにも着手しました。

私が入社前からエラーの発生数や応答時間のメトリクスが作成されており、ダッシュボードでそれらを見るという文化もありました。

この文化にも助けられました。どんな指標を大事にしていたか、何に問題意識をもっていたか、がなんとなく掴めたからです。

一方でメンバーからは、各指標の値はわかるが、サービスがどんな状態なのかわからない、何か良い方法はないかと相談を受けました。

そこで、サービスと構成を調べながら、

  • サービスが正常に動いているというのはどんな状態なのか
  • サービスが正常ではない状態は、どのように観察/検知できるか

を関係者と話し合い、取り急ぎ第一弾として、その条件からシステムの稼働率(≒ SLI)を定義しました。

定義後はチームで稼働率を追っていけるように、稼働率と関連項目を確認するためのダッシュボードを作成し、情報共有とモニタリングをはじめました。

ダッシュボードの表やメトリクスは、指標の数値や推移に加えて、現在目標ライン(≒ SLO)を上回っているのか下回っているのかがぱっと見てわかるように、状態に応じて表示色を変えたりメトリクス内にしきい値を描画したりするなど、まずはわかりやすさを重要視しました。

サービスが正常に動作し価値を届けられているのはどのような状態か、また現在それが目指すレベルにあるのか、チームで共通認識を持ちはじめられたことが良かったです。一方で、見える化したことによる副作用もありました。

良かったこと:サービスの現状理解が進み、稼働に対する意識が高くなった

まずは見える化したことで、以下の気づきや変化がありました。

  • 想像以上に未応答数や不安定な時間があることがわかった
  • エラーや未応答に意識が向くようになった

既存のダッシュボードでも、その瞬間の応答状況は確認できていましたが、メトリクスで推移を見ることで過去分も確認できるようになりました。

その結果、想像以上に応答率が落ちていたり遅延していたりするタイミングがあることをチームで共有できました。

今では、リリース時の確認や、朝会などでの定期的な確認をするようになり、稼働(サービス提供)への意識もより強くなりました。

失敗したこと:SLO違反との向き合い方の意識合わせ不足

稼働への意識が強くなったことは良かったのですが、現在のサービスとチームでは初めてのSLI・SLOの制定であったこともあり、どうやって運用していくかまでは気がまわっていませんでした。目標値を下回った(SLO違反が発生した)場合のチームの動き方まで、チームと話ができていなかったことが最初の反省点です。

現在のチームが、サービスに対してとても思いが強く責任感のあるチームだったこともあり、そんなチームにとっては「SLOに違反している ≒ 価値が提供できていないユーザがいる」があることはインパクトのあることでした。

そのため、設定当初はメトリクスの上下や一件のエラー発生の通知すべてに反応していました。

一方でサービスの性質上、ユーザーも利用環境も非常に種類が多く攻撃や不正なアクセスも少なくありません。それもあって、見える化をした直後は、認識していなかったエラーも観察されると同時に検知機構も過敏に反応し不要な通知をするなど、粗い部分もありました。

当時はエラーとして検知しても、本当に内部で発生したエラーである場合もあれば、存在しないもしくは許可されていない箇所への不正アクセスである場合もありました。そうすると、対応に疲弊してしまったりアラームや通知がオオカミ少年のように認識されてしまったりすることもありました。

どのエラーが本当に対応しなければいけないもので、どのエラーが誤検知や対応不要なものであるかを、導入の初期段階ではまだ精査しきれてはいませんでした。

エラー検知やSLOの基準となる指標の上下に必要以上に一喜一憂することなく、状況を把握して冷静に対応するためにも、SLOや指標の通知やメトリクスをどのように業務に組み込んでいくのかの認識をあわせることがまずは必要でした。

特にSLOを設定したばかりでチームに馴染むまでは、SLOは何を目的として設定しているのか、満たせている場合はどんな状態で、満たしていない場合はどんな状態で何をしなければいけないのか、を言語化して共通認識を持てるように共有することが必要でした。チームに根付かせるプロセスをもっとしっかり踏まなければいけなかったと反省しています。

失敗したこと:設定後の運用にまで手が回っていないこと

また、指標や目標の見直しまで手が回っていないことも反省点のひとつです。

新しい機能の提供や新しい形式でのコンテンツ配信が増えれば、それぞれに応じて指標や目標ラインも変わるはずです。例えば、APIとHTMLや動画と音声では、それぞれユーザに応答が届くまでの目標時間も異なるはずです。また新しい指標が必要になることもあるはずです。

また、稼働率が低下した要因を追えるようなモニタリングも当時は考慮できていませんでした。例えば内部エラーの検知や発生時のリクエストの詳細情報などです。

SLIやSLOの運用や見える化が、サービスの変化や成長に追いついていませんでした。

特に初回に設定した目標は、しばらくは定期的に確認と検討をする機会を作るべきでした。アップデートを踏まえたサービスの提供価値とそれを満たす状態を見直す機会を設けていきます。

ボトルネックの解消

サービスの状況を見える化した後は、運用やシステムのボトルネックを見つけて解消することに着手しました。

アラームやインシデントの棚卸しと稼働状況の見える化で、性能低下のタイミングや業務コストが高い箇所がつかめたので、その原因やボトルネック箇所の特定の解消、そのためのデータやログの収集や通知に取り組みました。

具体的には以下に取り組みました。

たとえば、特定の時間帯の応答時間の増加をダッシュボードで確認したため調査をはじめたところ、ログやトレース情報からデータ登録時にエラーと数回のリトライの発生を確認しました。参照もそれに引っ張られる形で影響をうけ、アプリケーションの応答速度に影響がでていました。

詳しい内容と原因特定のため、アラームやトレース情報などのモニタリング項目を追加し事象の調査しました。

調査の結果、弊社ではElasticsearchを利用しているのですが、そこでリクエスト過多のエラーが発生していたため、データ更新量やスレッドプールの状態を観察して処理を特定し、データ登録方法やクラスターの設定を見直し、応答速度の増加を解消することができました。

結果的にアラームの見直しやモニタリングが、ボトルネックの発見と解消につながったのは良かったです。

脆弱性の対策

以下のような脆弱性の対策にも着手しました。

  1. 脆弱性スキャンのツールやサービスの利用
  2. 外部の診断会社による診断の実施
  3. 定期的に脆弱性情報を収集/確認
  4. クラウドやネットワーク設定の確認

1と2については、外部の診断会社や各種サービスを利用して、サーバ/コンテナやアプリケーションに脆弱性がないかスキャンをはじめています。

3については、開発言語やライブラリに関連する脆弱性を、手動でピックアップし調査しています。が、後述しますが、まだうまく運用を回せていません。

4については、クラウドやネットワークに誤った設定(意図しないポート公開やアクセス設定など)がされていないかをチェックしています。

都度確認するのは難しいため、事前にポリシーをつくり、そのポリシーに則っているかをスキャンするようにしています。 このポリシーの作成と運用についてはAWS Config とAWS CDKを用いた手法で取り組んでいます。以下の書籍にまとめていますので、よろしければご覧ください。 https://techbookfest.org/product/5762421668970496?productVariantID=6028076402081792

難しかったこと:活動の必要性や必要なコストの妥当感について理解を得ること

特に2については、診断にかかるコストは安いものではありません。金銭はもちろんですが、環境やデータ準備や診断会社からのヒアリングへの対応もあるため、SREだけでなく開発チームにも負荷はかかります。

1や3のスキャンや情報収集も運用コストがかかります。ユーザに提供価値として直接目に見えて届くものではなく、効果の実感につながるまでに時間も機会も必要です。

意味のある脆弱性対策として機能させるには、継続的な活動と活動を行うための一定のリソースが必要になります。

また、せっかく調査して得た脆弱性情報も、サービスに与える影響に落とし込めないと、情報が流れても誰にも有益な情報としては伝わりません。

「私たち(のサービス)にどう関係あるの?」になるのは当然です。


< xxxでディレクトリトラバーサルの危険あり!
< xxxでオーバーフローの脆弱性が見つかった!


< うちのサービスに関係あるの?
< どのサービスがどう攻撃されるの?

人も工数も少ない小さなチームの中で、継続的な活動にコストを使わせてもらうためには、チームに活動の意義やコストの妥当性が伝わっていることが必要なのだと感じました。

良かったこと:脆弱性に関する書籍の輪読会をおこなったこと

弊社では毎週書籍の輪読会をおこなっているのですが、上の課題に取り組む時期に、輪読会で書籍「体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践」を読む機会をつくることができました。

  • どんな脆弱性や攻撃手段があるのか
  • アプリケーションにどのような影響があるのか
  • 対してどのような対策が必要なのか

輪読会でこれらについて学び議論し意見交換をすることができました。

脆弱性の概要や種類を体系的に学ぶことはもちろんですが、自分たちが開発しているサービスではどんな危険性がありどんな対策が必要なのかを、議論しながら深堀りができました。

この輪読会のおかげで、必要性の認識や対策意識について共通認識を作り始めることができました。とてもありがたかったです。

ちなみに、弊社ではビブリオバトルで輪読会の書籍を決めます。ちなみにこの安全なWebアプリケーションの作り方を推薦したのは私です。https://tech.forstartups.com/entry/2022/03/11/110557

課題:スキャン結果の確認と対応のコスト確保

その後、対策の活動をはじめたのですが、スキャン結果をいかしきれず重要度が高くないものは対応が先送りになるケースも少なくありません。

特に実際に4つの脆弱性対策の施策をはじめた直後は、重要度が低いものも含めて大量に検出してしまい、どこから着手すべきかわからなくなるほどでした。

また、3については、開発言語やライブラリに関連する脆弱性を手動でピックアップし調査していました。ですが、どうしても時間がかかってしまうため、今は大きい脆弱性が見つかった場合に個別で調査するにとどまっています。

1と4で、CI/CDへの組み込みや定期スキャンを実施し、2で外部の専門の会社に診断を依頼してカバーしてもらいつつ、緊急性の高いものや詳細な調査が必要なものを3で調査する、という運用にとどまっています。

スキャンをする方法はそろってきたので、スキャン結果取得から対応までに時間が空いてしまわないように、情報の整理と対応を効率的にできるように改善していきます。

利用SaaSの管理

インフラのリソースとあわせて、SaaSクラウドサービスのアカウントやコストの管理もはじめました。

  • メンバー一覧作成(メール・Githubアカウント)
  • アカウントの発行と削除
  • アカウント発行/削除方法のドキュメント化
  • 毎月の請求業務
  • プランやアカウント変更の見積もりと稟議申請
  • 新しいSaaSを利用するときの確認や申請、法務(リーガルチェック)連携など

開発で多くのSaaSを利用している一方で、各チームにインターンやパートナーの皆さんがいる関係で、不定期にメンバーの増減があります。

アカウント発行が必要なタイミングで、シートが増えてプランも変わり決済が必要となるため、社内の稟議申請も必要になります。

利用しているSaaSをメンバーの情報と一緒に管理をしはじめ、SaaSの利用が必要になったタイミングで必要な権限のアカウントを発行し、費用の増減も合わせて記録・申請する形式で運用をはじめました。

良かったこと:アカウントとコストの管理が一緒にできることの効率性

前述のとおり、メンバーの増減が不定期にあるため、アカウント発行とプランや料金変更も不定期にあります。

都度、チームメンバーが稟議を作成して申請業務をしていては、業務のスイッチングコストも増えてしまいます。担当者が曖昧な状態で、申請や管理業務が漏れていたこともありました。

金額と利用状況をあわせて管理することで、チームで見たときにコミュニケーションや申請業務のコストも抑えられると考えました。

また、不在時に急ぎ必要な場合も、用意した手順書でアカウント発行/削除できる運用にしておくことで、管理人数が少なくとも対応ができるようにしておけば

メンバーが少ない組織でも、各チームを横断して契約や管理ができるようにしておくことで、少しは手間やコストを抑えながら管理できているかなと思っています。

課題:アカウント発行のタイミング

一方で、SaaSのアカウントをいつ発行するか、運用に迷っていることもあります。

  • チーム参画時にアカウント一括で発行
  • 必要になったタイミングで都度発行

「チーム参画時にアカウントを一括で発行」すれば、アカウント数や費用も見積もりがしやすく、アカウントの登録や削除も利用しているSaaSすべてについて実施するため、ある程度決まった手順で実施ができるため、管理の手間が省けるはずです。アカウント管理のサービスも使いやすいはずです。

一方で業務内容やポジションによって、利用するSaaSに違いもあることから、アカウント発行してもメンバーによっては利用が少ないSaaSもあります。

また、アクティブでないアカウントが気づかずに不正アクセスされるケースもゼロではないと思います。

職種やロールによって、アカウント発行のパターンを作ることも検討しましたが、パターン化することも簡単ではなく、結局必要になったタイミングで追加することになりそうで断念しました。

「必要になったタイミングで都度発行」することで、不正アクセスや使用が少ないアカウント分のコストも抑えることはできますが、アカウント発行の作業は都度不定期に発生しますし、誰に何を発行したか記録しておかないと、全サービスを確認する必要があるためアカウント削除作業の手間が増えます。

現在は、メンバーが多くないことも有り、SaaSの費用削減をねらって「必要になったタイミングで都度発行」の方法をとっています。

誰にどのSaaSアカウント発行をしたか記録する目的も兼ねて、SlackにSaaSアカウント管理の専用のチャンネルを作成して運用しています。

これで、発行依頼や削除依頼にある程度機械的にルールを設けることで手間を減らすことを試みていますが、アカウント周りについてはまだまだ改善しなければいけないことが多いです。

ポストモーテム

これは私がはじめたものではなく前任の方が導入してくれていたのですが、月に一度ポストモーテムの場を設けてチームをまたいでインシデントの振り返りをしています。こちらも入社後に引き継いで継続しています。

専用のリポジトリを用意しており、インシデントの対応が完了した後にリポジトリにPRを作成しフォーマットにそって記録する運用としています。フォーマットはSREの書籍の付録にあるものとほぼ同じです。

月に一度、PRを元に各チームから発表してもらう形式をとっています。

失敗したこと:発表するメンバーの負荷の考慮不足

発表する側の負荷の高さが課題のひとつです。

現在のフローは、以下のようになっており、担当者が自主的に情報をまとめレポートを作る必要があります。

  • インシデントの対応完了
  • 担当者がポストモーテムのリポジトリにPR作成
  • 当時の関係者でPRレビュー
  • 月に1度の発表の場でPRのレポートを発表
  • PRをマージして完了

ようやく対応が終わってからのインシデントの発表を億劫に感じるメンバーもいたため、日々の業務や運用フローにからめるなどて、もう少し発表者の負担を軽減できないかと運用を見直している最中です。

また、コストはかかるものの、ポストモーテムの場で共有することで自チームにも他チームにも学びがありサービスの向上につながると認識してもらうために、もっと体験を良くできる方法を模索しています。

失敗したこと:議論しやすい場を作り切れていないこと

参加人数が増えたせいか、以前よりも議論や考察の会話の量が減ってきています。

以前は、対応内容について意見の交換があったり、改善のアイデアをライトに言い合ったりする場だったのですが、人数が増えて一人あたりの発言の機会が少なくなったせいか、以前よりも議論の時間が少なくなりました。

また、以前は報告されるインシデントも大小様々で、中にはインシデント発生まで至っていなくとも、ヒヤリハットや運用の学びの出来事が発表されることもありました。

おそらく、議論のネタやアイデアを持ち込むことや、議論そのものに参加することで得られる良い体験や学びの実感が薄くなってきてしまっているのだと思います。

今は全チーム一緒になって開催しているため、一人ひとりの発言の機会を増やすために、報告後の議論をいくつかのグループにわけての実施や、チーム単位での実施も検討しました。ですが、自チームの事象についてそもそも当事者であるためすでに議論がなされていることもあり、他チームとの共有や意見交換も意義の一つであると思い、開催の変更には至っていません。

現在もポストモーテム自体は開催していますが、参加してくれるメンバーが良い体験と感じてもらえるように、議論を促したり質問が気軽にできたりするような場をつくれるように日々やり方や進行を模索しています。

まとめ

課題や失敗は色々ありましたが、総じて「情報を整理して、共通の文脈にあわせた上でチームに共有して、共通の認識をつくっていくこと」をもっと大事にして、「活動’の意義や価値を実感してもらうこと」を増やすべきであっと反省しています。言語化して伝えるだけではなく体験として実感してもらうことが一番伝わると思っています。

チームの設立時からジョインしていたり、同じ立場やロールで動くメンバーがいる場合には、共通言語や共通認識の基礎があるため、それらにずいぶん助けられていたのだと感じました。

複数のスモールチームに参画し、新しいことを始めて運用に取り入れて継続的に活動していくことは、土台づくりからとなるため、時間もかかりとても難しいです。

ですが、各チームの各メンバーがオーナーシップをもってサービス開発に取り組んでおり、良い活動となりそうなことについては積極的に取り入れる文化があることに大きく助けられました。そのおかげで新参者の私が新しいポジションでも活動を続けられています。

「良い活動となりそう」と感じてもらった上で、実際にサービスの価値や信頼性をあげられるように、今後もチームと一緒に議論しながら活動していこうと思います。

これから

現在は、IaCやCI/CDの改善、ログ収集と分析に取り組んでいます。

また今年になって、インフラおよびSRE業務にメンバーが一人増えました。

できることも増え、速度もあげられているのでとてもうれしいです。また何より、各チームと関わりはあったものの、チームとしては一人だったので、楽しさやモチベーションもあがりました。

ですが、まだまだ課題も取り組みたいこともたくさんあります。

フォースタートアップスでは一緒に取り組んでくれる方を募集しています。まだまだ未熟な部分は多いですが、興味を持っていただけましたらぜひご連絡ください。

GithubActions未経験者がcreateトリガーでブランチのフィルタ条件を追加してみた話

こんにちは、2022年4月にフォースタートアップスにジョインしたエンジニアの八巻(@hachimaki37)と申します。主に社内向けプロダクト「タレントエージェンシー支援システム(SFA/CRM)」のシステム開発を担当しております。

今回は初めてGithubActionsの改修チケットに関わることになり、途中ヒイヒイ言いながらも試行錯誤して解決に至った話について書いていければと思います。

本題に入る前に、まずは「GithubActionsとは?」と「GithubActionsの導入経緯について」簡単に述べていきたいと思います。

GithubActionsとは?

GithubActionsはGithubが2019年に出したCI/CDサービスです。

GitHub Actionsを使用すると、ワールドクラスのCI / CDですべてのソフトウェアワークフローを簡単に自動化できます。 GitHubから直接コードをビルド、テスト、デプロイでき、コードレビュー、ブランチ管理、問題のトリアージを希望どおりに機能させます。

引用元:https://github.co.jp/features/actions

当社の導入経緯について

弊社のユースケースですとGithubActionsの方にコストメリットがあることがわかったため「コスト削減」を目的にCircleCIからGithubActionsへ移行しました。

導入されたがこんな問題があった

当社の場合、pushトリガー + ブランチのフィルタ条件を用いて発火条件を制御しております。

サンプルコード

name: staging Build and Deploy

on:
  push:
    branches:
      - staging_axx**
      - staging_bxx**
      - staging_cxx**

※サンプルコードをベースに以下話を進めています

1. Github上からブランチを作成するとGithubActionsが発火しない

pushを発火条件としているため、Github上でのブランチ作成(=pushなくGithub上にブランチができあがる)がこの条件にヒットせず、発火されない

2. ブランチのpush順序をミスるとGithubActionsが発火されない

▶︎正しい順序

  1. ローカルPC上で、作業用ブランチをmasterから作成する(feature/hogehoge)
  2. ローカルPC上で、stagingブランチを作業用ブランチから作成(staging_axx/hogehoge)しpushする
  3. 作業用ブランチ(feature/hogehoge)をpushする

▶︎誤った順序

  1. ローカルPC上で、作業用ブランチをmasterから作成する(feature/hogehoge)
  2. 上記3を先に実行する(作業用ブランチ(feature/hogehoge)をpushする)
  3. 上記2を次に実行する(ローカルPC上で、stagingブランチを作業用ブランチから作成(staging_axx/hogehoge)しpushする)

当社では、github-flowに沿った運用を採用しております。誤った順序でpushした場合、pushトリガーの条件にヒットせず、GithubActionsが発火されない

上記の体験から非常に使い勝手が悪い!!そんな声が開発メンバーから上がっておりました。

どんな状態を目指したのか

シンプルに「必要な時に、適宜GithubActionsを発火させたい」ということです。

最初のアイディアは、pushトリガーのようにブランチフィルタを使えば簡単に実現できる!と思っていたのも束の間、実現にあたってこんな問題を抱えておりました。

The create event does not support branch filter and tag filter.つまり、pushトリガーのようなブランチのフィルタ条件が、createトリガーにはサポートされていなかったのです。悲しい...

調査と試したこと

ここからヒイヒイ言いながら試行錯誤の日々が始まりました。

  • createトリガーを追加したらどのような動作が走るのかまず試してみる
  • ん?createトリガーってブランチフィルターをそもそもサポートしてないんじゃ無理くないか?と思いながら、いい解決方法ないかなぁとググりまくる。あった!→https://github.com/orgs/community/discussions/26286
  • jobs自体に条件を追加してみる
  • stepsに条件を追加してみる
  • 良い記述方法を模索する
  • 条件を追加してもSyntax errorがたくさんでる..そもそもどうやって記述すればいいのだろうか

などなど、試行錯誤をしながら進めておりました。

苦労したこと

  • 適切な箇所(jobs, stepsなど)への条件追加を模索したこと、追加したはいいもののSyntax errorが多発するなど、最初は構文理解に苦労しました。「コードを追加してみてpush」「Github上からブランチをcreateする」を繰り返すことで、徐々に理解に繋がりました。
  • 全てのブランチでjobsが発火してしまう。当たり前ですが、createトリガーを追加すると全てのブランチを作成したタイミングでjobsが発火してしまうため、頭を悩ませました。ここは記事(https://github.com/orgs/community/discussions/26286)を参考にコードを書いてみて、特定のブランチ名だと「success」「skipped」になる方法で、解決に至りました。

実装したサンプルコード

createされたブランチが、以下のブランチ名にマッチする場合に発火するよう、条件を追加しました。

name: staging Build and Deploy

on:
  push:
    branches:
      - staging_axx**
      - staging_bxx**
      - staging_cxx**
  create:

jobs:
  build:
    name: Build and Push
    runs-on: ubuntu-latest
    timeout-minutes: 30
    environment: staging

    if:
      contains( github.ref_name, 'staging_axx/' ) ||
      contains( github.ref_name, 'staging_bxx/' ) ||
      contains( github.ref_name, 'staging_cxx' )

    env:
      省略

    steps:
    - name: Checkout
      uses: actions/checkout@v2

- name: hogehoge
      run: test1
- name: foofoo
      run: test2
- name: fugafuga
      run: test3

createされたブランチがマッチした場合

ブランチ名:staging_axx/fix-hogehoge

createトリガーが発火し、GithubActionsが実行される

createされたブランチがマッチしなかった場合

ブランチ名:staging_zxx/fix-hogehoge

createトリガーが発火せず、skipされる

あとがき

正直結構な時間を費やしてしまいチームにご迷惑をおかけしてしまいましたが、リリース後、チームの方からは嬉しい嬉しいフィードバックがありました!!!

今回記述した設定ですと、対象ブランチの数が増加するとコードが​​冗長になるので、リファクタリングができないか?と考えておりますが、問題自体は無事解消に至りました。

私自身エンジニア歴は3年ほどで、今まではサーバーサイドをメインに経験を積んできました。次のステップを考えた際にフルスタック(コーディング以外も含む)に動けるエンジニアへの成長を目指し、フォースタートアップスにジョインしました。

今回初めてGithubActions周りのチケットに携わりましたが、直近はVue.jsなどフロントエンドにも関わる機会が増え、嬉しい成長痛を日々感じ業務にあたっております。

なかなか経験のないチケット着手はワクワクと不安の感情が入り混じりますが、当社では初めてのチケットの場合「good first issue」を付け、初めてでも取っ掛かりやすい環境を構築しております。

まだまだやるべきこと、やりたいことがたくさんあります。ぜひ一緒に成長していきませんか? 当社は採用大募集中です。ご興味ありましたらぜひ一度カジュアルにお話できたらと思います。

採用ページはこちら。(冒頭のTwitterにDM頂いてもOKです!)

MetabaseとRedashどっちが良い?組織の成長とデータ活用の悩み

こんにちは、エンジニアの速水です。

フォースタは22年度4Qで社員が115名となりました。その数は毎年130%成長が続き、まさに拡大中の組織です。組織が100人にもなると役割分担ができてくる一方、事業の全体感をパッとつかむのは難しくなってきます。「タレントエージェンシー支援システム(SFA/CRM)」では、スタートアップ企業、転職を希望される人、ポジションの情報を集約しているのですが、組織の拡大に伴いデータ分析を必要とするシーンも増えてきました。 以前からBIツールのRedashを導入していたのですが、運用で出てきた苦しみ、そこからのMetabaseの導入、残る悩みどころについてまとめていきます。

Redash運用で発生した問題

  • 管理できないほどにクエリが増殖した(その数なんと1400以上!)

  • 開発チームへSQLに関するヘルプが増える

ミニマム導入からスタートしたこともあり、Redashはルールがほぼない状態でした。全員が自由にクエリを作成でき、命名規則やタグ管理、アーカイブ基準は曖昧なまま、クエリはどんどん増えていきました。 それに社員が増えることで、下記状態に陥ります。

  • XXについてデータを出したい!

  • 同じような分析をしているクエリを探すが見つからない!(よくわからん!)

  • SQLを0から書くのは大変だから、とりあえず開発チームに相談しよう!

開発チームはプロダクト開発の中でデータベースもエンハンスしているので、相談を受けないわけにはいきません。とはいえ、表示項目の順番が異なるだけのクエリや、企業や職種などの絞り込みが若干異なるだけのクエリに、毎度エンジニアの工数をかけるのは苦しくなってきました。データ分析はしてもらいたい、だけどエンジニアが都度個々人の依頼を受けカスタマイズしたSQLを書くのも難しい。 エンジニアがSQL勉強会を開きデータ分析の民主化を図ったりもしましたが、定着は難しく一部の人が習得するのみにとどまりました。

Metabaseで解決できるのか

私はRedash上のクエリ分析を経て「同じデータ元で視点が異なるクエリが並列に保存されていること」を課題と定義しました。

例えば企業の採用支援のシーンにおいて、

  • 企業担当は、自分の担当企業のデータが見たい

  • マネージャーは、担当チームの企業データが見たい

という状況があります。 データ元は同じなのに、視点(絞り込み、表示項目の有無/順序)の違いによって並列にクエリが増えてしまうと、その見分けは難しくなる一方です。

MetabaseはRedashにないGUIとクエリをディレクトリ管理できることが強みです。

  • 表示項目の増減/順序の変更・絞り込み条件の指定がGUIで変更できる

  • クエリを階層型ディレクトリで管理できる

https://www.metabase.com/

簡単なデータ出力であれば、GUIでクエリを組み立てることができますし、出力におけるカラム(列)の順序変更や、カラムごとの絞り込みがマウス操作でできます。実際に検証環境で何人かに見てもらい、これならSQLを書けない自分でも操作できると感じていただけたのが導入の後押しとなりました。

項目や日付データでの絞り込みがGUIでできるのは純粋に便利

またクエリをディレクトリに入れて管理ができるので、分析軸ごとに整理することが可能です。ディレクトリごとにパーミッション(クエリを編集できる、閲覧のみ、閲覧できず)も設定できるので、ユーザグループの作成と合わせて「誰がどこを閲覧/編集できる」を明確にすることができます。

MetabaseはAWS Fargateで手動構築し、Terraformでリソース管理するようにしました。環境を作ってしまえばデータベースと接続するだけなので、短期間で進めることができました。 https://hub.docker.com/r/metabase/metabase

Metabaseで困ったこと

複雑なクエリをRehashから移行しようとすると詰まることが多いです。変数を複数設定したようなクエリは「;」でsyntax errorが出てしまったり。Temporary Tables もサポートされていません。複雑なクエリはSQLをそのままコピーするだけでは動きませんでした。 またGUIで簡単にクエリが作成できる反面、多数のJOINで出力データがとても大きくなることもあるので注意が必要です。

現状

Redashは継続利用しながらMetabaseを利用し始めた段階で、クエリの移行はもちろん、数が増えた時に混乱しないような整理を、悩みながら進めている最中です。進めていて実感しているのは、ディレクトリでクエリ管理できるようになっただけで、パッと見た時にどんなデータがあるのかわかりやすいということです。移行途中でクエリが少ないからじゃない?というツッコミはあれど、以前よりは直感的に認識できるようになったと思います。

フォースタートアップスでは共に働く仲間を募集中です。本記事を読んで興味を持っていただけましたら採用情報をご覧ください。

t_wadaさんに社内向けTDD研修を開いてもらったよ

どうも、ばやし(@bayashimura)です。

先日、和田卓人(@t_wada)さんにフォースタートアップスのエンジニア向けにTDD(テスト駆動開発)研修をやってもらったので、紹介していきます。

きっかけ

フォースタートアップスでは私が入社する前から自動テストに一定の投資をしていました。 大体の機能に関してはテストが存在し、テストを書かずにプルリクを投げると「書いてください」と返ってくる文化でもあります。 しかしプロダクトのコードが増えるに従い、テストコードも増加し、以下のような問題が発生しておりました。

  • テストの可読性が低く、テスト内容に対する認知負荷が高い
  • テストのメンテナンスコストが高くてしんどい(すぐ壊れる)
  • e2eテストを導入したがflakyで、導入したことをちょっと後悔してる

こういった課題にもやもやしたものを抱えつつそのうちどうにかしないとな、と日々を過ごしていた中、CTOから「研修やりたいんだけどなにか心当たりない?」と聞かれました。
これは!と思い和田さんのTDD研修を提案し、実現する運びとなりました。 最初は問い合わせ先もわからなかったのですが、NTTコミュニケーションズの岩瀬(iwashi86)さんに紹介していただき、見事開催の運びとなりました。岩瀬さんありがとうございます!

研修内容に関しての詳細は割愛しますが、午前中は和田さんのライブコーディングを見て、午後はみんなで実際にTDDで書きつつ、和田さんとの公開1on1をするという形式でした。
これがめちゃめちゃ良くて、研修実施後アンケートでも、満足度は最高5のところ、驚異の平均4.9。 感想も「学びしかない」「これまで受けてきた研修で一番良かった」といった感想がきており、最高といった感じです。

テスト研修後にチームに起きた変化も書いていこうと思います。

テスト駆動開発で書くメンバーがでてきた

今回のTDD研修を受け、TDDで開発する人が私を含め何人かでてきました。慣れないテスト駆動開発で、正直コードを書くスピードは遅くなったなという感覚はありますが

  • 設計に対して丁寧に向き合えるようになった
  • 一度に向き合うものを減らしたことで沼にはまらなくなった

という感覚があります。もうちょっと熟達したらもっとメリットが出てきそうな感覚です。

生みの苦しみ中

一山越えたあと

テストケースを日本語で書くようになった

今までRSpecのテストケースは英語で書いていました。 ただ母国語ではない英語で書くにあたり、細かいニュアンスを書くのが難しく

context 'if valid request' do
  it 'response success' do
  end
end

上記のようなテストケース名などがたくさんありました。

TDD研修でテストケースは仕様を表すということを学び、和田さんから 「テストケースは母国語、チームの公用語で書くのをおすすめしています」 という教えもあり、それから一部のチームではテストケースは日本語で書くようになりました。 新しく書く場合や、テストケースに変更を加える場合は基本的に日本語にしているのですが、早速地獄の蓋が開いてきてる感じがあり、最高だなといった感じです。 以上、t_wadaさんに社内向けTDD研修を開いてもらった話でした。 想像以上に良い学びを得たので、今後も有識者を招いてこういった研修をやっていきたいと思います。

うらやましいと思ったそこのあなた。弊社採用大募集中なので、お気軽にどうぞ。

採用ページはこちら

開発に至る前の要件定義で四苦八苦した話

こんにちは、エンジニアの藤田です。 普段は社内向けのプロダクト「タレントエージェンシー支援システム(SFA/CRM)」の開発をしています。

ヒューマンキャピタリストはTA(タレントエージェンシー)本部という部署に所属しており、そのTA本部が使うシステムを内製で開発しています。

エンジニアとしてジョインして約半年、ここでの開発手法はアジャイルでフラットな開発チームであり、優先順位はあるもののタスクは開発者の裁量で取って進めていくスタイルで割と自由に開発しています。

書籍『アジャイルサムライ』第2章冒頭で書かれている、

典型的なアジャイルチームには、あらかじめ決まった役割分担は存在しないッ!!

というやつですね。

普段のタスク内容は、RubyやVue.jsを使ったシステム開発、バージョンアップ対応、インフラに強いメンバーはインフラ整備など行っています。

さて、ここ最近私が取ったタスクが「POと共にユーザヒアリングに同席しながら要件定義していく」というタスクでした。

正確に言えば、当初は「とある機能追加の為のUI、設計を考える」タスクであり、平行で動いている別の改修タスクと切り離し可能な作業と考えていたのですが、進めていくうちに他の改修機能との兼ね合いを考慮する必要もでてきたり、別のタスクが自分のタスクにも影響でてきたりして、こう思ったわけです。

「もっと上流から考え直す必要があるじゃん。。」

ということで色々苦労したのですが、ユーザの声を聞けたいい経験でもあったのでその時の記録を書いていこうと思います。

TA本部のとある課題と我々開発チームのミッション

おおよその業務は社内向けシステムで運用を管理できているのですが、当然ですが組織や事業は日々変化していきます。
そのため我々が気づかないうちに新たな運用が発生し、別のツール(スプレッドシートやその他管理ツール)を現場でカスタマイズし管理していくケースが往々にして発生します。
今回要件定義したものも、詳細は書けませんがこういった業務のひとつです。

別のツールだとしても管理できているならいいじゃんとなるかもしれませんが、次のようなデメリットもいくつかあるので

  • 情報が分散されてデータが管理しにくい
  • 属人化してしまう

今回は組織として挙がった課題を解決できるよう、システムに組み込むといったものになります。

開発者の課題

要件定義をしようとしたところ、以下の課題に直面しました。

  • 何となくやりたいこと、現場の課題があることはわかったが、具体的な「システムで管理できていない運用」のところがよくわかっていない。
  • 通常の運用も具体的なところまではわかっていない部分はある。

というわけで、いきなり躓くわけです。

困った時のヒミツ道具ということで手元にある『エンジニアリング組織論への招待』を開いてみると以下のことが書かれていました。

ソフトウェアにおける実現

それは誰かの曖昧な要求からスタートし、それが具体的で明確な何かに変わっていく過程が実現で、その過程のすべてがエンジニアリングという行為です。
つまり、「曖昧さ」を減らし、「具体性・明確さ」を増やす行為が「エンジニアリングとは何か」という答えでもあるのです。

1-2. 不確実性とエンジニアリング

具体性・明確さを増やすために、まずはユーザの運用から整理しようと思い、業務フロー図を作成しました。

業務フロー図作成

ユーザヒアリングまで少し時間があったので、業務の理解と整理の為に業務フロー図の作成準備に入りました。
先にこれをやるメリットは以下が考えられます。

  • 事前に図として整理することでヒアリングの質が向上する。
  • 質問の準備ができる。
  • 図を共有しながら話もできる。

どのような運用をしているかはslackのやり取りにもヒントがあったり、開発チームでも知見のあるメンバーはいるのでそれらの情報を集め整理しながら簡単な業務フロー図を作成しました。
あくまでチームで理解の共有ができればOKなので、結果以下のような図になりました。

登場人物の関係性と下半分は時系列の処理フローのような図があったり、パターンを考えた図も含まれています。
教科書通りの業務フロー図とは異なりますが、いいのでしょうか?

いいということにしましょう!

最初の段階では仮説思考的に作成したものとなり、その後にチーム内で相談、議論したりユーザから正確な運用をヒアリングしながら図を更に肉付けしていきました。

ユーザヒアリングや業務フロー図作成の過程で以下のようなことがわかってきました。

  • ユーザの業務運用フローはどういったものか
    • どのように管理しているのか
  • 課題の具体的な特定

また、今回システム改修をする上で考慮しなければならない点も整理できるようになりました。

組織として

  • 現場の運用をシステムで管理したい。

現場として

  • スプレッドシート管理の拡張性は手放せない。
  • システム化することで業務が回りづらい状態になっては困る。

開発チームとしては上記、両方考慮しながら開発しなければなりません。

ちなみにユーザヒアリングについては1.5ヶ月の間にトータル10回程行いましたが、POは別途回数こなしてヒアリングしていました。 様々なチームや役割があるので多角的に情報収集し、その中でベストな方向性を模索しました。

システムの新機能について要件定義

システムで管理出来ていない運用フローについて明らかになってきました。次のステップはどのように既存システムに新機能を組み込むかになります。

まずはPOが大まかな仕様を考えたり、壁打ちしたり、開発チームで仕様を検討するMTGを行いました。
仕様について検討する機会が多くなったため、仕様検討MTGを増やして議論を重ねました。

チームミーティングは1回のMTGで1時間〜2時間を週に2〜4回くらい、その時の課題によって増やしたり減らしたりしました。トータルで10~20時間は仕様検討に使いました。
この段階でシステム開発における「達成すべきもの」が徐々に明確になってきました。

ワイヤーフレーム、画面遷移図、ER図を作成

また、この工程でワイヤーフレーム、画面遷移図、ER図も作成します。
ワイヤーフレームはチーム内で理解のズレが無いよう、できる限り正確な情報でデザインしていき、それを基に更に開発チーム内で議論、ユーザとも感触を伺ったりしていき、改良を重ねていきました。

この段階での正確なワイヤフレーム作成手法はその昔、ECサイト開発をしていた時に一緒に働いたWeb制作会社のやり方を真似ました。
完成品をイメージしやすいのはもちろんですが、なかなか綺麗なワイヤーフレームを見て感心したものです。
バックエンドエンジニアの自分がやっても下手で時間的コストが掛かるので簡単な対応の時はやりませんが、他人と理解のズレが発生しそうな仕様を考える時などはこのやり方を意識してます。

話を戻しますが、ここで重要になるのが、先に挙げた以下2点です。

  • この新たな機能追加が組織の課題解決になっているか、かつ
  • 現場の業務改悪になっていないか。

ユーザは既に別の管理ツールで運用しており、我々が下手な追加機能を開発しても使ってもらえません。社内のユーザとはいえ、使いたいと思う機能を提供しないと使われないのは一緒です。
開発者として使われないまま負の遺産になることは何としても阻止しなければなりません。

そこで開発チームの提案とユーザの考えの違いに差がないか、慎重に仕様を詰めていきます。
ズレていたら再度、仕様見直しと共にドキュメント修正、開発チームで議論、ユーザへ提案またはヒアリングを繰り返していきます。
週一2時間でプラニングポーカーの見積もりミーティングがあるのですが、この対応の話をしていていたら長引いて見積りができない回が何回かありました。
もちろん見積りミーティングはリスケです。

またここまで、できる限り正確なワイヤーフレームを作成してきましたが、開発中に「こっちの方がいい」はどうしても出てくるのであくまでチームの共通理解が主な目的になります。

ER図等のDB設計についても「DB設計検討作業」をチケット化し、担当者が数パターンを開発チームに提案、チームで合意を得ながら進めていきました。

ユーザーストーリー作成、タスク化

この段階まで来たらかなり「曖昧さ」が減り、「具体性・明確さ」が増えてきました。
あとはユーザーストーリー作成や作業のタスク化をし、開発メンバーでプランニングポーカーの見積もりをしていきます。

また、この段階でフロント→バックエンドの仕様を描いたシーケンス図も作成しましたが、これも開発段階で変わってくることはあるので、あくまで参考程度の情報にしています。

最後に

組織の課題解決をどのようにシステムに落とし込むかを皆で頭抱えながら進めていき、話し合いの最中や開発の段階でも新たな課題が発生しては、仕様詰め、設計、見積もりを繰り返してきましたが、無事に開発も着手でき、部分的にリリースもできてきました。

今回の開発作業では、「組織の課題を解決できるだけでなく、現場のユーザの使い勝手」の2つを考えなければならないのが大変でした。
開発チームの提案がユーザ側には通らない場面も多々あり、運用ヒアリングしていく中で「なるほど」と納得する場面が多く勉強になりました。

手段として、色々ドキュメント書いたりミーティング増やしたりしましたが、「何の為に作るか」の目的は常に意識する必要があるかなと思いました。
もちろん手段もいいやり方を吸収していきたいです。

-社員と別け隔てがない環境- Startups Firstの為に常に挑戦し続けるフォースタのエンジニアインターンで学んだこと

初めまして、杉谷です。

2021/10月から入社までの約5ヶ月間、インターンとして働き4月から正社員として入社しました。 今回は、そのインターンについてや5ヶ月間で学んだことについて書いていきたいと思います。

フォースタのインターンってどんなことするの?

最初に、インターン内容についてですが、 インターンは、大きく「課題」・「実務」の2ステップ構成になっています。

まず初めに、課題に取り組みます。

この課題は、実務で扱うプロダクト内容や扱う技術への理解を深める為に設けられています。 例を挙げると、弊社ではElasticsearchという検索エンジンを使っているので、課題では「実際にフィールドを追加してAPIのレスポンスを返す」といった実務でも行われるAPIの改修に取り組んでもらったりしています。 フォースタ以外でも通用する技術を効率よくキャッチアップ出来る機会なので、1つの魅力的な点かなと個人的には思っています。 課題を修了することで、一定レベルのプロダクト・技術的な知識を共有した状態になり、スムーズに実務に着手することが出来ます。

またこれはユニークな点ですが、課題はインターン生自身の手によって常に更新されています。 更新というのは具体的に、課題を修了し実務に携わっているインターン生が、前もって学んでおいた方が良いことをまとめ、それを課題に反映させています。彼らが主体性を持って自身の学びを課題にアウトプットすることで、常に課題は改善され、同時に彼ら自身の成長にも繋がるというサイクルです。 実際、私が課題に着手していた際は課題の数は6つでしたが、現在では11個に増えています。ただ多ければ良いという話でもないので、初期に追加された課題を複数まとめてアップデートし、より密度の高い課題にすべくインターン生の方々が自身の経験をもとに試行錯誤しています。(課題10くらいまでに抑えるのが理想です)

そんな優秀なインターン生が寄稿している記事もありますので是非ご一読下さい。

tech.forstartups.com

課題を修了すると、いよいよ実務に取り組みます。 主にインターン生は、『STARTUP DB』やその裏側にあたる管理システム(STARTUPS DATA PLATFORM)に携わります。 社内にいるユーザーからの意見を基に改修を行なったり、新規機能開発を行うなど幅広いタスクに着手しています。 またタイトルにある通り、社員とインターンの間に垣根がなく、実務に当たる際は優先的なタスクから順にインターン生・正社員問わず着手しています。

さらに、自身の興味のあるプロダクトや磨きたい技術が実務にあれば、手を挙げて挑戦できます。 私自身、インターンとして働いている時にフロントエンドの開発に興味があったので、手を挙げた結果翌月からフロントの開発にもアサインされました。(他にもインフラをやりたくて手を挙げて、現在terraformをバリバリかいているインターン生もいます) 社員との垣根が無く自由度が高い分、それに伴う責任やプレッシャーもありますが、やりたいことに挑戦できる環境はとても貴重な為、モチベーション高く業務にコミット出来る環境だなとインターンを通して感じています。

スキルアップ会や勉強会で幅広い知識をインプット

エンジニアインターン生は、業務時間内で課題や実務以外に2つの会に参加しています。

スキルアップ会(輪読会)では、主に1冊の本をみんなで輪読しディスカッションを行いスキルアップを図っています。その一冊は、社員・インターン生関係なくビブリオバトルを通して選ばれます。 詳しい内容は下記の記事にて紹介していますのでご参照下さい。

tech.forstartups.com

勉強会では、発表者を募りそれぞれが興味のあることや共有したいことをテーマにして30分プレゼンしています。 テーマは様々で、実務に即したものから自身の興味のあることについてなど話しています。 もちろん、インターン生も発表することが可能で、自身の学んだことをアウトプットする場所として推奨されています。 私自身もこの記事を書く前に、勉強会でこれまで学んできたことについて発表し、改めてこの5ヶ月で学んできたことを自分の中により深く落とし込むことが出来ました。

この他にスプリント定例や各種mtgもあるのですが、それはまた別の記事で書ければなと思っています。上記の内容でもっと詳しく知りたいと思った方は、ぜひ一度カジュアル面談に来て頂けると嬉しいです。

インターンを通して学んだこと

ここからは自身がインターンを通して学んだことについて書いていきたいと思います。 細かいところまで列挙してしまうと、長くなってしまうので特に自身への影響が大きかったものを挙げたいと思います。

理解しやすくパフォーマンスを考慮したコードを書けているか

これは、自身のコードが他人にとって可読性が高いか、プロダクトにどれくらいの負荷がかかるのかといった点を考慮しながら書けているか常に意識すべきであるという学びを表しています。

フォースタのインターンでは、エラー調査や改修作業などは主にインターン生が担当しています。 エラー調査はプロダクトを知るのにとても良い方法で、色々な箇所のソースコードを読み漁ります。そうして改修箇所を特定しリファクタリングを行うのですが、当然調査をすればするほどプロダクトのソースコードに詳しくなっていきます。そうなると、「あの箇所、ページの描画速度が遅いから改善したほうがいいな」とか「ここら辺は共通化できそうだ」という点が出てきます。 そうして改修を重ねるうちに、自身が書くソースコードでもパフォーマンスを考慮して書けているかを自然と意識するようになりました。 これは、実務に入らないと分からないことだなとインターンを通して深く理解しました。

自走できないと活躍できない

今まで「自走力」というのは、ある一定の技術力を有することであると考えていました。 しかしそれ以外にも「目標を持って取り組み、それに見合った行動・成果を出す」といったことも「自走力」であるとインターンを通して学びました。

これを実感したのは、実務に取り掛かった初期の頃です。 課題を通してプロダクトへの理解は一定程度得たものの、いざタスクに取り掛かろうとすると何から着手すべきかの順序立てが上手く出来ませんでした。結果、当初予定していたよりもだいぶかかってしまうという結果に終わりました。

そこから、「まずはタスクをしっかり期日までに完了させる」という目標を持つようになりました。 そのような目標を持つことで、段階的にやるべきことを順序立てていけるようになりました。 また、質問の仕方も目標を持ったことで徐々に変わっていきました。質問も同じように順序立て、具体的にどうしたかったのか、それが出来ない理由と試したことは何かなどを落とし込んで質問を行えるようになりました。 そうすることで煮詰まることが少なくなり、タスクをこなすスピードが改善され目標に対して自身の行動や成果が追いつくようになりました。

この学びは、自由度が高いからこそ高い自走力を求められる、フォースタのエンジニアインターンの環境があったからだと実感しています。

ユーザーの領域に関する知識も深く蓄えないとvisionは遠退く

これは、どんな領域・分野のユーザーが『STARTUP DB』をどのように活用し、どのような情報を求めているのか、自分もユーザーのことを深く理解する必要性があるということです。 言い方を変えれば、エンジニアだからと言ってユーザーと接する最前線に行かずにいるのではなく、むしろ前のめりに出て話をしに行き、そこから常にユーザーの本質的な課題を探す姿勢を持つことも重要であるということです。

結局のところ、フォースタのエンジニアが技術力を高めるのはビジョンを達成する為であり、それがモチベーションとなり日々挑戦し続けられます。

www.wantedly.com

私はこの5ヶ月、なるべく自身のプロダクトを使う領域の人や他部署の方と話をするように心がけ、「STARTUP DBが今どんなユーザーに使われているのか」・「STARTUP DBにはどんな情報を求めているのか」といった開発しているだけでは中々見えてこない情報を蓄えるようにしました。そうすることで、ユーザーの視点でプロダクトを見たり、それを基に課題を自分なりに見つけ壁打ちするといったことが出来る様になってきました。そして日々ユーザーと話しその中から見つけ出した課題の解決策をプロダクトに反映させることが、ビジョン実現の一番の近道ではないかとインターンを経て改めて強く実感しています。 またこの学びは、共に目指すべき目標が同じであり挑戦する仲間が集まったフォースタのインターンだったからこそ学べたものと深く実感しています。

ここまで読んで頂きありがとうございます。 この5ヶ月の間、インターンとして挑戦し上記に述べた学びは勿論、それ以外にもたくさんのことを学ぶ貴重な機会でした。 これからは、正社員として日本からグローバルに戦えるスタートアップを創出するべくモチベーション高く挑戦していきたいと思います!

エンジニアが週末に作ったアプリケーションをオフラインイベントで披露した話

こんにちは.エンジニアの藤井(@yutafujii)です. フォースタではおよそ2年ぶりに”感謝祭”というイベントを開催いたしました.(イベントレポートはこちらでご確認いただけます)

イベント当日は,来場者が受付されるたびに会場内のスクリーンにお名前と写真がポップアップ表示されていました.これは個人開発で作成された簡単なアプリケーションだったのですが,今回はこの開発経緯や技術的検討点についてお話ししようと思います.

スクリーンにお名前と写真がポップアップ

感謝祭とは

フォースタ感謝祭とは,日頃お世話になっている起業家や投資家,スタートアップエコシステムに関わるみなさまをオフィスにお招きし,立食パーティ形式で交流していただくイベントです.なお,抗原検査を行った上での参加を必須とするなど,必要な感染対策は講じての実施です.

開催にあたり,今回私は運営委員に入り込み,イベントをよりよいものに仕上げるためにエンジニアリングの視点で関わってきました.

モチベーション

さて,その感謝祭での我々の悩みが,「どうやったらゲスト同士の交流を促進できるか」というものです. 立食パーティという性質上,場だけを提供すると,どうしても知り合い同士で話をしてしまう・知っている人が少なく孤立してしまう,という状況になりがちです.

こうした交流促進の課題感に対して,運営メンバーのキックオフミーティングで一意見として

「会場にスクリーンを設置して,ゲストが来場するごとにそこに顔写真をバーンって映せたら,”あ,いまこの人きたんだ・この人も来ているんだ”ってのがわかって,ゲスト同士がコミュニケーションを取るきっかけが作れるよね」

という提案をしてみたところ.即座に「藤井さん,これできる?」

アサインされてしまいました.流石,みんな仕事できる.

「うーん,とりあえず検討してみます」

プロトタイプ

個人開発としてプロトタイプ作成に取り掛かります.

当初の要件からすると,スクリーン(=クライアントPC)は受動的に来場者を表示する必要があります.

WebSocketか,定期的なAPIリクエストでのデータ取得のどちらかで実現可能だとは感じ,せっかくなので実装経験がなかったWebSocketを使おうと思いました.その方がリアルタイム性でも勝りますし.

となると,ステートフルなサーバーが必要になるので,S3/CloudFrontでの静的ホスティングではなくEC2かECSに載っけて動かさないといけなさそうです.

ここまでの要件だけなら言語やフレームワークは何でも良さそうだったので,せっかくならと思い実装経験がなかったNext.jsをチョイス.ちなみに弊社では基本的にNuxt.jsを使っています.

起点となるユーザーの操作ですが,ゲストの受付管理はスプレッドシートで行うのが好ましかったため,以下のようなフローを検討しました:

  1. 受付で名刺をお預かりし,スプレッドシートにチェックをつける
  2. GAS(Google App Script)でセルに記載されたゲスト情報をAPIエンドポイントに送信
  3. リクエストを受け取ったNext.jsサーバーは,WebSocketを通してクライアントに対してデータを送信
  4. クライアントPCはゲスト情報を画面に表示する

想定するデータフロー

画面への映し方については,ランダムな座標にフェードイン・フェードアウトのアニメーションで表示させました.

できあがったローカルでのプロトタイプはこんな感じです.スプレッドシートにチェックを入れるたびに,画面にゲスト情報が表示されます

フィードバック

さて,運営チームのミーティングで見せてみました.

「おお〜...」

こういうのってレスポンスからなんとなくわかりますよね.”悪くはなさそうだが,もうちょっと何か”というところですね.

ミーティング中に追加で要件をいくつか整理して時間内に実現可能か試してみることに.

プロトタイプに対する追加の要件

ブラッシュアップ

というわけでこれらに対応していきます

要件1:ゲストのスクリーン出現場所はランダムではなく中央に固定

画面の真ん中にゲスト写真を出現させること自体はもちろん難しくなさそうです.

ただ,一気にゲストが来場して受付担当者がスプレッドシートチェックボックスを次々に押しても見栄えが悪くならないようにしたい.一瞬で別のゲストの写真に切り替わっちゃったら寂しいですから.

そこで,キューを用いることにしました

キューを用いた表示方法

  1. WebSocketで受理したデータはまずはキューにpush
  2. 中央の表示部が空いていたらそのままpop
  3. 表示部では一定時間経過したらデータを落としてキューから次のゲストデータをpopする

こんな感じで実装をしてみます.

// pages/spread.js
// *表示パターンごとにページを作り,パターンをコンポーネント名称にした

const reducer = (state, action) => {
    switch (action.type) {
        case 'ENQUEUE':
          // 3箇所の表示場所が空いていたら,すぐに表示
          // そうでなければキューにpush
          // 詳細は省略
        case 'POP_LEFT':
          // キューの先頭をpopしてleftに代入
        case 'POP_CENTER':
          // 省略
        case 'POP_RIGHT':
          // 省略
        default:
            return state
    }
}


const Spread = () => {
    // キューとゲストを表示する中央3箇所をステート管理
    const [newGuestState, dispatch] = useReducer(reducer, {
        queue: [],
        left: undefined,
        center: undefined,
        right: undefined,
    })

    // websocket経由でデータを受理したらキューに入れる
    const socketInitializer = async () => {
        await fetch('/api/socket')
        socket = io()

        socket.on('update-input', msg => {
            dispatch({ type: 'ENQUEUE', data: JSON.parse(msg) })
        })
    }
    useEffect(() => socketInitializer(), [])

    // 中央3箇所の表示場所はそれぞれデータの変更をウォッチ
    // 一定時間経過後にキュー先頭をpopして表示する
    useEffect(() => {
        if (newGuestState.left) {
            const timer = setTimeout(() => {
                dispatch({ type: 'POP_LEFT' })
                readyToRender(n)
            }, TIMEOUT)
            return () => clearTimeout(timer)
        }
    }, [newGuestState.left])
    useEffect(() => {
      // ...
    }, [newGuestState.center])
    useEffect(() => {
      // ...
    }, [newGuestState.right])

    // ...

要件2:ゲスト写真はフェードアウトせず残し続ける

さて,フェードアウトさせないことはもちろん容易にできそうですが,データの永続性が気になります.

実際のイベント開催時には,何らかの原因で画面が動かなくなってリロードしてみるということが確実に起きると考えていました. その際に画面をリロードしてもこれまで来場されたゲストがきちんと残ってスクリーンに映っているようにするためには,WebSocketでフロー情報を受け取るだけでは実現が難しそうです.ここにきてデータベースが必要そうでした.

そこで,PostgresQLを導入することにしました.ORMにはPrismaを選定しました.はい,公式ドキュメントに載ってたのをそのまま使おうとしただけです.

データベースをシステムに追加

これに伴い,GASからPOSTリクエストを受けるAPIエンドポイントにはデータベースへのINSERT処理を追加し,画面コンポーネントファイルには getServerSideProps を追加しました.

// pages/api/arrive.js

const Arrive = catchErrorsFrom(async (req, res) => {
    if (req.method == 'POST') {
        try {

            // APIエンドポイントの処理にデータベース保存を追加
            const guest = await prisma.guest.create({
                data: {
                    name: req.body.name,
                    company: req.body.company,
                    position: req.body.position,
                    imageUrl: req.body.imageUrl,
                    checked_in_at: new Date().toISOString()
                }
            })

            res.status(200).json(guest)
            if (res.socket.server.io) {
                res.socket.server.io.emit('update-input', JSON.stringify(guest))
            }
        } catch (e) {
// pages/spread.js

// ...

// 既に来場されたゲスト一覧を取得し最初から表示する
const getServerSideProps = async () => {
    const guests = await prisma.guest.findMany()

    return {
        props: {
            initialGuests: JSON.parse(JSON.stringify(guests))
        }
    }
}

const Spread = ({ initialGuests }) => {
    // 来場されたゲストをステート管理
    const [guests, setGuests] = useState(initialGuests)
    const [newGuestState, dispatch] = useReducer(reducer, {
        queue: [],
        left: undefined,
        center: undefined,
        right: undefined,
    })


    useEffect(() => {
        if (newGuestState.left) {

            // 新しく来場されたゲストをguestステートに追加し,
            const guest = { ...newGuestState.left }
            const n = guests.length
            setGuests(prevGuests => [...prevGuests, guest])

            const timer = setTimeout(() => {
                dispatch({ type: 'POP_LEFT' })

                // 一定時間経過後に画面周辺に表示させる(メソッド内容は省略)
                readyToRender(n)
            }, TIMEOUT)
            return () => clearTimeout(timer)
        }
    }, [newGuestState.left])

// ...

クライアント側の処理はおよそ以下の図のようになります

画面の中にゲスト写真をどう配置するのかは次に検討します

要件3:ゲスト写真は来場された順に周辺に寄せて表示していく

要素divにあたるCSSposition: absolute にしておき,topleft属性をJS側で与えてあげれば各要素を好きな場所に表示させることはできます.

実際,ランダムな場所に表示させていた最初のプロトタイプはこうして実装していました.

来た人から順に画面の外側に並ぶようにするにはどうしたらいいのか...

まず,ゲスト写真の配置場所としてあり得る座標を計算しておき,中心からの距離が遠い順にソートして,来場された順に先頭の座標から割り当てていく,という方針をとることにしてみました.

要素を配置する座標を全て計算

画面中心からの距離でgridをソート

実装としては,座標(grid)をステート管理し,画面(window)サイズが変化するたびに再計算する処理を加えたのと,gridが変化するたびにゲスト写真のポジションを再度割り当て直す処理を追加します.

// pages/spread.js

import useWindowDimensions from '../hooks/useWindowDimensions'
import shuffle from '../utils/distance_ord'

const Spread = ({ initialGuests }) => {
    // ...

    // windowサイズをウォッチしてgridを再計算
    useEffect(() => {
        if (windowDimensions.width) {
            let newGrid = []

            // for each item has width, height = 80px
            const xMax = windowDimensions.width / UNIT_OF_GRID
            const yMax = windowDimensions.height / UNIT_OF_GRID
            for (let y = 0; y < yMax; y++) {
                if (y % 2 == 0) {
                    for (let x = 0; x < xMax; x++) {
                        newGrid.push([y/yMax * 100, x/xMax * 100])
                    }
                } else {
                    for (let x = 0; x < xMax-1; x++) {
                        newGrid.push([y/yMax * 100, (x+0.5)/xMax * 100 ])
                    }
                }
            }
            // 画面中心の一定のエリアは除外した
            newGrid = newGrid.filter(grid => !(25 < grid[1] && grid[1] < 78 && 33 < grid[0] && grid[0] < 65))

            // shuffleはgridを中心からの距離(の2乗)でソートする関数
            setGrid(shuffle(newGrid))
        }
    }, [windowDimensions])

    // gridをウォッチしてゲストの配置座標を振り直す
    useEffect(() => {
        const postGuests = guests.map((guest, index) => {
            const n = grid.length
            const top = n === 0 ? 0 : grid[index % n][0]
            const left = n === 0 ? 0 : grid[index % n][1]
            return {
                ...guest,
                styleProps: {
                    top: `${top}%`,
                    left: `${left}%`,
                },
                render: true,
            }
        })
        setGuests(postGuests)
    }, [grid])

windowサイズが変化するたびに座標を再計算しソートするには O(n) + O(nlogn) の計算がクライアント側で必要ですが,イベントの参加者は1000人もこないですし,n < 1000 を前提に作れたので,現実的な処理時間になりそうです.

要件3番外編:集合体恐怖症の方も気分を害さないように

さて,ここまで実装してみて,途中経過をSlackで運営メンバーに共有してみると...

スタンプが1個もつかない...! むしろ「気持ち悪い」という趣旨のコメントが...

たしかに,ギョッとする見た目なんですよね.テストデータは写真素材が少なかったので,デフォルトにした蛍光カラーのアバターが目立つとはいえ.

「気持ち悪い」という趣旨のコメント,冗談めかして言われてますがクリティカルな問題だと認識しました.当日大きなスクリーンに映るこの画面を見て同じ気分になるゲストがいるかもしれない,と.

そこで,画面の縮尺を変化させて,集合体っぽく映らないように対応できるようにしておきました.つまり,

  • 画面全体にわたって整列しない程度にまで縮小する
  • 画面内に20個くらいしか映らない程度まで拡大する

といった操作を当日行えるようにしておきました.

ただし,どのような縮尺だったとしても画面中央に出てくる新たなゲストは大きく映したかったので,これらの要素は全てCSS属性を vhvw で指定し,他は px指定するように変更しました.

CSSを使い分ける

画面サイズを変更して気分を害さない見た目に調整できる

要件4:背景は感謝祭の画像を検討

実装者としてはもっとも簡単なこの部分の実装が,一番ユーザーの印象にインパクトを与えるんですよね.

背景を変えて,ついでにちょっとしたアニメーションも追加します.

画面中央に常に表示するアニメーションを追加する

さて,開催数日前にここまでの状態に仕上がり,運営メンバーがいるチャネルに連絡してみると...

上出来!この反応を見た時に,「これなら当日出してもゲストが見てくれそう」と直感しました.

そのほか

一時のプロジェクトでAWSリソースを変に汚したくなかったので,終わったら全てもれなく削除が可能なようにIaC(Terraform)管理しておきました.デプロイについてはGithub Actionsでdocker-composeファイルを元にECS serviceを起動するようにしています.

こうして完成したアプリケーションは,会場受付においてあるスプレッドシートと連動して次のように動きます.

完成

感謝祭当日

幸いなことに何の問題もなく,きちんと動きました.

また,ありがたいことにSNSへの投稿素材にしてくださった方もいらっしゃいました.

エンジニアとして大変うれしいですね.

おわりに

社内から複数のフィードバック聞かせていただき,目的だった「ゲスト同士の交流を促進」に多少なり貢献できていたようです.よかった.

一方で,「もう帰ったのかわからない」「フォースタ社員がより交流を促進できるためにはxxといった使い方ができたらよかった」など次の課題も見えました. 次回やるなら改善してみたいところです.

We are Hiring!

フォースタートアップスでは共に働く仲間を募集中です。本記事を読んで興味を持っていただけましたら採用情報をご覧ください。