μΈλΆ API νΈμΆκ³Ό DB λ°μμ νΈλμμ κ²½κ³ λΆλ¦¬
μμ½
λ§μΌμ€ν€ κΈ°λ° ν¬λΌμ°λνλ© μλΉμ€μμ OpenAIλ₯Ό μ΄μ©ν AI κ²μ¦μ λΉλκΈ°λ‘ μ²λ¦¬νμ΅λλ€. κ²μ¦ μμ² μ체λ μ¬μ©μ μμ²κ³Ό λΆλ¦¬λμ΄ μμμ§λ§, λΉλκΈ° μμ λ΄λΆμμλ OpenAI API νΈμΆκ³Ό κ²°κ³Ό μ μ₯μ΄ νλμ DB νΈλμμ μ ν¬ν¨λμ΄ μμμ΅λλ€.
μΈλΆ API μλ΅μ κΈ°λ€λ¦¬λ λμμλ JDBC 컀λ₯μ μ΄ μ μ λλ ꡬ쑰μκ³ , 10μ΄ μ§μ° νκ²½μμ λμ DB μ‘°νκ° 3,005ms ν μ€ν¨νλ κ²μ νμΈνμ΅λλ€. μΈλΆ API νΈμΆκ³Ό DB μμ μ νΈλμμ κ²½κ³λ₯Ό λΆλ¦¬ν λ€μλ λμΌν 쑰건μμ 컀λ₯μ μ΄ νμ λ°νλκ³ λμ DB μ‘°νλ 8ms λ§μ μ μ μ²λ¦¬λμ΅λλ€.
λͺ©μ°¨
1. λ°°κ²½
μμ΄λμ΄ μ μμμ μ λ¬Έκ°, μ€ν°μλ₯Ό μ°κ²°νλ λ§μΌμ€ν€ κΈ°λ° ν¬λΌμ°λνλ© μλΉμ€λ₯Ό ν νλ‘μ νΈλ‘ κ°λ°νμ΅λλ€. μμ΄λμ΄κ° 곡κ°λκΈ° μ μ AIκ° κ³Όλκ΄κ³ νν, μ μ¬ μλΉμ€ μ‘΄μ¬ μ¬λΆ, λ§μΌμ€ν€ κ΅¬μ²΄μ± λ±μ κ²μ¦νκ³ , κ²°κ³Όλ₯Ό μ λ’°λ μ μμ νμ μ λ¬Έκ° μ¬μ¬μ λ°μνλ κΈ°λ₯μ ꡬννμ΅λλ€.
OpenAI APIλ μλ΅ μκ°μ΄ μΌμ νμ§ μκΈ° λλ¬Έμ μΈλΆ API μ§μ°μ΄ μμ΄λμ΄ λ±λ‘ μμ²μ μ§μ μν₯μ μ£Όμ§ μλλ‘ νμ΅λλ€. κ²μ¦ μμ²κ³Ό μνλ₯Ό DBμ μ μ₯ν λ€ μ¬μ©μμκ² μλ΅νκ³ , μ€μ AI κ²μ¦μ Spring Eventλ₯Ό μ΄μ©ν΄ λ³λ μ€λ λμμ λΉλκΈ°λ‘ μ²λ¦¬νμ΅λλ€.
@Async("verificationTaskExecutor")
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void processAiVerification(VerificationRequestedEvent event) {
// AI κ²μ¦ μ²λ¦¬
}
@TransactionalEventListener(AFTER_COMMIT)μ μ μ©ν΄ μμ΄λμ΄ μμ± νΈλμμ μ΄ μ μμ μΌλ‘ 컀λ°λ μ΄νμλ§ κ²μ¦μ΄ μμλλλ‘ νμ΅λλ€. @Asyncμ μ μ© ThreadPoolTaskExecutorλ₯Ό μ¬μ©ν΄ μ€μ κ²μ¦ μμ μ λ³λ μ€λ λμμ μ€ννμ΅λλ€.
μ¬μ©μ μμ²κ³Ό AI κ²μ¦ μμ μ체λ λΆλ¦¬λμ΄ μμμ§λ§, λΉλκΈ° μ²λ¦¬ λ΄λΆμ νΈλμμ λ²μλ₯Ό λ€μ νμΈνλ©΄μ λ€λ₯Έ λ¬Έμ κ° λ³΄μμ΅λλ€.
2. λ¬Έμ
AI κ²μ¦μ λΉλκΈ°λ‘ μ²λ¦¬νλ κ³Όμ μμ TransactionTemplateλ‘ μμν νΈλμμ λ΄λΆμμ κ²μ¦ μν°ν° μ‘°νλΆν° OpenAI API νΈμΆ, κ²°κ³Ό μ μ₯κΉμ§ λͺ¨λ μννκ³ μμμ΅λλ€.
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void processAiVerification(VerificationRequestedEvent event) {
transactionTemplate.executeWithoutResult(
status -> processAiVerificationInTransaction(event)
);
}
public void processAiVerificationInTransaction(VerificationRequestedEvent event) {
// κ²μ¦ μν°ν° μ‘°ν
AiVerificationStructuredResult result =
openAiVerificationService.verify(event.request());
// κ²°κ³Ό μ μ₯
// μ λ’°λ μ μ κ°±μ
// μν λ³κ²½
}
μ¦ μλ³Έ μμ²μ νΈλμμ κ³Όλ λΆλ¦¬λμ΄ μμμ§λ§, λ³λ μ€λ λμμ λ€μ μμν νΈλμμ μ΄ OpenAI μλ΅μ κΈ°λ€λ¦¬λ λμ κ³μ μ μ§λλ ꡬ쑰μμ΅λλ€.
DB νΈλμμ μ΄ μ μ§λλ λμμλ JDBC 컀λ₯μ λ ν¨κ» μ μ λ©λλ€. λ°λΌμ μ€μ DB μμ μ΄ μλ μΈλΆ API λκΈ° μκ°μλ 컀λ₯μ μ΄ νλ‘ λ°νλμ§ μμ μ μμ΅λλ€.
OpenAI ν΄λΌμ΄μΈνΈμ read timeoutμ 300μ΄λ‘ μ€μ λμ΄ μμμ΅λλ€. λ¨μΌ νΈμΆμ΄ timeoutκΉμ§ μ§μ°λ κ²½μ° μ΅λ 300μ΄ κ°κΉμ΄ 컀λ₯μ μ΄ μ μ λ μ μλ ꡬ쑰μμ΅λλ€.
openai:
verification:
timeout-seconds: 300
κ²μ¦ μμ²μ΄ λμμ μ¦κ°νλ©΄ AI κ²μ¦κ³Ό μ§μ κ΄λ ¨μ΄ μλ μμ²κΉμ§ DB 컀λ₯μ μ ν λΉλ°μ§ λͺ»ν κ°λ₯μ±μ΄ μμ΅λλ€. μΈλΆ API μλ΅ μ§μ°μ΄ DB 컀λ₯μ μ μ λ₯Ό ν΅ν΄ λ€λ₯Έ μμ²μΌλ‘ μ νλ μ μλ ꡬ쑰μμ΅λλ€.
μ§μ μΈ‘μ ν΄ νμΈ
μ½λλ§μΌλ‘ ꡬ쑰μ μΈ μνμ νμΈν μ μμμ§λ§, μ€μ λ‘ μΈλΆ API λκΈ° μ€ μ»€λ₯μ μ΄ μ μ λλμ§ νμΈνκΈ° μν΄ μΈ‘μ νκ²½μ ꡬμ±νμ΅λλ€.
- HikariCP maximum-pool-size=1
- 컀λ₯μ νλ μ ν μκ° 3,000ms
- OpenAI Mock μλ΅ 10μ΄ μ§μ°
- OpenAI λκΈ° μ€ λ³λμ DB μ‘°ν μ€ν
μ€μ read timeoutμΈ 300μ΄λ₯Ό κ·Έλλ‘ μ¬ννμ§ μκ³ 10μ΄ μ§μ°μ μ¬μ©ν μ΄μ λ νμΈνλ €λ λμμ΄ μΈλΆ API λκΈ° μκ° λμ DB 컀λ₯μ μ΄ ν¨κ» μ μ λλμ§ μ¬λΆμκΈ° λλ¬Έμ λλ€.
[μ΄κΈ°] active=0, idle=1, waiting=0
[DelayedMock] OpenAI νΈμΆ μ§μ
- 10000ms μ§μ° μμ
[OpenAI λκΈ° μ€] active=1, idle=0, waiting=0
[λμ DB μ‘°ν] μ€ν¨ | μμ=3005ms
[λμ DB μ‘°ν] μμΈ=CannotGetJdbcConnectionException
OpenAI μλ΅μ κΈ°λ€λ¦¬λ λμ HikariCPκ° active=1, idle=0 μνλ₯Ό μ μ§νκ³ , κ°μ μμ μ μ€νν DB μ‘°νλ 컀λ₯μ μ ν λΉλ°μ§ λͺ»ν΄ 3,005ms ν CannotGetJdbcConnectionExceptionμΌλ‘ μ€ν¨νμ΅λλ€.
3. ν΄κ²°
λΉλκΈ° κ²μ¦μ νΈλμμ κ²½κ³λ₯Ό μΈλΆ API νΈμΆ λ¨μκ° μλλΌ μ€μ DB μμ λ¨μλ‘ λ€μ λλ΄μ΅λλ€.
- μ§§μ νΈλμμ μμ κ²μ¦ μνμ μ·¨μ μ¬λΆ νμΈ
- OpenAI API νΈμΆμ νΈλμμ λ°μμ μν
- μλ΅μ λ°μ μ΄ν μλ‘μ΄ νΈλμμ μ μμν΄ κ²°κ³Ό μ μ₯
κ²μ¦ μμ²
↓
μν νμΈ
[μ§§μ TX]
↓
OpenAI API νΈμΆ
[TX μμ]
↓
κ²μ¦ κ²°κ³Ό · μ λ’°λ μ μ · μν λ°μ
[μ§§μ TX]
λ¨Όμ μ§§μ νΈλμμ μμ νμ¬ κ²μ¦ μνμ μ·¨μ μ¬λΆλ§ νμΈνκ³ μ’ λ£ν©λλ€. μ΄λ―Έ μ·¨μλ μμ²μ΄λΌλ©΄ OpenAI APIλ₯Ό νΈμΆνμ§ μμ΅λλ€.
μ΄ν OpenAI API νΈμΆμ νΈλμμ μμ΄ μννκ³ , μλ΅μ λ°μ λ€μλ§ μλ‘μ΄ TransactionTemplateμ μμν΄ κ²μ¦ κ²°κ³Ό, μ λ’°λ μ μ, μμ΄λμ΄ μν λ± μ€μ DB λ³κ²½μ΄ νμν μμ μ μ²λ¦¬νλλ‘ λ³κ²½νμ΅λλ€.
κΈ°μ‘΄μ @TransactionalEventListener(AFTER_COMMIT)κ³Ό @Async ꡬ쑰λ κ·Έλλ‘ μ μ§νμ΅λλ€.
μλ³Έ μμ² νΈλμμ μ΄ μ μμ μΌλ‘ 컀λ°λ ν λ³λ μ€λ λμμ AI κ²μ¦μ μ€ννλ€λ ꡬ쑰 μμ²΄κ° λ¬Έμ κ° μλλΌ, κ·Έ λΉλκΈ° μμ λ΄λΆμμ μΈλΆ API νΈμΆκΉμ§ DB νΈλμμ μ ν¬ν¨ν κ²μ΄ λ¬Έμ μκΈ° λλ¬Έμ λλ€.
ν΅μ¬μ λΉλκΈ° μ²λ¦¬ μ¬λΆκ° μλλΌ λΉλκΈ° μμ λ΄λΆμ νΈλμμ λ²μμμ΅λλ€.
4. κ²°κ³Ό
λ³κ²½ νμλ λμΌνκ² HikariCP ν ν¬κΈ°λ₯Ό 1λ‘ μ ννκ³ OpenAI Mock μλ΅μ 10μ΄ μ§μ°μν¨ λ€ λ€μ μΈ‘μ νμ΅λλ€.
Before - νΈλμμ λ΄λΆμμ μΈλΆ API νΈμΆ
========== 컀λ₯μ
μ μ μΈ‘μ ==========
[μ΄κΈ°] active=0, idle=1, waiting=0
[DelayedMock] OpenAI νΈμΆ μ§μ
- 10000ms μ§μ° μμ
[OpenAI λκΈ° μ€] active=1, idle=0, waiting=0
HikariPool - Timeout failure stats (total=1/1, idle=0/1, active=1, waiting=0)
[λμ DB μ‘°ν] μ€ν¨ | μμ=3005ms
[λμ DB μ‘°ν] μμΈ=CannotGetJdbcConnectionException
[λμ DB μ‘°ν] λ©μμ§=Failed to obtain JDBC Connection
[DelayedMock] μ§μ° μ’
λ£ - μλ΅ λ°ν
[μλ£ ν] active=0, idle=1
=====================================

OpenAI μλ΅μ κΈ°λ€λ¦¬λ λμ active=1, idle=0 μνκ° μ μ§λμ΅λλ€. κ°μ μμ μ μ€νν DB μ‘°νλ 컀λ₯μ μ ν λΉλ°μ§ λͺ»ν΄ 3,005ms ν CannotGetJdbcConnectionExceptionμΌλ‘ μ€ν¨νμ΅λλ€.
After - μΈλΆ API νΈμΆμ νΈλμμ λ°μΌλ‘ λΆλ¦¬
========== 컀λ₯μ
μ μ μΈ‘μ ==========
[μ΄κΈ°] active=0, idle=1, waiting=0
[DelayedMock] OpenAI νΈμΆ μ§μ
- 10000ms μ§μ° μμ
[OpenAI λκΈ° μ€] active=0, idle=1, waiting=0
[λμ DB μ‘°ν] μ±κ³΅ | μμ=8ms | κ²°κ³Ό=1
[DelayedMock] μ§μ° μ’
λ£ - μλ΅ λ°ν
[μλ£ ν] active=0, idle=1
=====================================

λμΌν 10μ΄ μ§μ° νκ²½μμλ active=0, idle=1 μνκ° μ μ§λμ΄ μ»€λ₯μ μ΄ νμ λ°νλ κ²μ νμΈνμ΅λλ€. κ°μ μμ μ μ€νν DB μ‘°νλ 8ms λ§μ μ μ μ²λ¦¬λμ΅λλ€.
| μΈ‘μ νλͺ© | Before | After |
| OpenAI λκΈ° μ€ Hikari active | 1 | 0 |
| OpenAI λκΈ° μ€ Hikari idle | 0 | 1 |
| λμ DB μ‘°ν | 3,005ms ν μ€ν¨ | 8ms μ±κ³΅ |
| μ‘°ν κ²°κ³Ό | CannotGetJdbcConnectionException | μ μ μ‘°ν |
μΈ‘μ 쑰건: HikariCP maximum-pool-size=1, connection-timeout=3,000ms, OpenAI Mock delay=10s
μΈ‘μ κ²°κ³Όλ ν¨κ» κ²μ¦
첫 μΈ‘μ μμλ κΈ°λνλ active=0μ΄ λμμ§λ§, νμΈν΄λ³΄λ μ§μ° Mock μμ²΄κ° νμ±νλμ§ μμ μνμμ΅λλ€. μ 체 μ€ν μκ°κ³Ό Mock μ§μ λ‘κ·Έλ₯Ό νμΈν΄ ν΄λΉ μΈ‘μ μ΄ μ ν¨νμ§ μλ€λ κ²μ λ°κ²¬νκ³ , μΈ‘μ νκ²½μ μμ ν λ€ Beforeμ Afterλ₯Ό λμΌν 쑰건μμ λ€μ μΈ‘μ νμ΅λλ€.
μ΄λ₯Ό ν΅ν΄ μνλ μμΉκ° λμλμ§λ§ νμΈνλ κ²μ΄ μλλΌ, μ€μ λ‘ μλν 쑰건μμ μΈ‘μ μ΄ μ΄λ£¨μ΄μ‘λμ§λ ν¨κ» νμΈνμ΅λλ€.
5. λ¨μ νκ³
μ΄λ² μΈ‘μ μ λ¨μΌ AI κ²μ¦ μμ²μ κΈ°μ€μΌλ‘ μ§ννμ΅λλ€. λ°λΌμ λμ κ²μ¦ μμ² μμ 컀λ₯μ ν ν¬κΈ°λ₯Ό λ¨κ³μ μΌλ‘ μ¦κ°μμΌ°μ λ μ²λ¦¬λκ³Ό λκΈ° μκ°μ΄ μ΄λ»κ² λ¬λΌμ§λμ§κΉμ§λ νμΈνμ§ λͺ»νμ΅λλ€.
maximum-pool-size=1 μμ 컀λ₯μ κ²½ν©μ λͺ ννκ² κ΄μ°°νκΈ° μν μΈ‘μ 쑰건μ λλ€. μ΄μ νκ²½μ ν ν¬κΈ°κ° λ ν¬λλΌλ μΈλΆ API νΈμΆ μ€ νΈλμμ μ΄ μ μ§λλ ꡬ쑰μμλ λμ μμ²μ΄ μ¦κ°ν μλ‘ λμΌν μ νμ 컀λ₯μ κ²½ν©μ΄ λ°μν κ°λ₯μ±μ΄ μμ΅λλ€.
λν μΈλΆ API νΈμΆ μ΅μ’ μ€ν¨ μμλ PENDING_ADMIN_REVIEW μνλ‘ μ ννκ³ κ΄λ¦¬μκ° λ€μ κ²μ¦ν μ μλ κ²½λ‘λ₯Ό λμμ§λ§, λ°±μ€ν κΈ°λ° μλ μ¬μλλ μ€μΌμ€λ¬ κΈ°λ° μλ νμκΉμ§λ ꡬννμ§ λͺ»νμ΅λλ€.
6. μ 리
μ½λλ₯Ό μ½μΌλ©΄μ λ¬Έμ κ°λ₯μ±μ νμΈν μ μμμ§λ§, μ€μ λ‘ μ΄λ μ λμ μν₯μ μ£Όλμ§λ μΈ‘μ μ΄ νμνμ΅λλ€. μ§μ μΈ‘μ ν λ€μλ "컀λ₯μ νμ΄ κ³ κ°λ μ μλ€"λ κ°λ₯μ± λμ "10μ΄ μ§μ° νκ²½μμ λμ DB μ‘°νκ° 3,005ms ν CannotGetJdbcConnectionExceptionμΌλ‘ μ€ν¨νλ€"λ κ²°κ³Όλ‘ λ¬Έμ λ₯Ό μ€λͺ ν μ μμμ΅λλ€.
μΈ‘μ κ³Όμ μμλ κ²°κ³Ό μμ²΄κ° μ λ’°ν μ μλμ§λ ν¨κ» νμΈνμ΅λλ€. 첫 μ€νμμ κΈ°λνλ μμΉκ° λμλλΌλ μ€μ μ§μ° Mockμ΄ μ€νλμ§ μμλ€λ©΄ μ ν¨ν κ²°κ³Όκ° μλμμ΅λλ€. κ·Έλμ μμΉλΏ μλλΌ λ‘κ·Έμ μ€ν 쑰건κΉμ§ νμΈν λ€ λμΌν 쑰건μμ λ³κ²½ μ νλ₯Ό λ€μ λΉκ΅νμ΅λλ€.
μ΄λ² κ°μ μ ν΅ν΄ λΉλκΈ° μ²λ¦¬ μ¬λΆμ νΈλμμ μ λ²μλ λ³κ°μ λ¬Έμ λΌλ μ μ νμΈνμ΅λλ€. μΈλΆ API νΈμΆμ λ³λ μ€λ λλ‘ λΆλ¦¬νλλΌλ κ·Έ λ΄λΆμμ νΈλμμ μ΄ κ³μ μ μ§λλ©΄ API μλ΅μ κΈ°λ€λ¦¬λ λμ DB 컀λ₯μ λ ν¨κ» μ μ λ μ μμ΅λλ€.
μ΄ν λΉμ·ν ꡬ쑰λ₯Ό μ€κ³ν λλ μΈλΆ API νΈμΆκ³Ό μ€μ DB μμ μ κ²½κ³λ₯Ό λ¨Όμ ꡬλΆνκ³ , λ€νΈμν¬ λκΈ° ꡬκ°μ λΆνμν νΈλμμ κ³Ό 컀λ₯μ μ μ κ° ν¬ν¨λμ΄ μμ§ μμμ§ νμΈνλ € ν©λλ€.