λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
회고

DB Function to Java Application 이관 (feat. ν”„λ‘œμ‹œμ €)

by 주발2 2024. 10. 14.
λ°˜μ‘ν˜•

이번 ν¬μŠ€νŒ…μ—μ„œλŠ” μ „ 직μž₯μ—μ„œ μ§„ν–‰ν–ˆλ˜ DB Function λ‘œμ§μ„ Java Application으둜 μ΄κ΄€ν•˜λŠ” μž‘μ—…μ— λŒ€ν•΄ μ •λ¦¬ν•˜κ³ μž ν•©λ‹ˆλ‹€. μ‹œκ°„μ΄ λ‹€μ†Œ ν˜λ €μ§€λ§Œ, 이 μž‘μ—… 과정을 λ˜λŒμ•„λ³΄κ³ μž ν•˜λŠ” λͺ©μ λ„ 있고, μœ μ‚¬ν•œ μž‘μ—…μ„ 진행할 λ•Œ μ°Έκ³ ν•  수 μžˆλ„λ‘ νŠΈλŸ¬λΈ”μŠˆνŒ… 및 κ³ λ € 사항 등을 정리해 보렀 ν•©λ‹ˆλ‹€.

 

ν¬μŠ€νŒ…μ€ κ°œμš” - κ³Όμ • - κ²°κ³Ό(μ„±κ³Ό) - 회고 순으둜 κ΅¬μ„±ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

 

κ°œμš”

기쑴의 DB Function은 쿠폰 λ°œκΈ‰ APIλ₯Ό μ²˜λ¦¬ν•˜λŠ” ν•¨μˆ˜λ‘œ, μ™ΈλΆ€ μ œνœ΄μ‚¬κ°€ λ°œκΈ‰ μš”μ²­μ„ ν•˜λ©΄ λ‚΄λΆ€μ μœΌλ‘œ μ—¬λŸ¬ ν”„λ‘œμ‹œμ €λ“€μ„ 순차적으둜 ν˜ΈμΆœν•˜μ—¬ 검증, λ‘œκΉ…, INSERT, UPDATE λ“± 쿠폰 λ°œκΈ‰μ„ μ²˜λ¦¬ν•˜λŠ” λ‘œμ§μ΄μ—ˆμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ 이 λ‘œμ§μ—λŠ” μ—¬λŸ¬ λ¬Έμ œκ°€ μžˆμ—ˆκ³ , 이λ₯Ό κ°œμ„ ν•˜κΈ° μœ„ν•΄ 이번 μž‘μ—…μ„ μ§„ν–‰ν•˜κ²Œ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

 

DB Function이 가지고 있던 μ£Όμš” λ¬Έμ œμ μ€ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  1. λ””λ²„κΉ…μ˜ 어렀움: DB ν•¨μˆ˜λ‘œ μž‘μ„±λœ 둜직이라 디버깅이 거의 λΆˆκ°€λŠ₯ν•œ μƒν™©μ΄μ—ˆμŠ΅λ‹ˆλ‹€.
  2. 슬둜우 쿼리 λ°œμƒ: κ°„ν—μ μœΌλ‘œ 슬둜우 쿼리가 λ°œμƒν•˜κ³  μžˆμœΌλ‚˜ μ •ν™•νžˆ μ–΄λŠ μ§€μ μ—μ„œ 병λͺ©μ΄ λ°œμƒν•˜λŠ”μ§€ νŒŒμ•…ν•˜κΈ°κ°€ μ–΄λ €μ› μŠ΅λ‹ˆλ‹€.
  3. μœ μ§€ 보수의 어렀움: λΆˆν•„μš”ν•œ μΏΌλ¦¬λ¬Έμ΄λ‚˜ λ¬΄μ˜λ―Έν•œ 둜그 ν…Œμ΄λΈ” μ €μž₯, λΆˆν•„μš”ν•œ μΆ”κ°€ 쑰회 쿼리, ν”„λ‘œμ‹œμ € 내에 κ΅¬ν˜„λœ μž¬μ‹œλ„(retry) 둜직 λ“± λΉ„νš¨μœ¨μ μΈ 뢀뢄이 λ‹€μ†Œ μ‘΄μž¬ν–ˆμŠ΅λ‹ˆλ‹€.
  4. 였랜 κΈ°κ°„ 방치된 μ½”λ“œ: μ˜€λž«λ™μ•ˆ μ½”λ“œκ°€ κ°œμ„ λ˜μ§€ μ•Šμ•˜κΈ°μ— νžˆμŠ€ν† λ¦¬ 관리가 λ˜μ§€ μ•Šκ³ , 주석과 μ‹€μ œ 둜직이 λΆˆμΌμΉ˜ν•˜λŠ” λ“±μ˜ 문제점이 μ‘΄μž¬ν–ˆμŠ΅λ‹ˆλ‹€.

이와 같은 λ¬Έμ œλ“€λ‘œ 인해 DB Function λ‘œμ§μ„ Java Application으둜 κ°œμ„ ν•˜λŠ” μž‘μ—…μ„ μ§„ν–‰ν•˜κ²Œ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

 

 

κ³Όμ •

κ°œμ„  μž‘μ—…μ˜ 과정은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  • κΈ°μ‘΄ 둜직 뢄석
  • 이관 일정 수립
  • 점진적 이관 (ν”„λ‘œμ‹œμ € λ‹¨μœ„)
  • κ°œμ„ 
  • ν…ŒμŠ€νŠΈ
  • 배포, λ‘€λ°±, λͺ¨λ‹ˆν„°λ§
  • νŠΈλŸ¬λΈ” μŠˆνŒ…

 

λ˜ν•œ Java둜 이관 μž‘μ—…μ„ μ§„ν–‰ν•˜λ©΄μ„œ κ³ λ €ν•΄μ•Ό ν–ˆλ˜ 사항듀은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€:

  • 기쑴의 둜직과 μ‹ κ·œ 둜직의 κ²°κ³Όκ°€ λ™μΌν•œμ§€ μ–΄λ–»κ²Œ 검증할 것인가? (ν…ŒμŠ€νŠΈ)
  • 운영 ν™˜κ²½μ—λŠ” μ–΄λ–»κ²Œ 배포λ₯Ό ν•  것인가? (배포 μ „λž΅)
  • 배포 ν›„ μž₯μ• κ°€ λ°œμƒν–ˆμ„ λ•Œ ν›„μ²˜λ¦¬ 방식은 μ–΄λ–»κ²Œ ν•΄μ•Ό ν•˜λŠ”κ°€? (데이터 μ •ν•©μ„±, λ‘€λ°± μ „λž΅ λ“±)

 

μœ„ λͺ©λ‘λ“€μ„ λ°”νƒ•μœΌλ‘œ ꡬ체적으둜 μ •λ¦¬ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

 

κΈ°μ‘΄ 둜직 뢄석

 

κΈ°μ‘΄ DB Function으둜 μž‘μ„±λœ λ‘œμ§μ€ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

(ν”„λ‘œμ‹œμ €λͺ…, ν•„λ“œλͺ… λ“± μ£Όμš” λ‚΄μš©λ“€μ€ λΈ”λΌμΈλ“œ μ²˜λ¦¬ν•˜μ˜€μŠ΅λ‹ˆλ‹€ ^^;)

 

λŒ€λž΅μ μœΌλ‘œ μš”μ²­ 정보 μ €μž₯, μœ νš¨μ„± 검증, 번호 쑰회 및 λ°œκΈ‰ 처리, μ‹œν€€μŠ€ 쑰회, 쿠폰 λ°œν–‰ 처리 λ“±μ˜ 과정을 순차적으둜 μ²˜λ¦¬ν•©λ‹ˆλ‹€.

 

이미지λ₯Ό 톡해 큰 흐름은 νŒŒμ•…ν•  수 μžˆμ—ˆμœΌλ‚˜, μ•žμ„œ λ§μ”€λ“œλ¦° κ²ƒμ²˜λŸΌ νžˆμŠ€ν† λ¦¬ 관리가 μ œλŒ€λ‘œ 이루어지지 μ•Šμ•˜μŠ΅λ‹ˆλ‹€. 이둜 인해, ν”„λ‘œμ‹œμ € 쿼리 처리 κ³Όμ •μ—μ„œ μ΄ν•΄λ˜μ§€ μ•ŠλŠ” 뢀뢄에 λŒ€ν•΄μ„œλŠ” ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ₯Ό μΆ”κ°€ν•˜μ—¬ 이해도λ₯Ό λ†’μ—¬κ°”μŠ΅λ‹ˆλ‹€.

(μ‹€μ œ ν…ŒμŠ€νŠΈ κ³Όμ •μ—μ„œ, ν”„λ‘œμ‹œμ € 둜직이 λ³„λ„μ˜ νŠΈλžœμž­μ…˜μ—μ„œ μ²˜λ¦¬λ˜μ–΄ μŠ€ν”„λ§ ν…ŒμŠ€νŠΈμ—μ„œ μ‚¬μš©ν•˜λ˜ @Transactional이 λ‘€λ°± λ˜μ§€ μ•ŠλŠ” μ΄μŠˆκ°€ λ°œμƒν•˜κΈ°λ„ ν–ˆμŠ΅λ‹ˆλ‹€.)

 

μ΄λŸ¬ν•œ κ³Όμ •μ—μ„œ κΈ°μ‘΄ ν”„λ‘œμ‹œμ € λ‘œμ§μ„ 뢄석해 λ³΄λ‹ˆ 주석과 μ‹€μ œ 둜직이 λ‹€λ₯Έ κ²½μš°λ„ λ§Žμ•˜κ³ , μ•žμ„œ μ–ΈκΈ‰ν•œ κ²ƒμ²˜λŸΌ μ‚¬μš©λ˜μ§€ μ•ŠλŠ” μ½”λ“œλ‚˜ λΉ„νš¨μœ¨μ μΈ λ‘œμ§λ“€μ΄ λ‹€μˆ˜ μ‘΄μž¬ν–ˆμŠ΅λ‹ˆλ‹€. 이둜 인해 κ°œμ„  μž‘μ—…μ„ λ”μš± λΉ λ₯΄κ²Œ 진행할 ν•„μš”μ„±μ„ 느끼게 된 것 κ°™μŠ΅λ‹ˆλ‹€.

 

 

이관 일정 수립

이관 일정은 각 ν”„λ‘œμ‹œμ €μ˜ 규λͺ¨μ— 따라 μ‘°κΈˆμ”© λ‹€λ₯΄κ²Œ μž‘μ•˜κ³ , λ‹€λ₯Έ μ—…λ¬΄λ‚˜ λ°œμƒν•  수 μžˆλŠ” μ‚¬μ΄λ“œ μ΄νŽ™νŠΈλ„ μ—¬μœ  μžˆλŠ” 일정을 μ„€μ •ν•˜κ³  μž‘μ—…μ„ μ§„ν–‰ν–ˆμŠ΅λ‹ˆλ‹€.

 

 

점진적 이관 (ν”„λ‘œμ‹œμ € λ‹¨μœ„)

이관은 μœ„ μΌμ •μ²˜λŸΌ ν”„λ‘œμ‹œμ € λ‹¨μœ„λ‘œ ν•˜λ‚˜μ”© μ΄κ΄€ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

 

As-Is) κΈ°μ‘΄μ—λŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ DB Function을 ν˜ΈμΆœν•˜κ³ , κ·Έ μ•ˆμ—μ„œ μ—¬λŸ¬ ν”„λ‘œμ‹œμ €λ“€μ„ λ‚΄λΆ€μ μœΌλ‘œ ν˜ΈμΆœν•˜λŠ” κ΅¬μ‘°μ˜€μŠ΅λ‹ˆλ‹€.

To-Be) 이λ₯Ό μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ 각각의 ν”„λ‘œμ‹œμ €λ“€μ„ 순차적으둜 직접 ν˜ΈμΆœν•˜λ„λ‘ μˆ˜μ •ν–ˆλŠ”λ°, 이 κ³Όμ •μ—μ„œ μ˜ˆμƒμΉ˜ μ•Šκ²Œ 슬둜우 쿼리의 원인을 λ°”λ‘œ νŒŒμ•…ν•  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

 

κ²½κ³Ό μ‹œκ°„, νŠΈλžœμž­μ…˜(URI), HTTP 호좜 수, SQL 건수, SQL μ‹œκ°„

κΈ°μ‘΄ λ‘œμ§μ—λŠ” DB Function 호좜만 λͺ¨λ‹ˆν„°λ§μ— μž‘νžˆκΈ°μ— SQL 쿼리 κ±΄μˆ˜κ°€ 1회둜만 κΈ°λ‘λ˜μ—ˆκΈ°μ— 전체 쿼리 결과만 확인할 수 μžˆμ—ˆμ§€λ§Œ, 둜직 λ³€κ²½ ν›„μ—λŠ” 각 ν”„λ‘œμ‹œμ €μ˜ 쿼리가 κ°œλ³„μ μœΌλ‘œ λͺ¨λ‹ˆν„°λ§λ˜μ—ˆκΈ° λ•Œλ¬Έμ—, μ–΄λŠ ν”„λ‘œμ‹œμ €μ—μ„œ 병λͺ©μ΄ λ°œμƒν•˜λŠ”μ§€ μ •ν™•νžˆ νŒŒμ•…ν•  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

 

 

κ°œμ„ 

슬둜우 쿼리의 원인은 MAX() ν•¨μˆ˜ μ‚¬μš©κ³Ό λΉ„νš¨μœ¨μ μΈ retry 둜직 κ΅¬ν˜„ λ“±μœΌλ‘œ 인해 λ°œμƒν•˜κ³  μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

# ν”„λ‘œμ‹œμ € 둜직
SELECT MAX(SEQ) FROM ... WHERE μƒν’ˆ_ID = ? AND ...

INSERT ...

# μ˜ˆμ™Έ λ°œμƒ μ‹œ (retry = 1)
SELECT MAX(SEQ) FROM ... WHERE μƒν’ˆ_ID = ? AND ...

INSERT ...

# μ˜ˆμ™Έ λ°œμƒ μ‹œ (retry = 2, SEQ = SEQ + 1)
SELECT MAX(SEQ) FROM ... WHERE μƒν’ˆ_ID = ? AND ...

INSERT ...

# μ˜ˆμ™Έ λ°œμƒ μ‹œ (retry = 3, SEQ = SEQ + 1)
SELECT MAX(SEQ) FROM ... WHERE μƒν’ˆ_ID = ? AND ...

INSERT ...

...

 

μ €λ ‡κ²Œ κ΅¬ν˜„λœ νžˆμŠ€ν† λ¦¬κΉŒμ§€λŠ” μ•Œ 수 μ—†μ§€λ§Œ, 쑰회 μ‘°κ±΄μ—μ„œ μ‚¬μš©λœ WHERE 절의 μ»¬λŸΌμ€ 볡합 인덱슀둜 μ„€μ •λ˜μ–΄ μžˆμ—ˆμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ SEQκ°€ μΈλ±μŠ€μ— ν¬ν•¨λ˜μ§€ μ•Šμ•˜κΈ° λ•Œλ¬Έμ— 데이터가 μ¦κ°€ν• μˆ˜λ‘ μ„±λŠ₯이 μ €ν•˜λ˜λŠ” λ¬Έμ œκ°€ λ°œμƒν•©λ‹ˆλ‹€. μ΄λŠ” λ°œκΈ‰μ΄ μ΄λ£¨μ–΄μ§ˆ λ•Œλ§ˆλ‹€ ν•΄λ‹Ή ν…Œμ΄λΈ”μ— λˆ„μ ν•΄μ„œ μŒ“μ΄κΈ° λ•Œλ¬Έμ—, λ°œκΈ‰λŸ‰μ΄ λ§Žμ€ μƒν’ˆμΌμˆ˜λ‘ 더 λ§Žμ€ 데이터가 μŒ“μ΄λŠ” μƒν™©μ΄μ—ˆμŠ΅λ‹ˆλ‹€.

 

μœ„μ—μ„œ μ‹€ν–‰λ˜λŠ” `μƒν’ˆ_ID`에 ν•΄λ‹Ήν•˜λŠ” μƒν’ˆλ“€μ„ μ‚΄νŽ΄λ³΄λ‹ˆ, λ°œκΈ‰λŸ‰μ΄ μƒλŒ€μ μœΌλ‘œ λ§Žμ€ μƒν’ˆλ“€μ€ λͺ¨λ‘ λ™μΌν•œ SEQ 값을 λ°˜ν™˜ν•˜κ³  μžˆμ—ˆμŠ΅λ‹ˆλ‹€. SEQ 값은 예λ₯Ό λ“€μ–΄ A01λΆ€ν„° Z99κΉŒμ§€(A02, A03 ... Z99) 1μ”© μ¦κ°€ν•˜λ©΄μ„œ λ§Œλ“€μ–΄μ§€κΈ° λ•Œλ¬Έμ—, λ°œκΈ‰μ΄ 자주 λ°œμƒν•˜λŠ” 경우 ν•΄λ‹Ή μƒν’ˆμ˜ SEQ 값은 λͺ¨λ‘ 'Z99'둜 λ™μΌν•œ κ²°κ³Όλ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

 

μœ„ 슬둜우 쿼리λ₯Ό κ°œμ„ ν•˜κΈ° μœ„ν•΄ 1) 쿼리 κ°œμ„ , 2) 캐싱 적용, 3) 인덱슀 μΆ”κ°€ μ„Έ 가지 λ°©λ²•μœΌλ‘œ μ ‘κ·Όν•˜μ˜€μŠ΅λ‹ˆλ‹€.

 

사싀 근본적인 해결책은 인덱슀λ₯Ό μΆ”κ°€ν•˜λŠ” κ²ƒμ΄μ§€λ§Œ, 인덱슀 μž‘μ—…μ€ DBA 뢄이 진행해야 ν•˜κΈ°μ— μ΅œμ†Œ 1~2μ£Όκ°€ μ†Œμš”λ˜λŠ” μž‘μ—…μž…λ‹ˆλ‹€. λ˜ν•œ ν•΄λ‹Ή ν…Œμ΄λΈ”μ˜ λ°μ΄ν„°λŠ” μˆ˜λ…„~μˆ˜μ‹­ λ…„κ°„ λˆ„μ λ˜μ—ˆκΈ°μ— 인덱슀 μΆ”κ°€λ₯Ό μœ„ν•΄μ„œλŠ” κ³Όκ±° 데이터 μ‚­μ œκ°€ λ¨Όμ € ν•„μš”ν–ˆμŠ΅λ‹ˆλ‹€. 이 μž‘μ—… λ˜ν•œ 2~3μ£ΌλŠ” μ†Œμš”λ˜λŠ” μƒν™©μ΄μ—ˆκΈ°μ—, 데이터 μ‚­μ œ 및 인덱슀 μž‘μ—…μ΄ μ²˜λ¦¬λ˜λŠ” λ™μ•ˆ, 캐싱을 μ μš©ν•˜μ—¬ κ°œμ„ ν•˜κ³ μž ν–ˆμŠ΅λ‹ˆλ‹€.

 

1) 쿼리 κ°œμ„ 

쿼리 κ°œμ„ μ€ 이전에 μ½μ—ˆλ˜ μΉœμ ˆν•œ SQL νŠœλ‹ λ‚΄μš©μ—μ„œ 'Top N' 쿼리λ₯Ό μ μš©ν•  수 μžˆμ„ 것 κ°™μ•˜μŠ΅λ‹ˆλ‹€.

μΉœμ ˆν•œ SQL νŠœλ‹ 일뢀

μœ„ λ‚΄μš©μ„ 기반으둜 κΈ°μ‘΄ 쿼리와 κ³ λ―Όν–ˆλ˜ μΏΌλ¦¬λŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

 

# κΈ°μ‘΄
SELECT NVL(MAX(SEQ), '0')
FROM ...
WHERE μƒν’ˆ_ID = ? AND ...


SORT AGGREGATEλŠ” 전체 둜우λ₯Ό λŒ€μƒμœΌλ‘œ 집계λ₯Ό μˆ˜ν–‰ν•  λ•Œ λ‚˜νƒ€λ‚˜λŠ” ν”Œλžœμž…λ‹ˆλ‹€.

μΉœμ ˆν•¨ SQL νŠœλ‹ - 5.1.2 Sort Aggregate

λŒ€λž΅μ μœΌλ‘œ μœ„μ™€ 같이 μˆ˜ν–‰μ΄ λ˜λŠ”λ°μš”, λ”°λΌμ„œ 기쑴의 MAX(SEQ) 쿼리도 전체 둜우λ₯Ό λŒ€μƒμœΌλ‘œ μˆ˜ν–‰ν•˜λŠ” 것 κ°™μŠ΅λ‹ˆλ‹€.

 

# Top N
SELECT SEQ 
FROM ( 
	SELECT SEQ 
	FROM ...
	WHERE μƒν’ˆ_ID = ? AND ...
	ORDER BY SEQ DESC
)
WHERE ROWNUM = 1;

COUNT(STOPKEY)μ—μ„œ ROWNUM으둜 μ§€μ •λœ 건수만큼 λ ˆμ½”λ“œλ₯Ό μ–»μœΌλ©΄ λ°”λ‘œ 멈좜 κ²ƒμœΌλ‘œ μ˜ˆμƒμ„ ν–ˆμ§€λ§Œ, κ·Έ μ•„λž˜ 뢀뢄에SORT(ORDER BY STOPKEY)κ°€ μ‘΄μž¬ν•˜λŠ” κ²ƒμœΌλ‘œ 보아 μœ„ μΏΌλ¦¬μ—μ„œλŠ” 데이터 정렬이 ν•„μš”ν•©λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ SEQκ°€ 인덱슀둜 섀정이 λ˜μ–΄ μžˆμ§€ μ•ŠκΈ° λ•Œλ¬Έμ— κ²°κ΅­ SEQ κΈ°μ€€μœΌλ‘œ 데이터 정렬이 ν•„μš”ν•˜κ³ , 이둜 인해 κ°œμ„  효과λ₯Ό 보지 λͺ»ν–ˆμŠ΅λ‹ˆλ‹€.

 

κΈ°μ‘΄ μΏΌλ¦¬λŠ” μ΅œλŒ“κ°’μ„ κ³„μ‚°ν•˜μ§€λ§Œ, 두 번째 μΏΌλ¦¬λŠ” μ •λ ¬λœ 데이터 쀑 첫 번째 값을 κ°€μ Έμ˜€λŠ” 방식이라고 μ΄ν•΄ν–ˆκ³ , 그둜 인해 SEQκ°€ κΈ°μ‘΄ μΈλ±μŠ€μ™€ ν•¨κ»˜ 볡합 인덱슀둜 μ„€μ •λœλ‹€λ©΄ μ •λ ¬ 없이 λΉ λ₯΄κ²Œ κ°€μ Έμ˜¬ 수 μžˆκΈ°μ—, κ²°κ΅­ μΈλ±μŠ€κ°€ ν•„μš”ν•œ 상황과 λ™μΌν•˜λ‹€κ³  νŒλ‹¨ν–ˆμŠ΅λ‹ˆλ‹€.

 

2) 캐싱 적용

μƒν’ˆ 쀑 λ°œκΈ‰μ΄ 자주 λ°œμƒν•˜κ³  SEQ 값이 λ™μΌν•œ μƒν’ˆμ— λŒ€ν•΄μ„œλŠ” 캐싱을 μ μš©ν•˜μ—¬ μœ„μ˜ 슬둜우 쿼리가 λ°œμƒν•˜λŠ” ν”„λ‘œμ‹œμ € λ‘œμ§μ„ ν˜ΈμΆœν•˜μ§€ μ•Šλ”λΌλ„ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ‹¨μ—μ„œ λΉ λ₯΄κ²Œ 처리될 수 μžˆλ„λ‘ ν–ˆμŠ΅λ‹ˆλ‹€.

 

ν•΄λ‹Ή 데이터(SEQ)λŠ” ν•œ 번 μ΅œλŒ“κ°’μ— λ„λ‹¬ν•˜λ©΄ κ°’ μžμ²΄κ°€ κ°±μ‹ λ˜μ§€ μ•Šκ³ , μƒν’ˆ λ˜ν•œ μΆ”κ°€λ˜μ§€ μ•ŠλŠ” 상황이기 λ•Œλ¬Έμ— 좔가적인 μž‘μ—… 및 운영이 ν•„μš”ν•œ κΈ€λ‘œλ²Œ 캐싱은 크게 κ³ λ €ν•˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.
λΆ„μ‚° μ„œλ²„λ₯Ό 운영 μ€‘μ΄μ§€λ§Œ λ°μ΄ν„°μ˜ κ°±μ‹ μ΄λ‚˜ μ‚­μ œ μž‘μ—…λ„ μ—†μ—ˆκΈ°μ— 둜컬 μΊμ‹œ(EhCache, Caffeine) 등도 λ„μž…ν•˜μ§€λŠ” μ•Šμ•˜μŠ΅λ‹ˆλ‹€.

 

3) 인덱슀 μΆ”κ°€

인덱슀 μΆ”κ°€λ₯Ό μœ„ν•΄μ„œλŠ” λ¨Όμ € 데이터 μ‚­μ œκ°€ ν•„μš”ν•œ μƒν™©μ΄μ—ˆμŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ κ³Όκ±° nλ…„μΉ˜μ˜ 데이터λ₯Ό λ¨Όμ € μ‚­μ œν•œ ν›„, 이후에 SEQ 값에 λŒ€ν•΄ 좔가적인 볡합 인덱슀λ₯Ό μ„€μ •ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

 

 

κ°œμ„  κ²°κ³Ό

As-Is) 캐싱 μ „

 

To-Be) 캐싱 ν›„

μœ„ μž‘μ—…λ“€μ„ μ²˜λ¦¬ν•œ κ²°κ³Ό, 전체 νŠΈλž˜ν”½ 쀑 μΌλΆ€λ§Œ μ‹ κ·œ 둜직으둜 μœ μž…λ˜κ³  μžˆμ–΄ κΈ°μ‘΄ 둜직과 μ‹ κ·œ 둜직의 속도 차이가 ν™•μ—°ν•˜κ²Œ μ‘΄μž¬ν•©λ‹ˆλ‹€.

 

 

ν…ŒμŠ€νŠΈ

ν…ŒμŠ€νŠΈλŠ” λ™μΌν•œ μš”μ²­κ°’μœΌλ‘œ κΈ°μ‘΄ 둜직과 μ‹ κ·œ λ‘œμ§μ„ 각각 ν˜ΈμΆœν•œ ν›„, 결과와 데이터 정합성이 λ™μΌν•œμ§€λ₯Ό λΉ„κ΅ν•˜λŠ” μ„€λ„μž‰ λ°©μ‹μœΌλ‘œ 검증을 ν–ˆμŠ΅λ‹ˆλ‹€.

μ„€λ„μž‰(shadowing)은, λ™μΌν•œ μš”μ²­μ— λŒ€ν•΄ κΈ°μ‘΄ μ„œλΉ„μŠ€μ™€ μƒˆλ‘œμš΄ μ„œλΉ„μŠ€μ˜ 응닡이 μΌμΉ˜ν•˜λŠ”μ§€ λΉ„κ΅ν•˜λŠ” 방법을 μ˜λ―Έν•©λ‹ˆλ‹€.

데이터가 Insertλ‚˜ Update λ˜λŠ” ν…Œμ΄λΈ”μ΄ λ§Žμ§€λŠ” μ•Šμ•˜κΈ°μ— 직접 데이터λ₯Ό 확인할 수 μžˆλ„λ‘ SQL ν…œν”Œλ¦Ώμ„ λ§Œλ“€κ³  각 ν˜ΈμΆœλ§ˆλ‹€ λΉ„κ΅ν•˜λ©° μž‘μ—…μ„ μ§„ν–‰ν–ˆμ—ˆλŠ”λ°μš”, ν…ŒμŠ€νŠΈλ₯Ό 진행할 λ•Œλ§ˆλ‹€ 맀번 쿼리λ₯Ό 직접 μ‹€ν–‰ν•΄μ„œ λΉ„κ΅ν•˜λŠ” μž‘μ—…μ΄ λ²ˆκ±°λ‘œμ› κΈ°μ—, 이λ₯Ό κ°„μ†Œν™”ν•˜κΈ° μœ„ν•΄ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ‚΄μ—μ„œ λΉ„κ΅ν•˜λŠ” λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό 톡해 APIκ°€ 호좜될 λ•Œλ§ˆλ‹€ κ³Όκ±° 둜직과 μ‹ κ·œ λ‘œμ§μ„ λ™μ‹œμ— ν˜ΈμΆœν•˜κ³ , 데이터 정합성을 μžλ™μœΌλ‘œ λΉ„κ΅ν•˜μ—¬ μ’€ 더 νŽΈλ¦¬ν•˜κ²Œ 비ꡐ할 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

 

μž‘μ—…μ„ μ§„ν–‰ν•˜λ©° λ±…ν¬μƒλŸ¬λ“œλŠ” μ–΄λ–»κ²Œ λ ˆκ±°μ‹œ μ„œλΉ„μŠ€λ₯Ό λ°•μ‚΄ λ‚΄λŠ”κ°€ λΌλŠ” ν¬μŠ€νŒ…μ„ μ°Έκ³ ν–ˆλŠ”λ°, ν•΄λ‹Ή λΈ”λ‘œκ·Έμ—μ„œλŠ” κΈ°μ‘΄ μ„œλΉ„μŠ€μ™€ μ‹ κ·œ μ„œλΉ„μŠ€ 응닡에 λŒ€ν•œ μŠ€νƒ―(statsd)을 κΈ°λ‘ν•˜κ³ , μ‹€μ œ κ°’μ˜ μ°¨μ΄λŠ” ν‚€λ°”λ‚˜(Kibana)에 λ‘œκΉ…ν•˜μ—¬ λͺ¨λ‹ˆν„°λ§ ν•œ 뢀뢄이 μΈμƒμ μ΄μ—ˆμŠ΅λ‹ˆλ‹€.

저희가 κ°€μž₯ λ¨Όμ € μ‹œλ„ν–ˆλ˜ 건 문제λ₯Ό λ‹¨μˆœν•˜κ²Œ λ°”κΎΈμ–΄ ν”„λ‘œμ νŠΈμ˜ κ°€μ‹œμ„±μ„ ν™•λ³΄ν•˜λŠ” μΌμ΄μ—ˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό μœ„ν•΄ μ €ν¬λŠ” μ„€λ„μž‰shadowing을 λ„μž…ν–ˆμŠ΅λ‹ˆλ‹€. μ„€λ„μž‰μ€ 말 κ·ΈλŒ€λ‘œ κ°™μ€ μš”μ²­μ— λŒ€ν•΄ κΈ°μ‘΄ μ„œλΉ„μŠ€μ™€ μƒˆλ‘œμš΄ μ„œλΉ„μŠ€μ˜ 응닡이 μΌμΉ˜ν•˜λŠ”μ§€ λΉ„κ΅ν•˜λŠ” 방법을 λœ»ν•©λ‹ˆλ‹€.

λ ˆκ±°μ‹œ μ„œλΉ„μŠ€κ°€ κΈ°μ‘΄κ³Ό λ™μΌν•˜κ²Œ λͺ¨λ“  HTTP μš”μ²­μ„ μ²˜λ¦¬ν•˜μ—¬ μ μ ˆν•œ 응닡을 λ°˜ν™˜ν•˜λ˜, μ΄λ•Œ ν•΄λ‹Ή HTTP μš”μ²­μ„ μ‹ κ·œ μ„œλΉ„μŠ€λ‘œλ„ λ™μΌν•˜κ²Œ μš”μ²­μ„ 보내도둝 ν–ˆμŠ΅λ‹ˆλ‹€. 이후 μ‹ κ·œ μ„œλΉ„μŠ€μ˜ 응닡과 κΈ°μ‘΄ μ„œλΉ„μŠ€μ˜ 응닡이 μΌμΉ˜ν•˜λŠ”μ§€ μ—¬λΆ€λ₯Ό νŒλ‹¨ν•΄ 이λ₯Ό μŠ€νƒ―statsd을 찍도둝 μ²˜λ¦¬ν–ˆκ³ , μ‹€μ œλ‘œ μ–΄λ–€ 값이 μ–΄λ–»κ²Œ λ‹€λ₯Έμ§€λŠ” ν‚€λ°”λ‚˜Kibanaλ₯Ό 톡해 검색해볼 수 μžˆλ„λ‘ λ‘œκΉ…ν–ˆμŠ΅λ‹ˆλ‹€.

https://blog.banksalad.com/tech/how-banksalald-decomposes-legacy-services/

μž₯애에 λ―Όκ°ν•œ APIμ΄κ±°λ‚˜ νŠΈλž˜ν”½μ΄ λ§Žμ€ API라면 μœ„μ™€ 같이 μ™„μ „ μžλ™ν™”μ˜ 방법도 κ³ λ €ν•΄λ³Ό 수 μžˆμ„ 것 κ°™μŠ΅λ‹ˆλ‹€.

 

 

배포, λ‘€λ°±, λͺ¨λ‹ˆν„°λ§

λ°°ν¬λŠ” 일뢀 νŠΈλž˜ν”½λ§Œ μ‹ κ·œ API둜 μœ μž…ν•˜κ³ , μ μ§„μ μœΌλ‘œ νŠΈλž˜ν”½μ˜ 비쀑을 λ†’μ΄λŠ” μΉ΄λ‚˜λ¦¬ 배포와 λΉ„μŠ·ν•œ μ „λž΅μ„ μ‚¬μš©ν–ˆμŠ΅λ‹ˆλ‹€.

μ „μ‚¬μ—μ„œ μ‚¬μš© 쀑인 배포 λ„κ΅¬λŠ” μ»€μŠ€ν…€ 섀정이 λΆˆκ°€λŠ₯ν•˜κΈ°μ— μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ ˆλ²¨μ—μ„œ 랜덀으둜 일뢀 μš”μ²­(5%)만 μ‹ κ·œ API둜 μœ μž…λ˜λ„λ‘ κ΅¬ν˜„ν•˜κ³ , 이후 μ‹ κ·œ 둜직이 ν˜ΈμΆœλ˜λŠ” λΉ„μœ¨μ„ μ μ§„μ μœΌλ‘œ λŠ˜λ ΈμŠ΅λ‹ˆλ‹€.

 

초기 5%의 νŠΈλž˜ν”½λ§Œ μ‹ κ·œ λ‘œμ§μ— ν• λ‹Ήλ˜μ—ˆκΈ°μ—, μ‹ κ·œ 둜직이 μ–Έμ œ ν˜ΈμΆœλ˜λŠ”μ§€ 계속 λͺ¨λ‹ˆν„°λ§μ„ ν•΄μ•Ό ν–ˆλŠ”λ°μš”, μ§€κΈˆ 돌이켜보면 μ‹ κ·œ 둜직이 호좜될 λ•Œ μŠ¬λž™ λ©”μ‹œμ§€μ™€ 같은 μ•Œλ¦Όμ„ μ„€μ •ν–ˆλ”λΌλ©΄ μ΄λŸ¬ν•œ λΉ„νš¨μœ¨μ μΈ λͺ¨λ‹ˆν„°λ§μ„ ν”Όν•  수 μžˆμ—ˆμ„ 것 κ°™λ„€μš”. πŸ˜‚ 

λ‘€λ°± λ˜ν•œ 기쑴에 μ „μ‚¬μ—μ„œ μ‚¬μš© 쀑인 배포 도ꡬλ₯Ό κ·ΈλŒ€λ‘œ ν™œμš©ν–ˆμŠ΅λ‹ˆλ‹€. 문제 λ°œμƒ μ‹œ λ‘€λ°±μ—λŠ” μ΅œλŒ€ 2~3λΆ„ 정도 μ†Œμš”λ  수 μžˆμ—ˆμ§€λ§Œ, ν•΄λ‹Ή APIκ°€ κ·Έ μ •λ„λ‘œ λ―Όκ°ν•˜μ§€λŠ” μ•Šμ•˜κ³ , ν΄λΌμ΄μ–ΈνŠΈ μΈ‘μ—μ„œ 3회의 μž¬μ‹œλ„(Retry) 둜직이 μ‘΄μž¬ν–ˆκΈ° λ•Œλ¬Έμ— μ‹ κ·œ λ‘œμ§μ—μ„œ μ‹€νŒ¨ν•˜λ”λΌλ„ 이후 κΈ°μ‘΄ 둜직이 호좜될 κ°€λŠ₯성이 λ†’μ•˜μŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ μž₯μ•  λ°œμƒ μ‹œ 즉각적인 둀백이 λ°˜λ“œμ‹œ ν•„μš”ν•œ 상황은 μ•„λ‹ˆμ—ˆμŠ΅λ‹ˆλ‹€.

 

λ‘€λ°± μ „λž΅μ— λŒ€ν•΄μ„œλŠ” 6λ…„ 묡은 λ ˆκ±°μ‹œ, RootController λ¦¬νŒ©ν† λ§ν•˜κΈ° ν¬μŠ€νŒ…μ„ μ°Έκ³ ν–ˆλŠ”λ°, λΉ λ₯Έ νŠΈλŸ¬λΈ” μŠˆνŒ…μ΄ ν•„μš”ν•œ 상황이라면 Feature Flag 등을 ν™œμš©ν•˜μ—¬ λΉ λ₯΄κ²Œ λŒ€μ‘ν•  수 μžˆλŠ” 방법을 κ³ λ €ν•΄ λ³Ό 수 μžˆμ„ 것 κ°™μŠ΅λ‹ˆλ‹€.

μ΄λ ‡κ²Œ 큰 μž‘μ—…μ„ 진행할 λ•Œ μ˜ˆμƒμΉ˜ λͺ»ν•œ λ³€μˆ˜κ°€ 있기 λ§ˆλ ¨μž…λ‹ˆλ‹€. 29CM 앱은 μ—¬λŸ¬ ν”Œλž«νΌμ΄ ν•¨κ»˜ λ§Œλ“€κ³  λ‹€μ–‘ν•œ μ„œλΉ„μŠ€λ₯Ό μ œκ³΅ν•˜κ³  있기 λ•Œλ¬Έμ— λͺ¨λ“  μΌ€μ΄μŠ€λ₯Ό μ™„λ²½ν•˜κ²Œ μ»€λ²„ν•˜κΈ° 쉽지 μ•ŠμŠ΅λ‹ˆλ‹€. RootController νŠΉμ„±μƒ μ•± μ‚¬μš©μ΄ λΆˆκ°€λŠ₯ν•œ κ²½μš°κ°€ 생길 μˆ˜λ„ 있기 λ•Œλ¬Έμ— λΉ λ₯Έ νŠΈλŸ¬λΈ” μŠˆνŒ…μ΄ ν•„μš”ν–ˆμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ λͺ¨λ°”일 μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜ 배포 ν”„λ‘œμ„ΈμŠ€ νŠΉμ„±μƒ FE λ‚˜ BE 처럼 λΉ λ₯΄κ²Œ λŒ€μ‘ν•  수 μ—†μ£ .

이λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ μ›κ²©μœΌλ‘œ μ½”λ“œλ₯Ό λΆ„κΈ°ν•  수 μžˆλŠ” Remote Flag κΈ°λŠ₯을 μ‚¬μš©ν–ˆμŠ΅λ‹ˆλ‹€. μ μ ˆν•œ λΆ„κΈ° λ‘œμ§μ„ κ΅¬ν˜„ν•˜κ³  μ›κ²©μœΌλ‘œ ON/OFF λ₯Ό ν•  수 μžˆλ‹€λ©΄ μ΄μŠˆκ°€ λ°œμƒν–ˆμ„ λ•Œ μƒˆλ‘œμš΄ 배포없이 λΉ λ₯΄κ²Œ λŒ€μ‘ν•  수 있죠.

이 μž‘μ—…μ˜ 핡심 μ „λž΅μ€ 말 κ·ΈλŒ€λ‘œ 둀백의 여지λ₯Ό 두고 κ°œλ°œν•˜λŠ” 것에 μžˆμŠ΅λ‹ˆλ‹€. μ €ν¬λŠ” Amplitude 의 Flag κΈ°λŠ₯을 톡해 μ‹€μ‹œκ°„μœΌλ‘œ κΈ°λŠ₯의 ON/OFF μ—¬λΆ€λ₯Ό κ²°μ •ν•  수 있게 κ°œλ°œν•©λ‹ˆλ‹€. 개발의 μ•ˆμ •μ„±μ„ μœ„ν•΄, λ°μ΄ν„°μ˜ 정합성을 μœ„ν•΄, μ‹€ν—˜ κ²°κ³Όλ₯Ό μ¦‰μ‹œ λ°˜μ˜ν•˜κΈ° μœ„ν•΄ μ μ ˆν•œ λΆ„κΈ° λ‘œμ§μ„ κ°œλ°œν•˜λŠ”λ° μ‹ κ²½μ”λ‹ˆλ‹€.

 

 

νŠΈλŸ¬λΈ” μŠˆνŒ…

ν•΄λ‹Ή μž‘μ—…μ„ μ§„ν–‰ν•˜λ©° λ°œμƒν•œ λ¬Έμ œλŠ” 크게 두 가지 정도가 μžˆμ—ˆλŠ”λ°μš”, 1. λ™μ‹œμ„± 문제 와 2. μ˜ˆμƒμΉ˜ λͺ»ν•œ νŠΈλž˜ν”½ μœ μž… μž…λ‹ˆλ‹€.

두 가지 λ¬Έμ œμ— λŒ€ν•΄ μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

1. λ™μ‹œμ„± 문제

νŠΉμ • ν”„λ‘œμ‹œμ €λ₯Ό Java Application으둜 μ΄κ΄€ν•˜λŠ” κ³Όμ •μ—μ„œ λ™μ‹œμ„± λ¬Έμ œκ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.

둜컬 및 개발 μ„œλ²„μ—μ„œ μΆ©λΆ„νžˆ ν…ŒμŠ€νŠΈλ₯Ό μ§„ν–‰ν–ˆλ‹€κ³  μƒκ°ν–ˆμ§€λ§Œ, 운영 ν™˜κ²½μ—μ„œ λ°œμƒν•œ μ˜ˆμƒμΉ˜ λͺ»ν•œ μ΄μŠˆμ˜€μ—ˆλŠ”λ°μš”,

 

Whatap의 히트맡 νŠΈλžœμž­μ…˜ ;;;;

ν•΄λ‹Ή ν”„λ‘œμ‹œμ €μ˜ λ‘œμ§μ€ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  1. μš”μ²­ 데이터λ₯Ό 기반으둜 λ°œκΈ‰ κ°€λŠ₯ν•œ μƒν’ˆμ˜ 쿠폰 번호 쑰회
  2. μ‘°νšŒν•œ 쿠폰 λ²ˆν˜Έκ°€ μ€‘λ³΅λœ 쿠폰인지 체크 (쿠폰 번호λ₯Ό PK둜 μ‚¬μš©ν•˜λŠ” κ°€μ§€λŠ” ν…Œμ΄λΈ”μ— INSERT) --> λ™μ‹œμ„± 문제 λ°œμƒ
  3. λ°œκΈ‰ κ°€λŠ₯ν•œ 쿠폰 번호라면, 쿠폰 ν…Œμ΄λΈ”μ—μ„œ λ°œκΈ‰ 처리 (Update)

λ™μ‹œμ„±μ΄ λ°œμƒν•˜λŠ” κ²½μš°λŠ” 2번 λ‹¨κ³„μ—μ„œ λΉ„μŠ·ν•œ μ‹œκ°„μ— λ™μΌν•œ μƒν’ˆμ— λŒ€ν•œ λ°œκΈ‰ μš”μ²­μ΄ λ“€μ–΄μ˜¬ λ•Œ λ°œμƒν•©λ‹ˆλ‹€.

 

ν•΄λ‹Ή ν”„λ‘œμ‹œμ €κ°€ 처리되고 λ‚œ ν›„ 슬둜우 쿼리가 λ°œμƒν•˜μ—¬ νŠΈλžœμž­μ…˜μ΄ 계속 λŒ€κΈ°ν•˜λŠ” 상황이고, 그둜 인해 μš”μ²­ A와 Bκ°€ λ™μΌν•œ μƒν’ˆμ— λŒ€ν•΄ μš”μ²­μ„ ν•  경우, λ™μΌν•œ 쿼리 쑰건으둜 인해 동일 쿠폰 번호λ₯Ό μ‘°νšŒν•˜κ³ , 두 μš”μ²­ λͺ¨λ‘ 컀밋이 λ˜μ§€ μ•Šμ€ 채 슬둜우 쿼리둜 인해 μ›¨μ΄νŒ…μ΄ λ°œμƒν•˜μ—¬ μ΅œμ’…μ μœΌλ‘œ 늦게 μš”μ²­ 온 B의 경우 컀밋 μ‹œμ μ—μ„œ PK μ˜ˆμ™Έκ°€ λ°œμƒν•©λ‹ˆλ‹€.

 

μœ„ 이미지가 λ°œμƒν•œ μ΄μœ μ— λŒ€ν•œ μ„€λͺ…μΈλ°μš”, λŒ€λž΅μ μœΌλ‘œλŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  • μš”μ²­ Aκ°€ ν”„λ‘œμ‹œμ €λ₯Ό ν˜ΈμΆœν•˜μ—¬ 쿠폰 번호(1234)λ₯Ό μ‘°νšŒν•˜κ³ , INSERT 성곡 (Commit μ „)
  • 이후 AλŠ” 슬둜우 쿼리둜 인해 Waiting
  • μš”μ²­ Bκ°€ λ™μΌν•œ μƒν’ˆμœΌλ‘œ ν”„λ‘œμ‹œμ €λ₯Ό 호좜 ν›„ λ™μΌν•œ 쿠폰 번호(1234)쑰회, INSERT 성곡 (Commit μ „)
  • B도 λ§ˆμ°¬κ°€μ§€λ‘œ 슬둜우 쿼리둜 인해 Waiting
  • λ¨Όμ € ν˜ΈμΆœν•œ A의 슬둜우 쿼리가 μ’…λ£Œλœ ν›„ Commit에 성곡
  • λ’€λŠ¦κ²Œ ν˜ΈμΆœν•œ B의 경우 슬둜우 쿼리 μ’…λ£Œ ν›„ Commit을 μ‹œλ„ν•˜μ§€λ§Œ, 이 μ‹œμ μ—μ„œ PK μ˜ˆμ™Έ λ°œμƒ

λ™μΌν•œ 쿠폰 번호λ₯Ό μ‘°νšŒν•˜λŠ” μš”μ²­ A, μš”μ²­ B

 

μœ„ 문제λ₯Ό 톡해 κΈ°μ‘΄ ν”„λ‘œμ‹œμ € λ‘œμ§μ—μ„œ μ΄ν•΄ν•˜μ§€ λͺ»ν–ˆλ˜ 뢀뢄이 ν•˜λ‚˜ ν•΄κ²°λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

ν•΄λ‹Ή ν”„λ‘œμ‹œμ €μ˜ λ‘œμ§μ€ μœ„μ™€ λ™μΌν•˜κ²Œ 쿠폰 번호 쑰회 -> INSERT -> PK Exception λ°œμƒ μ‹œ λ™μΌν•˜κ²Œ 쿠폰 번호 쑰회 -> INSERT ... 의 둜직으둜 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ Retry 둜직이 ν”„λ‘œμ‹œμ € 내에 κ΅¬ν˜„λ˜μ–΄ μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

 

이 λ™μ‹œμ„± 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ κ°€μž₯ λ¨Όμ € μ ‘κ·Όν•œ 방법은 νŠΈλžœμž­μ…˜μ„ λΆ„λ¦¬ν•˜λŠ” λ°©λ²•μΈλ°μš”, (SELECT-INSERT-UPDATE) 둜직과 슬둜우 쿼리가 λ°œμƒν•˜λŠ” λ‘œμ§μ— λŒ€ν•΄ νŠΈλžœμž­μ…˜μ„ λΆ„λ¦¬ν•˜μ—¬ INSERT λ‘œμ§μ„ μˆ˜ν–‰ν•œ ν›„ μ¦‰μ‹œ Commit을 μ²˜λ¦¬ν•˜λ©΄ ν•΄κ²°λ˜μ§€ μ•Šμ„κΉŒ μƒκ°ν–ˆμŠ΅λ‹ˆλ‹€.

μœ„μ™€ 같이 μ „νŒŒ 속성을 REQUIRES_NEW 둜 μ„€μ •ν•˜μ—¬, 슬둜우 쿼리가 λ°œμƒν•˜κΈ° μ „ Commit이 λ¨Όμ € 처리되면, λ‹€λ₯Έ νŠΈλžœμž­μ…˜μ—μ„œ 쿠폰 번호λ₯Ό μ‘°νšŒν•  λ•Œ λ‹€λ₯Έ 쿠폰 번호λ₯Ό μ‘°νšŒν•  것이라고 μƒκ°ν–ˆμŠ΅λ‹ˆλ‹€.

Spring Transaction Propagation

 

 

ν•˜μ§€λ§Œ νŠΈλžœμž­μ…˜μ„ λΆ„λ¦¬ν•˜λ”λΌλ„ λ™μΌν•œ λ¬Έμ œκ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€. μš”μ²­ A(빨간색), μš”μ²­ B(νŒŒλž€μƒ‰)을 μ‹œκ°„λŒ€λ³„λ‘œ 정리해보면 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  • [11:00:15.707, exec-50] λ°œκΈ‰μš”μ²­
  • [11:00:15.710, exec-51] λ°œκΈ‰ μš”μ²­
  • [11:00:15.790, exec-50] 쿠폰 λ°œκΈ‰ 성곡
  • [11:00:15.843, exec-51] 쿠폰 λ°œκΈ‰ μ‹€νŒ¨ (PK μ˜ˆμ™Έ λ°œμƒ)

νŠΈλžœμž­μ…˜μ„ λΆ„λ¦¬ν–ˆμŒμ—λ„ 두 μš”μ²­μ΄ 0.003μ΄ˆλΌλŠ” 맀우 짧은 μ‹œκ°„ 차이둜 왔기에 컀밋 μžμ²΄κ°€ 0.003초 μ•ˆμ— μ™„λ£Œλ˜μ§€ λͺ»ν•˜λ©΄μ„œ λ™μΌν•œ λ¬Έμ œκ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.

 

λ‹€μŒμœΌλ‘œλŠ” κΈ°μ‘΄ ν”„λ‘œμ‹œμ € 둜직과 λΉ„μŠ·ν•˜κ²Œ 쿠폰 번호 쑰회 μ‹œ N개λ₯Ό 쑰회 ν•˜κ³ , λ™μΌν•œ λ‘œμ§μ„ μˆ˜ν–‰ν•˜λ˜ PK μ—λŸ¬κ°€ λ°œμƒν•˜λ©΄ try-catch둜 μ˜ˆμ™Έλ₯Ό 작고, λ‹€μŒ 쿠폰 번호둜 μ²˜λ¦¬ν•˜λ„λ‘ μž‘μ—…μ„ ν–ˆμŠ΅λ‹ˆλ‹€.

μœ„ 방식을 μ μš©ν•œ μ΄ν›„λ‘œ λ™μΌν•œ λ¬Έμ œλŠ” λ°œμƒν•˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.

(참고둜 νŠΈλžœμž­μ…˜μ˜ μ „νŒŒ 속성을 REQUIRES_NEW둜 μ„€μ •ν•˜μ§€ μ•ŠμœΌλ©΄, try-catch둜 μ˜ˆμ™Έλ₯Ό μ²˜λ¦¬ν•˜λ”λΌλ„ rollback-only marked둜 인해 둀백이 λ°œμƒν•˜μ—¬, μ˜ˆμƒν–ˆλ˜ 둜직과 λ‹€λ₯΄κ²Œ μž‘λ™ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 이와 κ΄€λ ¨ν•˜μ„œλŠ” 응? 이게 μ™œ λ‘€λ°±λ˜λŠ”κ±°μ§€? λ₯Ό μ°Έκ³ ν•΄μ£Όμ„Έμš”!)

 

μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄λ‚˜ λ°μ΄ν„°λ² μ΄μŠ€μ— μ œκ³΅ν•˜λŠ” Lock을 μ΄μš©ν•˜λ©΄ 더 κΉ”λ”ν•˜κ²Œ ν•΄κ²°ν•  수 μžˆμ—ˆμ„ 것 같은데, 이와 같이 μ²˜λ¦¬ν•œ μ΄μœ λŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  • λͺ¨λ“  μΌ€μ΄μŠ€μ—μ„œ λ™μ‹œμ„± λ¬Έμ œκ°€ λ°œμƒν•˜λŠ” 것이 μ•„λ‹Œ, νŠΉμ • μ œνœ΄μ‚¬λ‚˜ μƒν’ˆμ— λŒ€ν•΄μ„œλ§Œ λ°œμƒν•©λ‹ˆλ‹€.
  • κ³Όκ±° νžˆμŠ€ν† λ¦¬λ₯Ό μ‚΄νŽ΄λ³Έ κ²°κ³Ό, νŠΉμ • μ œνœ΄μ‚¬μ—μ„œ λ°œκΈ‰ μ²˜λ¦¬λŸ‰μ„ 늘리기 μœ„ν•΄ λ³‘λ ¬λ‘œ λ°œκΈ‰ APIλ₯Ό ν˜ΈμΆœν•˜λŠ” 상황이 μžˆμ—ˆκ³ , λ³‘λ ¬λ‘œ μ²˜λ¦¬ν•˜λŠ” μŠ€λ ˆλ“œ μˆ˜λŠ” μ΅œλŒ€ 3κ°œμ˜€κΈ° λ•Œλ¬Έμ— 이 μˆ˜λŠ” μΆ”κ°€ μš”μ²­μ΄ μ—†μœΌλ©΄ μ¦κ°€ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

λ§Œμ•½ Lock을 μ μš©ν•œλ‹€λ©΄, νŠΉμ • μƒν’ˆμ— λŒ€ν•΄μ„œλŠ” λ™μ‹œμ„±μ΄ λΉˆλ²ˆν•˜κ²Œ λ°œμƒν•˜λŠ” 상황이기에 νŠΉμ • μƒν’ˆμ— λŒ€ν•΄μ„œλ§Œ λΆ€λΆ„μ μœΌλ‘œ(νŠΉμ • μƒν’ˆ, μ œνœ΄μ‚¬ λ“±) 비관적 락을 μ μš©ν•΄ λ³Ό 수 μžˆμ„ 것 κ°™μŠ΅λ‹ˆλ‹€.

 

 

2. μ˜ˆμƒμΉ˜ λͺ»ν•œ νŠΈλž˜ν”½ μœ μž…

μœ„ λ¬Έμ œλŠ” 쿠폰 λ°œκΈ‰ μ‹œ λ°œμƒν•˜λ˜ 슬둜우 쿼리가 κ°œμ„ λ¨μ— 따라 쿠폰의 생성 속도가 λ°œκΈ‰ 속도λ₯Ό 따라가지 λͺ»ν•˜μ—¬ λ°œμƒν•œ μ΄μŠˆμΈλ°μš”, ν΄λΌμ΄μ–ΈνŠΈμ— μ‘΄μž¬ν•˜λŠ” 리트라이(μ΅œλŒ€ 3회) 둜직으둜 인해 3배의 νŠΈλž˜ν”½μ΄ μœ μž…λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

 

ν˜„μž¬ κ΅¬μ‘°λŠ” λŒ€λž΅μ μœΌλ‘œ μœ„μ™€ κ°™μ€λ°μš”,

μ™ΈλΆ€ μ œνœ΄μ‚¬κ°€ λ°œκΈ‰ μš”μ²­μ„ μœ„ν•΄ λ‚΄λΆ€ μ„œλ²„μ— μš”μ²­ν•˜κ³ , λ‚΄λΆ€ μ„œλ²„μ—μ„œ λ‹€μ‹œ λ°œκΈ‰ μ„œλ²„λ‘œ API μš”μ²­μ„ λ³΄λ‚΄λŠ” κ΅¬μ‘°μž…λ‹ˆλ‹€.

(λ‚΄λΆ€ μ„œλ²„ 없이 μ™ΈλΆ€ μ œνœ΄μ‚¬μ—μ„œ λ°”λ‘œ λ°œκΈ‰ μ„œλ²„μ— μš”μ²­ν•˜λŠ” 게 κ°€μž₯ λ‹¨μˆœν•˜μ§€λ§Œ, μ •ν™•ν•œ νžˆμŠ€ν† λ¦¬λ₯Ό μ•Œ μˆ˜λŠ” μ—†μ§€λ§Œ κ³Όκ±° 사업 κ³„μ•½μœΌλ‘œ 인해 λ‚΄λΆ€ μ„œλ²„λ₯Ό κ²½μœ ν•˜λŠ” ꡬ쑰가 λ˜μ—ˆκ³ , B2B와 B2C에 따라 처리 방식이 λ‹€λ₯΄κ²Œ λ˜μ–΄μžˆλŠ” κ²ƒμœΌλ‘œ λ“€μ—ˆλ˜ 것 κ°™μŠ΅λ‹ˆλ‹€..;)

 

μ™ΈλΆ€ μ œνœ΄μ‚¬μ—μ„œλŠ” μš”μ²­ν•œ μˆ˜λŸ‰λ§ŒνΌ 쿠폰을 받지 λͺ»ν•˜λ©΄ λ¬΄ν•œνžˆ μš”μ²­μ„ λ°˜λ³΅ν•˜λŠ” ν”„λ‘œμ„ΈμŠ€λ‘œ κ΅¬ν˜„λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. κ³Όκ±°μ—λŠ” 슬둜우 쿼리둜 인해(?) 쿠폰이 μƒμ„±λ˜λŠ” μ‹œκ°„μ„ μ–΄λŠ 정도 확보할 수 μžˆμ—ˆμ§€λ§Œ, ν˜„μž¬λŠ” API 응닡이 μ¦‰μ‹œ 이루어지기 λ•Œλ¬Έμ— μš”μ²­ λΉˆλ„κ°€ λ”μš± μ¦κ°€ν•˜κ²Œ 된 μƒν™©μž…λ‹ˆλ‹€.

 

λ”°λΌμ„œ μœ„μ˜ 문제λ₯Ό κ°œμ„ ν•˜κΈ° μœ„ν•΄ μ μš©ν•œ(μ μš©ν•  수 μžˆλŠ”) 방법은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  • μ™ΈλΆ€ μ œνœ΄μ‚¬μ—μ„œ λ°œκΈ‰ μš”μ²­ μ‹œ μ—λŸ¬κ°€ λ°œμƒν•˜λ©΄ μΆ©λΆ„ν•œ 간격을 두고 μž¬μ‹œλ„ν•˜λ„λ‘ λ³€κ²½
  • 쿠폰 μž”μ—¬λŸ‰μ΄ λΆ€μ‘±ν•  경우 λ‚΄λΆ€ νŒ€μ—μ„œλŠ” Retry λ‘œμ§μ„ μ œκ±°ν•œλ‹€.
  • Rate Limiter 등을 κ΅¬ν˜„ν•˜κ³ , λ§Žμ€ ν˜ΈμΆœλŸ‰μ— λŒ€ν•΄ μ μ ˆν•˜κ²Œ μ œμ–΄ν•œλ‹€.
  • λŒ€λŸ‰ λ°œκΈ‰μ΄ ν•„μš”ν•œ 경우, 쿠폰 풀을 사전에 μΆ©λΆ„νžˆ ν™•λ³΄ν•˜κ³  자체 λͺ¨λ‹ˆν„°λ§μ„ κ°•ν™”ν•œλ‹€.

 

사싀 근본적인 해결책은 쿠폰 생성 λ‘œμ§μ„ κ°œνŽΈν•΄μ•Ό ν•˜μ§€λ§Œ, λ‹Ήμ‹œμ—λŠ” μ—¬λŸ¬ μ œμ•½μ΄ μ‘΄μž¬ν–ˆκ³  λ―Έλž˜μ—λŠ” μ‹œμŠ€ν…œ 톡합 λ“±μ˜ κ³„νšλ„ μžˆμ—ˆκΈ°μ— ν•΄λ‹Ή μž‘μ—…μ„ μ§„ν–‰ν•˜μ§€λŠ” λͺ»ν–ˆμŠ΅λ‹ˆλ‹€.

 

 

κ²°κ³Ό(μ„±κ³Ό)

As-Is

 

To-Be

κΈ°μ‘΄ API의 νŠΈλžœμž­μ…˜κ³Ό μ‹ κ·œ API의 νŠΈλžœμž­μ…˜ μ„±λŠ₯ 비ꡐ μž…λ‹ˆλ‹€.

 

평균 처리 μ‹œκ°„μ΄ μ•½ 1,500msμ—μ„œ 25ms둜 κ°œμ„ λ˜μ—ˆμŠ΅λ‹ˆλ‹€. (μΊμ‹±μ μš©, κ³Όκ±° 데이터 μ‚­μ œ, 인덱슀 적용)

μ΅œλŒ€ 처리 μ‹œκ°„λ„ 60,000msμ—μ„œ 1,400ms둜 κ°œμ„ λ˜μ—ˆμŠ΅λ‹ˆλ‹€. (슬둜우 쿼리 κ°œμ„  및 ν”„λ‘œμ‹œμ € λ‚΄ λΉ„νš¨μœ¨μ μΈ Retry 둜직 제거 λ“±)

 

 

회고

μž‘μ—…μ„ ν•˜κ³ λ‚˜μ„œ κ½€λ‚˜ μ‹œκ°„μ΄ μ§€λ‚˜μ„œ μž‘μ„±ν•˜κ²Œ λ˜μ–΄μ„œ, μ •ν™•νžˆ κΈ°μ–΅λ‚˜μ§€ μ•Šκ±°λ‚˜ μžλ£Œκ°€ μ—†λŠ” 뢀뢄도 μ‘΄μž¬ν•©λ‹ˆλ‹€.

 

μž‘μ—…ν•˜λ©΄μ„œ κΉ¨λ‹«κ³ , μ–»κ²Œλœ κ΅ν›ˆμ€ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  • 크고 λ³΅μž‘ν•œ λ¬Έμ œλŠ” (항상) κ°€λŠ₯ν•œ μž‘μ€ λ‹¨μœ„λ‘œ λΆ„ν• ν•˜κ³  μ ‘κ·Όν•˜μž.
  • μ½”λ“œ λ‚΄ (였래된) 주석을 μ‹ λ’°ν•˜μ§€ 말고 κ²½κ³„ν•˜μž..
  • 미래의 제3자 (ν˜Ήμ€ 본인)λ₯Ό μœ„ν•΄ μ½”λ“œ 가독성에 μΆ©λΆ„νžˆ μ‹ κ²½ μ“°μž.
  • 운영 ν™˜κ²½μ—μ„œλŠ” 항상 μ‚¬μ΄λ“œ μ΄νŽ™νŠΈκ°€ λ°œμƒν•  수 μžˆμŒμ„ 염두에 두고 μž‘μ—…ν•˜μž.

 

 

λ°˜μ‘ν˜•

λŒ“κΈ€