<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>Architecture · 仁才徳</title>
    <link>https://jared.lynskey.co.nz/ja/tags/architecture/</link>
    <description>ソフトウェアエンジニアリングと開発組織のリーダーシップについて。評価、採用とチームの拡大、CI/CD、DevOps モニタリング、エージェンティック AI。ソウルより。</description>
    <generator>Hugo</generator>
    <language>ja</language>
    <copyright>© 2026 仁才徳</copyright>
    <lastBuildDate>Wed, 28 Oct 2026 00:00:00 +1300</lastBuildDate>
    <atom:link href="https://jared.lynskey.co.nz/ja/tags/architecture/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>意思決定ドキュメントとは？ADRの例とテンプレート付き実践ガイド</title>
      <link>https://jared.lynskey.co.nz/ja/posts/2024/2024-12-03-decision-docs/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/ja/posts/2024/2024-12-03-decision-docs/</guid>
      <pubDate>Tue, 03 Dec 2024 00:00:00 +0000</pubDate>
      <dc:creator>Jared Lynskey</dc:creator>
      <category>decision-making</category>
      <category>documentation</category>
      <category>ADR</category>
      <category>architecture</category>
      <category>team alignment</category>
      <description>意思決定ドキュメント（意思決定文書）とは、決めたその時点の背景、検討した選択肢、それを選んだ理由を書き残す文書です。半年後に同じ議論を蒸し返さないために。実際に使われるADRやRFCの書き方を、ADRの例とそのまま使えるテンプレート付きで解説します。</description>
      <content:encoded><![CDATA[<p>どんなチームも意思決定をします。そして、ほとんどのチームは、なぜその決定をしたのかを忘れます。半年後に誰かが「なんでMongoDBじゃなくてPostgreSQLにしたんだっけ？」と言い出し、当時の制約を覚えている人は誰もおらず、同じ議論がもう一度始まります。しかも大抵、前回より少ない顔ぶれで。</p>
<p>意思決定文書はこれを解決します。ADRでもRFCでも決定ログでも、呼び方は何でも構いません。大事なのは、選択をしたその瞬間に、背景と選択肢と理由を書き残すことです。今年、開発チームを率いる立場になってから、そのありがたみを以前にも増して感じています。組織の記憶になるか、組織の言い伝えになるかの分かれ目です。</p>
<hr>

<h3 class="relative group">手間をかける価値がある理由
    <div id="手間をかける価値がある理由" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e6%89%8b%e9%96%93%e3%82%92%e3%81%8b%e3%81%91%e3%82%8b%e4%be%a1%e5%80%a4%e3%81%8c%e3%81%82%e3%82%8b%e7%90%86%e7%94%b1" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>わかりやすい見返りは、同じ議論を二度しなくて済むことです。「APIのバージョニング戦略って、もう決めましたよね？」決定内容と理由の記録がはっきり残っていれば、決着済みの問題は決着済みのままで、チームは来た道を辿り直す代わりに実行にエネルギーを使えます。</p>
<p>もうひとつの見返りは、時間が経ってから効いてきます。プロジェクトは何ヶ月も何年も続き、人は入れ替わり、最後に残るのはコードです。ただしコードが教えてくれるのは「何を」決めたかだけで、「なぜ」は決して語りません。意思決定文書は、当時あった制約、却下された代替案、誰が判断したかという推論の部分を保存します。新しいメンバーは言い伝えではなくコンテキストを手に入れます。チーム横断の決定は、結論だけでなく検討された要素まで全員に見えるので、本当の納得を得やすくなります。そして意思決定者と貢献者に名前が付くことで、実行にもちゃんと持ち主ができます。</p>
<hr>

<h3 class="relative group">いつ書くか
    <div id="いつ書くか" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%81%84%e3%81%a4%e6%9b%b8%e3%81%8f%e3%81%8b" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>すべての決定に文書が要るわけではありません。私が書くのは、決定が次のどれかに当てはまるときです。</p>
<ul>
<li><strong>後戻りが高くつく</strong> — 技術スタックの選定（フレームワーク、データベース、クラウドプロバイダー）、アーキテクチャパターン（マイクロサービスかモノリスか、イベント駆動かリクエスト・レスポンスか）、サードパーティベンダーの選定、セキュリティ・コンプライアンスの方針</li>
<li><strong>影響が長く残る</strong> — 外部システムが依存するAPI契約、複数サービスにまたがるデータモデル、確立されたパターンへの破壊的変更</li>
<li><strong>チームをまたぐ</strong> — 複数チームや非技術系ステークホルダーの合意が要るもの、リソース配分や優先順位の判断</li>
<li><strong><a href="https://jared.lynskey.co.nz/ja/posts/2023/2023-10-18-spike/" >スパイク</a>の結論</strong> — タイムボックス付きの調査が終わったら、発見と決定が蒸発する前に文書に残す</li>
</ul>
<p>逆に、簡単に元へ戻せる決定、チーム内部の実装詳細、自分のスコープの外にほぼ影響しない選択には、形式張った文書は不要です。誰にも必要とされない文書は、ただの宿題です。</p>
<hr>

<h3 class="relative group">ライフサイクル
    <div id="ライフサイクル" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%83%a9%e3%82%a4%e3%83%95%e3%82%b5%e3%82%a4%e3%82%af%e3%83%ab" aria-label="アンカー">#</a>
    </span>
    
</h3>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
</span></span><span class="line"><span class="cl">│   ドラフト   │ ──► │   レビュー   │ ──► │    承認     │ ──► │    実装     │
</span></span><span class="line"><span class="cl">│    提案     │     │ フィードバック│     │    受理     │     │    完了     │
</span></span><span class="line"><span class="cl">└─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘
</span></span><span class="line"><span class="cl">                           │
</span></span><span class="line"><span class="cl">                           ▼
</span></span><span class="line"><span class="cl">                    ┌─────────────┐
</span></span><span class="line"><span class="cl">                    │   却下/     │
</span></span><span class="line"><span class="cl">                    │   廃止     │
</span></span><span class="line"><span class="cl">                    └─────────────┘</span></span></code></pre></div></div>
<p>著者が問題と候補案をドラフトにまとめ、ステークホルダーがレビューします。この作業の元が取れるのはレビュー段階です。前提が疑われ、盲点が表に出てくるのはここなので。そのうえで意思決定者（多くはテックリード、アーキテクト、プロダクトオーナー）が最終判断を下し、チームが実装します。実装中に現実に合わせて変えた部分があれば、文書も更新します。レビューで死ぬ文書もあれば、状況が変わって後から廃止される文書もあります。どちらも明確にマークしてください。「承認済み」のまま古びた文書は、次に見つけた人への罠になります。</p>
<hr>

<h3 class="relative group">読む価値のある文書にするには
    <div id="読む価値のある文書にするには" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e8%aa%ad%e3%82%80%e4%be%a1%e5%80%a4%e3%81%ae%e3%81%82%e3%82%8b%e6%96%87%e6%9b%b8%e3%81%ab%e3%81%99%e3%82%8b%e3%81%ab%e3%81%af" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>お気に入りの解決策ではなく、問題から始めます。何を解決しようとしているのか、なぜ今それが重要なのか、何もしなければどうなるのか。問題認識が揃っていないまま解決策を議論しても、それはただの演劇です。</p>
<p>次に、本物の選択肢を並べます。強い好みがあっても、最低2〜3案。それぞれに簡潔な説明、長所と短所、ざっくりした工数見積もり（Tシャツサイズで十分です：S/M/L/XL）、そしてリスクと軽減策を付けます。代替案を書き残すことが、後の読者が「なぜ勝者が勝ったのか」を理解する手がかりになります。</p>
<p>トレードオフははっきり言葉にします。「アーキテクチャの純粋さよりも市場投入スピードを選択しています」「より良いスケーラビリティのために運用の複雑さが増すことを受け入れています」「生のパフォーマンスよりも開発者体験を優先しています」。この手の一文は、何ページの分析よりも将来の読者の役に立ちます。</p>
<p>議論は具体的なものに結びつけましょう。解決策の実装イメージを示すコードスニペット、システム間の相互作用の図、<a href="https://jared.lynskey.co.nz/ja/posts/2023/2023-10-18-spike/" >スパイク</a>で作った概念実証へのリンク。抽象的な議論からは抽象的な決定しか生まれません。</p>
<p>最後に期限です。いつまでに決めるのか、誰がいつまでにインプットを出すのか、決定が遅れると何が止まるのか。期限のない決定は、いつまでも漂い続けます。</p>
<hr>

<h3 class="relative group">例：アーキテクチャ決定記録（ADR）
    <div id="例アーキテクチャ決定記録adr" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e4%be%8b%e3%82%a2%e3%83%bc%e3%82%ad%e3%83%86%e3%82%af%e3%83%81%e3%83%a3%e6%b1%ba%e5%ae%9a%e8%a8%98%e9%8c%b2adr" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>意思決定文書の実践的な例を以下に示します：</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="gh"># ADR-001: API Authentication Strategy
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gs">**Status:**</span> Accepted
</span></span><span class="line"><span class="cl"><span class="gs">**Date:**</span> 2024-11-15
</span></span><span class="line"><span class="cl"><span class="gs">**Decision Maker:**</span> Sarah Chen (Platform Architect)
</span></span><span class="line"><span class="cl"><span class="gs">**Contributors:**</span> Backend Team, Security Team, Mobile Team
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Context
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Our public API currently uses API keys for authentication. As we expand to
</span></span><span class="line"><span class="cl">support third-party integrations and mobile apps, we need a more robust
</span></span><span class="line"><span class="cl">authentication mechanism that supports:
</span></span><span class="line"><span class="cl"><span class="k">-</span> Token expiration and refresh
</span></span><span class="line"><span class="cl"><span class="k">-</span> Scoped permissions
</span></span><span class="line"><span class="cl"><span class="k">-</span> User-level authentication (not just service-level)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Decision
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">We will implement OAuth 2.0 with JWT tokens for API authentication.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Options Considered
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">### Option 1: OAuth 2.0 with JWT (Recommended)
</span></span></span><span class="line"><span class="cl">**Description:** Industry-standard protocol with self-contained tokens
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">| Pros | Cons |
</span></span><span class="line"><span class="cl">|------|------|
</span></span><span class="line"><span class="cl">| Industry standard, well-documented | More complex initial implementation |
</span></span><span class="line"><span class="cl">| Self-contained tokens reduce database lookups | Tokens cannot be revoked instantly |
</span></span><span class="line"><span class="cl">| Broad library support | Requires refresh token management |
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gs">**Effort:**</span> Medium (2-3 sprints)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">### Option 2: Session-based Authentication
</span></span></span><span class="line"><span class="cl">**Description:** Traditional server-side sessions with cookies
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">| Pros | Cons |
</span></span><span class="line"><span class="cl">|------|------|
</span></span><span class="line"><span class="cl">| Simple to implement | Not suitable for mobile apps |
</span></span><span class="line"><span class="cl">| Easy to revoke sessions | Requires sticky sessions or shared session store |
</span></span><span class="line"><span class="cl">| Familiar to most developers | Doesn&#39;t scale as well |
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gs">**Effort:**</span> Small (1 sprint)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">### Option 3: Custom Token System
</span></span></span><span class="line"><span class="cl">**Description:** Build our own token-based authentication
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">| Pros | Cons |
</span></span><span class="line"><span class="cl">|------|------|
</span></span><span class="line"><span class="cl">| Fully customizable | Reinventing the wheel |
</span></span><span class="line"><span class="cl">| No external dependencies | Security risks from custom implementation |
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gs">**Effort:**</span> Large (4+ sprints)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Consequences
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">-</span> Mobile team can implement standard OAuth flows
</span></span><span class="line"><span class="cl"><span class="k">-</span> We&#39;ll need to set up a token refresh mechanism
</span></span><span class="line"><span class="cl"><span class="k">-</span> API documentation will need updates for OAuth flows
</span></span><span class="line"><span class="cl"><span class="k">-</span> Existing API key users will need a migration path (6-month deprecation)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## References
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">-</span> [<span class="nt">OAuth 2.0 RFC 6749</span>](<span class="na">https://tools.ietf.org/html/rfc6749</span>)
</span></span><span class="line"><span class="cl"><span class="k">-</span> Internal spike document: [<span class="nt">Authentication Options Spike</span>](<span class="na">/spikes/auth-spike-2024</span>)
</span></span><span class="line"><span class="cl">- Security team review: SEC-2024-042</span></span></code></pre></div></div>
<hr>

<h3 class="relative group">使い回せるテンプレート
    <div id="使い回せるテンプレート" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e4%bd%bf%e3%81%84%e5%9b%9e%e3%81%9b%e3%82%8b%e3%83%86%e3%83%b3%e3%83%97%e3%83%ac%e3%83%bc%e3%83%88" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>出発点としてこのテンプレートを使い、チームに合わせて削ってください。</p>
<table>
	<thead>
			<tr>
					<th><strong>属性</strong></th>
					<th><strong>詳細</strong></th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>決定/課題名</strong></td>
					<td>[明確で説明的なタイトル]</td>
			</tr>
			<tr>
					<td><strong>ステータス</strong></td>
					<td>[ドラフト / レビュー中 / 承認済み / 却下 / 廃止]</td>
			</tr>
			<tr>
					<td><strong>影響度</strong></td>
					<td>[高 / 中 / 低]</td>
			</tr>
			<tr>
					<td><strong>オーナー</strong></td>
					<td>[決定を推進する人の名前]</td>
			</tr>
			<tr>
					<td><strong>意思決定者</strong></td>
					<td>[最終権限を持つ人]</td>
			</tr>
			<tr>
					<td><strong>期限</strong></td>
					<td>[決定を下す期限]</td>
			</tr>
	</tbody>
</table>

<h4 class="relative group">問題の説明
    <div id="問題の説明" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e5%95%8f%e9%a1%8c%e3%81%ae%e8%aa%ac%e6%98%8e" aria-label="アンカー">#</a>
    </span>
    
</h4>
<p><em>どんな問題を解決しようとしているか？なぜ重要か？何もしない場合のコストは？</em></p>

<h4 class="relative group">背景
    <div id="背景" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e8%83%8c%e6%99%af" aria-label="アンカー">#</a>
    </span>
    
</h4>
<p><em>コンテキスト、履歴、関連する詳細を提供する。関連文書、過去の決定、スパイク結果へのリンクを含める。</em></p>

<h4 class="relative group">制約
    <div id="制約" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e5%88%b6%e7%b4%84" aria-label="アンカー">#</a>
    </span>
    
</h4>
<p><em>どの解決策でも守るべき制限や要件は何か？</em></p>
<ul>
<li>予算の制約</li>
<li>タイムラインの要件</li>
<li>技術的制約（既存システム、スキルセット）</li>
<li>コンプライアンスまたはセキュリティ要件</li>
</ul>

<h4 class="relative group">検討した解決策
    <div id="検討した解決策" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e6%a4%9c%e8%a8%8e%e3%81%97%e3%81%9f%e8%a7%a3%e6%b1%ba%e7%ad%96" aria-label="アンカー">#</a>
    </span>
    
</h4>
<table>
	<thead>
			<tr>
					<th><strong>オプション</strong></th>
					<th><strong>説明</strong></th>
					<th><strong>長所</strong></th>
					<th><strong>短所</strong></th>
					<th><strong>工数</strong></th>
					<th><strong>リスク</strong></th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>オプション1</strong></td>
					<td><em>説明</em></td>
					<td><em>長所A、長所B</em></td>
					<td><em>短所A、短所B</em></td>
					<td><em>S/M/L/XL</em></td>
					<td><em>低/中/高</em></td>
			</tr>
			<tr>
					<td><strong>オプション2</strong></td>
					<td><em>説明</em></td>
					<td><em>長所A、長所B</em></td>
					<td><em>短所A、短所B</em></td>
					<td><em>S/M/L/XL</em></td>
					<td><em>低/中/高</em></td>
			</tr>
			<tr>
					<td><strong>オプション3</strong></td>
					<td><em>説明</em></td>
					<td><em>長所A、長所B</em></td>
					<td><em>短所A、短所B</em></td>
					<td><em>S/M/L/XL</em></td>
					<td><em>低/中/高</em></td>
			</tr>
	</tbody>
</table>

<h4 class="relative group">推奨
    <div id="推奨" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e6%8e%a8%e5%a5%a8" aria-label="アンカー">#</a>
    </span>
    
</h4>
<p><em>推奨するオプションを述べ、制約とトレードオフを考慮した上でなぜそれが最良の選択かを要約する。</em></p>

<h4 class="relative group">決定
    <div id="決定" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e6%b1%ba%e5%ae%9a" aria-label="アンカー">#</a>
    </span>
    
</h4>
<p><em>決定が下されたら最終決定を文書化する。推奨と異なる場合は理由を説明する。</em></p>

<h4 class="relative group">結果
    <div id="結果" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e7%b5%90%e6%9e%9c" aria-label="アンカー">#</a>
    </span>
    
</h4>
<p><em>この決定の影響は何か？必要なフォローアップ作業は何か？</em></p>

<h4 class="relative group">アクションアイテム
    <div id="アクションアイテム" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%82%a2%e3%82%af%e3%82%b7%e3%83%a7%e3%83%b3%e3%82%a2%e3%82%a4%e3%83%86%e3%83%a0" aria-label="アンカー">#</a>
    </span>
    
</h4>
<table>
	<thead>
			<tr>
					<th><strong>アクション</strong></th>
					<th><strong>担当者</strong></th>
					<th><strong>期限</strong></th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><em>アクション1</em></td>
					<td><em>名前</em></td>
					<td><em>日付</em></td>
			</tr>
			<tr>
					<td><em>アクション2</em></td>
					<td><em>名前</em></td>
					<td><em>日付</em></td>
			</tr>
	</tbody>
</table>

<h4 class="relative group">参考資料
    <div id="参考資料" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e5%8f%82%e8%80%83%e8%b3%87%e6%96%99" aria-label="アンカー">#</a>
    </span>
    
</h4>
<p><em>関連文書、外部リソース、スパイク結果、過去の決定へのリンク。</em></p>
<hr>

<h3 class="relative group">習慣として根付かせる
    <div id="習慣として根付かせる" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e7%bf%92%e6%85%a3%e3%81%a8%e3%81%97%e3%81%a6%e6%a0%b9%e4%bb%98%e3%81%8b%e3%81%9b%e3%82%8b" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>文書は決まった場所に置きます。リポジトリ内の専用フォルダでもWikiスペースでも構いませんが、見つけられない決定は存在しないのと同じです。そして軽く保つこと。書くのに何日もかかる文書は、そもそも書かれません。コードコメントやコミットメッセージ、プルリクエストから文書を参照して、「なぜ」を「何を」のそばに繋いでおきましょう。決定が廃止されたら、古い文書は消さずに廃止とマークして新しい決定へリンクします。履歴こそが価値なので。そして<a href="https://jared.lynskey.co.nz/ja/posts/2023/2023-09-01-retrospecs/" >レトロスペクティブ</a>で過去の決定をたまに見直してください。どれが持ちこたえ、どれを今ならやり直すか。</p>
<p>失敗パターンも同じくらい予測可能です。分析麻痺には、期限を切って手持ちの情報で決めること。ほとんどの決定は後から見直せます。意味のある批評が一度も出ないハンコ押しレビューは、価値を足さずにプロセスだけ足しています。書かれたのに実行されない孤児文書は、文書がないより悪い。詳細の書きすぎにも注意で、文書が扱うのは「なぜ」と「何を」であって「どのように」ではありません。実装の詳細は技術仕様書へ。そして異論が出たら、それも書き残してください。決定はそのまま通るかもしれませんが、その異論こそ、1年後の誰かが必要とするコンテキストです。</p>
<p>次の重要な決定から始めてみてください。荒削りでもいいので書いて、プロセスは使いながら磨けばいい。「なんでXを選んだんだっけ？」という会議が、誰かがADRを読むだけで消える。その一回一回が、チームに返ってくる時間です。</p>
<hr>

<h3 class="relative group">参考資料
    <div id="参考資料-1" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e5%8f%82%e8%80%83%e8%b3%87%e6%96%99-1" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p><strong>このサイトの関連記事：</strong></p>
<ul>
<li><a href="https://jared.lynskey.co.nz/ja/posts/2023/2023-10-18-spike/" >アジャイルソフトウェア開発におけるスパイクの理解</a> - 大きな決定の前にスパイクで情報を集める</li>
<li><a href="https://jared.lynskey.co.nz/ja/posts/2023/2023-09-01-retrospecs/" >レトロスペクティブ：フィードバックループの力</a> - 継続的改善の一環として過去の決定を見直す</li>
<li><a href="https://jared.lynskey.co.nz/ja/posts/2023/2023-10-08-planning-an-api/" >APIの計画</a> - 意思決定文書の考え方をAPI設計に応用する</li>
<li><a href="https://jared.lynskey.co.nz/ja/posts/2024/2024-01-04-githubforprojectmanagement/" >プロジェクト管理のためのGitHub</a> - 意思決定文書をコードのそばに置いて管理する</li>
</ul>
<p><strong>外部リソース：</strong></p>
<ul>
<li><a href="https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions"  target="_blank" rel="noreferrer">Documenting Architecture Decisions - Michael Nygard</a> - ADRを広めたオリジナルの記事</li>
<li><a href="https://adr.github.io/"  target="_blank" rel="noreferrer">ADR GitHub Organization</a> - ADRのためのツール、テンプレート、実例</li>
<li><a href="https://www.industrialempathy.com/posts/design-docs-at-google/"  target="_blank" rel="noreferrer">Design Docs at Google</a> - Googleの設計ドキュメントのやり方</li>
<li><a href="https://rust-lang.github.io/rfcs/"  target="_blank" rel="noreferrer">RFC Process at Rust</a> - Rust言語のよく整備されたRFCプロセス</li>
<li><a href="https://engineering.atspotify.com/2020/04/when-should-i-write-an-architecture-decision-record/"  target="_blank" rel="noreferrer">Spotify&rsquo;s Decision Record Template</a> - SpotifyのADRの使いどころ</li>
</ul>
]]></content:encoded>
      <media:content url="https://jared.lynskey.co.nz/en/posts/2024/2024-12-03-decision-docs/featured.jpg" medium="image" type="image/jpeg" />
    </item>
    <item>
      <title>シングルテナント vs マルチテナント SaaS：どちらをいつ選ぶか</title>
      <link>https://jared.lynskey.co.nz/ja/posts/2024/2024-04-07-singletenantsingleinstance/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/ja/posts/2024/2024-04-07-singletenantsingleinstance/</guid>
      <pubDate>Sat, 06 Apr 2024 00:30:03 +0000</pubDate>
      <dc:creator>Me</dc:creator>
      <category>SaaS</category>
      <category>architecture</category>
      <category>single-tenant</category>
      <category>multi-tenant</category>
      <description>シングルテナント（マルチインスタンス）とマルチテナントのSaaSは何が違うのか。分離、コスト、運用の比較、その中間の選択肢、どちらをいつ選ぶか、共有データベースでテナントを分ける方法、そして顧客ごとのインスタンス運用を現実的にするツールについてまとめました。</description>
      <content:encoded><![CDATA[<p>シングルテナントのSaaSは、顧客ごとにアプリケーションとデータベースを1セットずつ用意します。マルチテナントのSaaSは、全顧客を1つの共有アプリケーションとデータベースに載せ、テナントIDで区別します。そのチェックはすべてのクエリで必要です。シングルテナントは、分離、顧客ごとの制御、コンプライアンス対応のしやすさと引き換えに、大量のコピーを運用するコストを払います。マルチテナントは安くて運用もずっとシンプルですが、顧客同士を分けておくのはコードの仕事になります。多くのプロダクトはマルチテナントから始め、本当に必要な一部の顧客にだけ専用インスタンスを提供しています。</p>
<p>B2BのSaaSをやっていると、いつか必ずテナンシーの問題にぶつかります。だいたいは、大口の見込み客から「うちのデータは他社と分離できますか？」と聞かれた日です。私もこの議論には何度か立ち会ってきました。答えは基本的に2つ。顧客ごとに専用インスタンスを用意する（シングルテナント・マルチインスタンス）か、全員を1つの共有インスタンスに乗せる（マルチテナント・シングルインスタンス）か。どちらが正解ということはなく、壊れ方が違うだけです。</p>

<h2 class="relative group">インフラストラクチャ設計
    <div id="インフラストラクチャ設計" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%82%a4%e3%83%b3%e3%83%95%e3%83%a9%e3%82%b9%e3%83%88%e3%83%a9%e3%82%af%e3%83%81%e3%83%a3%e8%a8%ad%e8%a8%88" aria-label="アンカー">#</a>
    </span>
    
</h2>
<figure class="diagram">
  <div role="img" aria-label="シングルテナントとマルチテナントの違い"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 790" aria-hidden="true" focusable="false">
<defs><marker id="tn-ah" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path class="d-head" d="M0,0 L10,5 L0,10 z"/></marker></defs>
<text class="d-title" x="320" y="164" text-anchor="middle" font-size="42">マルチインスタンス</text>
<text class="d-sub" x="320" y="204" text-anchor="middle" font-size="24">シングルテナント：顧客ごとに1セット</text>
<rect class="d-panel" x="12" y="234" width="616" height="304" rx="36"/>
<rect class="d-group" x="25" y="250" width="186" height="272" rx="22"/>
<rect class="d-box" x="33" y="280" width="170" height="86" rx="14"/>
<text class="d-label" x="118.0" y="337.64" text-anchor="middle" font-size="24">データベース</text>
<polygon class="d-badge" points="118.0,256.0 143.7,274.7 133.9,304.8 102.1,304.8 92.3,274.7"/>
<g class="d-icon">
<ellipse cx="118" cy="273" rx="10" ry="4"/>
<path d="M108,273 v15 a10,4 0 0 0 20,0 v-15 M108,280 a10,4 0 0 0 20,0"/>
</g>
<rect class="d-box" x="33" y="418" width="170" height="86" rx="14"/>
<text class="d-label" x="118.0" y="473.84" text-anchor="middle" font-size="19">アプリケーション</text>
<polygon class="d-badge" points="118.0,394.0 143.7,412.7 133.9,442.8 102.1,442.8 92.3,412.7"/>
<g class="d-icon">
<rect x="110" y="411" width="16" height="16" rx="2"/>
<rect class="d-icon-fill" x="114.5" y="415.5" width="7" height="7"/>
<path stroke-width="1.4" d="M114,411 v-4 M114,427 v4 M110,415 h-4 M126,415 h4 M118,411 v-4 M118,427 v4 M110,419 h-4 M126,419 h4 M122,411 v-4 M122,427 v4 M110,423 h-4 M126,423 h4"/>
</g>
<line class="d-line" x1="118" y1="508" x2="118" y2="652" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<g class="d-person-back"><circle cx="84" cy="710.88" r="14.44"/><path d="M58.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person-back"><circle cx="152" cy="710.88" r="14.44"/><path d="M126.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person"><circle cx="118" cy="704.0" r="19.0"/><path d="M84.0,766 v-12.0 a34.0,28.0 0 0 1 68.0,0 v12.0 z"/></g>
<rect class="d-group" x="227" y="250" width="186" height="272" rx="22"/>
<rect class="d-box" x="235" y="280" width="170" height="86" rx="14"/>
<text class="d-label" x="320.0" y="337.64" text-anchor="middle" font-size="24">データベース</text>
<polygon class="d-badge" points="320.0,256.0 345.7,274.7 335.9,304.8 304.1,304.8 294.3,274.7"/>
<g class="d-icon">
<ellipse cx="320" cy="273" rx="10" ry="4"/>
<path d="M310,273 v15 a10,4 0 0 0 20,0 v-15 M310,280 a10,4 0 0 0 20,0"/>
</g>
<rect class="d-box" x="235" y="418" width="170" height="86" rx="14"/>
<text class="d-label" x="320.0" y="473.84" text-anchor="middle" font-size="19">アプリケーション</text>
<polygon class="d-badge" points="320.0,394.0 345.7,412.7 335.9,442.8 304.1,442.8 294.3,412.7"/>
<g class="d-icon">
<rect x="312" y="411" width="16" height="16" rx="2"/>
<rect class="d-icon-fill" x="316.5" y="415.5" width="7" height="7"/>
<path stroke-width="1.4" d="M316,411 v-4 M316,427 v4 M312,415 h-4 M328,415 h4 M320,411 v-4 M320,427 v4 M312,419 h-4 M328,419 h4 M324,411 v-4 M324,427 v4 M312,423 h-4 M328,423 h4"/>
</g>
<line class="d-line" x1="320" y1="508" x2="320" y2="652" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<g class="d-person-back"><circle cx="286" cy="710.88" r="14.44"/><path d="M260.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person-back"><circle cx="354" cy="710.88" r="14.44"/><path d="M328.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person"><circle cx="320" cy="704.0" r="19.0"/><path d="M286.0,766 v-12.0 a34.0,28.0 0 0 1 68.0,0 v12.0 z"/></g>
<rect class="d-group" x="429" y="250" width="186" height="272" rx="22"/>
<rect class="d-box" x="437" y="280" width="170" height="86" rx="14"/>
<text class="d-label" x="522.0" y="337.64" text-anchor="middle" font-size="24">データベース</text>
<polygon class="d-badge" points="522.0,256.0 547.7,274.7 537.9,304.8 506.1,304.8 496.3,274.7"/>
<g class="d-icon">
<ellipse cx="522" cy="273" rx="10" ry="4"/>
<path d="M512,273 v15 a10,4 0 0 0 20,0 v-15 M512,280 a10,4 0 0 0 20,0"/>
</g>
<rect class="d-box" x="437" y="418" width="170" height="86" rx="14"/>
<text class="d-label" x="522.0" y="473.84" text-anchor="middle" font-size="19">アプリケーション</text>
<polygon class="d-badge" points="522.0,394.0 547.7,412.7 537.9,442.8 506.1,442.8 496.3,412.7"/>
<g class="d-icon">
<rect x="514" y="411" width="16" height="16" rx="2"/>
<rect class="d-icon-fill" x="518.5" y="415.5" width="7" height="7"/>
<path stroke-width="1.4" d="M518,411 v-4 M518,427 v4 M514,415 h-4 M530,415 h4 M522,411 v-4 M522,427 v4 M514,419 h-4 M530,419 h4 M526,411 v-4 M526,427 v4 M514,423 h-4 M530,423 h4"/>
</g>
<line class="d-line" x1="522" y1="508" x2="522" y2="652" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<g class="d-person-back"><circle cx="488" cy="710.88" r="14.44"/><path d="M462.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person-back"><circle cx="556" cy="710.88" r="14.44"/><path d="M530.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person"><circle cx="522" cy="704.0" r="19.0"/><path d="M488.0,766 v-12.0 a34.0,28.0 0 0 1 68.0,0 v12.0 z"/></g>
<text class="d-title" x="982" y="50" text-anchor="middle" font-size="42">マルチテナント</text>
<text class="d-sub" x="982" y="88" text-anchor="middle" font-size="24">全員で1つのインスタンスを共有</text>
<rect class="d-panel" x="698" y="104" width="568" height="434" rx="44"/>
<rect class="d-box" x="887" y="126" width="190" height="86" rx="14"/>
<text class="d-label" x="982.0" y="183.64" text-anchor="middle" font-size="24">データベース</text>
<polygon class="d-badge" points="982.0,102.0 1007.7,120.7 997.9,150.8 966.1,150.8 956.3,120.7"/>
<g class="d-icon">
<ellipse cx="982" cy="119" rx="10" ry="4"/>
<path d="M972,119 v15 a10,4 0 0 0 20,0 v-15 M972,126 a10,4 0 0 0 20,0"/>
</g>
<line class="d-line" x1="982" y1="216" x2="982" y2="246" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<rect class="d-group" x="704" y="240" width="556" height="156" rx="30"/>
<rect class="d-box" x="714" y="282" width="536" height="90" rx="16"/>
<polygon class="d-badge" points="982.0,248.0 1007.7,266.7 997.9,296.8 966.1,296.8 956.3,266.7"/>
<g class="d-icon">
<rect x="974" y="265" width="16" height="16" rx="2"/>
<rect class="d-icon-fill" x="978.5" y="269.5" width="7" height="7"/>
<path stroke-width="1.4" d="M978,265 v-4 M978,281 v4 M974,269 h-4 M990,269 h4 M982,265 v-4 M982,281 v4 M974,273 h-4 M990,273 h4 M986,265 v-4 M986,281 v4 M974,277 h-4 M990,277 h4"/>
</g>
<rect class="d-box" x="722" y="306" width="168" height="56" rx="12"/>
<text class="d-label" x="806.0" y="346.84" text-anchor="middle" font-size="19">アプリケーション</text>
<rect class="d-box" x="898" y="306" width="168" height="56" rx="12"/>
<text class="d-label" x="982.0" y="346.84" text-anchor="middle" font-size="19">アプリケーション</text>
<rect class="d-box" x="1074" y="306" width="168" height="56" rx="12"/>
<text class="d-label" x="1158.0" y="346.84" text-anchor="middle" font-size="19">アプリケーション</text>
<rect class="d-box" x="887" y="440" width="190" height="86" rx="14"/>
<text class="d-label" x="982.0" y="496.56" text-anchor="middle" font-size="21">ロードバランサー</text>
<polygon class="d-badge" points="982.0,404.0 1007.7,422.7 997.9,452.8 966.1,452.8 956.3,422.7"/>
<g class="d-icon">
<path d="M982,416 l6,9 h-12 z M982,425 v5 M972,433 h20 M972,433 v3 M982,433 v3 M992,433 v3"/>
<rect class="d-icon-fill" x="969" y="436" width="6" height="5"/>
<rect class="d-icon-fill" x="979" y="436" width="6" height="5"/>
<rect class="d-icon-fill" x="989" y="436" width="6" height="5"/>
</g>
<line class="d-line" x1="806" y1="364" x2="922" y2="438" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<line class="d-line" x1="982" y1="364" x2="982" y2="398" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<line class="d-line" x1="1158" y1="364" x2="1042" y2="438" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<line class="d-line" x1="932" y1="528" x2="780" y2="652" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<g class="d-person-back"><circle cx="716" cy="710.88" r="14.44"/><path d="M690.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person-back"><circle cx="784" cy="710.88" r="14.44"/><path d="M758.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person"><circle cx="750" cy="704.0" r="19.0"/><path d="M716.0,766 v-12.0 a34.0,28.0 0 0 1 68.0,0 v12.0 z"/></g>
<line class="d-line" x1="982" y1="528" x2="982" y2="652" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<g class="d-person-back"><circle cx="948" cy="710.88" r="14.44"/><path d="M922.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person-back"><circle cx="1016" cy="710.88" r="14.44"/><path d="M990.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person"><circle cx="982" cy="704.0" r="19.0"/><path d="M948.0,766 v-12.0 a34.0,28.0 0 0 1 68.0,0 v12.0 z"/></g>
<line class="d-line" x1="1032" y1="528" x2="1184" y2="652" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<g class="d-person-back"><circle cx="1180" cy="710.88" r="14.44"/><path d="M1154.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person-back"><circle cx="1248" cy="710.88" r="14.44"/><path d="M1222.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person"><circle cx="1214" cy="704.0" r="19.0"/><path d="M1180.0,766 v-12.0 a34.0,28.0 0 0 1 68.0,0 v12.0 z"/></g>
</svg>
</div>
</figure>


<h3 class="relative group">シングルテナント・マルチインスタンス
    <div id="シングルテナントマルチインスタンス" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%82%b7%e3%83%b3%e3%82%b0%e3%83%ab%e3%83%86%e3%83%8a%e3%83%b3%e3%83%88%e3%83%9e%e3%83%ab%e3%83%81%e3%82%a4%e3%83%b3%e3%82%b9%e3%82%bf%e3%83%b3%e3%82%b9" aria-label="アンカー">#</a>
    </span>
    
</h3>

  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    ><strong>シングルテナント、マルチインスタンス</strong>: この構成では、インフラのインスタンスを複数用意して、複数のテナントにサービスを提供します。</span>
</div>

<p>こちらの魅力は「分離」に尽きます。メリットはほぼすべてそこから生まれます。顧客のデータがそれぞれ独立した環境にあるので、セキュリティレビューやコンプライアンスの話がぐっと楽になります。デプロイの失敗や重いワークロードの影響も、全顧客ではなく1顧客で済みます。インスタンスごとに独立してスケール、チューニング、カスタマイズができて、リソースが専用なので、隣のテナントの使い方次第でパフォーマンスが揺れることもありません。メンテナンスも顧客単位で計画できるのでダウンタイムを小さく抑えられますし、料金設計をきちんとすれば、インフラコストをテナントごとの実際の使用量に合わせることもできます。</p>
<p>その代償は、すべてをN個ずつ運用することになる点です。リソースのオーバーヘッドは積み上がり、インスタンス間で設定はドリフトし、フリート全体でバージョンとデータを揃えること自体がひとつのプロジェクトになります。スケーリング、分離、コスト管理の問題は消えるのではなく、増殖するのです。</p>

<h3 class="relative group">マルチテナント・シングルインスタンス
    <div id="マルチテナントシングルインスタンス" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%83%9e%e3%83%ab%e3%83%81%e3%83%86%e3%83%8a%e3%83%b3%e3%83%88%e3%82%b7%e3%83%b3%e3%82%b0%e3%83%ab%e3%82%a4%e3%83%b3%e3%82%b9%e3%82%bf%e3%83%b3%e3%82%b9" aria-label="アンカー">#</a>
    </span>
    
</h3>

  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    ><strong>マルチテナント、シングルインスタンス</strong>: 1つのアプリケーションで複数のテナントにサービスを提供します。</span>
</div>

<p>こちらがデフォルトになっているのには理由があります。デプロイはひとつ、バックアップするデータベースもひとつ、本番のバージョンもひとつ。リソース効率が良く、運用はシンプルで、アップデートもセキュリティパッチも全顧客に同時に届きます。</p>
<p>難しさは「共有」の裏返しです。あるテナントの重い使い方が全員のパフォーマンスに響きますし、顧客ごとのカスタマイズには限界があります。そしてデータの分離はインフラではなくアプリケーションコードの責務になるため、バグひとつがテナント間のデータ漏えいになりえます。スケーラビリティのボトルネックは全顧客に同時に効きますし、「この顧客だけ」という影響範囲が存在しないぶん、メンテナンスにも余計に気を使います。</p>
<p><a href="https://www.scaleway.com/en/blog/saas-multi-tenant-vs-multi-instance-architectures/"  target="_blank" rel="noreferrer">SaaS ソリューションの詳細 - マルチインスタンス vs. マルチテナントアーキテクチャ</a></p>

<h2 class="relative group">マルチテナント vs マルチインスタンス
    <div id="マルチテナント-vs-マルチインスタンス" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%83%9e%e3%83%ab%e3%83%81%e3%83%86%e3%83%8a%e3%83%b3%e3%83%88-vs-%e3%83%9e%e3%83%ab%e3%83%81%e3%82%a4%e3%83%b3%e3%82%b9%e3%82%bf%e3%83%b3%e3%82%b9" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>同じ選択が「マルチテナント vs マルチインスタンス」と呼ばれることもよくありますが、同じものを反対側から見ているだけです。「テナント」は顧客の話で、1つのソフトウェアを何社で共有するか。「インスタンス」はデプロイの話で、ソフトウェアをいくつ動かすか。シングルテナントのプロダクトは顧客ごとに1つずつ動かすマルチインスタンス構成で、マルチテナントのプロダクトはたいてい全員で共有するシングルインスタンスです。つまり「マルチインスタンスにしてほしい」という要望は、シングルテナントにしてほしいという意味です。</p>
<p>実際には2択というよりグラデーションで、本当の判断はほとんどデータベースで決まります。いちばん共有寄りなのは、全顧客の行を同じテーブルに入れて <code>tenant_id</code> 列を持たせ、すべてのクエリでそれを絞り込む形です。運用コストは最も安く、間違えるのも最も簡単です。一歩進むと、共有データベースの中に顧客ごとのスキーマを切る形になります。分離がきれいになり顧客単位のバックアップも取れますが、マイグレーションはスキーマの数だけ実行することになります。さらに進むと、アプリケーションは共有のまま、データベースを顧客ごとに分ける形です。スタック一式を顧客ごとに動かさなくてもデータをしっかり分離できるので、よく選ばれる中間地点です。反対の端が完全なシングルテナントで、アプリケーションもデータベースも専用です。AWSのSaaSガイダンスでは両端をサイロ（silo）とプール（pool）、その組み合わせをブリッジ（bridge）と呼んでいて、顧客のセキュリティチームに構成を説明するときに便利な言葉です。</p>

<h2 class="relative group">シングルテナントとマルチテナントの比較
    <div id="シングルテナントとマルチテナントの比較" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%82%b7%e3%83%b3%e3%82%b0%e3%83%ab%e3%83%86%e3%83%8a%e3%83%b3%e3%83%88%e3%81%a8%e3%83%9e%e3%83%ab%e3%83%81%e3%83%86%e3%83%8a%e3%83%b3%e3%83%88%e3%81%ae%e6%af%94%e8%bc%83" aria-label="アンカー">#</a>
    </span>
    
</h2>
<table>
	<thead>
			<tr>
					<th></th>
					<th>シングルテナント（マルチインスタンス）</th>
					<th>マルチテナント（シングルインスタンス）</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>データの分離</td>
					<td>顧客ごとに別のインフラ</td>
					<td>コード、またはデータベースのポリシーで担保</td>
			</tr>
			<tr>
					<td>顧客あたりのコスト</td>
					<td>高い：顧客ごとにスタック一式</td>
					<td>低い：全員で1つを共有</td>
			</tr>
			<tr>
					<td>運用</td>
					<td>デプロイ、アップグレード、ダッシュボードが顧客の数だけ</td>
					<td>どれも1つずつ</td>
			</tr>
			<tr>
					<td>ノイジーネイバー</td>
					<td>なし</td>
					<td>重いテナント1社で全員が遅くなりうる</td>
			</tr>
			<tr>
					<td>カスタマイズ</td>
					<td>顧客単位で可能、バージョンを分けることも</td>
					<td>ほぼ設定とフィーチャーフラグまで</td>
			</tr>
			<tr>
					<td>リリース</td>
					<td>顧客ごとに順番に展開</td>
					<td>全員に同時</td>
			</tr>
			<tr>
					<td>デプロイ失敗の影響</td>
					<td>1顧客</td>
					<td>全顧客</td>
			</tr>
			<tr>
					<td>セキュリティレビューとデータの所在</td>
					<td>答えやすい</td>
					<td>説明に手間がかかる</td>
			</tr>
	</tbody>
</table>

<h2 class="relative group">どちらをいつ選ぶか
    <div id="どちらをいつ選ぶか" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%81%a9%e3%81%a1%e3%82%89%e3%82%92%e3%81%84%e3%81%a4%e9%81%b8%e3%81%b6%e3%81%8b" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>特別な理由がない限り、マルチテナントを選びましょう。少額を払う顧客がたくさんいるとき、ユーザーが自分でサインアップするとき、そして30個の環境を抱えたら回らなくなるくらいチームが小さいときは、マルチテナントが正しいデフォルトです。</p>
<p>シングルテナントが手間に見合うのは、いくつかの場面に限られます。医療、金融、行政の顧客は、データを物理的に分けることや特定の国に置くことを求めることが多く、セキュリティレビューでそれに正直に答えるいちばん簡単な方法が専用インスタンスです。エンタープライズ契約の大口顧客が数社なら、専用スタックのコストを負担してもらえますし、そもそもそれを期待されていることも多いです。そして、顧客ごとにリリースのタイミングを変えたい、バージョンを固定したい、深くカスタマイズしたいという要望があるなら、インスタンスを分けることはオーバーヘッドではなく、売っているものの一部になります。</p>
<p>結局、多くのチームは両方に落ち着きます。ほぼ全員向けにはマルチテナントのプロダクトを、必要としていてお金も払ってくれる一部の顧客には、エンタープライズプランとして専用インスタンスを。マルチテナント版で最初からすべてにテナントIDを持たせておけば、あとで1社だけ専用データベースに移すのは書き直しではなく、マイグレーションで済みます。</p>

<h2 class="relative group">マルチテナントの分離を壊れにくくする
    <div id="マルチテナントの分離を壊れにくくする" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%83%9e%e3%83%ab%e3%83%81%e3%83%86%e3%83%8a%e3%83%b3%e3%83%88%e3%81%ae%e5%88%86%e9%9b%a2%e3%82%92%e5%a3%8a%e3%82%8c%e3%81%ab%e3%81%8f%e3%81%8f%e3%81%99%e3%82%8b" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>マルチテナントでいちばん怖いバグは、テナントの絞り込みを忘れたクエリです。エラーにはなりません。ほかの顧客のデータを黙って返すだけで、コードレビューですべてを拾えるわけでもありません。Postgresを使っているなら、行レベルセキュリティ（RLS）でこのチェックをデータベース側に移せます。絞り込みを忘れたクエリでも、見えるのは今の顧客の行だけになります。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">ALTER</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">invoices</span><span class="w"> </span><span class="n">ENABLE</span><span class="w"> </span><span class="k">ROW</span><span class="w"> </span><span class="k">LEVEL</span><span class="w"> </span><span class="k">SECURITY</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="n">POLICY</span><span class="w"> </span><span class="n">tenant_isolation</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">invoices</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">USING</span><span class="w"> </span><span class="p">(</span><span class="n">tenant_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">current_setting</span><span class="p">(</span><span class="s1">&#39;app.tenant_id&#39;</span><span class="p">)::</span><span class="n">uuid</span><span class="p">);</span></span></span></code></pre></div></div>
<p>あとは各リクエストが、トランザクションの中で <code>SET LOCAL app.tenant_id = '…'</code> を実行してテナントを設定します。こうすればコネクションプールの接続が前の顧客のIDを次のリクエストに持ち越すことはなく、設定し忘れたリクエストは行の代わりにエラーを受け取ります。注意点は2つあります。アプリはテーブルの所有者ではないロールで接続すること。所有者は <code>FORCE ROW LEVEL SECURITY</code> を付けない限り、このポリシーを素通りします。そしてスーパーユーザーでは絶対に接続しないこと。こちらは何をしても素通りです。</p>

<h2 class="relative group">シングルテナントはどこから始めるか
    <div id="シングルテナントはどこから始めるか" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%82%b7%e3%83%b3%e3%82%b0%e3%83%ab%e3%83%86%e3%83%8a%e3%83%b3%e3%83%88%e3%81%af%e3%81%a9%e3%81%93%e3%81%8b%e3%82%89%e5%a7%8b%e3%82%81%e3%82%8b%e3%81%8b" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>シングルテナントで行くなら、それが「管理可能」になるか「スローモーションの災害」になるかを分けるのはツールです。30個の環境を手作業で面倒見ることはできません。</p>
<p>核になるのはコンテナとInfrastructure as Codeです。Dockerでアプリを全テナント同一の形にパッケージし、Kubernetesで大量のインスタンスのデプロイとスケーリングを捌きます。TerraformやPulumi、AWS CloudFormationでテナント一式のスタックをコードとして定義しておけば、31社目の顧客の立ち上げは週末の徹夜作業ではなくプルリクエストになります。VM寄りの世界にいるなら、Ansible、Chef、Puppetといった構成管理ツールが同じ役割を果たしてくれます。CI/CD（Jenkins、GitLab CI/CD）はいつも以上に重要です。すべてのリリースを、付きっきりにならずに全インスタンスへ展開しないといけないからです。</p>
<p>その先はプロダクトの形次第です。ワークロードによってはサーバーレス（AWS Lambda、Azure Functions）でサーバー層ごと消せます。Amazon RDSやAzure SQL Databaseのようなマネージドデータベースは、テナントごとのDBを手で管理する苦行から救ってくれます。モニタリング（Prometheus、Grafana、New Relic）はフリートを持った時点で必須になります。どのインスタンスが不調かは、顧客に言われる前に知りたいですよね。認証まわりはAuth0やOktaのようなIAMサービスに任せれば、テナントごとに作り直さずに済みます。使っている言語にも、この手の配管作業を省いてくれるSaaS向けのフレームワークやライブラリがきっとあります。ホスティングはAWS、Azure、Google Cloud Platformといった大手クラウドならどこでも問題ありません。</p>
<p>初日から全部を揃える必要はありません。コンテナとIaCから始めて、テナントが増えるにつれてモニタリングとIAMを足し、あとは実際に痛みを感じた順に選んでいけば十分です。</p>

<h2 class="relative group">ざっくり言うと
    <div id="ざっくり言うと" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%81%96%e3%81%a3%e3%81%8f%e3%82%8a%e8%a8%80%e3%81%86%e3%81%a8" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>シングルテナント、つまりマルチインスタンスは、顧客ごとにアプリケーションとデータベースを別々に持つ構成です。分離と顧客単位の制御が手に入る代わりに、運用するフリートを抱えます。マルチテナントは1つを全員で共有する構成で、安くてシンプルですが、分離はコードで担保します。多くのプロダクトはマルチテナントで始め、すべてにテナントIDを持たせてデータベースにそれを強制させたうえで、必要としていてお金も払う一部の顧客にだけ専用インスタンスを提供するのがよいでしょう。シングルテナントで運用するなら、管理できる状態を保つ鍵はコンテナとInfrastructure as Codeです。</p>
]]></content:encoded>
      <media:content url="https://jared.lynskey.co.nz/en/posts/2024/2024-04-07-singletenantsingleinstance/featured.jpg" medium="image" type="image/jpeg" />
    </item>
  </channel>
</rss>
