はじめに
hacomono VP of Platform Engineering のやじ(@srv)です。気がつくと家に車が3台ありました。
今回は「マルチテナントへの道 Provisioning 偏」と題しまして、マルチテナント化のプロビジョニングに関するお話をさせていただこうと思います。
TL;DR
- Rails のマルチテナント化は apartment gem を利用して実現
- テナントグループという単位で Rails API を分割
- テナント追加等の日々のオペレーションは Kickflow 経由で実行
Rails のマルチテナント化
マルチテナント環境では、複数のテナントが同じアプリケーションにアクセスしますが、データは分離されている必要があります。hacomono では、Rails のマルチテナント化を実現するために、apartment gem を利用しています。apartment gem は、スキーマベースのマルチテナントアーキテクチャを簡単に構築できるため、効率的にテナントごとにデータを分離することができます。
graph LR A((Rails アプリケーション)) --> C[apartment gem] C --> D1(テナント1 スキーマ) C --> D2(テナント2 スキーマ) D1 --> E1{テーブル1} D1 --> E2{テーブル2} D2 --> E3{テーブル1} D2 --> E4{テーブル2}
マルチテナントアーキテクチャとその種類
マルチテナントアーキテクチャは、一つのシステムで複数の顧客(テナント)が同時に利用できる構造を実現するアプローチです。主に3つのアーキテクチャがあり、それぞれ「共有スキーマ」、「専用スキーマ」、「専用データベース」です。
共有スキーマ
すべてのテナントが同じスキーマを共有し、データはテナントIDで区別されます。
graph LR A((Rails アプリケーション)) --> C[データベース] C --> D{テーブル1} C --> E{テーブル2} D --> F1(テナント1 データ) D --> F2(テナント2 データ) E --> G1(テナント1 データ) E --> G2(テナント2 データ)
専用スキーマ
各テナントに専用のスキーマが割り当てられ、データが分離されます。
graph LR A((Rails アプリケーション)) --> C[データベース] C --> D1(テナント1 スキーマ) C --> D2(テナント2 スキーマ) D1 --> E1{テーブル1} D1 --> E2{テーブル2} D2 --> E3{テーブル1} D2 --> E4{テーブル2}
専用データベース
各テナントに独立したデータベースが提供され、データの分離が最も高いレベルで実現されます。
graph LR A((Rails アプリケーション)) A --> C1(データベース1) A --> C2(データベース2) C1 --> D1{テーブル1} C1 --> D2{テーブル2} C2 --> D3{テーブル1} C2 --> D4{テーブル2}
Rails でマルチテナントアプリケーションを構築する際に利用される apartment gemは、データベースのスキーマを利用したマルチテナントアーキテクチャを実現します。apartment gemは、テナントごとに独立したスキーマを作成し、テナント間でデータを分離することができます。これにより、開発者はテナントごとのデータ管理を容易に行うことができ、アプリケーションのパフォーマンスとセキュリティも向上します。
apartment gem の採用理由
hacomono では元々、シングルテナント方式でサービスを提供しておりました。そのためマルチテナント化を行うための仕組みが出来ておらず、この中で逐次的にシングルテナントからマルチテナントへ移行するため並行稼動が可能なアーキテクチャが求められました。そこで Rails に対してほとんど変更を加える事なくマルチテナント化が可能なapartment gemを採用しました。
シングルテナント
graph LR A((Rails アプリケーション)) --> C[データベース1] C --> D{テーブル1} C --> E{テーブル2} D --> F1(テナント1 データ) E --> G1(テナント1 データ) A2((Rails アプリケーション)) --> C2[データベース2] C2 --> D2{テーブル1} C2 --> E2{テーブル2} D2 --> F21(テナント2 データ) E2 --> G21(テナント2 データ)
マルチテナント
graph LR A((Rails アプリケーション)) --> C[apartment gem] C --> D1(テナント1 スキーマ) C --> D2(テナント2 スキーマ) D1 --> E1{テーブル1} D1 --> E2{テーブル2} D2 --> E3{テーブル1} D2 --> E4{テーブル2}
テナントグループ
データベースのテーブル数を一定以下に抑えるため、hacomono ではテナントグループという単位で Rails API を分割しています。これにより、各テナントグループごとに独立したデータベースを持ち、テーブル数の肥大化を防ぐことができます。
テナントとテナントグループのマッピングや、どの様に通信を振り分けるかについては過去のブログ記事をご参照頂ければと思います。
テナントグループの追加と Terraform
テナントグループの追加は、Terraform を用いて実行しています。Terraform を利用することで安全かつ効率的にテナントグループを追加することができます。
Terraform の実行と StepFunctions
%%{init:{'theme':'base'}}%% sequenceDiagram actor User as User participant SF as StepFunctions participant ECST as ECS Task participant Terraform as Terraform User->>SF: Start Execution SF->>ECST: Launch ECS Task ECST->>Terraform: Run Terraform command Terraform->>Terraform: Apply changes
Terraform は、AWS StepFunctions 経由で ECS 上で実行されています。StepFunctions は、複数の AWS サービスを組み合わせて分散型アプリケーションのワークフローを構築できるサービスです。この機能を利用して、Terraform を ECS タスクとして実行し、インフラストラクチャの変更を自動化しています。
また、Terraform の実行に失敗した場合も StepFunctions の機能を利用して一定回数再実行される様になっており、一時的な障害により構築に失敗している場合も自動で回復します。
テナントグループへのテナント追加と Rake Task
テナントグループへのテナント追加は、Rake Task を StepFunctions 経由の ECS タスクで実行しています。内部でテナントの統計情報を持っており、統計情報を使って適切なテナントグループが自動で選択される様になっております。Rake Task では apartment gem のテナント作成機能を呼び出しています。
日々のオペレーションと Kickflow
テナント追加やその他の日々のオペレーションは、Kickflow という社内のワークフロー基盤を利用して実行されています。Kickflow はエンジニアの介入なしでワークフローを実行できるため、エンジニアは開発に集中できるようになります。
その他の業務に関しても Slack Workflow 経由での ChatOps による運用を行っております。
まとめ
今回は、「マルチテナントへの道 Provisioning 偏」と題して、hacomono のマルチテナントアーキテクチャのプロビジョニングに関する話をさせていただきました。apartment gem を用いた Rails のマルチテナント化や、Terraform と StepFunctions を利用したインフラストラクチャの管理、そして Kickflow による日々のオペレーションの効率化など様々な技術を組み合わせて、マルチテナント環境を効率的に運用しています。
今後も、引き続きモニタリングやオートスケーリングに関する仕組みを紹介し、最後に hacomono の次のアーキテクチャの計画についても触れさせていただこうと予定しております。
それでは、次回の投稿もお楽しみに!
Special Thanks
今回のブログは多岐にわたり ChatGPT-4 に協力いただいて書いております。アウトラインからの文章作成、並びに Mermaid による図の作成は全て ChatGPT-4 の力によるものです。
こちらのブログ作成に関しては以下のブログで紹介しております。