<img src="https://blog.atj.sh/post-attachments/v/0194e507-718f-7380-8cb2-cb96a164684c" width="100%"/>

<br />

<p>
  ์ด ๋ธ”๋กœ๊ทธ๋Š” ๋„ค์ด๋ฒ„ ๋ธ”๋กœ๊ทธ๋‚˜ ํ‹ฐ์Šคํ† ๋ฆฌ ๋“ฑ์˜ ์„œ๋น„์Šค ์—†์ด ์šด์˜๋˜๊ณ  ์žˆ๋‹ค. ๊ธ€์— ์ฒจ๋ถ€๋˜๋Š” ์ด๋ฏธ์ง€๋“ค์„ ์œ„ํ•œ ๋ณ„๋„์˜ ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค ๋˜ํ•œ ์‚ฌ์šฉ์ค‘์ด์ง€ ์•Š๋‹ค. ๋ธ”๋กœ๊ทธ์—์„œ ๋ณด์—ฌ์ง€๋Š” ๋ชจ๋“  ์ด๋ฏธ์ง€๋Š” PostgreSQL DB์— ์ €์žฅ๋˜๊ณ , ์ด๋ฏธ์ง€ ์กฐํšŒ ์š”์ฒญ์ด ์˜ค๋ฉด ์„œ๋ฒ„๊ฐ€ DB์— ์ €์žฅ๋œ ์ด๋ฏธ์ง€ ์ด์ง„ ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์™€์„œ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์—ฌ์ฃผ๊ณ  ์žˆ๋‹ค.
</p>


<h2>
  ๋™๊ธฐ
</h2>

<p>
  ์ฒ˜์Œ ๋ธ”๋กœ๊ทธ๋ฅผ ์™„์„ฑํ•  ๋‹น์‹œ์—๋Š” ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๊ธฐ๋Šฅ์ด ์—†์—ˆ๋‹ค. ์ด ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๊ธฐ๋Šฅ์ด ์—†๋Š” ๊ฒƒ ๋•Œ๋ฌธ์— ๊ธ€์„ ์“ธ ๋•Œ ์•„์‰ฌ์šด ์ƒํ™ฉ์ด ๋งŽ์•„์กŒ๊ณ , ์ด์ œ๋Š” ์ œ๋ฐœ ๊ตฌํ˜„ํ•ด๋†“๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ๋‹ค. 
</p>

<p>
  ์ด ๋ธ”๋กœ๊ทธ์˜ ๊ธฐ์ˆ  ์Šคํƒ์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.
</p>

<ul>
<li><a href="https://www.rust-lang.org/" target="_blank">Rust lang</a> - ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด</li>
<li><a href="https://github.com/tokio-rs/axum"target="_blank">axum</a> - Rust ๊ธฐ๋ฐ˜ ์›น ํ”„๋ ˆ์ž„์›Œํฌ</li>
<li><a href="https://aws.amazon.com/ko/lambda/"target="_blank">AWS Lambda</a> - ์ปดํ“จํŒ… ์ธํ”„๋ผ์ŠคํŠธ๋Ÿญ์ณ</li>
<li><a href="https://postgresql.org/"target="_blank">PostgreSQL</a> - DB</li>
<li><a href="https://kit.svelte.dev/"target="_blank">SvelteKit</a> - Svelte ๊ธฐ๋ฐ˜ ์›น ํ”„๋ ˆ์ž„์›Œํฌ</li>
<li><a href="https://pages.cloudflare.com/"target="_blank">Cloudflare Pages</a> - ๋ฌด๋ฃŒ ์›น ํ˜ธ์ŠคํŒ… ์„œ๋น„์Šค</li>
<li><a href="https://supabase.com/"target="_blank">Supabase</a> - BaaS & ๋ฌด๋ฃŒ PostgreSQL ํ˜ธ์ŠคํŒ… ์„œ๋น„์Šค</li>
</ul>

<br />

<p>
  ๋‚˜๋Š” ์ด ๊ธฐ์ˆ  ์Šคํƒ์„ ๋ณ€ํ•จ ์—†์ด ์œ ์ง€ํ•˜๊ณ  ์‹ถ์—ˆ๋‹ค. ๋ฌด์–ธ๊ฐ€ ์„œ๋น„์Šค๋ฅผ ๋”ํ•˜๊ธฐ๋„ ์‹ซ์—ˆ๋‹ค. ์ผ๋‹จ ์‹œ๋„ํ•ด๋ดค๋‹ค. 
</p>

<h2>
  ๋ฉ”์ธ ์•„์ด๋””์–ด: DB์— ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•˜์ž
</h2>


<p>
  DB๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์˜ค๋žซ๋™์•ˆ ์ €์žฅํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. ์‹ค๋ฌด์—์„œ๋Š” DB์— ์ˆ˜์น˜๊ฐ’์ด๋‚˜ ์ด๋ฉ”์ผ ์ •๋ณด์™€ ๊ฐ™์€ ์ˆซ์ž, ํ…์ŠคํŠธ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ณค ํ•œ๋‹ค. 
</p>

<p>
  ๋ณดํ†ต ์ด๋ฏธ์ง€ ํŒŒ์ผ์€ DB๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ ๊ณณ์— ์ €์žฅ๋œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด NAS๋‚˜ ํด๋ผ์šฐ๋“œ ์Šคํ† ๋ฆฌ์ง€์— ์ €์žฅ๋˜๊ณค ํ•œ๋‹ค. ๊ด€๋ฆฌ์˜ ์šฉ์ด์„ฑ๊ณผ ๋ฐ์ดํ„ฐ ์„ฑ๊ฒฉ์— ๋”ฐ๋ฅธ ๋ถ„๋ฆฌ ๋•Œ๋ฌธ์ด๋‹ค.
</p>

<p>
  ๊ทธ๋Ÿฐ๋ฐ ๋‚˜๋Š”, ์ˆซ์ž๋‚˜ ํ…์ŠคํŠธ๋‚˜ ์ด๋ฏธ์ง€๋‚˜ ๊ฒฐ๊ตญ 0๊ณผ 1๋กœ ์ด๋ฃจ์–ด์ง„ ์ด์ง„ ๋ฐ์ดํ„ฐ๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๊ทธ๋ƒฅ DB์— ์ „๋ถ€ ๋ณด๊ด€ํ•ด๋ณด๊ธฐ๋กœ ํ–ˆ๋‹ค.
</p>

<p>
  ๊ธฐ์ˆ ์ ์œผ๋กœ ์‚ดํŽด๋ณด๋‹ˆ, ์ง€๊ธˆ ์‚ฌ์šฉ์ค‘์ธ DB์ธ <a href="https://www.postgresql.org/docs/current/datatype-binary.html" target="_blank">PostgreSQL์—์„œ ์ด์ง„ ์ •๋ณด ์ €์žฅ์„ ์ง€์›</a>ํ•œ๋‹ค๋Š” ์‚ฌ์‹ค์„ ํ™•์ธํ–ˆ๋‹ค. ์ด๊ฒŒ ๋œ๋‹ค๋ฉด ์ด๋ฏธ์ง€ ์ €์žฅ์ด๋‚˜ ๋” ๋‚˜์•„๊ฐ€์„œ ์ผ๋ฐ˜ ํŒŒ์ผ ์ €์žฅ๋„ ๊ฐ€๋Šฅํ•ด์ง„๋‹ค.
</p>

<h2>
  ํ…Œ์ด๋ธ” ์„ค๊ณ„
</h2>

<p>
  ํŒŒ์ผ์„ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•œ ํ…Œ์ด๋ธ”์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค๊ณ„ํ–ˆ๋‹ค.
</p>

<pre>
  CREATE TABLE public.post_attachment (
    id UUID NOT NULL,
    content BYTEA NOT NULL,
    written_by_id INTEGER NOT NULL,
    file_extension VARCHAR(20) NOT NULL DEFAULT '',
    mimetype VARCHAR(255) NOT NULL,
    PRIMARY KEY (id)
);
</pre>

<ul>
  <li>id: ์ธํ„ฐ๋„ท์—์„œ ํŒŒ์ผ์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•œ URL. UUID ํ˜•์‹์ด๋ฉฐ, URL์— ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ• ๋‹น๋œ๋‹ค. <code>https://blog.atj.sh/post-attachments/v/<b>0194e521-339c-7e10-b205-a2a9416fdb97</b></code></li>
  <li>content: ํŒŒ์ผ์˜ ์ด์ง„ ์ •๋ณด.</li>
  <li>written_by_id: ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•œ ์‚ฌ๋žŒ์˜ ID.</li>
  <li>file_extension: ํŒŒ์ผ์˜ ํ™•์žฅ์ž. ํŒŒ์ผ์ด ์—…๋กœ๋“œ๋  ๋•Œ ํŒŒ์ผ์˜ ์ด๋ฆ„์—์„œ ๊ฐ€์ ธ์™€ DB์— ์ €์žฅํ•œ๋‹ค. ํŒŒ์ผ์˜ ์›๋ž˜ ์ด๋ฆ„์€ ์ €์žฅ๋˜์ง€ ์•Š๊ณ  ๋Œ€์‹  ํ™•์žฅ์ž๋งŒ ์ €์žฅ๋œ๋‹ค.</li>
  <li>mimetype: ํŒŒ์ผ์˜ mimetype. ํŒŒ์ผ์ด ์—…๋กœ๋“œ๋  ๋•Œ <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob#instance_properties"><code>Blob.type()</code></a> ๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•ด ๊ฐ€์ ธ์™€์ ธ์„œ DB์— ์ €์žฅ๋œ๋‹ค.</li>
</ul>

<h2>
  ์—…๋กœ๋“œ ์‹œ๋‚˜๋ฆฌ์˜ค
</h2>

<p>
  ๋‚ด๊ฐ€ ๊ธ€์„ ์“ฐ๋ฉฐ ์ด๋ฏธ์ง€๋ฅผ ์ฒจ๋ถ€ํ•  ๋•Œ ์„œ๋ฒ„์—์„œ ์ด๋ฃจ์–ด์ง€๋Š” ์ž‘์—…์ด๋‹ค.
</p>

<img src="https://blog.atj.sh/post-attachments/v/0194e521-339c-7e10-b205-a2a9416fdb97" class="full-unrounded"/>

<p>
  ์ด๋ฏธ์ง€ ์••์ถ•์˜ ๊ฒฝ์šฐ์—๋Š” ์›๋ž˜ CloudFlare Pages ์„œ๋ฒ„๋‹จ์—์„œ ์ฒ˜๋ฆฌ๋˜๋„๋ก ํ•˜๋ ค๊ณ  ํ–ˆ๋Š”๋ฐ, <a href="https://community.cloudflare.com/t/rendering-svg-using-canvas-with-cloudflare-workers/90952" target="_blank">์ด๋ฏธ์ง€ ์••์ถ•์— ํ•ต์‹ฌ์ ์œผ๋กœ ํ•„์š”ํ•œ Canvas API๊ฐ€ ์ง€์›๋˜์ง€ ์•Š์•„์„œ ๊ด€๋’€๊ณ </a>, AWS Lambda์—์„œ ์ฒ˜๋ฆฌํ•˜๊ธฐ์—๋Š” <a href="https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html" target="_blank">ํ—ˆ์šฉ ์š”์ฒญ ํฌ๊ธฐ๊ฐ€ 6MB ์ˆ˜์ค€</a>์ด๋ผ์„œ ์š”์ฒญ์ด ๊ฑฐ๋ถ€๋˜๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒํ–ˆ๋‹ค.
</p>

<p>
  ๋”ฐ๋ผ์„œ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ํƒ€๊ธฐ ์ „์— ๋ธŒ๋ผ์šฐ์ € ์•ˆ์—์„œ ์ด๋ฏธ์ง€ ์••์ถ•์„ ๋ฏธ๋ฆฌ ์ง„ํ–‰ํ•˜๋„๋ก ๊ตฌํ˜„ํ–ˆ๋‹ค. ๊ฒฐ๋ก ์ ์œผ๋กœ๋Š” ๋„คํŠธ์›Œํฌ ํŠธ๋ž˜ํ”ฝ์ด ์ ˆ์•ฝ๋˜๊ณ  DB์— ์ €์žฅ๋˜๋Š” ๋ฐ์ดํ„ฐ์˜ ํฌ๊ธฐ ๋˜ํ•œ ์ž‘์•„์กŒ๋‹ค. 
</p> 

<p>
  ์••์ถ• ์•Œ๊ณ ๋ฆฌ์ฆ˜์€ <a href="https://ko.wikipedia.org/wiki/WebP">WebP๋กœ ์••์ถ•ํ•˜๋Š” ๋ฐฉ๋ฒ•</a>์„ ์‚ฌ์šฉํ–ˆ๋Š”๋ฐ, 12MB ์ด๋ฏธ์ง€๋ฅผ 100KB ์ •๋„์˜ ํฌ๊ธฐ๋กœ ์••์ถ•ํ•  ์ •๋„๋กœ ํšจ์œจ์ด ์ข‹์•˜๋‹ค.
</p>


<h2>
   ์กฐํšŒ ์‹œ๋‚˜๋ฆฌ์˜ค
</h2>
<p>
  ๋ฐฉ๋ฌธ์ž๊ฐ€ ์ด๋ฏธ์ง€๋ฅผ ๋ณผ ๋•Œ ์„œ๋ฒ„์—์„œ ์ด๋ฃจ์–ด์ง€๋Š” ์ž‘์—…์ด๋‹ค.
</p>

<img src="https://blog.atj.sh/post-attachments/v/0194e521-2c4a-71d3-bb0b-fcf2bb5419c4" class="full-unrounded"/>

<p>
  CloudFlare Pages์—์„œ ์ž์ฒด์ ์œผ๋กœ ์ง€์›๋˜๋Š” <a href="https://developers.cloudflare.com/workers/runtime-apis/cache/">Cache API</a>๋ฅผ ์ ์šฉํ–ˆ๋‹ค. ๋•๋ถ„์— DB ์กฐํšŒ๋กœ ์ธํ•œ ๋ถ€ํ•˜๊ฐ€ ์กฐ๊ธˆ์ด๋‚˜๋งˆ ์ค„์–ด๋“ค์—ˆ๋‹ค. ๋˜ํ•œ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์•Œ์•„์„œ ์บ์‹ฑํ•  ์ˆ˜ ์žˆ๋„๋ก <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control" target="_blank">Cache-Control ํ—ค๋”</a>๋ฅผ ์ ์šฉํ–ˆ๋‹ค. 
</p>

<img src="https://blog.atj.sh/post-attachments/v/0194e539-fa72-7c10-a22e-0d1a2bf03c76" width="100%" />

<p>
  ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ์ œ๊ณตํ•  ๋•Œ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ œ๊ณต๋  ์ˆ˜ ์žˆ๋„๋ก, ์ด๋ฏธ์ง€์˜ ํ˜•์‹์„ HTTP ์‘๋‹ต ํ—ค๋”์— ์ •ํ™•ํ•˜๊ฒŒ ๊ธฐ์ž…ํ•˜๋Š” ์ž‘์—…์ด ํ•„์š”ํ–ˆ๋‹ค. ์ด๋ฏธ์ง€๊ฐ€ ์„œ๋ฒ„์—์„œ ์กฐํšŒ๋  ๋•Œ <a href="https://github.com/atjsh/rust-blog/blob/ad708e876d7def1683e282fe13576fa14f6b7e94/server/src/routers/post_attachment.rs#L44" target="_blank">์ด๋ฏธ์ง€์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๊ฐ€ HTTP ์‘๋‹ต ํ—ค๋”์— ์ž˜ ๋‹ด๊ฒจ์„œ ์ „๋‹ฌ๋˜๋„๋ก ๊ตฌํ˜„</a>ํ•˜์˜€๋‹ค. <br /> ๊ฐœ์ธ์ ์œผ๋กœ๋Š” ์ด ์ž‘์—…์„ ํ•˜๋ฉด์„œ HTTP Mimetype๊ณผ ์ด๋ฏธ์ง€ ํ™•์žฅ์ž์˜ ์ฐจ์ด๋ฅผ ์ข€ ๋” ๋ช…ํ™•ํ•˜๊ฒŒ ์•Œ ์ˆ˜ ์žˆ์—ˆ๋‹ค.
</p>

<h2>๊ฒฐ๋ก : ๊ตฌํ˜„์— ์„ฑ๊ณตํ–ˆ์ง€๋งŒ ๊ฐœ์„ ์˜ ์—ฌ์ง€๋Š” ์žˆ์Œ</h2>

<p>
  ์ด ๊ธ€์—์„œ ์‚ฌ์šฉ๋œ ์ด๋ฏธ์ง€๋“ค์€ ๋ชจ๋‘ ์œ„ ๋กœ์ง์„ ํ†ตํ•ด DB๋กœ๋ถ€ํ„ฐ ์กฐํšŒ๋˜์–ด ๋ธŒ๋ผ์šฐ์ €์— ๋ Œ๋”๋ง๋œ ๊ฒƒ์ด๋‹ค. ์ด๋ฏธ์ง€๋“ค์ด ์ž˜ ๋ณด์ธ๋‹ค๋ฉด ๊ตฌํ˜„์ด ์•ˆ์ •์ ์œผ๋กœ ์™„๋ฃŒ๋˜์—ˆ๋‹ค๋Š” ๋œป์ผ ๊ฒƒ์ด๋‹ค. ๋งŒ์•ฝ ์ด๋ฏธ์ง€๊ฐ€ ์•ˆ ๋ณด์ธ๋‹ค๋ฉด, ์ค‘๊ฐ„์—์„œ ๋ฏธ์ฒ˜ ๋ฐœ๊ฒฌ ๋ชปํ•œ ๋ฒ„๊ทธ๊ฐ€ ์žˆ์–ด ๋ธŒ๋ผ์šฐ์ €๊นŒ์ง€ ์ด๋ฏธ์ง€๊ฐ€ ๋„๋‹ฌํ•˜์ง€ ๋ชปํ•œ ๊ฒƒ์œผ๋กœ ๋ณด๋ฉด ๋œ๋‹ค.
</p>

<p>
  HTTP ์บ์‹ฑ์„ ์ ์šฉํ•˜๊ธด ํ–ˆ์ง€๋งŒ ์™„๋ฒฝํžˆ ํŒŒ์•…ํ•˜์ง„ ๋ชปํ•ด์„œ ์ผ๋ถ€๋ถ„๋งŒ ๊ตฌํ˜„ํ–ˆ๋‹ค. ๊ธฐํšŒ๊ฐ€ ๋˜๋ฉด ๋” ์™„์ „ํ•˜๊ฒŒ ๊ตฌํ˜„ํ•˜๊ณ  ์‹ถ๋‹ค.
</p>

<p>
  1MB ํฌ๊ธฐ๋ณด๋‹ค ์ž‘์€ ์ด๋ฏธ์ง€ ์ œ๊ณต์€ ์„ฑ๊ณตํ–ˆ์ง€๋งŒ, ๊ทธ๋ณด๋‹ค ํฐ ์ด๋ฏธ์ง€๋‚˜ ๋Œ€์šฉ๋Ÿ‰ ํŒŒ์ผ ์ œ๊ณต์€ ์œ„์—์„œ ์„ค๋ช…ํ•œ ๋ฐ”์™€ ๊ฐ™์ด ์—ฌ๋Ÿฌ ์ œํ•œ ๋•Œ๋ฌธ์— ํž˜๋“ค๋‹ค. ์‹ฌ์ง€์–ด 10MB ์ •๋„์˜ ํŒŒ์ผ ์ œ๊ณต๋„ ์ง€๊ธˆ์€ ๋ถˆ๊ฐ€๋Šฅํ•œ ์ƒํ™ฉ์ด๋‹ค. ์ด๋ฏธ์ง€์˜ ๊ฒฝ์šฐ์—๋Š” WebP ์••์ถ•์ด ์›Œ๋‚™ ํšจ์œจ์ ์ด๋ผ ๋ถˆํŽธํ•œ ์ ์ด ์—†๋Š” ์ƒํ™ฉ์ด์ง€๋งŒ, ๋‹ค๋ฅธ ํŒŒ์ผ์˜ ๊ฒฝ์šฐ์—๋Š” ์šฉ๋Ÿ‰ ์ œํ•œ์ด ํฌ๊ฒŒ ๋‹ค๊ฐ€์˜ฌ ์ˆ˜ ๋ฐ–์—  ์—†๋‹ค. 
</p>

<p>
  ๊ธฐ์ˆ  ์Šคํƒ์„ ์œ ์ง€ํ•˜๋Š” ๋™์‹œ์— ๋” ํฐ ํŒŒ์ผ์„ ์„œ๋น™ํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•์„ ์—ฐ๊ตฌํ•ด๋ด์•ผ๊ฒ ๋‹ค.
</p>

<h2>
  ์†Œ์Šค์ฝ”๋“œ
</h2>

<p>์ด ๋ธ”๋กœ๊ทธ์˜ ์†Œ์Šค์ฝ”๋“œ๋Š” ์˜คํ”ˆ์†Œ์Šค์ด๋‹ค. ๋‚ด <a href="https://github.com/atjsh/rust-blog" target="_blank">GitHub</a>์—์„œ ํ™•์ธ ๊ฐ€๋Šฅํ•˜๋‹ค.</p>


<p>
  ์ด๋ฒˆ ๊ธฐ๋Šฅ์ด ์ถ”๊ฐ€๋œ ์ปค๋ฐ‹ ๋‚ด์—ญ์€ ๋‹ค์Œ ๋งํฌ์—์„œ ํ™•์ธ ๊ฐ€๋Šฅํ•˜๋‹ค. <a href="https://github.com/atjsh/rust-blog/compare/89958ca4f61585c1a8c0036d6b31301769cccb3c...ad708e876d7def1683e282fe13576fa14f6b7e94?diff=unified" target="_blank">atjsh/rust-blog - 89958c...ad708e</a>
</p>

<h2>
  ๊ต‰์žฅํžˆ ๋งŒ์กฑ
</h2>

<p>
  ๊ฑฐ์˜ 2๋‹ฌ๊ฐ„ ๋ฏธ๋ฃฌ ์ž‘์—…์ธ๋ฐ ํ•˜๋ฃจ๋งŒ์— ๊ตฌํ˜„์„ ๋๋ƒˆ๋‹ค. ๋“œ๋””์–ด ๋๋‚ด์„œ ์†์ด ํ›„๋ จํ•˜๋‹ค.
</p>