개요
JAVA, Python, Rust 모두 각각 메모리를 관리하는 방식이 참 다양하고 다릅니다.
이중에서 FrontEnd의 표준이 된 코드 발사대인 Node.js의 메모리 관리 방식에 대해서 알아볼까 합니다.
Node.js의 관리 Layer
Node.js는 근본적으로 Crome을 동작시키는 V8 Engine을 기반으로 동작합니다.
V8 Engine이 브라우저 프로그램에서 동작된다면 여기서 JS를 동작시키는 엔진을 똑 떼서 가져와 CLI기반의 JS코드를 동작시킬 수 있도록 만든 것이 바로 Node.js입니다.
https://nodejs.org/en/learn/getting-started/the-v8-javascript-engine
Node.js — The V8 JavaScript Engine
Node.js® is a free, open-source, cross-platform JavaScript runtime environment that lets developers create servers, web apps, command line tools and scripts.
nodejs.org
즉, Node.js는 근본적으로 V8엔진을 이용해 JS코드의 파싱을 하고 동작을 시키는 일종의 Wrapper역할을 한다고도 볼 수 있습니다.
대부분의 System과 연결된 동작의 경우는 V8에서 관리를 합니다.
C언어로 작성하여 Linux, Window의 Low Level인 Kernel과 상호작용을 하기 위함이죠.
A라는 동작을 했을때 Node가 V8엔진의 API를 호출하여 V8엔진이 커널에게 자신이 해야하는 동작을 전달해주는 형태인 겁니다.
메모리는 어디서 관리할까?
그렇다면 메모리는 어느 Layer에서 관리를 할까요?
V8? Node?
정답은 V8 Engine과 Node 모두 합니다.
그게 무슨 말도 안되는 소리임 System Level은 V8 Engine이 한다고 위에서 이야기했잖아;
맞습니다.
우리가 아는 메모리는 V8 Engine이 관리하는게 맞지요.
그러나 GC 즉, 가비지 컬렉터는?
Node가 동작시킵니다.
https://github.com/v8/v8/blob/14.8.30/src/base/platform/platform-linux.cc
v8/src/base/platform/platform-linux.cc at 14.8.30 · v8/v8
The official mirror of the V8 Git repository. Contribute to v8/v8 development by creating an account on GitHub.
github.com
이 파일을 한번 뜯어봅시다.
// 55ac243aa000-55ac243ac000 r--p 00000000 fe:01 31594735 /usr/bin/foo
if (!ReadHex(&entry.start, &delim)) return std::nullopt;
if (!ReadHex(&entry.end, &delim)) return std::nullopt;
for (int i = 0; i < 4; ++i) {
if (!ReadChar(&entry.raw_permissions[i])) return std::nullopt;
}
int fd = open(enclosing_region.pathname, O_RDONLY);
struct stat stat_buf;
if (fstat(fd, &stat_buf)) { /* 에러 처리 */ }
if (stat_buf.st_dev != enclosing_region.dev ||
stat_buf.st_ino != enclosing_region.inode) {
close(fd);
return false;
}
void* mapped_address = mmap(new_address, size, protection,
MAP_FIXED | MAP_PRIVATE, fd, offset_in_file);
보이는 바와 같이 공통적으로 실제 메모리에 접근해서 동작을 하는 것을 볼 수 있습니다.
즉 System과 강결합된 코드들이 많은 모습이죠
그에 반면에 이번엔 해당 파일을 한번 훑어보죠
node/deps/uv/src/unix/linux.c at 2ea7a7b67c3d698a0e80d2591365e2dc5c5f7a81 · nodejs/node
Node.js JavaScript runtime ✨🐢🚀✨. Contribute to nodejs/node development by creating an account on GitHub.
github.com
코드가 길어서 특정 줄만 링크를 가져왔긴합니다.
보이는 것과 동일하게 여기서는 Cgroup을 v1과 v2로 분기처리해서 호출하는 모습이 포착됩니다.
static void uv__get_cgroup2_memory_limits(char buf[static 1024], uint64_t* high,
uint64_t* max) {
char filename[4097];
char* p;
int n;
/* Find out where the controller is mounted. */
p = buf + strlen("0::/");
n = (int) strcspn(p, "\n");
/* Read the memory limits of the controller. */
snprintf(filename, sizeof(filename), "/sys/fs/cgroup/%.*s/memory.max", n, p);
*max = uv__read_uint64(filename);
snprintf(filename, sizeof(filename), "/sys/fs/cgroup/%.*s/memory.high", n, p);
*high = uv__read_uint64(filename);
}
static void uv__get_cgroup1_memory_limits(char buf[static 1024], uint64_t* high,
uint64_t* max) {
char filename[4097];
char* p;
int n;
uint64_t cgroup1_max;
/* Find out where the controller is mounted. */
p = uv__cgroup1_find_memory_controller(buf, &n);
if (p != NULL) {
snprintf(filename, sizeof(filename),
"/sys/fs/cgroup/memory/%.*s/memory.soft_limit_in_bytes", n, p);
*high = uv__read_uint64(filename);
snprintf(filename, sizeof(filename),
"/sys/fs/cgroup/memory/%.*s/memory.limit_in_bytes", n, p);
*max = uv__read_uint64(filename);
/* If the controller wasn't mounted, the reads above will have failed,
* as indicated by uv__read_uint64 returning 0.
*/
if (*high != 0 && *max != 0)
goto update_limits;
}
결론
다음과 같은 결론을 낼 수 있겠네요.
V8
System Level에서 메모리의 실제 주소를 요청/할당/파기 하는 동작은 V8 Engine에서 동작을 합니다.
왜 이걸 V8에서 할까?
V8엔진은 앞서 설명한 바와 같이 브라우저의 JS파싱을 하는 엔진이기 때문이죠.
결국 브라우저 레벨에서도 누군가는 메모리 관리를 해주어야 합니다.
다만, GC와 같은 동작은 더 상위 Layer에서 처리하면 되기 때문에 V8엔진의 설계적인 방향에서는 제외되었을 가능성이 높다고 볼 수 있습니다.
Node
GC를 동작시키기 위한 현재 메모리 상태를 감지하는 것은 Node에서 동작을 합니다.
이건 왜 Node에서 할까?
Node는 V8 Engine을 래핑하고 그 위에서 JS가 동작가능하도록 만든 프로그램입니다.
즉 V8에서 메모리를 할당/파기/호출하는 것과 다르게 언제 인메모리에 적재된 데이터를 갈아 엎을지, 내 메모리 현황은 어떤지는 V8보다 상위 Layer인 Node에서 하는 것이 맞고, V8에선 당연히 구현이 안되어 있기 때문으로 보는게 맞겠네요.
이 글에서 DevOps가 얻을 수 있는 인사이트
K8S 1.25버전 부터는 공식적으로 cgroup v2를 지원합니다.
이에 대한 내용은 해당 사이트에서 더 깊게 볼 수 있으니 참고해보시면 좋습니다.
cgroups(7) - Linux manual page
cgroups(7) — Linux manual page cgroups(7) Miscellaneous Information Manual cgroups(7) NAME top cgroups - Linux control groups DESCRIPTION top Control groups, usually referred to as cgroups, are a Linux kernel feature which allow p
man7.org
근데 여기서 문제가 발생합니다.
대부분 AWS의 EKS를 사용하는 사람들은 공감하겠지만 AWS에서는 Amazon Linux라는 것에 종속되기 참 쉽습니다.
이미 1.33 Cluster Upgrade를 해본 사람을 공감하겠지만 옛날부터 Amazon Linux 2를 쓰던 사람들은 필연적으로 EKS Cluster 1.33 업그레이드에서 Amazon Linux 2023으로 넘어가야하는 피눈물 나는 작업을 해보았을 거라 예상됩니다...ㅜ
기존 클러스터 업그레이드 작업도 기본적으로 무중단 Multi Cluster Blue/Green Update 환경을 만들어 두지 않는 이상 힘든 작업이었겠지만 이번 1.33은 더욱 크게 느껴질 것 같네요.
이유는?
기존에는 Cluster 업그레이드 딸깍 → Node AMI 교체 후 Rolling 형태로만 해도 충분히 동작 했을 겁니다.
하지만 이번엔 근간이 되는 AMI 자체를 엎어야하는 상황이 생겨버렸죠.
이유는? 아래 사진과 같습니다.

AL 2가 불안불안하더니 결국 종료해버렸습니다 ㅋㅋ ㅜㅜㅜ

이젠 EKS에서도 Amazon Linux 2의 지원을 종료해버렸기에 이젠 AMI 교체가 필연적으로 이루어져야겠죠...
AL 2023에선 어떤 문제가 있을까요?
컨테이너의 자원을 관리하는 cgroup이 바로 위에서 언급한 cgroup v2로 업데이트 되었습니다.
결국 cgroup v1을 지원하던 구버전의 언어/프레임워크는 모두 업데이트를 강행해야만 한다는 의미 입니다.
여기선 Node.js의 메모리 관리만을 다루기 때문에 Node.js의 지원 버전을 말해볼까 합니다.
바로 20.3버전 부터 지원합니다.
https://www.reddit.com/r/node/comments/1c29sge/cgroups_v2_support_in_nodejs/?tl=ko
이러한 이슈가 있고,
20버전의 코드를 보면 cgroup v1, v2에 대한 분기처리(함수)가 없었습니다.
20.03은?
node/deps/uv/src/unix/linux.c at 57679e1c97a90057d5ca9e1f0d1f3c6255d20200 · nodejs/node
Node.js JavaScript runtime ✨🐢🚀✨. Contribute to nodejs/node development by creating an account on GitHub.
github.com
cgroup v1, v2에 대한 분기처리도 생기고, 기존에 linux-<역할>.c 형태의 파일들이 linux.c로 통합된 모습을 볼 수 있습니다.
즉, Amazon Linux 2023 AMI에서 20.03버전 이전 버전을 사용할 경우 cgroup v2를 지원하는 Amazon Linux 2023에서는 Node.js가 자신에게 할당된 Memory를 읽지 못하고 적재하다가 OOM이 펑펑 터질 수 있다는 슬픈 이야기 입니다..
그럼 어떻게 해야할까요?
개발자와 같이 열심히 버전업 해야죠...ㅎㅎ
마무리
Python은 결론만 이야기하면 cgroup 이슈가 자체적으로는 존재하지 않았습니다.
그럼 JAVA는...? 있네요~!
하지만 따로 다루지는 않겠습니다. (넘 길어요...이해도도 떨어지고 말이죠)
https://younsl.github.io/blog/cgroup-v2/
cgroup v2
back2024-09-30 cgroup v2개요cgroup v1에서 cgroup v2로 전환할 경우 발생할 수 있는 문제를 정리한 페이지입니다.Amazon Linux 2023은 기본적으로 cgroup v2를 사용합니다. 따라서 Amazon Linux 2에서 Amazon Linux 2023으
younsl.github.io
대신 처음보는 블로그이지만 잘 정리해둔 블로그가 있어서 링크를 가져왔으니 참고해보면 좋을 것 같습니다~