๐Ÿ’พ TransactionSynchronization์˜ afterCommit๊ณผ afterCompletion ๊ทธ๋ฆฌ๊ณ  Redis ๋ฐ์ดํ„ฐ ์œ ์‹ค ๋ฌธ์ œ ํ•ด๊ฒฐ๊ธฐ

Lee Jin Hyukยท2025๋…„ 9์›” 14์ผ

์ฃผ์ฐจ๊ถŒ ํ”„๋กœ์ ํŠธ

๋ชฉ๋ก ๋ณด๊ธฐ
2/2

๋“ค์–ด๊ฐ€๋ฉฐ

์Šคํ”„๋ง์—์„œ @Transactional์€ ์šฐ๋ฆฌ๊ฐ€ ์ƒ๊ฐํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ๋” ๋งŽ์€ ๊ฑธ ์ฑ…์ž„์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
ํŠนํžˆ DB ํŠธ๋žœ์žญ์…˜๊ณผ ๊ด€๋ จ๋œ ๋ชจ๋“  ์ž‘์—…์„ ํ•˜๋‚˜์˜ ๋‹จ์œ„๋กœ ๋ฌถ์–ด ์„ฑ๊ณตํ•˜๋ฉด ์ปค๋ฐ‹, ์‹คํŒจํ•˜๋ฉด ๋กค๋ฐฑ์„ ๋ณด์žฅํ•˜์ฃ .

๊ทธ๋Ÿฐ๋ฐ Redis ๊ฐ™์€ ์™ธ๋ถ€ ์ €์žฅ์†Œ๋ฅผ ๋‹ค๋ฃฐ ๋•Œ๋Š” ์ด์•ผ๊ธฐ๊ฐ€ ์กฐ๊ธˆ ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค.
์™œ๋ƒํ•˜๋ฉด Redis๋Š” RDBMS๊ฐ€ ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— ์Šคํ”„๋ง์˜ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ ๋ฒ”์œ„์— ํฌํ•จ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

์ตœ๊ทผ ํ”„๋กœ์ ํŠธ์—์„œ ๋ฐ”๋กœ ์ด ๋ถ€๋ถ„ ๋•Œ๋ฌธ์— ๋ฐ์ดํ„ฐ ์œ ์‹ค ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๊ณ , ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด TransactionSynchronization์˜ afterCommit์„ ํ™œ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.
์ด ๊ธ€์—์„œ๋Š” ๊ทธ ๊ณผ์ •์„ ์ •๋ฆฌํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๋ฌธ์ œ ์ƒํ™ฉ

ํ”„๋กœ์ ํŠธ์—์„œ๋Š” DB(MySQL)๊ณผ Redis๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

  • DB โ†’ ๋ฉ”์ธ ๋ฐ์ดํ„ฐ ์ €์žฅ

  • Redis โ†’ sorted set์„ ํ™œ์šฉํ•œ ์ž„์‹œ ํ(๋Œ€๊ธฐ์—ด) ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ

๋ฌธ์ œ๊ฐ€ ๋œ ๋ถ€๋ถ„์€ Redis์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ popํ•˜๋Š” ์‹œ์ ์ด์—ˆ์Šต๋‹ˆ๋‹ค.

@Transactional
public void processTicket() {
    // DB ์ €์žฅ
    ticketRepository.save(ticket);
    
    // Redis์—์„œ pop
    Ticket ticket = redisQueue.pop();

    // ...์ค‘๊ฐ„ ์ƒ๋žต
    // ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ๋กค๋ฐฑ
}

์˜๋„ํ•œ ํ๋ฆ„

  1. ์‹ ์ฒญ ๋ฐ์ดํ„ฐ๋ฅผ DB์— ์ €์žฅํ•œ๋‹ค.

  2. Redis(๋Œ€๊ธฐ์—ด)์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ pop(์‚ญ์ œ)ํ•œ๋‹ค.

  3. ๋ฌธ์ œ๊ฐ€ ์žˆ์œผ๋ฉด ํŠธ๋žœ์žญ์…˜์„ ๋กค๋ฐฑํ•œ๋‹ค.

์‹ค์ œ ๋™์ž‘

  • Redis์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ pop โ†’ Redis์—๋Š” ๋” ์ด์ƒ ํ•ด๋‹น ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Œ โŒ

  • DB ์ €์žฅ ์ค‘ ์˜ˆ์™ธ ๋ฐœ์ƒ โ†’ DB ํŠธ๋žœ์žญ์…˜ ๋กค๋ฐฑ

  • Redis๋Š” ๋กค๋ฐฑ๋˜์ง€ ์•Š์Œ โ†’ ๊ฒฐ๊ตญ ๋ฐ์ดํ„ฐ ์œ ์‹ค ๋ฐœ์ƒ โš ๏ธ

์ฆ‰, RDBMS์ธ MySQL์€ @Transactional๋กœ ๊ด€๋ฆฌ๊ฐ€ ๋˜์ง€๋งŒ Redis๋Š” ๊ด€๋ฆฌ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋ถˆ์ผ์น˜ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ•ด๊ฒฐ : TransactionSynchronization๋ž€?

์—ฌ๊ธฐ์„œ ๋“ฑ์žฅํ•˜๋Š” ๊ฒƒ์ด ๋ฐ”๋กœ TransactionSynchronization์ž…๋‹ˆ๋‹ค.

์Šคํ”„๋ง์—์„œ๋Š” ํŠธ๋žœ์žญ์…˜์˜ ์ƒ๋ช…์ฃผ๊ธฐ(lifecycle)์— ๋งž์ถฐ ํŠน์ • ๋™์ž‘์„ ์ฝœ๋ฐฑ(callback) ํ˜•ํƒœ๋กœ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ด๋ฅผ ์ œ๊ณตํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ TransactionSynchronization์ž…๋‹ˆ๋‹ค.

๋Œ€ํ‘œ์ ์ธ ๋ฉ”์„œ๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

beforeCommit() : ์‹ค์ œ ์ปค๋ฐ‹ ์ง์ „์— ์‹คํ–‰

afterCommit() : ํŠธ๋žœ์žญ์…˜์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ปค๋ฐ‹๋œ ํ›„ ์‹คํ–‰

afterCompletion(int status) : ํŠธ๋žœ์žญ์…˜์ด ๋๋‚œ ํ›„(์„ฑ๊ณต/์‹คํŒจ ํฌํ•จ) ์‹คํ–‰

์ฆ‰, DB ํŠธ๋žœ์žญ์…˜์ด ๋๋‚œ ์‹œ์ ์— ๋งž์ถฐ ์™ธ๋ถ€ ๋ฆฌ์†Œ์Šค ์ž‘์—…์„ ์•ˆ์ „ํ•˜๊ฒŒ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด์ฃ .

afterCommit vs afterCompletion

afterCommit

DB ํŠธ๋žœ์žญ์…˜์ด ์ปค๋ฐ‹์— ์„ฑ๊ณตํ–ˆ์„ ๋•Œ๋งŒ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

์‹คํŒจ(rollback) ์‹œ์—๋Š” ํ˜ธ์ถœ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

afterCompletion

ํŠธ๋žœ์žญ์…˜์ด ๋๋‚˜๋ฉด ๋ฌด์กฐ๊ฑด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

์„ฑ๊ณต/์‹คํŒจ ์—ฌ๋ถ€๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ(STATUS_COMMITTED, STATUS_ROLLED_BACK)๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ‘‰ Redis์—์„œ pop ์ž‘์—…์€ afterCommit์— ๋„ฃ์–ด์•ผ ๋ฐ์ดํ„ฐ ์œ ์‹ค์„ ๋ง‰์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‹ค์ œ ์ฝ”๋“œ ์ ์šฉ

import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

@Transactional
public void processTicket() {
    Ticket ticket = redisQueue.peek(); // ์ผ๋‹จ peek์œผ๋กœ ํ™•์ธ๋งŒ
    
    ticketRepository.save(ticket);

    // ํŠธ๋žœ์žญ์…˜์ด ์ปค๋ฐ‹๋œ ์ดํ›„์— pop ์‹คํ–‰
    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
        @Override
        public void afterCommit() {
            redisQueue.pop(ticket.getId());
        }
    });
}

๋ณ€๊ฒฝ๋œ ํ๋ฆ„

  1. Redis์—์„œ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ peek๋งŒ ํ•˜๊ณ  โ†’ ์•„์ง ์•ˆ์ „ํ•˜๊ฒŒ ๋ณด๊ด€

  2. DB ์ €์žฅ ์‹œ๋„

  3. DB ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ ์„ฑ๊ณต ์‹œ โ†’ afterCommit() ํ˜ธ์ถœ โ†’ Redis์—์„œ ์ตœ์ข… pop

  4. DB ํŠธ๋žœ์žญ์…˜ ์‹คํŒจ ์‹œ โ†’ afterCommit() ์‹คํ–‰๋˜์ง€ ์•Š์Œ โ†’ Redis ๋ฐ์ดํ„ฐ ๊ทธ๋Œ€๋กœ ์œ ์ง€

    • ์ฐธ๊ณ ๋กœ Quartz๋ฅผ ์ด์šฉํ•œ ์Šค์ผ€์ค„๋Ÿฌ๋กœ ์ผ์ •ํ•œ ์‹œ๊ฐ„๋งˆ๋‹ค 1~4๋ฒˆ๊นŒ์ง€ ๊ณ„์† ์ง„ํ–‰๋จ์œผ๋กœ Redis์— ๋ฐ์ดํ„ฐ๊ฐ€ ๊ทธ๋Œ€๋กœ ์œ ์ง€๋˜๋ฉด ๋ฐ์ดํ„ฐ ์œ ์‹ค์ด ์—†์Œ

โœ… ์ด ๋ฐฉ์‹์œผ๋กœ ๋ฐ์ดํ„ฐ ์œ ์‹ค ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์ฐธ๊ณ 

๐Ÿ’ก Redis์—์„œ๋„ @Transactional์ฒ˜๋Ÿผ ๋™์ž‘ํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ์˜ต์…˜(setEnableTransactionSupport)์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.
์ด ์˜ต์…˜์„ ์ ์šฉํ•˜๋ฉด @Transactional ์•ˆ์—์„œ redis๋„ ๋กค๋ฐฑ์ด ๋˜์–ด ์ด ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋ถ€๊ฐ€์ž‘์—… ์—†์–ด ๊ฐ„ํŽธํ•ฉ๋‹ˆ๋‹ค. (ํ•„์ž๋„ TransactionSynchronization์—์„œ ์ด ์˜ต์…˜์œผ๋กœ ๋ฆฌํŒฉํ† ๋ง ํ•˜์˜€์Šต๋‹ˆ๋‹ค)

0๊ฐœ์˜ ๋Œ“๊ธ€