2025๋…„ 8์›” 29์ผยท4๋ถ„ ์ฝ๊ธฐ

PostgreSQL์—์„œ ํ•ด์‹œ ์—ฐ์‡„๋กœ ๋ณ€์กฐ ๊ฐ์ง€ ๊ฐ€๋Šฅํ•œ ๊ฐ์‚ฌ ์ถ”์  ๋งŒ๋“ค๊ธฐ

PostgreSQL์—์„œ ์ถ”๊ฐ€ ์ „์šฉ ํ…Œ์ด๋ธ”๊ณผ ํ•ด์‹œ ์—ฐ์‡„๋ฅผ ์‚ฌ์šฉํ•ด ๋ฆฌ๋ทฐ๋‚˜ ์กฐ์‚ฌ ์ค‘ ํŽธ์ง‘์ด ์‰ฝ๊ฒŒ ๊ฐ์ง€๋˜๋„๋ก ํ•˜๋Š” ๋ณ€์กฐ ๊ฐ์ง€ ๊ฐ์‚ฌ ๋กœ๊ทธ๋ฅผ ์•Œ์•„๋ณด์„ธ์š”.

PostgreSQL์—์„œ ํ•ด์‹œ ์—ฐ์‡„๋กœ ๋ณ€์กฐ ๊ฐ์ง€ ๊ฐ€๋Šฅํ•œ ๊ฐ์‚ฌ ์ถ”์  ๋งŒ๋“ค๊ธฐ

์ผ๋ฐ˜ ๊ฐ์‚ฌ ๋กœ๊ทธ๋Š” ์™œ ์‰ฝ๊ฒŒ ๋ฌธ์ œ์‹œ๋˜๋Š”๊ฐ€

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

๋งŽ์€ โ€œ๊ฐ์‚ฌ ๋กœ๊ทธโ€๋Š” ๋‹จ์ง€ ์ผ๋ฐ˜ ํ…Œ์ด๋ธ”์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. ํ–‰์„ ์—…๋ฐ์ดํŠธํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ๊ทธ ์‚ฌ๊ฑด์˜ ๊ธฐ๋ก๋„ ์—…๋ฐ์ดํŠธ๋˜๊ฑฐ๋‚˜ ์‚ญ์ œ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

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

"๋ณ€์กฐ ๊ฐ์ง€(tamper-evident)"๋ž€ ์ž‘์€ ์ˆ˜์ •(ํ•„๋“œ ํ•˜๋‚˜ ๋ณ€๊ฒฝ, ํ–‰ ํ•˜๋‚˜ ์ œ๊ฑฐ, ์ด๋ฒคํŠธ ์žฌ์ •๋ ฌ ๋“ฑ)๋„ ๋‚˜์ค‘์— ํƒ์ง€๋˜๋„๋ก ๊ฐ์‚ฌ ์ถ”์ ์„ ์„ค๊ณ„ํ•œ๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค. ๋งˆ๋ฒ•์„ ์•ฝ์†ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ๋ˆ„๊ตฐ๊ฐ€ โ€œ์ด ๋กœ๊ทธ๊ฐ€ ์ง„์งœ์ธ์ง€ ์–ด๋–ป๊ฒŒ ์•Œ์ฃ ?โ€๋ผ๊ณ  ๋ฌผ์„ ๋•Œ ๋กœ๊ทธ๊ฐ€ ๊ฑด๋“œ๋ ค์กŒ๋Š”์ง€ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๋Š” ๊ฒ€์‚ฌ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Œ์„ ์•ฝ์†ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋ฌด์—‡์„ ์ฆ๋ช…ํ•ด์•ผ ํ• ์ง€ ๊ฒฐ์ •ํ•˜๊ธฐ

๋ณ€์กฐ ๊ฐ์ง€ ๊ฐ์‚ฌ ์ถ”์ ์€ ๋‚˜์ค‘์— ๋งˆ์ฃผํ•  ์งˆ๋ฌธ์— ๋‹ตํ•  ์ˆ˜ ์žˆ์„ ๋•Œ๋งŒ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค: ๋ˆ„๊ฐ€ ๋ฌด์—‡์„ ํ–ˆ๋Š”๊ฐ€, ์–ธ์ œ ํ–ˆ๋Š”๊ฐ€, ๋ฌด์—‡์ด ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”๊ฐ€.

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

๋‹ค์Œ์œผ๋กœ ์–ด๋–ค ๊ฒƒ์ด ์ด๋ฒคํŠธ๋ฅผ ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“œ๋Š”์ง€ ๊ฒฐ์ •ํ•˜์„ธ์š”. ๊ฐ์‚ฌ์ž๋Š” ๋ณดํ†ต ํ–‰์œ„์ž(์œ ์ € ๋˜๋Š” ์„œ๋น„์Šค), ์„œ๋ฒ„ ์ธก ํƒ€์ž„์Šคํƒฌํ”„, ์ˆ˜ํ–‰๋œ ๋™์ž‘, ์˜ํ–ฅ์„ ๋ฐ›์€ ๊ฐ์ฒด๋ฅผ ๊ธฐ๋Œ€ํ•ฉ๋‹ˆ๋‹ค. ์—…๋ฐ์ดํŠธ์˜ ๊ฒฝ์šฐ์—๋Š” ๋ณ€๊ฒฝ ์ „/ํ›„ ๊ฐ’(๋˜๋Š” ์ ์–ด๋„ ๋ฏผ๊ฐํ•œ ํ•„๋“œ)์„ ์ €์žฅํ•˜๊ณ , ์—ฌ๋Ÿฌ ์ž‘์€ DB ๋ณ€๊ฒฝ์„ ํ•˜๋‚˜์˜ ์‚ฌ์šฉ์ž ํ–‰๋™์— ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๋„๋ก ์š”์ฒญ id ๋˜๋Š” ์ƒ๊ด€ id(request/correlation id)๋ฅผ ํ•จ๊ป˜ ์ €์žฅํ•˜์„ธ์š”.

๋งˆ์ง€๋ง‰์œผ๋กœ ์‹œ์Šคํ…œ์—์„œ "๋ถˆ๋ณ€(immutable)"์ด ๋ฌด์—‡์„ ์˜๋ฏธํ•˜๋Š”์ง€ ๋ช…ํ™•ํžˆ ํ•˜์„ธ์š”. ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๊ทœ์น™์€: ๊ฐ์‚ฌ ํ–‰์„ ์ ˆ๋Œ€ ์—…๋ฐ์ดํŠธํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•˜์ง€ ๋ง๊ณ , ์˜ค์ง INSERT๋งŒ ํ•˜๋ผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ž˜๋ชป๋œ ์ ์ด ์žˆ์œผ๋ฉด ๊ธฐ์กด์„ ๋ฎ์–ด์“ฐ์ง€ ๋ง๊ณ  ์ด๋ฅผ ๋ฐ”๋กœ์žก๋Š” ์ƒˆ ์ด๋ฒคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์›๋ณธ์€ ๊ทธ๋Œ€๋กœ ๋‚จ๊ฒจ๋‘์„ธ์š”.

์ถ”๊ฐ€ ์ „์šฉ(append-only) ๊ฐ์‚ฌ ํ…Œ์ด๋ธ” ๋งŒ๋“ค๊ธฐ

๊ฐ์‚ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ผ๋ฐ˜ ํ…Œ์ด๋ธ”๊ณผ ๋ถ„๋ฆฌํ•˜์„ธ์š”. ์ „์šฉ audit ์Šคํ‚ค๋งˆ๋Š” ์‹ค์ˆ˜๋กœ ์ˆ˜์ •ํ•˜๋Š” ์ผ์„ ์ค„์ด๊ณ  ๊ถŒํ•œ ๊ด€๋ฆฌ๋ฅผ ๋” ์‰ฝ๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

๋ชฉํ‘œ๋Š” ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค: ํ–‰์€ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ๋ณ€๊ฒฝ๋˜๊ฑฐ๋‚˜ ์ œ๊ฑฐ๋˜๋ฉด ์•ˆ ๋ฉ๋‹ˆ๋‹ค. PostgreSQL์—์„œ๋Š” ๊ถŒํ•œ(privileges)๊ณผ ํ…Œ์ด๋ธ” ์„ค๊ณ„์˜ ๋ช‡ ๊ฐ€์ง€ ์•ˆ์ „์žฅ์น˜๋กœ ์ด๋ฅผ ๊ฐ•์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ์€ ์‹ค์šฉ์ ์ธ ์‹œ์ž‘ ํ…Œ์ด๋ธ” ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค:

CREATE SCHEMA IF NOT EXISTS audit;

CREATE TABLE audit.events (
  id            bigserial PRIMARY KEY,
  entity_type   text        NOT NULL,
  entity_id     text        NOT NULL,
  event_type    text        NOT NULL CHECK (event_type IN ('INSERT','UPDATE','DELETE')),
  actor_id      text,
  occurred_at   timestamptz NOT NULL DEFAULT now(),
  request_id    text,
  before_data   jsonb,
  after_data    jsonb,
  notes         text
);

์กฐ์‚ฌ ์‹œ ํŠนํžˆ ์œ ์šฉํ•œ ๋ช‡๋ช‡ ํ•„๋“œ:

  • occurred_at์€ DEFAULT now()๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์‹œ๊ฐ„์„ ์ฐ๊ฒŒ ํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์•„๋‹Œ ์„œ๋ฒ„๊ฐ€ ์‹œ๊ฐ„์„ ๊ธฐ๋กํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  • entity_type๊ณผ entity_id๋Š” ํ•˜๋‚˜์˜ ๋ ˆ์ฝ”๋“œ๋ฅผ ๋ณ€๊ฒฝ ์ด๋ ฅ ์ „์ฒด์—์„œ ์ถ”์ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
  • request_id๋Š” ํ•˜๋‚˜์˜ ์‚ฌ์šฉ์ž ํ–‰๋™์ด ์—ฌ๋Ÿฌ ํ–‰์œผ๋กœ ๋‚˜๋‰œ ๊ฒฝ์šฐ ์ด๋ฅผ ์ถ”์ ํ•˜๋Š” ๋ฐ ๋„์›€๋ฉ๋‹ˆ๋‹ค.

์—ญํ• (role)๋กœ ์ž ๊ทธ์„ธ์š”. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์—ญํ• ์€ audit.events์— ๋Œ€ํ•ด INSERT์™€ SELECT๋Š” ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ UPDATE๋‚˜ DELETE๋Š” ๋ชปํ•˜๊ฒŒ ํ•˜์„ธ์š”. ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ๊ณผ ๋” ๊ฐ•๋ ฅํ•œ ๊ถŒํ•œ์€ ์•ฑ์—์„œ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ด€๋ฆฌ์ž ์—ญํ• ์—๋งŒ ๋‘์„ธ์š”.

ํŠธ๋ฆฌ๊ฑฐ๋กœ ๋ณ€๊ฒฝ ์บก์ฒ˜ํ•˜๊ธฐ(๊ฐ„๋‹จํ•˜๊ณ  ์˜ˆ์ธก ๊ฐ€๋Šฅํ•˜๊ฒŒ)

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

ํŠธ๋ฆฌ๊ฑฐ๋Š” ์ง€๋ฃจํ•˜๊ฒŒ ์œ ์ง€ํ•˜์„ธ์š”. ํŠธ๋ฆฌ๊ฑฐ์˜ ์ž„๋ฌด๋Š” ํ•˜๋‚˜๋ฟ์ž…๋‹ˆ๋‹ค: ์ค‘์š”ํ•œ ํ…Œ์ด๋ธ”์—์„œ INSERT, UPDATE, DELETE๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ๋งˆ๋‹ค ๊ฐ์‚ฌ ์ด๋ฒคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ.

์‹ค์šฉ์ ์ธ ๊ฐ์‚ฌ ๋ ˆ์ฝ”๋“œ๋Š” ๋ณดํ†ต ํ…Œ์ด๋ธ” ์ด๋ฆ„, ์ž‘์—… ์œ ํ˜•, ๊ธฐ๋ณธ ํ‚ค, ๋ณ€๊ฒฝ ์ „ํ›„ ๊ฐ’, ํƒ€์ž„์Šคํƒฌํ”„, ๊ทธ๋ฆฌ๊ณ  ๊ด€๋ จ ๋ณ€๊ฒฝ์„ ๊ทธ๋ฃนํ™”ํ•  ์ˆ˜ ์žˆ๋Š” ์‹๋ณ„์ž(ํŠธ๋žœ์žญ์…˜ id์™€ ์ƒ๊ด€ id)๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

์ƒ๊ด€ id๋Š” "20๊ฐœ ํ–‰์ด ์—…๋ฐ์ดํŠธ๋˜์—ˆ๋‹ค"์™€ "์ด๊ฑด ๋ฒ„ํŠผ ํด๋ฆญ ํ•˜๋‚˜์˜€๋‹ค"์˜ ์ฐจ์ด๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์š”์ฒญ ๋‹น ํ•œ ๋ฒˆ ์ƒ๊ด€ id๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๊ณ (์˜ˆ: DB ์„ธ์…˜ ์„ค์ •), ํŠธ๋ฆฌ๊ฑฐ๋Š” ์ด๋ฅผ ์ฝ์–ด ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ƒ๊ด€ id๊ฐ€ ์—†์„ ๋•Œ๋„ ๊ทธ๋ฃนํ™”ํ•  ์ˆ˜ ์žˆ๋„๋ก txid_current()๋ฅผ ์ €์žฅํ•˜์„ธ์š”.

๋‹ค์Œ์€ ๊ฐ์‚ฌ ํ…Œ์ด๋ธ”์—๋งŒ INSERTํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์˜ˆ์ธก ๊ฐ€๋Šฅ์„ฑ์„ ์œ ์ง€ํ•˜๋Š” ๊ฐ„๋‹จํ•œ ํŠธ๋ฆฌ๊ฑฐ ํŒจํ„ด์ž…๋‹ˆ๋‹ค(์Šคํ‚ค๋งˆ ์ด๋ฆ„์„ ๋งž๊ฒŒ ์กฐ์ •ํ•˜์„ธ์š”):

CREATE OR REPLACE FUNCTION audit_row_change() RETURNS trigger AS $$
DECLARE
  corr_id text;
BEGIN
  corr_id := current_setting('app.correlation_id', true);

  INSERT INTO audit_events(
    occurred_at, table_name, op, row_pk,
    old_row, new_row, db_user, txid, correlation_id
  ) VALUES (
    now(), TG_TABLE_NAME, TG_OP, COALESCE(NEW.id, OLD.id),
    to_jsonb(OLD), to_jsonb(NEW), current_user, txid_current(), corr_id
  );

  RETURN COALESCE(NEW, OLD);
END;
$$ LANGUAGE plpgsql;

ํŠธ๋ฆฌ๊ฑฐ์— ๋งŽ์€ ์ผ์„ ํ•˜๋ ค๋Š” ์œ ํ˜น์„ ์ฐธ์œผ์„ธ์š”. ์ถ”๊ฐ€ ์ฟผ๋ฆฌ, ๋„คํŠธ์›Œํฌ ํ˜ธ์ถœ, ๋ณต์žกํ•œ ๋ถ„๊ธฐ๋ฌธ์€ ํ”ผํ•˜์„ธ์š”. ์ž‘์€ ํŠธ๋ฆฌ๊ฑฐ๋Š” ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์‰ฝ๊ณ  ์‹คํ–‰ ์†๋„๊ฐ€ ๋น ๋ฅด๋ฉฐ ๊ฒ€ํ†  ์‹œ ๋…ผ์Ÿ์˜ ์—ฌ์ง€๊ฐ€ ์ ์Šต๋‹ˆ๋‹ค.

ํŽธ์ง‘์ด ํ”์ ์„ ๋‚จ๊ธฐ๋„๋ก ํ•ด์‹œ ์—ฐ์‡„ ์ถ”๊ฐ€ํ•˜๊ธฐ

์ค€์ˆ˜ ๊ฐ€๋Šฅํ•œ ๊ด€๋ฆฌ์ž ํŒจ๋„ ๋ฐฐํฌ
๊ถŒํ•œ ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ์ œ์–ด์™€ ์ค‘์š”ํ•œ ํ…Œ์ด๋ธ”์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ์ „์šฉ ์ด๋ฒคํŠธ ๊ธฐ๋ก์œผ๋กœ ์•ˆ์ „ํ•œ ๊ด€๋ฆฌ์ž ํŒจ๋„์„ ๊ตฌ์ถ•ํ•˜์„ธ์š”.
์•ฑ ๋งŒ๋“ค๊ธฐ

์ถ”๊ฐ€ ์ „์šฉ ํ…Œ์ด๋ธ”์€ ๋„์›€์ด ๋˜์ง€๋งŒ ์ถฉ๋ถ„ํ•œ ๊ถŒํ•œ์„ ๊ฐ€์ง„ ๋ˆ„๊ตฐ๊ฐ€๋Š” ์—ฌ์ „ํžˆ ๊ณผ๊ฑฐ ํ–‰์„ ๋‹ค์‹œ ์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•ด์‹œ ์—ฐ์‡„๋Š” ๊ทธ๋Ÿฐ ์ˆ˜์ •์„ ๋ˆˆ์— ๋„๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

๊ฐ ๊ฐ์‚ฌ ํ–‰์— ๋‘ ๊ฐœ์˜ ์ปฌ๋Ÿผ์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”: prev_hash์™€ row_hash(๋•Œ๋•Œ๋กœ chain_hash๋ผ๊ณ ๋„ ํ•จ). prev_hash๋Š” ๋™์ผ ์ฒด์ธ ๋‚ด ์ด์ „ ํ–‰์˜ ํ•ด์‹œ๋ฅผ ์ €์žฅํ•˜๊ณ , row_hash๋Š” ํ˜„์žฌ ํ–‰์˜ ๋ฐ์ดํ„ฐ์™€ prev_hash๋ฅผ ํฌํ•จํ•ด ๊ณ„์‚ฐํ•œ ํ˜„์žฌ ํ–‰์˜ ํ•ด์‹œ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

๋ฌด์—‡์„ ํ•ด์‹ฑํ•˜๋А๋ƒ๊ฐ€ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ๋™์ผํ•œ ํ–‰์ด ํ•ญ์ƒ ๊ฐ™์€ ํ•ด์‹œ๋ฅผ ๋งŒ๋“ค์–ด๋‚ด๋„๋ก ์•ˆ์ •์ ์ด๊ณ  ๋ฐ˜๋ณต ๊ฐ€๋Šฅํ•œ ์ž…๋ ฅ์„ ์›ํ•ฉ๋‹ˆ๋‹ค.

์‹ค์šฉ์ ์ธ ์ ‘๊ทผ์€ ๊ณ ์ •๋œ ์ปฌ๋Ÿผ(ํƒ€์ž„์Šคํƒฌํ”„, ํ–‰์œ„์ž, ๋™์ž‘, ์—”ํ‹ฐํ‹ฐ id)์œผ๋กœ ๊ตฌ์„ฑ๋œ ํ‘œ์ค€ ๋ฌธ์ž์—ด๊ณผ ์ผ๊ด€๋œ ์ง๋ ฌํ™”(์ข…์ข… jsonb) ๋ฐ prev_hash๋ฅผ ํ•ด์‹œ ์ž…๋ ฅ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ณต๋ฐฑ, JSON ํ‚ค ์ˆœ์„œ, ๋กœ์ผ€์ผ๋ณ„ ํฌ๋งท์ฒ˜๋Ÿผ ์˜๋ฏธ ์—†์ด ๋ฐ”๋€” ์ˆ˜ ์žˆ๋Š” ์„ธ๋ถ€์‚ฌํ•ญ์— ์ฃผ์˜ํ•˜์„ธ์š”. ํƒ€์ž…์„ ์ผ๊ด€๋˜๊ฒŒ ์œ ์ง€ํ•˜๊ณ  ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ๋ฐฉ์‹์œผ๋กœ ์ง๋ ฌํ™”ํ•˜์„ธ์š”.

์ „์ฒด DB๊ฐ€ ์•„๋‹Œ ์ŠคํŠธ๋ฆผ๋ณ„ ์ฒด์ธ

๋ชจ๋“  ๊ฐ์‚ฌ ์ด๋ฒคํŠธ๋ฅผ ํ•˜๋‚˜์˜ ๊ธ€๋กœ๋ฒŒ ์‹œํ€€์Šค๋กœ ์ฒด์ธํ•˜๋ฉด ์“ฐ๊ธฐ ๋ณ‘๋ชฉ์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŽ์€ ์‹œ์Šคํ…œ์€ ํ…Œ๋„ŒํŠธ๋ณ„, ์—”ํ‹ฐํ‹ฐ ํƒ€์ž…๋ณ„, ๋˜๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๊ฐ์ฒด๋ณ„ ๊ฐ™์€ "์ŠคํŠธ๋ฆผ" ๋‚ด์—์„œ ์ฒด์ธํ•ฉ๋‹ˆ๋‹ค.

๊ฐ ์ƒˆ๋กœ์šด ํ–‰์€ ์ž์‹ ์˜ ์ŠคํŠธ๋ฆผ์— ๋Œ€ํ•œ ์ตœ์‹  row_hash๋ฅผ ์กฐํšŒํ•ด prev_hash๋กœ ์ €์žฅํ•œ ๋‹ค์Œ ์ž์‹ ์˜ row_hash๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.

-- Requires pgcrypto
-- digest() returns bytea; store hashes as bytea
row_hash = digest(
  concat_ws('|',
    stream_key,
    occurred_at::text,
    actor_id::text,
    action,
    entity,
    entity_id::text,
    payload::jsonb::text,
    encode(prev_hash, 'hex')
  ),
  'sha256'
);

์ฒด์ธ ํ—ค๋“œ๋ฅผ ์Šค๋ƒ…์ƒท์œผ๋กœ ์ €์žฅํ•˜๊ธฐ

๊ฒ€ํ†  ์†๋„๋ฅผ ๋†’์ด๋ ค๋ฉด ์ŠคํŠธ๋ฆผ๋ณ„๋กœ ์ตœ์‹  row_hash(โ€œ์ฒด์ธ ํ—ค๋“œโ€)๋ฅผ ์ฃผ๊ธฐ์ ์œผ๋กœ ์ž‘์€ ์Šค๋ƒ…์ƒท ํ…Œ์ด๋ธ”์— ์ €์žฅํ•˜์„ธ์š”(์˜ˆ: ํ•˜๋ฃจ ๋‹จ์œ„). ์กฐ์‚ฌ ์‹œ ์ „์ฒด ์ด๋ ฅ์„ ํ•œ ๋ฒˆ์— ์Šค์บ”ํ•˜์ง€ ์•Š๊ณ ๋„ ๊ฐ ์Šค๋ƒ…์ƒท๊นŒ์ง€ ์ฒด์ธ์„ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์Šค๋ƒ…์ƒท์€ ๋‚ด๋ณด๋‚ธ ๊ฒƒ๋“ค์„ ๋น„๊ตํ•ด ์˜์‹ฌ์Šค๋Ÿฌ์šด ๊ฐ„๊ฒฉ์„ ์ฐพ๊ธฐ๋„ ์‰ฝ์Šต๋‹ˆ๋‹ค.

๋™์‹œ์„ฑ ๋ฐ ์ˆœ์„œ ๋ฌธ์ œ ์ฒ˜๋ฆฌ

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

๋จผ์ € ์ฒด์ธ์ด ๋ฌด์—‡์„ ๋‚˜ํƒ€๋‚ด๋Š”์ง€ ๊ฒฐ์ •ํ•˜์„ธ์š”. ํ•˜๋‚˜์˜ ๊ธ€๋กœ๋ฒŒ ์ฒด์ธ์€ ์„ค๋ช…ํ•˜๊ธฐ ์‰ฝ์ง€๋งŒ ๊ฒฝ์Ÿ์ด ๊ฐ€์žฅ ์‹ฌํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ์ฒด์ธ์€ ๊ฒฝ์Ÿ์„ ์ค„์ด์ง€๋งŒ ๊ฐ ์ฒด์ธ์ด ๋ฌด์—‡์„ ์ฆ๋ช…ํ•˜๋Š”์ง€ ๋ช…ํ™•ํžˆ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์–ด๋–ค ๋ชจ๋ธ์„ ์„ ํƒํ•˜๋“  ๋‹จ์กฐ ์ฆ๊ฐ€ํ•˜๋Š” ์ด๋ฒคํŠธ id(๋ณดํ†ต ์‹œํ€€์Šค๋กœ ์ง€์›๋˜๋Š” id)๋กœ ์—„๊ฒฉํ•œ ์ˆœ์„œ๋ฅผ ์ •์˜ํ•˜์„ธ์š”. ํƒ€์ž„์Šคํƒฌํ”„๋Š” ์ถฉ๋Œํ•  ์ˆ˜ ์žˆ๊ณ  ์กฐ์ž‘๋  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ถฉ๋ถ„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

prev_hash๋ฅผ ๊ณ„์‚ฐํ•  ๋•Œ์˜ ๋ ˆ์ด์Šค ์ปจ๋””์…˜์„ ํ”ผํ•˜๋ ค๋ฉด ๊ฐ ์ŠคํŠธ๋ฆผ์— ๋Œ€ํ•ด "๋งˆ์ง€๋ง‰ ํ•ด์‹œ ๊ฐ€์ ธ์˜ค๊ธฐ + ๋‹ค์Œ ํ–‰ ์‚ฝ์ž…"์„ ์ง๋ ฌํ™”ํ•˜์„ธ์š”. ์ผ๋ฐ˜์ ์ธ ๋ฐฉ๋ฒ•์€ ์ŠคํŠธ๋ฆผ ํ—ค๋“œ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๋‹จ์ผ ํ–‰์„ ์ž ๊ทธ๊ฑฐ๋‚˜ ์ŠคํŠธ๋ฆผ id๋กœ ์–ด๋“œ๋ฐ”์ด์ €๋ฆฌ ๋ฝ(advisory lock)์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ชฉํ‘œ๋Š” ๊ฐ™์€ ์ŠคํŠธ๋ฆผ์˜ ๋‘ ์ž‘์„ฑ์ž๊ฐ€ ๋™์‹œ์— ๊ฐ™์€ ๋งˆ์ง€๋ง‰ ํ•ด์‹œ๋ฅผ ์ฝ์ง€ ๋ชปํ•˜๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

ํŒŒํ‹ฐ์…”๋‹๊ณผ ์ƒค๋”ฉ์€ "๋งˆ์ง€๋ง‰ ํ–‰"์ด ์–ด๋””์— ์žˆ๋Š”์ง€์— ์˜ํ–ฅ์„ ์ค๋‹ˆ๋‹ค. ๊ฐ์‚ฌ ๋ฐ์ดํ„ฐ๋ฅผ ํŒŒํ‹ฐ์…”๋‹ํ•  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒํ•˜๋ฉด ์ŠคํŠธ๋ฆผ ํ‚ค์™€ ๋™์ผํ•œ ํŒŒํ‹ฐ์…˜ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•ด ๊ฐ ์ฒด์ธ์ด ํ•˜๋‚˜์˜ ํŒŒํ‹ฐ์…˜ ์•ˆ์— ์™„์ „ํžˆ ํฌํ•จ๋˜๋„๋ก ํ•˜์„ธ์š”(์˜ˆ: ํ…Œ๋„ŒํŠธ id). ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ…Œ๋„ŒํŠธ ์ฒด์ธ์€ ๋‚˜์ค‘์— ์„œ๋ฒ„ ๊ฐ„ ์ด๋™ํ•˜๋”๋ผ๋„ ๊ฒ€์ฆ ๊ฐ€๋Šฅํ•˜๊ฒŒ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.

์กฐ์‚ฌ ์ค‘ ์ฒด์ธ์„ ๊ฒ€์ฆํ•˜๋Š” ๋ฐฉ๋ฒ•

์ค‘์š” ์ž‘์—… ๊ฐ์‚ฌํ•˜๊ธฐ
๋กœ๊ทธ์ธ, ๊ถŒํ•œ ๋ณ€๊ฒฝ, ํ™˜๋ถˆ, ์ˆ˜๋™ ์˜ค๋ฒ„๋ผ์ด๋“œ ๊ฐ™์€ ์ค‘์š”ํ•œ ์ž‘์—…์„ ์‹œ๊ฐ์  ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์œผ๋กœ ๊ฐ์‚ฌ ์ด๋ฒคํŠธ์— ๊ธฐ๋กํ•˜์„ธ์š”.
์‹œ์ž‘ํ•˜๊ธฐ

ํ•ด์‹œ ์—ฐ์‡„๋Š” ๋ˆ„๊ตฐ๊ฐ€ ๋ฌผ์—ˆ์„ ๋•Œ ์ฒด์ธ์ด ์—ฌ์ „ํžˆ ์œ ํšจํ•จ์„ ์ฆ๋ช…ํ•  ์ˆ˜ ์žˆ์–ด์•ผ๋งŒ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค. ๊ฐ€์žฅ ์•ˆ์ „ํ•œ ๋ฐฉ๋ฒ•์€ ์ฝ๊ธฐ ์ „์šฉ ๊ฒ€์ฆ ์ฟผ๋ฆฌ(๋˜๋Š” ์ž‘์—…)๋ฅผ ์‹คํ–‰ํ•ด ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๋กœ๋ถ€ํ„ฐ ๊ฐ ํ–‰์˜ ํ•ด์‹œ๋ฅผ ์žฌ๊ณ„์‚ฐํ•˜๊ณ  ๊ธฐ๋ก๋œ ๊ฐ’๊ณผ ๋น„๊ตํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

on-demand๋กœ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ„๋‹จํ•œ ๊ฒ€์ฆ๊ธฐ

๊ฒ€์ฆ๊ธฐ๋Š” ๊ฐ ํ–‰์— ๋Œ€ํ•ด ์˜ˆ์ƒ ํ•ด์‹œ๋ฅผ ์žฌ๋นŒ๋“œํ•˜๊ณ , ๊ฐ ํ–‰์ด ์ด์ „ ํ–‰๊ณผ ์—ฐ๊ฒฐ๋˜๋Š”์ง€ ํ™•์ธํ•˜๋ฉฐ, ์ด์ƒํ•œ ์ ์„ ํ”Œ๋ž˜๊ทธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ์€ ์œˆ๋„์šฐ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ผ๋ฐ˜์ ์ธ ํŒจํ„ด์ž…๋‹ˆ๋‹ค. ์ปฌ๋Ÿผ ์ด๋ฆ„์„ ์ž์‹ ์˜ ํ…Œ์ด๋ธ”์— ๋งž๊ฒŒ ์กฐ์ •ํ•˜์„ธ์š”.

WITH ordered AS (
  SELECT
    id,
    created_at,
    actor_id,
    action,
    entity,
    entity_id,
    payload,
    prev_hash,
    row_hash,
    LAG(row_hash) OVER (ORDER BY created_at, id) AS expected_prev_hash,
    /* expected row hash, computed the same way as in your insert trigger */
    encode(
      digest(
        coalesce(prev_hash, '') || '|' ||
        id::text || '|' ||
        created_at::text || '|' ||
        coalesce(actor_id::text, '') || '|' ||
        action || '|' ||
        entity || '|' ||
        entity_id::text || '|' ||
        payload::text,
        'sha256'
      ),
      'hex'
    ) AS expected_row_hash
  FROM audit_log
)
SELECT
  id,
  created_at,
  CASE
    WHEN prev_hash IS DISTINCT FROM expected_prev_hash THEN 'BROKEN_LINK'
    WHEN row_hash IS DISTINCT FROM expected_row_hash THEN 'HASH_MISMATCH'
    ELSE 'OK'
  END AS status
FROM ordered
WHERE prev_hash IS DISTINCT FROM expected_prev_hash
   OR row_hash IS DISTINCT FROM expected_row_hash
ORDER BY created_at, id;

"๊นจ์กŒ๋Š”๊ฐ€ ์•„๋‹Œ๊ฐ€"๋ฅผ ๋„˜์–ด์„œ, ๋ฒ”์œ„ ๋‚ด ๋ˆ„๋ฝ๋œ id(๊ฐญ), ์ˆœ์„œ๊ฐ€ ๋’ค๋ฐ”๋€ ๋งํฌ, ์‹ค์ œ ์›Œํฌํ”Œ๋กœ์™€ ๋งž์ง€ ์•Š๋Š” ์˜์‹ฌ์Šค๋Ÿฌ์šด ์ค‘๋ณต ๋“ฑ์„ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

๊ฒ€์ฆ ๊ฒฐ๊ณผ๋ฅผ ๋ถˆ๋ณ€ ์ด๋ฒคํŠธ๋กœ ๊ธฐ๋กํ•˜๊ธฐ

์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ํ‹ฐ์ผ“์— ๋ฌป์–ด๋‘์ง€ ๋งˆ์„ธ์š”. ๊ฒ€์ฆ ๊ฒฐ๊ณผ๋Š” ๋ณ„๋„์˜ ์ถ”๊ฐ€ ์ „์šฉ ํ…Œ์ด๋ธ”(์˜ˆ: audit_verification_runs)์— ์‹คํ–‰ ์‹œ๊ฐ„, ๊ฒ€์ฆ๊ธฐ ๋ฒ„์ „, ๋ˆ„๊ฐ€ ํŠธ๋ฆฌ๊ฑฐํ–ˆ๋Š”์ง€, ์ ๊ฒ€ํ•œ ๋ฒ”์œ„, ๊นจ์ง„ ๋งํฌ์™€ ํ•ด์‹œ ๋ถˆ์ผ์น˜ ์ˆ˜ ๋“ฑ์„ ํฌํ•จํ•ด ์ €์žฅํ•˜์„ธ์š”.

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋‘ ๋ฒˆ์งธ ์ถ”์  ๊ธฐ๋ก์ด ์ƒ๊น๋‹ˆ๋‹ค: ๊ฐ์‚ฌ ๋กœ๊ทธ๊ฐ€ ์˜จ์ „ํ•  ๋ฟ ์•„๋‹ˆ๋ผ ์ •๊ธฐ์ ์œผ๋กœ ์ ๊ฒ€ํ•ด์™”๋‹ค๋Š” ์‚ฌ์‹ค์„ ์ฆ๋ช…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‹ค์šฉ์ ์ธ ์ฃผ๊ธฐ๋Š”: ๊ฐ์‚ฌ ๋กœ์ง์— ์˜ํ–ฅ์„ ์ฃผ๋Š” ๋ฐฐํฌ ํ›„, ํ™œ์„ฑ ์‹œ์Šคํ…œ์€ ๋งค์ผ, ๊ณ„ํš๋œ ๊ฐ์‚ฌ ์ „์—๋Š” ํ•ญ์ƒ ๊ฒ€์ฆ์„ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋ณ€์กฐ ๊ฐ์ง€๋ฅผ ๊นจ๋Š” ํ”ํ•œ ์‹ค์ˆ˜๋“ค

๊ฐ์‚ฌ๋ฅผ ์‹ค์ œ ์›Œํฌํ”Œ๋กœ๋กœ ์ „ํ™˜
์ค‘์š” ์ž‘์—…์„ ์ผ๊ด€๋˜๊ณ  ์กฐํšŒ ๊ฐ€๋Šฅํ•œ ๊ฐ์‚ฌ ๊ธฐ๋ก์œผ๋กœ ๋‚จ๊ธฐ๋Š” ๋‚ด๋ถ€ ๊ด€๋ฆฌ์ž ๋„๊ตฌ๋ฅผ ๊ตฌ์ถ•ํ•˜์„ธ์š”.
์‹œ์ž‘ํ•˜๊ธฐ

๋Œ€๋ถ€๋ถ„ ์‹คํŒจ๋Š” ํ•ด์‹œ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์ž์ฒด์˜ ๋ฌธ์ œ๊ฐ€ ์•„๋‹ˆ๋ผ ์˜ˆ์™ธ์™€ ๊ฐ„๊ฒฉ(gap) ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ๋ฐ˜๋ฐ•ํ•  ์—ฌ์ง€๋ฅผ ์ฃผ๋Š” ์˜ˆ์™ธ์™€ ๊ฐ„๊ฒฉ์ด ๋ฌธ์ œ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

์‹ ๋ขฐ๋ฅผ ์žƒ๋Š” ๊ฐ€์žฅ ๋น ๋ฅธ ๋ฐฉ๋ฒ•์€ ๊ฐ์‚ฌ ํ–‰์„ ์—…๋ฐ์ดํŠธํ•˜๋„๋ก ํ—ˆ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. "์ด๋ฒˆ ํ•œ ๋ฒˆ๋งŒ"์ด๋ผ๋„ ํ—ˆ์šฉํ•˜๋ฉด ์ „๋ก€์™€ ์‹ค์ œ๋กœ ์—ญ์‚ฌ๋ฅผ ๋‹ค์‹œ ์“ฐ๋Š” ๊ฒฝ๋กœ๋ฅผ ๋งŒ๋“  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ˆ˜์ •์ด ํ•„์š”ํ•˜๋ฉด ๊ธฐ์กด์„ ๋ฎ์–ด์“ฐ๊ธฐ๋ณด๋‹ค ์ˆ˜์ • ์‚ฌ์‹ค์„ ์„ค๋ช…ํ•˜๋Š” ์ƒˆ ์ด๋ฒคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ์›๋ณธ์„ ์œ ์ง€ํ•˜์„ธ์š”.

ํ•ด์‹œ ์—ฐ์‡„๋Š” ๋ถˆ์•ˆ์ •ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ํ•ด์‹ฑํ•  ๋•Œ๋„ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค. JSON์€ ํ”ํ•œ ํ•จ์ •์ž…๋‹ˆ๋‹ค. JSON ๋ฌธ์ž์—ด์„ ํ•ด์‹œํ•˜๋ฉด ํ‚ค ์ˆœ์„œ, ๊ณต๋ฐฑ, ์ˆซ์ž ํฌ๋งท์˜ ์‚ฌ์†Œํ•œ ์ฐจ์ด๋กœ ํ•ด์‹œ๊ฐ€ ๋‹ฌ๋ผ์ง€๊ณ  ๊ฒ€์ฆ์ด ์‹œ๋„๋Ÿฌ์›Œ์ง‘๋‹ˆ๋‹ค. ์ •๊ทœํ™”๋œ ํ˜•ํƒœ, jsonb, ๋˜๋Š” ๋‹ค๋ฅธ ์ผ๊ด€๋œ ์ง๋ ฌํ™”๋ฅผ ์„ ํ˜ธํ•˜์„ธ์š”.

๋ฐฉ์–ด ๊ฐ€๋Šฅํ•œ ์ถ”์ ์„ ์•ฝํ™”์‹œํ‚ค๋Š” ๋‹ค๋ฅธ ํŒจํ„ด๋“ค:

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

๊ถŒํ•œ ๋ถ„๋ฆฌ(separation of duties)๊ฐ€ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ๋™์ผํ•œ ์—ญํ• ์ด ๊ฐ์‚ฌ ์ด๋ฒคํŠธ๋ฅผ ์‚ฝ์ž…ํ•˜๋ฉด์„œ ๋™์‹œ์— ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ๋ณ€์กฐ ๊ฐ์ง€๋Š” ํ†ต์ œ์ฑ…์ด ์•„๋‹ˆ๋ผ ๋‹จ์ง€ ์•ฝ์†์— ๋ถˆ๊ณผํ•ด์ง‘๋‹ˆ๋‹ค.

๋ฐฉ์–ด ๊ฐ€๋Šฅํ•œ ๊ฐ์‚ฌ ์ถ”์  ์ฒดํฌ๋ฆฌ์ŠคํŠธ

๋ฐฉ์–ด ๊ฐ€๋Šฅํ•œ ๊ฐ์‚ฌ ์ถ”์ ์€ ๋ณ€๊ฒฝํ•˜๊ธฐ ์–ด๋ ต๊ณ  ๊ฒ€์ฆํ•˜๊ธฐ ์‰ฌ์›Œ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ถŒํ•œ ๊ด€๋ฆฌ๋กœ ์‹œ์ž‘ํ•˜์„ธ์š”: ๊ฐ์‚ฌ ํ…Œ์ด๋ธ”์€ ์‹ค์ œ๋กœ ์ถ”๊ฐ€ ์ „์šฉ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์—ญํ• ์€ ์‚ฝ์ž…(๋ฐ ๋ณดํ†ต์€ ์กฐํšŒ)๋งŒ ํ•  ์ˆ˜ ์žˆ๊ณ  ์—…๋ฐ์ดํŠธ๋‚˜ ์‚ญ์ œ๋Š” ๋ชป ํ•˜๊ฒŒ ํ•˜์„ธ์š”. ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ์€ ์—„๊ฒฉํžˆ ์ œํ•œํ•˜์„ธ์š”.

๊ฐ ํ–‰์ด ์กฐ์‚ฌ์ž๊ฐ€ ๋ฌผ์„ ์งˆ๋ฌธ์— ๋‹ตํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜์„ธ์š”: ๋ˆ„๊ฐ€ ํ–ˆ๋Š”๊ฐ€, ์–ธ์ œ(์„œ๋ฒ„ ์ธก) ํ–ˆ๋Š”๊ฐ€, ๋ฌด์Šจ ์ผ์ด ์žˆ์—ˆ๋Š”๊ฐ€(๋ช…ํ™•ํ•œ ์ด๋ฒคํŠธ ์ด๋ฆ„๊ณผ ๋™์ž‘), ๋ฌด์—‡์„ ๊ฑด๋“œ๋ ธ๋Š”๊ฐ€(์—”ํ‹ฐํ‹ฐ ์ด๋ฆ„๊ณผ id), ๊ทธ๋ฆฌ๊ณ  ์–ด๋–ป๊ฒŒ ์—ฐ๊ฒฐ๋˜๋Š”๊ฐ€(์š”์ฒญ/์ƒ๊ด€ id ๋ฐ ํŠธ๋žœ์žญ์…˜ id).

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

์šด์˜์ ์œผ๋กœ ๊ฒ€์ฆ์„ ์ผ๋ฐ˜ ์ž‘์—…์ฒ˜๋Ÿผ ์ทจ๊ธ‰ํ•˜์„ธ์š”:

  • ์˜ˆ์•ฝ๋œ ๋ฌด๊ฒฐ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ํ†ต๊ณผ/์‹คํŒจ ๊ฒฐ๊ณผ์™€ ๋ฒ”์œ„๋ฅผ ์ €์žฅํ•˜์„ธ์š”.
  • ๋ถˆ์ผ์น˜, ๊ฐญ, ๊นจ์ง„ ๋งํฌ์— ๋Œ€ํ•ด ๊ฒฝ๊ณ ๋ฅผ ์„ค์ •ํ•˜์„ธ์š”.
  • ๋ณด๊ด€ ๊ธฐ๊ฐ„(retention) ๋™์•ˆ ์ถฉ๋ถ„ํžˆ ๋ฐฑ์—…์„ ๋ณด๊ด€ํ•˜๊ณ  ๋ณด๊ด€ ์ •์ฑ…์„ ์ž ๊ฐ€ ๊ฐ์‚ฌ ๊ธฐ๋ก์ด ์กฐ๊ธฐ์— "์ •๋ฆฌ"๋˜์ง€ ์•Š๊ฒŒ ํ•˜์„ธ์š”.

์˜ˆ์‹œ: ์ปดํ”Œ๋ผ์ด์–ธ์Šค ๊ฒ€ํ† ์—์„œ ์˜์‹ฌ์Šค๋Ÿฌ์šด ์ˆ˜์ •์„ ์ฐพ๊ธฐ

๊ฐ์‚ฌ ์ด๋ฒคํŠธ ์ผ๊ด€์„ฑ ์œ ์ง€
๊ธฐ๋Šฅ ์ „๋ฐ˜์— ๊ฑธ์ณ ์ด๋ฒคํŠธ ์ด๋ฆ„๊ณผ ํŽ˜์ด๋กœ๋“œ๋ฅผ ํ‘œ์ค€ํ™”ํ•ด ์‹œ๊ฐ„์ด ์ง€๋‚˜๋„ ๋ฐฉ์–ด ๊ฐ€๋Šฅํ•œ ๊ฐ์‚ฌ ์ถ”์ ์„ ์œ ์ง€ํ•˜์„ธ์š”.
๋ฌด๋ฃŒ๋กœ ์‹œ์ž‘

์ž์ฃผ ๋‚˜์˜ค๋Š” ํ…Œ์ŠคํŠธ ์‚ฌ๋ก€๋Š” ํ™˜๋ถˆ ๋ถ„์Ÿ์ž…๋‹ˆ๋‹ค. ๊ณ ๊ฐ์ด $250 ํ™˜๋ถˆ์ด ์Šน์ธ๋˜์—ˆ๋‹ค๊ณ  ์ฃผ์žฅํ•˜๋Š”๋ฐ ์‹œ์Šคํ…œ์—๋Š” ์ด์ œ $25๋กœ ๋ณด์ธ๋‹ค๊ณ  ํ•ฉ์‹œ๋‹ค. ์ง€์›ํŒ€์€ ์Šน์ธ ๋‚ด์šฉ์ด ๋งž๋‹ค๊ณ  ์ฃผ์žฅํ•˜๊ณ  ์ปดํ”Œ๋ผ์ด์–ธ์Šค๋Š” ๋‹ต์„ ์›ํ•ฉ๋‹ˆ๋‹ค.

์ƒ๊ด€ id(์ฃผ๋ฌธ id, ํ‹ฐ์ผ“ id, ๋˜๋Š” refund_request_id)์™€ ์‹œ๊ฐ„ ๋ฒ”์œ„๋ฅผ ์‚ฌ์šฉํ•ด ๊ฒ€์ƒ‰ ๋ฒ”์œ„๋ฅผ ์ขํžˆ์„ธ์š”. ํ•ด๋‹น ์ƒ๊ด€ id์— ๋Œ€ํ•œ ๊ฐ์‚ฌ ํ–‰์„ ๊ฐ€์ ธ์™€ ์Šน์ธ ์‹œ๊ฐ„ ์ฃผ๋ณ€์„ ๋ธŒ๋ž˜ํ‚ทํ•˜์„ธ์š”.

์›ํ•˜๋Š” ๊ฒƒ์€ ์ „์ฒด ์ด๋ฒคํŠธ ์ง‘ํ•ฉ์ž…๋‹ˆ๋‹ค: ์š”์ฒญ ์ƒ์„ฑ, ํ™˜๋ถˆ ์Šน์ธ, ํ™˜๋ถˆ ๊ธˆ์•ก ์„ค์ •, ๊ทธ๋ฆฌ๊ณ  ์ดํ›„์˜ ์—…๋ฐ์ดํŠธ๋“ค. ๋ณ€์กฐ ๊ฐ์ง€ ์„ค๊ณ„๊ฐ€ ๋˜์–ด ์žˆ๋‹ค๋ฉด ์‹œํ€€์Šค๊ฐ€ ์˜จ์ „ํ–ˆ๋Š”์ง€๋„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

๊ฐ„๋‹จํ•œ ์กฐ์‚ฌ ํ๋ฆ„:

  • ์ƒ๊ด€ id์— ๋Œ€ํ•œ ๋ชจ๋“  ๊ฐ์‚ฌ ํ–‰์„ ์‹œ๊ฐ„ ์ˆœ์œผ๋กœ ๋‹น๊ฒจ์˜ต๋‹ˆ๋‹ค.
  • ์ €์žฅ๋œ ํ•„๋“œ(๋ฐ prev_hash)์—์„œ ๊ฐ ํ–‰์˜ ํ•ด์‹œ๋ฅผ ์žฌ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
  • ๊ณ„์‚ฐํ•œ ํ•ด์‹œ์™€ ์ €์žฅ๋œ ํ•ด์‹œ๋ฅผ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค.
  • ์ฒซ ๋ฒˆ์งธ๋กœ ๋‹ค๋ฅธ ํ–‰์„ ์‹๋ณ„ํ•˜๊ณ  ์ดํ›„ ํ–‰๋“ค๋„ ์‹คํŒจํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ๋‹จ์ผ ๊ฐ์‚ฌ ํ–‰์„ ํŽธ์ง‘(์˜ˆ: ๊ธˆ์•ก์„ 250์—์„œ 25๋กœ ๋ณ€๊ฒฝ)ํ–ˆ๋‹ค๋ฉด ๊ทธ ํ–‰์˜ ํ•ด์‹œ๋Š” ๋” ์ด์ƒ ๋งž์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋‹ค์Œ ํ–‰์€ ์ด์ „ ํ•ด์‹œ๋ฅผ ํฌํ•จํ•˜๋ฏ€๋กœ ๋ถˆ์ผ์น˜๋Š” ๋ณดํ†ต ์•ž์œผ๋กœ ์ „ํŒŒ๋ฉ๋‹ˆ๋‹ค. ์ด ์ „ํŒŒ(cascade)๊ฐ€ ์ง€๋ฌธ์ž…๋‹ˆ๋‹ค: ์‚ฌํ›„์— ๊ฐ์‚ฌ ๊ธฐ๋ก์ด ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค๋Š” ๊ฒƒ์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

์ฒด์ธ์ด ์•Œ๋ ค์ค„ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ: ์ˆ˜์ •์ด ๋ฐœ์ƒํ–ˆ๋Š”์ง€, ์ฒด์ธ์ด ์ฒ˜์Œ ๋Š๊ธด ์œ„์น˜, ์˜ํ–ฅ์„ ๋ฐ›์€ ํ–‰์˜ ๋ฒ”์œ„. ์ฒด์ธ๋งŒ์œผ๋กœ ์•Œ๋ ค์ฃผ์ง€ ๋ชปํ•˜๋Š” ๊ฒƒ: ๋ˆ„๊ฐ€ ์ˆ˜์ •ํ–ˆ๋Š”์ง€, ๋ฎ์–ด์จ์ง„ ๊ฒฝ์šฐ ์›๋ž˜ ๊ฐ’์ด ๋ฌด์—‡์ด์—ˆ๋Š”์ง€, ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”๋“ค๋„ ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ์—ฌ๋ถ€ ๋“ฑ์ž…๋‹ˆ๋‹ค.

๋‹ค์Œ ๋‹จ๊ณ„: ์•ˆ์ „ํ•˜๊ฒŒ ์ ์šฉํ•˜๊ณ  ์œ ์ง€ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ค๊ธฐ

๊ฐ์‚ฌ ์ถ”์ ์„ ๋‹ค๋ฅธ ๋ณด์•ˆ ์ œ์–ด์ฒ˜๋Ÿผ ๋‹ค๋ฃจ์„ธ์š”. ์ž‘์€ ๋‹จ๊ณ„๋กœ ๋กค์•„์›ƒํ•˜๊ณ  ์ž‘๋™์„ ์ฆ๋ช…ํ•œ ๋’ค ํ™•๋Œ€ํ•˜์„ธ์š”.

๋…ผ์Ÿ์ด ์ƒ๊ธฐ๋ฉด ๊ฐ€์žฅ ํฐ ํ”ผํ•ด๋ฅผ ์ž…์„ ์•ก์…˜(๊ถŒํ•œ ๋ณ€๊ฒฝ, ์ง€๊ธ‰, ํ™˜๋ถˆ, ๋ฐ์ดํ„ฐ ๋‚ด๋ณด๋‚ด๊ธฐ, ์ˆ˜๋™ ์˜ค๋ฒ„๋ผ์ด๋“œ)๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜์„ธ์š”. ๊ทธ๊ฑธ ์ ์šฉํ•œ ํ›„ ํ•ต์‹ฌ ์„ค๊ณ„๋ฅผ ๋ฐ”๊พธ์ง€ ์•Š๊ณ  ์œ„ํ—˜์ด ๋‚ฎ์€ ์ด๋ฒคํŠธ๋“ค์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”.

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

๋ณต์› ์—ฐ์Šต(restore drills)์€ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ์กฐ์‚ฌ๋Š” ์ข…์ข… ๋ผ์ด๋ธŒ ์‹œ์Šคํ…œ์ด ์•„๋‹Œ ๋ฐฑ์—…์—์„œ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค. ์ •๊ธฐ์ ์œผ๋กœ ํ…Œ์ŠคํŠธ DB๋กœ ๋ณต์›ํ•ด ์ฒด์ธ ๋์—์„œ ๋๊นŒ์ง€ ๊ฒ€์ฆํ•˜์„ธ์š”. ๋ณต์› ํ›„ ๋™์ผํ•œ ๊ฒ€์ฆ ๊ฒฐ๊ณผ๋ฅผ ์žฌํ˜„ํ•  ์ˆ˜ ์—†๋‹ค๋ฉด ๋ณ€์กฐ ๊ฐ์ง€๋ฅผ ๋ฐฉ์–ดํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.

๋‚ด๋ถ€ ๋„๊ตฌ์™€ ๊ด€๋ฆฌ์ž ์›Œํฌํ”Œ๋กœ๋ฅผ AppMaster (appmaster.io)๋กœ ๋นŒ๋“œํ•˜๋Š” ๊ฒฝ์šฐ, ์„œ๋ฒ„ ์ธก์—์„œ ๊ฐ์‚ฌ ์ด๋ฒคํŠธ ์“ฐ๊ธฐ๋ฅผ ํ‘œ์ค€ํ™”ํ•˜๋ฉด ์ด๋ฒคํŠธ ์Šคํ‚ค๋งˆ์™€ ์ƒ๊ด€ id๊ฐ€ ๊ธฐ๋Šฅ ์ „๋ฐ˜์—์„œ ์ผ๊ด€๋˜๊ฒŒ ์œ ์ง€๋˜์–ด ๊ฒ€์ฆ๊ณผ ์กฐ์‚ฌ๊ฐ€ ํ›จ์”ฌ ๊ฐ„๋‹จํ•ด์ง‘๋‹ˆ๋‹ค.

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

์‰ฌ์šด ์‹œ์ž‘
๋ฉ‹์ง„๋งŒ๋“ค๊ธฐ

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

์‹œ์ž‘ํ•˜๋‹ค
PostgreSQL์—์„œ ํ•ด์‹œ ์—ฐ์‡„๋กœ ๋ณ€์กฐ ๊ฐ์ง€ ๊ฐ€๋Šฅํ•œ ๊ฐ์‚ฌ ์ถ”์  ๋งŒ๋“ค๊ธฐ | AppMaster