๋ณธ๋ฌธ์œผ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ

๐Ÿ“Š์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ๋ฐ ์ตœ์ ํ™”

Sprout ์„œ๋ฒ„์˜ ์„ฑ๋Šฅ ํŠน์„ฑ์„ ํŒŒ์•…ํ•˜๊ณ  ๋ณ‘๋ชฉ ์ง€์ ์„ ๊ฐœ์„ ํ•œ ์ „์ฒด ๊ณผ์ •์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค. ์ดˆ๊ธฐ ๋ฒค์น˜๋งˆํ‚น๋ถ€ํ„ฐ ํ”„๋กœํŒŒ์ผ๋ง ๋ถ„์„, ์ฝ”๋“œ ๋ฆฌํŒฉํ† ๋ง, ๊ทธ๋ฆฌ๊ณ  ์ตœ์ข… ์„ฑ๋Šฅ ๊ฐœ์„ ๊นŒ์ง€์˜ ์—ฌ์ •์„ ๋‹จ๊ณ„๋ณ„๋กœ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๊ฐœ์š”โ€‹

Sprout๋Š” Java NIO ๊ธฐ๋ฐ˜์˜ ๊ณ ์„ฑ๋Šฅ ์›น ์„œ๋ฒ„๋กœ, BIO(Blocking I/O)์™€ NIO(Non-blocking I/O)๋ฅผ ๋ชจ๋‘ ์ง€์›ํ•˜๋ฉฐ ํ”Œ๋žซํผ ์Šค๋ ˆ๋“œ์™€ ๊ฐ€์ƒ ์Šค๋ ˆ๋“œ ์ค‘ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ตฌ์กฐ์  ์œ ์—ฐ์„ฑ ๋•๋ถ„์— ๋‹ค์–‘ํ•œ ์กฐํ•ฉ์—์„œ์˜ ์„ฑ๋Šฅ ํŠน์„ฑ์„ ๋น„๊ตํ•˜๊ณ  ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝโ€‹

ํ•ญ๋ชฉ์‚ฌ์–‘
CPU10 Cores
Memory32GB
OSmacOS Sequoia 15.6.1
JDKOpenJDK 21
ToolGatling 3.x

์„œ๋ฒ„ ๊ตฌ์„ฑ ์กฐํ•ฉโ€‹

Sprout๋Š” ์‹คํ–‰ ๋ชจ๋“œ์™€ ์Šค๋ ˆ๋“œ ํƒ€์ž…์„ ์กฐํ•ฉํ•˜์—ฌ 4๊ฐ€์ง€ ๊ตฌ์„ฑ์œผ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

I/O ๋ชจ๋“œ์Šค๋ ˆ๋“œ ํƒ€์ž…์„ค๋ช…
Hybrid (BIO)Platform ThreadsHTTP๋Š” BIO๋กœ ๋™์ž‘, ๊ณ ์ • ํฌ๊ธฐ ์Šค๋ ˆ๋“œ ํ’€ ์‚ฌ์šฉ (150๊ฐœ)
Hybrid (BIO)Virtual ThreadsHTTP๋Š” BIO๋กœ ๋™์ž‘, ๊ฐ€์ƒ ์Šค๋ ˆ๋“œ ์‚ฌ์šฉ
NIOPlatform ThreadsHTTP๊ฐ€ NIO๋กœ ๋™์ž‘, ๊ณ ์ • ํฌ๊ธฐ ์Šค๋ ˆ๋“œ ํ’€ ์‚ฌ์šฉ
NIOVirtual ThreadsHTTP๊ฐ€ NIO๋กœ ๋™์ž‘, ๊ฐ€์ƒ ์Šค๋ ˆ๋“œ ์‚ฌ์šฉ

์„ค์ • ์˜ˆ์‹œ

server:
execution-mode: nio # ์‹คํ–‰ ๋ชจ๋“œ: nio ๋˜๋Š” hybrid
thread-type: virtual # ์Šค๋ ˆ๋“œ ์ข…๋ฅ˜: virtual ๋˜๋Š” platform
thread-pool-size: 150 # platform ์Šค๋ ˆ๋“œ์ผ ๊ฒฝ์šฐ ์‚ฌ์šฉํ•  ์Šค๋ ˆ๋“œ ํ’€ ํฌ๊ธฐ

Phase 1: ์ดˆ๊ธฐ ๋ฒค์น˜๋งˆํ‚นโ€‹

๋ฒค์น˜๋งˆํฌ ์‹œ๋‚˜๋ฆฌ์˜คโ€‹

์„ธ ๊ฐ€์ง€ ์‹œ๋‚˜๋ฆฌ์˜ค๋กœ ์„œ๋ฒ„์˜ ํŠน์„ฑ์„ ์ธก์ •ํ–ˆ์Šต๋‹ˆ๋‹ค

1. HelloWorld ์‹œ๋‚˜๋ฆฌ์˜ค (์•ฝ 8,000 ์š”์ฒญ)โ€‹

@GetMapping("/hello")
public String hello() {
return "Hello, World!";
}

๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ์‘๋‹ต์œผ๋กœ ์ˆœ์ˆ˜ ์„œ๋ฒ„ ์„ฑ๋Šฅ์„ ์ธก์ •ํ•ฉ๋‹ˆ๋‹ค.

2. CPU Intensive ์‹œ๋‚˜๋ฆฌ์˜ค (์•ฝ 2,000 ์š”์ฒญ)โ€‹

@GetMapping("/cpu")
public String cpu(@RequestParam(required = false, defaultValue = "35") String n) {
int num = Integer.parseInt(n);
long result = fibonacci(num);
return "Fibonacci(" + num + ") = " + result;
}

@GetMapping("/cpu-heavy")
public String cpuHeavy(@RequestParam(required = false, defaultValue = "10000") String limit) {
int max = Integer.parseInt(limit);
int primeCount = countPrimes(max);
return "Primes up to " + max + ": " + primeCount;
}

CPU ๋ฐ”์šด๋“œ ์ž‘์—…์—์„œ์˜ ์„œ๋ฒ„ ์ฒ˜๋ฆฌ ๋Šฅ๋ ฅ์„ ์ธก์ •ํ•ฉ๋‹ˆ๋‹ค.

3. Latency ์‹œ๋‚˜๋ฆฌ์˜ค (์•ฝ 20,000 ์š”์ฒญ)โ€‹

@GetMapping("/latency")
public String latency(@RequestParam(required = false, defaultValue = "100") String ms) {
int delay = Integer.parseInt(ms);
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return "Interrupted";
}
return "Delayed response after " + delay + "ms";
}

I/O ๋ธ”๋กœํ‚น์ด ๋นˆ๋ฒˆํ•œ ์ƒํ™ฉ์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•ฉ๋‹ˆ๋‹ค.

์ดˆ๊ธฐ ๋ฒค์น˜๋งˆํ‚น ๊ฒฐ๊ณผโ€‹

์กฐํ•ฉHelloWorldCPULatencyํŠน์„ฑ ์š”์•ฝ
Hybrid + Platform84%53%95.6%์•ˆ์ •์ ์ด๋‚˜ Warm-up ์˜์กด์ 
Hybrid + Virtual87%47%94.7%์ดˆ๋ฐ˜ ์˜ค๋ฒ„ํ—ค๋“œ ์žˆ์œผ๋‚˜ ์‘๋‹ต ๋น ๋ฆ„
NIO + Platform82%45%93.4%์ดˆ๊ธฐ Selector ๋ณ‘๋ชฉ ๋ฐœ์ƒ
NIO + Virtual69%64.9%92.2%CPU ๋ถ€ํ•˜์—์„  ์ตœ์ , I/O์—” ๋ถ€์ 

์ฃผ์š” ๋ฐœ๊ฒฌ ์‚ฌํ•ญโ€‹

1. Warm-up ๋ฌธ์ œโ€‹

๋ชจ๋“  ์กฐํ•ฉ์—์„œ ์ดˆ๋ฐ˜ 5-10์ดˆ ๊ตฌ๊ฐ„์— ์š”์ฒญ ์‹คํŒจ(KO)๊ฐ€ ์ง‘์ค‘๋˜๊ณ  ์ดํ›„ ์•ˆ์ •ํ™”๋˜๋Š” ํŒจํ„ด์ด ๊ด€์ฐฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜ํ”„๋ฅผ ๋ณด๋ฉด ํŠน์ • ์‹œ์ (์•ฝ 300๊ฑด์˜ ์š”์ฒญ ์ฒ˜๋ฆฌ ํ›„)๋ถ€ํ„ฐ ์„ฑ๋Šฅ์ด ๊ธ‰์ฆํ•˜๋Š”๋ฐ, ์ด๋Š” JIT ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ "hot" ์ƒํƒœ๋กœ ์ „ํ™˜๋œ ์‹œ์ ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

์›์ธ ๋ถ„์„

  • JIT ์ปดํŒŒ์ผ์ด ์™„๋ฃŒ๋˜๊ธฐ ์ „๊นŒ์ง€ ์ธํ„ฐํ”„๋ฆฌํ„ฐ ๋ชจ๋“œ๋กœ ์‹คํ–‰๋˜์–ด ๋А๋ฆผ
  • ํŠนํžˆ NIO ๊ตฌ์กฐ๋Š” Selector, Channel, ByteBuffer ๋“ฑ ๋ณต์žกํ•œ ๋ฃจํ”„ ๋•Œ๋ฌธ์— JIT threshold ๋„๋‹ฌ์— ๋” ๋งŽ์€ ๋ฐ˜๋ณต ํ•„์š”
  • BIO๋Š” ๋‹จ์ˆœ socket read/write๋ผ ๋นจ๋ฆฌ ์ตœ์ ํ™” ๊ฐ€๋Šฅ

2. NIO + Virtual Thread์˜ ํŠน์ดํ•œ ํŒจํ„ดโ€‹

HelloWorld ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ๋Š” ๊ฐ€์žฅ ๋‚ฎ์€ ์„ฑ๊ณต๋ฅ (69%)์„ ๋ณด์˜€์ง€๋งŒ, CPU Intensive ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ๋Š” ๊ฐ€์žฅ ๋†’์€ ์„ฑ๊ณต๋ฅ (64.9%)์„ ๊ธฐ๋กํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ•ด์„

  • CPU ์ง‘์•ฝ์  ์ž‘์—…: NIO์˜ ์ด๋ฒคํŠธ ๋ถ„์‚ฐ๊ณผ Virtual Thread์˜ ๊ฐ€๋ฒผ์›€์ด ์‹œ๋„ˆ์ง€๋ฅผ ๋ƒ„
  • I/O ์ง€์—ฐ ์ž‘์—…: ์˜๋„์  ์ง€์—ฐ์ด NIO์˜ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ์™€ ๋งž์ง€ ์•Š์•„ ์˜คํžˆ๋ ค ์˜ค๋ฒ„ํ—ค๋“œ ๋ฐœ์ƒ

3. Hybrid + Virtual Thread์˜ ์•ˆ์ •์„ฑโ€‹

๋Œ€๋ถ€๋ถ„์˜ ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ ๊ฐ€์žฅ ๊ท ํ˜•์žกํžŒ ์„ฑ๋Šฅ์„ ๋ณด์—ฌ์คฌ์Šต๋‹ˆ๋‹ค. BIO์˜ ๋‹จ์ˆœํ•จ๊ณผ Virtual Thread์˜ ๊ฒฝ๋Ÿ‰ ํŠน์„ฑ์ด ์ž˜ ์กฐํ•ฉ๋˜์–ด ์•ˆ์ •์ ์ธ ๊ฒฐ๊ณผ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

Phase 2: ๋ณ‘๋ชฉ ์ง€์  ๋ถ„์„โ€‹

์ดˆ๊ธฐ ๋ฒค์น˜๋งˆํ‚น์—์„œ Warm-up ๋ฌธ์ œ๋ฅผ ํ™•์ธํ•œ ํ›„, ์ •ํ™•ํ•œ ๋ณ‘๋ชฉ ์ง€์ ์„ ์ฐพ๊ธฐ ์œ„ํ•ด ํ”„๋กœํŒŒ์ผ๋ง ๋„๊ตฌ๋ฅผ ํ™œ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

async-profiler ๋ถ„์„โ€‹

async-profiler๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ CPU, ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น, Wall-clock ํ”„๋กœํŒŒ์ผ๋ง์„ ์ง„ํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค.

# async-profiler ์‹คํ–‰ ์Šคํฌ๋ฆฝํŠธ
env DYLD_LIBRARY_PATH=$ASYNC_PROFILER_HOME/lib $ASPROF \
-d 30 -e cpu -o flamegraph -f cpu-flamegraph.html $PID

env DYLD_LIBRARY_PATH=$ASYNC_PROFILER_HOME/lib $ASPROF \
-d 30 -e alloc -o flamegraph -f alloc-flamegraph.html $PID

env DYLD_LIBRARY_PATH=$ASYNC_PROFILER_HOME/lib $ASPROF \
-d 30 -e wall -o flamegraph -f wall-flamegraph.html $PID

CPU ํ”„๋กœํŒŒ์ผ ๊ฒฐ๊ณผโ€‹

  • JIT ์ปดํŒŒ์ผ: C2Compiler::compile_method, compiler_thread_loop ๋“ฑ์ด ๊ฐ€์žฅ ๋งŽ์€ CPU ์‹œ๊ฐ„ ์†Œ๋น„
  • JMX ์˜ค๋ฒ„ํ—ค๋“œ: DefaultMBeanServerInterceptor.getAttribute๊ฐ€ ์ƒ๋‹นํ•œ CPU ์‚ฌ์šฉ
  • ๋„คํŠธ์›Œํฌ ์ฒ˜๋ฆฌ: DefaultConnectionManager.acceptConnection์—์„œ ๋Œ€๊ธฐ ๊ด€๋ จ ํ•จ์ˆ˜(__psynch_cvwait) ๋ฐœ๊ฒฌ

๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น ํ”„๋กœํŒŒ์ผ ๊ฒฐ๊ณผโ€‹

๊ฐ€์žฅ ์ค‘์š”ํ•œ ๋ฐœ๊ฒฌ์ด์—ˆ์Šต๋‹ˆ๋‹ค

์ฃผ์š” ํ• ๋‹น ์ง€์ :

  1. HTTP ์š”์ฒญ ํŒŒ์‹ฑ (์•ฝ 18%): HttpHeaderParser.parse โ†’ ByteBuffer.allocate
  2. HTTP ์‘๋‹ต ๋ฒ„ํผ ์ƒ์„ฑ (์•ฝ 32%): HttpUtils.createResponseBuffer
  3. ์š”์ฒญ ๋ผ์šฐํŒ…/ํ•„ํ„ฐ๋ง: FilterChain.doFilter, HandlerMappingImpl.findHandler
  4. ๋ฌธ์ž์—ด ํŒŒ์‹ฑ/์ •๊ทœ์‹: Pattern.matcher, Pattern.split

์ดํ‰: ์ „์ฒด ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น์˜ ์•ฝ 50%๊ฐ€ ByteBuffer ์ƒ์„ฑ์— ์‚ฌ์šฉ๋˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

Wall-clock ํ”„๋กœํŒŒ์ผ ๊ฒฐ๊ณผโ€‹

์ „์ฒด ์‹คํ–‰ ์‹œ๊ฐ„ ์ค‘ 95% ์ด์ƒ์ด ์Šค๋ ˆ๋“œ์˜ ๋Œ€๊ธฐ(Waiting) ๋˜๋Š” ํœด๋ฉด(Idle) ์ƒํƒœ์˜€์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ •์ƒ์ ์ธ ํŒจํ„ด์œผ๋กœ, ์‹ค์ œ ์ž‘์—… ์‹œ์—๋Š” ์•ž์„œ ๋ฐœ๊ฒฌํ•œ ๋ณ‘๋ชฉ ์ง€์ ๋“ค์ด ๋ฌธ์ œ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

JMC(JDK Mission Control) ๋ถ„์„โ€‹

# JVM ์˜ต์…˜์œผ๋กœ JFR ๊ธฐ๋ก ํ™œ์„ฑํ™”
-XX:StartFlightRecording=filename=jit-profile/recording.jfr,duration=300s,settings=profile
-XX:+UnlockDiagnosticVMOptions
-XX:+LogCompilation
-XX:LogFile=jit-profile/hotspot_%p.log
-XX:+PrintInlining
-XX:+PrintCompilation

JIT ์ปดํŒŒ์ผ ์‹œ๊ฐ„ ๋ถ„์„โ€‹

๊ฐ€์žฅ ๊ธด ์ปดํŒŒ์ผ ์‹œ๊ฐ„(์•ฝ 4.75ms)์„ ์†Œ๋น„ํ•œ ๋ฉ”์„œ๋“œ๋“ค

  1. HttpUtils.readRawRequest - HTTP ์š”์ฒญ ์ฝ๊ธฐ
  2. ClassLoader ๊ด€๋ จ ๋ฉ”์„œ๋“œ - ํด๋ž˜์Šค ๋กœ๋”ฉ
  3. ObjectOutputStream.writeOrdinaryObject - ๊ฐ์ฒด ์ง๋ ฌํ™”

GC ๋ถ„์„โ€‹

Young Collection Total Time: 55.848 ms (123ํšŒ)
Old Collection Total Time: 25.299 ms (1ํšŒ)
Total GC Time: 81.146 ms
์ „์ฒด ์‹คํ–‰ ์‹œ๊ฐ„ ๋Œ€๋น„ GC ๋น„์œจ: 0.054%

GC ๋ถ€ํ•˜๋Š” ๊ฑฐ์˜ ์—†๋Š” ์ˆ˜์ค€์ด์—ˆ์ง€๋งŒ, ByteBuffer ํ• ๋‹น ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ฉด ๋” ๊ฐœ์„ ๋  ์—ฌ์ง€๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

JITWatch ๋ถ„์„โ€‹

# ์ƒ์„ธํ•œ JIT ๋กœ๊ทธ ์ƒ์„ฑ
-XX:+LogCompilation
-XX:+PrintInlining
-XX:+PrintAssembly

HttpUtils.readRawRequest ๋ถ„์„โ€‹

JITWatch๋ฅผ ํ†ตํ•ด ์ •ํ™•ํ•œ ๋ฌธ์ œ์ ์„ ๋ฐœ๊ฒฌํ–ˆ์Šต๋‹ˆ๋‹ค

๋ฐœ๊ฒฌ๋œ ๋ฌธ์ œ

  1. "callee is too large": ๋ฉ”์„œ๋“œ ํฌ๊ธฐ๊ฐ€ JIT ์ปดํŒŒ์ผ๋Ÿฌ์˜ ์ธ๋ผ์ด๋‹ ํ•œ๊ณ„(~325 ๋ฐ”์ดํŠธ) ์ดˆ๊ณผ
  2. "unpredictable branch": ๋ถ„๊ธฐ ์˜ˆ์ธก๋ฅ  50% (chunked vs content-length)
  3. "callee uses too much stack": new String(bytes, UTF_8) ๋ฐ˜๋ณต ์ƒ์„ฑ์œผ๋กœ ์Šคํƒ ์••๋ฐ•
// ๋ฌธ์ œ๊ฐ€ ์žˆ๋˜ ์›๋ณธ ์ฝ”๋“œ (93์ค„, ์•ฝ 450 ๋ฐ”์ดํŠธ์ฝ”๋“œ)
public static String readRawRequest(ByteBuffer initial, InputStream in) throws IOException {
StringBuilder sb = new StringBuilder();

// ... ํ—ค๋” ์ฝ๊ธฐ ๋กœ์ง ...

// ๋ถ„๊ธฐ ์˜ˆ์ธก ์‹คํŒจ ์›์ธ
if (chunked) {
bodyStart += readChunkedBody(bin);
} else if (contentLength > -1) {
// content-length ์ฒ˜๋ฆฌ
}

return headers + "\r\n\r\n" + bodyStart;
}

Phase 3: ์„ฑ๋Šฅ ๊ฐœ์„  1์ฐจ - ByteBufferPool ๋„์ž…โ€‹

๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ByteBuffer ํ’€๋ง์„ ๋„์ž…ํ–ˆ์Šต๋‹ˆ๋‹ค.

ByteBufferPool ๊ตฌํ˜„โ€‹

@Component
public class ByteBufferPool implements InfrastructureBean {
// ์ž์ฃผ ์“ฐ์ด๋Š” ๋ฒ„ํผ ํฌ๊ธฐ ์‚ฌ์ „ ์ •์˜
public static final int SMALL_BUFFER_SIZE = 2048; // 2KB โ†’ ํ”„๋กœํ† ์ฝœ ํƒ์ง€์šฉ
public static final int MEDIUM_BUFFER_SIZE = 8192; // 8KB โ†’ ์ผ๋ฐ˜ ์š”์ฒญ ์ฝ๊ธฐ์šฉ
public static final int LARGE_BUFFER_SIZE = 32768; // 32KB โ†’ ๋Œ€์šฉ๋Ÿ‰ ์‘๋‹ต์šฉ

private final ConcurrentHashMap<Integer, PoolConfig> pools;

public ByteBufferPool() {
this.pools = new ConcurrentHashMap<>();
initializePool(SMALL_BUFFER_SIZE, 500);
initializePool(MEDIUM_BUFFER_SIZE, 500);
initializePool(LARGE_BUFFER_SIZE, 100);
}

public ByteBuffer acquire(int size) {
int poolSize = findPoolSize(size);
PoolConfig config = pools.get(poolSize);

ByteBuffer buffer = config.pool.poll();
if (buffer != null) {
buffer.clear();
return buffer;
}

return allocateBuffer(poolSize);
}

public void release(ByteBuffer buffer) {
if (buffer == null) return;

PoolConfig config = pools.get(buffer.capacity());
if (config == null) return;

if (config.pool.size() >= config.maxPoolSize) return;

buffer.clear();
config.pool.offer(buffer);
}
}

์ ์šฉ ๊ฒฐ๊ณผโ€‹

์„ฑ๋Šฅ ํ–ฅ์ƒ

  • ์„ฑ๊ณต๋ฅ : 67% โ†’ 81% (14% ํ–ฅ์ƒ)
  • ByteBuffer ํ• ๋‹น: ์ „์ฒด ํ• ๋‹น์˜ 50% โ†’ ๊ฑฐ์˜ 0%๋กœ ๊ฐ์†Œ

๋ฉ”๋ชจ๋ฆฌ ํ”„๋กœํŒŒ์ผ ๋ณ€ํ™”

  • ByteBuffer.allocate ๊ด€๋ จ ์Šคํƒ์ด ๊ฑฐ์˜ ์‚ฌ๋ผ์ง
  • GC ๋ถ€๋‹ด ๊ฐ์†Œ๋กœ JMX ์˜ค๋ฒ„ํ—ค๋“œ๋„ ํ•จ๊ป˜ ๊ฐ์†Œ
  • CPU ํ”„๋กœํŒŒ์ผ์—์„œ JMX ๊ด€๋ จ CPU ์‚ฌ์šฉ๋Ÿ‰ ์™„์ „ ์ œ๊ฑฐ

Phase 4: ์„ฑ๋Šฅ ๊ฐœ์„  2์ฐจ - JIT ์นœํ™”์  ์ฝ”๋“œ ๋ฆฌํŒฉํ† ๋งโ€‹

JITWatch ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ HttpUtils.readRawRequest ๋ฉ”์„œ๋“œ๋ฅผ ๋ฆฌํŒฉํ† ๋งํ–ˆ์Šต๋‹ˆ๋‹ค.

๋ฆฌํŒฉํ† ๋ง ์›์น™โ€‹

  1. ๋ฉ”์„œ๋“œ ๋ถ„๋ฆฌ: ๋‹จ์ผ ์ฑ…์ž„ ์›์น™ + JIT ์ธ๋ผ์ด๋‹ ์ตœ์ ํ™” (< 325 ๋ฐ”์ดํŠธ)
  2. ์กฐ๊ธฐ ๋ฆฌํ„ด: ๋นˆ๋„ ๋†’์€ ์ผ€์ด์Šค ์šฐ์„  ์ฒ˜๋ฆฌ๋กœ ๋ถ„๊ธฐ ์˜ˆ์ธก๋ฅ  ํ–ฅ์ƒ
  3. ์ œ๋กœ ์นดํ”ผ: ๋ถˆํ•„์š”ํ•œ String ์ƒ์„ฑ ์ œ๊ฑฐ
  4. BufferedInputStream ์žฌ์‚ฌ์šฉ: ๋ฐ์ดํ„ฐ ์†์‹ค ๋ฐฉ์ง€

๋ฉ”์„œ๋“œ ๋ถ„๋ฆฌโ€‹

Before: ๋‹จ์ผ ๊ฑฐ๋Œ€ ๋ฉ”์„œ๋“œ (93์ค„, ~450 ๋ฐ”์ดํŠธ์ฝ”๋“œ)โ€‹

public static String readRawRequest(ByteBuffer initial, InputStream in) {
// 43์ค„์˜ ๋ณต์žกํ•œ ๋กœ์ง
// - ํ—ค๋” ์ฝ๊ธฐ
// - ํŒŒ์‹ฑ
// - ๋ฐ”๋”” ์ฝ๊ธฐ (content-length/chunked)
}

After: 3๊ฐœ์˜ ์ž‘์€ ๋ฉ”์„œ๋“œ๋กœ ๋ถ„๋ฆฌโ€‹

// 1. ์กฐํ•ฉ ๋ฉ”์„œ๋“œ (30์ค„, ~200 ๋ฐ”์ดํŠธ์ฝ”๋“œ)
public static String readRawRequest(ByteBuffer initial, InputStream in) throws IOException {
BufferedInputStream bin = new BufferedInputStream(in);
String headerPart = readHeadersFromStream(initial, bin);

int headerEnd = headerPart.indexOf("\r\n\r\n");
if (headerEnd < 0) return headerPart;

String headers = headerPart.substring(0, headerEnd);
String bodyStart = headerPart.substring(headerEnd + 4);

// ์กฐ๊ธฐ ๋ฆฌํ„ด ํŒจํ„ด (๋นˆ๋„ ์ˆœ์„œ)
int contentLength = parseContentLength(headers);
if (contentLength > 0) {
String body = readBodyWithContentLength(bin, contentLength, bodyStart);
return headers + "\r\n\r\n" + body;
}

if (contentLength == 0) {
return headers + "\r\n\r\n" + bodyStart;
}

if (isChunked(headers)) {
String chunkedBody = readChunkedBody(bin);
return headers + "\r\n\r\n" + bodyStart + chunkedBody;
}

return headers + "\r\n\r\n" + bodyStart;
}

// 2. ํ—ค๋” ์ฝ๊ธฐ ์ „์šฉ (18์ค„, ~120 ๋ฐ”์ดํŠธ์ฝ”๋“œ)
private static String readHeadersFromStream(ByteBuffer initial, BufferedInputStream bin) {
StringBuilder sb = new StringBuilder();

if (initial != null && initial.hasRemaining()) {
byte[] arr = new byte[initial.remaining()];
initial.get(arr);
sb.append(new String(arr, StandardCharsets.UTF_8));
}

while (!sb.toString().contains("\r\n\r\n")) {
int ch = bin.read();
if (ch == -1) break;
sb.append((char) ch);
}

return sb.toString();
}

// 3. ๋ฐ”๋”” ์ฝ๊ธฐ ์ „์šฉ (10์ค„, ~80 ๋ฐ”์ดํŠธ์ฝ”๋“œ)
private static String readBodyWithContentLength(BufferedInputStream bin,
int contentLength,
String bodyStart) {
int alreadyRead = bodyStart.getBytes(StandardCharsets.UTF_8).length;
int remaining = contentLength - alreadyRead;

if (remaining <= 0) return bodyStart;

byte[] bodyBytes = bin.readNBytes(remaining);
return bodyStart + new String(bodyBytes, StandardCharsets.UTF_8);
}

๊ฐœ์„  ํšจ๊ณผ:

  • ๋ชจ๋“  ๋ฉ”์„œ๋“œ๊ฐ€ 325 ๋ฐ”์ดํŠธ ์ดํ•˜๊ฐ€ ๋˜์–ด C2 ์ปดํŒŒ์ผ๋Ÿฌ ์ธ๋ผ์ด๋‹ ๋Œ€์ƒ์ด ๋จ
  • ๋ฉ”์„œ๋“œ๋ณ„ ์ฑ…์ž„์ด ๋ช…ํ™•ํ•ด์ ธ ํ…Œ์ŠคํŠธ์™€ ์œ ์ง€๋ณด์ˆ˜ ์šฉ์ด

์กฐ๊ธฐ ๋ฆฌํ„ด ํŒจํ„ดโ€‹

Before: ๋ณต์žกํ•œ if-else ์ค‘์ฒฉ (๋ถ„๊ธฐ ์˜ˆ์ธก๋ฅ  50%)โ€‹

if (chunked) {
// chunked ์ฒ˜๋ฆฌ (์‹ค์ œ๋กœ๋Š” 10% ๋ฏธ๋งŒ)
} else if (contentLength > -1) {
// content-length ์ฒ˜๋ฆฌ (์‹ค์ œ๋กœ๋Š” 80% ์ด์ƒ)
}

After: ๋นˆ๋„ ์ˆœ์„œ๋Œ€๋กœ ์กฐ๊ธฐ ๋ฆฌํ„ด (์˜ˆ์ƒ ๋ถ„๊ธฐ ์˜ˆ์ธก๋ฅ  80%+)โ€‹

// 1. Content-Length > 0 (80%+ ์ผ€์ด์Šค) โ†’ ์ฆ‰์‹œ ๋ฆฌํ„ด
int contentLength = parseContentLength(headers);
if (contentLength > 0) {
String body = readBodyWithContentLength(bin, contentLength, bodyStart);
return headers + "\r\n\r\n" + body;
}

// 2. Content-Length == 0 (5% ์ผ€์ด์Šค) โ†’ ์ฆ‰์‹œ ๋ฆฌํ„ด
if (contentLength == 0) {
return headers + "\r\n\r\n" + bodyStart;
}

// 3. Chunked (10% ๋ฏธ๋งŒ) โ†’ ์ฆ‰์‹œ ๋ฆฌํ„ด
if (isChunked(headers)) {
String chunkedBody = readChunkedBody(bin);
return headers + "\r\n\r\n" + bodyStart + chunkedBody;
}

๊ฐœ์„  ํšจ๊ณผ

  • CPU ๋ถ„๊ธฐ ์˜ˆ์ธก ์„ฑ๊ณต๋ฅ ์ด 50% โ†’ 80%๋กœ ํ–ฅ์ƒ
  • ํŒŒ์ดํ”„๋ผ์ธ ํ”Œ๋Ÿฌ์‹œ ๋นˆ๋„ ๊ฐ์†Œ

ํ—ค๋” ํŒŒ์‹ฑ ์ตœ์ ํ™”โ€‹

Before: split() + toLowerCase() (์š”์ฒญ๋‹น ~40๊ฐœ ๊ฐ์ฒด ์ƒ์„ฑ)โ€‹

private static int parseContentLength(String headers) {
for (String line : headers.split("\r\n")) { // String[] ๋ฐฐ์—ด ์ƒ์„ฑ
if (line.toLowerCase().startsWith("content-length:")) { // String ๋ณต์‚ฌ
return Integer.parseInt(line.split(":")[1].trim()); // ๋˜ ๋ฐฐ์—ด ์ƒ์„ฑ
}
}
return -1;
}

After: indexOf() + ์ง์ ‘ ๋ฌธ์ž ๋น„๊ต (0~1๊ฐœ ๊ฐ์ฒด ์ƒ์„ฑ)โ€‹

private static int parseContentLength(String headers) {
int pos = 0;
int headersLength = headers.length();

while (pos < headersLength) {
int lineEnd = headers.indexOf("\r\n", pos);
if (lineEnd < 0) lineEnd = headersLength;

// "content-length:" ๋Œ€์†Œ๋ฌธ์ž ๋ฌด์‹œ ๋น„๊ต (15์ž)
if (regionMatchesIgnoreCase(headers, pos, "content-length:", 15)) {
int colonIdx = headers.indexOf(':', pos);
if (colonIdx < 0 || colonIdx >= lineEnd) {
pos = lineEnd + 2;
continue;
}

// ๊ฐ’ ์ถ”์ถœ (trim ์—†์ด ์ง์ ‘ ์ฒ˜๋ฆฌ)
int valueStart = colonIdx + 1;
while (valueStart < lineEnd && headers.charAt(valueStart) == ' ') {
valueStart++;
}

int valueEnd = lineEnd;
while (valueEnd > valueStart && headers.charAt(valueEnd - 1) == ' ') {
valueEnd--;
}

try {
return Integer.parseInt(headers.substring(valueStart, valueEnd));
} catch (NumberFormatException e) {
return -1;
}
}

pos = lineEnd + 2;
}
return -1;
}

๊ฐœ์„  ํšจ๊ณผ

  • ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น: ์š”์ฒญ๋‹น 40๊ฐœ โ†’ 01๊ฐœ ๊ฐ์ฒด (97.5% ๊ฐ์†Œ)
  • String[] ๋ฐฐ์—ด ์ƒ์„ฑ ์ œ๊ฑฐ
  • toLowerCase() ๋ณต์‚ฌ ์ œ๊ฑฐ
  • split(":") ๋ฐฐ์—ด ์ƒ์„ฑ ์ œ๊ฑฐ

๋ฆฌํŒฉํ† ๋ง 2์ฐจ ๊ฒฐ๊ณผโ€‹

Full Warm-up ํ…Œ์ŠคํŠธ (์•ฝ 120,000 ์š”์ฒญ)โ€‹

  • ์„ฑ๊ณต๋ฅ : 99.73% โ†’ 99.84% (0.11% ํ–ฅ์ƒ)
  • JIT ์ปดํŒŒ์ผ ์‹œ๊ฐ„: readRawRequest ๋ณ‘๋ชฉ ์ง€์  ์™„์ „ํžˆ ์ œ๊ฑฐ
  • JITWatch ์ œ์•ˆ ์‚ฌํ•ญ: 0๊ฐœ (๋ชจ๋“  ์ตœ์ ํ™” ์™„๋ฃŒ)

GC ์˜ํ–ฅ ๋ถ„์„โ€‹

Young GC Count: 123 โ†’ 127 (+3.2%)
Young GC Total Time: 55.848 ms โ†’ 62.710 ms (+12.3%)
Average GC Time: 0.454 ms โ†’ 0.494 ms (+8.8%)

GC ์••๋ ฅ์ด ์•ฝ๊ฐ„ ์ฆ๊ฐ€ํ–ˆ์ง€๋งŒ ๋ฌด์‹œ ๊ฐ€๋Šฅํ•œ ์ˆ˜์ค€์ž…๋‹ˆ๋‹ค. substring() ์‚ฌ์šฉ์œผ๋กœ ์ธํ•œ ๋‹จ๊ธฐ ๊ฐ์ฒด ์ฆ๊ฐ€๊ฐ€ ์›์ธ์œผ๋กœ ์ถ”์ •๋ฉ๋‹ˆ๋‹ค.

์ตœ์ข… ์„ฑ๋Šฅ ๊ฐœ์„  ๊ฒฐ๊ณผโ€‹

Partial Warm-up ํ…Œ์ŠคํŠธ (์•ฝ 8,000 ์š”์ฒญ)โ€‹

์‹ค์ œ ์šด์˜ ํ™˜๊ฒฝ์— ๊ฐ€๊นŒ์šด ์งง์€ Warm-up ์‹œ๋‚˜๋ฆฌ์˜ค๋กœ ์ตœ์ข… ์„ฑ๋Šฅ์„ ์ธก์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

I/O ๋ชจ๋ธExecutor ํƒ€์ž…๊ธฐ์กด ์„ฑ๊ณต๋ฅ (%)๊ฐœ์„  ํ›„ ์„ฑ๊ณต๋ฅ (%)๊ฐœ์„ ํญ(ฮ”%)
Hybrid (BIO)Platform Threads83.9596.54+12.59
Hybrid (BIO)Virtual Threads88.0096.25+8.25
NIOPlatform Threads72.7996.28+23.49
NIOVirtual Threads69.0098.00+29.00

์ฃผ์š” ๊ฐœ์„  ์‚ฌํ•ญโ€‹

1. Warm-up ์‹œ๊ฐ„ ๋‹จ์ถ•โ€‹

  • ์•ˆ์ •ํ™”๊นŒ์ง€ ์†Œ์š” ์‹œ๊ฐ„: ์•ฝ 60์ดˆ โ†’ ์•ฝ 20์ดˆ
  • JIT ์ปดํŒŒ์ผ ๋ณ‘๋ชฉ ์ œ๊ฑฐ๋กœ ์ดˆ๊ธฐ ์‘๋‹ต ์‹คํŒจ์œจ ๋Œ€ํญ ๊ฐ์†Œ

2. NIO ๊ตฌ์กฐ์˜ ๊ทน์ ์ธ ๊ฐœ์„ โ€‹

  • NIO + Virtual Thread: 69% โ†’ 98% (+29%)
  • NIO + Platform Thread: 72.79% โ†’ 96.28% (+23.49%)
  • NIO ๊ตฌ์กฐ๊ฐ€ cold-start์— ๊ฐ€์žฅ ๋ฏผ๊ฐํ–ˆ์œผ๋‚˜, ๊ฐœ์„  ํ›„ ๊ฐ€์žฅ ํฐ ํญ์˜ ํ–ฅ์ƒ

3. ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ ๊ฐœ์„ โ€‹

  • ByteBuffer ํ• ๋‹น: ์ „์ฒด ํ• ๋‹น์˜ 50% โ†’ ๊ฑฐ์˜ 0%
  • GC ๋ถ€๋‹ด ๊ฐ์†Œ๋กœ ์•ˆ์ •์ ์ธ ์‘๋‹ต ์‹œ๊ฐ„ ์œ ์ง€
  • JMX ์˜ค๋ฒ„ํ—ค๋“œ ์™„์ „ ์ œ๊ฑฐ

4. CPU ํšจ์œจ ํ–ฅ์ƒโ€‹

  • JIT ์ปดํŒŒ์ผ ์‹œ๊ฐ„ ๊ฐ์†Œ
  • ๋ถ„๊ธฐ ์˜ˆ์ธก ์„ฑ๊ณต๋ฅ  ํ–ฅ์ƒ
  • ๋ฉ”์„œ๋“œ ์ธ๋ผ์ด๋‹ ์„ฑ๊ณต

๊ถŒ์žฅ ๊ตฌ์„ฑโ€‹

ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ณ„ ๊ถŒ์žฅ ๊ตฌ์„ฑ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค

๋†’์€ ์ฒ˜๋ฆฌ๋Ÿ‰์ด ํ•„์š”ํ•œ API ์„œ๋ฒ„โ€‹

server:
execution-mode: hybrid
thread-type: virtual

ํŠน์ง•

  • ๊ฐ„๋‹จํ•œ ์ฝ”๋“œ๋กœ ๋†’์€ ์ฒ˜๋ฆฌ๋Ÿ‰
  • ์ž๋™ ๋ฐฑํ”„๋ ˆ์…”
  • ๊ฐ€์ƒ ์Šค๋ ˆ๋“œ์™€ ์ž˜ ์ž‘๋™

์ตœ๋Œ€ ํ™•์žฅ์„ฑ์ด ํ•„์š”ํ•œ ์„œ๋ฒ„ (์—ฐ๊ฒฐ ์ง‘์•ฝ์ )โ€‹

server:
execution-mode: nio
thread-type: virtual

ํŠน์ง•

  • cold-start์—์„œ ์ตœ๊ณ  ๊ฐœ์„ ํญ (+29%)
  • ์•ˆ์ •ํ™” ํ›„ ๊ฐ€์žฅ ๋†’์€ ์„ฑ๊ณต๋ฅ  (98%)
  • ์ˆ˜์ฒœ ๊ฐœ์˜ ๋™์‹œ ์—ฐ๊ฒฐ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ

์•ˆ์ •์„ฑ ์šฐ์„  ์„œ๋ฒ„โ€‹

server:
execution-mode: hybrid
thread-type: platform
thread-pool-size: 200

ํŠน์ง•

  • ๊ฐ€์žฅ ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ๋™์ž‘
  • ๋ชจ๋“  ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ ๊ท ํ˜•์žกํžŒ ์„ฑ๋Šฅ
  • ๋ ˆ๊ฑฐ์‹œ ํ˜ธํ™˜์„ฑ

์ถ”๊ฐ€ ์ตœ์ ํ™” ๊ณ ๋ ค์‚ฌํ•ญโ€‹

1. JVM ์˜ต์…˜ ํŠœ๋‹โ€‹

Tiered Compilationโ€‹

# C2 ์ปดํŒŒ์ผ๋Ÿฌ๋งŒ ์‚ฌ์šฉ (์ตœ๋Œ€ ์„ฑ๋Šฅ, ๋А๋ฆฐ ์‹œ์ž‘)
-XX:TieredStopAtLevel=4

# C1 ์ปดํŒŒ์ผ๋Ÿฌ๋งŒ ์‚ฌ์šฉ (๋น ๋ฅธ ์‹œ์ž‘, ๋‚ฎ์€ ํ”ผํฌ ์„ฑ๋Šฅ)
-XX:TieredStopAtLevel=1

JIT ์ปดํŒŒ์ผ ์ž„๊ณ„๊ฐ’ ์กฐ์ •โ€‹

# ๋” ๋น ๋ฅธ JIT ์ปดํŒŒ์ผ ํŠธ๋ฆฌ๊ฑฐ
-XX:CompileThreshold=5000

# ์ธ๋ผ์ด๋‹ ํ•œ๊ณ„ ์ฆ๊ฐ€
-XX:MaxInlineSize=500
-XX:FreqInlineSize=500

2. ์ถ”๊ฐ€ ํ’€๋ง ๋Œ€์ƒโ€‹

ํ˜„์žฌ ByteBuffer๋งŒ ํ’€๋งํ•˜๊ณ  ์žˆ์ง€๋งŒ, ๋‹ค์Œ ๊ฐ์ฒด๋“ค๋„ ํ’€๋ง ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค

// HttpRequest/HttpResponse ๊ฐ์ฒด ํ’€๋ง
@Component
public class HttpObjectPool {
private final Queue<HttpRequest<?>> requestPool = new ConcurrentLinkedQueue<>();
private final Queue<HttpResponse> responsePool = new ConcurrentLinkedQueue<>();

public HttpRequest<?> borrowRequest() { /* ... */ }
public void returnRequest(HttpRequest<?> request) { /* ... */ }
}

3. ๋ผ์šฐํŒ… ์บ์‹œ ๊ฐœ์„ โ€‹

ํ˜„์žฌ ๊ฐ„๋‹จํ•œ ์บ์‹ฑ๋งŒ ์ ์šฉ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค

// ํ˜„์žฌ ๊ตฌํ˜„
private final Map<String, PathPattern> pathPatterns = new ConcurrentHashMap<>();

// ๊ฐœ์„  ๊ฐ€๋Šฅ ๋ฐฉํ–ฅ: LRU ์บ์‹œ
private final Cache<String, RequestMappingInfo> routingCache =
CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterAccess(10, TimeUnit.MINUTES)
.build();

4. GC ํŠœ๋‹โ€‹

# G1 GC ์ตœ์ ํ™”
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:G1HeapRegionSize=16m

# ๋˜๋Š” ์ €์ง€์—ฐ GC (Shenandoah, ZGC)
-XX:+UseShenandoahGC
# -XX:+UseZGC

์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋งโ€‹

ํ”„๋กœํŒŒ์ผ๋ง ์Šคํฌ๋ฆฝํŠธโ€‹

ํ”„๋กœ์ ํŠธ์— ํฌํ•จ๋œ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

# async-profiler๋ฅผ ์‚ฌ์šฉํ•œ ํ”„๋กœํŒŒ์ผ๋ง
./run-performance-tests.sh

# JIT ๋ถ„์„์„ ์œ„ํ•œ ์ƒ์„ธ ๋กœ๊ทธ
./jit-benchmark.sh

Wireshark๋ฅผ ํ†ตํ•œ ๋„คํŠธ์›Œํฌ ๋ถ„์„โ€‹

# HTTP ํŠธ๋ž˜ํ”ฝ ์บก์ฒ˜ ๋ฐ ๋ถ„์„
./wireshark-analysis.sh

๊ฒฐ๋ก โ€‹

Sprout ์„œ๋ฒ„๋Š” ์ฒด๊ณ„์ ์ธ ์„ฑ๋Šฅ ๋ถ„์„๊ณผ ๊ฐœ์„  ์ž‘์—…์„ ํ†ตํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์„ฑ๊ณผ๋ฅผ ๋‹ฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค

์ฃผ์š” ์„ฑ๊ณผโ€‹

  1. Warm-up ์‹œ๊ฐ„ 67% ๋‹จ์ถ•: 60์ดˆ โ†’ 20์ดˆ
  2. ์ดˆ๊ธฐ ์„ฑ๊ณต๋ฅ  ์ตœ๋Œ€ 29% ํ–ฅ์ƒ: NIO + VT ์กฐํ•ฉ์—์„œ 69% โ†’ 98%
  3. ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ 97.5% ๊ฐœ์„ : ์š”์ฒญ๋‹น ๊ฐ์ฒด ์ƒ์„ฑ 40๊ฐœ โ†’ 0~1๊ฐœ
  4. JIT ์ปดํŒŒ์ผ ๋ณ‘๋ชฉ ์™„์ „ ์ œ๊ฑฐ: ์ฃผ์š” hot path ๋ฉ”์„œ๋“œ ์ธ๋ผ์ด๋‹ ์„ฑ๊ณต

ํ•ต์‹ฌ ๊ตํ›ˆโ€‹

  1. ํ”„๋กœํŒŒ์ผ๋ง์˜ ์ค‘์š”์„ฑ: async-profiler, JMC, JITWatch๋ฅผ ์กฐํ•ฉํ•˜์—ฌ ์ •ํ™•ํ•œ ๋ณ‘๋ชฉ ์ง€์  ํŒŒ์•…
  2. JIT ์นœํ™”์  ์ฝ”๋”ฉ: ๋ฉ”์„œ๋“œ ํฌ๊ธฐ, ๋ถ„๊ธฐ ์˜ˆ์ธก, ์กฐ๊ธฐ ๋ฆฌํ„ด ํŒจํ„ด์ด ์„ฑ๋Šฅ์— ํฐ ์˜ํ–ฅ
  3. ๊ฐ์ฒด ํ’€๋ง: GC ์–ธ์–ด์—์„œ ๋ฉ”๋ชจ๋ฆฌ ์ฃผ๋„๊ถŒ์„ ์žก๋Š” ํšจ๊ณผ์ ์ธ ๋ฐฉ๋ฒ•
  4. ๊ตฌ์กฐ์  ์œ ์—ฐ์„ฑ: ๋‹ค์–‘ํ•œ I/O ๋ชจ๋ธ๊ณผ ์Šค๋ ˆ๋“œ ์ „๋žต์œผ๋กœ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ณ„ ์ตœ์ ํ™” ๊ฐ€๋Šฅ

์šด์˜ ๊ถŒ์žฅ์‚ฌํ•ญโ€‹

  • ์ผ๋ฐ˜์ ์ธ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜: Hybrid + Virtual Thread
  • ๊ณ ๋ถ€ํ•˜ ์‹ค์‹œ๊ฐ„ ์„œ๋น„์Šค: NIO + Virtual Thread
  • ์•ˆ์ •์„ฑ ์šฐ์„ : Hybrid + Platform Thread

์œ„ ์„ฑ๋Šฅ ๊ฐœ์„ ์ ์— ๋Œ€ํ•œ ๋ถ€๊ฐ€์ ์ธ ์‚ฌํ•ญ์€ ์ œ ๊ฐœ์ธ ๋ธ”๋กœ๊ทธ์— ๊ธฐ์žฌ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.