서론

Node.js 는 여러 요청을 순차적으로 처리하지 않는다. 작업이 완료되면 등록해둔 콜백을 실행하는 방식이기 때문에, 기본적으로는 각 단위의 작업들 간 컨텍스트가 유지되지 않는다!

그런데 웹 서버를 구현할 때, 만든 값(요청 ID, 사용자 정보 등)을 그 뒤에 이어지는 모든 비동기 작업에서 꺼내 쓰려면 컨텍스트가 필요하다. 모니터링 툴은 트레이스에서 DB요청, 서비스 함수 실행등을 요청별로 묶어 보여준다. 이러한 기능은 어떻게 구현될 수 있는 것인지 알아보았다.


AsyncHooks 와 AsyncLocalStorage 

개요

  • AsyncLocalStorage는 AsyncHooks 을 이용하여 작업 간 공유되는 데이터를 관리한다
  • AsyncHooks는 비동기 리소스의 생명주기를 추적하여 사용자가 각 주기에 커스텀 처리를 가능하게 함.
    • (hook: 기존 실행 루틴에 내 코드를 걸어서 끼워 넣는다는 의미)

AsyncHooks

1. 훅 등록하기

async_hooks.js의 createHook()과 enable()을 호출하면, 아래 흐름을 거쳐 C++ 런타임(Environment)에 훅 실행용 트램펄린 함수가 등록된다. 훅에 등록한 함수들이 트램펄린 함수를 거치도록 해서 컨텍스트 복원·에러 격리·성능 최적화를 중앙집중식으로 처리하는 목적이다. (트램펄린에 한번 튕겨서 호출한다는 의미)

 

2. 훅 실행

예를 들어 net.js의 서버 객체를 생성하여 사용하면 TCPWrap 객체가 생성되는데 (이 과정은 포스트에 정리해두었다), 이 때 부모 클래스인 AsyncWrap 가 TCPWrap 객체의 동작에 따라 해당하는 주기에 등록해둔 함수를 호출한다.

 

new TCPWrap()

      AsyncWrap::AsyncWrap

           └─ AsyncWrap::AsyncReset

                  └─ AsyncWrap::EmitAsyncInit

async_wrap.cc. 비동기 작업이 시작되면(생성자) init에 등록한 훅을 호출한다.

 

TCPWrap::Listen

         └─ OnConnection

                 └─ MakeCallback

tcp_wrap.cc. libuv의 uv_listen함수에 OnConnection 함수를 콜백으로 넘긴다.
connection_wrap.cc
async_wrap.cc

AsyncLocalStorage

AsyncLocalStorage 생성자에서 _enable()을 거쳐 storageHook가 실행된다. 여기서 새로 생성된 비동기 리소스에 부모 리소스가 가진 store를 복사하는 함수(_propage)를 init 에 등록한다. store는 심볼로 정의된, 리소스에 종속된 프로퍼티로 관리한다.

async_hook.js
async_hooks.js


활용 사례

한계

  • context가 동기 경계에서만 유지된다는 점 (예: 다른 프로세스로의 IPC, Worker Threads 간에는 단절)
  • 내부적으로 Map을 기반으로 store를 관리하므로 메모리 누수 가능성

 


출처

Node.js Docs 와 소스코드

+ Recent posts