こんにちは。バックエンドエンジニアの松嶋です。
2024年10月25日から10月26日の2日間、Kaigi on Rails 2024が開催され、弊社はRubyスポンサーとしてスポンサーLTとブース出展の機会をいただきました。
初めてのブース出展でもあり、不慣れな部分もあったかもしれませんが、このような貴重な機会をいただけて、たくさんの方々が私たちのブースに遊びにきてくださったこと、とても感謝しています。
ブース出展の内容については、同じくバックエンドエンジニアのid:rakutekがブログを書いているので、こちらを御覧ください。
私は、初日の25日にスポンサー枠で「クラシルの現在とこれから」というタイトルで発表させていただきました。500人以上の多くの方々の前で話すことは初めてだったのでとても緊張していましたが、無事何事もなく終えられたので安堵しました。登壇後には感想や質問もいただき、とても嬉しかったです。
今回の発表では、次の3つのテーマについてお話ししました:
- クラシルの規模感
- クラシルのアーキテクチャやその背景
- 最近の技術的な取り組み
これらのテーマを選んだ理由は、クラシルというサービスは広く知られているものの、実際にその裏側を支えるアプリケーションやシステムについては積極的に発信してこなかったため、あまり知られていないのではないかという思いがあったからです。
そこで、サービス開始から9年目を迎えたクラシルが現在どうなっているのか、歴史的背景を踏まえながら、参加者の皆さんに知ってもらうことを目的としました。
本ブログは、発表時間の都合上伝えきれなかった部分も補足しながら、改めてクラシルの「現在」について紹介させてください。
rails stats
発表ではspecの部分を省略していましたが、こちらがrails stats
の全貌です。
総コード行数は、約41万の大規模なアプリケーションです。Chirashi
という接頭語のディレクトリがありますが、それらはRails Engine
が活用されている部分で、一部モジュール化しています。Chirashi
というのは、クラシルとは別にクラシルチラシというサービスを運営しており、それらはクラシルに強く依存していること、クラシルほど機能を多く必要としなかったことから、Rails Engine
で実装されています。
また、3年前のrails stats
結果が残っていたので比較してみると、約2.4倍に増加していることから、活発に開発が行われてきたことが分かるかと思います。
クラシルはネイティブアプリのイメージが強いかと思いますが、実はWeb版も存在しており、Vue.js
が使われています。そのため、JavaScript
のコード量が多くなっています。
ただ、Web版のクラシルは負債が結構溜まってきているため、現在リアーキテクチャを進めています。最終的には、Rails依存している部分をなくし、Node.js + React
で構成される状態を目指しています。こちらの詳細については、弊社のフロントエンドエンジニアがブログ化する予定なので、楽しみにしていてください。
+----------------------+--------+--------+---------+---------+-----+-------+ | Name | Lines | LOC | Classes | Methods | M/C | LOC/M | +----------------------+--------+--------+---------+---------+-----+-------+ | Controllers | 19799 | 16071 | 402 | 1607 | 3 | 8 | | Helpers | 623 | 489 | 1 | 78 | 78 | 4 | | Jobs | 2191 | 1932 | 39 | 108 | 2 | 15 | | Models | 26344 | 16450 | 353 | 1232 | 3 | 11 | | Mailers | 139 | 111 | 8 | 12 | 1 | 7 | | View | 1050 | 1004 | 0 | 0 | 0 | 0 | | Stylesheets | 27373 | 24170 | 0 | 0 | 0 | 0 | | JavaScript | 122266 | 109419 | 0 | 1 | 0 | 109417 | | Libraries | 14096 | 11500 | 194 | 727 | 3 | 13 | | Applibraries | 8043 | 6504 | 160 | 673 | 4 | 7 | | Batches | 3338 | 2767 | 53 | 229 | 4 | 10 | | Forms | 2478 | 2035 | 26 | 241 | 9 | 6 | | Services | 11143 | 9062 | 343 | 876 | 2 | 8 | | Serializers | 6452 | 5116 | 154 | 744 | 4 | 4 | | Uploaders | 1456 | 1163 | 45 | 141 | 3 | 6 | | Chirashi Controllers | 1734 | 1459 | 50 | 185 | 3 | 5 | | Chirashi Helpers | 337 | 266 | 1 | 58 | 58 | 2 | | Chirashi Mailers | 57 | 38 | 5 | 3 | 0 | 10 | | Chirashi Views | 58 | 50 | 0 | 0 | 0 | 0 | | Chirashi Stylesheets | 10185 | 8305 | 0 | 0 | 0 | 0 | | Chirashi JavaScript | 5057 | 4630 | 0 | 0 | 0 | 0 | | Chirashi Libraries | 66 | 57 | 4 | 5 | 1 | 9 | | Chirashi Forms | 274 | 221 | 5 | 29 | 5 | 5 | | Chirashi Services | 1738 | 1423 | 50 | 141 | 2 | 8 | | Chirashi Serializers | 612 | 521 | 23 | 53 | 2 | 7 | | Batch specs | 4509 | 3918 | 0 | 0 | 0 | 0 | | Controller specs | 6967 | 5873 | 0 | 7 | 0 | 837 | | Form specs | 4642 | 4143 | 1 | 0 | 0 | 0 | | Helper specs | 973 | 737 | 0 | 3 | 0 | 243 | | Job specs | 2782 | 2404 | 0 | 0 | 0 | 0 | | Library specs | 8944 | 7500 | 0 | 4 | 0 | 1873 | | Applibrary specs | 12988 | 11073 | 1 | 1 | 1 | 11071 | | Mailer specs | 438 | 397 | 1 | 1 | 1 | 395 | | Model specs | 24597 | 19720 | 7 | 7 | 1 | 2815 | | Request specs | 66331 | 54195 | 18 | 30 | 1 | 1804 | | Serializer specs | 3936 | 3269 | 6 | 2 | 0 | 1632 | | Service specs | 5090 | 4320 | 0 | 0 | 0 | 0 | | Chirashi Controller specs | 1460 | 1204 | 0 | 0 | 0 | 0 | | Chirashi Helper specs | 57 | 50 | 0 | 0 | 0 | 0 | | Chirashi Mailer specs | 200 | 176 | 0 | 0 | 0 | 0 | | Chirashi Library specs | 21 | 19 | 0 | 0 | 0 | 0 | | Chirashi Form specs | 312 | 274 | 0 | 0 | 0 | 0 | | Chirashi Service specs | 1300 | 1140 | 0 | 0 | 0 | 0 | | Chirashi Serializer specs | 38 | 32 | 0 | 0 | 0 | 0 | +----------------------+--------+--------+---------+---------+-----+-------+ | Total | 412494 | 345207 | 1950 | 7198 | 3 | 45 | +----------------------+--------+--------+---------+---------+-----+-------+ Code LOC: 224763 Test LOC: 120444 Code to Test Ratio: 1:0.5
クラシルを支えるアーキテクチャ
クラシルはAWS上で構築されており、ID認証基盤とクラシル本体で分かれています。アカウント間はTransit Gateway経由で通信しています。 また、アプリケーションはECS Fargate上で動いています。
現在は、マネージドサービスを中心に利用していますが、以前は自前運用していたものが多く存在していました。しかし、それらが属人化したり、ブラックボックス化したことで、障害の温床になっていたため、ここ数年でマネージドサービスに移行しました。
LTでは詳しい説明を割愛しましたが、どのようなものを自前運用からマネージドサービスに移行したのか、代表的なものを4つ挙げて紹介したいと思います。
1. 動画変換基盤
以前、弊社の動画変換基盤は、ffmpegを用いた自社基盤(AWS Batch)が使われていました。
自社基盤の動画変換フローは以下のようなものでした。
S3に動画ファイルがアップロードされるとSNSに通知が飛び、Lambdaがフックされます。LambdaからAWS Batchのジョブが作成され、ECS上で動画変換が実行された後、再びS3に変換後のファイルがアップロードされます。
また、自社基盤はモニタリングが十分に行われておらず、対応できる動画フォーマットも限られているという課題もありました。これらを改善しながら保守していく選択肢もありましたが、非機能要件の面でMediaConvertの方が変換速度・拡張性・保守性の観点で優れていると判断し、移行しました。
現在は、動画が投稿されるとSQSにジョブがエンキューされ、shoryuken (ECS Fargate) によりMediaConvertのジョブが作成されて動画変換が行われています。
2. 検索基盤
以前はElasticsearchをEC2上にセルフホストして検索基盤を運用していたのですが、TV放送等で負荷が高まる場合は、事前にスケールアウトする設定を手動で行う必要がありました。例えば、Elasticsearchが動いているEC2にログインし、インデックスのレプリカ数の変更やシャードの配置確認等をコマンドラインで行っていました。検索機能はユーザーによく使われる機能の1つであり、手順を間違えて障害を起こしてしまうと影響が大きいため、かなり神経をすり減らす作業でもありました。
そのような背景から、Elastic Cloudを採択しました。Elastic Cloudの良い点は、運用負荷が軽減される以外にも、Elastic社の日本法人によるサポートが手厚かったり、最新バージョンへの追従が容易であるところです。
また、自前運用していた時より、レイテンシの悪化が懸念されましたが、Elastic CloudはAWS上で動かすことができ、Traffic Filterを使うことでプライベート接続が可能なため、移行後も安定しています。
3. バッチ処理
以前は専用のバッチ処理サーバー(EC2)でRailsアプリケーションが稼働し、wheneverでcron jobを管理していました。
しかし、データ量の増加に伴い、バッチ処理の需要が増加してきた一方で、サーバーの冗長化が難しく、単一障害点となっていました。また、処理が重いバッチが同時刻に実行されると、サーバーのCPUやメモリが逼迫し、他のバッチ処理に影響が出ることもあったため、開発者が実行タイミングを慎重に調整する必要がありました。
これらの課題を解決するために、EventbridgeとStep Functionsを組み合わせて、ECS run taskをスケジュール実行する方式に移行しました。また、Eventbridgeはat-least one
配信であるためバッチ処理の冪等性を担保する必要がありましたが、Eventbridgeのペイロードにコンテキスト属性である<aws.scheduler.execution-id>
を渡すことで*1、これを実現しています。
4. DBマイグレーション
以前のデプロイフローは、バッチサーバー上にマイグレーションコマンドが書かれたデプロイスクリプトが置いてあり、マイグレーションが必要な場合は先にバッチサーバーへデプロイすることで、DBのスキーマ変更に対応していました。
しかし、デプロイの順番を間違えたり忘れたりしてしまうと障害につながってしまうため、安全性が低いという課題がありました。
まずは、全てのデプロイパイプラインにマイグレーション工程を組み込み、どのデプロイでもマイグレーションが確実に実行されるようにしました。しかし、この方法では安全性は高まったものの、デプロイ時間が長くなり、開発生産性が低下しました。
本来であれば、スキーマ変更が必要な場合のみマイグレーションが実行されるべきなので、最終的にはGithub Actionsでリリースブランチとのdiffを取り、マイグレーションファイルが追加された時だけECS run taskでマイグレーションを実行するデプロイフローになりました。
その結果、安全性と開発生産性の両立が実現し、SREと開発者の両者の負担が軽減されました。
最近の技術的な取り組みについて
最後に、最近の技術的な取り組みの1つとして、AWS Bedrockを用いてUGCコンテンツの品質管理の仕組みを構築した話をしました。
クラシルは、2年前にブランドリニューアルを行い、ユーザーもレシピ投稿可能になりました。 これにより、ユーザーの多様な食の好みや要望に応えることが可能となりましたが、「品質管理」と「データ構造」の2つの大きな課題が浮き彫りとなりました。
一般投稿によりレシピの幅が広がる一方で、レシピではないものや、情報不足のコンテンツが投稿されることがあり、品質管理が難しくなっていました。 また、フリーフォーマットで投稿されるため、データが構造化されておらず、レシピの特徴を把握しづらいという問題もありました。
そこで私たちは、AWSのBedrock API(Anthropic社のClaude3 sonnetモデル)をRailsアプリケーションに組み込み、レシピ基準を満たしているかを自動判定し、レシピデータを構造化する仕組みを構築しました。
多くのLLMが存在する中でClaude3 sonnetを採用したのは、以下の3点です。
- コスト・精度・パフォーマンスの観点で比較検証した結果、私たちの要件を最も満たしていたこと
- Bedrockを介してinvoke model APIを実行できるため、AWSのIAMによる細かな権限管理が可能であること
- マルチリージョンでリクエストを分散し可用性を担保できたり、AWS SDKに組み込まれているリトライ機構を利用することで自前実装やライブラリ導入が不要で、4xx or 5xxエラーは自動リトライ可能だったりと*2、AWSの機能を最大限活用することで可用性を維持しながら素早く開発できること
また、レシピ判定とデータ構造化の工程を1つにまとめず、別々のタスクに分割し、個別のLLMで処理する設計としました。これは、1つにまとめると指示が冗長になり精度が低下してしまうためです。
リクエストが2回に増えるためコスト増が懸念されましたが、レシピ判定に必要なテキストデータから余分な文字列(ハッシュタグやURLなど)を取り除き、消費トークン数を抑えることで、コストは許容範囲内に収まっています。
上記のような弊社の取り組みは、AWSの事例として取り上げていただきました。嬉しい!
最後に
改めてになりますが、Kaigi on Rails 2024という貴重な場でLTの機会をいただきまして、本当にありがとうございました。
弊社は、今後も積極的に開発者コミュニティに貢献していく予定です。
delyは開発者コミュニティに貢献し、今後更に強い開発チームを作りたいと考え、開発系のイベントに積極的にスポンサーになっていきます。delyチームにどんどんお声がけください🙌
— Yusuke Horie/dely (@yusuke_horie) 2024年10月28日
delyはKaigi on Rails 2024にRubyスポンサーとして参加しました! - dely Tech Blog https://t.co/rm3Zp3tHVx
また、バックエンドエンジニアを含む各種エンジニアを大募集しています。
エンジニア採用情報まとめはこちらから! delyjp.notion.site
バックエンドエンジニア herp.careers
シニアバックエンドエンジニア herp.careers
SRE herp.careers
26年度の新卒エンジニア用特設サイトはこちら dely.jp