<?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>Streaming on IT Comparison</title><link>https://comparison.metacog.co.kr/tags/streaming/</link><description>Recent content in Streaming on IT Comparison</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Sun, 06 Sep 2026 10:04:00 +0900</lastBuildDate><atom:link href="https://comparison.metacog.co.kr/tags/streaming/index.xml" rel="self" type="application/rss+xml"/><item><title>Unary vs Streaming RPC: One Request-Response vs Continuous Message Flow</title><link>https://comparison.metacog.co.kr/posts/2026-09-06-unary-vs-streaming-rpc-one-request-response-vs-continuous-me/</link><pubDate>Sun, 06 Sep 2026 10:04:00 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-09-06-unary-vs-streaming-rpc-one-request-response-vs-continuous-me/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;Unary and streaming are the two call shapes gRPC (and similar RPC frameworks) support over HTTP/2. A &lt;strong class="kw"&gt;unary call&lt;/strong&gt; behaves like a classic function call — one request in, one response out, then done — while a &lt;strong class="kw"&gt;streaming call&lt;/strong&gt; keeps the connection open so either side can send multiple messages over time. The choice affects latency, backpressure handling, and how errors surface mid-exchange.&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="30" text-anchor="middle" style="fill:var(--primary)" font-size="16" font-weight="bold"&gt;Unary RPC&lt;/text&gt;&lt;text x="480" y="30" text-anchor="middle" style="fill:var(--primary)" font-size="16" font-weight="bold"&gt;Streaming RPC&lt;/text&gt;&lt;rect x="110" y="50" width="100" height="40" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="160" y="75" text-anchor="middle" style="fill:var(--content)" font-size="12"&gt;Client&lt;/text&gt;&lt;rect x="110" y="280" width="100" height="40" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="160" y="305" text-anchor="middle" style="fill:var(--content)" font-size="12"&gt;Server&lt;/text&gt;&lt;line x1="140" y1="90" x2="140" y2="278" style="stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;polygon points="140,278 135,268 145,268" style="fill:var(--compare-a)"/&gt;&lt;text x="98" y="185" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;request&lt;/text&gt;&lt;line x1="180" y1="278" x2="180" y2="90" style="stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;polygon points="180,90 175,100 185,100" style="fill:var(--compare-a)"/&gt;&lt;text x="224" y="185" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;response&lt;/text&gt;&lt;text x="160" y="345" text-anchor="middle" style="fill:var(--secondary)" font-size="11"&gt;one call = one request + one response, then closed&lt;/text&gt;&lt;rect x="430" y="50" width="100" height="40" 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" style="fill:var(--content)" font-size="12"&gt;Client&lt;/text&gt;&lt;rect x="430" y="280" width="100" height="40" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="480" y="305" text-anchor="middle" style="fill:var(--content)" font-size="12"&gt;Server&lt;/text&gt;&lt;line x1="480" y1="90" x2="480" y2="278" style="stroke:var(--border)" stroke-width="1.5" stroke-dasharray="3,3"/&gt;&lt;line x1="450" y1="120" x2="450" y2="150" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;polygon points="450,150 445,140 455,140" style="fill:var(--compare-b)"/&gt;&lt;line x1="450" y1="165" x2="450" y2="195" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;polygon points="450,195 445,185 455,185" style="fill:var(--compare-b)"/&gt;&lt;line x1="450" y1="210" x2="450" y2="240" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;polygon points="450,240 445,230 455,230" style="fill:var(--compare-b)"/&gt;&lt;text x="406" y="188" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;msg 1..N&lt;/text&gt;&lt;text x="480" y="345" text-anchor="middle" style="fill:var(--secondary)" font-size="11"&gt;one call = many messages over a long-lived connection&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;Unary RPC&lt;/th&gt;
&lt;th&gt;Streaming RPC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Call initiation&lt;/td&gt;
&lt;td&gt;Client opens the call and immediately sends the complete request&lt;/td&gt;
&lt;td&gt;Client opens the call, which may send zero, one, or many messages before or while reading responses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client-to-server messages&lt;/td&gt;
&lt;td&gt;Exactly one request message per call&lt;/td&gt;
&lt;td&gt;One (server-streaming) or many (client-streaming, bidi) messages per call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server-to-client messages&lt;/td&gt;
&lt;td&gt;Exactly one response message per call&lt;/td&gt;
&lt;td&gt;One (client-streaming) or many (server-streaming, bidi) messages per call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flow control&lt;/td&gt;
&lt;td&gt;Not needed — a single frame per direction fits within normal HTTP/2 windows&lt;/td&gt;
&lt;td&gt;HTTP/2 flow-control windows and backpressure govern how fast messages can be sent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connection/call lifetime&lt;/td&gt;
&lt;td&gt;Logically short-lived: opens and closes within one round trip&lt;/td&gt;
&lt;td&gt;Can stay open for the duration of a long-running exchange, sometimes indefinitely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency and overhead&lt;/td&gt;
&lt;td&gt;Full connection/setup overhead paid per call since each call is independent&lt;/td&gt;
&lt;td&gt;Setup overhead amortized across many messages, lowering per-message latency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Termination and errors&lt;/td&gt;
&lt;td&gt;A single status code ends the call atomically — it either succeeded or failed&lt;/td&gt;
&lt;td&gt;Status is sent only when the stream closes; errors can occur mid-stream after partial data was already delivered&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;Unary sends exactly one &lt;strong class="kw"&gt;request&lt;/strong&gt; and gets exactly one response, while streaming allows either side to send a &lt;strong class="kw"&gt;sequence&lt;/strong&gt; of messages over the same call&lt;/li&gt;
&lt;li&gt;Streaming relies on HTTP/2 &lt;strong class="kw"&gt;flow control&lt;/strong&gt; to manage backpressure across many frames; unary has nothing to manage&lt;/li&gt;
&lt;li&gt;A streaming call&amp;rsquo;s &lt;strong class="kw"&gt;connection&lt;/strong&gt; stays open far longer than a unary call&amp;rsquo;s brief request-response window&lt;/li&gt;
&lt;li&gt;Unary calls fail or succeed as a single &lt;strong class="kw"&gt;atomic&lt;/strong&gt; unit; streaming calls can deliver partial results before an error terminates them&lt;/li&gt;
&lt;li&gt;Streaming amortizes per-call &lt;strong class="kw"&gt;overhead&lt;/strong&gt; across many messages, cutting per-message latency compared to repeated unary calls&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;Unary RPC&lt;/strong&gt;&lt;/p&gt;</description></item></channel></rss>