Backend

<Spring> SUSU์˜ Coroutine

wjdtkdgns 2024. 5. 11. 00:04

๐Ÿ“Œ SUSU ํ”„๋กœ์ ํŠธ

YAPP 23๊ธฐ์—์„œ ์ง„ํ–‰ํ•œ ๊ฒฝ์กฐ์‚ฌ๋น„ ๊ด€๋ฆฌ ์„œ๋น„์Šค์ด๋‹ค.

์ž์„ธํ•œ ๋‚ด์šฉ์€ ์•„๋ž˜ ๋งํฌ๋ฅผ ์ด์šฉํ•˜๋ฉด ๋œ๋‹ค.

๊ฒฐํ˜ผ์‹, ๋Œ์ž”์น˜, ์žฅ๋ก€์‹, ์ƒ์ผ ๊ฐ™์€ ๊ฒฝ์กฐ์‚ฌ๋ฅผ ์ฑ™๊ธด ์  ์žˆ๋‚˜์š”?
์ฃผ๊ณ ๋ฐ›์€ ์†Œ์ค‘ํ•œ ๋งˆ์Œ๋“ค, ์ˆ˜์ˆ˜์™€ ํ•จ๊ป˜ ๊ฒฝ์กฐ์‚ฌ๋น„๋ฅผ ๋˜‘๋˜‘ํ•˜๊ฒŒ ๊ด€๋ฆฌํ•ด์š”!
 

Disquiet*

IT ์„œ๋น„์Šค ๋ฉ”์ด์ปค๋“ค์˜ ์†Œ์…œ ๋„คํŠธ์›Œํฌ. ๋””์Šค์ฝฐ์ด์—‡์—์„œ ์„œ๋กœ์˜ ํ”„๋กœ์ ํŠธ๋ฅผ ๊ณต์œ ํ•ด ๋ณด์„ธ์š”!

disquiet.io

๋งŽ๊ด€๋ถ€ ใ…Žใ…Ž

 

๐Ÿ“Œ ์™œ Coroutine์„ ์ผ์„๊นŒ

๐Ÿ”ต Coroutine ์ด๋ž€

์ž์„ธํ•œ ์„ค๋ช…์€ ์ด ๊ธ€์—์„œ ์•ˆ ํ•  ๊ฒƒ์ด๋‹ค.

๊ฐ„๋‹จํžˆ ์†Œ๊ฐœ๋งŒ ํ•ด๋ณด๊ฒ ๋‹ค.

 

์œ„ํ‚ค๋ฐฑ๊ณผ์—๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์“ฐ์—ฌ์žˆ๋‹ค.

Coroutines are computer program components that allow execution to be suspended and resumed, generalizing subroutines for cooperative multitasking.

์ค‘์ง€, ์žฌ๊ฐœ๋ฅผ ์ง€์›ํ•˜๋Š” ์ปดํ“จํ„ฐ ํ”„๋กœ๊ทธ๋žจ ๊ตฌ์„ฑ์š”์†Œ

ํ˜‘๋ ฅ์  ๋ฉ€ํ‹ฐํ…Œ์Šคํ‚น์„ ์œ„ํ•œ ์ผ๋ฐ˜ํ™”๋œ ์„œ๋ธŒ ๋ฃจํ‹ด

 

์ด๋ฅผ ๋งŒ๋“  ๋ชฉํ‘œ๋Š” ์ด๋Ÿฌํ•˜๋‹ค

- No dependency on a particular implementation of Futures or other such rich library;
- Cover equally the "async/await" use case and "generator blocks";
- Make it possible to utilize Kotlin coroutines as wrappers for different existing asynchronous APIs (such as Java NIO, different implementations of Futures, etc).

๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ Coroutine์„ ํ†ตํ•ด ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ฒŒ ์ด๊ฒƒ์˜ ๋ชฉํ‘œ์ด๋‹ค.

 

์‚ฌ๋žŒ๋“ค์€ ํ”ํžˆ Coroutine์„ ๊ฒฝ๋Ÿ‰ ์“ฐ๋ ˆ๋“œ๋ผ๊ณ  ํ•œ๋‹ค.

์ด๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ํŠน์ง• ๋•Œ๋ฌธ์ด๋‹ค.

  1. context-switching ๋น„์šฉ์ด ์ ๋‹ค
  2. suspend๋ฅผ ์ด์šฉํ•œ ๋ฃจํ‹ด์˜ ์ค‘์ง€์™€ ์žฌ๊ฐœ๋ฅผ ์ง€์›ํ•œ๋‹ค.
  3. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ˆ˜์ค€์—์„œ ์Šค์ผ€์ค„๋ง์„ ๊ด€๋ฆฌํ•œ๋‹ค.

์ •๋ฆฌํ•ด๋ณด์ž๋ฉด ์ฝ”๋ฃจํ‹ด์€ ์ด๋Ÿฌํ•˜๋‹ค.

  • ์‹คํ–‰์˜ ์ง€์—ฐ๊ณผ ์žฌ๊ฐœ๋ฅผ ํ—ˆ์šฉํ•จ์œผ๋กœ์จ, ๋น„์„ ์ ์  ๋ฉ€ํ‹ฐํƒœ์Šคํ‚น์„ ์œ„ํ•œ ์„œ๋ธŒ ๋ฃจํ‹ด์„ ์ผ๋ฐ˜ํ™”ํ•œ ์ปดํ“จํ„ฐ ํ”„๋กœ๊ทธ๋žจ ๊ตฌ์„ฑ์š”์†Œ
  • ๋น„๋™๊ธฐ + non-blocking ์ง€์›ํ•ด ์ฃผ๋Š” ๊ฒฝ๋Ÿ‰ ์Šค๋ ˆ๋“œ

 

๐Ÿ”ต ์ˆ˜์ˆ˜์˜ ๊ธฐ์ˆ  ์Šคํƒ

์ˆ˜์ˆ˜๋Š” Kotlin, Spring์„ ์ด์šฉํ–ˆ๋‹ค.

Spring์€ Reactive Stack์œผ๋กœ ๊ตฌ์„ฑํ–ˆ๋‹ค.

ํ•˜์ง€๋งŒ R2DBC๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜๋‹ค.

๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ์„ ๋†’์ด๊ธฐ ์œ„ํ•ด JPA๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

์ด๋ฅผ ์œ„ํ•ด ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์•ผ ํ–ˆ๊ณ , ์ด๋ฅผ Coroutine์„ ์ด์šฉํ•ด ์ฒ˜๋ฆฌํ–ˆ๋‹ค.

 

๐Ÿ”ต Webflux์™€ Blocking I/O

Webflux๋Š” Netty๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ž‘๋™ํ•œ๋‹ค.

Netty๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ์†Œ์ˆ˜์˜ EventLoop๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ž‘๋™ํ•œ๋‹ค.

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

 

์ด ์‹œ์Šคํ…œ์— Blocking ๋กœ์ง์ด ์ฃผ์–ด์ง€๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?

Reactor MeltDown์ด ๋ฐœ์ƒํ•œ๋‹ค.

์ด๋•Œ ์•ž์„  ์ž‘์—…์ด ๋๋‚˜์ง€ ์•Š๊ณ  EventLoop๋ฅผ ์ ์œ ํ•ด ๋ฒ„๋ฆฐ๋‹ค๋ฉด, ํ์— ์Œ“์ธ ์ž‘์—…์€ ๋Œ€๊ธฐํ•˜๋Š” ์ƒํ™ฉ์ด ๋œ๋‹ค.

์ด๋Š” ๊ณง ํ”„๋กœ๊ทธ๋žจ์˜ ์ „์ฒด์ ์ธ ์„ฑ๋Šฅ ์ €ํ•˜๋กœ ์ด์–ด์ง„๋‹ค.

 

๊ทธ๋Ÿฌ๋ฏ€๋กœ Blocking ๋กœ์ง์ด EventLoop์—์„œ ๋™์ž‘ํ•˜๋Š” ์ƒํ™ฉ์„ ๋งŒ๋“ค๋ฉด ์•ˆ ๋œ๋‹ค.

์‹ค์ œ๋กœ ๊ณต์‹ ๋ฌธ์„œ์—๋„ ์ด๋ ‡๊ฒŒ ์ ํ˜€์žˆ๋‹ค.

Blocking API๋Š” ๋™์‹œ์„ฑ ๋ชจ๋ธ์— ์ข‹์ง€ ์•Š๋‹ค.

์ฒ˜๋ฆฌํ•  ๊ฑฐ๋ฉด ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ์—์„œ ์ฒ˜๋ฆฌํ•ด๋ผ.

์ด๊ฒŒ ๊ณต์‹ ๋ฌธ์„œ๊ฐ€ ๊ถŒํ•˜๋Š” Webflux์—์„œ Blocking ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฒ•์ด๋‹ค.


SUSU ์„œ๋น„์Šค์—์„œ๋„ Blocking ๋กœ์ง์ด ์กด์žฌํ•œ๋‹ค.

JPA๊ฐ€ ์ด์— ํ•ด๋‹นํ•œ๋‹ค.

JPA๊ฐ€ JDBC ๊ธฐ๋ฐ˜์œผ๋กœ ์ž‘๋™ํ•ด์„œ Blocking ํ•˜๊ฒŒ ์ž‘๋™ํ•œ๋‹ค.

๊ทธ๋ž˜์„œ ์ด ๋ถ€๋ถ„์„ ๋น„๋™๊ธฐ์ฒ˜๋ฆฌ ํ•ด์•ผ ํ–ˆ๊ณ , ์ด๋ฅผ ์ฝ”๋ฃจํ‹ด์„ ์ด์šฉํ•ด ์ฒ˜๋ฆฌํ–ˆ๋‹ค.

 

๐Ÿ”ต Webflux, Reactor์™€ Coroutine

Webflux๋Š” Project Reactor๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌํ˜„๋˜์—ˆ๋‹ค.

๊ทธ๋ž˜์„œ ๋‚ด๋ถ€ ๊ตฌ์กฐ๋ฅผ ์‚ดํŽด๋ณด๋ฉด Mono, Flux๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

Coroutine์„ ์ด์šฉํ•˜์ง€ ์•Š๊ณ  ๊ตฌํ˜„ํ•œ๋‹ค๋ฉด, Project Reactor๋ฅผ ์ด์šฉํ•ด ๊ตฌํ˜„ํ•˜๋ฉด ๋œ๋‹ค.

 

๊ทธ๋ ‡๋‹ค๋ฉด Webflux + Reactor์™€ Coroutine์€ ์–ด๋–ป๊ฒŒ ๊ณต์กดํ•  ์ˆ˜ ์žˆ๋Š”๊ฐ€?

์ด๋Š” Webflux์˜ RequestMappingHandlerAdapter  handle(...)์„ํ™•์ธํ•ด ๋ณด๋ฉด ์•Œ ์ˆ˜ ์žˆ๋‹ค.

@Override
public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
    ... // ์ƒ๋žต
    InvocableHandlerMethod invocableMethod = this.methodResolver.getRequestMappingMethod(handlerMethod);
    ... // ์ƒ๋žต
    Mono<HandlerResult> resultMono = this.modelInitializer
            .initModel(handlerMethod, bindingContext, exchange)
            .then(Mono.defer(() -> invocableMethod.invoke(exchange, bindingContext)))
            .doOnNext(result -> result.setExceptionHandler(exceptionHandler))
            .onErrorResume(ex -> exceptionHandler.handleError(exchange, ex));
    ... // ์ƒ๋žต
}

์š”์ฒญ์— ์•Œ๋งž์€ Method๋ฅผ ์ฐพ์•„์„œ ์‹คํ–‰ํ•œ๋‹ค.

ํ•ด๋‹น invoke๋ฅผ ๋”ฐ๋ผ ๋“ค์–ด๊ฐ€๋‹ค ๋ณด๋ฉด, InvocableHandlerMethod์˜ invokeFunction(...)์œผ๋กœ ์ด์–ด์ง„๋‹ค.

@Nullable
public static Object invokeFunction(Method method, Object target, Object[] args, boolean isSuspendingFunction, ServerWebExchange exchange) throws InvocationTargetException, IllegalAccessException {
    if (isSuspendingFunction) {
        Object coroutineContext = exchange.getAttribute("org.springframework.web.server.CoWebFilter.context");
        return coroutineContext == null ? CoroutinesUtils.invokeSuspendingFunction(method, target, args) : CoroutinesUtils.invokeSuspendingFunction((CoroutineContext)coroutineContext, method, target, args);
    } else {
        KFunction<?> function = ReflectJvmMapping.getKotlinFunction(method);
        if (function == null) {
            return method.invoke(target, args);
        } else {
            ... // ์ƒ๋žต
        }
    }
}

 

์ด ํ•จ์ˆ˜์—์„œ suspend ํ•จ์ˆ˜๋ƒ ์•„๋‹ˆ๋ฉด ์ผ๋ฐ˜ ํ•จ์ˆ˜๋ƒ์— ๋”ฐ๋ผ์„œ ๋ถ„๊ธฐ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

๋งŒ์•ฝ suspend function์ž„์„ ๊ฐ€์ •ํ•˜๊ณ  ์ง„ํ–‰ํ•œ๋‹ค๋ฉด, CoroutinesUtils์˜ invokeSuspendingFunction(...)๋ฅผ ๋งˆ์ฃผํ•˜๊ฒŒ ๋œ๋‹ค.

/**
 * Invoke a suspending function and converts it to {@link Mono} or
 * {@link Flux}.
 * @param context the coroutine context to use
 * @param method the suspending function to invoke
 * @param target the target to invoke {@code method} on
 * @param args the function arguments. If the {@code Continuation} argument is specified as the last argument
 * (typically {@code null}), it is ignored.
 * @return the method invocation result as reactive stream
 * @throws IllegalArgumentException if {@code method} is not a suspending function
 * @since 6.0
 */
@SuppressWarnings("deprecation")
public static Publisher<?> invokeSuspendingFunction(CoroutineContext context, Method method, Object target,
        Object... args) {
    Assert.isTrue(KotlinDetector.isSuspendingFunction(method), "'method' must be a suspending function");
    KFunction<?> function = Objects.requireNonNull(ReflectJvmMapping.getKotlinFunction(method));
    if (method.isAccessible() && !KCallablesJvm.isAccessible(function)) {
        KCallablesJvm.setAccessible(function, true);
    }
    Mono<Object> mono = MonoKt.mono(context, (scope, continuation) ->
                KCallables.callSuspend(function, getSuspendedFunctionArgs(method, target, args), continuation))
            .filter(result -> !Objects.equals(result, Unit.INSTANCE))
            .onErrorMap(InvocationTargetException.class, InvocationTargetException::getTargetException);
    ... // ์ƒ๋žต
    return mono;
}

์„ค๋ช…์—๋„ ๋‚˜์™€์žˆ๋“ฏ์ด, suspend function์„ ์‹คํ–‰์‹œํ‚ค๊ณ  Mono, Flux๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

Mono๋ฅผ Coroutine์— ๋งž์ถฐ ๋ณ€ํ™˜ํ•œ ๋‹ค์Œ suspend function์„ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

์ด์— ๋”๋ถˆ์–ด Reactor Context๋ฅผ Coroutine Context๋กœ ๋ณ€ํ™˜ํ•ด ์ฃผ๋Š” ์ž‘์—…๋„ ํ•ด์ค€๋‹ค.

public fun <T> mono(
    context: CoroutineContext = EmptyCoroutineContext,
    block: suspend CoroutineScope.() -> T?
): Mono<T> {
    require(context[Job] === null) { "Mono context cannot contain job in it." +
            "Its lifecycle should be managed via Disposable handle. Had $context" }
    return monoInternal(GlobalScope, context, block)
}

private fun <T> monoInternal(
    scope: CoroutineScope, // support for legacy mono in scope
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T?
): Mono<T> = Mono.create { sink ->
    val reactorContext = context.extendReactorContext(sink.currentContext())
    val newContext = scope.newCoroutineContext(context + reactorContext)
    val coroutine = MonoCoroutine(newContext, sink)
    sink.onDispose(coroutine)
    coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
}

internal fun CoroutineContext.extendReactorContext(extensions: ContextView): CoroutineContext =
    (this[ReactorContext]?.context?.putAll(extensions) ?: extensions).asCoroutineContext()

 

์ด๋ฅผ ํ†ตํ•ด Reactor + Webflux์™€ Coroutine์€ ๊ณต์กดํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

 

๐Ÿ“Œ ์–ด๋””์— ์–ด๋–ป๊ฒŒ Coroutine์„ ์ผ์„๊นŒ

SUSU ํ”„๋กœ์ ํŠธ์— ์ฝ”๋ฃจํ‹ด์ด ์ฒ˜๋ฆฌ๋œ ๊ณณ์€ ํฌ๊ฒŒ 3๊ฐœ์ด๋‹ค.

  1. Blocking I/O ์ฒ˜๋ฆฌ
  2. ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ 
  3. ์ด๋ฒคํŠธ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ

 

๐Ÿ”ต Blocking I/O ์ฒ˜๋ฆฌ

Coroutine์€ ์—ฌ๋Ÿฌ Dispatcher๋ฅผ ์ง€์›ํ•ด ์ค€๋‹ค.

Dispatcher๋ฅผ ๋ณ€๊ฒฝํ•˜์—ฌ ์ฝ”๋ฃจํ‹ด์„ ์‹คํ–‰ํ•˜๊ณ  ์‹ถ์„ ๋•Œ, withContext๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

SUSU์—์„œ๋Š” MDC ๊ฐ’์„ ์ž์‹ ์ฝ”๋ฃจํ‹ด์— ์ „ํŒŒํ•˜๊ธฐ ์œ„ํ•ด ์•„๋ž˜์™€ ๊ฐ™์€ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉํ–ˆ๋‹ค.

suspend fun <T> withMDCContext(
    context: CoroutineContext = Dispatchers.IO,
    block: suspend () -> T,
): T {
    val contextMap = MDC.getCopyOfContextMap() ?: emptyMap()
    return withContext(context + MDCContext(contextMap)) { block() }
}

 

์ด ํ•จ์ˆ˜๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์‚ฌ์šฉ๋˜์—ˆ๋‹ค.

suspend fun findByIdOrNull(uid: Long): User? {
    return withMDCContext(Dispatchers.IO) { userRepository.findByIdOrNull(uid) }
}

IO ์ž‘์—…์— ์ตœ์ ํ™”๋œ Dispatcher.IO๋ฅผ ํ†ตํ•ด IO ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•จ์œผ๋กœ์จ ๋” ํšจ์œจ์ ์ธ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ธฐ ์œ„ํ•ด ๋…ธ๋ ฅํ–ˆ๋‹ค

 

๐Ÿ”ต ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ

์„œ๋น„์Šค์˜ latency๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด ๋‹ค์ˆ˜์˜ ์ž‘์—…์„ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ–ˆ๋‹ค.

๋Œ€ํ‘œ์ ์œผ๋กœ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

val userEnvelopeStatistic = parZip(
        { envelopeStatisticService.getRecentSpentFor1Year(user.uid) },
        { envelopeStatisticService.getMostFrequentRelationship(user.uid) },
        { envelopeStatisticService.getMostFrequentCategory(user.uid) },
        { envelopeStatisticService.getMaxReceivedEnvelope(user.uid) },
        { envelopeStatisticService.getMaxSentEnvelope(user.uid) }
    ) {
    	... // ์ƒ๋žต
    }

ํ•œ ๊ฐ’์„ ๊ตฌํ•˜๊ธฐ ์œ„ํ•ด 5๊ฐœ์˜ ์ž‘์—…์ด ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌ๋˜๊ณ  ๊ฐ’์ด ๋ฐ˜ํ™˜๋˜๋„๋ก ๊ตฌ์„ฑํ–ˆ๋‹ค.

๋‹ค์ˆ˜์˜ ์ฝ”๋ฃจํ‹ด์„ ์ƒ์„ฑํ•˜์—ฌ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌํ•œ ํ›„, ๊ฐ’์ด ๋ชจ๋‘ ๋ฐ˜ํ™˜๋  ๊ฒฝ์šฐ ์ดํ›„ ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ–ˆ๋‹ค.

 

์œ„์—์„œ ์‚ฌ์šฉํ•œ parzip์˜ ๊ฒฝ์šฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•œ ๊ฒƒ์ด๋‹ค

defered์™€ awaitall์„ ์ด์šฉํ•ด๋„ ๋˜์ง€๋งŒ, ์ด๋ฅผ ์ด์šฉํ•˜๋ฉด ๋” ํŽธํ•˜๋‹ค.

์‚ฌ์šฉ์„ ์ถ”์ฒœํ•œ๋‹ค.

 

High-level concurrency | Arrow

Coroutines are one of the

arrow-kt.io

 

๐Ÿ”ต ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ

์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜์˜ ๊ฒฝ์šฐ, suspend function์œผ๋กœ ๋ณ€ํ™˜์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.

suspend function์ด ๋‚ด๋ถ€์ ์œผ๋กœ ๋ณ€ํ™˜๋˜๋ฉฐ, Continuation ๊ฐ์ฒด๊ฐ€ ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋“ค์–ด๊ฐ€๊ฒŒ ๋˜๋ฉด, ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

๊ทธ๋Ÿฌ๋ฏ€๋กœ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋ฅผ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ ค๋ฉด CoroutineScope๋ฅผ ์ด์šฉํ•˜์—ฌ ์ƒˆ๋กœ์šด ์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„๋ฅผ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

CoroutineScope(...)๋กœ ์‚ฌ์šฉํ•ด๋„ ๋˜์ง€๋งŒ, ์ด ๋˜ํ•œ MDC ๊ด€๋ จ ์ด์Šˆ๋กœ ๋”ฐ๋กœ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉํ–ˆ๋‹ค.

fun mdcCoroutineScope(context: CoroutineContext, traceId: String): CoroutineScope {
    val contextMap = MDC.getCopyOfContextMap() ?: emptyMap()
    contextMap.plus("traceId" to traceId)
    return CoroutineScope(context + MDCContext(contextMap))
}

 

์ด ํ•จ์ˆ˜๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์‚ฌ์šฉ๋˜์—ˆ๋‹ค.

@Component
class SystemActionLogEventListener(
    private val systemActionLogService: SystemActionLogService,
) {
    @EventListener
    fun subscribe(event: SystemActionLogEvent) {
        mdcCoroutineScope(Dispatchers.IO + Job(), event.traceId).launch {
            SystemActionLog(
                ipAddress = event.ipAddress,
                path = event.path,
                httpMethod = event.method,
                userAgent = event.userAgent,
                host = event.host,
                referer = event.referer,
                extra = event.extra
            ).run { systemActionLogService.record(this) }
        }
    }
}

์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ์˜ ๊ฒฝ์šฐ ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ์—์„œ ์ฒ˜๋ฆฌ๋  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์„ ๊ฐ์•ˆํ•˜์—ฌ, BaseEvent์— MDC traceId๊ฐ€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋‹ด๊ธฐ๊ฒŒ ๊ตฌ์„ฑํ–ˆ๋‹ค.

์ด์— ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋ฅผ ์ด์šฉํ–ˆ๋”๋ผ๋„, ์–ด๋–ค ์š”์ฒญ์˜ ์ด๋ฒคํŠธ์ธ์ง€ traceId๋ฅผ ํ†ตํ•ด ์•Œ ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„ํ–ˆ๋‹ค.

 

 

์ž์„ธํ•œ ์ฝ”๋“œ๋Š” ์•„๋ž˜์—์„œ ํ™•์ธํ•˜๋ฉด ๋œ๋‹ค

 

GitHub - ok-su-su/oksusu-susu-api: super ๊ทน๋ฝ Server

super ๊ทน๋ฝ Server. Contribute to ok-su-su/oksusu-susu-api development by creating an account on GitHub.

github.com