์ฝ๊ธฐ๋ ๋ฉ์ฉกํ๋ฐ ์ฐ๊ธฐ๋ง ์ ๋ถ 500์ผ๋ก ์ฃฝ์๋ค. ๊ทธ๊ฒ๋ ์ด์ ๊น์ง ์ ๋๋ ์๋น์ค๊ฐ. ๋ฒ์ธ์ ์์ธ๋ก "SQL ๋ก๊ทธ ์ข ์์๊ฒ ๋ณด๋ ค๊ณ " ๋ผ์ ๋ฃ์ ๋๋ผ์ด๋ฒ์๋ค. PgBouncer ๋ค์์ @Transactional ์ฐ๊ธฐ ์์ฒญ๋ง ๊ณจ๋ผ ์ฃฝ์ด๋ ์ด ์ฅ์ ๋ฅผ ์ด๋ป๊ฒ ์ก์๋์ง ์ ๋ฆฌํ๋ค.
ํต์ฌ ์์ฝ
- ์ฆ์: ์กฐํ(SELECT)๋ ์ ์,
@Transactional์ฐ๊ธฐ ์์ฒญ๋ง ์ ๋ถ 500 (CannotCreateTransactionException). ํฌ์ค์ฒดํฌ๋ DOWN. - ํ๊ฒฝ: ์ฑ์ด PostgreSQL์ ์ง์ ๋ถ์ง ์๊ณ PgBouncer(์ปค๋ฅ์
ํ๋ฌ)๋ฅผ ๊ฒฝ์ .
pool_mode = transaction. - ๋ฐฉ์์ : ๋๊ตฐ๊ฐ JDBC ๋๋ผ์ด๋ฒ๋ฅผ
org.postgresql.Driver→ log4jdbc(DriverSpy, SQL ๋ก๊น ํ๋ก์)๋ก ๋ฐ๊ฟ. - ๊ทผ๋ณธ ์์ธ: PgBouncer ํธ๋์ญ์
ํ๋ง + server-side prepared statement ์ถฉ๋. ๋๋ผ์ด๋ฒ๋ฅผ ๋ฐ๊พธ๋ฉฐ
prepareThreshold=0์ต์ ์ด ๋น ์ง. - ํด๊ฒฐ: ๋๋ผ์ด๋ฒ ์๋ณต +
prepareThreshold=0(๋๋ ํด๋น DB๋งpool_mode=session).
1. ๋ฐฐ๊ฒฝ — ์ฐ๋ฆฌ DB ์์ PgBouncer๊ฐ ์๋ค
๋จผ์ ๊ตฌ์กฐ๋ถํฐ. ๋ณดํต ์คํ๋ง ์ฑ์ ์ด๋ ๊ฒ PostgreSQL์ ๋ฐ๋ก ๋ถ๋๋ค๊ณ ์๊ฐํ๋ค.
App (Spring/JPA) โโโบ PostgreSQL
๊ทธ๋ฐ๋ฐ ์ฐ๋ฆฌ ํ๊ฒฝ์ ์ค๊ฐ์ ํ ๋จ์ด ๋ ์๋ค. ์ปค๋ฅ์ ์๊ฐ ํญ์ฆํด DB ์ปค๋ฅ์ ์ฌ๋กฏ์ด ๊ณ ๊ฐ๋๋ ๊ฑธ ๋ง์ผ๋ ค๊ณ , ์๋ฒ ๋ด๋น์ด PgBouncer๋ผ๋ ์ปค๋ฅ์ ํ๋ฌ๋ฅผ ์๋จ์ ๋ ๊ฒ์ด๋ค.
App (Spring/JPA) โโโบ PgBouncer โโโบ PostgreSQL
(pool_mode=transaction)
PgBouncer๋ "์์ญ ๊ฐ์ ์ฑ ์ปค๋ฅ์
์ ์์์ ์ค์ DB ์ปค๋ฅ์
์ผ๋ก ๋ค์คํ"ํด์ฃผ๋ ์ค๊ฐ ๊ด๋ฆฌ์๋ค. ๋ฌธ์ ๋ ์ด ๋ค์คํ ๋ฐฉ์(pool_mode)์ด ๋ช ๊ฐ์ง๊ฐ ์๋ค๋ ์ ์ด๋ค.
| pool_mode | ์๋ฒ ์ปค๋ฅ์ ์ ์ธ์ ๊น์ง ๋น๋ ค์ฃผ๋ | ํน์ง |
|---|---|---|
session |
ํด๋ผ์ด์ธํธ๊ฐ ์ฐ๊ฒฐ์ ๋์ ๋๊น์ง | ์ธ์ ๊ธฐ๋ฅ ์์ , ์ปค๋ฅ์ ์ ์ฝ ํจ๊ณผ ์ ์ |
transaction |
ํธ๋์ญ์ ํ๋ ๋๋ ๋๊น์ง๋ง | ์ปค๋ฅ์ ์ ์ฝ ํผ, ์ธ์ ์ํ ์ ์ด์๋จ์ |
statement |
์ฟผ๋ฆฌ ํ ๋ฌธ์ฅ ๋จ์ | ๊ฐ์ฅ ๊ณต๊ฒฉ์ , ์ ์ฝ ๋ง์ |
์ฐ๋ฆฌ๋ ์ปค๋ฅ์
์ ์ฝ์ด ์ ์คํด์ transaction ๋ชจ๋์๋ค. ์ด ํ ์ค์ด ์ด๋ฒ ์ฅ์ ์ ์ ๋ฐ์ ์ฐจ์งํ๋ค. "ํธ๋์ญ์
์ด ๋๋๋ฉด ์๋ฒ ์ปค๋ฅ์
์ ํ์ํด์ ๋ค๋ฅธ ํด๋ผ์ด์ธํธ์๊ฒ ๋๊ธด๋ค"๋ ๋์์ ๊ผญ ๊ธฐ์ตํ์.
2. ๋ฌธ์ ๋ฐ์ — ์ฝ๊ธฐ๋ ๋๋๋ฐ ์ฐ๊ธฐ๋ง ์ฃฝ๋๋ค
์ด๋ ๋ ์์นจ, ๋ฐฑ์๋๊ฐ ์ด์ํ๊ฒ ๊ตด๊ธฐ ์์ํ๋ค.
- ํ๋ฉด ์กฐํ(๋จ์
SELECT) → ์ ์ - ๋ฑ๋ก/์์ ์ฒ๋ผ
@Transactional์ด ๊ฑธ๋ฆฐ ์ฐ๊ธฐ API → ์ ๋ถ 500 - ์คํ๋ง ์ก์ถ์์ดํฐ
/health→ DOWN
๋ก๊ทธ๋ฅผ ๊น๋ณด๋ ์ฐ๊ธฐ ์์ฒญ๋ง๋ค ์ด ์์ธ๊ฐ ์ฐํ๋ค.
org.springframework.transaction.CannotCreateTransactionException:
Could not open JDBC Connection for transaction;
nested exception is org.postgresql.util.PSQLException: ...
at o.s.jdbc.datasource.DataSourceTransactionManager.doBegin(...)
at o.s.jdbc.datasource.DataSourceTransactionManager.doBegin(...)
...
CannotCreateTransactionException. ์คํ๋ง์ด "ํธ๋์ญ์
์ ์์์กฐ์ฐจ ๋ชป ํ๊ฒ ๋ค"๊ณ ์์ ๋ ๊ฒ์ด๋ค. ์ฝ๊ธฐ๋ ๋๋๋ฐ ํธ๋์ญ์
์ฐ๊ธฐ๋ง ๊นจ์ง๋ค๋, ์ง๊ด์ ์ผ๋ก ์ ์ ์๋ฟ๋ ๋น๋์นญ์ด์๋ค.
3. ์ง๋จ ๊ณผ์ — ๋ฒ์๋ฅผ ์ขํ๋๊ฐ๋ค
3-1. DB๊ฐ ์ฃฝ์๋? → ์๋๋ค
๊ฐ์ฅ ๋จผ์ ์์ฌํ ๊ฑด DB ์์ฒด์๋ค. ๊ทธ๋์ ์ฑ์ ๊ฑฐ์น์ง ์๊ณ , ํ์ด์ฌ psycopg2๋ก DB์ ์ง์ INSERT๋ฅผ ๋ ๋ ค๋ดค๋ค.
import psycopg2
conn = psycopg2.connect(host="db-host", port=5432, dbname="appdb",
user="app", password="****")
cur = conn.cursor()
cur.execute("INSERT INTO t_test(name) VALUES ('hello')")
conn.commit() # ← ์ ์ ์ปค๋ฐ๋จ!
print("write ok")
์ ์์ ์ผ๋ก ๋ค์ด๊ฐ๋ค. DB๋, ์คํค๋ง๋, ๊ถํ๋ ๋ฉ์ฉกํ๋ค. ์ฆ ๋ฌธ์ ๋ PostgreSQL์ด ์๋๋ผ ์ฑ ↔ DB ์ฌ์ด ์ด๋๊ฐ์ ์๋ค. ์ด ํ ๋ฒ์ ์คํ์ผ๋ก ์ฉ์์ ์์์ "DB ์๋ฒ"๋ฅผ ์ง์ ๋ค. ํธ๋ฌ๋ธ์ํ ์์ ์ ์ผ ์ค์ํ ๊ฑด ์ด๋ ๊ฒ ๋ฒ์๋ฅผ ๋ฐ์ฉ ์๋ผ๋ด๋ ๊ฒ์ด๋ค.
3-2. ์ต๊ทผ ๋ฐ๋ ๊ฒ ๋ญ์ง? → ๋๋ผ์ด๋ฒ ๊ต์ฒด
"์ด์ ๊น์ง ๋๋๋ฐ ์ค๋ ์ ๋๋ค"๋ฉด ๊ทธ ์ฌ์ด ๋ณ๊ฒฝ์ ์ ์์ฌํด์ผ ํ๋ค. ์ปค๋ฐ ๋ก๊ทธ๋ฅผ ๋ค์ ธ๋ณด๋, ์ ๋ ๋๊ตฐ๊ฐ SQL ๋ก๊ทธ๋ฅผ ๋ณด๊ธฐ ์ข๊ฒ ์ฐ์ผ๋ ค๊ณ JDBC ๋๋ผ์ด๋ฒ๋ฅผ ๋ฐ๊ฟ๋จ๋ค.
# before (์ ์)
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://pgbouncer-host:6432/appdb?prepareThreshold=0
# after (์ฅ์ )
spring.datasource.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
spring.datasource.url=jdbc:log4jdbc:postgresql://pgbouncer-host:6432/appdb
๋ ๊ฐ์ง๊ฐ ๋์์ ๋ฐ๋์๋ค. โ ๋๋ผ์ด๋ฒ๊ฐ org.postgresql.Driver → log4jdbc DriverSpy(์ค์ ๋๋ผ์ด๋ฒ ์์ ๋ผ๋ ๋ก๊น
ํ๋ก์). โก ๊ทธ๋ฌ๋ฉด์ URL์ prepareThreshold=0 ์ต์
์ด ์ฌ๊ทธ๋จธ๋ ์ฌ๋ผ์ก๋ค.
3-3. ๊ต์ฐจ๊ฒ์ฆ — ์ธ ๋ชจ๋ธ์๊ฒ ๊ฐ์ ์ง๋ฌธ์ ๋์ก๋ค
์์ธ์ด PgBouncer์ธ์ง ๋๋ผ์ด๋ฒ์ธ์ง ํ์ ์ด ์ ์์, ๊ฐ์ ์ฆ์·ํ๊ฒฝ์ ์ ๋ฆฌํด Claude·GPT·Gemini ์ธ ๋ชจ๋ธ์ ๋๊ฐ์ด ๋ฌผ์ด๋ดค๋ค. ์ ๋ค ์ ์ ๋ชจ์ "PgBouncer ํธ๋์ญ์ ๋ชจ๋ × server-side prepared statement ์ถฉ๋"์ ์ง๋ชฉํ๋ค. ํนํ GPT๊ฐ ๋ฉ์ปค๋์ฆ์ ๊ฐ์ฅ ์ ํํ ์ง์ด์คฌ๋ค. ํ ๋ชจ๋ธ๋ง ๋ดค์ผ๋ฉด ๋์ณค์ ๋ํ ์ผ์ ๊ต์ฐจ๊ฒ์ฆ์ด ์ก์์คฌ๋ค.
4. ๊ทผ๋ณธ ์์ธ — prepared statement๋ "๊ทธ ์ปค๋ฅ์ ์ ์ฌ๋ฌผํจ"์ด๋ค
ํต์ฌ ๋ฉ์ปค๋์ฆ์ ์ด๋ ๋ค. PostgreSQL JDBC ๋๋ผ์ด๋ฒ(pgJDBC)๋ ๊ฐ์ ์ฟผ๋ฆฌ๊ฐ ๋ฐ๋ณต๋๋ฉด, ๋งค๋ฒ ํ์ฑํ์ง ์์ผ๋ ค๊ณ ์ผ์ ํ์(prepareThreshold, ๊ธฐ๋ณธ 5) ์ดํ server-side prepared statement๋ก ์น๊ฒฉ์ํจ๋ค. ์๋ฒ์ S_1 ๊ฐ์ ์ด๋ฆํ๋ฅผ ๋ถ์ฌ ๋ฏธ๋ฆฌ ์ค๋นํด๋๊ณ , ๋ค์๋ถํด ๊ทธ ์ด๋ฆํ๋ง ํธ์ถํ๋ ๋ฐฉ์์ด๋ค.
๋น์ : prepared statement๋ ํฌ์ค์ฅ ์ฌ๋ฌผํจ์ ๋ฏธ๋ฆฌ ๋ฃ์ด๋ ์ด๋ํ๋ค. "3๋ฒ ์ฌ๋ฌผํจ์ ๋ด ์ ๋ฐ ์์ด์"๋ผ๊ณ ์ธ์น๋ฉด ์ง์์ด ๊บผ๋ด์ค๋ค. ๊ทธ๋ฐ๋ฐ PgBouncer
transaction๋ชจ๋๋, ์ด๋ ํ ์ธํธ(=ํธ๋์ญ์ )๊ฐ ๋๋ ๋๋ง๋ค ์ฌ๋ฌผํจ์ ํ์ํด ๋ค๋ฅธ ํ์์๊ฒ ๋ฐฐ์ ํ๋ค. ๋ค์์ ๊ฐ๋๋ ๋ํํ ๋ 5๋ฒ ์ฌ๋ฌผํจ์ด ๋ฐฐ์ ๋๋๋ฐ, ๊ฑฐ๊ธฐ์ ๋ด ์ ๋ฐ์ด ์๋ค. "3๋ฒ์ ์๋ค๋ฉฐ์?" → ์ง์: "๊ทธ๋ฐ ์ ๋ฐ ์๋๋ฐ์(prepared statement does not exist)."
์ฆ ํธ๋์ญ์
์ด ๋๋๋ฉด PgBouncer๊ฐ ์๋ฒ ์ปค๋ฅ์
(backend)์ ํ์ํ๋ฏ๋ก, ๋ค์ ํธ๋์ญ์
์ ๋ค๋ฅธ backend์ ๋ถ์ ์ ์๋ค. ๊ทธ๋ฐ๋ฐ ๋๋ผ์ด๋ฒ๋ "์๊น ๋ง๋ S_1 ๊ทธ๋๋ก ์ฐ๋ฉด ๋์ง" ํ๊ณ ํธ์ถํ๋ค. ์ backend์ S_1์ด ์๋ค. ๊ทธ๋์ ์ด๋ฐ ๋ฅ์ ์๋ฌ๊ฐ ํฐ์ง๋ค.
ERROR: prepared statement "S_1" does not exist (SQLSTATE 26000)
-- ๋๋
ERROR: prepared statement "S_3" already exists (SQLSTATE 42P05)
๊ทธ๋ผ ์ ์ฝ๊ธฐ๋ ๋๊ณ ์ฐ๊ธฐ๋ง ์ฃฝ์๋?
- ์ฝ๊ธฐ: ๋จ๋ฐ์ฑ autocommit
SELECT๋ ํธ๋์ญ์ ์ ๊ธธ๊ฒ ๋ฌถ์ด์ง ์๊ณ , prepared statement ์น๊ฒฉ ์๊ณ์น์๋ ์ ์ ๋ฟ๋๋ค. ๊ทธ๋์ ํ๋ฉด์ ์ผ๋ก ๋ฉ์ฉกํด ๋ณด์ธ๋ค. - ์ฐ๊ธฐ:
@Transactional์BEGIN์ผ๋ก ํธ๋์ญ์ ์ ๋ช ์์ ์ผ๋ก ์ด๊ณ , Hibernate๊ฐ INSERT/UPDATE/์ํ์ค ์กฐํ ๋ฑ ์ฌ๋ฌ PreparedStatement๋ฅผ ๋ฐ๋ณต ์ฌ์ฉํ๋ค. prepared statement mismatch๊ฐ ์ ํต์ผ๋ก ํฐ์ง๋ ๊ตฌ๊ฐ์ด๋ค.
์ฌ๊ธฐ์ log4jdbc ํ๋ก์๊ฐ ๊ธฐ๋ฆ์ ๋ถ์๋ค. ์ ์์ด๋ prepareThreshold=0(=server-side prepared statement ๋๊ธฐ) ์ต์
์ด ๋๋ผ์ด๋ฒ๋ฅผ ๋ฐ๊พธ๋ฉฐ ๋น ์ง์, pgJDBC ๊ธฐ๋ณธ๊ฐ(5)์ผ๋ก ๋์๊ฐ prepared statement๊ฐ ๋ค์ ์ผ์ก๊ณ , PgBouncer ํธ๋์ญ์
๋ชจ๋์ ์ ๋ฉด์ถฉ๋ํ๋ค. ๊ฒฐ๊ตญ ์คํ๋ง ์
์ฅ์์ ํธ๋์ญ์
์ ์ ์์ ์ผ๋ก ์์·์ ์งํ ์ ์์ผ๋ CannotCreateTransactionException์ผ๋ก ๊ฐ์ธ ๋์ง ๊ฒ์ด๋ค.
5. ํด๊ฒฐ — ๋ ๋จ๊ณ๋ก
5-1. ์ฆ์ ๋ณต๊ตฌ: ๋๋ผ์ด๋ฒ ์๋ณต
์ด์ ์ฅ์ ์ํฉ์ด๋ผ ๋จผ์ ์ถํ์ ๋ง์์ผ ํ๋ค. ๋ก๊น ํ๋ก์ ๋๋ผ์ด๋ฒ๋ฅผ ๊ฑท์ด๋ด๊ณ ํ์ค ๋๋ผ์ด๋ฒ๋ก ๋๋๋ ธ๋ค.
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://pgbouncer-host:6432/appdb?prepareThreshold=0
๋๋๋ฆฌ์๋ง์ ์ฐ๊ธฐ ํธ๋์ญ์ ์ด ์ฆ์ ๋ณต๊ตฌ๋๊ณ , ํฌ์ค์ฒดํฌ๋ UP์ผ๋ก ๋์์๋ค.
5-2. ์๊ตฌ ๋์: ์ต์ ์ ๋ช ์์ ์ผ๋ก ๋ฐ๋๋ค
ํต์ฌ์ PgBouncer ํธ๋์ญ์ ๋ชจ๋ ๋ค์์๋ server-side prepared statement๋ฅผ ๋๋ ๊ฒ์ด๋ค. JDBC URL์ ๋ค์์ ๋ช ์ํ๋ค.
jdbc:postgresql://pgbouncer-host:6432/appdb?prepareThreshold=0&preparedStatementCacheQueries=0
prepareThreshold=0: server-side prepared statement๋ก ์น๊ฒฉํ์ง ์์ (ํต์ฌ).preparedStatementCacheQueries=0: ํด๋ผ์ด์ธํธ ์ธก statement ์บ์๊น์ง ์ค์ด๋ ๋ณด์กฐ ์ต์ .
์ ํ์ง๋ฅผ ํ๋ก ์ ๋ฆฌํ๋ฉด ์ด๋ ๋ค.
| ๋์ | ๋ฐฉ๋ฒ | ํธ๋ ์ด๋์คํ |
|---|---|---|
| prepareThreshold=0 | ์ฑ JDBC URL์ ์ต์ ์ถ๊ฐ (๊ถ์ฅ) | ๊ฐ์ฅ ํํ ํด๊ฒฐ์ฑ . ์ฑ๋ง ๊ณ ์น๋ฉด ๋จ |
| pool_mode=session | ํด๋น DB/์ ์ ๋ง ์ธ์ ํ๋ง | ํธํ์ฑ ์ต๊ณ , ์ปค๋ฅ์ ์ ์ฝ ํจ๊ณผ ๊ฐ์ |
| max_prepared_statements | PgBouncer 1.21+์์ 0์ด ์๋ ๊ฐ | ๋ฒ์ ·๋ฉ๋ชจ๋ฆฌ ๊ณ ๋ ค ํ์ |
| ๋ก๊น ํ๋ก์ ์ ๊ฑฐ | ์ด์์์ log4jdbc ์์ ์ฌ์ฉ ์ง์ | SQL ๋ก๊น ์ ๊ด์ธก ๋๊ตฌ/์ฌ๋ก์ฐ์ฟผ๋ฆฌ๋ก ๋์ฒด |
์ง๋จ์ ์ด ๋ช ๋ น
PgBouncer ํ ์ํ๋ ๊ด๋ฆฌ ์ฝ์์์ ๋ฐ๋ก ํ์ธํ ์ ์๋ค. ํ์ด ์์ง๋ ๊ฑด์ง, ์ค์ (mode) ๋ฌธ์ ์ธ์ง ๊ฐ๋ฅด๋ ๋ฐ ์ ์ฉํ๋ค.
$ psql -p 6432 -U pgbouncer_admin -d pgbouncer -c "SHOW POOLS"
# database | pool_mode | cl_active | cl_waiting | maxwait | ...
# appdb | transaction | 3 | 0 | 0 | ...
cl_waiting·maxwait๊ฐ ์์ด๋ฉด ํ ์์ง, ๊ทธ๊ฒ ์๋๋ผ๋ฉด pool_mode์ ํด๋ผ์ด์ธํธ JDBC ์ค์ ์ ์์ฌํ๋ฉด ๋๋ค.
๋ณด๋์ค โ — log4jdbc(DriverSpy)๋ ๋์ฒด ๋ญ ํ๊ธธ๋
log4jdbc๋ ๊ทธ ์์ฒด๋ก DB์ ๋ถ๋ ๋๋ผ์ด๋ฒ๊ฐ ์๋๋ค. ์ค์ ๋๋ผ์ด๋ฒ ์์ ๋ผ์ด๋๋ ํ๋ก์(wrapper)๋ค. ๊ตฌ์กฐ๋ฅผ ๊ทธ๋ฆผ์ผ๋ก ๋ณด๋ฉด ์ด๋ ๋ค.
// ํ์ค ๊ฒฝ๋ก
App โโโบ org.postgresql.Driver โโโบ DB
// log4jdbc๋ฅผ ๋ผ์ฐ๋ฉด
App โโโบ DriverSpy (ํ๋ก์) โโโบ org.postgresql.Driver โโโบ DB
โ
โโโบ ์ค๊ฐ๋ JDBC ํธ์ถ(SQL/ํ๋ผ๋ฏธํฐ/ํ์ด๋ฐ)์ ๊ฐ๋ก์ฑ SLF4J๋ก ๋ก๊น
๊ทธ๋์ ์ฐ๋ ค๋ฉด ๋ ๊ฐ์ง๋ฅผ ๋ฐ๊ฟ์ผ ํ๋ค. โ ๋๋ผ์ด๋ฒ ํด๋์ค๋ฅผ net.sf.log4jdbc.sql.jdbcapi.DriverSpy๋ก โก JDBC URL ์์ jdbc:log4jdbc: ์ ๋์ฌ๋ฅผ ๋ถ์ด๊ธฐ. ๋ฌธ์ ๋ ์ด ๊ณผ์ ์์ ๊ธฐ์กด URL์ ๋ถ์ด ์๋ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ(?prepareThreshold=0 ๋ฑ)๋ฅผ ๊ทธ๋๋ก ์ฎ๊ฒจ ์ ์ด์ผ ํ๋ค๋ ์ ์ด๋ค. ์ฐ๋ฆฌ๋ ๋ฐ๋ก ์ด๊ฑธ ๋์ณค๋ค. ํ๋ก์๊ฐ ๋ชจ๋ JDBC ํธ์ถ์ ํ ๋ฒ ๋ ๊ฐ์ธ ๋ค์ฌ๋ค๋ณด๋ ๊ตฌ์กฐ๋ผ, ๋ฏธ๋ฌํ ๋๋ผ์ด๋ฒ ๋์ ์ฐจ์ด๋ ์ต์
๋๋ฝ์ด ๊ทธ๋๋ก ์ฌ๊ณ ๋ก ์ด์ด์ง๋ค. "๋ก๊น
ํ๋ ค๊ณ ๋ผ์ด ๊ฒ ๋์๊น์ง ๋ฐ๊พผ๋ค"๋ ๊ฒ ํ๋ก์ ๋๋ผ์ด๋ฒ์ ์๋ ์ ๊ฒ์ด๋ค.
๋ณด๋์ค โก — ๋น์ทํ ์ฆ์์ด๋ฉด ์ด ์์๋ก ์ ๊ฒ
ํน์ ์ง๊ธ "์กฐํ๋ ๋๋๋ฐ ์ฐ๊ธฐ๋ง ๊นจ์ง๋" ์ํฉ์ด๋ผ๋ฉด, ์๋ ์์๋ก ์ขํ๊ฐ๋ฉด ๋น ๋ฅด๋ค.
- DB ์ง์ ์ฐ๊ธฐ๋ก DB ๊ฒฐ๋ฐฑ๋ถํฐ ํ์ธ (psycopg2 / psql). ๋๋ฉด DB๋ ์ฉ์์ ์์์ ์ ์ธ.
- ์์ธ์ nested cause๋ฅผ ๋๊น์ง ๊น๋ณธ๋ค.
prepared statement "..." does not exist / already exists,SQLSTATE 26000 / 42P05๊ฐ ๋ณด์ด๋ฉด ๊ฑฐ์ ํ์ . - ์๋จ์ PgBouncer/ํ๋ฌ๊ฐ ์๋์ง, ์๋ค๋ฉด
SHOW POOLS๋กpool_mode๋ฅผ ํ์ธ.transaction์ด๋ฉด ์์ฌ ๊ฐํ. - JDBC URL์
prepareThreshold=0์ด ์๋์ง ํ์ธ. ์์ผ๋ฉด ์ถ๊ฐํ๊ณ ์ฌ๊ธฐ๋ํด ๋ฐ๋ก ์ด์๋๋์ง ๋ณธ๋ค. - ์ต๊ทผ ๋๋ผ์ด๋ฒ/๋ฐ์ดํฐ์์ค ์ค์ ๋ณ๊ฒฝ ์ด๋ ฅ์ ํ์ธ. ๋ก๊น ํ๋ก์ ๋๋ผ์ด๋ฒ๊ฐ ๋ผ์๋์ง ๋ณธ๋ค.
์ด ๋ค์ฏ ์ค์ด๋ฉด ๋๋ถ๋ถ 30๋ถ ์์ ์์ธ์ด ์กํ๋ค.
6. ๊ตํ
- "๋ก๊ทธ ์ข ์์๊ฒ"๊ฐ ์ด์์ ๋ฉ์ถ ์ ์๋ค. log4jdbc ๊ฐ์ ํ๋ก์ ๋๋ผ์ด๋ฒ๋ ์ค์ ๋๋ผ์ด๋ฒ ์์ ๋ผ์ด๋ค์ด ๋์์ ๋ฐ๊พผ๋ค. ํนํ PgBouncer ํธ๋์ญ์ ๋ชจ๋์ฒ๋ผ ๊น๋ค๋ก์ด ํ๊ฒฝ์์ ์์ ์ฌ์ฉ์ ํผํ์. SQL ๋ก๊น ์ p6spy ์์ ํฌ์ ๋ณด๋ค ๊ด์ธก ๋๊ตฌ·์ฌ๋ก์ฐ์ฟผ๋ฆฌ ๋ก๊ทธ๋ก ๋นผ๋ ๊ฒ ์์ ํ๋ค.
- ์ค์ ์ ๋ฐ๊ฟ ๋ "์ต์
์ด ๋ฐ๋ผ์๋์ง"๊น์ง ๋ณธ๋ค. ์ด๋ฒ ์ฅ์ ์ ์ง์ง ํธ๋ฆฌ๊ฑฐ๋ ๋๋ผ์ด๋ฒ ์์ฒด๊ฐ ์๋๋ผ, ๊ต์ฒด ๊ณผ์ ์์ ์กฐ์ฉํ ๋น ์ง
prepareThreshold=0ํ ์ค์ด์๋ค. - PgBouncer transaction ๋ชจ๋ = ์ธ์
์ํ๊ฐ ์ ์ด์๋จ๋๋ค. prepared statement,
SET, ์์ ํ ์ด๋ธ, advisory lock,LISTEN/NOTIFY๊ฐ์ ์ธ์ ๊ธฐ๋ฐ ๊ธฐ๋ฅ์ ์ด ๋ชจ๋์ ์๊ทน์ด๋ค. ํ๋ฌ ๋ค์ ์๋ค๋ ๊ฑธ ํญ์ ์์ํด์ผ ํ๋ค. - "์ฝ๊ธฐ๋ ๋๋๋ฐ ์ฐ๊ธฐ๋ง ์ฃฝ๋๋ค"๋ ๊ฐ๋ ฅํ ๋จ์๋ค. DB๊ฐ ํต์งธ๋ก ์ฃฝ์ ๊ฒ ์๋๋ผ, ํธ๋์ญ์ ·์ธ์ ์ํ๊ฐ ์ฝํ ๊ฒฝ๋ก๋ง ๊นจ์ง๋ค๋ ๋ป. ์ฆ์์ ๋น๋์นญ์ ๊ทธ๋ฅ ๋๊ธฐ์ง ๋ง์.
- ๋ฒ์๋ฅผ ๋ฐ์ฉ ์๋ผ๋ผ. psycopg2 ์ง์ ์ฐ๊ธฐ ํ ๋ฒ์ผ๋ก "DB๋ ๊ฒฐ๋ฐฑ"์ ์ฆ๋ช ํ๊ณ ๋๋, ๋จ์ ๊ฑด ์ฑ↔ํ๋ฌ↔๋๋ผ์ด๋ฒ ๊ตฌ๊ฐ๋ฟ์ด์๋ค. ์์ฌ ๋ฒ์๋ฅผ ๋น ๋ฅด๊ฒ ์ขํ๋ ๊ฒ ๊ณง ํด๊ฒฐ ์๋๋ค.
๊ฐ์ ๊ตฌ์กฐ(์ฑ → PgBouncer → PostgreSQL)๋ฅผ ์ฐ๋ ๋ถ์ด๋ผ๋ฉด, pool_mode๊ฐ transaction์ธ์ง ๋จผ์ ํ์ธํ๊ณ JDBC URL์ prepareThreshold=0์ด ๋ฐํ ์๋์ง ์ ๊ฒํด๋ณด๊ธธ. ์ด๋ ๋ ๊ฐ์๊ธฐ ์ฐ๊ธฐ๋ง ์ฃฝ๋ ๊ทธ ํฉ๋นํ ์ฅ์ ๋ฅผ, ํ ์ค๋ก ์๋ฐฉํ ์ ์๋ค.