<?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/"><channel><title>Database-Design on IT Comparison</title><link>https://comparison.metacog.co.kr/tags/database-design/</link><description>Recent content in Database-Design on IT Comparison</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Sun, 06 Sep 2026 09:52:35 +0900</lastBuildDate><atom:link href="https://comparison.metacog.co.kr/tags/database-design/index.xml" rel="self" type="application/rss+xml"/><item><title>Shared Database vs Database Per Service: Data Ownership in Microservices</title><link>https://comparison.metacog.co.kr/posts/2026-09-06-shared-database-vs-database-per-service-data-ownership-in-mi/</link><pubDate>Sun, 06 Sep 2026 09:52:35 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-09-06-shared-database-vs-database-per-service-data-ownership-in-mi/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;This compares two data architecture patterns for microservices: a &lt;strong class="kw"&gt;shared database&lt;/strong&gt; where multiple services read and write the same schema, versus &lt;strong class="kw"&gt;database per service&lt;/strong&gt; where each service owns an isolated data store. The choice determines how tightly services are coupled, how transactions and queries span service boundaries, and how independently teams can deploy and scale.&lt;/p&gt;
&lt;h2 id="comparison-diagram"&gt;Comparison Diagram&lt;/h2&gt;
&lt;div class="compare-diagram"&gt;
&lt;svg viewBox="0 0 640 360" xmlns="http://www.w3.org/2000/svg"&gt;&lt;text x="160" y="30" text-anchor="middle" font-size="18" font-weight="bold" style="fill:var(--primary)"&gt;Shared Database&lt;/text&gt;&lt;text x="480" y="30" text-anchor="middle" font-size="18" font-weight="bold" style="fill:var(--primary)"&gt;Database Per Service&lt;/text&gt;&lt;line x1="320" y1="50" x2="320" y2="330" stroke-width="1" stroke-dasharray="4,4" style="stroke:var(--border)"/&gt;&lt;rect x="30" y="70" width="110" height="44" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)"/&gt;&lt;text x="85" y="97" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;Service A&lt;/text&gt;&lt;rect x="30" y="155" width="110" height="44" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)"/&gt;&lt;text x="85" y="182" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;Service B&lt;/text&gt;&lt;rect x="30" y="240" width="110" height="44" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)"/&gt;&lt;text x="85" y="267" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;Service C&lt;/text&gt;&lt;rect x="210" y="110" width="90" height="150" rx="8" stroke-width="2" style="fill:var(--compare-a-soft);stroke:var(--compare-a)"/&gt;&lt;text x="255" y="180" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;Shared&lt;/text&gt;&lt;text x="255" y="198" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;DB&lt;/text&gt;&lt;line x1="140" y1="92" x2="210" y2="145" stroke-width="1.5" style="stroke:var(--compare-a)"/&gt;&lt;line x1="140" y1="177" x2="210" y2="185" stroke-width="1.5" style="stroke:var(--compare-a)"/&gt;&lt;line x1="140" y1="262" x2="210" y2="225" stroke-width="1.5" style="stroke:var(--compare-a)"/&gt;&lt;rect x="350" y="70" width="100" height="44" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)"/&gt;&lt;text x="400" y="97" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;Service A&lt;/text&gt;&lt;rect x="480" y="70" width="90" height="44" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)"/&gt;&lt;text x="525" y="97" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;DB A&lt;/text&gt;&lt;line x1="450" y1="92" x2="480" y2="92" stroke-width="1.5" style="stroke:var(--compare-b)"/&gt;&lt;rect x="350" y="155" width="100" height="44" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)"/&gt;&lt;text x="400" y="182" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;Service B&lt;/text&gt;&lt;rect x="480" y="155" width="90" height="44" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)"/&gt;&lt;text x="525" y="182" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;DB B&lt;/text&gt;&lt;line x1="450" y1="177" x2="480" y2="177" stroke-width="1.5" style="stroke:var(--compare-b)"/&gt;&lt;rect x="350" y="240" width="100" height="44" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)"/&gt;&lt;text x="400" y="267" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;Service C&lt;/text&gt;&lt;rect x="480" y="240" width="90" height="44" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)"/&gt;&lt;text x="525" y="267" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;DB C&lt;/text&gt;&lt;line x1="450" y1="262" x2="480" y2="262" stroke-width="1.5" style="stroke:var(--compare-b)"/&gt;&lt;text x="160" y="345" text-anchor="middle" font-size="11" style="fill:var(--secondary)"&gt;Single point of coupling &amp;amp; contention&lt;/text&gt;&lt;text x="480" y="345" text-anchor="middle" font-size="11" style="fill:var(--secondary)"&gt;Isolated data, independent scaling&lt;/text&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;h2 id="comparison-table"&gt;Comparison Table&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Shared Database&lt;/th&gt;
&lt;th&gt;Database Per Service&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Schema ownership&lt;/td&gt;
&lt;td&gt;One schema shared and often co-owned by multiple teams&lt;/td&gt;
&lt;td&gt;Each service exclusively owns and evolves its own schema&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write path&lt;/td&gt;
&lt;td&gt;Any service can write directly to shared tables&lt;/td&gt;
&lt;td&gt;Writes go only through the owning service&amp;rsquo;s API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-service queries&lt;/td&gt;
&lt;td&gt;Simple SQL joins across tables in one database&lt;/td&gt;
&lt;td&gt;Requires API calls, data replication, or an aggregation layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Distributed transactions&lt;/td&gt;
&lt;td&gt;Native ACID transactions across affected tables&lt;/td&gt;
&lt;td&gt;Needs sagas or eventual consistency to span services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema migrations&lt;/td&gt;
&lt;td&gt;Any change risks breaking other services using the table&lt;/td&gt;
&lt;td&gt;Migrations are local and safe to run independently&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Independent scaling&lt;/td&gt;
&lt;td&gt;Database becomes a shared bottleneck under load&lt;/td&gt;
&lt;td&gt;Each store can be scaled or tuned to its own service&amp;rsquo;s needs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Technology choice&lt;/td&gt;
&lt;td&gt;All services locked into one database engine&lt;/td&gt;
&lt;td&gt;Each service can pick the best-fit database technology&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure isolation&lt;/td&gt;
&lt;td&gt;A database outage or lock contention affects every service&lt;/td&gt;
&lt;td&gt;An outage is contained to the owning service&amp;rsquo;s data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="key-differences"&gt;Key Differences&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Shared database allows cheap &lt;strong class="kw"&gt;cross-table joins&lt;/strong&gt; but couples every consuming service to one schema&lt;/li&gt;
&lt;li&gt;Database per service enforces &lt;strong class="kw"&gt;service autonomy&lt;/strong&gt; at the cost of needing sagas for cross-service transactions&lt;/li&gt;
&lt;li&gt;Schema changes in a shared database require coordinating &lt;strong class="kw"&gt;multiple teams&lt;/strong&gt;, while per-service schemas change independently&lt;/li&gt;
&lt;li&gt;A shared database creates a single &lt;strong class="kw"&gt;failure domain&lt;/strong&gt;; per-service databases contain outages to one service&lt;/li&gt;
&lt;li&gt;Polyglot persistence — choosing different database engines per need — is only possible with &lt;strong class="kw"&gt;database per service&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-to-use-each"&gt;When to Use Each&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Shared Database&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Many Indexes vs Write Speed: Read Optimization vs Insert Throughput</title><link>https://comparison.metacog.co.kr/posts/2026-09-06-many-indexes-vs-write-speed-read-optimization-vs-insert-thro/</link><pubDate>Sun, 06 Sep 2026 09:46:35 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-09-06-many-indexes-vs-write-speed-read-optimization-vs-insert-thro/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;Every &lt;strong class="kw"&gt;index&lt;/strong&gt; you add speeds up specific queries by letting the database jump straight to matching rows instead of scanning the whole table. But each one is a second (or third, or thirteenth) structure that must be updated on every insert, update, and delete, so piling on indexes steadily erodes &lt;strong class="kw"&gt;write speed&lt;/strong&gt;. The right balance depends on whether your workload is dominated by reads or writes.&lt;/p&gt;
&lt;h2 id="comparison-diagram"&gt;Comparison Diagram&lt;/h2&gt;
&lt;div class="compare-diagram"&gt;
&lt;svg viewBox="0 0 640 360" xmlns="http://www.w3.org/2000/svg"&gt;&lt;line x1="320" y1="15" x2="320" y2="345" style="stroke:var(--border)" stroke-width="1.5" stroke-dasharray="4 4"/&gt;&lt;text x="160" y="28" text-anchor="middle" font-size="16" style="fill:var(--primary)"&gt;Many Indexes&lt;/text&gt;&lt;text x="480" y="28" text-anchor="middle" font-size="16" style="fill:var(--primary)"&gt;Few Indexes&lt;/text&gt;&lt;rect x="125" y="48" width="70" height="34" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="160" y="70" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;WRITE&lt;/text&gt;&lt;rect x="445" y="48" width="70" height="34" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="480" y="70" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;WRITE&lt;/text&gt;&lt;line x1="160" y1="82" x2="43" y2="118" style="stroke:var(--compare-a)" stroke-width="1"/&gt;&lt;line x1="160" y1="82" x2="99" y2="118" style="stroke:var(--compare-a)" stroke-width="1"/&gt;&lt;line x1="160" y1="82" x2="155" y2="118" style="stroke:var(--compare-a)" stroke-width="1"/&gt;&lt;line x1="160" y1="82" x2="211" y2="118" style="stroke:var(--compare-a)" stroke-width="1"/&gt;&lt;line x1="160" y1="82" x2="267" y2="118" style="stroke:var(--compare-a)" stroke-width="1"/&gt;&lt;rect x="19" y="118" width="48" height="26" rx="3" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.2"/&gt;&lt;rect x="75" y="118" width="48" height="26" rx="3" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.2"/&gt;&lt;rect x="131" y="118" width="48" height="26" rx="3" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.2"/&gt;&lt;rect x="187" y="118" width="48" height="26" rx="3" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.2"/&gt;&lt;rect x="243" y="118" width="48" height="26" rx="3" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.2"/&gt;&lt;text x="43" y="135" text-anchor="middle" font-size="9" style="fill:var(--content)"&gt;IDX&lt;/text&gt;&lt;text x="99" y="135" text-anchor="middle" font-size="9" style="fill:var(--content)"&gt;IDX&lt;/text&gt;&lt;text x="155" y="135" text-anchor="middle" font-size="9" style="fill:var(--content)"&gt;IDX&lt;/text&gt;&lt;text x="211" y="135" text-anchor="middle" font-size="9" style="fill:var(--content)"&gt;IDX&lt;/text&gt;&lt;text x="267" y="135" text-anchor="middle" font-size="9" style="fill:var(--content)"&gt;IDX&lt;/text&gt;&lt;text x="160" y="160" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;5 index updates per write&lt;/text&gt;&lt;line x1="43" y1="144" x2="160" y2="180" style="stroke:var(--compare-a)" stroke-width="1"/&gt;&lt;line x1="99" y1="144" x2="160" y2="180" style="stroke:var(--compare-a)" stroke-width="1"/&gt;&lt;line x1="155" y1="144" x2="160" y2="180" style="stroke:var(--compare-a)" stroke-width="1"/&gt;&lt;line x1="211" y1="144" x2="160" y2="180" style="stroke:var(--compare-a)" stroke-width="1"/&gt;&lt;line x1="267" y1="144" x2="160" y2="180" style="stroke:var(--compare-a)" stroke-width="1"/&gt;&lt;rect x="90" y="180" width="140" height="32" rx="3" style="fill:none;stroke:var(--border)" stroke-width="1.2"/&gt;&lt;text x="160" y="200" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;DISK&lt;/text&gt;&lt;text x="160" y="240" text-anchor="middle" font-size="11" style="fill:var(--secondary)"&gt;Write latency&lt;/text&gt;&lt;rect x="60" y="250" width="200" height="18" rx="3" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.2"/&gt;&lt;text x="160" y="290" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;Higher latency, faster reads&lt;/text&gt;&lt;line x1="480" y1="82" x2="452" y2="118" style="stroke:var(--compare-b)" stroke-width="1"/&gt;&lt;line x1="480" y1="82" x2="508" y2="118" style="stroke:var(--compare-b)" stroke-width="1"/&gt;&lt;rect x="428" y="118" width="48" height="26" rx="3" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.2"/&gt;&lt;rect x="484" y="118" width="48" height="26" rx="3" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.2"/&gt;&lt;text x="452" y="135" text-anchor="middle" font-size="9" style="fill:var(--content)"&gt;IDX&lt;/text&gt;&lt;text x="508" y="135" text-anchor="middle" font-size="9" style="fill:var(--content)"&gt;IDX&lt;/text&gt;&lt;text x="480" y="160" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;2 index updates per write&lt;/text&gt;&lt;line x1="452" y1="144" x2="480" y2="180" style="stroke:var(--compare-b)" stroke-width="1"/&gt;&lt;line x1="508" y1="144" x2="480" y2="180" style="stroke:var(--compare-b)" stroke-width="1"/&gt;&lt;rect x="410" y="180" width="140" height="32" rx="3" style="fill:none;stroke:var(--border)" stroke-width="1.2"/&gt;&lt;text x="480" y="200" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;DISK&lt;/text&gt;&lt;text x="480" y="240" text-anchor="middle" font-size="11" style="fill:var(--secondary)"&gt;Write latency&lt;/text&gt;&lt;rect x="430" y="250" width="100" height="18" rx="3" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.2"/&gt;&lt;text x="480" y="290" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;Lower latency, slower reads&lt;/text&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;h2 id="comparison-table"&gt;Comparison Table&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Many Indexes&lt;/th&gt;
&lt;th&gt;Few Indexes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Write path&lt;/td&gt;
&lt;td&gt;Every INSERT/UPDATE/DELETE also updates each index&amp;rsquo;s structure&lt;/td&gt;
&lt;td&gt;Writes mostly touch just the base table (or 1-2 indexes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Structures updated per write&lt;/td&gt;
&lt;td&gt;One update per index plus the table (N+1 operations)&lt;/td&gt;
&lt;td&gt;Minimal: table plus a small, fixed set of index updates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disk I/O per write&lt;/td&gt;
&lt;td&gt;Extra page writes and WAL/journal entries for each index B-tree&lt;/td&gt;
&lt;td&gt;Fewer page writes, smaller transaction log footprint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write throughput &amp;amp; latency&lt;/td&gt;
&lt;td&gt;Lower sustained throughput; each write costs more&lt;/td&gt;
&lt;td&gt;Higher sustained throughput; commits return faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read/query performance&lt;/td&gt;
&lt;td&gt;Fast lookups and filtering across many indexed columns&lt;/td&gt;
&lt;td&gt;Slower queries on unindexed columns; more full scans&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage footprint&lt;/td&gt;
&lt;td&gt;Larger on-disk size from redundant index copies of data&lt;/td&gt;
&lt;td&gt;Smaller footprint, closer to raw table size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance cost&lt;/td&gt;
&lt;td&gt;Rebuilds, vacuums, and statistics updates scale with index count&lt;/td&gt;
&lt;td&gt;Cheaper, faster maintenance windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best-fit workload&lt;/td&gt;
&lt;td&gt;Read-heavy, query-diverse systems (reporting, OLAP)&lt;/td&gt;
&lt;td&gt;Write-heavy, ingest-heavy systems (logging, OLTP, ETL)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="key-differences"&gt;Key Differences&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Every extra index adds a corresponding update on each &lt;strong class="kw"&gt;write&lt;/strong&gt; operation, not just at read time.&lt;/li&gt;
&lt;li&gt;Many indexes shrink &lt;strong class="kw"&gt;query latency&lt;/strong&gt; but inflate &lt;strong class="kw"&gt;insert cost&lt;/strong&gt; on the same table.&lt;/li&gt;
&lt;li&gt;Fewer indexes cut &lt;strong class="kw"&gt;WAL volume&lt;/strong&gt; and lock contention during heavy write bursts.&lt;/li&gt;
&lt;li&gt;Index count is a direct trade between &lt;strong class="kw"&gt;read performance&lt;/strong&gt; and &lt;strong class="kw"&gt;write throughput&lt;/strong&gt;, not a free win.&lt;/li&gt;
&lt;li&gt;&lt;strong class="kw"&gt;Rebuild and vacuum&lt;/strong&gt; overhead grows with every index the database has to maintain.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-to-use-each"&gt;When to Use Each&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Many Indexes&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Normalization vs Denormalization: Split Tables vs Duplicated Data</title><link>https://comparison.metacog.co.kr/posts/2026-09-06-normalization-vs-denormalization-split-tables-vs-duplicated/</link><pubDate>Sun, 06 Sep 2026 09:38:11 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-09-06-normalization-vs-denormalization-split-tables-vs-duplicated/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;Normalization organizes data into separate, related tables to eliminate redundancy and protect &lt;strong class="kw"&gt;integrity&lt;/strong&gt;, while denormalization intentionally merges and duplicates data to boost &lt;strong class="kw"&gt;read speed&lt;/strong&gt;. The right choice depends on whether your workload is dominated by frequent writes or by heavy, complex reads.&lt;/p&gt;
&lt;h2 id="comparison-diagram"&gt;Comparison Diagram&lt;/h2&gt;
&lt;div class="compare-diagram"&gt;
&lt;svg viewBox="0 0 640 360" xmlns="http://www.w3.org/2000/svg"&gt;&lt;text x="160" y="36" text-anchor="middle" font-size="18" style="fill:var(--primary)"&gt;Normalization&lt;/text&gt;&lt;text x="480" y="36" text-anchor="middle" font-size="18" style="fill:var(--primary)"&gt;Denormalization&lt;/text&gt;&lt;rect x="40" y="70" width="180" height="56" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="130" y="92" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Users&lt;/text&gt;&lt;text x="130" y="108" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;id, name&lt;/text&gt;&lt;rect x="40" y="156" width="180" height="56" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="130" y="178" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Orders&lt;/text&gt;&lt;text x="130" y="194" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;id, user_id, product_id&lt;/text&gt;&lt;rect x="40" y="242" width="180" height="56" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="130" y="264" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Products&lt;/text&gt;&lt;text x="130" y="280" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;id, name, price&lt;/text&gt;&lt;line x1="130" y1="126" x2="130" y2="156" style="stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;path d="M 220 184 C 260 184 260 270 220 270" fill="none" style="stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="130" y="316" text-anchor="middle" font-size="11" style="fill:var(--secondary)"&gt;3 linked tables, zero duplication&lt;/text&gt;&lt;rect x="360" y="70" width="240" height="228" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;line x1="360" y1="108" x2="600" y2="108" style="stroke:var(--compare-b)" stroke-width="1"/&gt;&lt;line x1="360" y1="146" x2="600" y2="146" style="stroke:var(--compare-b)" stroke-width="1"/&gt;&lt;line x1="360" y1="184" x2="600" y2="184" style="stroke:var(--compare-b)" stroke-width="1"/&gt;&lt;line x1="360" y1="222" x2="600" y2="222" style="stroke:var(--compare-b)" stroke-width="1"/&gt;&lt;line x1="360" y1="260" x2="600" y2="260" style="stroke:var(--compare-b)" stroke-width="1"/&gt;&lt;text x="375" y="92" font-size="11" style="fill:var(--primary)"&gt;order_id | customer | product&lt;/text&gt;&lt;text x="375" y="130" font-size="11" style="fill:var(--content)"&gt;101 | Alice | Widget&lt;/text&gt;&lt;text x="375" y="168" font-size="11" style="fill:var(--content)"&gt;102 | Alice | Gadget&lt;/text&gt;&lt;text x="375" y="206" font-size="11" style="fill:var(--content)"&gt;103 | Bob | Widget&lt;/text&gt;&lt;text x="375" y="244" font-size="11" style="fill:var(--content)"&gt;104 | Bob | Gizmo&lt;/text&gt;&lt;text x="480" y="316" text-anchor="middle" font-size="11" style="fill:var(--secondary)"&gt;1 wide table, repeated values&lt;/text&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;h2 id="comparison-table"&gt;Comparison Table&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Normalization&lt;/th&gt;
&lt;th&gt;Denormalization&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Design goal&lt;/td&gt;
&lt;td&gt;Eliminate redundancy by decomposing data into logical entities&lt;/td&gt;
&lt;td&gt;Optimize for fast retrieval by pre-combining related data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Table structure&lt;/td&gt;
&lt;td&gt;Many narrow, related tables linked by foreign keys&lt;/td&gt;
&lt;td&gt;Fewer, wider tables that embed related data directly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data redundancy&lt;/td&gt;
&lt;td&gt;Minimal; each fact stored in exactly one place&lt;/td&gt;
&lt;td&gt;Deliberate; the same fact may appear in many rows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write operations&lt;/td&gt;
&lt;td&gt;Single-row updates ripple correctly since data lives once&lt;/td&gt;
&lt;td&gt;Updates must touch every duplicated copy or drift occurs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read operations&lt;/td&gt;
&lt;td&gt;Requires assembling data from multiple tables&lt;/td&gt;
&lt;td&gt;Data is already co-located, so reads are direct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Joins needed&lt;/td&gt;
&lt;td&gt;Frequent, often multi-table joins for common queries&lt;/td&gt;
&lt;td&gt;Rare or none, since data is flattened in advance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data integrity risk&lt;/td&gt;
&lt;td&gt;Low; constraints enforce a single source of truth&lt;/td&gt;
&lt;td&gt;Higher; duplicate copies can become inconsistent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage requirements&lt;/td&gt;
&lt;td&gt;Compact, no duplicated values&lt;/td&gt;
&lt;td&gt;Larger footprint due to stored redundancy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="key-differences"&gt;Key Differences&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Normalization removes &lt;strong class="kw"&gt;redundancy&lt;/strong&gt; by splitting data into related tables; denormalization reintroduces it deliberately for speed&lt;/li&gt;
&lt;li&gt;Normalized schemas need more &lt;strong class="kw"&gt;joins&lt;/strong&gt; at read time, while denormalized ones avoid them by pre-joining data&lt;/li&gt;
&lt;li&gt;Denormalization trades update simplicity for risk of &lt;strong class="kw"&gt;anomalies&lt;/strong&gt; when duplicated copies fall out of sync&lt;/li&gt;
&lt;li&gt;Normalization favors &lt;strong class="kw"&gt;write-heavy&lt;/strong&gt; transactional workloads; denormalization favors &lt;strong class="kw"&gt;read-heavy&lt;/strong&gt; analytical ones&lt;/li&gt;
&lt;li&gt;Storage cost is lower under normalization but query complexity is lower under denormalization&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-to-use-each"&gt;When to Use Each&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Normalization&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Star Schema vs Snowflake Schema: Dimensional Modeling Compared</title><link>https://comparison.metacog.co.kr/posts/2026-08-04-star-schema-vs-snowflake-schema-dimensional-modeling-compare/</link><pubDate>Tue, 04 Aug 2026 05:19:48 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-08-04-star-schema-vs-snowflake-schema-dimensional-modeling-compare/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;Star schema and snowflake schema are two ways to structure dimension tables around a fact table in a data warehouse. Star schema keeps dimensions flat and &lt;strong class="kw"&gt;denormalized&lt;/strong&gt; for fast, simple joins, while snowflake schema splits dimensions into related sub-tables that are &lt;strong class="kw"&gt;normalized&lt;/strong&gt; to reduce redundancy. The choice trades query simplicity against storage efficiency and data integrity.&lt;/p&gt;
&lt;h2 id="comparison-diagram"&gt;Comparison Diagram&lt;/h2&gt;
&lt;div class="compare-diagram"&gt;
&lt;svg viewBox="0 0 640 360" xmlns="http://www.w3.org/2000/svg"&gt;
&lt;line x1="320" y1="20" x2="320" y2="340" style="stroke:var(--border)" stroke-width="1" stroke-dasharray="4,4"/&gt;
&lt;text x="160" y="35" text-anchor="middle" style="fill:var(--primary)" font-size="16" font-weight="bold"&gt;Star Schema&lt;/text&gt;
&lt;text x="480" y="35" text-anchor="middle" style="fill:var(--primary)" font-size="16" font-weight="bold"&gt;Snowflake Schema&lt;/text&gt;
&lt;rect x="135" y="172" width="50" height="36" rx="3" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;
&lt;text x="160" y="194" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;Fact&lt;/text&gt;
&lt;rect x="130" y="86" width="60" height="28" rx="3" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;
&lt;text x="160" y="104" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;Dim&lt;/text&gt;
&lt;rect x="130" y="276" width="60" height="28" rx="3" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;
&lt;text x="160" y="294" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;Dim&lt;/text&gt;
&lt;rect x="30" y="176" width="60" height="28" rx="3" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;
&lt;text x="60" y="194" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;Dim&lt;/text&gt;
&lt;rect x="230" y="176" width="60" height="28" rx="3" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;
&lt;text x="260" y="194" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;Dim&lt;/text&gt;
&lt;line x1="160" y1="172" x2="160" y2="114" style="stroke:var(--compare-a)" stroke-width="1.5"/&gt;
&lt;line x1="160" y1="208" x2="160" y2="276" style="stroke:var(--compare-a)" stroke-width="1.5"/&gt;
&lt;line x1="135" y1="190" x2="90" y2="190" style="stroke:var(--compare-a)" stroke-width="1.5"/&gt;
&lt;line x1="185" y1="190" x2="230" y2="190" style="stroke:var(--compare-a)" stroke-width="1.5"/&gt;
&lt;text x="160" y="335" text-anchor="middle" style="fill:var(--secondary)" font-size="11"&gt;Dimensions denormalized, one hop to fact&lt;/text&gt;
&lt;rect x="455" y="172" width="50" height="36" rx="3" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;
&lt;text x="480" y="194" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;Fact&lt;/text&gt;
&lt;rect x="365" y="177" width="50" height="26" rx="3" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;
&lt;text x="390" y="194" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;Dim&lt;/text&gt;
&lt;rect x="545" y="177" width="50" height="26" rx="3" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;
&lt;text x="570" y="194" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;Dim&lt;/text&gt;
&lt;line x1="455" y1="190" x2="415" y2="190" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;
&lt;line x1="505" y1="190" x2="545" y2="190" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;
&lt;rect x="450" y="127" width="60" height="26" rx="3" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;
&lt;text x="480" y="144" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;Dim&lt;/text&gt;
&lt;rect x="450" y="57" width="60" height="26" rx="3" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;
&lt;text x="480" y="74" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;Sub&lt;/text&gt;
&lt;line x1="480" y1="172" x2="480" y2="153" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;
&lt;line x1="480" y1="127" x2="480" y2="83" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;
&lt;rect x="450" y="227" width="60" height="26" rx="3" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;
&lt;text x="480" y="244" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;Dim&lt;/text&gt;
&lt;rect x="450" y="287" width="60" height="26" rx="3" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;
&lt;text x="480" y="304" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;Sub&lt;/text&gt;
&lt;line x1="480" y1="208" x2="480" y2="227" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;
&lt;line x1="480" y1="253" x2="480" y2="287" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;
&lt;text x="480" y="335" text-anchor="middle" style="fill:var(--secondary)" font-size="11"&gt;Dimensions normalized into sub-tables&lt;/text&gt;
&lt;/svg&gt;
&lt;/div&gt;
&lt;h2 id="comparison-table"&gt;Comparison Table&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Star Schema&lt;/th&gt;
&lt;th&gt;Snowflake Schema&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dimension structure&lt;/td&gt;
&lt;td&gt;Each dimension is a single flat table with all descriptive attributes together&lt;/td&gt;
&lt;td&gt;Dimensions are split into multiple related tables organized by hierarchy level&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data redundancy&lt;/td&gt;
&lt;td&gt;Attributes like category or region repeat across many rows within a dimension&lt;/td&gt;
&lt;td&gt;Redundant attributes are moved into separate sub-tables and referenced by key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Join complexity per query&lt;/td&gt;
&lt;td&gt;Fact table joins directly to each dimension, one hop per dimension&lt;/td&gt;
&lt;td&gt;Queries often need multi-level joins through sub-dimension chains to reach an attribute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query performance&lt;/td&gt;
&lt;td&gt;Fewer joins generally mean faster scans and simpler execution plans&lt;/td&gt;
&lt;td&gt;Extra joins across normalized levels typically add query latency and planning overhead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage footprint&lt;/td&gt;
&lt;td&gt;Larger on disk due to repeated attribute values across rows&lt;/td&gt;
&lt;td&gt;Smaller footprint since each attribute value is stored once and referenced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Update and integrity handling&lt;/td&gt;
&lt;td&gt;Updating a shared attribute means touching many rows, risking inconsistency&lt;/td&gt;
&lt;td&gt;Updating a shared attribute means changing one row in a sub-table, preserving integrity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ETL and load complexity&lt;/td&gt;
&lt;td&gt;Simpler load logic since each dimension maps to one target table&lt;/td&gt;
&lt;td&gt;More complex load logic to populate and link multiple normalized tables correctly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BI tool and end-user friendliness&lt;/td&gt;
&lt;td&gt;Flat structure maps naturally to how most BI tools expect dimensions&lt;/td&gt;
&lt;td&gt;Nested hierarchies can confuse drag-and-drop BI tools and require extra modeling&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="key-differences"&gt;Key Differences&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Star schema keeps each dimension as one &lt;strong class="kw"&gt;flat table&lt;/strong&gt;; snowflake schema breaks dimensions into &lt;strong class="kw"&gt;normalized&lt;/strong&gt; sub-tables.&lt;/li&gt;
&lt;li&gt;Star schema favors fewer joins and faster &lt;strong class="kw"&gt;read performance&lt;/strong&gt;; snowflake schema favors lower &lt;strong class="kw"&gt;storage redundancy&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Snowflake schema reduces update anomalies by centralizing shared attributes, improving &lt;strong class="kw"&gt;data integrity&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Star schema is generally the default recommendation in &lt;strong class="kw"&gt;Kimball-style&lt;/strong&gt; dimensional modeling for BI workloads.&lt;/li&gt;
&lt;li&gt;Snowflake schema&amp;rsquo;s extra joins increase &lt;strong class="kw"&gt;query complexity&lt;/strong&gt; for both engines and end users.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-to-use-each"&gt;When to Use Each&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Star Schema&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Shared Database vs Database per Service: Data Ownership in Microservices</title><link>https://comparison.metacog.co.kr/posts/2026-08-04-shared-database-vs-database-per-service-data-ownership-in-mi/</link><pubDate>Tue, 04 Aug 2026 05:14:24 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-08-04-shared-database-vs-database-per-service-data-ownership-in-mi/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;A &lt;strong class="kw"&gt;shared database&lt;/strong&gt; lets multiple services read and write the same tables through one common schema, while &lt;strong class="kw"&gt;database per service&lt;/strong&gt; gives each service its own private data store that only it can touch directly. The choice determines how tightly services are coupled at the data layer, how independently teams can deploy, and how much work cross-service queries and transactions require.&lt;/p&gt;
&lt;h2 id="comparison-diagram"&gt;Comparison Diagram&lt;/h2&gt;
&lt;div class="compare-diagram"&gt;
&lt;svg viewBox="0 0 640 360" xmlns="http://www.w3.org/2000/svg"&gt;&lt;line x1="320" y1="20" x2="320" y2="340" style="stroke:var(--border)" stroke-width="1" stroke-dasharray="4 4"/&gt;&lt;text x="160" y="32" text-anchor="middle" style="fill:var(--primary)" font-size="16" font-weight="bold"&gt;Shared Database&lt;/text&gt;&lt;text x="480" y="32" text-anchor="middle" style="fill:var(--primary)" font-size="16" font-weight="bold"&gt;Database per Service&lt;/text&gt;&lt;rect x="30" y="55" width="70" height="35" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="65" y="77" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;Service A&lt;/text&gt;&lt;rect x="125" y="55" width="70" height="35" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="160" y="77" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;Service B&lt;/text&gt;&lt;rect x="220" y="55" width="70" height="35" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="255" y="77" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;Service C&lt;/text&gt;&lt;line x1="65" y1="90" x2="140" y2="168" style="stroke:var(--secondary)" stroke-width="1.5"/&gt;&lt;line x1="160" y1="90" x2="160" y2="168" style="stroke:var(--secondary)" stroke-width="1.5"/&gt;&lt;line x1="255" y1="90" x2="180" y2="168" style="stroke:var(--secondary)" stroke-width="1.5"/&gt;&lt;rect x="115" y="170" width="90" height="55" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;ellipse cx="160" cy="170" rx="45" ry="12" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;ellipse cx="160" cy="225" rx="45" ry="12" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="160" y="204" text-anchor="middle" style="fill:var(--content)" font-size="12" font-weight="bold"&gt;Shared DB&lt;/text&gt;&lt;text x="160" y="300" text-anchor="middle" style="fill:var(--secondary)" font-size="11"&gt;one schema, every service reads/writes it directly&lt;/text&gt;&lt;rect x="355" y="48" width="100" height="180" rx="6" style="fill:none;stroke:var(--border)" stroke-width="1" stroke-dasharray="4 3"/&gt;&lt;rect x="450" y="48" width="100" height="180" rx="6" style="fill:none;stroke:var(--border)" stroke-width="1" stroke-dasharray="4 3"/&gt;&lt;rect x="545" y="48" width="90" height="180" rx="6" style="fill:none;stroke:var(--border)" stroke-width="1" stroke-dasharray="4 3"/&gt;&lt;rect x="370" y="55" width="70" height="35" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="405" y="77" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;Service X&lt;/text&gt;&lt;rect x="465" y="55" width="70" height="35" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="500" y="77" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;Service Y&lt;/text&gt;&lt;rect x="560" y="55" width="70" height="35" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="595" y="77" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;Service Z&lt;/text&gt;&lt;line x1="405" y1="90" x2="405" y2="150" style="stroke:var(--secondary)" stroke-width="1.5"/&gt;&lt;line x1="500" y1="90" x2="500" y2="150" style="stroke:var(--secondary)" stroke-width="1.5"/&gt;&lt;line x1="595" y1="90" x2="595" y2="150" style="stroke:var(--secondary)" stroke-width="1.5"/&gt;&lt;rect x="375" y="150" width="60" height="42" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;ellipse cx="405" cy="150" rx="30" ry="8" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;ellipse cx="405" cy="192" rx="30" ry="8" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="405" y="175" text-anchor="middle" style="fill:var(--content)" font-size="10"&gt;DB X&lt;/text&gt;&lt;rect x="470" y="150" width="60" height="42" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;ellipse cx="500" cy="150" rx="30" ry="8" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;ellipse cx="500" cy="192" rx="30" ry="8" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="500" y="175" text-anchor="middle" style="fill:var(--content)" font-size="10"&gt;DB Y&lt;/text&gt;&lt;rect x="565" y="150" width="60" height="42" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;ellipse cx="595" cy="150" rx="30" ry="8" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;ellipse cx="595" cy="192" rx="30" ry="8" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="595" y="175" text-anchor="middle" style="fill:var(--content)" font-size="10"&gt;DB Z&lt;/text&gt;&lt;text x="480" y="300" text-anchor="middle" style="fill:var(--secondary)" font-size="11"&gt;each service owns a private schema, accessed only via its API&lt;/text&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;h2 id="comparison-table"&gt;Comparison Table&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Shared Database&lt;/th&gt;
&lt;th&gt;Database per Service&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Data ownership&lt;/td&gt;
&lt;td&gt;No single owner — all services see and can modify the same tables&lt;/td&gt;
&lt;td&gt;Each service exclusively owns its schema; no one else can touch it directly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access path&lt;/td&gt;
&lt;td&gt;Services query the database directly, often with raw SQL against shared tables&lt;/td&gt;
&lt;td&gt;Other services only get data through the owning service&amp;rsquo;s API or published events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-service transactions&lt;/td&gt;
&lt;td&gt;Native ACID transactions and joins span all the data in one commit&lt;/td&gt;
&lt;td&gt;No shared transaction; consistency across services needs sagas or eventual consistency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-service queries&lt;/td&gt;
&lt;td&gt;Simple SQL joins pull data from any table in one query&lt;/td&gt;
&lt;td&gt;Requires API composition, data replication, or a separate CQRS read model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema changes&lt;/td&gt;
&lt;td&gt;A column or table change can silently break unrelated services&lt;/td&gt;
&lt;td&gt;Schema changes are internal; only the public API contract must stay stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Technology choice&lt;/td&gt;
&lt;td&gt;All services are locked into one database engine and schema&lt;/td&gt;
&lt;td&gt;Each service can pick the storage engine that fits its data (polyglot persistence)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure isolation&lt;/td&gt;
&lt;td&gt;A database outage or lock contention affects every service at once&lt;/td&gt;
&lt;td&gt;An outage in one service&amp;rsquo;s database doesn&amp;rsquo;t directly take down the others&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operational overhead&lt;/td&gt;
&lt;td&gt;One database to provision, back up, and tune&lt;/td&gt;
&lt;td&gt;N databases to provision, monitor, back up, and scale independently&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="key-differences"&gt;Key Differences&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Shared database gives every service direct access to the same tables, so a change in one place can silently break another service&amp;rsquo;s queries — a form of &lt;strong class="kw"&gt;tight coupling&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Database per service forces all cross-service data access through an API, giving each service true &lt;strong class="kw"&gt;encapsulation&lt;/strong&gt; of its data.&lt;/li&gt;
&lt;li&gt;Cross-entity consistency is a native &lt;strong class="kw"&gt;ACID transaction&lt;/strong&gt; in a shared database, but needs a &lt;strong class="kw"&gt;saga pattern&lt;/strong&gt; or eventual consistency once data is split per service.&lt;/li&gt;
&lt;li&gt;Reporting and ad-hoc joins are trivial with a shared database&amp;rsquo;s SQL, while database per service usually needs a separate &lt;strong class="kw"&gt;CQRS read model&lt;/strong&gt; to answer cross-service queries.&lt;/li&gt;
&lt;li&gt;Database per service allows &lt;strong class="kw"&gt;polyglot persistence&lt;/strong&gt; — each service picks its own database engine — whereas shared database locks every service to one engine and schema.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-to-use-each"&gt;When to Use Each&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Shared Database&lt;/strong&gt;&lt;/p&gt;</description></item></channel></rss>