TL;DR
- ํธ๋์ญ์ ์์ ์์ ์ด ํด๋ผ์ด์ธํธ๋ง๋ค ๋ฌ๋ผ autocommit ๊ธฐ๋ณธ๊ฐ ํ์ธ์ด ๋จผ์
- ๊ฒฝ๊ณ ๊ธฐ์ค์ ํ๋. ํจ๊ป ์คํจํด์ผ ํ๋ ๊ฒ๋ง ๋ฌถ๊ณ ์ธ๋ถ ํธ์ถ์ ๋ฐ์ผ๋ก ๋บ
- ์ํ์ค์ ์ธ๋ถ ๋ถ์ ํจ๊ณผ๋ ๋กค๋ฐฑ์ ๋ฐ๋ฅด์ง ์์ผ๋ฏ๋ก ์ฌ์๋ ์ค๊ณ๊ฐ ๋ณ๋๋ก ํ์
AI-assisted
1. ํธ๋์ญ์ ์ ์ธ์ ์์๋๋๊ฐ
ํธ๋์ญ์ ์ ์ธ ๋ ์ฒ์ ๊ฑธ๋ฆฌ๋ ๊ณณ์ ๋ด๊ฐ ์ง๊ธ ํธ๋์ญ์ ์์ ์๋์ง ๋ชจ๋ฅธ๋ค๋ ์ ์ด๋ค.
BEGIN์ ์ ์ณค๋๋ฐ ์ด๋ ค ์๊ธฐ๋ ํ๊ณ ์ณค๋ค๊ณ ์๊ฐํ๋๋ฐ ์ ์ด๋ ค ์๊ธฐ๋ ํ๋ค. ํด๋ผ์ด์ธํธ๋ง๋ค ๊ธฐ๋ณธ๊ฐ์ด ๋ค๋ฅด๊ธฐ ๋๋ฌธ์ด๋ค.
| ํด๋ผ์ด์ธํธ | autocommit ๊ธฐ๋ณธ | ํธ๋์ญ์ ์ด ์ด๋ฆฌ๋ ์์ |
|---|---|---|
| psql | on | BEGIN์ด๋ START TRANSACTION์ ์ง์ ์น ๋ |
| psycopg3 | off | ์ฒซ ์ฟผ๋ฆฌ๋ฅผ ๋ณด๋ผ ๋ ์๋์ผ๋ก. SELECT ํ๋๋ก๋ ์ด๋ฆฐ๋ค |
| JDBC | on | setAutoCommit(false) ์ดํ ์ฒซ ์ฟผ๋ฆฌ |
| SQLAlchemy 2.0 | off (autobegin) | ์ฒซ execute() ํธ์ถ ๋ ์๋์ผ๋ก ์ด๋ฆฐ๋ค |
| Django ORM | on | ๊ฐ์ฅ ๋ฐ๊นฅ atomic() ๋ธ๋ก์ ๋ค์ด๊ฐ ๋ |
autocommit์ด ์ผ์ ธ ์์ผ๋ฉด ๋ฌธ์ฅ ํ๋๊ฐ ๊ณง ํธ๋์ญ์
ํ๋๋ค. UPDATE ๋ ์ค์ ๋๋ํ ์คํํ๋ฉด ํธ๋์ญ์
๋ ๊ฐ๋ก ๋๋์ด ์ค๊ฐ์ ๋จ์ด ๋ผ์ด๋ค ์ ์๋ค.
๋๋ฉด ๋ฐ๋๊ฐ ๋๋ค. psycopg3์ SQLAlchemy 2.0์ BEGIN์ ์ ์จ๋ ์ฒซ ๋ฌธ์ฅ์์ ํธ๋์ญ์
์ ์ด๊ณ ๋ช
์์ ์ผ๋ก ๋ซ๊ธฐ ์ ๊น์ง ์ ์งํ๋ค.
psycopg ๋ฌธ์์์ ๋ค์๊ณผ ๊ฐ์ด ๋ช
์ํ๊ณ ์๋ค.
By default even a simple
SELECTwill start a transaction: in long-running programs, if no further action is taken, the session will remain idle in transaction
์กฐํ๋ง ํ๋ ์ฝ๋๋ ์ปค๋ฐํ์ง ์์ผ๋ฉด idle in transaction์ผ๋ก ๋จ๋๋ค.
์ง๊ธ ์ํ๋ ์๋ฒ์์ ํ์ธํ๋ค.
SELECT pid, state, xact_start, query
FROM pg_stat_activity WHERE pid = pg_backend_pid();state๊ฐidle์ด๋ฉด ํธ๋์ญ์ ๋ฐidle in transaction์ด๋ฉด ์ด์ด๋๊ณ ์๋ฌด๊ฒ๋ ์ ํ๋ ์คxact_start๊ฐ ์ฑ์์ ธ ์์ผ๋ฉด ๊ทธ ์๊ฐ๋ถํฐ ํธ๋์ญ์ ์ด ์ด๋ ค ์๋ค
์ฃผ์: ๊ฐ์ ์ฝ๋๊ฐ ํ๊ฒฝ๋ง๋ค ๋ค๋ฅด๊ฒ ๋๋ค
- ๋ก์ปฌ์์ psql๋ก ๊ฒ์ฆํ SQL์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฎ๊ธฐ๋ฉด ํธ๋์ญ์ ๊ฒฝ๊ณ๊ฐ ๋ฌ๋ผ์ง๋ค
- ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ฐ๊พธ๋ฉด ๊ฒฝ๊ณ๊ฐ ๋ฌ๋ผ์ง๋ค. psycopg3์ SQLAlchemy๋ ์ ์ด์ด๋ ์ด๋ฆฌ๊ณ JDBC์ Django๋ ์ ๋ซ์๋ ๋ซํ๋ค
- ํ๋ ์์ํฌ๋ฅผ ๋ฐ๊ฟ ๋๋ ๊ธฐ๋ณธ๊ฐ๋ถํฐ ํ์ธํ๋ค
2. ์ด๋์ ๋ซ์ ๊ฒ์ธ๊ฐ
๊ฒฝ๊ณ ๊ธฐ์ค์ ํ๋๋ค. ํจ๊ป ์คํจํด์ผ ํ๋ ๊ฒ๋ง ๋ฌถ๋๋ค.
์ค์ ์์๋ ์ด๋ ๊ฒ ๋ฐ๊ฟ ๋ฌผ์ผ๋ฉด ํ๋จ์ด ๋น ๋ฅด๋ค. โ์ด๊ฒ ์คํจํ๋ฉด ์์ ๊ฒ๋ ์ทจ์๋ผ์ผ ํ๋?โ ์๋๋ผ๋ฉด ๋ค๋ฅธ ํธ๋์ญ์ ์ผ๋ก ๋๋๋ค.
ํ๋ณด๋ ๋์ฒด๋ก ์ ์ด๋ค.
| ๋ฒ์ | ์ธ์ ๋ง๋ | ์ํ |
|---|---|---|
| ๋ฌธ์ฅ ๋จ์ | ๋จ์ ์กฐํ, ๋ ๋ฆฝ์ ์ธ ๋จ๊ฑด ๊ฐฑ์ | ์ฌ๋ฌ ๋ฌธ์ฅ์ ์ ํฉ์ฑ์ ๋ชป ์งํด |
| ์์ ๋จ์ | ๋๋ถ๋ถ์ ๊ฒฝ์ฐ | ์์. ๊ธฐ๋ณธ์ผ๋ก ์ผ๋๋ค |
| ์์ฒญ ๋จ์ | ์งง๊ณ DB๋ง ๊ฑด๋๋ฆฌ๋ API | ์ธ๋ถ ํธ์ถ์ด ๋ผ๋ฉด ์ค๋ ์ด๋ฆผ |
๋๊ฒ ์ก์ ๋์ ์ข๊ฒ ์ก์ ๋ ์๋ ๊ฒ์ด ๋ค๋ฅด๋ค.
- ๋๊ฒ ์ก์ผ๋ฉด: ๋ฝ ์ ์ง ์๊ฐ ์ฆ๊ฐ, ์ปค๋ฅ์ ์ ์ , MVCC ์ฃฝ์ ๋ฒ์ ํ์ ์ง์ฐ
- ์ข๊ฒ ์ก์ผ๋ฉด: ์ค๊ฐ ์ํ๊ฐ ๋ฐ์์ ๋ณด์, ์ผ๋ถ๋ง ๋ฐ์๋๊ณ ๋๋จ
์ฃผ๋ฌธ ์ฒ๋ฆฌ๋ฅผ ์๋ก ๋ค๋ฉด ์ด๋ ๊ฒ ๊ฐ๋ฆฐ๋ค.
# ํ๋๋ก ๋ฌถ๋๋ค: ์ฌ๊ณ ์ ์ฃผ๋ฌธ์ ํจ๊ป ์คํจํด์ผ ํ๋ค
with conn.transaction():
decrease_stock(item_id, qty)
create_order(user_id, item_id, qty)
# ๋ฐ์ผ๋ก ๋บ๋ค: ์๋ฆผ์ด ์คํจํด๋ ์ฃผ๋ฌธ์ ์ด์ ์์ด์ผ ํ๋ค
send_notification(user_id)๊ฒฝ๊ณ๋ฅผ ๋ชป ์ ํ๊ฒ ์ ๋
- ๋๋๋ฆฌ๋ฉด ์ ๋๋ ๊ฒ์ด ํ๋๋ผ๋ ์์ผ๋ฉด ๊ทธ ์ง์ ์ด ๊ฒฝ๊ณ๋ค
- ์ ๋งคํ๋ฉด ์ข๊ฒ ์์ํ๋ค. ๋์ค์ ๋ํ๋ ํธ์ด ๋ฐ๋๋ณด๋ค ์ฝ๋ค
3. ํธ๋์ญ์ ์์ ๋ฃ์ผ๋ฉด ์ ๋๋ ๊ฒ
ํ์ ์ฒ๋ฆฌ๋ก ๋ถ๋ฆฌํ ์ ์๋ ์์ ์ ๋น๋๊ธฐ๋ก ๋๊ฒจ ๋๊ธฐ ํธ๋์ญ์ ์ ์ฐ๊ธฐ ๋ฒ์๋ฅผ ์ค์ผ ์ ์๋ค.
๊ฒฝ๊ณ๋ฅผ ๋๊ฒ ์ก๋ ๊ฒ ์์ฒด๋ณด๋ค ์์ ๋ฌด์์ด ๋ค์ด๊ฐ๋๋๊ฐ ๋ฌธ์ ์ธ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค.
- ์ธ๋ถ API ํธ์ถ: ์๋๊ฐ ๋๋ฆฌ๋ฉด ๊ทธ ์๊ฐ๋งํผ ํธ๋์ญ์ ์ด ์ด๋ ค ์๋ค. ํ์์์์ด 30์ด๋ฉด 30์ด ๋์ ๋ฝ๊ณผ ์ปค๋ฅ์ ์ ์ฅ๋ค
- ์ฌ์ฉ์ ์ ๋ ฅ ๋๊ธฐ: ํ๋ฉด์ ๋์๋๊ณ ์๋ต์ ๊ธฐ๋ค๋ฆฌ๋ ๊ตฌ์กฐ๋ ํธ๋์ญ์ ์์ ๋ค์ด๊ฐ๋ฉด ์ ๋๋ค
- ๋ฌด๊ฑฐ์ด ๊ณ์ฐ: DB์ ๋ฌด๊ดํ ์ฐ์ฐ์ ํธ๋์ญ์ ๋ฐ์์ ๋๋ด๊ณ ๊ฒฐ๊ณผ๋ง ๋ค๊ณ ๋ค์ด๊ฐ๋ค
- ๋์ฉ๋ ํ์ผ ์ฒ๋ฆฌ: ์ ๋ก๋์ ํ์ฑ์ ๋ฐ์์ ํ๋ค
ํธ๋์ญ์ ์ด ์ด๋ ค ์๋ ๋์ ๋ถ์ก๊ณ ๋์ง ์๋ ์์์ด ์ ์ด๋ค.
- ์ปค๋ฅ์ : ํ์์ ํ๋๊ฐ ๊ณ์ ๋น ์ ธ ์๋ค
- ๋ฝ: ๊ฐ์ ํ์ ๊ธฐ๋ค๋ฆฌ๋ ํธ๋์ญ์ ์ด ์ค์ ์ ๋ค
- ์ค๋ ์ท: ์ด ํธ๋์ญ์ ์ด ๋ณผ์ง ๋ชจ๋ฅด๋ ์ ๋ฒ์ ์ ๋ชป ์ง์ด๋ค
์ฃผ์:
idle in transaction์ด ๋จ๊ธฐ๋ ๊ฒ
- ํธ๋์ญ์ ์ ์ด๊ณ ์๋ฌด ์ผ๋ ์ ํ๋ ์ํ๋ค. ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ธ๋ถ ์๋ต์ ๊ธฐ๋ค๋ฆฌ๋ ์ค์ด๊ฑฐ๋ ์ปค๋ฐ์ ์์์ ๋ ์๊ธด๋ค
- PostgreSQL์์๋ `VACUUM`์ด ์ฃฝ์ ํ์ ํ์ํ์ง ๋ชปํ๋ค. ์ด ์ธ์ ์ด ์ ๋ฒ์ ์ ๋ณผ์ง๋ ๋ชจ๋ฅด๊ธฐ ๋๋ฌธ์ด๋ค
- ์ธ์ ํ๋๊ฐ ํ ์ด๋ธ ์ ์ฒด์ ํ์๋ฅผ ๋ง๋๋ค. ๊ฐฑ์ ์ด ์ฆ์ ํ ์ด๋ธ์ด๋ฉด ๋ฉฐ์น ๋ง์ ํฌ๊ธฐ๊ฐ ๋ช ๋ฐฐ๊ฐ ๋๋ค
- ์ปค๋ฅ์ ํ๋ ํจ๊ป ๋ง๋ฅธ๋ค. ํ ํฌ๊ธฐ๊ฐ 20์ธ๋ฐ 5๊ฐ๊ฐ ์ด ์ํ๋ฉด ์ค์ง ๋์์ฑ์ด 15๋ค
idle_in_transaction_session_timeout์ผ๋ก ๊ฐ์ ์ข ๋ฃ๋ฅผ ๊ฑธ์ด๋๋ค
4. ์คํจํ ๋ค์ ๋ฒ์ด์ง๋ ์ผ
ํธ๋์ญ์ ์ค๊ฐ์ ์๋ฌ๊ฐ ๋๋ฉด ๊ทธ๋ค์ ๋์์ด DB๋ง๋ค ๋ค๋ฅด๋ค.
PostgreSQL์ ํธ๋์ญ์ ์ ์ฒด๋ฅผ ์คํจ ์ํ๋ก ๋๋ฆฐ๋ค.
ERROR: duplicate key value violates unique constraint "users_email_key"
-- ์ดํ ์ด๋ค ๋ฌธ์ฅ์ ๋ณด๋ด๋
ERROR: current transaction is aborted,
commands ignored until end of transaction block์ฒซ ์๋ฌ ์ดํ ๋ชจ๋ ๋ฌธ์ฅ์ด ๊ฑฐ๋ถ๋๋ค. ROLLBACK์ด๋ COMMIT์ผ๋ก ํธ๋์ญ์
์ ๋ซ๊ธฐ ์ ๊น์ง ์๋ฌด๊ฒ๋ ๋ชป ํ๋ค. ๋ก๊ทธ์ ๋ ๋ฒ์งธ ๋ฉ์์ง๋ง ์๋ฉ ์์ฌ ์์ธ์ ๋ชป ์ฐพ๋ ๊ฒฝ์ฐ๊ฐ ์ฌ๊ธฐ์ ์๊ธด๋ค.
MySQL์ ๋ค๋ฅด๋ค. ๋ฌธ์ฅ ํ๋๋ง ์คํจํ๊ณ ํธ๋์ญ์ ์ ์ด์ ์์ด ๋ค์ ๋ฌธ์ฅ์ด ๊ทธ๋๋ก ์คํ๋๋ค.
| DB | ์๋ฌ 1๊ฑด์ด ๋ฌ์ ๋ |
|---|---|
| PostgreSQL | ํธ๋์ญ์ ์ ์ฒด๊ฐ aborted. ๋๋จธ์ง ์ ๋ถ ๊ฑฐ๋ถ |
| MySQL | ๊ทธ ๋ฌธ์ฅ๋ง ์คํจ. ๋๋จธ์ง๋ ๊ณ์ ์คํ |
MySQL์์ ๋๋ ๋ฐฐ์น๋ฅผ PostgreSQL๋ก ์ฎ๊ธฐ๋ฉด ์ฌ๊ธฐ์ ๊นจ์ง๋ค. โ์คํจํ ๊ฑด์ ๊ฑด๋๋ฐ๊ณ ๊ณ์โ์ด ํตํ์ง ์๋๋ค.
SAVEPOINT๋ก ํ ๊ฑด๋ง ๋ฒ๋ฆฌ๊ธฐ
๋ฐ๋ณต ์ ์ฌ์์ ์ผ๋ถ ์คํจ๋ฅผ ํก์ํ๋ ค๋ฉด SAVEPOINT๋ฅผ ์ด๋ค.
BEGIN;
SAVEPOINT sp;
INSERT INTO users VALUES (...); -- ์ค๋ณต ํค๋ก ์คํจ
ROLLBACK TO SAVEPOINT sp; -- ์ด ๊ฑด๋ง ์ทจ์. ํธ๋์ญ์
์ ์ด์๋๋ค
RELEASE SAVEPOINT sp;
SAVEPOINT sp;
INSERT INTO users VALUES (...); -- ๋ค์ ๊ฑด์ ์ ์ ์งํ
RELEASE SAVEPOINT sp;
COMMIT;ROLLBACK TO SAVEPOINT๊ฐ aborted ์ํ๋ฅผ ๊ทธ ์ง์ ๊น์ง ๋๊ฐ์์ค๋ค. PostgreSQL์์ โ์คํจํ ๊ฑด๋ง ๊ฑด๋๋ฐ๊ธฐโ๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ด๋ค.
SAVEPOINT: ์ค์ฒฉ ํธ๋์ญ์ ์ด ์๋๋ค
- ํธ๋์ญ์ ์์ ํธ๋์ญ์ ์ ๋ง๋๋ ๊ฒ ์๋๋ผ ๋๊ฐ์ ์ง์ ์ ํ์ํด๋๋ ๊ฒ์ด๋ค
RELEASEํด๋ ์ปค๋ฐ๋์ง ์๋๋ค. ํ์ ์ ๋ฐ๊นฅCOMMIT์์ ์ ํ๊บผ๋ฒ์ ์ผ์ด๋๋ค- ๋ฐ๊นฅ์ด ๋กค๋ฐฑ๋๋ฉด ์์์ ์ฑ๊ณตํ ๊ฒ๋ ์ ๋ถ ์ฌ๋ผ์ง๋ค
- ORM์ ์ค์ฒฉ
atomic()๋ธ๋ก์ด ๋ด๋ถ์ ์ผ๋ก ์ด๊ฑธ ์ด๋ค
์ฃผ์: SAVEPOINT๋ฅผ ๊ฑด๋ง๋ค ์ก์ผ๋ฉด
- ๋ฐ๋ณต ํ์๋งํผ ์๋ฒ ์์์ด ์์ธ๋ค. ์๋ง ๊ฑด ๋ฃจํ์์๋ ๋ถ๋ด์ด ๋์ ๋๋ค
- ์คํจ๊ฐ ๋๋ฌผ๋ฉด SAVEPOINT ์์ด ๊ฐ๋ค๊ฐ ์๋ฌ๊ฐ ๋ฌ์ ๋๋ง ํธ๋์ญ์ ์ ๋ค์ ์ฌ๋ ํธ์ด ์ธ๋ค
- ๋๋ ์ ์ฌ๋ผ๋ฉด
INSERT ... ON CONFLICT DO NOTHING์ผ๋ก ์ ์ด์ ์คํจ๋ฅผ ์ ๋ง๋๋ ์ชฝ์ ๋จผ์ ๋ณธ๋ค
5. ๋กค๋ฐฑํด๋ ๋์์ค์ง ์๋ ๊ฒ
ROLLBACK์ ํธ๋์ญ์
์์ ๋ณ๊ฒฝ๋ง ๋๋๋ฆฐ๋ค. ๋ฐ์ ์๋ ๊ฒ์ ๊ทธ๋๋ก ๋จ๋๋ค.
์ํ์ค
์ํ์ค: ๋ฒํธ๋ฅผ ํ๋์ฉ ๋ฐ๊ธํ๋ ์นด์ดํฐ
- ํ ์ด๋ธยท์ธ๋ฑ์ค์ ๊ฐ์ ๊ธ์ DB ๊ฐ์ฒด๋ค. ์์ ๋ ๊ฒ์ โ๋ช ๋ฒ๊น์ง ๋๊ฐ๋์งโ ํ๋๋ฟ์ด๋ค
nextval()์ด ๋ค์ ๋ฒํธ๋ฅผ ๊บผ๋ด๋ฉด์ ์นด์ดํฐ๋ฅผ ์ฌ๋ฆฐ๋ค.currval()์ ์ด ์ธ์ ์ด ๋ง์ง๋ง์ ๋ฐ์ ๊ฐ์ ๋ณด์ฌ์ค ๋ฟ ์ฌ๋ฆฌ์ง ์๋๋คbigserial์ด๋GENERATED ALWAYS AS IDENTITY๋ก ์ปฌ๋ผ์ ๋ง๋ค๋ฉด ์ํ์ค๊ฐ ๋ธ๋ ค ๋ถ๋๋ค.ordersํ ์ด๋ธ์id๋ฉด ์ด๋ฆ์ดorders_id_seq๋ค- INSERT์์
id๋ฅผ ๋๊ธฐ์ง ์์ผ๋ฉด DB๊ฐ ๋์nextval์ ๋ถ๋ฅธ๋ค. ์ง์ ์น ์ผ์ ๊ฑฐ์ ์๋ค
BEGIN;
SELECT nextval('orders_id_seq'); -- 42
ROLLBACK;
SELECT nextval('orders_id_seq'); -- 43. 42๋ ์์ ์ฌ๋ผ์ก๋คPostgreSQL ์ํ์ค ํจ์ ๋ฌธ์๊ฐ ์ด์ ๋ฅผ ์ ์ด๋์๋ค.
the value obtained by
nextvalis not reclaimed for re-use if the calling transaction later aborts
๋๋๋ฆฌ๋ ค๋ฉด ์ํ์ค์ ๋ฝ์ ๊ฑธ์ด์ผ ํ๋๋ฐ ๊ทธ๋ฌ๋ฉด ๋ฒํธ๋ฅผ ๋ฐ์ผ๋ ค๋ ํธ๋์ญ์ ์ด ์ ๋ถ ์ค์ ์ ๋ค. ๋์์ฑ์ ์ํด ์ผ๋ถ๋ฌ ํธ๋์ญ์ ๋ฐ์ ๋๋ค.
MySQL์ AUTO_INCREMENT๋ ๊ฐ๋ค.
์ฃผ์: ์ฐ์๋ ๋ฒํธ๋ฅผ ์๊ตฌํ๋ ์๊ฑด
- ๋ฌธ์์์ ์ธ๊ธํ๋ฏ์ด PostgreSQL ์ํ์ค๋ก๋ gapless ๋ฒํธ๋ฅผ ๋ง๋ค ์ ์๋ค
- ๋กค๋ฐฑ๋ฟ ์๋๋ผ
INSERT ... ON CONFLICT์์๋ ๊ตฌ๋ฉ์ด ์๊ธด๋ค. ์ถฉ๋์ ๊ฐ์งํ๊ธฐ ์ ์nextval์ ์ด๋ฏธ ๋ถ๋ฅด๊ธฐ ๋๋ฌธ์ด๋ค- ์ธ๊ธ๊ณ์ฐ์ ๋ฒํธ์ฒ๋ผ ๋น ์ง์์ด ์ด์ด์ ธ์ผ ํ๋ ๊ฐ์ ๋ณ๋ ํ ์ด๋ธ์ ๋ฝ์ ๊ฑธ๊ณ ๋ฐ๊ธํ๊ฑฐ๋ ์ปค๋ฐ ์ดํ ํ์ฒ๋ฆฌ๋ก ๋งค๊ธด๋ค
- PK์ ๊ตฌ๋ฉ์ด ์๊ธฐ๋ ๊ฒ ์์ฒด๋ ๋ฌธ์ ๊ฐ ์๋๋ค. ๊ทธ๊ฑธ ์ ๋ฌด ๋ฒํธ๋ก ๋ ธ์ถํ ๋ ๋ฌธ์ ๊ฐ ๋๋ค
์ธ๋ถ ๋ถ์ ํจ๊ณผ
ํธ๋์ญ์ ์ด ์ปค๋ฒํ๋ ๋ฒ์๋ ๊ทธ DB ์๊น์ง๋ค.
| ๋์ | ๋กค๋ฐฑ๋๋ |
|---|---|
INSERT ยท UPDATE ยท DELETE | ๋๋ค |
| ์ํ์ค ์๋น | ์ ๋๋ค |
| ์ธ๋ถ API ํธ์ถ | ์ ๋๋ค |
| ํ ๋ฉ์์ง ๋ฐํ | ์ ๋๋ค |
| ๋ฉ์ผ ๋ฐ์ก | ์ ๋๋ค |
| ํ์ผ ์ฐ๊ธฐ | ์ ๋๋ค |
๋์์ ๋ ๊ฐ๋๋ค.
- ์ปค๋ฐ ์ดํ๋ก ๋ฏธ๋ฃฌ๋ค: ๊ฐ์ฅ ๋จ์ํ๋ค. ๋ค๋ง ์ปค๋ฐ๊ณผ ํ์ ๋์ ์ฌ์ด์์ ์ฃฝ์ผ๋ฉด ์ ์ค๋๋ค
- outbox๋ฅผ ์ด๋ค: ๋ณด๋ผ ๋ด์ฉ์ ๊ฐ์ ํธ๋์ญ์ ์์์ ํ ์ด๋ธ์ ์ ์ด๋๊ณ ๋ณ๋ ํ๋ก์ธ์ค๊ฐ ์ฝ์ด ๋ฐ์กํ๋ค. ํธ๋์ญ์ ์ด ๋กค๋ฐฑ๋๋ฉด ๋ฐ์ก ๋์๋ ํจ๊ป ์ฌ๋ผ์ง๋ค
# ์ปค๋ฐ ์์์: ๋ณด๋ผ ๋ด์ฉ๋ง ๊ธฐ๋ก
with conn.transaction():
create_order(...)
insert_outbox(topic="order.created", payload=...)
# ๋ณ๋ ์์ปค๊ฐ outbox๋ฅผ ์ฝ์ด ์ค์ ๋ก ๋ฐ์กTransactional Outbox: ํ ์ด๋ธ ํ๋๋ก ๋ง๋๋ ์ค๊ณ ํจํด
- ๊ตฌํ์ ์ ํ๋ฆฌ์ผ์ด์ ๋ชซ์ด๋ค. ๋ฐ์ก ๋๊ธฐ ํ ์ด๋ธ๊ณผ ๊ทธ๊ฒ์ ์ฝ์ด ๋ณด๋ด๋ ์์ปค๋ฅผ ์ง์ ๋ง๋ ๋ค
- ํ์ํ ๊ฒ์ด ํ ์ด๋ธ๊ณผ ํธ๋์ญ์ ๋ฟ์ด๋ผ PostgreSQLยทMySQLยทOracle ์ด๋์๋ ๊ฐ๊ฒ ๋ง๋ ๋ค
- ์์ปค๋ฅผ ์ฌ๋ฌ ๋ ๋์ฐ๋ ค๋ฉด
FOR UPDATE SKIP LOCKED๋ก ์ง์ด๊ฐ๋ค. ๊ฐ์ ํ์ ๋ ๋ฒ ๋ณด๋ด์ง ์๋๋ค- ๋ฐ์ก์ ์ฑ๊ณตํ๊ณ ์๋ฃ ํ์๋ฅผ ๋จ๊ธฐ๊ธฐ ์ ์ ์ฃฝ์ผ๋ฉด ๋ค์ ๋ณด๋ธ๋ค. ๋ฐ๋ ์ชฝ์์ ์ค๋ณต์ ๊ฑธ๋ฌ์ผ ํ๋ค
6. DDL๊ณผ ํธ๋์ญ์
์คํค๋ง ๋ณ๊ฒฝ์ ํธ๋์ญ์ ์ผ๋ก ๋ฌถ์ ์ ์๋์ง๊ฐ DB๋ง๋ค ๊ฐ๋ฆฐ๋ค.
PostgreSQL์ DDL๋ ๋กค๋ฐฑ๋๋ค.
BEGIN;
ALTER TABLE users ADD COLUMN phone text;
UPDATE users SET phone = '';
-- ์ฌ๊ธฐ์ ์คํจํ๋ฉด
ROLLBACK; -- ์ปฌ๋ผ ์ถ๊ฐ๊น์ง ์๋ ์ผ์ด ๋๋ค๋ง์ด๊ทธ๋ ์ด์ ์ ์ฒด๋ฅผ ํ ํธ๋์ญ์ ์ผ๋ก ๋ฌถ์ผ๋ฉด ์ค๊ฐ์ ์คํจํด๋ ๋๋๋ฆด ์ ์๋ค. ๋๊ธด ์คํค๋ง๊ฐ ๋จ์ง ์๋๋ค.
MySQL์ DDL์ด ์๋ฌต์ ์ปค๋ฐ์ ์ผ์ผํจ๋ค.
MySQL ๋ฌธ์ ํํ์ ์ด๋ ๋ค.
implicitly end any transaction active in the current session, as if you had done a
COMMITbefore executing the statement
START TRANSACTION;
INSERT INTO users VALUES (...); -- ์์ง ์ปค๋ฐ ์ ๋จ
ALTER TABLE users ADD COLUMN phone text;
-- ์ด ์์ ์ ์์ INSERT๊น์ง ํจ๊ป ํ์ ๋๋ค
ROLLBACK; -- ๋๋๋ฆด ๊ฒ์ด ์๋คCREATEยทALTERยทDROPยทTRUNCATE ๊ณ์ด ์ ๋ถ๊ฐ ํด๋นํ๋ค. GRANTยทREVOKE ๊ฐ์ ๊ถํ ๋ฌธ์ฅ๊ณผ LOCK TABLES๋ ๋ง์ฐฌ๊ฐ์ง๋ค.
์ฃผ์: MySQL์์ ๋ฐ์ฏค ์ ์ฉ๋ ๋ง์ด๊ทธ๋ ์ด์
- ๋ง์ด๊ทธ๋ ์ด์ ์ด ์ค๊ฐ์ ์คํจํ๋ฉด ์ ๋จ๊ณ๋ ํ์ ๋๊ณ ๋ค๋ ์ ๋ ์ํ๋ก ๋ฉ์ถ๋ค
- ๋๋๋ฆฌ๋ ค๋ฉด ์ญ๋ฐฉํฅ ์คํฌ๋ฆฝํธ๋ฅผ ์ง์ ์คํํด์ผ ํ๋ค. ์๋ ๋กค๋ฐฑ์ด ์๋ค
- ๊ทธ๋์ MySQL ์ชฝ ๋ง์ด๊ทธ๋ ์ด์ ๋๊ตฌ๋ ๋จ๊ณ๋ฅผ ์๊ฒ ์ชผ๊ฐ๊ณ ๊ฐ ๋จ๊ณ๋ฅผ ๋ฉฑ๋ฑํ๊ฒ ๋ง๋๋ ๋ฐฉํฅ์ผ๋ก ๊ฐ๋ค
START TRANSACTION๊ณผSET autocommit = 1์์ฒด๋ ์๋ฌต์ ์ปค๋ฐ์ ์ผ์ผํจ๋ค. ํธ๋์ญ์ ์์์ ๋ค์START TRANSACTION์ ์น๋ฉด ์์ ๊ฒ์ด ์ปค๋ฐ๋๋ค
DDL์ด ์ก๋ ๋ฝ์ ์ฑ๊ฒฉ์ด ๋ ๋ฌ๋ผ์ PostgreSQL์์๋ ALTER TABLE ํ ์ค์ด ์๋น์ค๋ฅผ ๋ฉ์ถ ์ ์๋ค.
7. ORM์์์ ๊ฒฝ๊ณ
ORM์ ํธ๋์ญ์ ๊ฒฝ๊ณ๋ฅผ ์จ๊ธด๋ค. ํธํ์ง๋ง ์ด๋์ ์ด๋ฆฌ๊ณ ๋ซํ๋์ง ๋ชจ๋ฅด๋ฉด ์์ ํจ์ ์ ๊ทธ๋๋ก ๋ฐ๋๋ค.
flush์ commit์ ๋ค๋ฅด๋ค
session.add(order)
session.flush() # SQL์ด ์คํ๋๋ค. ํธ๋์ญ์
์ ์์ง ์ด๋ ค ์๋ค
# order.id๋ ์ฑ์์ง๋ค. ๋ค๋ฅธ ์ธ์
์์๋ ์ ๋ณด์ธ๋ค
session.commit() # ์ฌ๊ธฐ์ ํ์ ๋๋คflush๋ ํ์ด์ฌ ๊ฐ์ฒด ์ํ๋ฅผ SQL๋ก ๋ง๋ค์ด ์คํํ๋ ๋์์ด๊ณ commit์ ํธ๋์ญ์
์ ๋ซ๋ ๋์์ด๋ค. ์๋ ์์ฑ๋ ID๊ฐ ํ์ํด์ flush๋ฅผ ๋ถ๋ฅด๊ณ ๋ ์ปค๋ฐํ๋ค๊ณ ์ฐฉ๊ฐํ๋ ๊ฒฝ์ฐ๊ฐ ์๋ค.
๊ฒฝ๊ณ๋ ๋ธ๋ก์ผ๋ก ์ง์ ํ๋ค
| ํ๋ ์์ํฌ | ์ง์ ๋ฐฉ์ |
|---|---|
| SQLAlchemy | with session.begin(): ๋ธ๋ก |
| Django | with transaction.atomic(): ๋ธ๋ก |
| Django (์์ฒญ ๋จ์) | ATOMIC_REQUESTS = True ์ค์ |
ATOMIC_REQUESTS๋ฅผ ์ผ๋ฉด ์์ฒญ ํ๋๊ฐ ํธ๋์ญ์
ํ๋๊ฐ ๋๋ค. ํธํ์ง๋ง ํธ๋์ญ์
์์ ์ธ๋ถ ํธ์ถ์ด ๋ค์ด๊ฐ๋ ๋ฌธ์ ๊ฐ ๊ทธ๋๋ก ๋ฐ์ํ๋ค.
์ฃผ์: ์์ฒญ ๋จ์ ํธ๋์ญ์ ๊ณผ ์ธ๋ถ ํธ์ถ
ATOMIC_REQUESTS๊ฐ ์ผ์ง ์ํ์์ ๋ทฐ ์์ ๊ฒฐ์ API ํธ์ถ์ด ์์ผ๋ฉด ๊ทธ ์๋ณต ์๊ฐ ๋ด๋ด ํธ๋์ญ์ ์ด ์ด๋ ค ์๋ค- ์ธ๋ถ ํธ์ถ์ด ์๋ ๋ทฐ๋
non_atomic_requests๋ก ๋นผ๊ฑฐ๋ ํธ์ถ์ ์ปค๋ฐ ์ดํ๋ก ์ฎ๊ธด๋ค- ์ธ์ ์ ์์ฒญ๋ณด๋ค ์ค๋ ์ด๋ ค๋๋ฉด ์ปค๋ฅ์ ์ด ํ๋ก ๋์๊ฐ์ง ์๋๋ค
8. ์ฌ์๋ ๊ฐ๋ฅํ๊ฒ ๋ง๋ค๊ธฐ
ํธ๋์ญ์ ์ด ์คํจํ๋ ๊ฒ ์์ฒด๋ ์ ์์ด๋ค. ๋ฌธ์ ๋ ๋ค์ ๋๋ ธ์ ๋ ๊ฐ์ ๊ฒฐ๊ณผ๊ฐ ๋์ค๋๋๋ค.
์ฌ์๋ํด๋ ๋๋ ์๋ฌ๋ง ์ฌ์๋ํ๋ค
| SQLSTATE | ์๋ฏธ | ์ฌ์๋ |
|---|---|---|
40001 | ์ง๋ ฌํ ์คํจ | ํ๋ค |
40P01 | ๋ฐ๋๋ฝ | ํ๋ค |
23505 | ์ ๋ํฌ ์ ์ฝ ์๋ฐ | ๋๊ฐ ๋ฌด์๋ฏธ |
23503 | ์ธ๋ ํค ์๋ฐ | ๋ฌด์๋ฏธ |
22P02 | ํ์ ๋ณํ ์คํจ | ๋ฌด์๋ฏธ |
์์ ๋์ ๋ค์ ํ๋ฉด ์ฑ๊ณตํ ์ ์๋ ์คํจ๋ค. ๋ค์ ์ ์ ์ ๋ ฅ์ด ์๋ชป๋ ๊ฒ์ด๋ผ ๋ช ๋ฒ์ ๋๋ ค๋ ๊ฐ๋ค. ๊ตฌ๋ถ ์์ด ์ฌ์๋ ๋ฃจํ๋ฅผ ๋๋ฆฌ๋ฉด ์๋ชป๋ ์์ฒญ์ ๋ถํ๋ง ์ค๋ค.
RETRYABLE = {"40001", "40P01"}
for _ in range(3):
try:
with conn.transaction():
do_work()
break
except psycopg.Error as e:
if e.sqlstate not in RETRYABLE:
raise # ์ฌ์๋ํด๋ ๊ฐ์ ๊ฒฐ๊ณผ์ธ ์๋ฌ๋ ๋ฐ๋ก ์ฌ๋ฆฐ๋ค
else:
raise RuntimeError("์ฌ์๋ ํ๋ ์ด๊ณผ")์ฌ์๋ ๋ฒ์๋ ํธ๋์ญ์ ์ ์ฒด๋ค
์ปค๋ฐ๋ง ๋ค์ ์๋ํ๋ ๊ฒ์ ์๋ฏธ๊ฐ ์๋ค. ์ฝ์ ๊ฐ ์์ฒด๊ฐ ๋ฌดํจ๋ผ์ ์ฝ๊ธฐ๋ถํฐ ๋ค์ ํด์ผ ํ๋ค. ๊ทธ๋์ ์ฌ์๋ ๋ฃจํ๊ฐ ํธ๋์ญ์ ๋ธ๋ก ๋ฐ๊นฅ์ ์์ด์ผ ํ๋ค.
๋ถ์ ํจ๊ณผ๊ฐ ์์ผ๋ฉด ์ฌ์๋๊ฐ ์ํํด์ง๋ค
์์ ๋ณธ ๋ถ์ ํจ๊ณผ ๋ฌธ์ ์ ์ด์ด์ง๋ค. ํธ๋์ญ์ ์์์ ์ธ๋ถ API๋ฅผ ํธ์ถํ๋ค๋ฉด ์ฌ์๋ํ ๋๋ง๋ค ๊ทธ ํธ์ถ์ด ๋ฐ๋ณต๋๋ค.
# ์ํ: ์ฌ์๋ํ ๋๋ง๋ค ๊ฒฐ์ ๊ฐ ๋ค์ ์ผ์ด๋๋ค
with conn.transaction():
charge_payment(...) # ์ธ๋ถ ํธ์ถ
create_order(...)
# ์์ : ์ธ๋ถ ํธ์ถ์ ๋ฐ์์ ํ ๋ฒ๋ง
payment_id = charge_payment(...) # ๋ฉฑ๋ฑ ํค๋ฅผ ํจ๊ป ๋ณด๋ธ๋ค
for _ in range(3): # ์ฌ์๋ํด๋ ๊ฒฐ์ ๋ ๋ฐ๋ณต๋์ง ์๋๋ค
try:
with conn.transaction():
create_order(payment_id=payment_id)
break
except psycopg.Error as e:
if e.sqlstate not in RETRYABLE:
raise์ฌ์๋๋ฅผ ์ ์ ๋ก ์ค๊ณํ๋ฉด ํธ๋์ญ์ ์์ DB ์์ ๋ง ๋จ๋๋ค. ์ธ๋ถ ํธ์ถ์ ํธ๋์ญ์ ๋ฐ์ผ๋ก ๋นผ์ผ ํ๋ ์ด์ ๊ฐ ํ๋ ๋ ๋์ด๋๋ค.
Serializable์ ์ฐ๋ฉด ์ฌ์๋๊ฐ ์ ํ์ด ์๋๋ผ ํ์๊ฐ ๋๋ค. ๊ฒฉ๋ฆฌ ์์ค์ ์ฌ๋ฆฌ๊ธฐ ์ ์ ์ด ๊ตฌ์กฐ๋ถํฐ ๊ฐ์ถฐ๋๋ค.