Classi開発者ブログ

教育プラットフォーム「Classi」を開発・運営するClassi株式会社の開発者ブログです。

SLO本読書会、監訳者である山口能迪さんを囲んでSLOの理解を深める会を実施しました

こんにちは。プロダクト本部プラットフォーム部SREチームの id:ut61z です。

SREチームが主体となってSLO本読書会を社内で実施しました。

そしてなんと贅沢なことに、監訳、翻訳に携わった Google Cloud の Developer Relations Engineer である山口能迪さん(@ymotongpoo、以下山口さん)にゲストとしてお越しいただき、読書会で出てきた疑問をぶつけてSLOについて理解を深める会を開催しました。

今回は読書会、そして山口さんを囲んでSLOの理解を深める会について、経緯や読書会の進め方、学んだことなどをレポートしたいと思います。

SLO本とは?

O'REILLY から翻訳書が出版されているこちらの『SLO サービスレベル目標』という書籍(以後:SLO本)です。

www.oreilly.co.jp

SLI・SLO・エラーバジェットの3つについて、概念の説明、どのように設計・運用するか、さらには組織にそれらを浸透させるためにどのような文化の醸成が必要かなどがわかりやすく記されています。

第Ⅰ部 〜第Ⅲ部の三部構成で、以下のようなトピックを扱っています。

  • "第Ⅰ部 SLOの開発" では主にSLI・SLO・エラーバジェットの概念と考え方についての紹介
  • "第Ⅱ部 SLOの実装" ではSLI・SLO・エラーバジェットの導入・運用の実践的なプラクティスや事例の紹介
  • "第Ⅲ部 SLOの文化" ではいかに組織にSLOを根付かせるか、実践的なプラクティスの紹介

SLO本読書会をしようと思ったきっかけ

読書会をしようと思ったきっかけは、2点あります。

1点目は、弊社では一部の機能で限定的にSLOを設定していますが、その他の多くのコンポーネント、あるいはサービス全体に対してのSLOの設定・運用ができているわけではなく、この本を通してSLOへの理解を深め、導入・運用の再出発を図りたかった点が挙げられます。

以下の記事でSLOを導入した機能について紹介しているので、興味があればぜひご覧ください。

tech.classi.jp tech.classi.jp

2点目はSLO本を先行して読んでみて、SLI・SLO・エラーバジェットは、エンジニアだけでなく幅広い職種のメンバーと一緒になってつくりあげる必要性を感じたので、エンジニアに限らず広く社内で読書会参加者を募り、読書会を通じて概念の共通理解を得たかった、という点がありました。

読書会をどう進めたか

2点目の観点から、社内の全メンバーに対して参加の呼びかけを行いました。
結果、エンジニアに限らず、QA、デザイナー、プロダクトマネージャーなど多様な職種のメンバーが参加してくれました。

また、エンジニア以外のメンバーも参加することも踏まえ、読書会のゴールを概念の理解とし、第Ⅰ部のみを読み進めるかたちとしました。
例外的に第1章で、第17章(最終章)を読むことを強く推奨していたので、第17章も読書会で扱いました。

1章ずつ担当を割り振り、担当者はその章を事前にまとめ、発表し、参加者同士で気になったことをディスカッションをするかたちで読書会を進めました。

読書会を通して学んだこと・浮かんだ疑問

学んだことは多岐にわたりますが、前提となる考え方について、エンジニア以外のメンバーも含めて共通理解を得られたことは大きかったと思います。

たとえば以下のような点は、ディスカッションでもたびたび言及され、参加者の多くが従来の考え方をUnlearnした内容でした。

  • ユーザーを基準にして考える
  • ユーザージャーニーをベースにしてSLIを設計する
  • 信頼性は高ければ高いほどよいというわけではない、コストとのトレードオフ
  • ユーザーが不満に思わないところを見定めて目標とする
  • 信頼性についてすべてを網羅的に管理することは不可能
  • エラーバジェットは"使う"ことができる
  • SLI・SLO・エラーバジェットはプロジェクト的に設定するものではなく、継続的に文化として取り組むもの

また、エンジニア以外のメンバーから以下のような意見も出ました。

  • QA: CUJ*1からSLIを定めるという考え方は、テスト項目をつくるうえでも参考になるかもしれない
  • デザイナー: ユーザーを基準にして考えるというのは「人間中心設計」と通じるものがある
  • プロダクトマネージャー: むやみにKPI等の数値を追って完璧を目指すのではなく、重要な指標がある値であるとき、それはユーザーにとってどういう状態なのか?という会話ができるようにしたい

多様な職種のメンバー間でディスカッションすることで、多角的にSLOを捉えることができ、さらには考え方の応用ができないかの議論にも発展して、とても実りのある時間になりました。

一方で、実際にClassiでSLI・SLO・エラーバジェットを運用するとしたらここはどう捉えるべきかなど、実践的な問いもいくつか出てきました。

  • ユーザーが満足しているかはどう計測すればよいか
  • CUJとはなにかを考えてみたとき、逆にCUJじゃないジャーニーはあるのか
  • 特定のコンテキスト(時間帯・繁忙期・ユーザー種別など)によって、ユーザーの期待値は異なるのでコンテキストを加味してSLIに重み付け等をしたほうがよいか、複雑にならないか
  • エラーバジェットのバーンレートアラート*2がうまく機能していれば従来のアラートの多くはなくせる、とあるが、なくすと困るケースも出てくるのではないか

これらの疑問を携えて、山口さんを囲んでお話を伺いました。

山口さんを囲んでSLOの理解を深める会

山口さんを囲む会と題して、SLO本を読んで浮かんだ疑問や、翻訳業についてなど様々な質問をさせていただきました。

「この本の中で和訳に困った章はありましたか」と伺ったところ、第9章は統計についての話ゆえ、専門用語を正しく使うことに気をつかい、別の統計の本も参考にしながら翻訳を進められていたということを仰っていて、翻訳者としての苦労が伺えたのが印象的でした。
計算式を正しく理解するために自分で検算してみたら、原書の誤りを見つけたりもしたそうで、そういった丁寧な正確さの追求があって翻訳が成し遂げられていると思うと、改めて感謝の念を抱きました。

さて、前述の疑問をぶつけてみてお話をしていただきましたので紹介したいと思います。

Q. ユーザーが満足しているかはどう計測すればよいか

Google のUXリサーチャーチームでは、ユーザーをお呼びし、CUJのフローの操作をしてもらいながら、どこで詰まっているか(いないか)を観察する、なんでも思ったことを喋ってもらう、操作後に全体の感想をもらう、など様々なフィードバックをもらうということを行っています。

それらを踏まえて、そのCUJのSLIの妥当性をチェックします。
あくまで例えですが、ユーザーがレイテンシよりエラーに不満を持っていたなら、エラー率のSLOの優先度を上げる、といったように対応することができます。

あるいは、機能リリースなどのSLIが変化するようなイベント前後で、問い合わせがどう変化したかを見て、間接的に評価するというのも手段のひとつです。

SLIでとれるデータは、実際のエンドユーザーの満足度を直接測っているわけではなく、相関を持っているだろうとしかいえません。
最後は決めの部分もありますが、使えそうなデータはすべて用いてSLIをユーザーの満足度に近づけていくというプロセスが大事だと思います。

Q. CUJとはなにかを考えてみたとき、逆にCUJじゃないジャーニーはあるのか

たとえばあるジャーニーに対してSLI・SLOを設定してみてSLO違反が起きたときに、ビジネス上影響がないとか、問い合わせがとくにあがってこないとか、そのコンポーネントを使っている別チームも困っていない、ということであれば、実際にはそこまで信頼性が高くなくてもよかった、という気づきになり、クリティカルかどうかを見直す機会になります。

あらゆるジャーニーにSLOを定めなくてはいけないということではなくて、SLOは自分たちが効率よく信頼性を管理するために使うものなので、関係者間で「ここはSLOを定めなくてもそんなに影響がない」という合意形成ができれば定める必要はないと思います。

Q. 特定のコンテキスト(時間帯・繁忙期・ユーザー種別など)によって、ユーザーの期待値は異なるのでコンテキストを加味してSLIに重み付け等をしたほうがよいか、複雑にならないか

ブラックフライデーの時期の売上が全体の大部分を占めるというようなサービスにおいては、その時期はSLOを厳し目に変更して設定する、というのはよく聞きますし、実際にそうすべきだと思います。

SLI・SLO・エラーバジェットなどのプラクティスはあくまでプラクティスとしてまとめたものであって、実際に会社が何を優先し、何を目指すか、サービスを利用しているユーザーがどう思うかなどに寄り添うようなかたちで決めていくべきだと思います。

ユーザー種別という文脈では、 Google の例だと、 Borg という共通インフラがあり、顧客が利用するサービスも社内のレポート用に使っているサービスもすべてその共通インフラ上で稼働しています。そうすると、それぞれのサービスがリソースの取り合いになるわけですが、優先すべき顧客のサービスに対してSLOを設定して運用しています。

提供するサービスによりますが、優先したい条件のユーザーがいるのであれば、それらを識別する情報でメトリクスをフィルタし、SLI・SLOを設計することはよくあることだと思います。

複雑になってしまうという点については、そうならざるを得ない部分はあります。
なぜならビジネスが複雑だからです。

振り返りなどを通して、複雑なメトリクスのなかからユーザーの満足度に最も近いものを探っていくプロセスが大事であり、また難しい点だと思います。

Q. エラーバジェットのバーンレートアラートがうまく機能していれば従来のアラートの多くはなくせる、とあるが、なくすと困るケースも出てくるのではないか

アクショナブルなアラートかどうかというのが大事なポイントになってきます。

バーンレートアラートは、このペースでメトリクスが悪化すると、確実に信頼性が落ちていくのが目に見えているので早めにアクションを取りましょう、といったかたちで使うことができます。

一方で従来のアラートは、あるしきい値を超えると発報するように設定することが多いので、それを基準にしてアクションすることが難しい(少なくともアクションすべきかどうか判断する必要がある)という点で差異があります。

従来のアラートをやめるためには2つ条件があり、まずはバーンレートアラートがアクショナブルであるということを関係者間で実感してもらうこと、そして、従来のアラートとバーンレートアラートを比較したときに、従来のアラートがどれぐらい無視されているかを確認することです。

バーンレートアラートを通してアクションした結果、従来のアラートを無視しても大丈夫でしたよね、という事実を積み重ね、関係者間で合意ができれば、納得感をもって従来のアラートを消すことができると思います。

もちろん、バーンレートアラート以外のアラートが有効である、アクショナブルであると実感できる場合はそれを使えばよいですし、バーンレートアラート OR NOT というように考える必要はないと思います。

全体の感想

山口さんにエンジニア以外のメンバーも巻き込んでSLO本の読書会を実施している点はすばらしいというお言葉をいただき、進め方は間違ってなかったという自信が持てました。

SLO本の序文冒頭に「信頼性は会話です」とありますが、その一言にSLI・SLO・エラーバジェットをどう考えるべきかについてあらゆるエッセンスが詰まっているように感じます。

読書会や山口さんとの会を通じて、絶えずチーム・組織で会話を重ね、ユーザーが満足しているとはどのような状態か、どうすればその値を計測することができるかを追求していく姿勢、プロセスが大事だということを学べました。

改めて山口さんにはすばらしい翻訳と、理解を深める会へのご参加にこの場を借りてお礼申し上げます。

まだまだClassiはSLOを運用できていると自信を持って言える段階にはありませんが、SLOの文化を築けるように取り組んでいきたいと思います。

*1:クリティカルユーザージャーニー: ユーザーが 1 つの目的を達成するために行うサービスとの一連のインタラクションがユーザージャーニーであり、それらのうち、信頼性が損なわれるとサービスとして成り立たなくなるものを指す

*2:バーンレートアラート: エラーバジェットに段階的にしきい値を設定し、それを超えて消費されたときに発報されるアラート。エラーバジェットポリシーに則ったアクションのトリガーとなる

コスト削減成功!Amazon Auroraの監査ログをS3に保存する仕組みを構築した話

こんにちは。プロダクト本部Growth部でエンジニアをしている id:ruru8net です。
前回はこちらの記事を書かせていただきました。
tech.classi.jp

今日は前述したSRE留学中にやったことの中の「Amazon Auroraの監査ログをCloudWatch Logsを経由せずS3に保存する」を紹介したいと思います。

前提

前掲の記事にもある通り、弊社のAWSにかかっているコストを調査したところCloudWatch Logsの特にAmazon RDSの監査ログの保存にコストがかかっていることがわかりました。今回は弊社で最も使用しているAmazon AuroraのMySQLのみを対象として、監査ログをCloudWatch Logsを経由せずS3に保存する仕組みを作成しました。

作成した仕組み

こちらのオープンソースの仕組みを参考に構築、またLambdaのソースを使いました。
github.com

Lambdaを使って監査ログをS3に保存する構成図

ただLambdaはMariaDBのみの対応でAuroraに対応しておらず、MariaDBとAuroraではいくつか仕様が異なる部分があったため、Auroraで動かせるように下記の変更を加えました。

  • ログのtimestamp
  • ログファイルのprefix
  • ログファイルのローテーションタイミング

監査ログに関するMariaDB MySQLとAurora MySQLの違い

1. ログのtimestamp

  • MariaDB
    • YYYYMMDD の後、ログに記録されたイベントの HH:MM:SS (24 時間制) が続く
  • Aurora
    • 記録されたイベントの UNIX タイムスタンプ (マイクロ秒の精度)

timestampはどこまでのログをS3にエクスポートしたかを記録する際に使用するので、このtimestamp取得部分をAurora用に変更しました。

lambda/internal/parser/auditlogparser.go

// ts, err := time.Parse("20060102 15:04:05", record[0])
timestamp, _ := strconv.ParseInt(record[0], 10, 64)
epochSeconds := timestamp / 1000000
t := time.Unix(epochSeconds, 0)
formatTime := t.Format("20060102 15:04:05")
ts, err := time.Parse("20060102 15:04:05", formatTime)

2. ログファイルのprefix

  • MariaDB
    • audit/server_audit.log
  • Aurora
    • audit/audit.log

生成される監査ログのファイル名のprefixが異なるので以下の部分を変更しました。

func NewRdsLogCollector(api rdsiface.RDSAPI, httpClient HTTPClient, region string, rdsInstanceIdentifier string, dbType string) *RdsLogCollector {
  return &RdsLogCollector{
    rds:                api,
    region:             region,
    httpClient:         httpClient,
    dbType:             dbType,
-   logFile:            "audit/server_audit.log",
+   logFile:            "audit/audit.log",
    instanceIdentifier: rdsInstanceIdentifier,
  }
}

3.ログファイルのローテーションタイミング

監査ログはサーバー内でファイルに書き込まれており、一定のタイミングでログファイルの入れ替え(ローテーション)が起こります。

  • MariaDB
    • ファイルサイズがSERVER_AUDIT_FILE_ROTATE_SIZEで設定したサイズを超えたとき(デフォルト1MB)
  • Aurora
    • ファイルサイズが100MBを超えたとき
    • 100MBを超えずに最初のログの書き込みから30分が経過した時

また書き込み中のログファイルとローテーションが完了したログファイルの名前規則もそれぞれ異なります。

  • MariaDB
    • 書き込み中のログファイル名は常にaudit/server_audit.log
    • ローテーション済みのログファイル名はaudit/server_audit.log.1 のようにファイル名に数字のsuffixがつく
  • Aurora
    • audit/audit.log.A.YYYY-MM-DD-HH-MM.1のようなファイル名になる
      • A: 0-3の数字が入る
        • Auroraでは常に4つのファイルに同時に書き込みが行われているためその判別
      • YYYY-MM-DD-HH-MM: 書き込み開始時間
      • 1: 数字のsufffix。開始時間が同じファイルが複数ある場合に数字が増えていく。

今回のLambdaでのS3に送る対象のログは、Lambda実行時にすでに書き込みが完了しローテーション済みのファイルのみを送るようにしています。
MariaDBと違いAuroraはローテーション済みのファイル判別が少し複雑なため、以下のように変更をしました。

lambda/internal/logcollector/rdslogcollector.go

func (l *LogFile) IsRotatedFile() bool {
-   matched, err := regexp.Match(`\.log\.\d+$`, []byte(l.LogFileName))
+// ログファイルが100MBに達しているかどうかを確認
+   if l.Size > 100000000 {
+     return true
+   }
+// ログのファイル名のsuffixからログファイルの生成時刻を取得
+   regex := regexp.MustCompile(`audit\.log\.\d{1}\.(\d{4}-\d{2}-\d{2}-\d{2}-\d{2})`)
+   matched := regex.FindStringSubmatch(l.LogFileName)
+   if len(matched) < 2 {
+     log.Warnf("Error matching log file: %v", l.LogFileName)
+     return false
+   }
+   createdTime, err := time.Parse("2006-01-02-15-04", matched[1])
+   // ローテーション完了時刻は現在時刻(UTC)の30分前
+   rotatedTime := time.Now().Add(-30 * time.Minute)
    if err != nil {
        log.Warnf("Error matching log file: %v", err)
        return false
    }
-   return matched
+// ログファイルの作成時刻がローテーション完了時刻より前であればログファイルはローテーション済み
+   return createdTime.Before(rotatedTime)

その他変更点

Classiでは1つのクラスターに対しライターとリーダーの2つのインスタンスが存在しています。元のLambdaではRDSのインスタンスを環境変数に取っており、このままだと1つのクラスターに対し2つのLambdaが必要になってしまうので、1クラスター1Lambdaで対応できるようにしたかったため、以下の2点を変更しました。

  • RDSのクラスター名を環境変数に取るようにする
  • RDSのクラスター名からインスタンスを取得し、各インスタンスに対してS3へのエクスポートを実行する

コストの変化

S3に送るようにしたことでCloudWatch Logsへの監査ログの送信を止めたところ、CloudWatchのコストを約53%減らすことができました。(10月中旬に監査ログの送信を止めたため9月と11月を比較しています)

CloudWatchのコスト変化のグラフ

実装後の感想

監査ログという観点だけでもMariaDBとAuroraで異なる部分があり、ドキュメントにない部分は実際にインスタンスを立ててみて動かして違いを探したりしました。
また作成したインフラリソースはTerraformで管理しています。今後新しくRDSを作成した際にも楽に監査ログをS3に送れるよう、for_eachを使ってリソースを定義するなど、拡張しやすくすることも考えました。ただ仕組みを作るだけでなく、今後その仕組みを自分以外が管理、運用してすることを考えて構築していくのがとても難しく時間がかかりましたが、とてもいい勉強になりました。結果的に大きくコスト削減にも貢献することができ、より達成感を感じられました。
今回はAurora MySQLに限定したLambdaになっているのですが、弊社ではPostgreSQLを使用している部分もあるので、他のRDSにも対応したLambdaになるようアプリケーションコードの改修もしていきたいです。

RubyWorld Conference 2023 に参加しました

こんにちは!エンジニアの id:kiryuanzu です。最近はよく GitHub Actions のワークフローいじりをしています。

今回の記事は11/9(木)〜11/10(金)に筆者が参加した RubyWorld Conference 2023 の参加レポートです。

2023.rubyworld-conf.org

RubyWorld Conference とは?

RubyWorld Conference は島根県松江市で年1回開催される Ruby のビジネスカンファレンスです。通称 RWC と呼ばれています。
公式サイトでは以下のように開催概要について説明されています。

RubyWorld Conferenceは、プログラミング言語「Ruby」の国内最大のビジネスカンファレンスです。 Rubyが、多様な現実世界にどのように適合し浸透していくのか、そのような普及過程と成長を考える機会となることを期待すると共に、Rubyのさらなる普及・発展とビジネス利用の促進を目指します。先進的な利用事例や最新の技術動向、開発者教育の状況などの情報を発信することで、「Rubyのエコシステム(生態系)」を知っていただくことができる場として開催します。 開催概要 | RubyWorld Conference 2023

RWC は過去に Classiの新卒メンバーで参加した RubyKaigi とはまた別系統の Ruby のカンファレンスとなります。

Classi のエンジニア3名が RubyKaigi 2022 に参加しました - Classi開発者ブログ

しかし実は RWC にも 2019年に当時の新卒メンバーたちが中心となって参加しています。当時の記事も残っており、当時のメンバーたちが楽しくイベント参加された様子が伝わる内容でした。

「Ruby World Conference2019」にスポンサーとして参加しました!(そして島根を楽しみました!) | Classi株式会社's Blog

筆者はこの時期まだ入社しておらず、今回が初めての参加となりました。RubyKaigi には何度も参加していましたが、RWC に関しては未知のイベントでどんな雰囲気なのかよく知らないままでいました。
今回参加してみて、RubyKaigi とはまた違った魅力の詰まったイベントだと知ることができました。その感じた魅力の一部を記事の中で紹介していこうと思います。

イベントの雰囲気

まず、オープニングトークで司会の方から「スーツを着ている人が多いカンファレンス」と紹介される場面があり一笑いが起きていたのが印象的でした。企業以外にも自治体や学校が深く関わっているという説明もあり、仕事柄スーツで参加されている方の割合が他のイベントより多いようです。
実際スポンサーブースに寄ってみるとスーツ姿の方がいらっしゃって、たいへん親切に活動説明と名刺交換をしてもらいました。 筆者が今まで参加したカンファレンスだとあまりフォーマルなスタイルでの関わりが少なく、ちょっと新鮮な体験でした。

また、発表会場には松江高専の学生たちが数十人ほど固まって話を聞いている様子を見かけました。どうやら RWC への参加が授業の一環となっているみたいです。他のカンファレンスだと学生が集団で参加する場面を見たことがなく、これも新鮮な光景でした。それに加えてブースや発表でも松江高専の関係者を見かける機会が何度かあったのも記憶に残っています。

オープニングトークでは島根県知事と松江市長の挨拶のお話、レセプションには島根県観光キャラクターの「しまねっこ」が登場する場面も。

このようなイベントが続き、RWC は島根県と松江市、高専や地元の Ruby企業が一体となって作り上げている印象を受けました。
会場の外でも、タクシーの運転手やお店の方から「Rubyのイベント」で話が通じており、街全体にも Ruby への理解が溶け込んでいるようでした。プログラミング言語が町おこしの取り組みへと繋がっているのを肌で感じ取れました。

松江駅前のローソン2階にある Ruby Association の「松江オープンソースラボ」

印象に残った発表

両日共にさまざまな方面の方が発表をされていました。1日目は Ruby での現場の開発の話や RBS や ERB 、並列並列プログラミングの実装といった技術に特化した内容が多かったです。
RBS Tutorial
ERB Hacks - Speaker Deck

それに対し2日目フィヨルドブートキャンプさんによる OSS教育についてのお話や、Classi の新卒教育でたいへんお世話になった igaiga さんによる小学生支援を意識したリアルタイムアドバイスツール「RuboSensei」についての発表などがあり、プログラミング教育方面の発表が多くあった印象です。
プログラミングスクールでのOSS教育 - Speaker Deck
RuboSensei - Speaker Deck

特に印象に残ったのは2日目の松江高専の方達による「Matz葉がにロボコンで実践するSmalrubyとmruby/cを活用したプログラミング教育」でした。
小学生向けのプログラミング教育としてロボコン講習会を実施される活動を行っており、小学生の継続した学びの実現を強く意識されて取り組まれているとのこと。他の県でもロボコン講習会という取り組み自体は存在しているようで、それがご当地ロボコンと呼ばれる文化があることも初めて知りました。
参加者集めとして地域の野球チームの子供たちに声をかけたり、高専のほかにも地域コミュニティもイベント運営の手伝いをされていたりするなど、地域ぐるみでプログラミング教育に力を入れているエピソードがとても素晴らしいなと思いました。子供たちが大会で好成績を残した時は大人たちが一緒になって喜んだというほっこりする話もありました。

発表者の方が付けていた蟹の帽子も可愛らしかったです。

(発表資料は見つけられなかったのですが、例年だと12月末に全発表のアーカイブが公開されるようなので、そこで再び見ることができそうです。)

発表会場以外にも大展示場のブースでもランチセッションやスポンサーブースもゆっくり見て回りました。
ESMスーパーライトニングトーカーズ(株式会社永和システムマネジメント)さんたちによる「ESMスーパーライトニングトークス」が高速に情報密度の高いLT をバンバンやっていて迫力がありました。

ESMさんのブースにも立ち寄って Rubyメソッドアクリルキーホルダーガチャを引いてきました。String#encode 、個人的にencodeの文字の並びのバランスが好きなのでちょっと嬉しい。

まとめ

このような感じで、2日間目一杯楽しんできました。
イベント後も、懇親会で Matz氏と一緒に焼肉を食べてお話しを聞く機会があったり、ベテラン Rubyist の方からコードレビューについての考え方を熱弁していただいたりなど楽しい出来事がたくさんありました。
筆者は RubyKaigi を知って Rubyコミュニティの魅力を知りましたが、その切り口から新しいイベントに参加してみたくなった時の一つの選択肢としてぜひ RWC を考えてみてもらいたいです。

来年の5月には RubyKaigi 2024 があります。あわよくば同僚にも参加してもらって Rubyコミュニティの面白さを知ってもらいたいな……と野望を秘めつつ、この文を締めたいと思います。
読んでいただきありがとうございました!

Classiの個別最適化エンジン CALE v2.0リリースまでの進化

はじめに

ClassiのPythonエンジニアで AL(アダプティブラーニング)チームのエンジニアリードの工藤 (id:irisuinwl) です。こんにちは。
最近のマイブームは寿司を握ることです。

さて、今回の記事ではアダプティブラーニングエンジン CALE の進化について書きたいと思います。

CALEとは、ざっくりいえば生徒の解答履歴を分析して能力を推定し、成績向上に役立つ問題を推薦するレコメンドエンジンです。

詳しくは以前の記事を参照ください:

tech.classi.jp

tech.classi.jp

Classiでは今年、新機能である学習トレーニング機能(以下、学トレ)をリリースしました。
学トレの一つに「AIが一人ひとりに合わせたおすすめ問題を提示する」機能があり、そちらでCALEが使われています。

corp.classi.jp

元々学トレリリース前ではWebテスト機能にて個別最適学習を実現するためのレコメンドを行っており、そのロジック実現のためCALE v1.0は利用されていました。

今回、学トレでのおすすめ問題機能を実現するにあたって、CALEを進化させるため、CALE v2.0 を開発しました。

この記事では、CALE v1.0からv2.0へどのように進化したかを紹介します。

  • はじめに
  • 概要
  • 従来のアーキテクチャ
  • アプリケーション基盤の変更
    • 移行を決定した背景
    • 移行作業
  • ユースケース設計とAPI設計の変更
    • 学習トレーニング機能のユースケース設計
    • API設計の見直し
  • データモデリングとストレージの変更
    • データモデリング
    • ストレージ構成の見直し
  • サービス構成の変更
  • まとめ

概要

CALE v1.0 ではリリース後の運用を通して、以下の課題が見つかりました。

  • Kubernetes の運用負担が大きい
  • 複雑なユースケースから生じる複雑なAPIエンドポイントとデータモデル
  • 当初の期待に対して過剰で複雑なストレージ構成とサービス構成

それに対してCALE v2.0では複雑にならないように以下のようにアーキテクチャ変更・開発の工夫をしました。

  • アプリケーション基盤の変更: Google Kubernetes Engine (GKE) から Amazon Elastic Container Service (ECS) へと移行
  • ユースケースの抽出とAPIエンドポイントの簡素化: ユースケースを課題配信・問題解答・レコメンドの3つを抽出、v1では8つあった API エンドポイントをv2では3つに絞り込みました。
  • ストレージ構成の変更: MySQL + Firestore + Google Cloud Storage (GCS) から MySQLのみの構成へと変更
  • サービス構成の統合: APIサービスとレコメンドロジックサービスの分離構成を統合し、モジュラーモノリスへと変更

結果としてシンプルなアーキテクチャを実現することができ、効果として以下のリードタイム短縮とレイテンシー改善をすることが出来ました

  • リリースまでのリードタイム中央値: 289時間 -> 129時間
  • 課題配信API p95 平均レイテンシー: 
    • v1:258ms -> v2: 26.8ms
  • 問題解答API p95 平均レイテンシー: 
    • v1: 413.7ms -> v2: 103.8ms
  • レコメンドAPI p95 平均レイテンシー: 
    • v1: 497.5ms -> v2: 28.6ms
続きを読む

Cloud Composerローカル開発ツールで運用業務を圧倒的に楽にする

こんにちは、データプラットフォームチームでデータエンジニアをしています、鳥山(@to_lz1)です。

本記事は datatech-jp Advent Calendar 2023 11日目の記事です。

データ基盤を運用する皆さま、ジョブの実行管理基盤には何を用いているでしょうか?

様々なOSSやサービスが群雄割拠するこの領域ですが、Google Cloud Platform(以下、GCP) のCloud Composerは選択肢の一つとして人気が根強いのではないかと思います。

一方、同サービスには時折「運用がつらい」とか「費用が高い(故に、開発者が気軽に触れる開発環境を用意できない)」といった言葉も聞かれるように思います*1

自分も入社以来触ってきて、そういった側面があることは完全には否定できないと考えています。しかしそれでも、ツールを用いてそうした負担を大幅に軽減することは可能です。今回はGCPが公式に提供するCloud Composerローカル開発CLIツールを実際の開発フローに導入したことと、それによって得られたメリットをご紹介します。

  • Cloud Composerローカル開発CLIツールについて
  • 導入前の課題
    • Cloud Composer環境の再現がつらい
    • Cloud Composerのアップグレードがつらい
  • 向き不向きに関する考察
    • PyPIパッケージのインストール・検証🙆
    • バージョンアップの検証🙆
    • 向かないと思われる用途
  • ローカル環境構築のスクリプト化
    • 1. Variablesのダミーの準備
    • 2. Poetry Export
    • 導入した結果
      • 残った依存とrenovateとの相性
      • そもそも依存をインストールすべきか?について
  • まとめ
続きを読む

Browserslist + GitHub Packageでサポート対象への対応を簡単にする

こんにちは!エンジニアのすずまさです。

皆さんが開発しているプロダクトでは、サポート対象の OS やブラウザのバージョンは規定されていますか? Classi ではそういった推奨環境が定められています。

ビルド後に推奨環境で動作させるために、社内で GitHub Package として公開された Browserslist の設定ファイルを使ったところ体験が良かったので、今回はその紹介をしようと思います。

Browserslist とは

その名の通り、下記のようなブラウザのリストを記述することで、フロントエンドのツール間でサポートするブラウザのバージョンを共有できるツールです。

last 2 Chrome versions
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR

例えば .browserslistrc に iOS >= 16 と記述してから npx browserslistを使用すると、次の結果が得られます(執筆時点での iOS の最新版は 17.1)。

$ npx browserslist
ios_saf 17.1
ios_saf 17.0
ios_saf 16.6-16.7
ios_saf 16.5
ios_saf 16.4
ios_saf 16.3
ios_saf 16.2
ios_saf 16.1
ios_saf 16.0

ここに出力されたブラウザをサポートするように、他のライブラリに伝えることができます。

Angular CLIでは、内部でこの Browserslist が使われており、Autoprefixer と babel にサポートブラウザを伝える役割を担っています。

なぜ Browserslist + GitHub Package を導入したか

Browserslist + GitHub Package を導入したきっかけは、Angular を v15 から v16 にアップデートしたときに、 Classi で動作保証している端末で画面が一部表示されない障害が起きたことでした。

そのリポジトリでは自前の Browserslist の設定はしておらず、Angular のデフォルト設定をそのまま使用していました。

browserslist.defaults = [
    'last 2 Chrome versions',
    'last 1 Firefox version',
    'last 2 Edge major versions',
    'last 2 Safari major versions',
    'last 2 iOS major versions',
    'Firefox ESR',
];

https://github.com/angular/angular-cli/blob/66b18b654bb47a320e686c4a9c752da64c52830e/packages/angular_devkit/build_angular/src/utils/supported-browsers.ts#L12-L20

Browserslist は caniuse-lite を使ってクエリ用のブラウザ DB を参照しています。

Angular のアップデートに caniuse-lite の更新も含んでいたため、その時新しくリリースされていた iOS 17 が Browserslist に反映されてlast 2 iOS major versionsの指すものが変わってしまい、障害につながってしまいました。

他のリポジトリでも発生しそうだと思い他チームにも共有したら「それなら設定を一箇所にまとめて各リポジトリから使えるようにしたいよね」という話があがり、社内のメンバーが Browserslist 設定用の GitHub Package を作成してくれました。

Browserslist + GitHub Package誕生までのスピード感のある会話

使い方

GitHub Packages の公式ページを参考に Browserslist の config ファイルをインストール後 Browserslist の設定ファイルで extends 構文を使用すれば使うことができます。

"browserslist": [
  "extends browserslist-config-mycompany"
]

https://github.com/browserslist/browserslist#shareable-configs

GitHub Packages を使うために CI の設定ファイルも書き換える必要がありますが、GitHub Actions を使っている場合は下記のようにすると package をインストールできるようになります。

steps:
  - uses: actions/checkout@v4
  - name: "Install npm dependencies"
    uses: actions/setup-node@v3
    with:
      node-version-file: ".node-version"
      registry-url: "https://npm.pkg.github.com"
  - run: yarn install --frozen-lockfile
    env:
      NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

簡単!

最後に

Classi はリポジトリ数が多く設定が分散しやすいので、今回紹介した Browserslist + GitHub Package は今まで使ってこなかったのが不思議なくらい便利でした。

今までサポート対象のブラウザに対応してない記法を使って不具合を起こしてしまった方や、各リポジトリで設定が統一されていないことに課題感を覚えている方がいたらぜひ試してみてください!

ここまで読んでいただきありがとうございました。

今年はAdvent calendarをやらないと決めた話

みなさん、こんにちは。日頃からClassi開発者ブログをご覧いただき、ありがとうございます。編集部のid:tetsuro-ito です。 すっかり秋も深まって寒くなってきました。今年の夏は暑く、冬も暖冬傾向が続くそうですが、寒いものはやはり寒いですね。季節の変わり目は体調を崩しやすい時期なので、注意したいところです。

さて、冬になると、IT業界では恒例のイベントとなったAdvent calendarがさまざまな切り口で開催されます。 Classiも2016年から毎年このイベントの企画をやってきました。

私も入社して以来、毎年必ずこのイベントにはエントリを書いていますが、最初の方では執筆者の確保が難しく、一人で複数記事を投稿することでどうにか企画を成立させていました。その後、エンジニアが多く入社してきたこともあって、一人一記事で25日を埋めることができるようになったり、書かれる内容もエンジニアだけではなく、他の職種のメンバーも書いてもらったりしていて、広がりを見せていました。

また、当時は会社のブログの場所がなかったため、Qiitaのプラットフォームに記事を投稿していましたが、2020年に開発者ブログが誕生した後は、投稿先もこちらにすることができたので、年末は賑わいを見せていました。

メリットとして良い面もたくさんありますが、デメリット部分もいくつかあり、今年は会社のAdvent calendarは実施しないという意思決定を開発者ブログ編集部で行いました。

なぜ今年はAdvent calendarをやらないと決めたか

どうしてこのような意思決定にいたったかのいきさつを少しお話しします。 開発者ブログの編集部では過去に編集長の id:aereal さんが方針を書いています。

停滞した開発者ブログを復活させるまで

そこで開発者ブログを 「より充実した発信環境を求めるClassiの技術者に向けた執筆の場」 とすることにしました。

これをもう少し分解すると:

  • 読み手にとっての価値より書き手にとっての価値を第一に考える
    • 場を作ったり書くことを継続することに踏み出せないでいるメンバーが主な対象
    • そのために慣れた書き手たちが技術ブログを暖めたり、期待値コントロール、レビュアーとしてサポートする
  • 以下のような点は「できたら良いこと」という扱いにして、当面は第一の目的としない
    • 会社のブランディング・認知向上
    • 採用への貢献

このブログの方針は今でも変わっていません。あくまでもClassiの技術者のよりよい発信の場であり、書き手にとっての価値を第一に考えることやブランディングや認知向上の手段は二の次として考えています。 上記の方針と照らして考えた際、Advent calendarの実施やその執筆者集めはイベント的には良いのですが、書き手の”書きたい”を優先的に考えることと相反しているようにも思えました。

また、これまで7年間続けてきた風習のような取り組みですが、惰性で続けてきてしまっている意味合いも少なからずあると思います。季節もののイベントですので、そういうものだと捉えても良いのですが、一度立ち止まってどうしてこの取り組みを実施するかを考え直してみた結果、一度やめてみる選択肢もあるのではないかという観点があがりました。業務の中でもよくある話ですが、一度始めてしまった取り組みをなかなかやめられないことはたくさんあるでしょう。そうした取り組みでも、立ち止まってなぜを考え、改めて取り組む必要があるならばまた再開すればよいだろうと結論づけました。

また、もう一つの理由があります。 それは短期間にたくさん投稿を集中させることによるブログ編集部のレビュー負荷の高まりと、その後の燃え尽き症候群の回避です。

下記は今年の開発者ブログの記事投稿数の月次推移です。

開発者ブログの記事投稿数月次推移

編集部では隔週に1本程度の記事が出るという緩いKPIがあります。月に2本記事が出ていれば、ミニマムのラインは出せている認識です。グラフを見ていただけるとわかりますが、昨年12月はAdvent calendarを実施したおかげで、16本もの記事を出すことができました。

これはこれですごいのですが、編集部のメンバーが個々の記事をレビューしたり、執筆者を集めたりということを行っていたので、非常に負荷が高まる時期になってしまいました。 その反動から2ヶ月記事が出ない状況が続いてしまいました。これは書き手の方のネタも出てしまったという要因もありますが、何よりブログ編集部の燃え尽き症状などもあったのではないかと考えています。 年度末に編集部で振り返りを実施した際に、改めてこの事実を認識し、改善したことで、その後は順調に記事を出せるような状況に戻りました。

このような背景を総合的に加味して、Classiでは今年のAdvent calendarを会社で実施することを見合わせることに決定しました。あくまで会社としてやらないだけで、個々のメンバーがそれぞれの技術領域のAdvent calendarに参加したり、記事を書いたりすることは推奨していますし、このイベント自体を否定するわけではありません。しかし、我々は書き手のことを考え、継続的に発信し続ける場所を維持したいので、このイベントから降りるという選択をしてみたわけです。本ブログでは12月も通常運行を続ける予定ですので、引き続き地に足のついた記事をお届けできれば幸いです。

© 2020 Classi Corp.