2025๋…„ 3์›” 03์ผยท6๋ถ„ ์ฝ๊ธฐ

๋ฉ€ํ‹ฐํ…Œ๋„ŒํŠธ ์•ฑ์„ ์œ„ํ•œ PostgreSQL ํ–‰ ์ˆ˜์ค€ ๋ณด์•ˆ ํŒจํ„ด

ํ…Œ๋„ŒํŠธ ๊ฒฉ๋ฆฌ์™€ ์—ญํ•  ๊ทœ์น™์„ ์œ„ํ•œ ์‹ค์šฉ์  ํŒจํ„ด์œผ๋กœ PostgreSQL ํ–‰ ์ˆ˜์ค€ ๋ณด์•ˆ(RLS)์„ ๋ฐฐ์šฐ๊ณ , ์ ‘๊ทผ ์ œ์–ด๋ฅผ ์•ฑ์ด ์•„๋‹Œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๊ฐ•์ œํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์„ธ์š”.

๋ฉ€ํ‹ฐํ…Œ๋„ŒํŠธ ์•ฑ์„ ์œ„ํ•œ PostgreSQL ํ–‰ ์ˆ˜์ค€ ๋ณด์•ˆ ํŒจํ„ด

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฐจ์›์˜ ์ ‘๊ทผ ํ†ต์ œ๊ฐ€ ์ค‘์š”ํ•œ ์ด์œ 

๋น„์ฆˆ๋‹ˆ์Šค ์•ฑ์—๋Š” ๋ณดํ†ต โ€œ์‚ฌ์šฉ์ž๋Š” ์ž๊ธฐ ํšŒ์‚ฌ์˜ ๋ฐ์ดํ„ฐ๋งŒ ๋ณผ ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹คโ€๊ฑฐ๋‚˜ โ€œ๊ด€๋ฆฌ์ž๋งŒ ํ™˜๋ถˆ์„ ์Šน์ธํ•  ์ˆ˜ ์žˆ๋‹คโ€ ๊ฐ™์€ ๊ทœ์น™์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŽ์€ ํŒ€์ด ์ด๋Ÿฐ ๊ทœ์น™์„ UI๋‚˜ API์—์„œ๋งŒ ๊ฐ•์ œํ•˜๊ณ  ๊ทธ๊ฒƒ์œผ๋กœ ์ถฉ๋ถ„ํ•˜๋‹ค๊ณ  ๊ฐ€์ •ํ•˜์ฃ . ๋ฌธ์ œ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋‹ฟ๋Š” ๊ฒฝ๋กœ๊ฐ€ ํ•˜๋‚˜ ๋” ๋Š˜์–ด๋‚ ์ˆ˜๋ก ๋ฐ์ดํ„ฐ ์œ ์ถœ ๊ฐ€๋Šฅ์„ฑ์ด ์ปค์ง„๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค: ๋‚ด๋ถ€ ๊ด€๋ฆฌ์ž ๋„๊ตฌ, ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์žก, ๋ถ„์„ ์ฟผ๋ฆฌ, ์žŠํ˜€์ง„ ์—”๋“œํฌ์ธํŠธ, ๋˜๋Š” ์–ด๋А ํ•œ ๊ฒ€์‚ฌ(check)๋ฅผ ๋ˆ„๋ฝํ•˜๋Š” ๋ฒ„๊ทธ ๋“ฑ์ž…๋‹ˆ๋‹ค.

ํ…Œ๋„ŒํŠธ ๊ฒฉ๋ฆฌ๋Š” ํ•œ ๊ณ ๊ฐ(ํ…Œ๋„ŒํŠธ)์ด ์‹ค์ˆ˜๋กœ๋ผ๋„ ๋‹ค๋ฅธ ๊ณ ๊ฐ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ฑฐ๋‚˜ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๊ฒŒ ๋งŒ๋“œ๋Š” ๊ฒƒ์„ ๋œปํ•ฉ๋‹ˆ๋‹ค. ์—ญํ•  ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ์ œ์–ด๋Š” ๊ฐ™์€ ํ…Œ๋„ŒํŠธ ๋‚ด๋ถ€์—์„œ๋„ ์—์ด์ „ํŠธ, ๋งค๋‹ˆ์ €, ์žฌ๋ฌด ๋“ฑ ๊ถŒํ•œ์ด ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์„ ๊ตฌ๋ถ„ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฐ ๊ทœ์น™์€ ์„ค๋ช…ํ•˜๊ธฐ๋Š” ์‰ฝ์ง€๋งŒ, ์—ฌ๋Ÿฌ ๊ณณ์— ํฉ์–ด์ ธ ์žˆ์œผ๋ฉด ์ผ๊ด€๋˜๊ฒŒ ์œ ์ง€ํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.

PostgreSQL์˜ ํ–‰ ์ˆ˜์ค€ ๋ณด์•ˆ(RLS)์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์–ด๋–ค ํ–‰์„ ๋ณผ ์ˆ˜ ์žˆ๊ณ  ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๊ฒฐ์ •ํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. ์•ฑ์˜ ๋ชจ๋“  ์ฟผ๋ฆฌ๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ WHERE ์ ˆ์„ ๊ธฐ์–ตํ•˜๊ธฐ๋ฅผ ๊ธฐ๋Œ€ํ•˜๋Š” ๋Œ€์‹ , ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์ •์ฑ…์„ ์ž๋™์œผ๋กœ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

RLS๊ฐ€ ๋งŒ๋Šฅ ๋ฐฉํŒจ๋Š” ์•„๋‹™๋‹ˆ๋‹ค. ์Šคํ‚ค๋งˆ ์„ค๊ณ„๋‚˜ ์ธ์ฆ์„ ๋Œ€์‹ ํ•ด์ฃผ์ง€ ์•Š๊ณ , ์ด๋ฏธ ๊ฐ•๋ ฅํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ถŒํ•œ(์˜ˆ: superuser)์„ ๊ฐ€์ง„ ์‚ฌ๋žŒ์„ ๋ง‰์•„์ฃผ์ง€๋„ ์•Š์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์ฝ๊ธฐ ์ •์ฑ…์ด ์—†๋Š”๋ฐ ์“ฐ๊ธฐ๋งŒ ํ—ˆ์šฉ๋˜๋Š” ์‹์˜ ๋…ผ๋ฆฌ์  ์‹ค์ˆ˜๋ฅผ ๋ง‰์œผ๋ ค๋ฉด ์ฝ๊ธฐ์™€ ์“ฐ๊ธฐ ๋ชจ๋‘์— ๋Œ€ํ•œ ์ •์ฑ…์„ ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์–ป๋Š” ์ด์ ์€ ๋ถ„๋ช…ํ•ฉ๋‹ˆ๋‹ค:

  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ‘๊ทผํ•˜๋Š” ๋ชจ๋“  ์ฝ”๋“œ ๊ฒฝ๋กœ์— ๋Œ€ํ•ด ํ•œ ๋ฒŒ์˜ ๊ทœ์น™์„ ์ ์šฉ
  • ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ๋ฐฐํฌ ์‹œ ๋ฐœ์ƒํ•˜๋Š” ์‹ค์ˆ˜ ๊ฐ์†Œ
  • SQL์—์„œ ์ ‘๊ทผ ๊ทœ์น™์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด ๊ฐ์‚ฌ๊ฐ€ ๋ช…ํ™•ํ•ด์ง
  • API ๋ฒ„๊ทธ๊ฐ€ ์ง€๋‚˜์น  ๋•Œ ๋” ๊ฐ•ํ•œ ๋ฐฉ์–ด์„  ์ œ๊ณต

์„ค์ • ๋น„์šฉ์€ ์ž‘์ง€ ์•Š์Šต๋‹ˆ๋‹ค. โ€œ์ด ์‚ฌ์šฉ์ž๊ฐ€ ๋ˆ„๊ตฌ์ธ์ง€โ€์™€ โ€œ์–ด๋–ค ํ…Œ๋„ŒํŠธ์ธ์ง€โ€๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ผ๊ด€๋˜๊ฒŒ ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ์‹์ด ํ•„์š”ํ•˜๊ณ , ์•ฑ์ด ์„ฑ์žฅํ• ์ˆ˜๋ก ์ •์ฑ…์„ ์œ ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. SaaS๋‚˜ ๋ฏผ๊ฐํ•œ ๊ณ ๊ฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃจ๋Š” ๋‚ด๋ถ€ ๋„๊ตฌ๋ผ๋ฉด ํˆฌ์ž ๋Œ€๋น„ ํšจ๊ณผ๊ฐ€ ํฝ๋‹ˆ๋‹ค.

์ „๋ฌธ ์šฉ์–ด ์—†์ด ๋ณด๋Š” ํ–‰ ์ˆ˜์ค€ ๋ณด์•ˆ ๊ธฐ์ดˆ

ํ–‰ ์ˆ˜์ค€ ๋ณด์•ˆ(RLS)์€ ์ฟผ๋ฆฌ๊ฐ€ ๋ณผ ์ˆ˜ ์žˆ๊ฑฐ๋‚˜ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋Š” ํ–‰์„ ์ž๋™์œผ๋กœ ํ•„ํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค. ํ™”๋ฉด์ด๋‚˜ API, ๋ฆฌํฌํŠธ๊ฐ€ ๊ทœ์น™์„ ์Šค์Šค๋กœ ๊ธฐ์–ตํ•˜๊ธฐ๋ฅผ ๊ธฐ๋Œ€ํ•˜๋Š” ๋Œ€์‹ , ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ๊ทœ์น™์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

PostgreSQL RLS์—์„œ๋Š” SELECT, INSERT, UPDATE, DELETE๋งˆ๋‹ค ๊ฒ€์‚ฌ๋˜๋Š” ์ •์ฑ…์„ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ •์ฑ…์ด โ€œ์ด ์‚ฌ์šฉ์ž๋Š” ํ…Œ๋„ŒํŠธ A์˜ ํ–‰๋งŒ ๋ณผ ์ˆ˜ ์žˆ๋‹คโ€๊ณ  ๋งํ•˜๋ฉด, ์žŠํžŒ ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€๋‚˜ ์ƒˆ ์ฟผ๋ฆฌ, ๊ธ‰ํ•œ ํ•ซํ”ฝ์Šค๋„ ๊ฐ™์€ ๋ณดํ˜ธ๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.

RLS๋Š” GRANT/REVOKE์™€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. GRANT๋Š” ํŠน์ • ์—ญํ• ์ด ํ…Œ์ด๋ธ”์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š”์ง€(๋˜๋Š” ํŠน์ • ์—ด์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š”์ง€)๋ฅผ ๊ฒฐ์ •ํ•˜๊ณ , RLS๋Š” ๊ทธ ํ…Œ์ด๋ธ” ๋‚ด๋ถ€์—์„œ ์–ด๋–ค ํ–‰์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š”์ง€๋ฅผ ์ •ํ•ฉ๋‹ˆ๋‹ค. ๋ณดํ†ต์€ ๋‘˜์„ ํ•จ๊ป˜ ์”๋‹ˆ๋‹ค: GRANT๋กœ ํ…Œ์ด๋ธ” ์ ‘๊ทผ์„ ์ œํ•œํ•˜๊ณ  RLS๋กœ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ํ–‰์„ ์ œํ•œํ•ฉ๋‹ˆ๋‹ค.

ํ˜„์‹ค์—์„œ๋„ ์ž˜ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ๋ทฐ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ RLS๋ฅผ ์ค€์ˆ˜ํ•˜๊ณ , ์กฐ์ธ๊ณผ ์„œ๋ธŒ์ฟผ๋ฆฌ๋„ ํ•„ํ„ฐ๋ง๋˜๋ฏ€๋กœ ์‚ฌ์šฉ์ž๊ฐ€ ์กฐ์ธ์„ ์ด์šฉํ•ด ๋‹ค๋ฅธ ์‚ฌ๋žŒ์˜ ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ •์ฑ…์€ ์–ด๋–ค ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๋“  ์ ์šฉ๋ฉ๋‹ˆ๋‹ค: ์•ฑ ์ฝ”๋“œ, SQL ์ฝ˜์†”, ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์žก, ๋ฆฌํฌํŒ… ๋„๊ตฌ ๋“ฑ.

RLS๋Š” ํ…Œ๋„ŒํŠธ ๊ฒฉ๋ฆฌ๊ฐ€ ๊ฐ•ํ•˜๊ฒŒ ์š”๊ตฌ๋˜๊ฑฐ๋‚˜ ๋™์ผํ•œ ํ…Œ์ด๋ธ”์„ ์—ฌ๋Ÿฌ ๋ฐฉ์‹์œผ๋กœ ์กฐํšŒํ•˜๊ฑฐ๋‚˜ ๋งŽ์€ ์—ญํ• ์ด ๊ฐ™์€ ํ…Œ์ด๋ธ”์„ ๊ณต์œ ํ•˜๋Š” ํ™˜๊ฒฝ(SaaS ๋ฐ ๋‚ด๋ถ€ ๋„๊ตฌ)์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค. ๋‹จ์ผ ์‹ ๋ขฐ ๋ฐฑ์—”๋“œ๋งŒ ์žˆ๋Š” ์ž‘์€ ์•ฑ์ด๋‚˜ ๋ฏผ๊ฐํ•˜์ง€ ์•Š์€ ๋ฐ์ดํ„ฐ์—๋Š” ๊ณผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ด€๋ฆฌ์ž ๋„๊ตฌ, ๋ฐ์ดํ„ฐ ์ถ”์ถœ, BI, ์Šคํฌ๋ฆฝํŠธ ๊ฐ™์€ ์ง„์ž…์ ์ด ํ•˜๋‚˜๋ผ๋„ ๋”ํ•ด์ง€๋ฉด RLS๊ฐ€ ๋ˆ๊ฐ’์„ ํ•ฉ๋‹ˆ๋‹ค.

ํ…Œ๋„ŒํŠธ, ์—ญํ• , ๋ฐ์ดํ„ฐ ์†Œ์œ ๊ถŒ์„ ๋จผ์ € ์ •๋ฆฌํ•˜์„ธ์š”

์ •์ฑ…์„ ํ•˜๋‚˜๋„ ์ž‘์„ฑํ•˜๊ธฐ ์ „์— ๋ˆ„๊ฐ€ ๋ฌด์—‡์„ ์†Œ์œ ํ•˜๋Š”์ง€ ๋ช…ํ™•ํžˆ ํ•˜์„ธ์š”. PostgreSQL RLS๋Š” ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์ด ์ด๋ฏธ ํ…Œ๋„ŒํŠธ, ์—ญํ• , ์†Œ์œ ๊ถŒ์„ ๋ฐ˜์˜ํ•˜๊ณ  ์žˆ์„ ๋•Œ ๊ฐ€์žฅ ์ž˜ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

ํ…Œ๋„ŒํŠธ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜์„ธ์š”. ๋Œ€๋ถ€๋ถ„์˜ SaaS ์•ฑ์—์„œ ๊ฐ€์žฅ ๋‹จ์ˆœํ•œ ๊ทœ์น™์€: ๊ณ ๊ฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ด๋Š” ๋ชจ๋“  ๊ณต์œ  ํ…Œ์ด๋ธ”์—๋Š” tenant_id๊ฐ€ ์žˆ๋‹ค์ž…๋‹ˆ๋‹ค. ์ธ๋ณด์ด์Šค ๊ฐ™์€ ๋ช…๋ฐฑํ•œ ํ…Œ์ด๋ธ”๋ฟ ์•„๋‹ˆ๋ผ ์ฒจ๋ถ€ ํŒŒ์ผ, ๋Œ“๊ธ€, ๊ฐ์‚ฌ ๋กœ๊ทธ, ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์žก ๊ฐ™์€ ์ž์ฃผ ์žŠํžˆ๋Š” ํ…Œ์ด๋ธ”๋„ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ์œผ๋กœ ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•˜๋Š” ์—ญํ•  ์ด๋ฆ„์„ ์ •ํ•˜์„ธ์š”. ์ง‘ํ•ฉ์„ ์ž‘๊ณ  ์‚ฌ๋žŒ ์นœํ™”์ ์œผ๋กœ ์œ ์ง€ํ•˜์„ธ์š”: owner, manager, agent, read-only ๊ฐ™์€ ๋น„์ฆˆ๋‹ˆ์Šค ์—ญํ• ์„ ์ •์ฑ… ๊ฒ€์‚ฌ์— ๋งคํ•‘ํ•ฉ๋‹ˆ๋‹ค(๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ญํ• ๊ณผ๋Š” ๋‹ค๋ฆ…๋‹ˆ๋‹ค).

๊ทธ๋‹ค์Œ ๋ ˆ์ฝ”๋“œ ์†Œ์œ ๊ถŒ์„ ๊ฒฐ์ •ํ•˜์„ธ์š”. ์–ด๋–ค ํ…Œ์ด๋ธ”์€ ๋‹จ์ผ ์‚ฌ์šฉ์ž ์†Œ์œ (์˜ˆ: ๊ฐœ์ธ ๋…ธํŠธ)์ด๊ณ , ์–ด๋–ค ํ…Œ์ด๋ธ”์€ ํŒ€ ์†Œ์œ (์˜ˆ: ๊ณต์œ  ์ธ๋ฐ•์Šค)์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‘ ๋ฐฉ์‹์„ ๊ณ„ํš ์—†์ด ์„ž์œผ๋ฉด ์ •์ฑ…์ด ์ฝ๊ธฐ ์–ด๋ ต๊ณ  ์šฐํšŒํ•˜๊ธฐ ์‰ฌ์›Œ์ง‘๋‹ˆ๋‹ค.

๊ฐ ํ…Œ์ด๋ธ”์— ๋Œ€ํ•ด ๊ฐ™์€ ์งˆ๋ฌธ์— ๋‹ตํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ทœ์น™์„ ๋ฌธ์„œํ™”ํ•˜์„ธ์š”:

  • ํ…Œ๋„ŒํŠธ ๊ฒฝ๊ณ„๋Š” ๋ฌด์—‡์ธ๊ฐ€(์–ด๋–ค ์—ด์ด ๊ทธ๊ฒƒ์„ ๊ฐ•์ œํ•˜๋Š”๊ฐ€)?
  • ๋ˆ„๊ฐ€ ํ–‰์„ ์ฝ์„ ์ˆ˜ ์žˆ๋Š”๊ฐ€(์—ญํ• ๊ณผ ์†Œ์œ ๊ถŒ ๊ธฐ์ค€)?
  • ๋ˆ„๊ฐ€ ํ–‰์„ ์ƒ์„ฑํ•˜๊ณ  ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ๋Š”๊ฐ€(์–ด๋–ค ์กฐ๊ฑด์—์„œ)?
  • ๋ˆ„๊ฐ€ ํ–‰์„ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋Š”๊ฐ€(๋ณดํ†ต ๊ฐ€์žฅ ์—„๊ฒฉํ•จ)?
  • ์–ด๋–ค ์˜ˆ์™ธ๊ฐ€ ํ—ˆ์šฉ๋˜๋Š”๊ฐ€(์ง€์› ์ธ๋ ฅ, ์ž๋™ํ™”, ๋‚ด๋ณด๋‚ด๊ธฐ ๋“ฑ)?

์˜ˆ: โ€œInvoicesโ€๋Š” ๋งค๋‹ˆ์ €๊ฐ€ ํ…Œ๋„ŒํŠธ์˜ ๋ชจ๋“  ์ธ๋ณด์ด์Šค๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๊ณ , ์—์ด์ „ํŠธ๋Š” ํ• ๋‹น๋œ ๊ณ ๊ฐ์˜ ์ธ๋ณด์ด์Šค๋งŒ, ์ฝ๊ธฐ ์ „์šฉ์€ ์กฐํšŒ๋งŒ ๊ฐ€๋Šฅํ•˜๊ณ  ์ˆ˜์ • ๋ถˆ๊ฐ€๋กœ ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ…Œ๋„ŒํŠธ ๊ฒฉ๋ฆฌ๋‚˜ ์‚ญ์ œ์ฒ˜๋Ÿผ ๋ฐ˜๋“œ์‹œ ์—„๊ฒฉํ•ด์•ผ ํ•  ๊ทœ์น™๊ณผ ๋งค๋‹ˆ์ €์—๊ฒŒ ์ถ”๊ฐ€ ๊ฐ€์‹œ์„ฑ์„ ์ค„ ์ˆ˜ ์žˆ๋Š” ์œ ์—ฐํ•œ ๊ทœ์น™์„ ๋ฏธ๋ฆฌ ๋‚˜๋ˆ ๋‘์„ธ์š”. AppMaster ๊ฐ™์€ ๋…ธ์ฝ”๋“œ ๋„๊ตฌ๋กœ ๋นŒ๋“œํ•œ๋‹ค๋ฉด ์ด ๋งคํ•‘์€ UI ๊ธฐ๋Œ€์น˜์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ทœ์น™์„ ์ผ์น˜์‹œํ‚ค๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

๋ฉ€ํ‹ฐํ…Œ๋„ŒํŠธ ํ…Œ์ด๋ธ”์„ ์œ„ํ•œ ์„ค๊ณ„ ํŒจํ„ด

๋ฉ€ํ‹ฐํ…Œ๋„ŒํŠธ RLS๋Š” ํ…Œ์ด๋ธ” ๊ตฌ์กฐ๊ฐ€ ์˜ˆ์ธก ๊ฐ€๋Šฅํ•  ๋•Œ ๊ฐ€์žฅ ์ž˜ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ํ…Œ์ด๋ธ”์ด ํ…Œ๋„ŒํŠธ๋ฅผ ๋‹ค๋ฅธ ๋ฐฉ์‹์œผ๋กœ ์ €์žฅํ•˜๋ฉด ์ •์ฑ…์ด ํผ์ฆ์ฒ˜๋Ÿผ ๋ณต์žกํ•ด์ง‘๋‹ˆ๋‹ค. ์ผ๊ด€๋œ ํ˜•ํƒœ๋Š” ์ •์ฑ…์„ ์ฝ๊ณ  ํ…Œ์ŠคํŠธํ•˜๊ณ  ์œ ์ง€ํ•˜๊ธฐ ์‰ฝ๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

๋จผ์ € ํ•˜๋‚˜์˜ ํ…Œ๋„ŒํŠธ ์‹๋ณ„์ž๋ฅผ ์ •ํ•˜๊ณ  ์–ด๋””์„œ๋‚˜ ์‚ฌ์šฉํ•˜์„ธ์š”. UUID๋Š” ์ถ”์ธกํ•˜๊ธฐ ์–ด๋ ต๊ณ  ๋งŽ์€ ์‹œ์Šคํ…œ์—์„œ ์ƒ์„ฑํ•˜๊ธฐ ์‰ฌ์›Œ์„œ ํ”ํžˆ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋‚ด๋ถ€์šฉ์ด๋ผ๋ฉด ์ •์ˆ˜๋„ ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค. ์‚ฌ๋žŒ์ด ์ฝ๊ธฐ ์‰ฌ์šด ์Šฌ๋Ÿฌ๊ทธ(์˜ˆ: "acme")๋Š” ๋ฐ”๋€” ์ˆ˜ ์žˆ์œผ๋‹ˆ ํ‘œ์‹œ์šฉ์œผ๋กœ ์ทจ๊ธ‰ํ•˜๊ณ  ํ•ต์‹ฌ ํ‚ค๋กœ๋Š” ์“ฐ์ง€ ๋งˆ์„ธ์š”.

ํ…Œ๋„ŒํŠธ ๋ฒ”์œ„ ๋ฐ์ดํ„ฐ์—๋Š” tenant_id ์—ด์„ ์ถ”๊ฐ€ํ•˜๊ณ  ๊ฐ€๋Šฅํ•˜๋ฉด NOT NULL๋กœ ๋งŒ๋“œ์„ธ์š”. ํ…Œ๋„ŒํŠธ ์—†์ด ์กด์žฌํ•  ์ˆ˜ ์žˆ๋Š” ํ–‰์€ ๋ณดํ†ต ๋ƒ„์ƒˆ๊ฐ€ ๋‚ฉ๋‹ˆ๋‹ค: ์ „์—ญ๊ณผ ํ…Œ๋„ŒํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ํ…Œ์ด๋ธ”์— ์„ž์œผ๋ฉด RLS ์ •์ฑ…์ด ์–ด๋ ต๊ณ  ์ทจ์•ฝํ•ด์ง‘๋‹ˆ๋‹ค.

์ธ๋ฑ์‹ฑ์€ ๋‹จ์ˆœํ•˜์ง€๋งŒ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. SaaS ์•ฑ์˜ ๋Œ€๋ถ€๋ถ„ ์ฟผ๋ฆฌ๋Š” ๋จผ์ € ํ…Œ๋„ŒํŠธ๋กœ ํ•„ํ„ฐ๋งํ•œ ๋’ค ์ƒํƒœ๋‚˜ ๋‚ ์งœ ๊ฐ™์€ ๋น„์ฆˆ๋‹ˆ์Šค ํ•„๋“œ๋กœ ์ขํž™๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์€ tenant_id ์ธ๋ฑ์Šค์ด๊ณ , ํŠธ๋ž˜ํ”ฝ์ด ๋งŽ์€ ํ…Œ์ด๋ธ”์€ (tenant_id, created_at)๋‚˜ (tenant_id, status) ๊ฐ™์€ ๋ณตํ•ฉ ์ธ๋ฑ์Šค๊ฐ€ ์ข‹์Šต๋‹ˆ๋‹ค.

์–ด๋–ค ํ…Œ์ด๋ธ”์ด ์ „์—ญ์ธ์ง€ ํ…Œ๋„ŒํŠธ ๋ฒ”์œ„์ธ์ง€ ๋นจ๋ฆฌ ๊ฒฐ์ •ํ•˜์„ธ์š”. ์ „์—ญ ํ…Œ์ด๋ธ” ์˜ˆ: ๊ตญ๊ฐ€, ํ†ตํ™” ์ฝ”๋“œ, ์š”๊ธˆ์ œ ์ •์˜. ํ…Œ๋„ŒํŠธ ๋ฒ”์œ„ ํ…Œ์ด๋ธ” ์˜ˆ: ๊ณ ๊ฐ, ์ธ๋ณด์ด์Šค, ํ‹ฐ์ผ“ ๋“ฑ ํ…Œ๋„ŒํŠธ๊ฐ€ ์†Œ์œ ํ•˜๋Š” ๊ฒƒ.

์œ ์ง€๋ณด์ˆ˜ ๊ฐ€๋Šฅํ•œ ๊ทœ์น™์„ ์›ํ•˜๋ฉด ๋ฒ”์œ„๋ฅผ ์ข๊ฒŒ ์œ ์ง€ํ•˜์„ธ์š”:

  • ํ…Œ๋„ŒํŠธ ๋ฒ”์œ„ ํ…Œ์ด๋ธ”: tenant_id NOT NULL, RLS ํ™œ์„ฑํ™”, ์ •์ฑ…์€ ํ•ญ์ƒ tenant_id๋ฅผ ๊ฒ€์‚ฌ
  • ์ „์—ญ ์ฐธ์กฐ ํ…Œ์ด๋ธ”: tenant_id ์—†์Œ, ํ…Œ๋„ŒํŠธ ์ •์ฑ… ์—†์Œ, ๋Œ€๋ถ€๋ถ„ ์—ญํ• ์— ๋Œ€ํ•ด ์ฝ๊ธฐ ์ „์šฉ
  • ๊ณต์œ ๋˜์ง€๋งŒ ํ†ต์ œ๋˜๋Š” ํ…Œ์ด๋ธ”: ๊ฐœ๋…๋ณ„๋กœ ํ…Œ์ด๋ธ” ๋ถ„๋ฆฌ(์ „์—ญ๊ณผ ํ…Œ๋„ŒํŠธ ํ–‰ ํ˜ผํ•ฉ ๊ธˆ์ง€)

AppMaster ๊ฐ™์€ ๋„๊ตฌ๋กœ ๋นŒ๋“œํ•˜๋ฉด tenant_id๊ฐ€ ํ‘œ์ค€ ํ•„๋“œ์ผ ๋•Œ ์ผ๊ด€๋œ ํŒจํ„ด์„ ์—ฌ๋Ÿฌ ๋ชจ๋“ˆ์—์„œ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹จ๊ณ„๋ณ„: ์ฒซ ๋ฒˆ์งธ ํ…Œ๋„ŒํŠธ ์ •์ฑ… ๋งŒ๋“ค๊ธฐ

๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™์„ ํ•œ๊ณณ์— ๋ชจ์œผ๊ธฐ
์—ญํ• , ์†Œ์œ ๊ถŒ, ์Šน์ธ ๊ทœ์น™์„ ์ค‘์•™ํ™”๋œ ๋ฐฑ์—”๋“œ ๋กœ์ง์œผ๋กœ ๋ฐ”๊ฟ” ๊ถŒํ•œ ๊ฒ€์‚ฌ๋ฅผ ๋ฐ˜๋ณตํ•˜์ง€ ๋งˆ์„ธ์š”.
ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

PostgreSQL RLS๋กœ ์–ป๋Š” ์ฒซ ๋ฒˆ์งธ ์‹ค์šฉ์  ์Šน๋ฆฌ๋Š”, ํŠน์ • ํ…Œ์ด๋ธ”์ด ํ˜„์žฌ ์„ธ์…˜์˜ ํ…Œ๋„ŒํŠธ ๋‚ด๋ถ€์—์„œ๋งŒ ์ฝํžˆ๋„๋ก ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์š”์ ์€ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค: API์—์„œ WHERE ์ ˆ์„ ์žŠ์–ด๋„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ๋‹ค๋ฅธ ํ…Œ๋„ŒํŠธ์˜ ํ–‰์„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋จผ์ € tenant_id ์—ด์ด ์žˆ๋Š” ํ…Œ์ด๋ธ”์—์„œ ์‹œ์ž‘ํ•˜์„ธ์š”:

ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;

RLS๋ฅผ ํ™œ์„ฑํ™”ํ•˜๋ฉด ๊ธฐ๋ณธ ๋™์ž‘์ด ์‚ฌ๋žŒ๋“ค์„ ๋†€๋ผ๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: ์—ญํ• ์ด RLS์˜ ๋Œ€์ƒ์ธ๋ฐ ์ผ์น˜ํ•˜๋Š” ์ •์ฑ…์ด ์—†์œผ๋ฉด SELECT๋Š” 0ํ–‰์„ ๋ฐ˜ํ™˜ํ•˜๊ณ (์“ฐ๊ธฐ ๋˜ํ•œ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค). ์ฒ˜์Œ์—๋Š” ์ด ์ƒํƒœ๊ฐ€ ๋ฐ”๋žŒ์งํ•ฉ๋‹ˆ๋‹ค.

์ด์ œ ์ตœ์†Œํ•œ์˜ ์ฝ๊ธฐ ์ •์ฑ…์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”. ์˜ˆ์ œ๋Š” ์•ฑ์ด ๋กœ๊ทธ์ธ ํ›„ app.tenant_id ๊ฐ™์€ ์„ธ์…˜ ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค:

CREATE POLICY invoices_tenant_read
ON invoices
FOR SELECT
USING (tenant_id = current_setting('app.tenant_id')::uuid);

๋‹ค์Œ์œผ๋กœ ์“ฐ๊ธฐ ๊ทœ์น™์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”. RLS์—์„œ USING์€ ๊ธฐ์กด ํ–‰์„ ๊ฑด๋“œ๋ฆด ์ˆ˜ ์žˆ๋Š”์ง€๋ฅผ ์ œ์–ดํ•˜๊ณ , WITH CHECK๋Š” ํ—ˆ์šฉ๋˜๋Š” ์ƒˆ ๊ฐ’์ธ์ง€ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค.

CREATE POLICY invoices_tenant_insert
ON invoices
FOR INSERT
WITH CHECK (tenant_id = current_setting('app.tenant_id')::uuid);

CREATE POLICY invoices_tenant_update
ON invoices
FOR UPDATE
USING (tenant_id = current_setting('app.tenant_id')::uuid)
WITH CHECK (tenant_id = current_setting('app.tenant_id')::uuid);

CREATE POLICY invoices_tenant_delete
ON invoices
FOR DELETE
USING (tenant_id = current_setting('app.tenant_id')::uuid);

์ •์ฑ…์€ ๊ธฐ๋ณธ์ ์œผ๋กœ PERMISSIVE์ž…๋‹ˆ๋‹ค. ์ฆ‰ ํ•œ ์ •์ฑ…๋งŒ ํ—ˆ์šฉํ•˜๋ฉด ์ ‘๊ทผ์ด ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ๊ทœ์น™์ด ๋ชจ๋‘ ํ†ต๊ณผํ•ด์•ผ ํ•  ๋•Œ๋Š” RESTRICTIVE๋ฅผ ์„ ํƒํ•˜์„ธ์š”(์˜ˆ: โ€œํ™œ์„ฑ ๊ณ„์ •์ธ ๊ฒฝ์šฐโ€ ๊ฐ™์€ ๋‘ ๋ฒˆ์งธ ๊ฐ€๋“œ ์ถ”๊ฐ€์— ์œ ์šฉ).

์ •์ฑ…์€ ์ž‘๊ณ  ์—ญํ•  ์ค‘์‹ฌ์œผ๋กœ ์œ ์ง€ํ•˜์„ธ์š”. ๋งŽ์€ OR์„ ๊ฐ€์ง„ ๊ฑฐ๋Œ€ํ•œ ๊ทœ์น™ ํ•˜๋‚˜ ๋Œ€์‹  ์ฒญ์ค‘๋ณ„๋กœ ๋ณ„๋„ ์ •์ฑ…์„ ๋งŒ๋“ค๋ฉด(์˜ˆ: invoices_tenant_read_app_user, invoices_tenant_read_support_agent) ํ…Œ์ŠคํŠธ์™€ ๋ฆฌ๋ทฐ๊ฐ€ ์‰ฌ์›Œ์ง€๊ณ  ๋‚˜์ค‘์— ์•ˆ์ „ํ•˜๊ฒŒ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ…Œ๋„ŒํŠธ์™€ ์‚ฌ์šฉ์ž ์ปจํ…์ŠคํŠธ ์•ˆ์ „ํ•˜๊ฒŒ ์ „๋‹ฌํ•˜๊ธฐ

RLS๊ฐ€ ์ž‘๋™ํ•˜๋ ค๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” โ€œ๋ˆ„๊ฐ€ ํ˜ธ์ถœํ–ˆ๋Š”๊ฐ€โ€์™€ โ€œ์–ด๋–ค ํ…Œ๋„ŒํŠธ์— ์†ํ•˜๋Š”๊ฐ€โ€๋ฅผ ์•Œ์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. RLS ์ •์ฑ…์€ ์ฟผ๋ฆฌ ์‹œ์ ์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ๊ฐ’๊ณผ๋งŒ ๋น„๊ตํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๊ทธ ์ปจํ…์ŠคํŠธ๋ฅผ ์„ธ์…˜์— ์ „๋‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ผ๋ฐ˜์ ์ธ ํŒจํ„ด์€ ์ธ์ฆ ํ›„ ์„ธ์…˜ ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜๊ณ  ์ •์ฑ…์—์„œ current_setting()์œผ๋กœ ์ฝ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์•ฑ์€ JWT ๊ฐ™์€ ๊ฒƒ์œผ๋กœ ์‹ ์›์„ ์ฆ๋ช…ํ•œ ๋’ค ํ•„์š”ํ•œ ํ•„๋“œ(tenant_id, user_id, role)๋งŒ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์— ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค.

-- ์š”์ฒญ๋‹น(ํ˜น์€ ํŠธ๋žœ์žญ์…˜๋‹น) ํ•œ ๋ฒˆ ์‹คํ–‰
SELECT set_config('app.tenant_id', '3f2a0c3e-9c7b-4d3f-9c5c-3c5e9c5d1a11', true);
SELECT set_config('app.user_id',   '8d9c6b1a-6b6d-4e32-9c0d-2bfe6f6c1111', true);
SELECT set_config('app.role',      'support_agent', true);

-- ์ •์ฑ…์—์„œ
-- tenant_id ์—ด์€ UUID
USING (tenant_id = current_setting('app.tenant_id', true)::uuid);

์„ธ ๋ฒˆ์งธ ์ธ์ˆ˜ true๋ฅผ ์“ฐ๋ฉด ํ˜„์žฌ ํŠธ๋žœ์žญ์…˜์— ๋กœ์ปฌํ•˜๊ฒŒ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. ์ปค๋„ฅ์…˜ ํ’€๋ง์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ์žฌ์‚ฌ์šฉ๋œ ์—ฐ๊ฒฐ์— ์–ด์ œ์˜ ํ…Œ๋„ŒํŠธ ์ปจํ…์ŠคํŠธ๊ฐ€ ๋‚จ์•„ ์žˆ์ง€ ์•Š๊ฒŒ ํ•˜๋ ค๋ฉด ์ด๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

JWT ํด๋ ˆ์ž„์—์„œ ์ปจํ…์ŠคํŠธ ์ฑ„์šฐ๊ธฐ

API๊ฐ€ JWT๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ํด๋ ˆ์ž„์„ ๊ณง์ด๊ณง๋Œ€๋กœ ์‹ ๋ขฐํ•˜์ง€ ๋ง๊ณ  ์ž…๋ ฅ์œผ๋กœ ์ทจ๊ธ‰ํ•˜์„ธ์š”. ํ† ํฐ ์„œ๋ช…๊ณผ ๋งŒ๋ฃŒ๋ฅผ ๊ฒ€์ฆํ•œ ๋’ค ํ•„์š”ํ•œ ํ•„๋“œ(tenant_id, user_id, role)๋งŒ ์„ธ์…˜ ์„ค์ •์œผ๋กœ ๋ณต์‚ฌํ•˜์„ธ์š”. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ง์ ‘ ํ—ค๋”๋‚˜ ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ด ๊ฐ’์„ ๋ณด๋‚ด๊ฒŒ ํ•˜์ง€ ๋งˆ์„ธ์š”.

์ปจํ…์ŠคํŠธ ๋ˆ„๋ฝ ๋˜๋Š” ์ž˜๋ชป๋œ ๊ฐ’: ๊ธฐ๋ณธ ๊ฑฐ๋ถ€

์„ค๊ณ„ํ•  ๋•Œ ์„ค์ • ๋ˆ„๋ฝ์ด 0ํ–‰์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ๋งŒ๋“œ์„ธ์š”.

current_setting('app.tenant_id', true)๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ˆ„๋ฝ ์‹œ NULL์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ ์ ˆํ•œ ํƒ€์ž…์œผ๋กœ ์บ์ŠคํŠธ(์˜ˆ: ::uuid)ํ•ด์„œ ํ˜•์‹์ด ์ž˜๋ชป๋˜๋ฉด ๋น ๋ฅด๊ฒŒ ์‹คํŒจํ•˜๊ฒŒ ํ•˜์„ธ์š”. ํ…Œ๋„ŒํŠธ/์‚ฌ์šฉ์ž ์ปจํ…์ŠคํŠธ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์—†์œผ๋ฉด ์ถ”์ •ํ•˜์ง€ ๋ง๊ณ  ์š”์ฒญ์„ ์‹คํŒจ์‹œํ‚ค์„ธ์š”.

์ด ๋ฐฉ์‹์€ ์ฟผ๋ฆฌ๊ฐ€ UI๋ฅผ ์šฐํšŒํ•˜๊ฑฐ๋‚˜ ์ƒˆ ์—”๋“œํฌ์ธํŠธ๊ฐ€ ์ถ”๊ฐ€๋œ ๊ฒฝ์šฐ์—๋„ ์ ‘๊ทผ ์ œ์–ด ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.

์œ ์ง€๋ณด์ˆ˜ํ•˜๊ธฐ ์‰ฌ์šด ์‹ค์šฉ์  ์—ญํ•  ํŒจํ„ด

UI์™€ ์ ‘๊ทผ์„ฑ ์ •๋ ฌ ์œ ์ง€
UI ์ ‘๊ทผ์„ ๋ฐฑ์—”๋“œ ๊ถŒํ•œ๊ณผ ์ผ์น˜์‹œํ‚ค๋Š” ์›น ๋ฐ ๋„ค์ดํ‹ฐ๋ธŒ ์•ฑ์„ ๋งŒ๋“œ์„ธ์š”.
์•ฑ ๋นŒ๋“œ

RLS ์ •์ฑ…์„ ์ฝ๊ธฐ ์‰ฝ๊ฒŒ ์œ ์ง€ํ•˜๋Š” ๊ฐ€์žฅ ์‰ฌ์šด ๋ฐฉ๋ฒ•์€ ์ •์ฒด(identity)์™€ ๊ถŒํ•œ(permissions)์„ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์€ users ํ…Œ์ด๋ธ”๊ณผ ์‚ฌ์šฉ์ž-ํ…Œ๋„ŒํŠธ-์—ญํ• ์„ ์—ฐ๊ฒฐํ•˜๋Š” memberships ํ…Œ์ด๋ธ”์„ ๋‘๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์ •์ฑ…์€ ํ•œ ๊ฐ€์ง€ ์งˆ๋ฌธ์— ๋‹ตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: โ€œํ˜„์žฌ ์‚ฌ์šฉ์ž๊ฐ€ ์ด ํ–‰์— ๋Œ€ํ•ด ์ ์ ˆํ•œ ๋ฉค๋ฒ„์‹ญ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”๊ฐ€?โ€

์—ญํ•  ์ด๋ฆ„์€ ์งํ•จ๋ณด๋‹ค ์‹ค์ œ ํ–‰๋™์— ์—ฐ๊ฒฐํ•˜์„ธ์š”. โ€œinvoice_viewerโ€๋‚˜ โ€œinvoice_approverโ€ ๊ฐ™์€ ์ด๋ฆ„์ด โ€œmanagerโ€๋ณด๋‹ค ์ •์ฑ…์„ ๋” ์˜ค๋ž˜ ๊ฒฌ๋””๊ณ  ๋ช…ํ™•ํ•ฉ๋‹ˆ๋‹ค.

์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์‰ฌ์šด ์—ญํ•  ํŒจํ„ด ๋ช‡ ๊ฐ€์ง€:

  • ์†Œ์œ ์ž ์ „์šฉ: ํ–‰์— created_by_user_id(๋˜๋Š” owner_user_id)๊ฐ€ ์žˆ๊ณ  ์ผ์น˜ ์—ฌ๋ถ€๋ฅผ ๊ฒ€์‚ฌ
  • ํŒ€ ์ „์šฉ: ํ–‰์— team_id๊ฐ€ ์žˆ๊ณ  ์ •์ฑ…์ด ๊ฐ™์€ ํ…Œ๋„ŒํŠธ ๋‚ด์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ๊ทธ ํŒ€์˜ ๋ฉค๋ฒ„์ธ์ง€ ๊ฒ€์‚ฌ
  • ์Šน์ธ๋œ ํ•ญ๋ชฉ๋งŒ: status = 'approved'์ผ ๋•Œ๋งŒ ์ฝ๊ธฐ ํ—ˆ์šฉ, ์“ฐ๊ธฐ๋Š” ์Šน์ธ์ž๋งŒ ํ—ˆ์šฉ
  • ํ˜ผํ•ฉ ๊ทœ์น™: ์ฒ˜์Œ์—” ์—„๊ฒฉํ•˜๊ฒŒ ์‹œ์ž‘ํ•˜๊ณ  ์ž‘์€ ์˜ˆ์™ธ๋ฅผ ์ถ”๊ฐ€(์˜ˆ: โ€œ์ง€์›ํŒ€์€ ์ฝ๊ธฐ ํ—ˆ์šฉ, ๋‹จ ํ…Œ๋„ŒํŠธ ๋ฒ”์œ„ ๋‚ดโ€)

ํฌ๋กœ์Šค-ํ…Œ๋„ŒํŠธ ๊ด€๋ฆฌ์ž(๋‹ค์ค‘ ํ…Œ๋„ŒํŠธ์— ์ ‘๊ทผํ•˜๋Š” ๊ด€๋ฆฌ์ž)๋Š” ๋งŽ์€ ํŒ€์ด ์‹ค์ˆ˜ํ•˜๋Š” ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ์ˆจ๊ฒจ์ง„ โ€œ์Šˆํผ์œ ์ €โ€ ์ง€๋ฆ„๊ธธ๋กœ ์ฒ˜๋ฆฌํ•˜์ง€ ๋ง๊ณ  ๋ช…์‹œ์ ์œผ๋กœ ๋‹ค๋ฃจ์„ธ์š”. platform_admin ๊ฐ™์€ ์ „์—ญ ๊ฐœ๋…์„ ๋งŒ๋“ค๊ณ  ์ •์ฑ…์—์„œ ์˜๋„์ ์œผ๋กœ ๊ฒ€์‚ฌํ•˜๊ฒŒ ํ•˜์„ธ์š”. ๊ฐ€๋Šฅํ•˜๋ฉด ํฌ๋กœ์Šค-ํ…Œ๋„ŒํŠธ ์ ‘๊ทผ์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ฝ๊ธฐ ์ „์šฉ์œผ๋กœ ๋‘๊ณ  ์“ฐ๊ธฐ๋Š” ๋” ๋†’์€ ํ—ˆ๋“ค์„ ์š”๊ตฌํ•˜์„ธ์š”.

๋ฌธ์„œํ™”๋Š” ์ƒ๊ฐ๋ณด๋‹ค ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ์ •์ฑ… ์œ„์— ์˜๋„๋ฅผ ์„ค๋ช…ํ•˜๋Š” ์งง์€ ์ฃผ์„์„ ๋‹ฌ์•„๋‘์„ธ์š”(์˜ˆ: โ€œApprovers๋Š” ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค. Viewers๋Š” ์Šน์ธ๋œ ์ธ๋ณด์ด์Šค๋งŒ ์ฝ์„ ์ˆ˜ ์žˆ๋‹ค.โ€). 6๊ฐœ์›” ํ›„ ๊ทธ ์งง์€ ์„ค๋ช…์ด ์ •์ฑ… ์ˆ˜์ • ์‹œ ํฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

AppMaster ๊ฐ™์€ ๋…ธ์ฝ”๋“œ ๋„๊ตฌ๋กœ ๋นŒ๋“œํ•ด๋„ ์ด ํŒจํ„ด์€ ์œ ํšจํ•ฉ๋‹ˆ๋‹ค. UI์™€ API๋Š” ๋น ๋ฅด๊ฒŒ ์›€์ง์—ฌ๋„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ทœ์น™์€ ๋ฉค๋ฒ„์‹ญ๊ณผ ๋ช…ํ™•ํ•œ ์—ญํ•  ์˜๋ฏธ์— ์˜์กดํ•ด ์•ˆ์ •์ ์œผ๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.

์˜ˆ์‹œ ์‹œ๋‚˜๋ฆฌ์˜ค: ์ธ๋ณด์ด์Šค์™€ ์ง€์›์„ ๊ฐ€์ง„ ๊ฐ„๋‹จํ•œ SaaS

ํ”„๋กœํ† ํƒ€์ž…์—์„œ ํ”„๋กœ๋•์…˜์œผ๋กœ ์ด๋™
์ค€๋น„๊ฐ€ ๋˜๋ฉด AppMaster Cloud ๋˜๋Š” ์ž์ฒด ํด๋ผ์šฐ๋“œ์— ๋ฐฐํฌํ•˜์„ธ์š”.
๋ฐฐํฌ

์ž‘์€ SaaS๋ฅผ ์ƒ์ƒํ•ด๋ณด์„ธ์š”. ์—ฌ๋Ÿฌ ํšŒ์‚ฌ๋ฅผ ์„œ๋น„์Šคํ•˜๊ณ  ๊ฐ ํšŒ์‚ฌ๋Š” ํ•˜๋‚˜์˜ ํ…Œ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค. ์•ฑ์—๋Š” ์ธ๋ณด์ด์Šค(๊ธˆ์œต)์™€ ์ง€์› ํ‹ฐ์ผ“(์ผ์ƒ ์ง€์›)์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๋Š” agent, manager, support์™€ ๊ฐ™์€ ์—ญํ• ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐ„๋‹จํ•œ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ: ๋ชจ๋“  ์ธ๋ณด์ด์Šค์™€ ํ‹ฐ์ผ“ ํ–‰์— tenant_id๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ํ‹ฐ์ผ“์—๋Š” assignee_user_id๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์•ฑ์€ ๋กœ๊ทธ์ธ ์งํ›„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ธ์…˜์— ํ˜„์žฌ ํ…Œ๋„ŒํŠธ์™€ ์‚ฌ์šฉ์ž๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

PostgreSQL RLS๊ฐ€ ์ผ์ƒ ์œ„ํ—˜์„ ์–ด๋–ป๊ฒŒ ๋ฐ”๊พธ๋Š”์ง€ ๋ณด์„ธ์š”.

Tenant A์˜ ์‚ฌ์šฉ์ž๊ฐ€ Tenant B์˜ ์ธ๋ณด์ด์Šค ID๋ฅผ ์ถ”์ธกํ•ด ๋ณด๊ฑฐ๋‚˜(๋˜๋Š” UI๊ฐ€ ์‹ค์ˆ˜๋กœ ๋ณด๋ƒˆ์„ ๋•Œ) ์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰๋˜๋”๋ผ๋„, ์ •์ฑ…์ด invoice.tenant_id = current_tenant_id๋ฅผ ์š”๊ตฌํ•˜๋ฏ€๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” 0ํ–‰์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. โ€œ์ ‘๊ทผ ๊ฑฐ๋ถ€โ€๊ฐ€ ์œ ์ถœ๋˜๋Š” ๋Œ€์‹  ๋นˆ ๊ฒฐ๊ณผ๊ฐ€ ์˜ต๋‹ˆ๋‹ค.

ํ•œ ํ…Œ๋„ŒํŠธ ๋‚ด๋ถ€์—์„œ๋Š” ์—ญํ• ์ด ์ ‘๊ทผ์„ ๋” ์ขํž™๋‹ˆ๋‹ค. ๋งค๋‹ˆ์ €๋Š” ํ…Œ๋„ŒํŠธ์˜ ๋ชจ๋“  ์ธ๋ณด์ด์Šค์™€ ํ‹ฐ์ผ“์„ ๋ณด๊ณ , ์—์ด์ „ํŠธ๋Š” ์ž์‹ ์—๊ฒŒ ํ• ๋‹น๋œ ํ‹ฐ์ผ“๊ณผ ์ž์‹ ์˜ ๋“œ๋ž˜ํ”„ํŠธ๋งŒ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•„ํ„ฐ๊ฐ€ ์„ ํƒ์ ์ผ ๋•Œ API์—์„œ ์—ฌ๊ธฐ์„œ ์‹ค์ˆ˜ํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค.

์ง€์›ํŒ€์€ ํŠน์ˆ˜ํ•œ ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค. ๊ณ ๊ฐ ์ง€์›์„ ์œ„ํ•ด ์ธ๋ณด์ด์Šค๋ฅผ ์กฐํšŒํ•  ์ˆ˜๋Š” ์žˆ์ง€๋งŒ amount, bank_account, tax_id ๊ฐ™์€ ๋ฏผ๊ฐํ•œ ํ•„๋“œ๋Š” ๋ฐ”๊ฟ€ ์ˆ˜ ์—†์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ˜„์‹ค์ ์ธ ํŒจํ„ด์€:

  • ์ง€์› ์—ญํ• ์— ๋Œ€ํ•ด ์ธ๋ณด์ด์Šค SELECT ํ—ˆ์šฉ(์—ฌ์ „ํžˆ ํ…Œ๋„ŒํŠธ ๋ฒ”์œ„)
  • UPDATE๋Š” โ€œ์•ˆ์ „ํ•œโ€ ๊ฒฝ๋กœ๋กœ๋งŒ ํ—ˆ์šฉ(์˜ˆ: ํŽธ์ง‘ ๊ฐ€๋Šฅํ•œ ์—ด๋งŒ ๋…ธ์ถœํ•˜๋Š” ๋ทฐ ๋˜๋Š” ๋ฏผ๊ฐ ํ•„๋“œ ๋ณ€๊ฒฝ์„ ๊ฑฐ๋ถ€ํ•˜๋Š” ์—„๊ฒฉํ•œ ์—…๋ฐ์ดํŠธ ์ •์ฑ…)

์ด์ œ โ€œ์‹ค์ˆ˜๋กœ ํ•„ํ„ฐ๋ฅผ ์ ์šฉํ•˜์ง€ ์•Š์€ APIโ€ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๋ณด์„ธ์š”. RLS๊ฐ€ ์—†์œผ๋ฉด ํฌ๋กœ์Šค-ํ…Œ๋„ŒํŠธ ์ธ๋ณด์ด์Šค๊ฐ€ ์œ ์ถœ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. RLS๊ฐ€ ์žˆ์œผ๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์„ธ์…˜ ํ…Œ๋„ŒํŠธ ์™ธ์˜ ํ–‰์„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์•„ ๋ฒ„๊ทธ๋Š” ํ™”๋ฉด ๊นจ์ง์œผ๋กœ ๋๋‚˜๊ณ  ๋ฐ์ดํ„ฐ ์œ ์ถœ๋กœ ์ด์–ด์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

AppMaster๋กœ ์ด๋Ÿฐ SaaS๋ฅผ ๋นŒ๋“œํ•˜๋”๋ผ๋„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๊ทœ์น™์„ ๋‘๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. UI ๊ฒ€์‚ฌ๋Š” ์œ ์šฉํ•˜์ง€๋งŒ, ๋ฌด์–ธ๊ฐ€ ๋น ์กŒ์„ ๋•Œ ๋ฒ„ํ‹ฐ๋Š” ๊ฒƒ์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ทœ์น™์ž…๋‹ˆ๋‹ค.

ํ”ํ•œ ์‹ค์ˆ˜์™€ ํšŒํ”ผ ๋ฐฉ๋ฒ•

RLS๋Š” ๊ฐ•๋ ฅํ•˜์ง€๋งŒ ์ž‘์€ ์‹ค์ˆ˜๊ฐ€ ๋ณด์•ˆ์ด โ€˜์•ˆ์ „ํ•œ ์ฒ™โ€™ ํ•˜๋Š” ์ƒํ™ฉ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฌธ์ œ๋Š” ๋ณดํ†ต ์ƒˆ ํ…Œ์ด๋ธ” ์ถ”๊ฐ€, ์—ญํ•  ๋ณ€๊ฒฝ, ์ž˜๋ชป๋œ DB ์‚ฌ์šฉ์ž๋กœ ํ…Œ์ŠคํŠธํ•  ๋•Œ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค.

์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ์‹คํŒจ๋Š” ์ƒˆ ํ…Œ์ด๋ธ”์—์„œ RLS๋ฅผ ํ™œ์„ฑํ™”ํ•˜๋Š” ๊ฒƒ์„ ์žŠ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ฃผ์š” ํ…Œ์ด๋ธ”์— ๋Œ€ํ•ด ์‹ ์ค‘ํžˆ ์ •์ฑ…์„ ์ž‘์„ฑํ•œ ๋’ค์— โ€œnotesโ€๋‚˜ โ€œattachmentsโ€ ๊ฐ™์€ ํ…Œ์ด๋ธ”์„ ์ถ”๊ฐ€ํ•˜๋ฉด์„œ ์ „์ฒด ์ ‘๊ทผ์„ ์—ด์–ด๋‘๊ณ  ๋ฐฐํฌํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทœ์น™์œผ๋กœ ๋งŒ๋“œ์„ธ์š”: ์ƒˆ ํ…Œ์ด๋ธ” = RLS ํ™œ์„ฑํ™” + ์ ์–ด๋„ ํ•˜๋‚˜์˜ ์ •์ฑ….

๋˜ ๋‹ค๋ฅธ ํ•จ์ •์€ ๋™์ž‘๋ณ„๋กœ ์ •์ฑ…์ด ์ผ์น˜ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. INSERT๋Š” ํ—ˆ์šฉํ•˜์ง€๋งŒ SELECT๋Š” ์ฐจ๋‹จํ•˜๋Š” ์ •์ฑ…์€ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•œ ๋’ค ๋ฐ”๋กœ โ€œ๋ฐ์ดํ„ฐ๊ฐ€ ์‚ฌ๋ผ์กŒ๋‹คโ€๋Š” ์‹์˜ ๋ฌธ์ œ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๋ฐ˜๋Œ€ ๊ฒฝ์šฐ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค: ์‚ฌ์šฉ์ž๋Š” ์ฝ์„ ์ˆ˜๋Š” ์žˆ์ง€๋งŒ ๋งŒ๋“ค ์ˆ˜ ์—†์–ด์„œ UI์—์„œ ์šฐํšŒํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ํ๋ฆ„์„ ์ƒ๊ฐํ•˜์„ธ์š”: โ€œ์ƒ์„ฑ ํ›„ ์กฐํšŒโ€, โ€œ์ˆ˜์ • ํ›„ ๋‹ค์‹œ ์—ด๊ธฐโ€, โ€œ์‚ญ์ œ ํ›„ ๋ชฉ๋ก ๋ณด๊ธฐโ€.

SECURITY DEFINER ํ•จ์ˆ˜๋ฅผ ์กฐ์‹ฌํ•˜์„ธ์š”. ํ•ด๋‹น ํ•จ์ˆ˜๋Š” ํ•จ์ˆ˜ ์†Œ์œ ์ž์˜ ๊ถŒํ•œ์œผ๋กœ ์‹คํ–‰๋˜์–ด RLS๋ฅผ ์šฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋ฉด ์ž‘๊ฒŒ ๋งŒ๋“ค๊ณ  ์ž…๋ ฅ์„ ๊ฒ€์ฆํ•˜๋ฉฐ ๋™์  SQL ์‚ฌ์šฉ์„ ํ”ผํ•˜์„ธ์š”.

๋˜ํ•œ ์•ฑ ์ธก ํ•„ํ„ฐ๋ง์—๋งŒ ์˜์กดํ•˜๋ฉด์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ ‘๊ทผ์„ ์—ด์–ด๋‘์ง€ ๋งˆ์„ธ์š”. ์ž˜ ์„ค๊ณ„๋œ API๋„ ์ƒˆ ์—”๋“œํฌ์ธํŠธ, ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์žก, ๊ด€๋ฆฌ์ž ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์ƒ๊ธฐ๋ฉด ํ™•์žฅ๋ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ญํ• ์ด ๋ชจ๋“  ๊ฒƒ์„ ์ฝ์„ ์ˆ˜ ์žˆ๋‹ค๋ฉด ์–ธ์  ๊ฐ€ ๋ฌธ์ œ๊ฐ€ ์ƒ๊น๋‹ˆ๋‹ค.

๋ฌธ์ œ๋ฅผ ์กฐ๊ธฐ์— ์žก์œผ๋ ค๋ฉด ์‹ค๋ฌด์ ์ธ ๊ฒ€์‚ฌ๋ฅผ ์œ ์ง€ํ•˜์„ธ์š”:

  • ๊ฐœ์ธ ๊ด€๋ฆฌ์ž ๊ณ„์ •์ด ์•„๋‹Œ ํ”„๋กœ๋•์…˜ ์•ฑ์ด ์‚ฌ์šฉํ•˜๋Š” ๋™์ผํ•œ DB ์—ญํ• ๋กœ ํ…Œ์ŠคํŠธ
  • ํ…Œ์ด๋ธ”๋‹น ํ•˜๋‚˜์˜ ์Œ์„ฑ ํ…Œ์ŠคํŠธ(๋‹ค๋ฅธ ํ…Œ๋„ŒํŠธ ์‚ฌ์šฉ์ž๊ฐ€ 0ํ–‰์„ ๋ณด์•„์•ผ ํ•จ)
  • ๊ฐ ํ…Œ์ด๋ธ”์ด ์˜ˆ์ƒํ•˜๋Š” ๋™์ž‘(SELECT, INSERT, UPDATE, DELETE)์„ ์ง€์›ํ•˜๋Š”์ง€ ํ™•์ธ
  • SECURITY DEFINER ์‚ฌ์šฉ ๊ฒ€ํ†  ๋ฐ ํ•„์š”์„ฑ ๋ฌธ์„œํ™”
  • ์ฝ”๋“œ ๋ฆฌ๋ทฐ์™€ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ฒดํฌ๋ฆฌ์ŠคํŠธ์— โ€œRLS ํ™œ์„ฑํ™”?โ€ ํ•ญ๋ชฉ ํฌํ•จ

์˜ˆ: ์ง€์› ์—์ด์ „ํŠธ๊ฐ€ ์ธ๋ณด์ด์Šค ๋ฉ”๋ชจ๋ฅผ ์ƒ์„ฑํ–ˆ๋Š”๋ฐ ๋ฐ”๋กœ ์ฝ์„ ์ˆ˜ ์—†๋‹ค๋ฉด ๋ณดํ†ต INSERT ์ •์ฑ…์€ ์žˆ์ง€๋งŒ SELECT ์ •์ฑ…์ด ์—†๊ฑฐ๋‚˜ ์„ธ์…˜์˜ ํ…Œ๋„ŒํŠธ ์ปจํ…์ŠคํŠธ๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

RLS ์„ค์ •์„ ๊ฒ€์ฆํ•˜๋Š” ๋น ๋ฅธ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

ํ…Œ๋„ŒํŠธ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ ํ‘œ์ค€ํ™”
Data Designer์—์„œ ํ•œ ๋ฒˆ๋งŒ tenant_id๋ฅผ ๋ชจ๋ธ๋งํ•˜๊ณ  ๋ชจ๋“  ํ…Œ์ด๋ธ”๊ณผ ๋ชจ๋“ˆ์—์„œ ์žฌ์‚ฌ์šฉํ•˜์„ธ์š”.
๋นŒ๋“œ ์‹œ์ž‘

RLS๋Š” ๊ฒ€ํ† ์ƒ์œผ๋กœ๋Š” ๋งž์•„ ๋ณด์ด์ง€๋งŒ ์‹ค์ œ ์‚ฌ์šฉ์—์„œ๋Š” ์‹คํŒจํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฒ€์ฆ์€ ์ •์ฑ…์„ ์ฝ๋Š” ๊ฒƒ๋ณด๋‹ค ํ˜„์‹ค์ ์ธ ๊ณ„์ •๊ณผ ์ฟผ๋ฆฌ๋กœ ๊นจ๋ณด๋Š” ๊ฒƒ์ด ๋” ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ์•ฑ์ด ์‚ฌ์šฉํ•  ๋ฐฉ์‹๋Œ€๋กœ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

๋จผ์ € ์ž‘์€ ํ…Œ์ŠคํŠธ ์‹ ์›์„ ๋งŒ๋“œ์„ธ์š”. ์ตœ์†Œ ๋‘ ๊ฐœ์˜ ํ…Œ๋„ŒํŠธ(Tenant A, Tenant B)๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ๊ฐ ํ…Œ๋„ŒํŠธ์— ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž์™€ ๊ด€๋ฆฌ์ž/๋งค๋‹ˆ์ € ์—ญํ• ์„ ํ•˜๋‚˜์”ฉ ๋งŒ๋“œ์„ธ์š”. โ€œ์ง€์› ์—์ด์ „ํŠธโ€๋‚˜ โ€œ์ฝ๊ธฐ ์ „์šฉโ€ ์—ญํ• ์„ ์ง€์›ํ•œ๋‹ค๋ฉด ๊ทธ ๊ณ„์ •๋„ ์ถ”๊ฐ€ํ•˜์„ธ์š”.

๊ทธ๋‹ค์Œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฐ˜๋ณต ๊ฐ€๋Šฅํ•œ ๊ฒ€์‚ฌ๋ฅผ ํ†ตํ•ด RLS๋ฅผ ์••๋ฐ• ํ…Œ์ŠคํŠธํ•˜์„ธ์š”:

  • ๊ฐ ์—ญํ• ์— ๋Œ€ํ•ด ํ•ต์‹ฌ ๋™์ž‘ ์‹คํ–‰: ๋ชฉ๋ก ์กฐํšŒ, ID๋กœ ๋‹จ์ผ ํ–‰ ์กฐํšŒ, ์‚ฝ์ž…, ์ˆ˜์ •, ์‚ญ์ œ. ๊ฐ ๋™์ž‘์— ๋Œ€ํ•ด ํ—ˆ์šฉ๋˜์–ด์•ผ ํ•  ๊ฒฝ์šฐ์™€ ์ฐจ๋‹จ๋˜์–ด์•ผ ํ•  ๊ฒฝ์šฐ๋ฅผ ๋ชจ๋‘ ์‹œ๋„.
  • ํ…Œ๋„ŒํŠธ ๊ฒฝ๊ณ„ ์ฆ๋ช…: Tenant A๋กœ์„œ Tenant B ๋ฐ์ดํ„ฐ์˜ ID๋กœ ์ฝ๊ธฐ/์ˆ˜์ •์„ ์‹œ๋„. ํ•ญ์ƒ 0ํ–‰ ๋˜๋Š” ๊ถŒํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋‚˜์™€์•ผ ํ•˜๋ฉฐ ์ผ๋ถ€ ํ–‰์ด ๋ณด์ด๋ฉด ์•ˆ ๋ฉ๋‹ˆ๋‹ค.
  • ์กฐ์ธ ๋ˆ„์ถœ ํ…Œ์ŠคํŠธ: ๋ณดํ˜ธ๋œ ํ…Œ์ด๋ธ”์„ ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”(์กฐํšŒ ํ…Œ์ด๋ธ” ํฌํ•จ)๊ณผ ์กฐ์ธํ•ด์„œ ์—ฐ๊ฒฐ๋œ ํ–‰์ด ์™ธ๋ถ€ ํ…Œ๋„ŒํŠธ์—์„œ ๋Œ๋ ค์˜ค์ง€ ์•Š๋Š”์ง€ ํ™•์ธ.
  • ์ปจํ…์ŠคํŠธ ๋ˆ„๋ฝ/์ž˜๋ชป๋œ ๊ฒฝ์šฐ ์ฐจ๋‹จ ํ™•์ธ: ์š”์ฒญ๋‹น ์„ค์ •ํ•˜๋Š” ํ…Œ๋„ŒํŠธ/์‚ฌ์šฉ์ž ์ปจํ…์ŠคํŠธ๋ฅผ ์ง€์šฐ๊ณ  ์žฌ์‹œ๋„. โ€œ์ปจํ…์ŠคํŠธ ์—†์Œโ€์€ ๋‹ซํž˜(fail closed)์ด์–ด์•ผ ํ•จ. ์ž˜๋ชป๋œ tenant id๋„ ์‹œ๋„.
  • ๊ธฐ๋ณธ ์„ฑ๋Šฅ ํ™•์ธ: ์ฟผ๋ฆฌ ํ”Œ๋žœ์„ ๋ณด๊ณ  ์ธ๋ฑ์Šค๊ฐ€ ํ…Œ๋„ŒํŠธ ํ•„ํ„ฐ ํŒจํ„ด(๋ณดํ†ต tenant_id์™€ ์ •๋ ฌ/๊ฒ€์ƒ‰ ๊ธฐ์ค€)์„ ์ง€์›ํ•˜๋Š”์ง€ ํ™•์ธ.

ํ…Œ์ŠคํŠธ์—์„œ ๋†€๋ผ์šด ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ค๋ฉด ์ •์ฑ…์ด๋‚˜ ์ปจํ…์ŠคํŠธ ์„ค์ •์„ ๋จผ์ € ๊ณ ์น˜์„ธ์š”. UI๋‚˜ API๋กœ ํŒจ์น˜ํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ทœ์น™์ด โ€œ๋Œ€์ฒด๋กœ ์ง€์ผœ์ง€๊ธธโ€ ๋ฐ”๋ผ๋Š” ์‹์œผ๋กœ ํ•ด๊ฒฐํ•˜์ง€ ๋งˆ์„ธ์š”.

๋‹ค์Œ ๋‹จ๊ณ„: ์•ˆ์ „ํ•˜๊ฒŒ ๋กค์•„์›ƒํ•˜๊ณ  ์ผ๊ด€์„ฑ ์œ ์ง€ํ•˜๊ธฐ

PostgreSQL RLS๋ฅผ ์•ˆ์ „์žฅ์น˜๋กœ ๋‹ค๋ฃจ์„ธ์š”: ์‹ ์ค‘ํ•˜๊ฒŒ ๋„์ž…ํ•˜๊ณ  ์ž์ฃผ ๊ฒ€์ฆํ•˜๋ฉฐ ํŒ€์ด ๋”ฐ๋ฅด๊ธฐ ์‰ฌ์šด ๋‹จ์ˆœํ•œ ๊ทœ์น™์„ ์œ ์ง€ํ•˜์„ธ์š”.

์ž‘๊ฒŒ ์‹œ์ž‘ํ•˜์„ธ์š”. ์œ ์ถœ์ด ๊ฐ€์žฅ ํฐ ํ”ผํ•ด๋ฅผ ์ฃผ๋Š” ํ…Œ์ด๋ธ”(๊ฒฐ์ œ, ์ธ๋ณด์ด์Šค, ๊ฐœ์ธ์ •๋ณด, ๊ณ ๊ฐ ๋ฉ”์‹œ์ง€)์— ์šฐ์„  RLS๋ฅผ ์ ์šฉํ•˜์„ธ์š”. ์ดˆ๋ฐ˜์˜ ์ž‘์€ ์„ฑ๊ณต์ด ๋ชจ๋‘์—๊ฒŒ ์ดํ•ด๋˜์ง€ ์•Š์€ ๋Œ€๊ทœ๋ชจ ๋กค์•„์›ƒ๋ณด๋‹ค ๋‚ซ์Šต๋‹ˆ๋‹ค.

์‹ค์šฉ์ ์ธ ๋กค์•„์›ƒ ์ˆœ์„œ๋Š” ๋ณดํ†ต ์ด๋ ‡์Šต๋‹ˆ๋‹ค:

  • ๋จผ์ € ํ•ต์‹ฌ ์†Œ์œ  ํ…Œ์ด๋ธ”(ํ–‰์ด ๋ช…ํ™•ํžˆ ํ•˜๋‚˜์˜ ํ…Œ๋„ŒํŠธ์— ์†ํ•˜๋Š” ํ…Œ์ด๋ธ”)
  • ๊ฐœ์ธ ๋ฐ์ดํ„ฐ(PII)๊ฐ€ ์žˆ๋Š” ํ…Œ์ด๋ธ”
  • ํ…Œ๋„ŒํŠธ๋กœ ํ•„ํ„ฐ๋ง๋˜๋Š” ๊ณต์œ  ํ…Œ์ด๋ธ”(๋ณด๊ณ ์„œ, ๋ถ„์„ ๋“ฑ)
  • ์กฐ์ธ ํ…Œ์ด๋ธ”๊ณผ ์—ฃ์ง€ ์ผ€์ด์Šค(๋‹ค๋Œ€๋‹ค ๊ด€๊ณ„)
  • ๊ธฐ๋ณธ์ด ์•ˆ์ •๋˜๋ฉด ๋‚˜๋จธ์ง€ ํ…Œ์ด๋ธ”

ํ…Œ์ŠคํŠธ๋ฅผ ํ•„์ˆ˜๋กœ ๋งŒ๋“œ์„ธ์š”. ์ž๋™ํ™”๋œ ํ…Œ์ŠคํŠธ๋Š” ์„œ๋กœ ๋‹ค๋ฅธ ํ…Œ๋„ŒํŠธ์™€ ์—ญํ• ๋กœ ๊ฐ™์€ ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. โ€œํ—ˆ์šฉํ•ด์•ผ ํ•จโ€๊ณผ โ€œ๊ฑฐ๋ถ€ํ•ด์•ผ ํ•จโ€ ๊ฒ€์‚ฌ๋ฅผ ๋ชจ๋‘ ํฌํ•จํ•˜์„ธ์š”. ๊ณผํ—ˆ์šฉ๋˜๋Š” ์‹ค์ˆ˜๋Š” ๋ฐœ๊ฒฌ์ด ๋Šฆ์„์ˆ˜๋ก ๋น„์šฉ์ด ํฝ๋‹ˆ๋‹ค.

์š”์ฒญ ํ๋ฆ„์—์„œ ์„ธ์…˜ ์ปจํ…์ŠคํŠธ๋ฅผ ์„ค์ •ํ•˜๋Š” ํ•œ ๊ณณ์„ ๋ช…ํ™•ํžˆ ํ•˜์„ธ์š”. tenant id, user id, role์€ ํ•œ ๋ฒˆ, ์ผ์ฐ ์ ์šฉํ•˜๊ณ  ๋‚˜์ค‘์— ์ถ”์ธกํ•˜์ง€ ๋งˆ์„ธ์š”. ํŠธ๋žœ์žญ์…˜ ์ค‘๊ฐ„์— ์ปจํ…์ŠคํŠธ๋ฅผ ์„ค์ •ํ•˜๋ฉด ๊ฒฐ๊ตญ ๋ˆ„๋ฝ๋˜๊ฑฐ๋‚˜ ์˜ค๋ž˜๋œ ๊ฐ’์œผ๋กœ ์ฟผ๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ƒ๊น๋‹ˆ๋‹ค.

AppMaster๋กœ ๋นŒ๋“œํ•  ๋•Œ๋Š” ์ƒ์„ฑ๋œ ๋ฐฑ์—”๋“œ API์™€ PostgreSQL ์ •์ฑ… ์‚ฌ์ด์˜ ์ผ๊ด€์„ฑ์„ ๊ณ„ํšํ•˜์„ธ์š”. ๋ชจ๋“  ์—”๋“œํฌ์ธํŠธ์—์„œ ๋™์ผํ•œ ์„ธ์…˜ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ํ‘œ์ค€ํ™”ํ•˜๋ฉด ์ •์ฑ…์ด ์–ด๋””์„œ๋‚˜ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. AppMaster๋ฅผ appmaster.io์—์„œ ์‚ฌ์šฉ ์ค‘์ด๋ผ๋ฉด UI์—์„œ๋„ ์ ‘๊ทผ์„ ์ œํ•œํ•˜๋”๋ผ๋„ RLS๋ฅผ ํ…Œ๋„ŒํŠธ ๊ฒฉ๋ฆฌ์— ๋Œ€ํ•œ ์ตœ์ข… ๊ถŒ์œ„๋กœ ๋‹ค๋ฃจ๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

๋งˆ์ง€๋ง‰์œผ๋กœ ์‹คํŒจ๋ฅผ ๊ด€์ฐฐํ•˜์„ธ์š”. ๊ถŒํ•œ ์‹คํŒจ๋Š” ๋กค์•„์›ƒ ์งํ›„ ํŠนํžˆ ์œ ์šฉํ•œ ์‹ ํ˜ธ์ž…๋‹ˆ๋‹ค. ๋ฐ˜๋ณต๋˜๋Š” ๊ฑฐ๋ถ€๋ฅผ ์ถ”์ ํ•˜๊ณ , ์‹ค์ œ ๊ณต๊ฒฉ์ธ์ง€, ํด๋ผ์ด์–ธํŠธ ํ”Œ๋กœ์šฐ๊ฐ€ ๊นจ์ง„ ๊ฒƒ์ธ์ง€, ์ •์ฑ…์ด ๋„ˆ๋ฌด ์—„๊ฒฉํ•œ ๊ฒƒ์ธ์ง€ ์กฐ์‚ฌํ•˜์„ธ์š”.

RLS๋ฅผ ๊ฑด๊ฐ•ํ•˜๊ฒŒ ์œ ์ง€ํ•˜๋Š” ์งง์€ ์Šต๊ด€ ๋ชฉ๋ก:

  • ๊ธฐ๋ณธ-๊ฑฐ๋ถ€ ์‚ฌ๊ณ ๋ฐฉ์‹๊ณผ ์˜๋„์ ์ธ ์˜ˆ์™ธ ์ถ”๊ฐ€
  • ๋ช…ํ™•ํ•œ ์ •์ฑ… ์ด๋ฆ„(table + action + audience)
  • ์ •์ฑ… ๋ณ€๊ฒฝ์€ ์ฝ”๋“œ ๋ณ€๊ฒฝ์ฒ˜๋Ÿผ ๋ฆฌ๋ทฐ
  • ๋กค์•„์›ƒ ์ดˆ๊ธฐ์— ๊ฑฐ๋ถ€ ๋กœ๊ทธ ์ˆ˜์ง‘ ๋ฐ ๊ฒ€ํ† 
  • RLS๊ฐ€ ์ ์šฉ๋œ ์ƒˆ ํ…Œ์ด๋ธ”๋งˆ๋‹ค ์ž‘์€ ํ…Œ์ŠคํŠธ ์„ธํŠธ ์ถ”๊ฐ€
์‰ฌ์šด ์‹œ์ž‘
๋ฉ‹์ง„๋งŒ๋“ค๊ธฐ

๋ฌด๋ฃŒ ์š”๊ธˆ์ œ๋กœ AppMaster๋ฅผ ์‚ฌ์šฉํ•ด ๋ณด์„ธ์š”.
์ค€๋น„๊ฐ€ ๋˜๋ฉด ์ ์ ˆํ•œ ๊ตฌ๋…์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‹œ์ž‘ํ•˜๋‹ค
๋ฉ€ํ‹ฐํ…Œ๋„ŒํŠธ ์•ฑ์„ ์œ„ํ•œ PostgreSQL ํ–‰ ์ˆ˜์ค€ ๋ณด์•ˆ ํŒจํ„ด | AppMaster