๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋กœ ์ด๋ฉ”์ผ ๋ฐœ์†ก ์„ฑ๋Šฅ ๊ฐœ์„ ํ•˜๊ธฐ (feat. ๋ชจ๋“ˆ ๋ถ„๋ฆฌ)

๊ฐœ์š”

ํ˜„์žฌ ์ €ํฌ Subport์—์„œ๋Š” ๊ตฌ๋… ๊ฒฐ์ œ์ผ์ด ๋‹ค๊ฐ€์˜ค๋ฉด ์ด๋ฉ”์ผ๋กœ ์•Œ๋ฆผ์„ ๋ฐœ์†กํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์•Œ๋ฆผ ์„ค์ •์€ ON/OFF๊ฐ€ ๊ฐ€๋Šฅํ•˜๊ณ  ๊ฒฐ์ œ 1์ผ ์ „ ๋˜๋Š” 3์ผ ์ „์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋ฉ”์ผ ๋ฐœ์†ก ๋กœ์ง์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. ์Šค์ผ€์ค„๋Ÿฌ๋ฅผ ํ†ตํ•ด ๋งค์ผ ์˜ค์ „ 6์‹œ 30๋ถ„์— ์˜ค๋Š˜์ด ๋ฐœ์†ก์ผ์ธ ๊ตฌ๋… ์ •๋ณด๋ฅผ ์กฐํšŒํ•˜์—ฌ ์ด๋ฉ”์ผ ๋ฐœ์†ก์šฉ ๊ฐ์ฒด ์ƒ์„ฑ
  2. ํ•ด๋‹น ๊ฐ์ฒด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์˜ค์ „ 7์‹œ์— ์ด๋ฉ”์ผ ๋ฐœ์†ก
@Transactional(readOnly = true)
public void send(LocalDateTime now) {
	List<EmailNotification> emailNotifications = getEmailNotifications(now, SendingStatus.PENDING);

	log.info("์ด๋ฉ”์ผ ๋ฐœ์†ก ์‹œ์ž‘ - ๋Œ€์ƒ ๊ฑด์ˆ˜: {}", emailNotifications.size());

	Map<String, List<EmailNotification>> groupedEmailNotifications = emailNotifications.stream()
		.collect(Collectors.groupingBy(EmailNotification::getRecipientEmail));

	for (Map.Entry<String, List<EmailNotification>> entry : groupedEmailNotifications.entrySet()) {
		emailSender.send(entry.getValue(), false, now); // ๋™๊ธฐ ํ˜ธ์ถœ
	}

	log.info("์ด๋ฉ”์ผ ๋ฐœ์†ก ์š”์ฒญ ์™„๋ฃŒ - ์ˆ˜์‹ ์ž ์ˆ˜: {}", groupedEmailNotifications.size());
}

 

๊ทธ๋Ÿฐ๋ฐ ๋ชจ๋“  ์ด๋ฉ”์ผ ๊ฐ์ฒด๋ฅผ ์กฐํšŒํ•œ ๋’ค ๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•˜๋‹ค ๋ณด๋‹ˆ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„์ด ๊ฝค ๊ธธ์—ˆ์Šต๋‹ˆ๋‹ค. ๋ฌผ๋ก  ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ์ด ์•„๋‹Œ ๋งŒํผ ์„œ๋น„์Šค ์šด์˜์—๋Š” ํฐ ์˜ํ–ฅ์ด ์—†์ง€๋งŒ, ๊ทธ๋งŒํผ ์Šค๋ ˆ๋“œ๊ฐ€ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์˜ค๋ž˜ ์ ์œ ๋˜๊ณ  ํ”„๋ฆฌํ‹ฐ์–ด ์ธ์Šคํ„ด์Šค ํ™˜๊ฒฝ์—์„œ๋Š” ์ด๊ฒƒ์ด ๋ถ€๋‹ด๋  ์ˆ˜ ์žˆ๋‹ค๊ณ  ํŒ๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค.

 

์ฐธ๊ณ )
์‹ค์ œ๋กœ ์ˆ˜๋ฐฑ, ์ˆ˜์ฒœ ๊ฑด์˜ ๋ฉ”์ผ์„ ๋ฐœ์†กํ•˜๋Š” ๊ฒƒ์€ OCI์˜ ์›” 3,000๊ฑด ์ œํ•œ๊ณผ ์ŠคํŒธ ์ฒ˜๋ฆฌ ์œ„ํ—˜์ด ์žˆ์–ด ๋ถˆ๊ฐ€๋Šฅํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ 10์—ฌ ๊ฑด์˜ ๋ฐœ์†ก์œผ๋กœ ํ‰๊ท  ์†Œ์š” ์‹œ๊ฐ„์„ ์ธก์ •ํ•œ ๋’ค, ํ•ด๋‹น ๊ฐ’์„ ๊ณ ์ •ํ•˜์—ฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ฐฉ์‹์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

๋น„๋™๊ธฐ ์ ์šฉ

์šฐ์„  ๋ฉ”์ผ ๋ฐœ์†ก ๋กœ์ง์„ ๋™๊ธฐ์—์„œ ๋น„๋™๊ธฐ๋กœ ์ „ํ™˜ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ฉ”์ผ ๋ฐœ์†ก ๋ฉ”์„œ๋“œ๋ฅผ ๋ณ„๋„์˜ ํด๋ž˜์Šค๋กœ ๋ถ„๋ฆฌํ•˜๊ณ  @Async ์–ด๋…ธํ…Œ์ด์…˜์„ ์ ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„์ด ํฌ๊ฒŒ ๋‹จ์ถ•๋˜์—ˆ์œผ๋ฉฐ, 1,000๊ฑด ๊ธฐ์ค€์œผ๋กœ ๋ฐœ์†ก ์š”์ฒญ ์ฒ˜๋ฆฌ๊ฐ€ ์•ฝ 1๋ถ„ ์ •๋„ ๋งŒ์— ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

 

๊ทธ๋Ÿฐ๋ฐ ์—ฌ๊ธฐ์„œ ํ™•์ธํ•ด์•ผ ํ•  ๋ถ€๋ถ„์ด ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋น„๋™๊ธฐ ์Šค๋ ˆ๋“œ ํ’€ ๊ธฐ๋ณธ ์„ค์ •์€ ์–ด๋–ป๊ฒŒ ๋ผ ์žˆ์„๊นŒ?

'@Async๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๋ณ„๋„์˜ ์Šค๋ ˆ๋“œ ํ’€ ์„ค์ •์„ ํ•˜์ง€ ์•Š์œผ๋ฉด SimpleAsyncTaskExecutor๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.'๋ผ๋Š” ์ •๋ณด๋ฅผ ํ”ํžˆ ์ ‘ํ•  ์ˆ˜ ์žˆ๊ณ  ์ € ์—ญ์‹œ ๊ทธ๋ ‡๊ฒŒ ์•Œ๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์Šคํ”„๋ง ๋ถ€ํŠธ์—์„œ ์ด๋Š” ์ •ํ™•ํ•˜์ง€ ์•Š์€ ์„ค๋ช…์ž…๋‹ˆ๋‹ค.

์Šคํ”„๋ง ๋ถ€ํŠธ์—์„œ๋Š” TaskExecutionAutoConfiguration ํด๋ž˜์Šค๋ฅผ ํ†ตํ•ด ์Šค๋ ˆ๋“œ ํ’€์ด ์ž๋™์œผ๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค.

 

TaskExecutorConfigurations ํด๋ž˜์Šค ๋‚ด๋ถ€๋ฅผ ์‚ดํŽด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ฆ‰, ํ”Œ๋žซํผ ์Šค๋ ˆ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์—” ThreadPoolTaskExecutor, ๊ฐ€์ƒ ์Šค๋ ˆ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์—” SimpleAsyncTaskExecutor๊ฐ€ ๊ธฐ๋ณธ Executor๋กœ ๋“ฑ๋ก๋ฉ๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ๋ณ„๋„์˜ ์„ค์ •์„ ํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด ์ผ๋ฐ˜์ ์œผ๋กœ ํ”Œ๋žซํผ ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ์ผ ๊ฒƒ์ด๊ณ , ์ด ๊ฒฝ์šฐ ๊ธฐ๋ณธ Executor๋กœ ThreadPoolTaskExecutor๊ฐ€ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

 

ํ”Œ๋žซํผ ์Šค๋ ˆ๋“œ์™€ ๊ฐ€์ƒ ์Šค๋ ˆ๋“œ (+ ์บ๋ฆฌ์–ด ์Šค๋ ˆ๋“œ)
ํ”Œ๋žซํผ ์Šค๋ ˆ๋“œ๋Š” OS๊ฐ€ ๊ด€๋ฆฌํ•˜๋Š” ์ „ํ†ต์ ์ธ ์ž๋ฐ” ์Šค๋ ˆ๋“œ๋กœ, Java๊ฐ€ OS์˜ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•ด ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ƒ๋Œ€์ ์œผ๋กœ ๋งŽ์€ ๋ฆฌ์†Œ์Šค๋ฅผ ์†Œ๋น„ํ•˜๋ฉฐ ์Šค๋ ˆ๋“œ ์ˆ˜๋Š” ์‹œ์Šคํ…œ ๋ฆฌ์†Œ์Šค์— ์˜ํ•ด ์ œํ•œ๋ฉ๋‹ˆ๋‹ค.
๊ฐ€์ƒ ์Šค๋ ˆ๋“œ๋Š” Java 21์—์„œ ์ •์‹ ๋„์ž…๋œ ๊ฒฝ๋Ÿ‰ ์Šค๋ ˆ๋“œ๋กœ, JVM ์œ„์—์„œ ์ƒ์„ฑ ๋ฐ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ํ”Œ๋žซํผ ์Šค๋ ˆ๋“œ๋ณด๋‹ค ํ›จ์”ฌ ๊ฐ€๋ณ๊ณ  ์ ์€ ๋ฆฌ์†Œ์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ, ๊ณ ์ •๋œ ์Šคํƒ ํฌ๊ธฐ ์—†์ด ์‚ฌ์šฉ๋Ÿ‰์— ๋”ฐ๋ผ ์œ ๋™์ ์œผ๋กœ ํฌ๊ธฐ๊ฐ€ ์กฐ์ ˆ๋ฉ๋‹ˆ๋‹ค. ๊ฐ€์ƒ ์Šค๋ ˆ๋“œ๋Š” ์บ๋ฆฌ์–ด ์Šค๋ ˆ๋“œ ์œ„์—์„œ ์‹คํ–‰๋˜๋Š”๋ฐ, ์บ๋ฆฌ์–ด ์Šค๋ ˆ๋“œ๋Š” ์‹ค์ œ CPU ์‹คํ–‰ ์‹œ๊ฐ„์„ ์ œ๊ณตํ•˜๋Š” ํ”Œ๋žซํผ ์Šค๋ ˆ๋“œ๋กœ, ์—ฌ๋Ÿฌ ๊ฐ€์ƒ ์Šค๋ ˆ๋“œ๋ฅผ ์‹œ๋ถ„ํ•  ๋ฐฉ์‹์œผ๋กœ ์Šค์ผ€์ค„๋งํ•ฉ๋‹ˆ๋‹ค. ๋•๋ถ„์— OS ์Šค๋ ˆ๋“œ ์ˆ˜์˜ ์ œํ•œ ์—†์ด ๋Œ€๋Ÿ‰์˜ ๋™์‹œ ์ž‘์—…์„ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋˜ํ•œ @Conditional(OnExecutorCondition.class)์ด ์ ์šฉ๋˜์–ด ์žˆ์–ด ์กฐ๊ฑด๋ถ€๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.
OnExecutorCondition์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

Executor ํƒ€์ž…์˜ ๋นˆ์ด ๋“ฑ๋ก๋˜์–ด ์žˆ์ง€ ์•Š๋‹ค๋ฉด ๊ธฐ๋ณธ ์Šค๋ ˆ๋“œ ํ’€ ์ƒ์„ฑ์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

 

ThreadPoolTaskExecutor์— ์ ์šฉ๋˜๋Š” ์„ค์ •๊ฐ’์€ TaskExecutionProperties ํด๋ž˜์Šค์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๊ธฐ๋ณธ๊ฐ’์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

ํ•œ ๊ฐ€์ง€ ์ฃผ์˜ํ•  ์ ์€ ํ ์‚ฌ์ด์ฆˆ๊ฐ€ Integer.MAX_VALUE๋กœ ์„ค์ •๋˜์–ด ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ThreadPoolTaskExecutor๋Š” ํ๊ฐ€ ๊ฐ€๋“ ์ฐผ์„ ๋•Œ ๋น„๋กœ์†Œ maxSize๊นŒ์ง€ ์Šค๋ ˆ๋“œ๋ฅผ ๋Š˜๋ฆฌ๋Š”๋ฐ, ์‚ฌ์‹ค์ƒ ํ๊ฐ€ ๊ฐ€๋“ ์ฐฐ ์ผ์ด ์—†์œผ๋ฏ€๋กœ ์Šค๋ ˆ๋“œ ์ˆ˜๋Š” coreSize์ธ 8์„ ๋„˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

 

๊ทธ๋ ‡๋‹ค๋ฉด SimpleAsyncTaskExecutor๋Š” ์–ธ์ œ ์‚ฌ์šฉ๋˜๋Š” ๊ฑธ๊นŒ?

AsyncExecutionInterceptor ํด๋ž˜์Šค์˜ getDefaultExecutor()์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

super.getDefaultExecutor(), ์ฆ‰ ๋ถ€๋ชจ ํด๋ž˜์Šค์ธ AsyncExecutionAspectSupport์˜ getDefaultExecutor()๊ฐ€ null์„ ๋ฐ˜ํ™˜ํ•  ๋•Œ SimpleAsyncTaskExecutor๊ฐ€ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

 

"๊ทธ๋Ÿผ ์–ธ์ œ null์ด ๋ฐ˜ํ™˜๋ ๊นŒ?"

 

์ด๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด AsyncExecutionAspectSupport ํด๋ž˜์Šค์˜ getDefaultExecutor()๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

  1. TaskExecutor.class ํƒ€์ž…์˜ ๋นˆ์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
  2. ๋™์ผ ํƒ€์ž…์˜ ๋นˆ์ด 2๊ฐœ ์ด์ƒ์ด๋ฉด NoUniqueBeanDefinitionException์ด ๋ฐœ์ƒํ•˜๊ณ , taskExecutor๋ผ๋Š” ์ด๋ฆ„์˜ ๋นˆ์„ ์ถ”๊ฐ€๋กœ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
  3. ๋นˆ์ด ์•„์˜ˆ ์—†์œผ๋ฉด NoSuchBeanDefinitionException์ด ๋ฐœ์ƒํ•˜๊ณ , ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ taskExecutor๋ผ๋Š” ์ด๋ฆ„์˜ ๋นˆ์„ ์ถ”๊ฐ€๋กœ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
  4. ์ด ๋‘ ๋ฒˆ์งธ ์กฐํšŒ์—์„œ๋„ ๋นˆ์„ ์ฐพ์ง€ ๋ชปํ•˜๋ฉด null์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

 

๊ฒฐ๊ตญ SimpleAsyncTaskExecutor๊ฐ€ ์‚ฌ์šฉ๋˜๋Š” ์ผ€์ด์Šค๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • TaskExecutor ํƒ€์ž…์˜ ๋นˆ์ด 2๊ฐœ ์ด์ƒ ๋“ฑ๋ก๋˜์–ด ์žˆ๊ณ 
  • ๊ทธ์ค‘ ์ด๋ฆ„์ด taskExecutor์ธ ๋นˆ๋„ ์—†์œผ๋ฉฐ
  • @Primary๋กœ ์ง€์ •๋œ ๋นˆ๋„ ์—†๊ณ 
  • @Async์—์„œ ์‚ฌ์šฉํ•  ๋นˆ์„ ๋ช…์‹œํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ

 

"์ด์ œ ์ „์ฒด ํ๋ฆ„์„ ์ •๋ฆฌํ•ด ๋ณด์ž"

 

1๏ธโƒฃ AsyncExecutionInterceptor์˜ invoke() ํ˜ธ์ถœ

๋‚ด๋ถ€์—์„œ determineAsyncExecutor()๋ฅผ ํ†ตํ•ด ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•  AsyncTaskExecutor๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.

 

2๏ธโƒฃ AsyncExecutionAspectSupport์˜ determineAsyncExecutor() ๋™์ž‘

qualifier ์ง€์ • ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด @Async("emailExecutor")์™€ ๊ฐ™์ด ์ด๋ฆ„์ด ๋ช…์‹œ๋ผ ์žˆ์œผ๋ฉด ํ•ด๋‹น ์ด๋ฆ„์œผ๋กœ ๋นˆ์„ ์กฐํšŒํ•˜๊ณ , ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด defaultExecutor๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

 

3๏ธโƒฃ defaultExecutor ๊ฒฐ์ •

AsyncExecutionAspectSupport์˜ ์ƒ์„ฑ์ž์—์„œ SingletonSupplier๋ฅผ ํ†ตํ•ด getDefaultExecutor()๋ฅผ ๋“ฑ๋กํ•ด ๋‘ก๋‹ˆ๋‹ค. (SingletonSupplier๋Š” @Async ๋ฉ”์„œ๋“œ๊ฐ€ ์ตœ์ดˆ๋กœ ํ˜ธ์ถœ๋˜๋Š” ์‹œ์ ์— ์ง€์—ฐ ํ˜ธ์ถœ์„ ํ•˜์—ฌ getDefaultExecutor()๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.)

getDefaultExecutor()๋Š” AsyncExecutionInterceptor์—์„œ ์˜ค๋ฒ„๋ผ์ด๋”ฉ๋œ ๋ฒ„์ „์ด ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

 

์ตœ์ข… ์š”์•ฝ

  1. @Async ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ
  2. AsyncExecutionInterceptor.invoke()
  3. determineAsyncExecutor()
  4. qualifier ์ง€์ • ์—ฌ๋ถ€ ํ™•์ธ
    • ์ง€์ •๋œ ๊ฒฝ์šฐ โžก ํ•ด๋‹น ์ด๋ฆ„์œผ๋กœ ๋นˆ ์กฐํšŒ
    • ๋ฏธ์ง€์ •์ธ ๊ฒฝ์šฐ โžก defaultExecutor ์‚ฌ์šฉ
  5. getDefaultExecutor() ํ˜ธ์ถœ (์ตœ์ดˆ ํ˜ธ์ถœ ์‹œ SingletonSupplier๊ฐ€ ์ง€์—ฐ ์‹คํ–‰)
    • TaskExecutor ๋นˆ์ด ํ•˜๋‚˜ โžก ํ•ด๋‹น ๋นˆ ์‚ฌ์šฉ
    • 2๊ฐœ ์ด์ƒ & taskExecutor ์ด๋ฆ„ ์žˆ์Œ โžก ํ•ด๋‹น ๋นˆ ์‚ฌ์šฉ
    • 2๊ฐœ ์ด์ƒ & taskExecutor ์ด๋ฆ„ ์—†์Œ โžก null
      • new SimpleAsyncTaskExecutor() ์‚ฌ์šฉ

 

์Šค๋ ˆ๋“œ ํ’€ ํฌ๊ธฐ ์„ค์ •

๊ธฐ๋ณธ ์Šค๋ ˆ๋“œ ํ’€ ์„ค์ •๋งŒ์œผ๋กœ๋„ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„์ด ์•ฝ 10๋ถ„์—์„œ 1๋ถ„์œผ๋กœ 90% ์ •๋„ ๊ฐœ์„ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์Šค๋ ˆ๋“œ ์ˆ˜๋ฅผ ๋Š˜๋ฆฌ๋ฉด ์ฒ˜๋ฆฌ ์†๋„๋ฅผ ๋” ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

"ํ”„๋ฆฌํ‹ฐ์–ด ์ธ์Šคํ„ด์Šค ํ™˜๊ฒฝ์ด๋ฉด ์ฝ”์–ด๊ฐ€ 1~2๊ฐœ์ด์ง€ ์•Š๋‚˜? ์Šค๋ ˆ๋“œ ์ˆ˜๋ฅผ ๋Š˜๋ ค๋„ ์˜๋ฏธ๊ฐ€ ์žˆ๋‚˜? ๊ดœํžˆ ์ปจํ…์ŠคํŠธ ์Šค์œ„์นญ ์˜ค๋ฒ„ํ—ค๋“œ๋งŒ ์ƒ๊ธฐ๋Š” ๊ฑฐ ์•„๋‹Œ๊ฐ€?"

๋ผ๊ณ  ์ƒ๊ฐํ•˜์‹ค ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ์ด๋ฉ”์ผ ๋ฐœ์†ก ์š”์ฒญ์€ I/O ๋ฐ”์šด๋“œ ์ž‘์—…์ž…๋‹ˆ๋‹ค. CPU๊ฐ€ ์ง์ ‘ ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์™ธ๋ถ€ SMTP ์„œ๋ฒ„์˜ ์‘๋‹ต์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์‹œ๊ฐ„์ด ๋Œ€๋ถ€๋ถ„์„ ์ฐจ์ง€ํ•˜๋ฏ€๋กœ, ์Šค๋ ˆ๋“œ ์ˆ˜๋ฅผ ๋Š˜๋ ค ์ „์ฒด ์ฒ˜๋ฆฌ๋Ÿ‰์„ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋ฌผ๋ก  ์Šค๋ ˆ๋“œ ์ˆ˜๋ฅผ ๋ฌด์ž‘์ • ๋Š˜๋ฆฌ๋Š” ๊ฒƒ์ด ์ •๋‹ต์€ ์•„๋‹™๋‹ˆ๋‹ค. ์Šค๋ ˆ๋“œ ์ˆ˜๊ฐ€ ๊ณผํ•˜๋ฉด ์ปจํ…์ŠคํŠธ ์Šค์œ„์นญ ๋น„์šฉ๊ณผ ์Šค๋ ˆ๋“œ๋‹น ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์ด ๋ฌธ์ œ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ 1,000๊ฑด ๋ฐœ์†ก์„ ๊ธฐ์ค€์œผ๋กœ ์Šค๋ ˆ๋“œ ์ˆ˜๋ฅผ ๋ณ€ํ™”์‹œํ‚ค๋ฉฐ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„๊ณผ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์„ ์ธก์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

์Šค๋ ˆ๋“œ ์ˆ˜๋ฅผ ๋Š˜๋ฆด์ˆ˜๋ก ์ฒ˜๋ฆฌ ์‹œ๊ฐ„์ด ๊ณ„์†ํ•ด์„œ ์ค„์—ˆ์ง€๋งŒ, 50๊ฐœ๋ฅผ ๋„˜์–ด์„œ๋ฉด์„œ๋ถ€ํ„ฐ๋Š” ์„ฑ๋Šฅ ๊ฐœ์„  ํšจ์œจ์ด ๊ธ‰๊ฐํ–ˆ๋Š”๋ฐ, ์ฒ˜๋ฆฌ ์‹œ๊ฐ„ ๊ฐ์†Œ ํšจ์œจ์ด ์ค„๊ณ , (ํ‘œ์—๋Š” ์ ์ง€ ์•Š์•˜์ง€๋งŒ) ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ฆ๊ฐ€ ํญ๋„ ์ปค์กŒ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์„ฑ๋Šฅ ๊ฐœ์„  ํšจ์œจ์ด ๊ฐ€์žฅ ์ข‹์€ 50๊ฐœ๊ฐ€ ๊ท ํ˜•์ ์ด๋ผ๊ณ  ํŒ๋‹จํ•˜์—ฌ ์„ค์ •๊ฐ’์œผ๋กœ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

 

๊ฒฐ๊ณผ์ ์œผ๋กœ 1,000๊ฑด ๋ฐœ์†ก ๊ธฐ์ค€ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„์ด 598.2์ดˆ โžก 14.5์ดˆ๋กœ, ์•ฝ 97.6% ๊ฐœ์„ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

 

๋ชจ๋“ˆ ๋ถ„๋ฆฌ

๊ธฐ์กด์—๋Š” API ์„œ๋ฒ„์™€ ์Šค์ผ€์ค„๋ง ์ฒ˜๋ฆฌ๊ฐ€ ํ•˜๋‚˜์˜ ์ปจํ…Œ์ด๋„ˆ์—์„œ ๋™์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค. ์Šค์ผ€์ค„๋ง ์ž‘์—… ์‹œ๊ฐ„์„ ํ”ผํ•ด ๋ฐฐํฌ๋ฅผ ์ง„ํ–‰ํ•œ๋‹ค๋ฉด ๋ฌธ์ œ์—†๊ฒ ์ง€๋งŒ, ์˜๋„์น˜ ์•Š๊ฒŒ ์Šค์ผ€์ค„๋ง ์‹คํ–‰ ์ค‘์— ๋ฐฐํฌ๊ฐ€ ๊ฒน์ณ ์ž‘์—…์ด ์ค‘๋‹จ๋˜๋Š” ์ƒํ™ฉ์ด ์ƒ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๋‘ ๋ชจ๋“ˆ์„ ๋ถ„๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

 

๋ฐฉ๋ฒ•์€ ๊ฐ„๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ณตํ†ต ์„ค์ •๊ณผ ์˜์กด์„ฑ์€ ๋ฃจํŠธ์˜ build.gradle์— ๋‘๊ณ , api์™€ batch ๋ชจ๋“ˆ ๊ฐ๊ฐ์— ๋ณ„๋„์˜ build.gradle์„ ๋‘์–ด ๋…๋ฆฝ์ ์œผ๋กœ ๊ด€๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค. Dockerfile๊ณผ ๋ฐฐํฌ ์Šคํฌ๋ฆฝํŠธ๋„ ๊ฐ๊ฐ ๋ถ„๋ฆฌํ•˜์—ฌ, ์„œ๋กœ์˜ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์— ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š๊ณ  ๊ฐ ๋ชจ๋“ˆ์˜ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์—๋งŒ ๋ฐฐํฌ๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ ๋˜๋„๋ก ๊ตฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

 

์ธ์Šคํ„ด์Šค ์ž์ฒด๋ฅผ ๋ถ„๋ฆฌํ•œ ๊ฒƒ์€ ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— ์„ฑ๋Šฅ์ƒ์˜ ์ด์ ์€ ์—†์ง€๋งŒ, ๋ฐฐํฌ ํƒ€์ด๋ฐ์œผ๋กœ ์ธํ•œ ์Šค์ผ€์ค„๋ง ์˜ค์ž‘๋™์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

 

ํ˜„์žฌ๋Š” DB ์ธ์Šคํ„ด์Šค๋ฅผ MySQL HeatWave๋กœ ์ด์ „ํ•˜๋ฉด์„œ ์—ฌ์œ  ์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒ๊ฒผ๊ธฐ์— batch ์ปจํ…Œ์ด๋„ˆ๋ฅผ ํ•ด๋‹น ์ธ์Šคํ„ด์Šค๋กœ ์ด์ „ํ•  ๊ณ„ํš์„ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. API ์š”์ฒญ ์—†์ด ์ˆœ์ˆ˜ ์Šค์ผ€์ค„๋ง ์šฉ๋„๋กœ๋งŒ ์‚ฌ์šฉ๋˜๋Š” ๋งŒํผ ์ ์ ˆํ•œ ๋ถ„๋ฆฌ๊ฐ€ ๋  ๊ฑฐ๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

 

๋งˆ๋ฌด๋ฆฌ

์ด๋ฉ”์ผ ๋ฐœ์†ก ๋กœ์ง์„ ๋™๊ธฐ ์ฒ˜๋ฆฌ์—์„œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋กœ ๊ฐœ์„ ํ•˜๊ณ , api ๋ชจ๋“ˆ๊ณผ batch ๋ชจ๋“ˆ์„ ๋ณ„๋„ ์ปจํ…Œ์ด๋„ˆ๋กœ ๋ถ„๋ฆฌํ•ด ๋ณด์•˜์Šต๋‹ˆ๋‹ค.

 

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

 

๊ทธ๋ฆฌ๊ณ  ๋ฉ”์ผ ๋ฐœ์†ก์ด I/O ๋ฐ”์šด๋“œ ์ž‘์—…์ธ ๋งŒํผ Java ๋ฒ„์ „์„ ์˜ฌ๋ ค(17 -> 21) ๊ฐ€์ƒ ์Šค๋ ˆ๋“œ๋ฅผ ๋„์ž…ํ•ด ๋ณผ๊นŒ๋„ ์ƒ๊ฐ ์ค‘์ž…๋‹ˆ๋‹ค. ๋‹ค๋งŒ ๋ณ„๋„ ์ธ์Šคํ„ด์Šค๋กœ์˜ ์ด์ „์ด ์™„๋ฃŒ๋œ ์ดํ›„์—๋Š” ์ถ”๊ฐ€์  ์ด์ ์ด ์–ผ๋งˆ๋‚˜ ์žˆ์„์ง€ ๊ณ ๋ฏผํ•ด ๋ด์•ผ ํ•  ๊ฑฐ ๊ฐ™์Šต๋‹ˆ๋‹ค๐Ÿค”