新卒で入ってくる君たちへ

こんにちは、freee株式会社 CTO の横路です。裏カレンダーでは初日を担当しましたが、表では最終日を担当します。

この記事はfreee Developers Advent Calendar 2018の最終日です。

新卒でエンジニアを目指すといってもさまざまな選択肢があるなかで、わたしは新卒の皆さんに自信をもってfreeeを選んでもらいたいと思っています。そこで今回は、なぜfreeeは新卒を採るのか?採用面接ではどこを見ているのか?新卒のみなさんに何を期待するのか?なぜ新卒でfreeeを選ぶのか?をお伝えしようと思います。

freeeの新卒採用でひとつ特徴的なのは、3年間でスモールチームのCTOになってほしいと伝えてることだと思います。CTOという役割自体、会社やフェイズによって多様に異なる中で、このフレーズ自体が眉唾ではありますが、今回はその真意もお伝えします。

なぜfreeeは新卒を採用するのか

そもそもなぜ新卒なのか。freeeが新卒採用を拡大してる理由は色々ありますが、次の3つはその中でも大きな要素です。

1. すでに優秀な新卒メンバーが急成長していて、今後も期待値が大きいから

freeeでは昨年度から新卒エンジニアが入社してますが、すでに1年目から機械学習プロダクトのインフラ立ち上げから運用までやり遂げたメンバーや、2年目で新プロダクトの最速リリースを達成したメンバーがいたり、その他にもすでに多くの新卒エンジニアメンバーが大きなチャレンジを任されて急成長してます。優秀な新卒メンバーに大きな期待値があり、会社も急成長を続けているから、これからも大きなチャレンジを任せられます。

2. 会社のミッションとして、社会に長く価値を届ける使命を持っているから

freeeは、「スモールビジネスを、世界の主役に。」というミッションを掲げて、中小企業の経営を変革するビジネスをやってます。その性質上、われわれのビジネスは一発当てて終わりというものではなく、10年、20年と価値を生み続けないと達成できないであろうものです。その中で世の中のトレンドをおさえたベストプラクティスを、常に瞬発力をもって市場に広く提供しつづけるためには、組織全体を若く保ちつづける必要があると思ってます。

3. 優秀なシニアは優秀な若者に突き上げられてさらに成長できるから

優秀なシニアは若手にとってよきメンターでありよき師匠ですが、シニア自身は案外孤独な存在でもあります。そんなときに、自分を超えていくかもしれない若者のポテンシャルを見て、より高みを目指したり若手の育成に情熱を傾ける優秀なシニアがfreeeでも増えてます。

freeeのシニアには、厳しくも面倒見がよいギークなひとが多いと思います。技術を課題解決の重要な手段のひとつと捉えつつ、それ自体を匠の技として楽しみ、結果として修練を欠かさない人たちです。それが、技術的に常に世の中のベストプラクティスを取り入れようとする風土や意思決定につながってます。

新卒採用で個人的に見るポイント

新卒採用の面談はとても難しいです。中途だと即戦力を求めるから、過去の経験と再現性を中心に深掘りしますが、新卒はたとえ技術的な素養があっても、社会的に影響力の大きなエンジニアリングの経験がある人はほとんどいません。今後の成長を感じる経験にあまり一貫性はないですが、個人的には以下の2点を重視してます。

1. 何かに夢中になって、短期間で成長したり成果を残したことがあるか?

freeeでは特に変化が難しい業界をスピード感をもって変えていくことにこだわっているから、短期間でというのは重要です。短期間でやりきるために、俯瞰的・構造的に課題を捉えて何か工夫したか?やりきることにこだわったか?というところを中心に見ます。

2. そのパッションを人に伝えられるか?

採用面談の評価項目には、面接官の主観が求められる項目もたくさんあります。その中の代表格は「一緒に働きたいか」という項目です。後述するプロジェクトの進め方にも通じますが、究極的にはプロジェクトを前進させるのはリーダーやメンバーのパッションや人柄だったりするので、相手の理解にあわせて丁寧・ロジカルにコミュニケーションができるかなどを見ます。

社交的であったりプレゼンがうまい必要は全くないですが、わかりやすく情熱を伝える力は求めます。

⇒ それでもわからない

それでもやっぱり短時間で見極めるのは難しいから、インターンシップで一緒に働いてみるのが一番というのが結論ではあります。 これはfreeeに限らないですが、短期でもいいからインターンに参加してお互いを知るのが、現状のベストプラクティスです。

新卒エンジニアに3年間で身につけてほしいこと

短いようで長い3年間で、ぜひ身につけてほしいのが、これからの時代の課題解決者に必要な次の2つのスキルです。

1. 顧客にどうやって価値が届くのか、細部まで想像できるようになること

想像できないものはつくれません。または、想像できる限りにおいてのみ最高のものをつくれます。疑似体験でもよいので、フルスクラッチでサービスを企画・開発・運用してみることをすすめます。チームでやってもいいですし、freeeでは他チームへの留学制度を利用できます。

Webサービスであれば、ユーザは何を思ってサービスを開くのか?画面をクリックしたら、裏側で具体的に何が起こってどうやって動いてるのか?あるいはどうやってユーザはサービスを知って、何が決め手でサービスを使ってくれたのか?そのユーザはうまくサービスを使えているのか?それをエンドツーエンドで細部まで想像できるようになることで、エンジニアの課題解決精度が飛躍的に伸びます。エンジニアの本分は課題解決だと思っていて、たくさんの解決オプションを持った上で、最善のオプションを選択できることが価値になります。とびきりのコードを書いても、その他のプロセスに大きなボトルネックがあれば、結果として顧客に価値は届きません。

何でも出来るようになれというわけではないですが、全体感を知った上で、自分の想像力の解像度が高いところ・低いところを常に意識してほしいと思います。

2. プロジェクトの成功体験を積み、よく振り返ること

成功するには失敗から学ぶことが重要とよく言われますが、それと同じくらい、成功体験そのものから得られる自信や勢いも重要です。 もちろん、プロジェクトの成功には様々な要素が絡むため、次につなげるためには自分やチームを客観的に見るための振り返りや セルフマネジメントも欠かせません。freeeでは、若手に成功体験を積んでもらうためのプロジェクトや、2週間に1回以上の 1on1など、メンバーの成長にコミットする環境を用意してます。

⇒ これらのスキルの意味

いまのfreeeの感覚では、これらのスキルを身につけるために3年間くらい必要かなというイメージです(逆に3年で出来ない人は5年、10年かけても出来る確率は低そう)。

そしてこれら2つのメタスキルは、プロダクト開発において役割を問わず応用が効くスキルでもあります。テックリード、アーキテクト、エンジニアマネージャ、プロダクトマネージャなど、役割によって高解像な想像力が求められる領域は異なりますが、課題解決者としての姿勢はすべて共通だと思います。

また、これらのスキルがきちんと身についていれば、たとえ立ち上げ期のスタートアップにCTOとして参画しても、どこでも活躍できてより大きく飛躍できると、スタートアップ界隈を見ていて思います。スモールチームのCTOというのは、「技術やエンジニアリングを軸に、プロジェクトの成功に大事なことなら何でもとことんやる」という役割の象徴だと思っていて、これらのスキルを身につけたら、freeeでは新しいサービスや事業はもちろん、さまざまなプロジェクトで次にチャレンジできる環境を用意していきます。

わたしが新卒3年目でfreeeの立ち上げに参画したときには、これらのスキルが十分でなくても腕一本でギリギリなんとかやってましたが、ある側面でものづくりのハードルが下がり、小さなサービスで大きな価値を届けやすくなってきている今の環境を考えると、これくらいのスキルがないと生き残るのは難しいなと感じてます。

なぜ新卒でfreeeを選ぶのか

さまざまな会社がある中で、なぜ新卒でfreeeを選ぶか?それは以下の2点から、エンジニアの本分としての本質的な課題解決スキルを最短で磨く環境があるからだと考えてます。

1. 世の中のリアルな社会問題解決がテーマ

freeeのターゲットである日本のスモールビジネスには、顕在化した課題が山積みです(低迷する生産性、労働力不足、後継者不在、黒字倒産...)。変わらなきゃいけないことはわかっているけど変わるのが遅れている業界に対して、どうやったら変えていけるか。やってみてうまくいかなければ、他にどういうオプションをとれるか。数打ってワンチャン当たればラッキーではなく、課題が解けるまで粘り強く頭をひねり手を動かし続けて社会問題を解く経験が、再現性があって市場価値の高いスキルにつながります。

2. スピードや世の中のベストプラクティスを重視

freeeは新しい技術もどんどん取り入れていくし、実際に創業から6年で7割の技術スタックはより最新のものに移行済みです。100人を超える開発組織で新しいプロダクトや機能を全速力で出しながらも、5年でRailsは3.2 -> 5.2へ。そしてバックエンドサービスはGoによるマイクロサービスへ、フロントエンドはCoffeeScriptから最新Babel on ES、Backbone.jsからReactへ、インフラはKubernetesへと全レイヤで技術転換が一巡してます。スピード感をもって技術で世の中を変えていくのがfreeeのアイデンティティだという風土や自負があります。

まとめ

エンジニアのキャリアの最終地点がCTOであるとは全く思っていませんが、これまで書いてきたようなことを総合して、これからの時代の一流の課題解決者のスタートラインとして、うちでは新卒エンジニアに3年でスモールチームのCTOになれという話をしてます。もちろん数人のチームでもCTOならばより高い視点や期待値を求められますが、少なくとも自信を持ってバッターボックスに立てると思います。

freeeは、若手にチャレンジさせる会社でありたい。

freeeは、これから社会に求められるエンジニアとして必要なスキルを最短で身につけてもらえる環境を用意していきます。

jobs.freee.co.jp

行ったことある?スクラム、アジャイル関連のイベント

こんにちは! saber_chica です。 この記事はfreee Developers Advent Calendar 2018 12/24(日付超えてしまいましたが...)の記事です。

本日はスクラム関連のイベントをご紹介したいと思います。 ラグビーの方ではないですよ!(完全に関係ないわけではないですが!)

なんで紹介するの?

スクラムは「複雑で変化の激しい問題に対応するためのフレームワークであり、可能な限り価値の高いプロダクトを生産的かつ創造的に届けるためのもの」とスクラムガイドで定義されています。 私自身はこれを、知り得ない未来の変化と付き合っていくために先人たちが考え抜いた、使わない手はないフレームワークだな〜と日頃考えています。

当然、スクラムやアジャイルが全ての課題を解決してくれるわけではありませんが、客観的に判断するための「現状」を著しくあらわにしてくれるもではあります。 その「現状」を把握した後に何かしらの対策を講じるのか、現状をそのままにするのかはそのプロダクトに関わる全ての人に委ねられるので大きく失敗もできるし、大きな改善にもつながる可能性を秘めています。

ただ、実際やり始めてみるとうまくいかないことが出てきたるするのですが、そんな時にうまくいったやり方を聞く機会としてイベントに参加することで、役に立つことが私自身あったのでご紹介して行きたいと思います!

Scrum Gathering Tokyo

https://2019.scrumgatheringtokyo.org/index.html

直近一番近くに開催される国内最大規模のスクラムのカンファレンスです。 参加者はスクラムの初心者からスクラム戦闘力の高いエキスパートまで、国内外から様々な立場の人々が集まるカンファレンスです。

講演だけでなくワークショップや参加者同士の交流もあり、スクラムを採用し始めてうまくいかないことをダイレクトに戦闘力の高い人たちに相談したり、意見やヒントをもらったりできる貴重な場です。私自身参加した時は、やる気を補充できる場ともなっていました。 そんな Scrum Gathering Tokyo ですが、今年のチケットは売り切れています。 次回行きたいと感じた人は、人気が高くチケットはすぐに売り切れることもしばしばあるので早め(10月ごろにアナウンスされることが多いのでその時期)に購入しておく方が良いです。

Agile Japan

https://www.agilejapan.org/

こちらのカンファレンスも非常に大きなイベントです。

Scrum Gathering Tokyo との違いはスクラム以外のアジャイルに関わる内容が幅広く(Scrum Gathering Tokyo も幅広いのですが!)含まれている点だと感じています。 あとは、Scrum Gathering Tokyo よりもエンタープライズ感が強めかな〜と感じるところも違う点です。 大きな組織でアジャイルな組織を作っていこうとしているときに悩めることがある時は刺さる講演が多くあるかと思います。

Scrum Masters Night!

https://smn.connpass.com/

2~4ヶ月に1回開催されていて、経験豊かなスクラム戦闘力の高い人たちも参加者の中にゴロゴロいて、悩みや困りごとがある人が参加すると得るものが非常に大きいです。 最近参加した際はOST(Open Space Technology)というやり方で進行されていて参加型のイベントです。

OSTとは参加者が熱い想いをもって話し合いたいテーマを持ち寄り、ファシリテーターが中心となりオープンな話し合いしてテーマを決めていくやり方です。 各テーマが決まったら、興味のあるテーマごとに別れディスカッションが始まっていきます。 私自身、スクラムでやっている時もやっていない時も開発プロセスに関わる部分で疑問や腑に落ちない点などが出てきた時はここに行ってヒントを得たりすることがよくあります。

アジャイル・ディスカッション!!!

https://agile-discussion.doorkeeper.jp/

こちらのイベントもOST(Open Space Technology)形式で開催されていました。 Scrum Masters Night!よりも開催頻度が高く、参加しやすいです。 Scrum Masters Night!と同じく、開発現場でコーチをされている人たちがいるので、悩ましいことなどがある時に参加すると突き刺さるものがあります。 私自身悩ましいことがある時は、参加してするようにしているコミュニティです。

アジャイルひよこクラブ

https://agile-hiyoko-club.connpass.com/

こちらのイベントは私自身はじめて参加したアジャイル関連のコミュニティで、非常に参加しやすく感じ参加していくうちに、今では私も運営に関わっています。 開催頻度は2ヶ月に1回です。

初心者を対象としているコミュニティで参加者も始めたばかりという人やこれから初めてみたいという人も多くいます。 悩み相談をメインコンテンツとしているので、悩みがあるが初心者すぎる悩みなんじゃないかと不安に思いながらも相談相手が欲しい時にはちょうど良いかと思います。

終わりに

気になるイベントがあった方はぜひ行けるタイミングで行ってみてください! なかった方、こんなのもあるよ!というのをご存知な方はぜひ教えてください。喜びます。

今回は私が参加したことのあるイベントにとどめましたが、プロダクトオーナーなら是非行って欲しいイベントもあるので気になる方がいたらどこかで紹介しようと思います。

最後まで読んでくださりありがとうございました!

明日はいよいよ最終日!とても楽しみです!

Goでスタックトレースを構造化して取り扱う

はじめましてfreee株式会社でソフトウェアエンジニアをしているbudougumi0617ことしみっちょです。普段はGoによるアカウントアグリゲーション機能(銀行同期基盤)の開発に携わっています。

この記事はfreee Developers Advent Calendar 2018 の23日目の記事です。

12月23日と言えば国民の祝日、Perfumeかしゆかさんの誕生日ですね。おめでとうございます。 今回は私が先日参加したPerfume 7th Tour 2018 「FUTURE POP」の感想を書こうと思ったのですが、ツアーが終わるまでネタバレは禁止なのでGoの話をすることにしました。

この記事では私が携わっているプロダクトで行なっているエラーハンドリングの一部とGoでスタックトレースを構造化に扱う方法について紹介します。

なお、文中で利用しているソースコードや出力結果は以下のサンプルリポジトリに用意したコードを利用しています。

github.com

Goのエラーハンドリングについて

Goの標準パッケージから生成されるエラーはそのまま愚直に利用するとエラーメッセージしか持っていない非常なシンプルな情報しか得られません。 プロダクトで利用する上ではスタックトレースやSeverityの仕組みがほしいですね。各社・各人が多様なアプローチをとっており、2018年12月のアドベントカレンダーや勉強会だけでもエラーの扱い方について以下のような投稿・発表がなされています(漏れていたらごめんなさい)。

私のGoのプロダクトではpkg/errorsをラップした独自errorsパッケージを利用しています。

github.com

pkg/errorsを利用すると、エラー情報にスタックトレースをエラー情報に含めることが可能です。 以下はpkg/errors.New関数から生成したエラーをfmt.Printf("%+v",err)と出力した結果に含まれているスタックトレースの引用です。

github.com/budougumi0617/errors-example/errors.NewByPkg
        /Users/budougumi0617/go/src/github.com/budougumi0617/errors-example/errors/errors.go:14
github.com/budougumi0617/errors-example/root/sub.(*Sub).ReturnErrorByPkg
        /Users/budougumi0617/go/src/github.com/budougumi0617/errors-example/root/sub/sub.go:18
github.com/budougumi0617/errors-example/root.secondFuncWithPkg
        /Users/budougumi0617/go/src/github.com/budougumi0617/errors-example/root/root.go:24
github.com/budougumi0617/errors-example/root.FirstFuncWithPkg
        /Users/budougumi0617/go/src/github.com/budougumi0617/errors-example/root/root.go:19
main.main
        /Users/budougumi0617/go/src/github.com/budougumi0617/errors-example/main.go:14
runtime.main
        /usr/local/opt/go/libexec/src/runtime/proc.go:201
runtime.goexit
        /usr/local/opt/go/libexec/src/runtime/asm_amd64.s:1333

呼び出し元の関数名や呼び出された場所のファイル行数などのスタックフレームを出力しているのがわかります。 上記のようにpkg/errorsを使うだけでスタックトレースをエラー情報に含めることができるようになるのですが、私のプロダクトの独自エラー型ではスタックトレースの生成を一部独自に行なっています。

なぜ独自にスタックトレース情報を取り扱うのか?

pkg/errorsで出来る以上ほとんど車輪の再発明なのですが、スタックトレースの取得を独自にすることの利点は、エラートラッキングサービスに加工後のスタックトレース情報を送信できることです。 私が担当しているプロダクトでは、BugSnagを利用してエラー情報のトラッキングをしています。BugSnagはSentryなどと同様のエラートラッキングサービスです。

www.bugsnag.com

そして、私が担当しているGoのプロダクトはos/exec.Cmd経由でRubyのスクリプトに一部の処理を移譲しています。Rubyスクリプトのスタックフレームについては当然Goのランタイムからは取得できません。 そのため、Rubyスクリプトの実行時に例外が発生したとき、Rubyのスタックトレースのフレーム情報ををGoのスタックトレースに追加してからBugSnagに送信しています*1 。ではどのようにすればGoのスタックトレースを操作することが可能なのでしょうか。

Goでスタックトレース(スタックフレーム)を構造化して操作する

まず、pkg/errorsはどのようにスタックトレースを取得しているのでしょうか。pkg/errorserrorを生成するerrors.Newなどは以下のような実装をしています。

https://github.com/pkg/errors/blob/2233dee583dcf88f3c8b22cb7a33f05a499800d8/errors.go#L100-L107

// New returns an error with the supplied message.
// New also records the stack trace at the point it was called.
func New(message string) error {
    return &fundamental{
        msg:   message,
        stack: callers(),
    }
}

このstackフィールドに入っている情報がスタックトレースの情報なのですが、実際に格納するオブジェクトを返しているcallers関数の実装は以下です。

https://github.com/pkg/errors/blob/059132a15dd08d6704c67711dae0cf35ab991756/stack.go#L133-L139

func callers() *stack {
    const depth = 32
    var pcs [depth]uintptr
    n := runtime.Callers(3, pcs[:])
    var st stack = pcs[0:n]
    return &st
}

この中のruntime.Callers関数で取得した[]uintptrがスタックトレースの各フレーム情報になります。pkg/errorsでは独自型のpkg/errors.Frame(type Frame uintptr)に格納して情報を取得しています。
なお、Go1.7以降ではruntimeパッケージ内にスタックトレース情報を模すruntime.Framesと、runtime.Callers関数で取得したuintptrをからFramesを取得するruntime.CallersFrames関数が追加されているのですが、こちらを利用すると加工が少し面倒なため、今回は利用しません。 (runtime.CallersFramesの利用方法はruntimeパッケージのExampleをご覧ください。)

本稿では以下のような独自型を定義します。

// Frame はスタックフレームを表現する独自構造体
type Frame struct {
    // File はそのスタックが発生した開始されたファイル
    File string
    // LineNumber はそのスタックが開始されたファイル行数
    LineNumber int
    // Name はそのスタックが開始された関数・メソッド名
    Name string
    // ProgramCounter は元データ
    ProgramCounter uintptr
}

そしてこのFrame構造体にruntime.Func構造体を介して各スタックフレーム情報を格納してきます。*runtime.Funcオブジェクトはruntime.Callersで取得したスタックフレーム情報のuintptrからruntime.FuncForPC関数を使って取得できます。*runtime.Funcオブジェクトを介してスタックフレーム情報を[]Frameに格納し直すコードが以下になります。

func callers() []uintptr {
    const depth = 32
    var pcs [depth]uintptr
    n := runtime.Callers(3, pcs[:])
    return pcs[0 : n-2]
}

// NewFrame はスタックトレースの各スタックフレームをFrameに格納する
func NewFrame(pcs []uintptr) []Frame {
    frames := []Frame{}

    for _, pc := range pcs {
        frame := Frame{ProgramCounter: pc}
        fn := runtime.FuncForPC(pc)
        if fn == nil {
            return frames
        }
        frame.Name = trimPkgName(fn)

        frame.File, frame.LineNumber = fn.FileLine(pc - 1)
        frames = append(frames, frame)
    }
    return frames
}

// package名を取り除く
func trimPkgName(fn *runtime.Func) string {
    name := fn.Name()
    if ld := strings.LastIndex(name, "."); ld >= 0 {
        name = name[ld+1:]
    }
    return name
}

あとはerrorインターフェースを満たす構造体に[]Frameを保持しておけば、スタックトレース情報を含んだ独自errorの定義の完成です(fmt.Stringerfmt.Formatterインターフェースも実装しておくとよいでしょう)。 次の出力は大雑把にError()メソッドを実装したMyErrorの出力です。

大雑把にスタックトレースを出力する独自error:
Cause: custom error
Trace:
/Users/budougumi0617/go/src/github.com/budougumi0617/errors-example/root/sub/sub.go:15 ReturnError
/Users/budougumi0617/go/src/github.com/budougumi0617/errors-example/root/root.go:16 secondFunc
/Users/budougumi0617/go/src/github.com/budougumi0617/errors-example/root/root.go:11 FirstFunc
/Users/budougumi0617/go/src/github.com/budougumi0617/errors-example/main.go:12 main

私のプロダクトでは[]Frameに更にRubyから取得したスタックトレース情報を付与したりしています。

今回は独自定義のFrame型を用意しましたが、例えばBugSnagを利用している場合、bugsnag-go/errors.StackFrame構造体にフレーム情報を格納し、bugsnag-go/errors.ErrorWithStackFramesインターフェースを実装すれば「細工したスタックトレース」をBugSnagのWebビューに表示することもできます。

// bugsnag-go/errors.StackFrame の定義
type StackFrame struct {
    File           string
    LineNumber     int
    Name           string
    Package        string
    ProgramCounter uintptr
}

// bugsnag-go/errors.ErrorWithStackFrame の定義
type ErrorWithStackFrames interface {
    Error() string
    StackFrames() []StackFrame
}

終わりに

今回はGoでエラーハンドリングを行なう際に自前でスタックトレースを取り扱う方法を紹介しました。

最後にfreeeでは一緒に働けるGopherを募集中です。 www.wantedly.com

明日は人事労務とスクラムに精通するエンジニア、そーださんです。お楽しみに。

*1:もちろん、Rubyスクリプト側からStderrPipe経由などで情報をGoに送り込む「仕込み」は必要です。

システム障害のおわびとまなび

はじめに

こんにちは、freee株式会社でCDO(最高開発責任者)をしている平栗です。 2018年10月31日に、freeeで起こしてしまったシステム障害について、その原因と対策、障害からの学びについて共有したいと思います。 この記事はfreee Developers Advent Calendarの22日目になります。

おわび

まず、約2時間半にわたりfreeeの全サービスを停止し、皆様に多大なるご迷惑をおかけしましたことを、改めてお詫び申し上げます。 今回の障害を大きな学びと成長の機会とし、今後の再発防止と業務改善に取り組んでまいります。

障害の経緯

2018年10月31日12時34分~15時00分の2時間26分の間、freeeの全サービスを一時停止し、すべてのサービスがご利用できなくなりました。 以下、復旧までの経緯です。

  • 11時24分 特定の機能が利用できなくなっていると、社内から不具合報告があがり、エンジニアチームで調査を開始
  • 11時30分 サポートチームからも、お客様から問い合わせがきていると共有
  • 11時56分 多くのリリースフラグ(リリースフラグについては後述)がオフになっていることに気づく
  • 12時34分 リリースフラグオフが原因と判断し、ただちに影響範囲を把握することが困難であったため、お客様のデータ保護のため全サービスを一時停止
  • 15時00分 影響範囲調査の結果、お客様のデータの毀損・漏洩がないことを確認し、サービスを再開

お客様のデータへの影響

お客様のデータ保護の観点からサービスを停止しましたが、変更されたリリースフラグの影響をすべて調査した結果、お客様のデータの漏洩・毀損等は一切ございませんでした。

リリースフラグとは

リリースフラグとは、システムの機能オン/オフを機動的に切り替えるための仕組みで、主に新機能のリリース管理に利用しています。 リリース直後の機能について不具合があった場合に、再度デプロイ作業をすることなく、システムの管理画面上から機動的にフラグをオフにして即座に切り戻すことができます。 基本的には、新しい機能が使えるかどうか以外システムへの影響はなく、リリースフラグはオンでもオフでもシステムは正常に作動するように設計されています。

障害の原因

システム障害の最初の原因は、リリースフラグ変更作業上のオペレーションミスでした。それにより意図せず多くのリリースフラグがオフになってしまい、お客様のデータへの影響がないと即座に判断することができず、データ保護を最優先に考え、サービスを一時停止しました。

原因の深掘りと対策

なぜオペレーションミスが起こったか

前述の通り、リリースフラグ自体は、本来オンでもオフでもシステムは正常に動作するように設計されています。リリースの切り戻しをより素早く行えるよう、システムの管理画面上で簡単に変更できるようにしていました。

具体的には、管理画面上ではリリースフラグが一覧表示になっており、フラグを変更するボタンをクリックして確認するだけで、変更可能でした。 意図したリリースフラグと一行ずれたものを押してしまうミスが起こりやすい状態になっていました。 対策として、一覧画面での変更をやめ、個別のリリースフラグの画面に遷移した上で変更をするようにし、変更する際にはリリースフラグの名前の入力をするようにしました。

オペレーション上も、リリースフラグの誤操作によるリスクは低いと考えていたので、作業時の確認を徹底するフローができていませんでした。 これに関しては、フォーム画面に必要事項を入力すると、作業上の注意項目がのった手順書が自動で作成される仕組みをつくり、月次モニタリング運用を行うことにしました。 また、手順書をつくる対象作業の見直しとその結果の周知も実施しました。

リリースフラグ自体は、今後も素早い切り戻しのために必要だと考えていて、その中でいかにミスの起こりにくい仕組みを実現できるかが難しいポイントだと思っています。 今後運用していく中で、リスクを適切に評価しながら改善していきたいと思っています。

なぜ影響範囲を即座に判断できなかったか

繰り返しになりますが、リリースフラグ自体は、本来オンでもオフでもシステムは正常に動作するように設計されています。それなのに影響範囲をすぐに判断できなかったのは、以下の3点がネックになったからでした。

1点目は、意図せずオフになったフラグが多かったことです。数が多いことで、すぐに影響範囲を正確に判断することが難しくなっていました。

2点目は、どのフラグがオフになったのかすぐにわからなかったことです。リリースフラグの変更はリスクが低いと考えていたこと、頻繁に操作するものではなかったことから、どのフラグがいつ・どう変更されたのかが、管理画面上ですぐわかるようになっていませんでした。activity_logや、直前のDBの状態を調べる必要がありました。

3点目は、古いリリースフラグが残っていたことでした。リリースフラグは主にリリース直後の切り戻しに使うのですが、リリース後経過観察をした上で削除する場合もあり、そのまま消されずに残っていたものが多くありました。そして負債化し、そのフラグの影響範囲を読むことが難しくなっていました。

対策として、1点目については、リリースフラグの大掃除大会を実施し、リリースフラグの数を大幅に減らすことにしました。数年間の負債が溜まり続けた結果、簡単に終わる作業ではないですが、障害を契機にエンジニアチーム全体で取り組んでいます。

2点目については、リリースフラグが変更されたときに、変更ログを自動的に社内SNS (slack) に飛ばす仕組みを作り、変更履歴をすぐに追えるようにしました。

3点目については、リリースフラグの管理体制を作ることにしました。リリースフラグの目的、残存期間の目安、期間を超過した場合のアクション、残存させ続ける条件等を定め、月次モニタリング運用に組み込むことにしました。

障害からのまなび

障害からの大きな学びは、開発上のリスクをサービスの成長とともに適切に評価しなおしていくことの大事さと難しさでした。 今回のシステム障害の原因となったリリースフラグでは、使いだした当初は障害につながるリスクは低いと思っていましたが、数年たつうちに、あっという間にリリースフラグもそれを扱うエンジニアも大きく増え、潜在的にリスクが増していました。 リリースフラグだけでなく、サービス全体としても、ユーザーさんの数、ユーザーさんの質、扱うデータの性質がどんどん変化していく中で、それらの変化を適切に捉えた上で、開発全体のリスク評価をしなければいけません。

10月31日のシステム障害から2ヶ月がたち、障害の原因分析とすぐに行う対策の完了に目処がついてきたので、来月からは改めて開発全体のリスクの再評価に取り組む予定です。 社内においてこれまで見落としてきたリスクがなぜ見落とされたのかを分解整理しつつ、社外の知見も借りながら、年単位で開発プロセスを振り返り、リスクを最小化していく努力を続けていきたいと思っています。

最後に

重ねて、利用者の皆様にシステム障害によりご迷惑、ご心配をお掛けしましたことをお詫び申し上げるとともに、お客様への価値を安全に、かつ最大最速で届けられるようfreeeの開発体制を進化させ続けていきたいと思います。

つきましては、freeeの開発基盤を進化させていくエンジニアを募集しております。 興味があれば、ぜひご連絡ください!

jobs.jobvite.com

明日は、freeeの銀行同期基盤を支えるgoエンジニア、しみっちょです!