[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article:moneyforward-multitenant-saas":3},{"meta":4,"markdown":118},{"type":5,"articleId":6,"slug":7,"title":8,"titleEn":9,"category":10,"summary":11,"publishedAt":12,"image":13,"vocabulary":14},"article","tech-mf-multitenant","moneyforward-multitenant-saas","Money Forwardのマルチテナント設計 — データ分離とセキュリティ","Money Forward's Multi-Tenant Design — Data Isolation and Security","tech","Money Forward's multi-tenant SaaS architecture: row-level vs schema-level isolation tradeoffs, tenant-aware authentication, encryption-at-rest per tenant, resource quotas, and noisy-neighbor mitigation.\n","2026-04-27T00:00:00Z","https:\u002F\u002Fimages.yamiyomi.com\u002Ftech-mf-multitenant.png",[15,20,24,28,32,36,41,45,50,54,58,62,66,70,74,78,82,86,90,94,98,102,106,110,114],{"word":16,"reading":17,"meaning":18,"level":19},"分離","ぶんり","separation\u002Fisolation","N2",{"word":21,"reading":22,"meaning":23,"level":19},"顧客","こきゃく","customer",{"word":25,"reading":26,"meaning":27,"level":19},"行","ぎょう","row",{"word":29,"reading":30,"meaning":31,"level":19},"列","れつ","column",{"word":33,"reading":34,"meaning":35,"level":19},"認証","にんしょう","authentication",{"word":37,"reading":38,"meaning":39,"level":40},"認可","にんか","authorization","N1",{"word":42,"reading":43,"meaning":44,"level":40},"暗号化","あんごうか","encryption",{"word":46,"reading":47,"meaning":48,"level":49},"鍵","かぎ","key","N3",{"word":51,"reading":52,"meaning":53,"level":19},"保存","ほぞん","storage",{"word":55,"reading":56,"meaning":57,"level":40},"漏洩","ろうえい","leak",{"word":59,"reading":60,"meaning":61,"level":19},"分割","ぶんかつ","partition",{"word":63,"reading":64,"meaning":65,"level":19},"共有","きょうゆう","shared",{"word":67,"reading":68,"meaning":69,"level":40},"隔離","かくり","isolation",{"word":71,"reading":72,"meaning":73,"level":40},"騒","そう","noisy",{"word":75,"reading":76,"meaning":77,"level":19},"隣人","りんじん","neighbor",{"word":79,"reading":80,"meaning":81,"level":19},"制限","せいげん","limit",{"word":83,"reading":84,"meaning":85,"level":19},"上限","じょうげん","upper limit",{"word":87,"reading":88,"meaning":89,"level":40},"帯域","たいいき","bandwidth",{"word":91,"reading":92,"meaning":93,"level":19},"利用者","りようしゃ","user",{"word":95,"reading":96,"meaning":97,"level":40},"識別子","しきべつし","identifier",{"word":99,"reading":100,"meaning":101,"level":40},"注入","ちゅうにゅう","injection",{"word":103,"reading":104,"meaning":105,"level":49},"守る","まもる","to protect",{"word":107,"reading":108,"meaning":109,"level":19},"規模","きぼ","scale",{"word":111,"reading":112,"meaning":113,"level":19},"接続","せつぞく","connection",{"word":115,"reading":116,"meaning":117,"level":19},"影響","えいきょう","impact","\n::para\n[Money Forward]{まねーふぉわーど:Money Forward}は[個人]{こじん:personal:N2}[家計]{かけい:household finance:N4}から[企業]{きぎょう:company:N1}[向け]{むけ:for:N3}クラウドサービスまで[幅広い]{はばひろい:wide-ranging:N2}プロダクトを[展開]{てんかい:deploy:N1}しています。[多く]{おおく:many:N4}の[顧客]{こきゃく:customer:N1}が[同じ]{おなじ:same:N4}インフラを[共有]{きょうゆう:share:N3}するマルチテナント[設計]{せっけい:design:N2}は、コスト[効率]{こうりつ:efficiency:N1}と[運用]{うんよう:operations:N4}[負荷]{ふか:load:N2}の[低減]{ていげん:reduction:N2}に[寄与]{きよ:contributing:N3}する[一方]{いっぽう:while:N4}、データ[分離]{ぶんり:isolation:N1}とセキュリティの[難しさ]{むずかしさ:difficulty:N3}が[常に]{つねに:always:N3}つきまといます。\n\n#en\nMoney Forward deploys a wide range of products from personal household finance to enterprise cloud services. While multi-tenant design — where many customers share the same infrastructure — contributes to cost efficiency and reduced operational load, the difficulty of data isolation and security always accompanies it.\n::\n\n::heading\n[行]{ぎょう:row:N5}[レベル]{れべる:level}[分離]{ぶんり:isolation:N1}とスキーマ[レベル]{れべる:level}[分離]{ぶんり:isolation:N1}\n\n#en\nRow-Level vs Schema-Level Isolation\n::\n\n::para\nマルチテナントの[実装]{じっそう:implementation:N2}には[大]{おお:big:N5}きく[二]{ふた:two:N5}つの[流派]{りゅうは:school:N1}があります。[一]{ひと:one:N5}つは[全]{ぜん:all:N3}テナントの[行]{ぎょう:row:N5}を[同じ]{おなじ:same:N4}テーブルに[入れて]{いれて:to put:N5}、`tenant_id`[列]{れつ:column:N3}で[分離]{ぶんり:isolate:N1}する[行]{ぎょう:row:N5}[レベル]{れべる:level}[方式]{ほうしき:method:N3}。もう[一]{ひと:one:N5}つはテナント[毎]{ごと:per:N5}にスキーマやデータベースを[分ける]{わける:to separate:N5}スキーマ[レベル]{れべる:level}[方式]{ほうしき:method:N3}です。[Money Forward]{まねーふぉわーど:Money Forward}はプロダクトの[特性]{とくせい:characteristic:N3}に[応じて]{おうじて:according to:N1}この[両者]{りょうしゃ:both:N3}を[使い分けて]{つかいわけて:to use selectively:N4}います。\n\n#en\nMulti-tenant implementation has two major schools. One is the row-level approach, where all tenants' rows are placed in the same table and isolated by a `tenant_id` column. The other is the schema-level approach, separating schemas or databases per tenant. Money Forward uses both selectively depending on product characteristics.\n::\n\n::callout\n[行]{ぎょう:row:N5}[レベル]{れべる:level}は[安価]{あんか:cheap:N1}だが[漏洩]{ろうえい:leak:N1}リスクが[集中]{しゅうちゅう:concentrated:N4}し、スキーマ[レベル]{れべる:level}は[安全]{あんぜん:safe:N3}だが[運用]{うんよう:operations:N4}コストが[爆発]{ばくはつ:explode:N2}する。[銀]{ぎん:silver:N4}の[弾丸]{だんがん:bullet:N1}はありません。\n\n#en\nRow-level is cheap but concentrates leak risk; schema-level is safer but operational cost explodes. There is no silver bullet.\n::\n\n::heading\nテナント[認識]{にんしき:aware:N3}[認証]{にんしょう:authentication:N1}\n\n#en\nTenant-Aware Authentication\n::\n\n::para\n[認証]{にんしょう:authentication:N1}トークンには[必ず]{かならず:always:N3}テナント[識別子]{しきべつし:identifier:N3}が[埋め込まれて]{うめこまれて:embedded:N2}おり、API[層]{そう:layer:N2}で[受け取った]{うけとった:received:N3}リクエストは[最初]{さいしょ:first:N3}に[現在]{げんざい:current:N3}のテナントコンテキストを[確立]{かくりつ:establish:N3}します。データベースアクセスは[全て]{すべて:all:N3}このコンテキスト[経由]{けいゆ:via:N3}で[行わ]{おこなわ:performed:N5}れ、[他]{た:other:N3}テナントの[行]{ぎょう:row:N5}が[誤って]{あやまって:mistakenly:N3}[読まれる]{よまれる:read:N5}ことがないように[アプリケーション]{あぷりけーしょん:application}[フレームワーク]{ふれーむわーく:framework}[層]{そう:layer:N2}で[強制]{きょうせい:enforced:N3}されます。\n\n#en\nAuthentication tokens always have a tenant identifier embedded, and requests received at the API layer first establish the current tenant context. All database access occurs through this context, and the application framework layer enforces that other tenants' rows cannot be mistakenly read.\n::\n\n::heading\n[行]{ぎょう:row:N5}[レベル]{れべる:level}セキュリティ（RLS）の[活用]{かつよう:utilization:N3}\n\n#en\nLeveraging Row-Level Security (RLS)\n::\n\n::para\nPostgreSQLの[行]{ぎょう:row:N5}[レベル]{れべる:level}セキュリティ[機能]{きのう:feature:N3}を[使う]{つかう:to use:N4}と、データベース[層]{そう:layer:N2}でも[同じ]{おなじ:same:N4}[制約]{せいやく:constraint:N3}を[掛け]{かけ:to apply:N3}られます。[アプリケーション]{あぷりけーしょん:application}コードに[万が一]{まんがいち:in case:N5}バグがあっても、[現在]{げんざい:current:N3}のテナントIDと[一致]{いっち:match:N1}しない[行]{ぎょう:row:N5}は[返さ]{かえさ:returned:N3}れません。「[アプリ]{あぷり:app}でも、DBでも、[両方]{りょうほう:both:N3}で[守る]{まもる:to protect:N3}」という[多]{た:multi-:N4}[層]{そう:layer:N2}[防御]{ぼうぎょ:defense:N2}が[テナント]{てなんと:tenant}[分離]{ぶんり:isolation:N1}の[基本]{きほん:basic:N1}[姿勢]{しせい:stance:N1}です。\n\n#en\nUsing PostgreSQL's row-level security feature, the same constraint can be applied at the database layer too. Even if there is a bug in application code, rows that do not match the current tenant ID are not returned. \"Protect at both the app and the DB\" — this multi-layered defense is the basic stance of tenant isolation.\n::\n\n::heading\nテナント[毎]{ごと:per:N5}の[暗号化]{あんごうか:encryption:N3}[鍵]{かぎ:key:N1}\n\n#en\nPer-Tenant Encryption Keys\n::\n\n::para\n[特に]{とくに:especially:N4}[機密性]{きみつせい:confidentiality:N1}の[高い]{たかい::N5}データは、テナント[毎]{ごと:per:N5}に[異なる]{ことなる:different:N1}[暗号化]{あんごうか:encryption:N3}[鍵]{かぎ:key:N1}で[保存]{ほぞん:store:N1}されます。[鍵]{かぎ:key:N1}[管理]{かんり:management:N2}サービス（KMS）から[取り出した]{とりだした:retrieved:N3}データキーで[保存]{ほぞん:storage:N1}[時]{じ:time:N5}に[暗号化]{あんごうか:encryption:N3}し、[読み出し]{よみだし:read:N5}[時]{じ:time:N5}に[復号]{ふくごう:decryption:N2}します。これによって、[万が一]{まんがいち:in case:N5}データベースの[ダンプ]{だんぷ:dump}が[流出]{りゅうしゅつ:leaked:N3}しても、[攻撃者]{こうげきしゃ:attacker:N1}は[個別]{こべつ:individual:N2}のテナント[鍵]{かぎ:key:N1}を[全て]{すべて:all:N3}[入手]{にゅうしゅ:obtain:N4}しなければ[平文]{ひらぶん:plaintext:N3}にできません。\n\n#en\nParticularly confidential data is stored with a different encryption key per tenant. Data keys retrieved from a key management service (KMS) encrypt at storage time and decrypt at read time. As a result, even if a database dump leaks, an attacker cannot reach plaintext without obtaining every individual tenant key.\n::\n\n::heading\n[騒]{そう:noisy:N1}がしい[隣人]{りんじん:neighbor:N1}[問題]{もんだい:problem:N4}\n\n#en\nThe Noisy Neighbor Problem\n::\n\n::para\n[共有]{きょうゆう:shared:N3}インフラの[宿命]{しゅくめい:fate:N3}として、[一]{いち:one:N5}テナントの[負荷]{ふか:load:N2}が[他]{た:other:N3}テナントの[応答]{おうとう:response:N1}[時間]{じかん:time:N5}に[影響]{えいきょう:impact:N1}を[与える]{あたえる:to give:N3}「[騒]{そう:noisy:N1}がしい[隣人]{りんじん:neighbor:N1}」[問題]{もんだい:problem:N4}があります。[Money Forward]{まねーふぉわーど:Money Forward}は[一]{いち:one:N5}テナント[当たり]{あたり:per:N3}のリクエスト[速度]{そくど:rate:N3}、[同時]{どうじ:concurrent:N4}[接続]{せつぞく:connection:N2}[数]{すう:count:N3}、CPU[時間]{じかん:time:N5}に[上限]{じょうげん:upper limit:N3}を[設けて]{もうけて:to set:N2}、[特定]{とくてい:specific:N3}テナントが[全]{ぜん:all:N3}リソースを[占有]{せんゆう:monopolize:N2}することを[防いで]{ふせいで:prevent:N2}います。\n\n#en\nAs a fate of shared infrastructure, the \"noisy neighbor\" problem exists where one tenant's load impacts other tenants' response times. Money Forward sets upper limits on per-tenant request rates, concurrent connection counts, and CPU time to prevent any specific tenant from monopolizing all resources.\n::\n\n::heading\n[大口]{おおぐち:large:N4}[顧客]{こきゃく:customer:N1}の[専用]{せんよう:dedicated:N2}クラスタへの[隔離]{かくり:isolation:N1}\n\n#en\nIsolating Large Customers to Dedicated Clusters\n::\n\n::para\n[特に]{とくに:especially:N4}[巨大]{きょだい:huge:N2}な[利用]{りよう:usage:N3}[規模]{きぼ:scale:N1}を[持つ]{もつ:to have:N4}[顧客]{こきゃく:customer:N1}は、[共有]{きょうゆう:shared:N3}クラスタから[切り離して]{きりはなして:to detach:N1}[専用]{せんよう:dedicated:N2}の[実行]{じっこう:execution:N3}[環境]{かんきょう:environment:N1}に[隔離]{かくり:isolate:N1}します。これは[他]{た:other:N3}テナントを[守る]{まもる:to protect:N3}と[同時]{どうじ:simultaneously:N4}に、[大口]{おおぐち:large:N4}[顧客]{こきゃく:customer:N1}に[安定]{あんてい:stable:N3}した[性能]{せいのう:performance:N3}を[届ける]{とどける:to deliver:N2}ためでもあります。「[全員]{ぜんいん:all:N3}に[同じ]{おなじ:same:N4}[体験]{たいけん:experience:N4}」ではなく「[規模]{きぼ:scale:N1}に[応じた]{おうじた:appropriate:N1}[配置]{はいち:placement:N3}」が[現実]{げんじつ:realistic:N3}[解]{かい:solution:N3}です。\n\n#en\nCustomers with especially huge usage scale are detached from the shared cluster and isolated in dedicated execution environments. This both protects other tenants and delivers stable performance to large customers. The realistic solution is not \"the same experience for everyone\" but \"placement according to scale.\"\n::\n\n::heading\n[監査]{かんさ:audit:N1}ログとアクセス[記録]{きろく:record:N2}\n\n#en\nAudit Logs and Access Records\n::\n\n::para\nどのテナントの、どのリソースに、[誰]{だれ:who:N3}が、いつアクセスしたかは[全て]{すべて:all:N3}[記録]{きろく:recorded:N2}されます。[内部]{ないぶ:internal:N3}[運用]{うんよう:operations:N4}[担当者]{たんとうしゃ:person in charge:N2}による[操作]{そうさ:operation:N1}も[例外]{れいがい:exception:N3}ではなく、テナントオーナーが[後]{あと:later:N5}から[内部]{ないぶ:internal:N3}アクセスを[確認]{かくにん:verify:N3}できる[仕組み]{しくみ:mechanism:N3}が[整備]{せいび:established:N1}されています。これは[GDPR]{ぴーぴーあーる:GDPR}や[個人]{こじん:personal:N2}[情報]{じょうほう:info:N3}[保護]{ほご:protection:N1}[法]{ほう:law:N3}など[各国]{かっこく:each country:N2}の[規制]{きせい:regulation:N3}への[対応]{たいおう:compliance:N1}にも[直結]{ちょっけつ:directly tied:N1}します。\n\n#en\nAll accesses — which tenant, which resource, by whom, and when — are recorded. Operations by internal staff are no exception, and a mechanism is established for tenant owners to verify internal accesses afterward. This is also directly tied to compliance with regulations in each country, such as GDPR and personal information protection laws.\n::\n\n::heading\n[共有]{きょうゆう:shared:N3}キャッシュの[危険]{きけん:risk:N3}\n\n#en\nRisks of Shared Caches\n::\n\n::para\nRedisやMemcachedなど[共有]{きょうゆう:shared:N3}キャッシュを[使う]{つかう:to use:N4}[際]{さい:occasion:N3}、キャッシュキーにテナントIDを[含めない]{ふくめない:not include:N2}と[他]{た:other:N3}テナントのデータが[誤って]{あやまって:mistakenly:N3}[返される]{かえされる:returned:N3}という[致命的]{ちめいてき:fatal:N1}なバグが[発生]{はっせい:occur:N4}します。[Money Forward]{まねーふぉわーど:Money Forward}では[全て]{すべて:all:N3}のキャッシュキーに[テナントプレフィックス]{てなんとぷれふぃっくす:tenant prefix}を[強制]{きょうせい:enforce:N3}し、[ライブラリ]{らいぶらり:library}[層]{そう:layer:N2}で[誤った]{あやまった:incorrect:N3}[使い方]{つかいかた:usage:N4}を[防いで]{ふせいで:prevent:N2}います。\n\n#en\nWhen using shared caches like Redis or Memcached, omitting the tenant ID from the cache key produces a fatal bug where another tenant's data is mistakenly returned. Money Forward enforces tenant prefixes on all cache keys and prevents incorrect usage at the library layer.\n::\n\n::heading\nおわりに\n\n#en\nConclusion\n::\n\n::para\nマルチテナントSaaSの[設計]{せっけい:design:N2}は、[単純な]{たんじゅんな:simple:N2}「データを[分ける]{わける:to separate:N5}」[作業]{さぎょう:work:N4}ではありません。[認証]{にんしょう:authentication:N1}、[認可]{にんか:authorization:N3}、データ[層]{そう:layer:N2}、キャッシュ[層]{そう:layer:N2}、[暗号化]{あんごうか:encryption:N3}、リソース[制限]{せいげん:limit:N3}、[監査]{かんさ:audit:N1}という[全]{ぜん:all:N3}レイヤーに「テナントを[意識]{いしき:awareness:N3}する」[設計]{せっけい:design:N2}を[一貫]{いっかん:consistent:N1}して[適用]{てきよう:apply:N3}する[必要]{ひつよう:necessity:N3}があります。[一]{ひと:one:N5}つでも[抜け]{ぬけ:gap:N3}があれば、そこから[漏洩]{ろうえい:leakage:N1}が[発生]{はっせい:occur:N4}し、[企業]{きぎょう:company:N1}の[信頼]{しんらい:trust:N3}が[一夜]{いちや:overnight:N4}にして[失われる]{うしなわれる:lost:N3}のです。\n\n#en\nMulti-tenant SaaS design is not a simple task of \"separating data.\" Tenant-aware design must be consistently applied across every layer — authentication, authorization, data layer, cache layer, encryption, resource limits, and auditing. If even one gap exists, leakage occurs there, and a company's trust can be lost overnight.\n::\n"]