食べチョク開発者ブログ

食べチョクエンジニアによるプロダクト開発ブログ

RubyKaigi 2022で"Mie Food Sponsor"をします。持ってけ楽しめ三重食材!

こんにちは。プロダクト開発チームの Engineering Manager 兼 人事の @hirashun です。

利用率No.1の産直通販サイト「食べチョク」を開発/運用するビビッドガーデンではリリース当初からバックエンドをすべてRubyで開発しており、ユーザーに迅速なサービス提供ができる恩恵を最大限にあずかっております。

この度RubyKaigiが3年ぶりのオフライン開催@三重県ということで、Rubyコミュニティーに何か恩返しは出来ないことか・・・と考えた結果、
ビビッドガーデンは"Mie Food Sponsor"として三重のこだわり食材を携えて協賛することにしました!

こだわりをもってRubyで開発を続けるみなさんに、三重の生産者の方々のこだわりをたっぷりお届けします!是非RubyKaigiで三重の魅力もお楽しみください。

三重のこだわりお届けします

オフライン参加の方だけでなく、オンライン参加の方にも楽しんでいただけるように、以下の2つのスポンサー内容を準備しました。

  • 日替わりで三重の食材を会場にて配布
  • 三重の特産品を抽選で毎日5名の方にプレゼント

それぞれ説明していきます!

日替わりで三重の食材を会場にて配布

「せっかく三重に来たんやで、〇〇食べてみ!」と言いたいところですが、昨今の情勢もありなかなか手軽に食べ物を提供することが難しくなってしまいました。
しかしそれでも生産者の方々のこだわりをRuby開発者に届けたい!そんな想いで「個包装」「その場で味わえる」「持ち帰り可能」という条件を満たす三重食材を厳選しました。

会場にて配布します。こちらがその一部です!

疲れた脳に染み渡る「無添加・無加工の天然はちみつ」

image.png (236.0 kB)

脳の活性化に効果があるブドウ糖・果糖・ビタミン類・ミネラルを多数配合し、消化吸収に手間がかからずダイレクトに体のエネルギー源になるはちみつが連日Ruby尽くしの皆様を強くサポートします。

採れたままのはちみつに一切加工を施さず瓶詰めすることで、風味や栄養価をそこなわず、はちみつ本来の魅力を楽しんでいただけます。

ぜひ一度、無添加・無加工の天然はちみつを味わってみてください。

舘養蜂場本店

こだわり抜いた味と香りでリラックス「希少かぶせ茶」

image.png (185.3 kB)

かぶせ茶生産量日本一の三重県。旨味・甘味成分が増加し、香りも良く深緑色でとても鮮やかな希少なかぶせ茶が一日の疲れを癒やすリラックスタイムを演出します。

新芽の本数を意図的に減らし、新芽1本当たりの栄養分が豊富になっています。 お茶の摘み取り2週間前に黒い覆いを被せていて、旨味・甘味が増加し、まろやかな味わいになります。手間はかかりますが、おいしいお茶作りのためには欠かせない作業です。

新芽のすべての部位(茎、芽、粉)が入った本物のお茶(荒茶)をお届けします。

かぶせ茶ファーム

私も欲しいです。
全員分は準備出来ませんでしたが、そこそこの数を準備しています。ぜひ楽しみにしていてください!

詳細の配布方法は追ってTwitterの「食べチョク開発部」アカウントにてご案内します。

三重県の特産品を抽選で毎日5名の方にプレゼント

その場ではお渡しが難しいけどこれだけは味わってほしいという三重の逸品をいずれか1品プレゼントします。
オンラインの方にもお届けできればと思い、今回はTwitterで応募、その後当選された方に食べチョクを通じて生産者さんより直接発送します!

一例を載せますが・・・

三重県南伊勢町のブランド真鯛『あなたに逢い鯛。』
肉の芸術・松阪牛

おわかりいただけただろうか。私も欲しいです。

3日間、毎日5名の方に当たります!ぜひご応募いただければと思います。
応募方法は「食べチョク開発部アカウントのフォロー」と「会期中に #rubykaigi と #食べチョク を付けてツイート」です。

twitter.com

RubyKaigiの感想、欲しい食材等、思い思いのつぶやきをお待ちしております。
※当選された方には食べチョク開発部アカウントよりDMをお送りさせていただきます
※対象者はRubyKaigiに参加している方とさせていただきます

CTO西尾より

先日CTOに就任した @nishio_dens のコメントです。

このたび「RubyKaigi 2022」にフードスポンサーとして協賛できることを、大変嬉しく思っております。

ビビッドガーデン(食べチョク)は、サービス開始当初からRubyを用いて開発を続けてきました。Rubyは私たちのようなスタートアップには非常に相性が良い言語です。
素早くサービスを立ち上げられること、直感的にコードを記述できること、開発に必要なライブラリやツールがひと通り揃っていることが魅力的な言語です。

私自身、エンジニアとしてのキャリアの多くをRubyとともに過ごしてきました。
Rubyは他のプログラミング言語に比べて自由度が高い言語です。この自由さが魅力であり、コードを書いていて楽しい気持ちになれる言語だと思っています。
複数のプログラミング言語を使ってこれまで開発をしてきましたが、私はRubyが一番好きな言語であると自信を持って言えます。

長い間お世話になっているRubyとRubyコミュニティーに貢献できることは感慨深いものがあります。
今回の「RubyKaigi 2022」はオフライン開催も予定されており、三重県の生産者のこだわり食材を会場にて配布いたします。

皆様とお会いできることを楽しみにしております。

prtimes.jp

おわりに

今まで様々な都市での開催を重ねるたびに、RubyKaigi運営の方々の「その都市ごと楽しんでいけよ」というメッセージを感じずにはいられませんでした。
この度、参加される皆様に三重のこだわりが詰まった食材を楽しんでいただくと共に、このような形でRubyKaigiのお手伝いができることを嬉しく思います。

RubyKaigiが終わった後も、三重の生産者の方々が作ったこだわり食材に思いを馳せていただけると幸甚です。
気になった方がいましたら是非三重の食材を覗いていってください。
https://www.tabechoku.com/areas/mie

ビビッドガーデンは「生産者のこだわりが正当に評価される世界へ」というビジョンのもと、一次産業の課題解決を推し進める会社です。
引き続きRubyをはじめとした技術を活用しながら、日本中の生産者の方々のこだわりがより多くの人に伝わるように、プロダクトを改善し続けようと思います。

ビビッドガーデンのことが気になりましたらこちらも是非ご覧くださいませ!
https://note.com/vividgarden_corp/

それでは皆様、当日スポンサーブースもしくはTwitterにてお会いしましょう!

食べチョクの自動テスト実行速度を2倍以上速くした

皆さんこんにちは、エンジニアの久保金田一です。

今回は、食べチョクの自動テスト改善チームが取り組んでいるテスト実行速度の改善についてお話しいたします。

自動テスト改善チームとは何か?について知りたい方は、以下のエントリーをご覧ください。

食べチョクにおける自動テストの現状

食べチョクでは、GitHub Actions のワークフローを使って、push をトリガーに自動テストを実行しています。食べチョクのリポジトリには System Spec までを含んでいて、E2E は別リポジトリとなっています。

メンバーが増えて体制が整ってきたこともあり、この 1 年くらいに実装した機能についてはテストがしっかり書かれていることが多いです。一方で、それ以上前に作られた機能については、まだまだカバレッジが低い箇所もあり、みんなで手分けして少しずつテストを追加しています。

不足していたテストの追加だけではなく、新しい機能の開発でもテストは増えていきます。これらのテスト追加に伴い出てきた課題がテスト実行時間の増加です。徐々にテストの実行時間が伸び、結果として CI 全体の時間が伸びてしまい、開発者体験が悪くなりつつあると感じ始めていました。具体的には、push してから merge するまで時間がかかる、push 後しばらく別の作業を実施してからテストの失敗を検知して前の作業に戻るので非効率、などの意見がありました。

このままでは開発効率が悪くなる一方なので、自動テストの速度改善をしよう!ということになりました。

CI 上でのテスト実行時間の計測と観察

単にテストが遅いと言っても、どこに問題があるかを明確にしないとどういった改善が有効か判断することができません。まずはボトルネックを突き止めるために、CI 上でのテスト実行時間の計測と観察をしました。

テスト実行時間をランダムで 20 回分集計してみたところ、最短で 11 分 44 秒、最長で 16 分 3 秒、平均で 13 分 17 秒かかっていました。

rspec_execution_time_before
改善前のRSpec実行時間

この全体の実行時間を「テスト自体の実行時間」と「ファイルの読み込み時間」に分けて確認したところ、「ファイルの読み込み時間」が全体の 30% 前後を占めていることがわかりました。 例えば、全体の実行時間が約 14 分だった場合では、「テスト自体の実行時間」が約 9 分 36 秒、「ファイルの読み込み時間」が 4 分 30 秒といった具合です。

Finished in 9 minutes 36 seconds (files took 4 minutes 29.8 seconds to load)
4846 examples, 0 failures, 15 pending

次に「テスト自体の実行時間」をさらに分析するために、実行が遅いテスト TOP30 を抽出してみました。

$ bundle exec rspec --profile 30

その結果、1 秒以上かかる example が 6 つあり、最も遅いテストでは 8.4 秒かかっていました。この 6 つテストだけで約 25 秒かかっており、改善の余地がありそうでした。

ただし、6 つのうち 4 つは System Spec で、この 4 つについては時間がかかるのも仕方がない部分もあります。

また、他のテストの実行時間は 1 秒未満だったので、全体の実行時間からすると 1 つ 1 つのテストを少しずつ改善しても大幅な改善は見込めませんでした。

ここまでの分析から次のことがわかりました。

  • 改善には「テストの実行時間の改善」と「ファイルの読み込み時間の改善」の 2 つの軸がありそう
  • 1 つ 1 つのテストの改善は、最初に取り組むべき改善策ではなさそう

速くするための改善策

実行時間の計測と観察の結果から、「テストの実行時間の改善」と「ファイルの読み込み時間の改善」の 2 つの軸が見えてきました。

まず「テストの実行時間の改善」については、テストを並列実行するという案が出ました。他社事例などを調べた結果、食べチョクでも並列実行することで大幅な時間短縮が見込めそうだったため、チャレンジしてみることにしました。

次に「ファイルの読み込み時間の改善」についてです。これは意図せず解決に至ったのですが、Ruby 3.0 系へのバージョンアップが有効でした。Ruby 2.7.2 から 3.0.4 にバージョンアップした結果、ファイルの読み込み時間が約 4 分 30 秒から約 60 秒まで短縮されました。

以上のことから、テスト実行速度の改善策としては「テストの並列実行」に取り組むことになりました。

テストの並列実行

テストの並列化は、主に以下のような実装をしました。

  • フロントエンドのテスト(Jest)とバックエンドのテスト(RSpec)の実行の分離
  • RSpec の実行を 10 個に並列化

Jest と RSpec の実行の分離は、ジョブを分けて並列実行にしただけなので、ここでは RSpec の実行を 10 個に並列化した実装について解説していきます。 以下は、RSpec 実行のジョブと、並列化をするためのファイル分割スクリプトです。

  backend:
    runs-on: ubuntu-latest
    needs:
      - backend-prepare
    timeout-minutes: 10
    strategy:
      fail-fast: false
      matrix:
        NODE_INDEX: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    env:
      NUMBER_OF_NODES: 10
    steps:
      ...buildステップ...
      - name: Exec RSpec
        run: |
          SPEC_FILES=$(./bin/rspec_splitter.rb --glob 'spec/**/*_spec.rb' --node-count "${NUMBER_OF_NODES}" --node-index "${MATRIX_NODE_INDEX}")
          bundle exec rspec --format progress --format RSpec::Github::Formatter --format RspecJunitFormatter --out "tmp/junit-rspec-${MATRIX_NODE_INDEX}.xml" ${SPEC_FILES}
        env:
          MATRIX_NODE_INDEX: ${{ matrix.NODE_INDEX }}
          ...その他のenv...
      - name: Rename coverage result
        ...カバレッジを送信するステップ...

rspec_splitter.rb

#!/usr/bin/env ruby
require "optparse"
options = ARGV.getopts("", *%w[glob: node-index: node-count:])
glob_pattern = options["glob"]
$node_count = options["node-count"].to_i
node_index = options["node-index"].to_i
SpecFile = Struct.new(:path, :index, keyword_init: true) do
  def group
    index % $node_count
  end
end
files = Dir[glob_pattern]
  .sort_by { |path| [File.size(path), path] }
  .reverse_each
  .with_index
  .map { |path, index| SpecFile.new(path: path, index: index) }
puts files.select { |f| f.group == node_index }.map(&:path)

まず、GitHub Actions 上で、ジョブの並列実行をするためには、Matrix Strategyを使用します。例えば、複数の OS や言語などのバージョンの組み合わせに対する Job を記述することが出来るとても便利な機能です。

ここでは、NODE_INDEX という変数にそれぞれ 0 から 9 までの値を保持しておきます。NODE_INDEX のみ定義しているので、ここで 10 個のジョブが生成されることになります。 fail-fastを false にしておくと、ジョブが fail しても他のジョブの処理が継続します。

次に、RSpec の分割実行です。テストファイルを分割し、各ジョブで実行するようにしました。テストファイルのサイズを取得し、並列にした時の実行時間が平準化するようにしています。

具体的には、テストファイルのサイズ比較で降順にした配列に対して、ファイルパスと配列のインデックスを持ったオブジェクトを作成します。配列のインデックス % node_count と NODE_INDEX を比較し、一致した NODE_INDEX のジョブ番号で実行するという実装にしています。

結果

テストの並列実行を実現した後に、再度テストの実行時間をランダムで 20 回分集計してみたところ、最短で 3 分 8 秒、最長で 7 分 46 秒、平均で 4 分 45 秒という結果になりました。 改善前の時点では、最短が 11 分 44 秒、最長が 16 分 3 秒、平均が 13 分 17 秒だったので、以前の半分以下の時間でテストを実行できるようになりました。

rspec_execution_time_after
改善後のRSpec実行時間

さらなる改善に向けて

テストの並列実行によりテストの実行速度は向上し、一旦は開発者体験も良くなりました。

しかしながら、プロダクトは日々成長し、機能もテストも追加されていきます。また、既存のテストに改善の余地があることも認識しています。

よって、テスト実行速度の改善についてはこれで終わりではなく、今後は以下のような地道な改善にも取り組んでいくつもりです。

  • 並列実行のチューニング
    • 今回並列実行は実現したが、並列数の調整や実行時間単位で分割するなどで効率を up できる可能性がある
  • テストの重複排除
    • どのレイヤーで何をテストするかなどを整理して、無駄に複数のレイヤーで同じテストをしないようにする
    • ロジックのテストを Request Spec のような高コストなレイヤーでやらない(必要な場合を除く)
  • 作成するテストデータを削減する
    • DB アクセスが関係ない部分のテストでは、FactoryBot で create せずに buildbuild_stubbedを使う
    • テストに関係ないデータがafter(:create)でたくさん作られがちなので、作らないようにする
    • 1 example につき 1 expect で書くとわかりやすいが、DB レコードが必要なテストは毎回レコード作成することになるので、RSpec の aggregate failures を活用して example を統合する

最後に

食べチョクでは仲間を募集しています。 ご興味がある方は是非、RECRUITからご応募ください。

RSpec実行時のレポート情報をクエリで可視化する

どうもはじめまして。 muryoimpl です。

前回のエントリ 食べチョクの自動テスト改善活動 〜これまでとこれから〜 で、自動テスト改善チームが発足したことを書きましたが、今回はその活動の中で実施した、RSpec による自動テストのカバレッジのデータ収集の自動化と、そのデータを利用した可視化について書きたいと思います。

これまではどう可視化していたか

食べチョクは Ruby on Rails で動いており、バックエンドの自動テストは RSpec を使って書いています。

テストカバレッジは定番の SimpleCov で計測して結果を HTML に出力し、テストケースごとの実行情報は RSpec JUnit Formatter を使って XML として出力して、GitHub Actions でそれらの情報を Code Climate に送信していました。

また、可視化という点では、以前ビビッドガーデン Advent Calendar 2021 で私が書いたエントリの画像のように、テストカバレッジを Google スプレッドシートに記録してグラフ化し、変化が見れるようにしていました。

これまでも現状のテストカバレッジは確認できてはいたのですが、自動テスト改善チームが発足したときのミーティングで以下のような意見が出て、改善することにしました。

  • テストカバレッジをクエリで見えるようにしたい
    • クエリを書いてデータを取り出せるようにしておけばいろいろな分析ができる
    • テストの足りていない箇所の洗い出しが誰でもできる
  • 手作業で記録するのは飽きた
    • 属人性や反映タイミングもまちまち
    • 自動化していつでも誰でも確認できるようにしたい

Redash で可視化できるようにデータを取り込めるようにする

食べチョクでは Redash を既に利用しているので、ここにデータを投入さえできれば後はクエリを書いてグラフ化するのも組み合わせてダッシュボード化するのも自由にできます。ではデータをどう準備するかということになるのですが、以下の戦略を採用することにしました。

  1. GitHub Actions でテストが実行されたら、SimpleCov で算出したテストカバレッジの情報をCSV化し、BigQuery に送信する
  2. GitHub Actions でテストが実行されたら、RSpec JUnit Formatter で出力した XML からテストの実行時間情報を取り出してCSV化し、これを BigQuery に送信する
  3. BigQuery のデータを Redash と連携する

3 については、BigQuery をデータソースとして Redash で扱う仕組みが既にあるので、1 と 2 の仕組みを用意すればよいことになります。

1 と 2 についても、元となる情報は既にあるので、必要な情報を読み出して加工し、CSV にするだけです。

1. SimpleCov が収集したテストカバレッジを CSV 化する

SimpleCov には収集したカバレッジ情報を出力する Formatter の仕組みが用意されています。この Formatter を独自に用意して CSV を出力します。

今回はテスト関連ということで spec/support 以下に simpelcov_bq_formatter.rb というファイル名で Formatter を配置しています。

format メソッドが SimpleCov から受け取る引数の result には、全体、ファイル毎、グループ毎にまとめたテストカバレッジの情報が詰まっています。これを利用して CSV を作ります。今回は全体のテストカバレッジ情報と、各ファイル毎のテストカバレッジ情報をそれぞれ出すことにしたので、全体のほうのファイル名は "All Files" という名前にしてレコードを作っています。

require "csv"

module SimpleCov
  module Formatter
    class BqFormatter
      ALL_FILES_NAME = "All Files".freeze

      def format(result)
        job_id = ENV["JOB_ID"]
        created_at = bq_datetime_format(result.created_at)

        CSV.open(File.join(output_path, "coverages_for_bq.csv"), "wb") do |csv|
          # 形式 (covered% は (lines covered / relevant lines * 100).round(2) )
          # job_id,branch name,created_at,group_name,filename,covered%,lines,relevant lines,lines covered,lines missed

          # 全体の結果 は一行として出力する
          csv << [
            job_id,
            branch_name,
            created_at,
            "All Files",
            "All Files",
            percentage(result.source_files.covered_percent),
            result.source_files.never_lines,
            result.source_files.lines_of_code,
            result.source_files.covered_lines,
            result.source_files.missed_lines,
          ]

          # ファイル毎の情報を一行として出力する
          result.groups.each do |group_name, source_files|
            source_files.each do |f|
              csv << [
                job_id,
                branch_name,
                created_at,
                group_name,
                short_filename(f.filename),
                percentage(f.covered_percent),
                f.lines.size,
                f.lines_of_code,
                f.covered_lines.size,
                f.missed_lines.size,
              ]
            end
          end
        end
      end

      def output_path
        SimpleCov.coverage_path
      end

      private

      # full path を app/**/*.rb 形式にする
      def short_filename(filename)
        filename.sub(SimpleCov.root, ".").gsub(%r{^\./}, "")
      end

      def bq_datetime_format(datetime)
        datetime.strftime("%Y-%m-%d %H:%M:%S")
      end

      def percentage(percent)
        percent.round(2)
      end

      def branch_name
        ENV["GITHUB_REF"]&.sub(%r{refs/.+/}, "")
      end
    end
  end
end

JOB_ID という環境変数は、GitHub Actions の github コンテキストの情報で構成されています。どの PR と紐づくのか判別できるように github.run_idgithub.run_number を使っていますが、GitHub Actions の Job を再実行しても これらは変更されないので、github.run_attempt を加えて一意性を出しています。

RSpec の実行は並列で行っているので、扱いやすいようにカバレッジの結果を 1 つにマージします。GitHub Actions の step として、SimpleCov の結果をマージするための設定を書いた Ruby のファイルを実行して集約しています。このときに、先ほど登場した環境変数 JOB_ID を設定して渡します。

jobs:
  ...
  report:
    ...
    steps:
      ...
      - name: Merge coverage files
        env:
          RAILS_ENV: test
          JOB_ID: ${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
        run: bundle exec ./bin/merge_simplecov_results.rb
      ...

bin/merge_simplecov_results.rb では出力先と複数の Formatter を指定しています。

#!/usr/bin/env ruby

require "simplecov"
require "simplecov-html"
require "simplecov_json_formatter"
require_relative "../spec/support/simplecov_bq_formatter"

SimpleCov.collate Dir["coverage/.result-*.json"], "rails" do
  formatter SimpleCov::Formatter::MultiFormatter.new([
    SimpleCov::Formatter::HTMLFormatter,
    SimpleCov::Formatter::JSONFormatter,
    SimpleCov::Formatter::BqFormatter,
  ])
  add_group "Forms", "app/forms"
  add_group "Services", "app/services"
  add_group "Decorators", "app/decorators"
end

これで SimpleCov の収集した情報を CSV として出力するようになりました。

2. RSpec JUnit Formatter で出力した XML から CSV を作成する

RSpec JUnit Formatter を導入して、RSpec を実行するコマンドに --out をつけてテストケース毎の結果を XML として出力しています。これを SimpleCov のときと同様に、GitHub Actions 上で読み込んで CSV に加工します。

先ほど出力した SimpleCov の CSV (coverages_for_bq.csv) を使うので、テストカバレッジの CSV を作成した後の step で実行するようにしています。

  - name: Merge coverage files
    env:
      RAILS_ENV: test
      JOB_ID: ${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
    run: bundle exec ./bin/merge_simplecov_results.rb
  - name: Create CSV file for BQ
    run: |
      bundle exec ./bin/export_spec_execution_time.rb
    env:
      JOB_ID: ${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}

XML の path を Dir.glob で取得して Nokogiri で読み込み、内容を CSV として出力しています。

以下の処理では、XML に含まれる spec ファイル名から対応する app のファイル名を算出するために coverages_for_bq.csv を読み込んだり変換したりしていますが、単純に結果を CSV に出力するためならば不要でしょう。実際、変換処理が長くなったので、ここでは省略しています。

#!/usr/bin/env ruby

require "csv"
require "nokogiri"
require "simplecov"
require_relative "../spec/support/simplecov_bq_formatter"

class SpecExecutionTimeExporter
  NODE_REGEXP = /\Ajunit-rspec-(\d+)\.xml\z/

  def initialize
    @xmls = Dir.glob(xml_glob_path).map do |xml_path|
      NODE_REGEXP =~ File.basename(xml_path)
      node_number = Regexp.last_match(1)
      [node_number, Nokogiri::Slop(File.read(xml_path))]
    end

    @csv = CSV.read(csv_path)
  end

  def to_csv
    CSV.open(File.join(output_path, "spec_execution_time_for_bq.csv"), "wb") do |csv|
      @xmls.each do |node_number, xml|
        suite = xml.testsuite
        suite.testcase.each do |c|
          app_fname = app_filename(c["file"]) # appファイル名

          csv << [
            job_id,
            app_fname, # appファイル名
            node_number, # node 番号
            filename_dict.key?(app_fname), # 該当appファイルの有無
            c["name"], # テストケース名
            c["time"], # 実行時間
          ]
        end
      end
    end
  end

  def filename_dict
    @_filename_dict ||= @csv.each_with_object({}) do |row, acc|
      app_filename = row[4] # filename
      acc[app_filename] = 0.0
    end
  end

  def job_id
    @_job_id = ENV.fetch("JOB_ID", nil)
  end

  def xml_glob_path
    File.join(SimpleCov.coverage_path, "..", "tmp", "junit-rspec-*.xml")
  end

  def csv_path
    File.join(SimpleCov.coverage_path, "coverages_for_bq.csv")
  end

  def output_path
    SimpleCov.coverage_path
  end

  private

  # system spec の app ファイルへの変換は諦める。
  # それ以外は 対応する app のファイルへの変換を試みる。
  def app_filename(spec_filename)
    return spec_filename.sub(%r{^\./spec}, "spec") if spec_filename.start_with?("./spec/system")

    name = spec_filename.sub(%r{^\./spec/}, "app/").sub(/_spec\.rb$/, "\.rb")

    -- ファイル名変換が長いため、 中略 --
    name
  end
end

exporter = SpecExecutionTimeExporter.new
exporter.to_csv

3. 出力した CSV を BigQuery に送信する

CSV ファイルを作成したので、後は BigQuery のテーブルにロードするだけです。 google-github-actions/auth と、google-github-actions/setup-gcloud を使って、認証し、bq コマンドで CSV をロードします。

steps:
  ...
  - name: Setup Google Cloud Auth
    if: ${{ success() && github.ref_name == 'develop' }}
    uses: google-github-actions/auth@v0
    with:
      credentials_json: '${{ secrets.GOOGLE_CREDENTIALS }}'
  - name: Setup Google Cloud SDK
    if: ${{ success() && github.ref_name == 'develop' }}
    uses: google-github-actions/setup-gcloud@v0
  - name: Publish Spec Report to BQ
    if: ${{ success() && github.ref_name == 'develop' }} # developブランチのみ記録する
    run: |
      bq load --source_format CSV tabechoku_dev_tools.tabechoku_rspec_coverages ./coverage/coverages_for_bq.csv
      bq load --source_format CSV tabechoku_dev_tools.tabechoku_rspec_execution_times ./coverage/spec_execution_time_for_bq.csv

全てのテスト実行の結果を取り込んでいるとさすがに煩雑になるので、default branch にマージされたときのみロードするようにしています。

Redash でクエリを作ってほしい情報を見られるようにする

データを取り込んでしまえば、後は SQL を書いてグラフ化するだけです。各種グラフを Dashboard 化していつでも結果を確認できるようにしています。

食べチョクでは、テストの実行時間、グループ毎のテストカバレッジに加え、テストカバレッジがしきい値より低い app ファイルの一覧を出力して、テスト追加対象のファイルが見えるようにしています。これにより、テスト追加対象のファイルと、テスト追加による成果の認知が容易になりました。

今回のデータを使った Redash の Dashboard
今回のデータを使った Redash の Dashboard

既存のデータをクエリで取捨選択、加工を可能にしたことで、単純なテストカバレッジや実行時間だけでなく、目的に沿った情報を自由に取得・表示することができ、有効活用できるようになりました。

今後は対応の成果差分がわかりやすくなるようにグラフを改善したり、通知を活用することでモチベーションが上がるかたちでテスト改善を進めていければと考えています。

まとめ

既存のテストカバレッジの情報とケース毎の実行情報を GitHub Actions 上で加工して CSV 化し、BigQuery にロードすることで、Redash と連携して現状を可視化、分析できるようにしました。

元々ある仕組みと情報を利用してデータの形式を変換してクエリを書けるようにしたことで、要求に合った情報を取り出すことができるようになり、可視化も容易になりました。このデータを活用しつつ、テストカバレッジ向上のモチベーションが上がるかたちでテスト改善活動を進めていければと考えています。

最後に

食べチョクを運営しているビビッドガーデンでは、一緒に働く仲間を募集しています。ご興味のある方はこちらの RECRUIT からどうぞ。

食べチョクの自動テスト改善活動 〜これまでとこれから〜

みなさん初めまして。 QAエンジニアのujeです。

食べチョクでは、2022年4月から正式に自動テスト改善チームが発足いたしました。 チームメンバーは機能開発との掛け持ちにはなりますが、Webエンジニア5名・QA1名で取り組んでいます。

自動テスト改善チームの発足に伴い、食べチョクのテストにまつわる振り返りと、改善チームが取り組んでいることをお話しいたします。

これまで

食べチョクはサービスリリースから5年がたつプロダクトです。 特にここ1年半は食べチョクに参加してくれる仲間が増えており、サービス開発のスピードも上がっています。

食べチョクは2020年まで、テストカバレッジが低く、またなかなか向上しない状態でした。 システム全体に影響がある改修をする際は、毎回全画面を一通り手作業で触りテストするなど労力のかかる状態でした。 この状態を脱するため、2020年末から少しずつ自動テスト改善の動きが始まりました。

20年11月-バックエンドのRSpec積極拡張方針が決まる

これまでは機能開発が優先されていて重要なところだけ自動テストが書かれている状況でした。 そのため、この時期から実装した機能のRSpecによるテスト実装を後回しにせず、並行して増やしていく方針に変わっていきました。

21年8月-カバレッジをデータ化して見える化が始まる

開発方針が変わったものの増やしていくだけとなっていたので、どの程度カバーされているかは把握できていませんでした。 そのためこの時期からデータ化して定期的に展開するようになりました。 数字が出たことで意欲が向上したためか、拡張速度が上がったと実感するのもこの時期でした。

21年9月-E2Eテストの本格導入が始まる

9月からWeb版のE2E導入、10月からAndroid版のE2E導入が始まりました。 現在はリリース前にユニットテストの他に、1日1回の定期実行とリリース直前にE2Eが成功することを確認する仕組みになっています。 なお、iOSについては今後、本格導入を検討しています。

22年1月からJestの本格導入が始まる

一時期フロントにまつわる不具合が増加しつつあったことに起因して、フロントエンドの単体テストの用途としてJest導入に踏み切りました。 E2Eはシステムテストの位置付けで外側から目的が達成できるか確認することとしています。 Jestはフロントエンドに特化した単体テストとしてパラメータ受け渡しなどのUI影響がある要素に影響がないかを重視して実装していっています。

これから

冒頭でお話した通り4月から正式に自動テスト改善活動チームが発足いたしました。 各メンバー機能開発との掛け持ちしながらになりますが、webエンジニア5名・QA1名という体制になっています。

発足理由

まだまだ古くからあるコードは1テストすらないことが多いです。 テストのない箇所が開発速度に影響が出たこともあり、解消を意識したのが1点目です。

またQCDバランスを意識したリリースするには自動テストが必要不可欠だと判断したことが2点目です。 現在、アプリに関わらないバックエンド・フロントエンド要素は1日1回リリースしています。 さらにメンバー拡大に伴い同時並行で進行する機能開発が増えてきました。 この2つから、壊れた際の検知を早めることが格段に大事になってきました。

これらの要素を考慮した結果、組織的な積極対策を目的としてチーム発足へと繋がりました。

主な活動

自動テストの速度向上・自動テストの充実化を目標に取り組んでいます。 ただし掛け持ちなので、やることはみんなで分担する&週1日など無理のない範囲で改善活動に時間を当てるような形になっています。

具体的には実行速度の監視・速度改善の対策案を相談したり、充実化でもデータ化・方針策定などを始めています。

チーム運営

QAである私は主にMTG進行の役割を担っており以下のようなことに注意を払って運営しています。(書いてあることは基本的なことばかりです)

  • 事前資料の準備&事前展開・MTG近づいたらリマインド・事前に資料記載のお願い
  • レビューと計画を1セットにして1時間以内のMTG(時短できるなら時短を目指す)
  • OODAループを利用してネクストアクションを決める
    • ※Observe、Orient、Decide、Actの4項目を軸にループを回す方法です。別手法にはPDCAサイクルがあります

まだまだこれから

今のところネクストアクションが無いなどの事態はなく、少しずつ前に進んでいるかなと感じています。 とはいえ、掛け持ちで割ける時間も決まっていることから成果を出していくのはこれからです。

メンバーと意見を出し合いより良いテスト体制作りを目指します。

最後に

食べチョクでは仲間を募集しています。 ご興味がある方は是非、RECRUITからご応募ください。

食べチョクのプロダクトチームとチームトポロジー

食べチョクのプロダクトチームとチームトポロジー

この記事はビビッドガーデン Advent Calendar 2021 最終日の記事です。

皆さんこんにちは、エンジニアの西尾です。

2019年、食べチョクのプロダクトチームは数名だけでした。 2020年から少しずつ、2021年頭からは一気にメンバーが増加し、12月現在は30名近いメンバーが所属しています。

プロダクトチームはもともと大きな1チームでしたが、2021年からチーム分割を検討し、6月頃から本格的に分割を始め、12月現在は7つのチームでプロダクト開発をすすめています。

組織設計、チーム分割にあたり参考にしたのが、チームトポロジーの概念です。 今回はチームトポロジーの一部を食べチョクでの実例を踏まえて紹介しつつ、運用してみての感想や今後の課題について紹介します。

本記事を書くにあたり、日本語版チームトポロジーを大いに参考にしています。

チームファースト

食べチョクのプロダクトチームは、もともと1つだけでした。 徐々にメンバーが増えていき、分割前には10〜15名ほどが所属する、大きな1チームとなっていました。

大きすぎるチームでは以下の問題が発生していました。

  • プロダクトが1つのチームで把握するには大きく、メンバーのプロダクトに対する認知負荷がとても高い状態になっていた
  • プロダクトの端から端まで把握できるメンバーは極めて少ない。そのため仕事がその分野に詳しい個人に張り付く状態となっていた
    • 例えば、定期便の担当はMさん、商品検索はKさん、注文画面はFさん担当といった具合になっていた
  • 仕事が個人に張り付くことにより、計画がメンバー個人に左右される状態になっていた
    • 今週中に最優先の施策、機能のデリバリーができるかどうかは、担当する個人の能力や状況に左右される
  • チームとしてサポートできず、大きなプレッシャーが個人にのしかかる状態であった
    • 今週中に最優先の施策、Aさんどうか体を壊さずに無理して実装してくれ!

プロダクトチームのメンバーは続々と増えている状態でした。 増員したのだから、その分チームとしてのケイパビリティを高め成果を出していくことが求められます。 メンバー数名でうまくいっていたときの開発方法や構成のまま増員しても、チームのパフォーマンスはどんどん落ちていってしまいます。

プロダクトが大きく複雑化したことにともない、個人が気合でプロダクトを開発するフェーズは終わりを迎えました。 組織がスケールするために、より大きな成果をだしていくために、2021年はチームとして成果をだしていかなければならない、すなわち物事をすべてチームファーストで考えるフェーズに変わりました。

チーム分割のアンチパターン

チームとして成果を出していくためには、チームサイズは重要です。 効率よく動くためには、大きな1チームから複数の5〜9名が所属する小さなチームに分割する必要があります。

どう分割したらよいのでしょうか。 職能別に分ければよいのか、顧客別に分ければよいのか、機能ベースでわければよいのか、はたまたミッションベースでチームをわければよいのか。

いろいろなアプローチが浮かび、どれも一長一短だと思います。ただし、アンチパターンは存在します。

チームトポロジーでは、頻繁にメンバーが入れ替わるようなプロジェクトベースのチームは良くないと紹介しています。 機能A開発プロジェクトが立ち上がり、メンバーがプロジェクトごとに集められ、プロジェクトが終了したら解散するようなチームはよくありません。 それはチームに知識が蓄積されず結局は個人に作業が紐付いてしまいます。また、メンバーは頻繁にコンテクストスイッチを求められパフォーマンスが出せない恐れがあります。

場当たり的で無策のままチームを分割するのもよくありません。単に情報が分断されただけの開発のしにくい組織になってしまいます。

変更のフローに最適化した組織

チーム分割のアンチパターンはわかりました。ではどうやって分割すればよいのでしょうか。 チームトポロジーでは、変更のフローに最適化した組織を作るのがよいと紹介しています。

なかなか難しい言葉ですが、要はチーム間の引き継ぎを極力少なくし、自分たちのチームの中でできるだけ仕事が完結するチームにするのが良いとのことです。

エンジニアであれば、ときにはすべて自分ひとりで考えて作って運用したほうが速いのでは、と感じたことはないでしょうか。 なぜ速いと思うのか。それは仮説出し、機能検討、デザイン、設計、開発、テスト、デリバリー、運用保守とそこからのフィードバックがすべて自分だけで完結し、 他のメンバーとの調整が発生しなくて楽だからではないでしょうか。

チームにも同じことが言えます。 企画は企画チームが考えて、開発チームに引き継ぐ。開発チームは機能を作りテストチームに引き継ぐ。テストチームの許可がでたらデプロイし運用チームに引き継ぐ。 運用チームは気になる点がありフィードバックはしたいけど他のチームと調整するのは面倒だな。 このように引き継ぎが多いとプロダクトを素早く改善しチームとしてのパフォーマンスを出すことはできません。 重要なフィードバックや気付きも得にくく、良いプロダクトを作ることはできません。

ストリームアラインドチーム

チームトポロジーでは、モダンなソフトウェアの開発と運用に必要なのは、たった4つのチームタイプであると紹介しています。 ストリームアラインドチーム、イネイブリングチーム、コンプリケイテッド・サブシステムチーム、プラットフォームチームの4つです。

このうち、組織の根幹となるのがストリームアラインドチームです。

ストリームとはなんでしょうか。 チームトポロジーでは、「ビジネスドメインや組織の能力に沿った仕事の継続的な流れ」と呼んでいます。 またまた難しい言葉ですが、例えばプロタクト開発においては、顧客にどんな価値を届けるかを考え、 企画検討からデリバリー、その後のフィードバックをもとにした改善までの一連の仕事の流れのことをストリームと呼びます。

ストリームアラインドチームは、単一のストリームに沿って動くチームです。 例えば、「カート機能を通じて、顧客がほしいと思った複数の商品を送料を押さえつつ購入できる体験を提供する」といった価値ストリームに沿って動くチームです。 そもそも顧客の課題は何なのか、何をしたら改善するのか、どういう設計にしたらよいか、本番運用はどうしたらよいか、 ストリームアラインドチームは自分たちの中で作業が完結する能力を備えている必要があります。

モダンなソフトウェア組織では、ほとんどのチームがストリームアラインドチームとなるとのことです。 食べチョクのプロダクトチームでは、上記の考えを踏まえつつ、2021年12月現在は3つのストリームアラインドチームを作っています。

ストリームアラインドチームを支える3つのチーム

ストリームアラインドチームはすべての根幹となるチームです。 このチームタイプの他に、ストリームアラインドチームを支える以下3つのチームタイプが定義されています。 今回はチームタイプの紹介は割愛します。詳しくはチームトポロジーを参照ください。

食べチョクのプロダクトチームでは、2021年12月現在、以下3つのチームを設置しています。

  • イネイブリングチーム
    • 1つのチーム、5名のメンバーが所属しています。内訳は開発全般を担当するメンバーが2名、フロントエンドエンジニアが2名、QAエンジニアが1名です。
  • プラットフォームチーム
    • 1つのチーム、4名のメンバーが所属しています。
  • コンプリケイテッド・サブシステムチーム
    • 1つのチーム、3名のメンバーが所属しています。

チームインタラクションモード

チームトポロジーでは、チーム構成について述べているだけでなく、チーム間の関わり方、インタラクションについても紹介しています。 インタラクションモードは、コラボレーション、X-as-a-Service、ファシリテーションの3つを定義しています。この概念も詳しくはチームトポロジーを参照ください。

感想と今後の課題

ここまではチームトポロジーの基本的な概念を、プロダクトチームの実例を踏まえつつ紹介しました。 ここからは実際に採用してみて直面した問題や感想、今後の課題について紹介します。

チームインタラクションはまだまだこれから

インタラクションモードに関しては、食べチョクではうまく実践できてはいません。

コラボレーションについては、2021年12月現在は1つのストリームアラインドチームとコンプリケイテッド・サブシステムチームが行っています。 これはまあ2つのチームが密に協力して機能開発に取り組んでいるというだけです。 朝会や日々の開発、レトロスペクティブは2つのチームで合同実施していますが、プランニングはそれぞれのチームで行っています。

ファシリテーションに関してはイネイブリングチームが行ってはいますが、うまく機能しているのかと聞かれるとそんなことはなく、まだまだこれからです。 ファシリテーションという概念になじみがなく、そもそも何をすればよいのか、チーム内でもうまく認識はとれていないのが実情です。

X-as-a-Serviceでのインタラクションは今の所できていません。

X-as-a-Serviceモデルがうまく機能するのは、サービス境界が正しく選択、実装され、サービスを提供するチームが優れたサービスマネジメントを実践している場合に限られる

とありますが、食べチョクではサービスの境界がまだまだ曖昧なために、運用はできていません。

イネイブリングチームは概念がわかりづらく、運用が難しい

イネイブリングチームは設置していますが、まだ名前だけという状態です。運用が難しい理由は2つあります。

  • ファシリテーションがそもそも難しい
  • イネイブリングチームとはどういうチームなのか? チーム内にもチーム外にもうまく理解されていない

ファシリテーションについては前述したとおり、まだまだこれからです。

イネイブリングチームとはそもそも何なのか、組織内で学習ができていないというのもあります。 2021年12月に日本語版チームトポロジーが出るまでは、英語の文献と一部の日本語サイトでの紹介しかされていなかったことも大きいと思っています。 今は日本語で読めるようになったため、12月からはチーム内でのTeam Topologies読み合わせ会をすすめて、少しずつ概念の学習をすすめています。

今はファシリテーション先のチームでは、手が回らない施策や機能実装、テストを変わりに担当する、便利なお助けチームになってしまっていることが多いのが実情です。

ストリームアラインドチームが複数のストリームを持っている

ストリームアラインドチームは、単一の仕事のストリームに沿って動くと定義されていますが、現状は複数のストリームを1つのチームが持ってしまっています。

これはストリームの境界がまだまだ曖昧であること、 すべてのストリーム分のチームが用意できるほどメンバーがいないこと、 やることが多くてフォーカスが絞れていないなどのプロダクトマネジメント上の課題など、さまざまな要因が重なっての結果です。

チームの認知負荷を下げるアーキテクチャになっていない

これからの技術課題です。 個人だけでなくチームの認知負荷に注意し、チームが扱うコードベース、アーキテクチャも扱いやすいよう適切に整理していく必要があります。

食べチョクは、今はまだ大きなモノリシックアプリケーションです。 「会員登録の機能を修正したら、なぜか注文画面が壊れた」といった問題が発生することもありました。 ドメインの境界が曖昧で、今は幅広くコードベースを把握していないと怖くて手を入れられない状態にあります。

チームがデリバリーしやすいように、認知負荷を下げて安全に手を加えられるようにするために、チームに合わせてアーキテクチャを整理していかなければなりません。

おわりに

わたしたちはまだチームトポロジーの概念を参考に、チームを整理しただけという段階です。 道半ば、チームとして組織としてスケールし成果を出していくためには、まだまだ改善の余地があります。

チームトポロジーは銀の弾丸ではないので、導入したから組織がうまくまわるというものではありません。 チームのあり方を変えるだけでなく、健全な組織文化の構築、技術やアーキテクチャの整理、プロダクトロードマップの整理やマネジメントの強化などもあわせて行っていく必要があります。

2022年も、よりよい価値のあるプロダクトを生産者・ユーザーに届けられるよう、そして食べチョクがさらなる成長を遂げられるよう、これからも精進していく次第です。

「あ、面白そう」と思ってもらいたい。カジュアル面談の中身をザクッと公開します

f:id:vividgarden-tech:20211206111602p:plain この記事はビビッドガーデン Advent Calendar 2021 の4日目の記事です。

こんにちは。ビビッドガーデンのプロダクト開発チームで採用と組織開発を担当している平野です。 簡単に自己紹介をすると、「webエンジニア歴8年」「ビビッドガーデンには3年前に入社してここ1年半は採用にコミット」「好きなものはビールとハロプロとパデル」という人です。

ビビッドガーデンではプロダクト開発に関わる人の採用において、候補者の方との相互理解というのを重視しています。 すなわち候補者の方にはビビッドガーデンの良いところと課題となっているところを「そんなとこまで?」というレベルまで知ってもらおうと思っており、その起点となる場がまさにカジュアル面談だと捉えています。

この記事ではプロダクト組織のすべてのカジュアル面談に出席している私がカジュアル面談の弊社における立ち位置実際にどんな話をしているのかという部分を書くことで、応募者の方にとってカジュアル面談がより価値のある時間になる手助けができれば・・・と思っています。

カジュアル面談にかけてる思い

我々としては応募者の方に「ビビッドガーデンで働くとどんな価値が得られるか」を理解してもらうこと、さらに砕けた言い方をすると「あ、面白そう」と思ってもらうことがカジュアル面談のゴールだと定義しています。

応募者の方に時間を割いてもらっているので基本は応募者の方が聞きたい話を丁寧にさせて頂くのですが、「そもそもどんな働き方をしたい方なのか」「どのくらいビビッドガーデンのことを知っているのか」ということ応募者の方にお聞きして、それに合わせて弊社からさまざまな情報をお伝えしています。

しかし今やプロダクト開発に関わるメンバーの採用はどんな職種でも「選考する側」「選考される側」というものはなく「お互いがお互いを選考している」という状況です。 その上選考フェーズにおいては候補者の方とお話できる時間は非常に限られています。

限られた時間の中で弊社で働くことに価値を感じて頂く為に、ついついポジティブな面だけを強調してしまいがちですが、キャリアの方向性などにミスマッチの懸念があればお互いのために必ずそれをお伝えするようにしています。 相手をリスペクトしながら飾ることなくストレートに話を伝えあう弊社の雰囲気を私自身が体現すべきですし、そのためにカジュアル面談は「あくまでカジュアルなので〜」という感じではなく本気で、誠意を持って取り組んでいます。

こう書いてしまうと堅苦しい印象もありますが、お話自体はカジュアルに楽しくさせていただいております。カジュアル面談への応募は本当に、本当にカジュアルにしてください〜!

カジュアル面談の実際の流れ

ごあいさつ

カジュアル面談は主に私と現場メンバーのペア体制でやっており、最初に我々の自己紹介をさせてもらっています。応募者の方には自己紹介は求めていません。

そのままアイスブレイクを兼ねて「お互いのカジュアル面談のゴール」「お聞きしたいこと」を確認させてもらいます。「転職を考えていて、開発体制などの内情を詳しく知りたい」「転職はあまり考えてないが興味はある会社なので話を聞いてみたかった」「実家が農家でビジネスモデルについて知りたかった」などのお答えをいただきます。なんでもござれです。 あわせて「これは選考ではないので我々から何かしら判断をするということはありません」ということをお伝えしています。

弊社について

応募者の方が「お聞きしたいこと」次第で順番は左右しますが、カジュアル面談が終わったタイミングではすべての応募者の方が弊社に関する理解度が一定レベルになるようにお話するよう心がけています。 基本セットとしては以下内容をお伝えしています。

  • 何をしたい会社なのか
    • 会社のミッションとビジョンについて
  • そのために何を作っているのか
    • 食べチョクというプロダクトの詳細説明
  • それをどう作っているのか
    • 開発組織の話(ユニット制、技術スタック)
  • どんな人が関わっているのか
    • メンバーの紹介
  • 今後どうしていきたいのか
    • 今期と中長期に何をしていきたいのか
  • そのためにどのような人を増やしたいのか
    • 職種に応じてお伝えします

ここでは専用のスライド資料(今月中に公開予定です!!)を使って説明しながら、所々止まって「気になったところありますか?」「もっと深く知りたいところはありますか?」「逆にこちらから聞いてもいいですか?」という形で対話形式で話を進めています。事業の話、開発の話、会社の話、なんでもござれです。

また、具体的に理解してもらうということに重点を置いていて、実際のプロダクトの画面、Slack(社内の雰囲気理解)、GitHub(プロダクトチーム以外も使っています)、ユーザーからの反響(Twitterなど)も画面共有して見てもらうようにしています。(機密情報は伏せています)

多くはこの過程で応募者の方の「聞きたかったこと」をカバーするのですが、必ず「気になっていた点はクリアになりましたか?」と確認させてもらっています。

締め

全体を通じて大体45分で終了となります。最後に聞き逃したことが無いかを確認させてもらいます。面談終了後でもいつでも連絡いただければすぐに回答しますともお伝えしています。

興味がある方には今後の選考フローに関する説明をしますが、この場で選考に進むかどうかの意向確認は行っておりません。持ち帰っていただいて一言「選考に進みたい」とご連絡いただければすぐに次の選考へとご案内しています。

しかしこの場で選考に進む意思を口頭で伝えていただく方、面談終盤に「今チャット送りました」と言ってくれる方、面談終了1分後にチャットを送ってくれる方も多いです。素直に大変うれしい気持ちになります。

お時間頂いたことに感謝して終了となります。

よく聞かれること

ビジネスモデルが農協とバッティングしてそうだけど、大丈夫なの?

提供しているサービスやターゲットには違いがあるものの、生産者の方への価値貢献という側面では同じ方向を向いている同志というお話をしています。

代表秋元のブログに詳しく記載があるので、気になる方は是非そちらも読んでみてください。一部引用します。

・提供しているサービス価値は異なり、すみ分けができていると認識しています。結果として農協さんに呼んでいただいて講演させていただくことも多々あります。

・皆さんが「一次産業を良くしていきたい」という共通の想いを持っています。 note.com

サービスの成長と共にプロダクト組織はどう変わりましたか?

1年半前まではwebエンジニア3-4人という体制でやっていましたが、今となっては以下の各職種のメンバーが在籍しており40名に迫る組織となっています。

  • webエンジニア
  • モバイルエンジニア
  • インフラエンジニア
  • QAエンジニア
  • データアナリスト
  • UIデザイナー
  • PdM

組織体制という観点では1つのチームがすべての開発を受け持っていた形から、プロダクトに対して定められたミッションを複数のユニットがそれぞれ自律して取り組むという体制に。

開発方針という観点では「作る、リリースする」ということが目的になりつつあった状態から、どのくらい価値提供ができたかという軸での分析を元に仮説検証を重ねて探求と学習を進める体制へと変わりました。

この部分は1年前から角谷さんに手伝ってもらっており、一緒に議論し続けて今の形になっています。現在も継続議論中です。 offers.jp

体制に関してはゴールは無いので、今後も常にアップデートし続ける、変化を歓迎するという姿勢を取っていきます。

エンジニアの評価制度はありますか?

昨年末に制定したものがあります。 簡単に説明するとにグレード毎に期待成果と年収が記載されており、メンバー各々がそれに沿った形で目標設定をしています。 半期ごとに評価が行われますが評価者との中間評価と毎月の1on1、私や各チームのリードメンバーとの毎週の1on1で軌道修正を図っています。

しかしこの評価制度はまだまだベータ版だと思っており、エンジニアが出す価値をちゃんと評価できているだろうかという観点ではまだまだ改善の余地があると思っています。 今月に評価制度のアップデートがありましたが、引き続きアップデートを重ねていく予定です。

どんな人がいますか?

プロダクト組織は全員中途入社で、出身企業はベンチャーからSIなどひとくくりにはできないくらい多様です。 しかしマインドという観点では皆下記2つの考えを持っています。

  • 自分の手でプロダクトを作り、それが誰かの課題を解決し、その様子が手にとるように分かる環境で働きたい
  • より良いプロダクトを作るために、慣れた体制や技術に固執せず、変化やアップデートすることを歓迎する

このマインドに加え「生産者の方への価値貢献」が全メンバー自分事化している点がチーム全体が一体感を持って前に進める秘訣だと感じています。

「実家が農家でした」などの農業バックグラウンドがあるメンバーは多くないのですが、 それでも生産者の方に関わるという事が自分事化するのは日常的に生産者さんの声やフィードバックが聞こえるプロダクトという特性のおかげだと思っています。

おわりに

ご覧いただきありがとうございました。 ビビッドガーデンは11月をもって6期目に突入しました。

1年前はエンジニア数人とデザイナーでがむしゃらに作っていた感じでしたが、1年で複数の少人数チームが自律的にデリバリーと仮説検証を進める体制へと大きく進化しました。 6期目はさらなる価値提供のために「より深くより広い領域で仮説検証をする」、「デリバリーを安定的に行う開発基盤を強固にする」という2点を実現させたいと思っています。

会社としても立ち上げ期から拡大期に一気に変わってきた状態です。ドラスティックに環境が変わり続ける現場にてより多くのメンバーと一緒にお仕事を楽しみたいと思っています。

カジュアル面談は以下媒体より申し込みが可能です。

Meetyでカジュアル面談

選考フローも必ず最初にカジュアル面談をはさみますのでこちらでエントリー頂いても大丈夫です。エントリーの時点では履歴書等必須ではありません。 herp.careers

それでは、カジュアル面談にてお会いしましょう!

監査ログをファイルに記録するためのGemを公開しました

こんにちは。
食べチョクの開発を副業でお手伝いしているプログラマーの花村です。

監査ログをJSONL(JSON Lines)のファイルに記録するためのGemのAuditLoggableを作成してrubygems.orgで公開しました。
ソースコードもGitHubで公開しています。 ​ rubygems.org github.com

なぜ開発したのか

食べチョクでは監査ログを記録するためにAuditedを利用していました。
AuditedはActiveRecordのコールバックを利用してモデルの変更を手軽にRDBに記録してくれる大変便利なGemです。

しかしRDBに記録するためテーブルサイズが肥大化しパフォーマンスに影響を与える場合があるというデメリットもあります。
食べチョクでは注文数の増加に伴ってこの問題に直面しました。

これを解決するにあたり以下のようなアプローチが思いつきます。

  1. 監査ログを定期的に消す
  2. 監査ログを記録するDBを分ける
  3. 監査ログをRDBとは別のストレージに記録する

1では過去分のデータが必要になった際に困りますし、2では延命措置にしかなりません。
ということで今回は3を採用しました。

記録したログファイルの扱い

AuditLoggableで記録したログファイルはCloudWatch Agentを利用してCloudWatch Logsに連携しています。
CloudWatch Logsには1か月分ほど保持する設定にしており、毎日CloudWatch LogsからS3へバックアップしています。
このときにCloudWatch LogsのExportTask機能を使ってエクスポートするとJSONのメッセージに余分な情報(CloudWatch Logsに取り込んだ日時の情報)がついてしまうので、S3のイベント通知を使いLambdaで整形処理を走らせています。

CloudWatch Logsに保持し続けずにS3に移しているのはコスト圧縮のためです。 1カ月より過去分を参照したいときはAthenaで検索できるようにしてあります。

なぜログファイルに記録するのか

監査ログの記録先としてRDB以外を使うのであればDynamoDBなどのNoSQLなDBに記録することも考えられます。
しかし以下の理由で記録先はファイルとしました。

  1. 記録の失敗しにくさ
  2. リアルタイムに参照できる必要はない​

Web API経由での記録では失敗することも多いのでそのためのハンドリングが煩雑になることが予想されます。
また監査ログを記録された瞬間から参照したいことはないので収集にラグがあっても構わないという判断がありました。

使い方

config/initializers/audit_loggable.rb など初期化ファイルで出力先のファイル名などを設定します。

AuditLoggable.configure do |config|
  if Rails.env.test?
    config.auditing_enabled = false
  end
  config.audit_log_path = Rails.root.join("log", "audits.log")
end

ApplicationRecord など必要なモデルで AuditLoggable::Extension でActiveRecodのモデルに機能を追加します。

class ApplicationRecord < ActiveRecord::Base
  extend AuditLoggable::Extension
end

監査ログを記録したいモデルで log_audit を呼び出して記録対象に設定します。

class Post < ApplicationRecord
  log_audit
end

ApplicatoinController などで以下のように情報集する機能を有効化することで「操作したユーザーID(とクラス名)」、「リモートIPアドレス」、「リクエストID」を監査ログに記録できるようになります。

class ApplicationController < ActionController::Base
  around_action AuditLoggable::Sweeper.new(current_user_methods: %i[current_user])
end

Auditedとの違い

Auditedと似た挙動をしますが主に以下の違いがあります。

  • 記録先はJSONLのログファイル
    • RDBではなくJSONLのログファイルに記録します
  • ActiveRecordのモデルへのメソッド追加は最小限
    • Auditedでは audit_xxx のようなインスタンスメソッドが複数追加されますが、AuditLoggableでは1組のaccessorの追加のみです
    • またクラスメソッドは機能の有効化用メソッド1つを追加するのみです
  • Associated Auditsは未サポート
    • 食べチョクでは不要であったことと複雑になってしまうので未サポートとしました
  • 条件付き記録は未サポート
    • 食べチョクでは不要であったことと複雑になってしまうので未サポートとしました

おわりに

似たような課題を抱えている方の解決の一助になれば幸いです。 もしバグを見つけたり機能を追加したいなどありましたらGitHubでPull RequestやIssueをお待ちしています。

github.com