<?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>System-Design on IT Comparison</title><link>https://comparison.metacog.co.kr/tags/system-design/</link><description>Recent content in System-Design on IT Comparison</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Sun, 06 Sep 2026 10:02:40 +0900</lastBuildDate><atom:link href="https://comparison.metacog.co.kr/tags/system-design/index.xml" rel="self" type="application/rss+xml"/><item><title>Batch vs Stream Processing: Bounded Data Dumps vs Continuous Event Flow</title><link>https://comparison.metacog.co.kr/posts/2026-09-06-batch-vs-stream-processing-bounded-data-dumps-vs-continuous/</link><pubDate>Sun, 06 Sep 2026 10:02:40 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-09-06-batch-vs-stream-processing-bounded-data-dumps-vs-continuous/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;Batch processing collects data over a period and runs computation on the whole bounded set at once, while stream processing handles each event as it arrives, continuously. The choice determines whether your system optimizes for &lt;strong class="kw"&gt;throughput and simplicity&lt;/strong&gt; or &lt;strong class="kw"&gt;low latency&lt;/strong&gt; on fresh results.&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;Batch&lt;/text&gt;&lt;text x="480" y="36" text-anchor="middle" font-size="18" style="fill:var(--primary)"&gt;Stream&lt;/text&gt;&lt;rect x="40" y="60" width="240" height="90" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="160" y="95" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;Data accumulates&lt;/text&gt;&lt;text x="160" y="115" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;into a bounded set&lt;/text&gt;&lt;circle cx="70" cy="130" r="5" style="fill:var(--compare-a)"/&gt;&lt;circle cx="110" cy="130" r="5" style="fill:var(--compare-a)"/&gt;&lt;circle cx="150" cy="130" r="5" style="fill:var(--compare-a)"/&gt;&lt;circle cx="190" cy="130" r="5" style="fill:var(--compare-a)"/&gt;&lt;circle cx="230" cy="130" r="5" style="fill:var(--compare-a)"/&gt;&lt;path d="M160 150 L160 190" style="stroke:var(--compare-a)" stroke-width="2" marker-end="url(#arrowA)"/&gt;&lt;rect x="80" y="195" width="160" height="55" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="160" y="217" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;Scheduled job&lt;/text&gt;&lt;text x="160" y="235" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;processes all at once&lt;/text&gt;&lt;path d="M160 250 L160 285" style="stroke:var(--compare-a)" stroke-width="2" marker-end="url(#arrowA)"/&gt;&lt;rect x="70" y="290" width="180" height="45" rx="4" style="fill:none;stroke:var(--compare-a)" stroke-width="1.5" stroke-dasharray="4 3"/&gt;&lt;text x="160" y="317" text-anchor="middle" font-size="13" style="fill:var(--secondary)"&gt;Result: high latency&lt;/text&gt;&lt;rect x="400" y="60" width="200" height="280" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="500" y="85" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;Events flow continuously&lt;/text&gt;&lt;circle cx="430" cy="115" r="5" style="fill:var(--compare-b)"/&gt;&lt;path d="M430 115 L490 140" style="stroke:var(--compare-b)" stroke-width="1.5" marker-end="url(#arrowB)"/&gt;&lt;rect x="460" y="130" width="80" height="24" rx="3" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1"/&gt;&lt;text x="500" y="146" text-anchor="middle" font-size="10" style="fill:var(--content)"&gt;process&lt;/text&gt;&lt;circle cx="430" cy="175" r="5" style="fill:var(--compare-b)"/&gt;&lt;path d="M430 175 L490 190" style="stroke:var(--compare-b)" stroke-width="1.5" marker-end="url(#arrowB)"/&gt;&lt;rect x="460" y="180" width="80" height="24" rx="3" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1"/&gt;&lt;text x="500" y="196" text-anchor="middle" font-size="10" style="fill:var(--content)"&gt;process&lt;/text&gt;&lt;circle cx="430" cy="235" r="5" style="fill:var(--compare-b)"/&gt;&lt;path d="M430 235 L490 240" style="stroke:var(--compare-b)" stroke-width="1.5" marker-end="url(#arrowB)"/&gt;&lt;rect x="460" y="230" width="80" height="24" rx="3" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1"/&gt;&lt;text x="500" y="246" text-anchor="middle" font-size="10" style="fill:var(--content)"&gt;process&lt;/text&gt;&lt;circle cx="430" cy="290" r="5" style="fill:var(--compare-b)"/&gt;&lt;path d="M430 290 L490 285" style="stroke:var(--compare-b)" stroke-width="1.5" marker-end="url(#arrowB)"/&gt;&lt;rect x="460" y="278" width="80" height="24" rx="3" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1"/&gt;&lt;text x="500" y="294" text-anchor="middle" font-size="10" style="fill:var(--content)"&gt;process&lt;/text&gt;&lt;text x="500" y="325" text-anchor="middle" font-size="13" style="fill:var(--secondary)"&gt;Result: low latency&lt;/text&gt;&lt;defs&gt;&lt;marker id="arrowA" markerWidth="8" markerHeight="8" refX="4" refY="4" orient="auto"&gt;&lt;path d="M0,0 L8,4 L0,8 Z" style="fill:var(--compare-a)"/&gt;&lt;/marker&gt;&lt;marker id="arrowB" markerWidth="8" markerHeight="8" refX="4" refY="4" orient="auto"&gt;&lt;path d="M0,0 L8,4 L0,8 Z" style="fill:var(--compare-b)"/&gt;&lt;/marker&gt;&lt;/defs&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;Batch Processing&lt;/th&gt;
&lt;th&gt;Stream Processing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Data ingestion&lt;/td&gt;
&lt;td&gt;Data collected and stored until job triggers&lt;/td&gt;
&lt;td&gt;Events consumed individually as they arrive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data scope per run&lt;/td&gt;
&lt;td&gt;Bounded, finite dataset (a file, a partition, a day&amp;rsquo;s data)&lt;/td&gt;
&lt;td&gt;Unbounded, continuous sequence of events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Processing trigger&lt;/td&gt;
&lt;td&gt;Scheduled interval or manual kickoff (hourly, nightly)&lt;/td&gt;
&lt;td&gt;Continuous, triggered by each event or micro-window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency to result&lt;/td&gt;
&lt;td&gt;Minutes to hours, depending on schedule&lt;/td&gt;
&lt;td&gt;Milliseconds to seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State management&lt;/td&gt;
&lt;td&gt;Recomputed fresh from full dataset each run&lt;/td&gt;
&lt;td&gt;Maintained incrementally across the event stream&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault recovery&lt;/td&gt;
&lt;td&gt;Rerun the failed job against the same input&lt;/td&gt;
&lt;td&gt;Checkpointing and replay from an offset in the log&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ordering guarantees&lt;/td&gt;
&lt;td&gt;Whole dataset available, so ordering enforced within the job&lt;/td&gt;
&lt;td&gt;Ordering must be explicitly handled (per-key, watermarks)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resource usage pattern&lt;/td&gt;
&lt;td&gt;Spiky: idle, then a burst of compute at run time&lt;/td&gt;
&lt;td&gt;Steady, sustained compute and memory footprint&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;Batch operates on a &lt;strong class="kw"&gt;bounded dataset&lt;/strong&gt;, stream operates on an &lt;strong class="kw"&gt;unbounded sequence&lt;/strong&gt; of events&lt;/li&gt;
&lt;li&gt;Batch trades latency for &lt;strong class="kw"&gt;simplicity and throughput&lt;/strong&gt;; stream trades complexity for &lt;strong class="kw"&gt;freshness&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Stream systems need &lt;strong class="kw"&gt;watermarks&lt;/strong&gt; to handle late or out-of-order events, which batch avoids entirely&lt;/li&gt;
&lt;li&gt;Failure recovery in batch means &lt;strong class="kw"&gt;rerunning the job&lt;/strong&gt;; stream relies on &lt;strong class="kw"&gt;checkpoint and replay&lt;/strong&gt; semantics&lt;/li&gt;
&lt;li&gt;Batch pipelines are easier to reason about and test since input is &lt;strong class="kw"&gt;fixed and reproducible&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;Batch Processing&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Single Model vs CQRS: One Data Model vs Split Read/Write Models</title><link>https://comparison.metacog.co.kr/posts/2026-09-06-single-model-vs-cqrs-one-data-model-vs-split-read-write-mode/</link><pubDate>Sun, 06 Sep 2026 09:53:08 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-09-06-single-model-vs-cqrs-one-data-model-vs-split-read-write-mode/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;A &lt;strong class="kw"&gt;single model&lt;/strong&gt; uses one unified set of classes and one schema for both reading and writing data, while &lt;strong class="kw"&gt;CQRS&lt;/strong&gt; (Command Query Responsibility Segregation) splits the system into separate write models (commands) and read models (queries) that can use different schemas, storage, or even databases. The choice matters because it trades simplicity and consistency for scalability and query flexibility as read and write demands diverge.&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="32" text-anchor="middle" font-size="16" style="fill:var(--primary)"&gt;Single Model&lt;/text&gt;&lt;text x="480" y="32" text-anchor="middle" font-size="16" style="fill:var(--primary)"&gt;CQRS&lt;/text&gt;&lt;rect x="60" y="60" width="90" height="40" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="105" y="85" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Read Req&lt;/text&gt;&lt;rect x="230" y="60" width="90" height="40" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="275" y="85" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Write Req&lt;/text&gt;&lt;path d="M150,80 L200,80" style="stroke:var(--compare-a)" stroke-width="1.5" marker-end="url(#arrowA1)"/&gt;&lt;path d="M230,80 L200,80" style="stroke:var(--compare-a)" stroke-width="1.5" marker-end="url(#arrowA2)"/&gt;&lt;rect x="140" y="140" width="140" height="60" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="2"/&gt;&lt;text x="210" y="165" text-anchor="middle" font-size="13" style="fill:var(--primary)"&gt;Model&lt;/text&gt;&lt;text x="210" y="183" text-anchor="middle" font-size="11" style="fill:var(--secondary)"&gt;reads + writes&lt;/text&gt;&lt;path d="M200,100 L210,140" style="stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;rect x="140" y="250" width="140" height="50" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="210" y="280" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;One DB / Schema&lt;/text&gt;&lt;path d="M210,200 L210,250" style="stroke:var(--compare-a)" stroke-width="1.5" marker-end="url(#arrowA3)"/&gt;&lt;rect x="390" y="60" width="90" height="40" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="435" y="85" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Query&lt;/text&gt;&lt;rect x="520" y="60" width="90" height="40" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="565" y="85" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Command&lt;/text&gt;&lt;rect x="395" y="140" width="90" height="55" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="2"/&gt;&lt;text x="440" y="163" text-anchor="middle" font-size="12" style="fill:var(--primary)"&gt;Read Model&lt;/text&gt;&lt;text x="440" y="180" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;optimized view&lt;/text&gt;&lt;rect x="525" y="140" width="90" height="55" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="2"/&gt;&lt;text x="570" y="163" text-anchor="middle" font-size="12" style="fill:var(--primary)"&gt;Write Model&lt;/text&gt;&lt;text x="570" y="180" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;domain logic&lt;/text&gt;&lt;path d="M435,100 L440,140" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;path d="M565,100 L570,140" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;rect x="395" y="260" width="90" height="45" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="440" y="287" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;Read Store&lt;/text&gt;&lt;rect x="525" y="260" width="90" height="45" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="570" y="287" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;Write Store&lt;/text&gt;&lt;path d="M440,195 L440,260" style="stroke:var(--compare-b)" stroke-width="1.5" marker-end="url(#arrowB1)"/&gt;&lt;path d="M570,195 L570,260" style="stroke:var(--compare-b)" stroke-width="1.5" marker-end="url(#arrowB2)"/&gt;&lt;path d="M525,282 C505,320 465,320 445,282" fill="none" style="stroke:var(--secondary)" stroke-width="1.2" stroke-dasharray="4,3" marker-end="url(#arrowB3)"/&gt;&lt;text x="485" y="335" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;sync / events&lt;/text&gt;&lt;defs&gt;&lt;marker id="arrowA1" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"&gt;&lt;path d="M0,0 L6,3 L0,6 Z" style="fill:var(--compare-a)"/&gt;&lt;/marker&gt;&lt;marker id="arrowA2" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"&gt;&lt;path d="M0,0 L6,3 L0,6 Z" style="fill:var(--compare-a)"/&gt;&lt;/marker&gt;&lt;marker id="arrowA3" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"&gt;&lt;path d="M0,0 L6,3 L0,6 Z" style="fill:var(--compare-a)"/&gt;&lt;/marker&gt;&lt;marker id="arrowB1" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"&gt;&lt;path d="M0,0 L6,3 L0,6 Z" style="fill:var(--compare-b)"/&gt;&lt;/marker&gt;&lt;marker id="arrowB2" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"&gt;&lt;path d="M0,0 L6,3 L0,6 Z" style="fill:var(--compare-b)"/&gt;&lt;/marker&gt;&lt;marker id="arrowB3" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"&gt;&lt;path d="M0,0 L6,3 L0,6 Z" style="fill:var(--secondary)"/&gt;&lt;/marker&gt;&lt;/defs&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;Single Model&lt;/th&gt;
&lt;th&gt;CQRS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Request entry point&lt;/td&gt;
&lt;td&gt;One API/service handles reads and writes through the same code path&lt;/td&gt;
&lt;td&gt;Requests are split upfront into command handlers and query handlers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data model shape&lt;/td&gt;
&lt;td&gt;One set of classes/entities represents the domain for every operation&lt;/td&gt;
&lt;td&gt;Separate write model (rich domain logic) and read model (denormalized, query-optimized)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write path&lt;/td&gt;
&lt;td&gt;Write validates and persists directly to the shared schema&lt;/td&gt;
&lt;td&gt;Command handler validates, applies business rules, persists to the write store&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read path&lt;/td&gt;
&lt;td&gt;Read queries the same schema writes use, often requiring joins&lt;/td&gt;
&lt;td&gt;Query handler reads from a precomputed, often denormalized read store&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sync between models&lt;/td&gt;
&lt;td&gt;Not applicable — there is only one model, so no sync is needed&lt;/td&gt;
&lt;td&gt;Read store is updated via events or projections after each write, introducing lag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Consistency guarantee&lt;/td&gt;
&lt;td&gt;Strong consistency by default since reads see writes immediately&lt;/td&gt;
&lt;td&gt;Eventual consistency between write and read sides unless engineered otherwise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling behavior&lt;/td&gt;
&lt;td&gt;Read and write load scale together since they share infrastructure&lt;/td&gt;
&lt;td&gt;Read and write sides scale independently to match different load profiles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operational complexity&lt;/td&gt;
&lt;td&gt;Low — one schema, one deployment, one mental model to maintain&lt;/td&gt;
&lt;td&gt;Higher — multiple stores, projection/event pipelines, and eventual-consistency debugging&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;Single model keeps one schema for everything; CQRS splits into a &lt;strong class="kw"&gt;write model&lt;/strong&gt; and a &lt;strong class="kw"&gt;read model&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;CQRS trades immediate consistency for &lt;strong class="kw"&gt;eventual consistency&lt;/strong&gt; via projections or events&lt;/li&gt;
&lt;li&gt;Single model is simpler to reason about; CQRS adds &lt;strong class="kw"&gt;operational overhead&lt;/strong&gt; from syncing multiple stores&lt;/li&gt;
&lt;li&gt;CQRS enables independent &lt;strong class="kw"&gt;scaling&lt;/strong&gt; of reads and writes; single model scales them together&lt;/li&gt;
&lt;li&gt;Query flexibility is higher in CQRS since read models can be shaped per &lt;strong class="kw"&gt;use case&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;Single Model&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Stateful vs Stateless: Where the Session Lives</title><link>https://comparison.metacog.co.kr/posts/2026-09-06-stateful-vs-stateless-where-the-session-lives/</link><pubDate>Sun, 06 Sep 2026 09:51:46 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-09-06-stateful-vs-stateless-where-the-session-lives/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;This comparison covers whether a server or protocol retains &lt;strong class="kw"&gt;session state&lt;/strong&gt; between requests, or treats every request as a fully &lt;strong class="kw"&gt;self-contained&lt;/strong&gt; unit with no memory of prior ones. The choice determines how you scale, fail over, and route traffic across instances.&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="32" text-anchor="middle" font-size="16" style="fill:var(--primary)"&gt;Stateful&lt;/text&gt;&lt;text x="480" y="32" text-anchor="middle" font-size="16" style="fill:var(--primary)"&gt;Stateless&lt;/text&gt;&lt;rect x="40" y="60" width="110" height="50" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="95" y="90" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;Client&lt;/text&gt;&lt;path d="M150 85 L230 85" style="stroke:var(--compare-a)" stroke-width="1.5" marker-end="url(#arrowA)"/&gt;&lt;rect x="230" y="60" width="120" height="50" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="290" y="84" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Server A&lt;/text&gt;&lt;text x="290" y="100" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;session: id=42&lt;/text&gt;&lt;path d="M290 110 L290 150" style="stroke:var(--border)" stroke-width="1.5" stroke-dasharray="4,3" marker-end="url(#arrowN)"/&gt;&lt;text x="330" y="135" font-size="10" style="fill:var(--secondary)"&gt;must return&lt;/text&gt;&lt;text x="330" y="148" font-size="10" style="fill:var(--secondary)"&gt;to same server&lt;/text&gt;&lt;rect x="230" y="160" width="120" height="50" rx="6" style="fill:none;stroke:var(--border)" stroke-width="1.5" stroke-dasharray="4,3"/&gt;&lt;text x="290" y="189" text-anchor="middle" font-size="12" style="fill:var(--secondary)"&gt;Server B&lt;/text&gt;&lt;text x="290" y="204" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;no session data&lt;/text&gt;&lt;path d="M95 110 L95 200 L230 200" style="stroke:var(--compare-a)" stroke-width="1.5" fill="none" marker-end="url(#arrowA)"/&gt;&lt;line x1="20" y1="260" x2="620" y2="260" style="stroke:var(--border)" stroke-width="1"/&gt;&lt;rect x="360" y="60" width="110" height="50" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="415" y="90" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;Client&lt;/text&gt;&lt;text x="415" y="106" text-anchor="middle" font-size="9" style="fill:var(--secondary)"&gt;carries token&lt;/text&gt;&lt;path d="M470 85 L540 85" style="stroke:var(--compare-b)" stroke-width="1.5" marker-end="url(#arrowB)"/&gt;&lt;rect x="540 " y="60" width="70" height="50" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="575" y="90" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;Server&lt;/text&gt;&lt;path d="M470 85 L540 160" style="stroke:var(--compare-b)" stroke-width="1.5" fill="none" marker-end="url(#arrowB)"/&gt;&lt;rect x="540" y="150" width="70" height="50" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="575" y="180" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;Server&lt;/text&gt;&lt;text x="415" y="200" font-size="10" style="fill:var(--secondary)"&gt;any server can&lt;/text&gt;&lt;text x="415" y="213" font-size="10" style="fill:var(--secondary)"&gt;handle the request&lt;/text&gt;&lt;text x="320" y="300" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Stateful: server pins session context and routing depends on it&lt;/text&gt;&lt;text x="320" y="325" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Stateless: request carries all context, any node can serve it&lt;/text&gt;&lt;defs&gt;&lt;marker id="arrowA" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto"&gt;&lt;path d="M0,0 L8,4 L0,8 z" style="fill:var(--compare-a)"/&gt;&lt;/marker&gt;&lt;marker id="arrowB" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto"&gt;&lt;path d="M0,0 L8,4 L0,8 z" style="fill:var(--compare-b)"/&gt;&lt;/marker&gt;&lt;marker id="arrowN" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto"&gt;&lt;path d="M0,0 L8,4 L0,8 z" style="fill:var(--border)"/&gt;&lt;/marker&gt;&lt;/defs&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;Stateful&lt;/th&gt;
&lt;th&gt;Stateless&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Request context&lt;/td&gt;
&lt;td&gt;Server retains prior interaction data across requests&lt;/td&gt;
&lt;td&gt;Each request carries all context needed to process it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Session storage&lt;/td&gt;
&lt;td&gt;Held in server memory or local session store&lt;/td&gt;
&lt;td&gt;None on server; state lives in client token or database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Routing requirement&lt;/td&gt;
&lt;td&gt;Requests must reach the same server (sticky sessions)&lt;/td&gt;
&lt;td&gt;Any server instance can handle any request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling model&lt;/td&gt;
&lt;td&gt;Vertical or sticky-session horizontal scaling only&lt;/td&gt;
&lt;td&gt;Trivial horizontal scaling, load balance freely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure recovery&lt;/td&gt;
&lt;td&gt;Server crash loses in-memory session unless replicated&lt;/td&gt;
&lt;td&gt;Server crash has no session impact, retry hits any node&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client design&lt;/td&gt;
&lt;td&gt;Client can be thin, server tracks progress&lt;/td&gt;
&lt;td&gt;Client or token must resend full context each call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical examples&lt;/td&gt;
&lt;td&gt;Database connections, WebSocket sessions, FTP&lt;/td&gt;
&lt;td&gt;REST APIs, HTTP with JWT, DNS lookups&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;Stateful servers keep &lt;strong class="kw"&gt;session memory&lt;/strong&gt;; stateless servers keep none between calls&lt;/li&gt;
&lt;li&gt;Stateless systems need &lt;strong class="kw"&gt;no sticky routing&lt;/strong&gt;, simplifying load balancers&lt;/li&gt;
&lt;li&gt;Stateful failover requires &lt;strong class="kw"&gt;session replication&lt;/strong&gt; to avoid data loss&lt;/li&gt;
&lt;li&gt;Stateless designs push state into the &lt;strong class="kw"&gt;client or token&lt;/strong&gt; instead of the server&lt;/li&gt;
&lt;li&gt;Horizontal scaling is &lt;strong class="kw"&gt;near-free&lt;/strong&gt; for stateless architectures&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;Stateful&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Monolith vs Microservices: One Deployable vs Many</title><link>https://comparison.metacog.co.kr/posts/2026-09-06-monolith-vs-microservices-one-deployable-vs-many/</link><pubDate>Sun, 06 Sep 2026 09:51:18 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-09-06-monolith-vs-microservices-one-deployable-vs-many/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;A &lt;strong class="kw"&gt;monolith&lt;/strong&gt; packages an application&amp;rsquo;s entire codebase and functionality into a single deployable unit running as one process, while &lt;strong class="kw"&gt;microservices&lt;/strong&gt; split that same functionality into independently deployable services that communicate over a network. The choice shapes how teams build, deploy, scale, and recover from failures, so it matters far beyond just code organization.&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="16" font-weight="bold" style="fill:var(--primary)"&gt;Monolith&lt;/text&gt;&lt;rect x="40" y="60" width="240" height="260" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="2"/&gt;&lt;line x1="160" y1="60" x2="160" y2="320" style="stroke:var(--border)" stroke-width="1" stroke-dasharray="4 3"/&gt;&lt;line x1="40" y1="190" x2="280" y2="190" style="stroke:var(--border)" stroke-width="1" stroke-dasharray="4 3"/&gt;&lt;text x="100" y="128" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;Auth&lt;/text&gt;&lt;text x="220" y="128" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;Orders&lt;/text&gt;&lt;text x="100" y="258" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;Inventory&lt;/text&gt;&lt;text x="220" y="258" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;Payments&lt;/text&gt;&lt;text x="160" y="338" text-anchor="middle" font-size="11" style="fill:var(--secondary)"&gt;Single process&lt;/text&gt;&lt;text x="160" y="352" text-anchor="middle" font-size="11" style="fill:var(--secondary)"&gt;Single deploy&lt;/text&gt;&lt;text x="480" y="36" text-anchor="middle" font-size="16" font-weight="bold" style="fill:var(--primary)"&gt;Microservices&lt;/text&gt;&lt;rect x="395" y="60" width="110" height="36" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="2"/&gt;&lt;text x="450" y="83" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;API Gateway&lt;/text&gt;&lt;line x1="420" y1="96" x2="375" y2="150" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;line x1="445" y1="96" x2="445" y2="150" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;line x1="455" y1="96" x2="515" y2="150" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;line x1="480" y1="96" x2="575" y2="150" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;rect x="345" y="150" width="60" height="50" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="2"/&gt;&lt;text x="375" y="180" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;Auth&lt;/text&gt;&lt;rect x="415" y="150" width="60" height="50" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="2"/&gt;&lt;text x="445" y="180" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;Orders&lt;/text&gt;&lt;rect x="485" y="150" width="60" height="50" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="2"/&gt;&lt;text x="515" y="180" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;Inventory&lt;/text&gt;&lt;rect x="545" y="150" width="60" height="50" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="2"/&gt;&lt;text x="575" y="180" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;Payments&lt;/text&gt;&lt;line x1="375" y1="215" x2="375" y2="245" style="stroke:var(--border)" stroke-width="1" stroke-dasharray="3 3"/&gt;&lt;line x1="445" y1="215" x2="445" y2="245" style="stroke:var(--border)" stroke-width="1" stroke-dasharray="3 3"/&gt;&lt;line x1="515" y1="215" x2="515" y2="245" style="stroke:var(--border)" stroke-width="1" stroke-dasharray="3 3"/&gt;&lt;line x1="575" y1="215" x2="575" y2="245" style="stroke:var(--border)" stroke-width="1" stroke-dasharray="3 3"/&gt;&lt;rect x="345" y="245" width="260" height="30" rx="3" style="fill:none;stroke:var(--border)" stroke-width="1" stroke-dasharray="3 3"/&gt;&lt;text x="475" y="264" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;separate databases&lt;/text&gt;&lt;text x="475" y="338" text-anchor="middle" font-size="11" style="fill:var(--secondary)"&gt;Independent services&lt;/text&gt;&lt;text x="475" y="352" text-anchor="middle" font-size="11" style="fill:var(--secondary)"&gt;Independent deploys&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;Monolith&lt;/th&gt;
&lt;th&gt;Microservices&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Codebase structure&lt;/td&gt;
&lt;td&gt;Single repository, one shared codebase for all functionality&lt;/td&gt;
&lt;td&gt;Multiple repositories, one per service with its own codebase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment unit&lt;/td&gt;
&lt;td&gt;Whole application built and shipped as one artifact&lt;/td&gt;
&lt;td&gt;Each service built, versioned, and shipped independently&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inter-module communication&lt;/td&gt;
&lt;td&gt;In-process function calls within the same runtime&lt;/td&gt;
&lt;td&gt;Network calls via HTTP, gRPC, or messaging between services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data storage&lt;/td&gt;
&lt;td&gt;Typically one shared database for the whole app&lt;/td&gt;
&lt;td&gt;Each service usually owns its own database or schema&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling&lt;/td&gt;
&lt;td&gt;Scale the entire application even if only one part is hot&lt;/td&gt;
&lt;td&gt;Scale only the specific services that need more capacity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault isolation&lt;/td&gt;
&lt;td&gt;A crash or memory leak in one module can take down the app&lt;/td&gt;
&lt;td&gt;A failing service degrades its own function without necessarily crashing others&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Technology stack&lt;/td&gt;
&lt;td&gt;One language and framework across the whole application&lt;/td&gt;
&lt;td&gt;Each service can use the language/framework best suited to it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team ownership and releases&lt;/td&gt;
&lt;td&gt;One team or a coordinated release train ships the whole app together&lt;/td&gt;
&lt;td&gt;Independent teams own and release their services on their own schedules&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;A monolith runs as a &lt;strong class="kw"&gt;single process&lt;/strong&gt;, while microservices are &lt;strong class="kw"&gt;distributed processes&lt;/strong&gt; talking over the network&lt;/li&gt;
&lt;li&gt;Microservices trade in-process call reliability for &lt;strong class="kw"&gt;network latency&lt;/strong&gt; and partial failure handling&lt;/li&gt;
&lt;li&gt;Independent deployability lets microservices teams ship on &lt;strong class="kw"&gt;separate release cadences&lt;/strong&gt;, which a monolith can&amp;rsquo;t offer&lt;/li&gt;
&lt;li&gt;Splitting services adds real &lt;strong class="kw"&gt;operational overhead&lt;/strong&gt; — service discovery, monitoring, and distributed tracing&lt;/li&gt;
&lt;li&gt;Data ownership per service enables &lt;strong class="kw"&gt;polyglot persistence&lt;/strong&gt; but sacrifices easy cross-entity transactions&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;Monolith&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Vertical vs Horizontal Scaling: Bigger Box vs More Boxes</title><link>https://comparison.metacog.co.kr/posts/2026-09-06-vertical-vs-horizontal-scaling-bigger-box-vs-more-boxes/</link><pubDate>Sun, 06 Sep 2026 09:36:59 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-09-06-vertical-vs-horizontal-scaling-bigger-box-vs-more-boxes/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;Vertical scaling grows capacity by adding more &lt;strong class="kw"&gt;CPU/RAM&lt;/strong&gt; to a single machine, while horizontal scaling grows capacity by adding &lt;strong class="kw"&gt;more nodes&lt;/strong&gt; behind a load balancer. The choice shapes your application&amp;rsquo;s architecture, failure model, and cost curve as it grows.&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="40" text-anchor="middle" font-size="18" style="fill:var(--primary)"&gt;Vertical&lt;/text&gt;&lt;text x="480" y="40" text-anchor="middle" font-size="18" style="fill:var(--primary)"&gt;Horizontal&lt;/text&gt;&lt;line x1="320" y1="20" x2="320" y2="340" style="stroke:var(--border)" stroke-width="1" stroke-dasharray="4 4"/&gt;&lt;rect x="120" y="230" width="80" height="60" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="160" y="265" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;Server&lt;/text&gt;&lt;rect x="110" y="150" width="100" height="70" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="2"/&gt;&lt;text x="160" y="180" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Server&lt;/text&gt;&lt;text x="160" y="197" text-anchor="middle" font-size="11" style="fill:var(--secondary)"&gt;+CPU +RAM&lt;/text&gt;&lt;rect x="95" y="60" width="130" height="80" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="2.5"/&gt;&lt;text x="160" y="95" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;Server&lt;/text&gt;&lt;text x="160" y="114" text-anchor="middle" font-size="11" style="fill:var(--secondary)"&gt;++CPU ++RAM&lt;/text&gt;&lt;path d="M160 145 L160 232" style="stroke:var(--compare-a)" stroke-width="1.5" stroke-dasharray="3 3" fill="none"/&gt;&lt;path d="M155 60 L155 -0" style="stroke:var(--border)" stroke-width="0"/&gt;&lt;line x1="160" y1="40" x2="160" y2="58" style="stroke:var(--compare-a)" stroke-width="1.5" stroke-dasharray="3 3"/&gt;&lt;rect x="430" y="60" width="100" height="36" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="2"/&gt;&lt;text x="480" y="83" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Load Balancer&lt;/text&gt;&lt;line x1="480" y1="96" x2="400" y2="150" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;line x1="480" y1="96" x2="480" y2="150" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;line x1="480" y1="96" x2="560" y2="150" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;rect x="365" y="150" width="70" height="50" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="400" y="180" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;Node&lt;/text&gt;&lt;rect x="445" y="150" width="70" height="50" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="480" y="180" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;Node&lt;/text&gt;&lt;rect x="525" y="150" width="70" height="50" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="560" y="180" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;Node&lt;/text&gt;&lt;rect x="485" y="230" width="70" height="50" rx="4" style="fill:none;stroke:var(--border)" stroke-width="1.5" stroke-dasharray="4 4"/&gt;&lt;text x="520" y="260" text-anchor="middle" font-size="11" style="fill:var(--secondary)"&gt;+Node&lt;/text&gt;&lt;line x1="480" y1="200" x2="520" y2="228" style="stroke:var(--border)" stroke-width="1" stroke-dasharray="3 3"/&gt;&lt;text x="160" y="330" text-anchor="middle" font-size="12" style="fill:var(--secondary)"&gt;Single node, growing&lt;/text&gt;&lt;text x="480" y="330" text-anchor="middle" font-size="12" style="fill:var(--secondary)"&gt;Many nodes, distributed&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;Vertical Scaling&lt;/th&gt;
&lt;th&gt;Horizontal Scaling&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scaling mechanism&lt;/td&gt;
&lt;td&gt;Add CPU, RAM, or faster disks to one machine&lt;/td&gt;
&lt;td&gt;Add more machines/nodes to a shared pool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Architecture requirement&lt;/td&gt;
&lt;td&gt;Works with any app, no code changes needed&lt;/td&gt;
&lt;td&gt;Requires stateless design, load balancing, and shared state (session store, distributed cache)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Upper limit&lt;/td&gt;
&lt;td&gt;Capped by the largest hardware SKU available&lt;/td&gt;
&lt;td&gt;Effectively unbounded, limited only by orchestration and cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Downtime during scale-up&lt;/td&gt;
&lt;td&gt;Often requires reboot or migration to bigger instance&lt;/td&gt;
&lt;td&gt;New nodes join the pool live, no downtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault tolerance&lt;/td&gt;
&lt;td&gt;Single point of failure — one box, one crash&lt;/td&gt;
&lt;td&gt;Node failures are absorbed by the remaining pool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost curve&lt;/td&gt;
&lt;td&gt;Price rises non-linearly at the high end (diminishing returns)&lt;/td&gt;
&lt;td&gt;Roughly linear cost per added unit of capacity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operational complexity&lt;/td&gt;
&lt;td&gt;Low — one server to patch, monitor, and secure&lt;/td&gt;
&lt;td&gt;Higher — needs service discovery, distributed monitoring, data consistency handling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical use case&lt;/td&gt;
&lt;td&gt;Monolithic apps, relational databases, legacy systems&lt;/td&gt;
&lt;td&gt;Stateless web services, microservices, cloud-native workloads&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;Vertical scaling upgrades a &lt;strong class="kw"&gt;single machine&lt;/strong&gt;; horizontal scaling adds &lt;strong class="kw"&gt;more machines&lt;/strong&gt; to a pool&lt;/li&gt;
&lt;li&gt;Horizontal scaling demands &lt;strong class="kw"&gt;stateless services&lt;/strong&gt;, while vertical scaling needs no architectural change&lt;/li&gt;
&lt;li&gt;Vertical scaling has a hard &lt;strong class="kw"&gt;hardware ceiling&lt;/strong&gt;; horizontal scaling scales near-linearly&lt;/li&gt;
&lt;li&gt;A single oversized server is a &lt;strong class="kw"&gt;single point of failure&lt;/strong&gt;, unlike a distributed node pool&lt;/li&gt;
&lt;li&gt;Horizontal scaling trades simplicity for &lt;strong class="kw"&gt;operational complexity&lt;/strong&gt; in orchestration and consistency&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;Vertical Scaling&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Push vs Pull Architecture: Who Initiates the Data Transfer</title><link>https://comparison.metacog.co.kr/posts/2026-08-04-push-vs-pull-architecture-who-initiates-the-data-transfer/</link><pubDate>Tue, 04 Aug 2026 05:24:00 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-08-04-push-vs-pull-architecture-who-initiates-the-data-transfer/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;Push and pull architecture describe who initiates data transfer between a producer and a consumer: in a &lt;strong class="kw"&gt;push model&lt;/strong&gt; the producer sends data the moment it&amp;rsquo;s ready, while in a &lt;strong class="kw"&gt;pull model&lt;/strong&gt; the consumer requests data on its own schedule. The choice shapes latency, coupling, and how systems handle scale or downtime.&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;defs&gt;&lt;marker id="arrowA" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"&gt;&lt;path d="M0,0 L10,5 L0,10 z" style="fill:var(--compare-a)"/&gt;&lt;/marker&gt;&lt;marker id="arrowB" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"&gt;&lt;path d="M0,0 L10,5 L0,10 z" style="fill:var(--compare-b)"/&gt;&lt;/marker&gt;&lt;/defs&gt;&lt;line x1="320" y1="20" x2="320" y2="340" style="stroke:var(--border)" stroke-width="1.5" stroke-dasharray="6,6"/&gt;&lt;text x="160" y="32" text-anchor="middle" style="fill:var(--primary)" font-size="20" font-weight="bold"&gt;PUSH&lt;/text&gt;&lt;text x="160" y="50" text-anchor="middle" style="fill:var(--secondary)" font-size="11"&gt;(producer-initiated)&lt;/text&gt;&lt;rect x="80" y="70" width="160" height="60" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="160" y="105" text-anchor="middle" style="fill:var(--content)" font-size="14"&gt;Producer&lt;/text&gt;&lt;rect x="80" y="250" width="160" height="60" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="160" y="285" text-anchor="middle" style="fill:var(--content)" font-size="14"&gt;Consumer&lt;/text&gt;&lt;line x1="160" y1="130" x2="160" y2="245" style="stroke:var(--compare-a)" stroke-width="2" marker-end="url(#arrowA)"/&gt;&lt;text x="175" y="190" style="fill:var(--secondary)" font-size="12"&gt;data pushed&lt;/text&gt;&lt;text x="480" y="32" text-anchor="middle" style="fill:var(--primary)" font-size="20" font-weight="bold"&gt;PULL&lt;/text&gt;&lt;text x="480" y="50" text-anchor="middle" style="fill:var(--secondary)" font-size="11"&gt;(consumer-initiated)&lt;/text&gt;&lt;rect x="400" y="70" width="160" height="60" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="480" y="105" text-anchor="middle" style="fill:var(--content)" font-size="14"&gt;Consumer&lt;/text&gt;&lt;rect x="400" y="250" width="160" height="60" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="480" y="285" text-anchor="middle" style="fill:var(--content)" font-size="14"&gt;Producer&lt;/text&gt;&lt;line x1="465" y1="130" x2="465" y2="245" style="stroke:var(--compare-b)" stroke-width="2" marker-end="url(#arrowB)"/&gt;&lt;text x="450" y="180" text-anchor="end" style="fill:var(--secondary)" font-size="12"&gt;request&lt;/text&gt;&lt;line x1="495" y1="245" x2="495" y2="130" style="stroke:var(--compare-b)" stroke-width="2" marker-end="url(#arrowB)"/&gt;&lt;text x="510" y="200" text-anchor="start" style="fill:var(--secondary)" font-size="12"&gt;response&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;Push&lt;/th&gt;
&lt;th&gt;Pull&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Initiator of transfer&lt;/td&gt;
&lt;td&gt;Producer sends data as soon as it&amp;rsquo;s available&lt;/td&gt;
&lt;td&gt;Consumer requests data on its own schedule&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data freshness&lt;/td&gt;
&lt;td&gt;Near-real-time; consumer receives updates immediately&lt;/td&gt;
&lt;td&gt;Bounded by poll interval; can lag between requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Consumer control&lt;/td&gt;
&lt;td&gt;Producer dictates pace; consumer must keep up&lt;/td&gt;
&lt;td&gt;Consumer sets pace and can throttle or batch requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coupling&lt;/td&gt;
&lt;td&gt;Producer must track and address its consumers&lt;/td&gt;
&lt;td&gt;Producer stays unaware of who is asking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scalability with consumers&lt;/td&gt;
&lt;td&gt;Fan-out cost grows with each new subscriber&lt;/td&gt;
&lt;td&gt;Each consumer&amp;rsquo;s load stays independent of the others&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offline or slow consumers&lt;/td&gt;
&lt;td&gt;Missed pushes risk data loss without a buffer or queue&lt;/td&gt;
&lt;td&gt;Consumer simply polls again later, no data lost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Idle resource usage&lt;/td&gt;
&lt;td&gt;Zero overhead when there is nothing new to send&lt;/td&gt;
&lt;td&gt;Wastes cycles and requests polling when nothing changed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical examples&lt;/td&gt;
&lt;td&gt;Webhooks, WebSockets, pub/sub message brokers&lt;/td&gt;
&lt;td&gt;REST polling, cron jobs, RSS feed readers&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;Push delivers data the instant it&amp;rsquo;s produced, minimizing &lt;strong class="kw"&gt;latency&lt;/strong&gt; at the cost of straining consumer readiness.&lt;/li&gt;
&lt;li&gt;Pull lets the consumer control &lt;strong class="kw"&gt;pacing&lt;/strong&gt;, avoiding overload but risking staleness between requests.&lt;/li&gt;
&lt;li&gt;Push requires the producer to maintain a &lt;strong class="kw"&gt;subscriber list&lt;/strong&gt;, increasing coupling and fan-out complexity.&lt;/li&gt;
&lt;li&gt;Pull wastes cycles on &lt;strong class="kw"&gt;empty polls&lt;/strong&gt; when nothing has changed since the last request.&lt;/li&gt;
&lt;li&gt;Push needs a buffering or queueing layer to survive consumer &lt;strong class="kw"&gt;downtime&lt;/strong&gt; without losing data.&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;Push&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Stateless vs Stateful Architecture: Where Request Context Lives</title><link>https://comparison.metacog.co.kr/posts/2026-08-04-stateless-vs-stateful-architecture-where-request-context-liv/</link><pubDate>Tue, 04 Aug 2026 05:18:18 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-08-04-stateless-vs-stateful-architecture-where-request-context-liv/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;This comparison covers how servers handle client context across requests: a &lt;strong class="kw"&gt;stateless&lt;/strong&gt; server treats every request as self-contained with no memory of what came before, while a &lt;strong class="kw"&gt;stateful&lt;/strong&gt; server keeps track of session data tied to a specific client across multiple requests. The choice shapes how easily a system scales, recovers from failure, and routes traffic under load.&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" font-family="sans-serif"&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" font-size="16" font-weight="bold" style="fill:var(--primary)"&gt;Stateless&lt;/text&gt;&lt;text x="480" y="35" text-anchor="middle" font-size="16" font-weight="bold" style="fill:var(--primary)"&gt;Stateful&lt;/text&gt;&lt;rect x="20" y="150" width="70" height="40" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="55" y="174" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Client&lt;/text&gt;&lt;rect x="130" y="150" width="70" height="40" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="165" y="168" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;Load&lt;/text&gt;&lt;text x="165" y="182" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;Balancer&lt;/text&gt;&lt;rect x="240" y="95" width="65" height="35" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="272" y="117" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;Server A&lt;/text&gt;&lt;rect x="240" y="152" width="65" height="35" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="272" y="174" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;Server B&lt;/text&gt;&lt;rect x="240" y="209" width="65" height="35" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="272" y="231" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;Server C&lt;/text&gt;&lt;line x1="90" y1="170" x2="128" y2="170" style="stroke:var(--compare-a)" stroke-width="1.5" marker-end="url(#arrowA)"/&gt;&lt;line x1="200" y1="165" x2="238" y2="130" style="stroke:var(--compare-a)" stroke-width="1.5" marker-end="url(#arrowA)"/&gt;&lt;line x1="200" y1="170" x2="238" y2="170" style="stroke:var(--compare-a)" stroke-width="1.5" marker-end="url(#arrowA)"/&gt;&lt;line x1="200" y1="175" x2="238" y2="212" style="stroke:var(--compare-a)" stroke-width="1.5" marker-end="url(#arrowA)"/&gt;&lt;text x="160" y="280" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;any server can handle any request&lt;/text&gt;&lt;text x="160" y="296" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;no session stored server-side&lt;/text&gt;&lt;rect x="355" y="150" width="70" height="40" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="390" y="174" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Client&lt;/text&gt;&lt;rect x="470" y="140" width="90" height="60" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="515" y="165" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;Server&lt;/text&gt;&lt;text x="515" y="180" text-anchor="middle" font-size="10" style="fill:var(--content)"&gt;+ Session&lt;/text&gt;&lt;line x1="425" y1="170" x2="468" y2="170" style="stroke:var(--compare-b)" stroke-width="1.5" marker-end="url(#arrowB)"/&gt;&lt;text x="447" y="160" text-anchor="middle" font-size="9" style="fill:var(--secondary)"&gt;sticky&lt;/text&gt;&lt;rect x="580" y="95" width="45" height="30" rx="4" stroke-dasharray="3 3" style="fill:none;stroke:var(--border)" stroke-width="1.5"/&gt;&lt;text x="602" y="114" text-anchor="middle" font-size="9" style="fill:var(--secondary)"&gt;Server&lt;/text&gt;&lt;rect x="580" y="215" width="45" height="30" rx="4" stroke-dasharray="3 3" style="fill:none;stroke:var(--border)" stroke-width="1.5"/&gt;&lt;text x="602" y="234" text-anchor="middle" font-size="9" style="fill:var(--secondary)"&gt;Server&lt;/text&gt;&lt;text x="480" y="280" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;must always return to&lt;/text&gt;&lt;text x="480" y="296" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;same server holding session&lt;/text&gt;&lt;defs&gt;&lt;marker id="arrowA" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto"&gt;&lt;path d="M0,0 L6,3 L0,6 Z" style="fill:var(--compare-a)"/&gt;&lt;/marker&gt;&lt;marker id="arrowB" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto"&gt;&lt;path d="M0,0 L6,3 L0,6 Z" style="fill:var(--compare-b)"/&gt;&lt;/marker&gt;&lt;/defs&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;Stateless&lt;/th&gt;
&lt;th&gt;Stateful&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Request self-sufficiency&lt;/td&gt;
&lt;td&gt;Each request carries all data needed to process it, independent of prior requests&lt;/td&gt;
&lt;td&gt;Each request depends on context accumulated from prior requests in the same session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State storage location&lt;/td&gt;
&lt;td&gt;Held externally (client token, database, cache) or not persisted at all&lt;/td&gt;
&lt;td&gt;Held in server memory or local storage tied to a specific server instance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Load balancing&lt;/td&gt;
&lt;td&gt;Any available server can handle any request; simple round-robin routing&lt;/td&gt;
&lt;td&gt;Requests must be routed to the specific server holding the session (sticky sessions)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Horizontal scaling&lt;/td&gt;
&lt;td&gt;Add or remove server instances freely with no coordination needed&lt;/td&gt;
&lt;td&gt;Requires state replication or migration before instances can be added or removed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure recovery&lt;/td&gt;
&lt;td&gt;A crashed server loses nothing; the next request is simply retried elsewhere&lt;/td&gt;
&lt;td&gt;A crashed server can drop the active session unless state was replicated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resource footprint per server&lt;/td&gt;
&lt;td&gt;Lower memory overhead since no per-client data is retained between requests&lt;/td&gt;
&lt;td&gt;Higher memory/storage overhead from tracking active sessions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical examples&lt;/td&gt;
&lt;td&gt;REST APIs, DNS lookups, serverless functions, CDN edge nodes&lt;/td&gt;
&lt;td&gt;Database connections, WebSocket sessions, FTP, multiplayer game servers&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;Stateless servers require no &lt;strong class="kw"&gt;session affinity&lt;/strong&gt;; stateful servers need sticky routing to reach the same instance.&lt;/li&gt;
&lt;li&gt;State in stateless systems lives in &lt;strong class="kw"&gt;external stores&lt;/strong&gt; or the client; state in stateful systems lives in server memory.&lt;/li&gt;
&lt;li&gt;Stateless architectures scale &lt;strong class="kw"&gt;horizontally&lt;/strong&gt; with ease, while stateful ones need state replication to scale out.&lt;/li&gt;
&lt;li&gt;A crashed stateless server loses nothing, but a crashed stateful server can drop an active &lt;strong class="kw"&gt;session&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;Stateless&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Monolith vs Microservices: Architecture Comparison</title><link>https://comparison.metacog.co.kr/posts/2026-08-04-monolith-vs-microservices-architecture-comparison/</link><pubDate>Tue, 04 Aug 2026 05:06:36 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-08-04-monolith-vs-microservices-architecture-comparison/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;Monolithic architecture packages an entire application as a single &lt;strong class="kw"&gt;deployable unit&lt;/strong&gt;, while microservices architecture splits it into independently deployable &lt;strong class="kw"&gt;distributed services&lt;/strong&gt;. The choice shapes everything from how teams organize their work to how failures propagate and how the system scales under load.&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="600" style="fill:var(--primary)"&gt;Monolith&lt;/text&gt;
&lt;text x="480" y="30" text-anchor="middle" font-size="18" font-weight="600" style="fill:var(--primary)"&gt;Microservices&lt;/text&gt;
&lt;rect x="60" y="55" width="200" height="205" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;
&lt;line x1="60" y1="123" x2="260" y2="123" style="stroke:var(--border)" stroke-width="1" stroke-dasharray="4,3"/&gt;
&lt;line x1="60" y1="191" x2="260" y2="191" style="stroke:var(--border)" stroke-width="1" stroke-dasharray="4,3"/&gt;
&lt;text x="160" y="93" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;UI Layer&lt;/text&gt;
&lt;text x="160" y="161" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Business Logic&lt;/text&gt;
&lt;text x="160" y="229" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Data Access&lt;/text&gt;
&lt;line x1="160" y1="260" x2="160" y2="280" style="stroke:var(--compare-a)" stroke-width="1.5"/&gt;
&lt;rect x="110" y="280" width="100" height="36" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;
&lt;text x="160" y="302" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;Shared DB&lt;/text&gt;
&lt;text x="160" y="340" text-anchor="middle" font-size="11" style="fill:var(--secondary)"&gt;single deployable unit&lt;/text&gt;
&lt;rect x="430" y="55" width="100" height="30" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;
&lt;text x="480" y="75" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;API Gateway&lt;/text&gt;
&lt;line x1="480" y1="85" x2="420" y2="128" style="stroke:var(--compare-b)" stroke-width="1.5" stroke-dasharray="4,3"/&gt;
&lt;line x1="480" y1="85" x2="520" y2="128" style="stroke:var(--compare-b)" stroke-width="1.5" stroke-dasharray="4,3"/&gt;
&lt;rect x="380" y="128" width="80" height="50" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;
&lt;text x="420" y="157" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Orders&lt;/text&gt;
&lt;rect x="480" y="128" width="80" height="50" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;
&lt;text x="520" y="157" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Users&lt;/text&gt;
&lt;line x1="420" y1="178" x2="420" y2="212" style="stroke:var(--compare-b)" stroke-width="1.5" stroke-dasharray="4,3"/&gt;
&lt;line x1="520" y1="178" x2="520" y2="212" style="stroke:var(--compare-b)" stroke-width="1.5" stroke-dasharray="4,3"/&gt;
&lt;line x1="460" y1="153" x2="480" y2="153" style="stroke:var(--compare-b)" stroke-width="1.5" stroke-dasharray="4,3"/&gt;
&lt;rect x="380" y="212" width="80" height="50" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;
&lt;text x="420" y="241" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Payments&lt;/text&gt;
&lt;rect x="480" y="212" width="80" height="50" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;
&lt;text x="520" y="241" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Inventory&lt;/text&gt;
&lt;rect x="380" y="180" width="32" height="14" rx="2" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1"/&gt;
&lt;text x="396" y="190" text-anchor="middle" font-size="8" style="fill:var(--secondary)"&gt;db&lt;/text&gt;
&lt;rect x="528" y="180" width="32" height="14" rx="2" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1"/&gt;
&lt;text x="544" y="190" text-anchor="middle" font-size="8" style="fill:var(--secondary)"&gt;db&lt;/text&gt;
&lt;rect x="380" y="264" width="32" height="14" rx="2" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1"/&gt;
&lt;text x="396" y="274" text-anchor="middle" font-size="8" style="fill:var(--secondary)"&gt;db&lt;/text&gt;
&lt;rect x="528" y="264" width="32" height="14" rx="2" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1"/&gt;
&lt;text x="544" y="274" text-anchor="middle" font-size="8" style="fill:var(--secondary)"&gt;db&lt;/text&gt;
&lt;text x="480" y="340" text-anchor="middle" font-size="11" style="fill:var(--secondary)"&gt;independent, own datastores&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;Monolithic Architecture&lt;/th&gt;
&lt;th&gt;Microservices Architecture&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Deployment unit&lt;/td&gt;
&lt;td&gt;Single deployable artifact containing all modules&lt;/td&gt;
&lt;td&gt;Multiple independently deployable services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inter-component communication&lt;/td&gt;
&lt;td&gt;In-process function calls&lt;/td&gt;
&lt;td&gt;Network calls (REST, gRPC, or messaging)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data storage&lt;/td&gt;
&lt;td&gt;Typically one shared database&lt;/td&gt;
&lt;td&gt;Each service owns and manages its own database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling approach&lt;/td&gt;
&lt;td&gt;Scale the entire application as one unit&lt;/td&gt;
&lt;td&gt;Scale individual services independently based on load&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault isolation&lt;/td&gt;
&lt;td&gt;A bug or crash can bring down the whole application&lt;/td&gt;
&lt;td&gt;Failures can be isolated to a single service when designed well&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Release and deployment process&lt;/td&gt;
&lt;td&gt;Single build/deploy pipeline with coordinated releases&lt;/td&gt;
&lt;td&gt;Independent CI/CD pipeline per service, deployed on its own schedule&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Technology stack flexibility&lt;/td&gt;
&lt;td&gt;One language and framework for the entire app&lt;/td&gt;
&lt;td&gt;Polyglot — each service can pick its own stack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operational overhead&lt;/td&gt;
&lt;td&gt;Low — one application to host and monitor&lt;/td&gt;
&lt;td&gt;High — requires service discovery, orchestration, and distributed tracing&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;Monolith code runs as a single process; microservices communicate as &lt;strong class="kw"&gt;independent processes&lt;/strong&gt; over the network.&lt;/li&gt;
&lt;li&gt;A monolith centers on one &lt;strong class="kw"&gt;shared database&lt;/strong&gt;, while microservices decentralize data ownership per service.&lt;/li&gt;
&lt;li&gt;Microservices allow &lt;strong class="kw"&gt;granular scaling&lt;/strong&gt; of just the components under load, unlike a monolith that scales as a whole.&lt;/li&gt;
&lt;li&gt;Splitting into services buys better fault containment but introduces real &lt;strong class="kw"&gt;operational complexity&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Well-designed microservices offer stronger &lt;strong class="kw"&gt;fault isolation&lt;/strong&gt; than a monolith, where one bug can crash everything.&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;Monolithic Architecture&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Cache-Aside vs Write-Through vs Write-Behind: Caching Strategies Compared</title><link>https://comparison.metacog.co.kr/posts/2026-08-02-cache-aside-vs-write-through-vs-write-behind-caching-strateg/</link><pubDate>Sun, 02 Aug 2026 23:52:10 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-08-02-cache-aside-vs-write-through-vs-write-behind-caching-strateg/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;Caching strategies differ mainly in who updates the cache and when. &lt;strong class="kw"&gt;Cache-Aside&lt;/strong&gt; leaves the application responsible for loading data into the cache on a miss and writing straight to the database, while &lt;strong class="kw"&gt;write-through/write-behind&lt;/strong&gt; caches push writes through the cache itself — synchronously for durability or asynchronously for speed. The choice shapes consistency guarantees, crash-safety, and how much write latency the app absorbs.&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;defs&gt;&lt;marker id="arrowA" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"&gt;&lt;path d="M0,0 L10,5 L0,10 z" style="fill:var(--compare-a)"/&gt;&lt;/marker&gt;&lt;marker id="arrowB" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"&gt;&lt;path d="M0,0 L10,5 L0,10 z" style="fill:var(--compare-b)"/&gt;&lt;/marker&gt;&lt;/defs&gt;&lt;text x="16" y="28" font-size="15" font-weight="bold" style="fill:var(--primary)"&gt;Cache-Aside&lt;/text&gt;&lt;rect x="20" y="50" width="70" height="36" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="55" y="72" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;App&lt;/text&gt;&lt;rect x="285" y="50" width="80" height="36" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="325" y="72" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Cache&lt;/text&gt;&lt;rect x="545" y="50" width="70" height="36" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="580" y="72" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;DB&lt;/text&gt;&lt;line x1="95" y1="68" x2="280" y2="68" style="stroke:var(--compare-a)" stroke-width="1.5" marker-end="url(#arrowA)"/&gt;&lt;text x="185" y="60" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;read&lt;/text&gt;&lt;line x1="370" y1="68" x2="540" y2="68" stroke-dasharray="5,4" style="stroke:var(--compare-a)" stroke-width="1.5" marker-end="url(#arrowA)" marker-start="url(#arrowA)"/&gt;&lt;text x="455" y="95" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;on miss: fetch + populate&lt;/text&gt;&lt;line x1="10" y1="130" x2="630" y2="130" stroke-dasharray="4,4" style="stroke:var(--border)" stroke-width="1"/&gt;&lt;text x="16" y="153" font-size="15" font-weight="bold" style="fill:var(--primary)"&gt;Write-Through&lt;/text&gt;&lt;rect x="20" y="168" width="70" height="36" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="55" y="190" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;App&lt;/text&gt;&lt;rect x="285" y="168" width="80" height="36" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="325" y="190" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Cache&lt;/text&gt;&lt;rect x="545" y="168" width="70" height="36" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="580" y="190" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;DB&lt;/text&gt;&lt;line x1="95" y1="186" x2="280" y2="186" style="stroke:var(--compare-b)" stroke-width="1.5" marker-end="url(#arrowB)"/&gt;&lt;text x="185" y="178" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;write&lt;/text&gt;&lt;line x1="370" y1="186" x2="540" y2="186" style="stroke:var(--compare-b)" stroke-width="1.5" marker-end="url(#arrowB)"/&gt;&lt;text x="455" y="178" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;sync write&lt;/text&gt;&lt;line x1="10" y1="248" x2="630" y2="248" stroke-dasharray="4,4" style="stroke:var(--border)" stroke-width="1"/&gt;&lt;text x="16" y="271" font-size="15" font-weight="bold" style="fill:var(--primary)"&gt;Write-Behind&lt;/text&gt;&lt;rect x="20" y="286" width="70" height="36" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="55" y="308" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;App&lt;/text&gt;&lt;rect x="285" y="286" width="80" height="36" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="325" y="308" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Cache&lt;/text&gt;&lt;rect x="545" y="286" width="70" height="36" rx="4" stroke-dasharray="4,3" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="580" y="308" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;DB&lt;/text&gt;&lt;line x1="95" y1="304" x2="280" y2="304" style="stroke:var(--compare-b)" stroke-width="1.5" marker-end="url(#arrowB)"/&gt;&lt;text x="185" y="296" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;write (fast ack)&lt;/text&gt;&lt;line x1="370" y1="304" x2="540" y2="304" stroke-dasharray="5,4" style="stroke:var(--compare-b)" stroke-width="1.5" marker-end="url(#arrowB)"/&gt;&lt;text x="455" y="296" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;async flush (delayed)&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;Cache-Aside&lt;/th&gt;
&lt;th&gt;Write-Through / Write-Behind&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read path&lt;/td&gt;
&lt;td&gt;App checks the cache first; on a miss, it reads from the DB itself and populates the cache&lt;/td&gt;
&lt;td&gt;Cache is always kept current on writes, so reads simply hit the cache without app-managed fallback logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write path&lt;/td&gt;
&lt;td&gt;App writes directly to the DB; the cache entry is invalidated or left stale until the next read&lt;/td&gt;
&lt;td&gt;App writes only to the cache; the cache layer propagates the write to the DB itself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write latency perceived by app&lt;/td&gt;
&lt;td&gt;Only DB write latency, since the cache isn&amp;rsquo;t touched on write&lt;/td&gt;
&lt;td&gt;Write-Through waits for both cache and DB commit; Write-Behind returns after the cache write only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Consistency between cache and DB&lt;/td&gt;
&lt;td&gt;Brief staleness window possible between invalidation and the next read&lt;/td&gt;
&lt;td&gt;Write-Through stays consistent immediately; Write-Behind lags until the queued flush completes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data loss risk on crash&lt;/td&gt;
&lt;td&gt;None — the DB is always the write target, so a cache crash loses nothing&lt;/td&gt;
&lt;td&gt;Write-Through has no loss; Write-Behind can lose unflushed writes if the cache crashes before flush&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Implementation complexity&lt;/td&gt;
&lt;td&gt;App owns miss handling and invalidation logic explicitly&lt;/td&gt;
&lt;td&gt;Cache layer owns persistence logic; Write-Behind adds a queue/flush scheduler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical use case&lt;/td&gt;
&lt;td&gt;Read-heavy workloads with unpredictable key access, e.g. Redis in front of a relational DB&lt;/td&gt;
&lt;td&gt;Write-Through suits systems needing instant durability; Write-Behind suits high write-throughput systems tolerant of brief loss, e.g. metrics buffers&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;Cache-Aside puts the application in charge of both fetch-on-miss and invalidation, while write-through/write-behind push that responsibility into the &lt;strong class="kw"&gt;cache layer&lt;/strong&gt; itself&lt;/li&gt;
&lt;li&gt;Only Write-Behind delivers real &lt;strong class="kw"&gt;write latency&lt;/strong&gt; savings by acknowledging before the DB commit completes&lt;/li&gt;
&lt;li&gt;Write-Through guarantees immediate durability at write time, while Write-Behind trades some of that &lt;strong class="kw"&gt;durability&lt;/strong&gt; for throughput&lt;/li&gt;
&lt;li&gt;Cache-Aside is the only strategy where a cache outage never risks &lt;strong class="kw"&gt;data loss&lt;/strong&gt;, since writes never pass through it&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;Cache-Aside&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Horizontal Scaling vs Vertical Scaling: Growing Out vs Growing Up</title><link>https://comparison.metacog.co.kr/posts/2026-08-02-horizontal-scaling-vs-vertical-scaling-growing-out-vs-growin/</link><pubDate>Sun, 02 Aug 2026 23:47:18 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-08-02-horizontal-scaling-vs-vertical-scaling-growing-out-vs-growin/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;Horizontal and vertical scaling are the two fundamental strategies for adding capacity to a system: one adds &lt;strong class="kw"&gt;more nodes&lt;/strong&gt; working in parallel, the other adds &lt;strong class="kw"&gt;more resources&lt;/strong&gt; to a single existing node. The choice shapes cost, downtime, fault tolerance, and how much your application architecture has to change to support it.&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="36" text-anchor="middle" style="fill:var(--primary)" font-size="18" font-weight="600"&gt;Horizontal Scaling&lt;/text&gt;&lt;text x="480" y="36" text-anchor="middle" style="fill:var(--primary)" font-size="18" font-weight="600"&gt;Vertical Scaling&lt;/text&gt;&lt;rect x="125" y="58" width="70" height="32" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="160" y="79" text-anchor="middle" style="fill:var(--content)" font-size="12"&gt;Load Balancer&lt;/text&gt;&lt;line x1="160" y1="90" x2="70" y2="148" style="stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;line x1="160" y1="90" x2="145" y2="148" style="stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;line x1="160" y1="90" x2="220" y2="148" style="stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;line x1="160" y1="90" x2="295" y2="148" style="stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;rect x="40" y="150" width="60" height="60" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="70" y="185" text-anchor="middle" style="fill:var(--content)" font-size="13"&gt;S1&lt;/text&gt;&lt;rect x="115" y="150" width="60" height="60" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="145" y="185" text-anchor="middle" style="fill:var(--content)" font-size="13"&gt;S2&lt;/text&gt;&lt;rect x="190" y="150" width="60" height="60" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="220" y="185" text-anchor="middle" style="fill:var(--content)" font-size="13"&gt;S3&lt;/text&gt;&lt;rect x="265" y="150" width="60" height="60" rx="4" style="fill:none;stroke:var(--compare-a)" stroke-width="1.5" stroke-dasharray="5,4"/&gt;&lt;text x="295" y="185" text-anchor="middle" style="fill:var(--compare-a)" font-size="18"&gt;+&lt;/text&gt;&lt;text x="160" y="245" text-anchor="middle" style="fill:var(--secondary)" font-size="12"&gt;scale out: add identical nodes&lt;/text&gt;&lt;text x="160" y="262" text-anchor="middle" style="fill:var(--secondary)" font-size="12"&gt;no downtime, redundant&lt;/text&gt;&lt;rect x="440" y="235" width="70" height="70" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="475" y="265" text-anchor="middle" style="fill:var(--content)" font-size="12"&gt;Server&lt;/text&gt;&lt;text x="475" y="282" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;2 CPU / 4GB&lt;/text&gt;&lt;line x1="475" y1="230" x2="475" y2="200" style="stroke:var(--compare-b)" stroke-width="2"/&gt;&lt;polygon points="475,190 469,202 481,202" style="fill:var(--compare-b)"/&gt;&lt;rect x="390" y="90" width="170" height="105" rx="4" style="fill:none;stroke:var(--compare-b)" stroke-width="1.5" stroke-dasharray="5,4"/&gt;&lt;text x="475" y="135" text-anchor="middle" style="fill:var(--content)" font-size="13"&gt;Same Server&lt;/text&gt;&lt;text x="475" y="155" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;16 CPU / 64GB&lt;/text&gt;&lt;text x="475" y="172" text-anchor="middle" style="fill:var(--secondary)" font-size="11"&gt;upgraded&lt;/text&gt;&lt;text x="475" y="325" text-anchor="middle" style="fill:var(--secondary)" font-size="12"&gt;scale up: add CPU/RAM/disk&lt;/text&gt;&lt;text x="475" y="342" text-anchor="middle" style="fill:var(--secondary)" font-size="12"&gt;often needs downtime&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;Horizontal Scaling&lt;/th&gt;
&lt;th&gt;Vertical Scaling&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mechanism&lt;/td&gt;
&lt;td&gt;Add more machines/nodes to the pool&lt;/td&gt;
&lt;td&gt;Add more CPU, RAM, or disk to an existing machine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Implementation&lt;/td&gt;
&lt;td&gt;Requires a load balancer and clustering to distribute work&lt;/td&gt;
&lt;td&gt;Swap hardware or resize the VM/instance in place&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Downtime&lt;/td&gt;
&lt;td&gt;Typically none; new nodes join the pool live&lt;/td&gt;
&lt;td&gt;Usually requires a reboot or maintenance window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application requirements&lt;/td&gt;
&lt;td&gt;App must be stateless or handle distributed state&lt;/td&gt;
&lt;td&gt;App can remain unaware, since it still runs on one node&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost model&lt;/td&gt;
&lt;td&gt;Roughly linear cost per added commodity node&lt;/td&gt;
&lt;td&gt;Cost rises steeply at high-end hardware tiers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault tolerance&lt;/td&gt;
&lt;td&gt;Redundant; a node failing doesn&amp;rsquo;t take the system down&lt;/td&gt;
&lt;td&gt;Single point of failure; that node failing is an outage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Capacity ceiling&lt;/td&gt;
&lt;td&gt;Practically unbounded, add nodes as needed&lt;/td&gt;
&lt;td&gt;Bounded by the largest machine/instance available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical use case&lt;/td&gt;
&lt;td&gt;Web-scale services, microservices, cloud-native apps&lt;/td&gt;
&lt;td&gt;Databases, legacy monoliths, short-term quick fixes&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;Horizontal scaling adds &lt;strong class="kw"&gt;more nodes&lt;/strong&gt; in parallel, while vertical scaling adds &lt;strong class="kw"&gt;more resources&lt;/strong&gt; to one existing node.&lt;/li&gt;
&lt;li&gt;Horizontal scaling needs a &lt;strong class="kw"&gt;load balancer&lt;/strong&gt; and app-level statelessness; vertical scaling needs no architectural change.&lt;/li&gt;
&lt;li&gt;Vertical scaling eventually hits a &lt;strong class="kw"&gt;hardware ceiling&lt;/strong&gt;; horizontal scaling can grow near-limitlessly.&lt;/li&gt;
&lt;li&gt;Vertical scaling usually requires &lt;strong class="kw"&gt;downtime&lt;/strong&gt; to resize, while horizontal scaling can add capacity live.&lt;/li&gt;
&lt;li&gt;Horizontal scaling improves &lt;strong class="kw"&gt;fault tolerance&lt;/strong&gt; through redundancy; vertical scaling keeps a single point of failure.&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;Horizontal Scaling&lt;/strong&gt;&lt;/p&gt;</description></item></channel></rss>