본문 바로가기
Computer Engineering

신뢰성 없는 네트워크, GC 이야기

by ybs 2024. 1. 7.
반응형

데이터 중심 애플리케이션 설계 8장 '분산 시스템의 골칫거리' 에서 GC 이야기가 나온다.

 

원격 노드가 일시적으로 응답하기를 멈췄지만(가비지 컬렉션 휴지가 길어졌을 수 있다), 나중에는 다시 응답하기 시작할 수 있다(p278).

심지어는 I/O 중단과 GC 중단이 공모해서 지연을 결합하기도 한다(p297).
원문: I/O pauses and GC pauses may even conspire to combine their delays

 

cf) conspire 는 실제로 공모(직역)했다기 보다 두 가지 독립적인 요소가 서로 의도적으로 협력하는 것처럼(비유적 표현) 보이는 상황을 설명하는 데 사용됐다고 보는게 맞을거 같다. 두 가지 지연 요소가 어떻게 시스템의 전반적인 성능에 더 큰 부정적 영향을 미치는지 강조한다.

 

이 책에서는 JVM GC 상황만 설명했는데, 사실 GC 는 File System 에서도 하고 SSD 에서도한다. 그래서 각각의 GC 에 대해서 정리해봤다.

 

먼저 책에서 설명한 JVM GC 때문에 네트워크 지연이 발생하는 문제부터 살펴보자(원문). JVM 옵션에 따라 다양한 타입의 GC 와 JVM 활동들이 GC log 파일들에 로깅되는데, background IO traffic 때문에 GC stop the world(모든 스레드가 일시적으로 멈춤) 시간이 길어질 수 있다. 

 

다시말해 background IO traffic 이 높은 상태에서는, GC logging write() system call 이 OS 에 의해 block 된다.

cf) write() operation 을 asynchronous mode((i.e. buffered IO or non-blocking IO) 로 사용해도 block 된다. 코드를 살펴봐도 파일 변경 사항을 디스크에 즉시 반영하도록 강제하는 system call 인 fsync 는 없었으니 asynchronous 한게 맞는데도 block 된다.

출처: eliminating-large-jvm-gc-pauses-caused-by-background-io-traffic

 

 

 

그렇다면 background IO 는 어떤것들이 있을까?

1. OS page cache writeback

OS 메모리 관리 기능 중 하나로, disk I/O 를 최적화하기 위해 일부 메모리를 page cache 로 사용한다. 이 cache 는 disk 에서 읽거나 disk 에 쓸 데이터를 일시적으로 저장한다. page cache 에 저장된 데이터를 dirty data 라고 한다.

 

2. 관리 및 유지보수 SW

3. 같이 위치한 다른 애플리케이션들의 IO 작업

4. 동일한 JVM instance 의 다른 IO 작업

 

그런데 왜 cache 된, 다시말해 buffered writes 들이 block 될까? 원문에선 이렇게 설명했다.

We realized that buffered writes could be stuck in kernel code. There are multiple reasons including:
(1) stable page write; and (2) journal committing.

 

이 문장은 OS kernel 에서 data write 과정이 예상치 못하게 지연될 수 있음을 의미한다. data 를 즉시 disk 에 기록하는 것이 아니라, buffer 에 저장한 후 kernel 이 결정한 적절한 시점에 disk 에 쓰는 과정에서 발생할 수 있는 문제를 나타낸다. '막혀버릴 수 있다'는 표현은 이러한 지연이 장애가 될 수 있음을 강조한다.

 

Stable Page Write: OS가 page cache 에 있는 data 를 disk 에 안정적으로 쓰기 위한 메커니즘이다. 만약 page 가 이미 OS 에 의해 disk 로 writeback 중이라면, 이 page 에 대한 추가적인 write 작업은 writeback 이 완료될 때까지 기다려야 한다. 이로 인해 buffer 된 write 작업이 지연될 수 있다.

 

Journal Committing: file system 에서는 file write 작업 중 data 일관성과 무결성을 보장하기 위해 Journal 영역에 data 를 생성하고 커밋(저장)한다. 새로운 data block 이 할당될 때, file system 은 먼저 Journal data 를 disk 에 커밋해야 한다. 이 과정 중에 다른 IO 작업들이 수행되고 있다면, Journal 커밋은 기다려야 하며, 이로 인해 buffer 된 write 작업이 지연될 수 있다.

출처: Data-Intensive Computing and Systems Laboratory

 

cf) EXT4 file system 에는 "지연 할당"(delayed allocation)이라는 기능이 있어서 일부 Journal data 를 OS writeback 시간까지 연기할 수 있어 문제를 완화시킬 순 있지만 완전히 해결하지는 못한다.

 

마지막으로 해결책은 무엇일까?

background IO 작업들을 줄이는 방법도 있고, SSD 를 쓰는 방법도 있다. 그리고 마지막으로 GC 로그 file 을 tmpfs에 두는 방법을 소개한다(i.e: -Xloggc:/tmpfs/gc.log). tmpfs는 disk file 백업이 없으므로, tmpfs file 에 쓰기는 disk 활동을 발생시키지 않으므로 disk IO에 의해 차단되지 않는다. 물론 이 방식도 문제가 있다. crash 가 나면 GC 로그 file 들이 loss 될 수 있다. 이에 대한 해결책은 정기적으로 영구 저장소에 백업하는거다.


 

다음으로 File System GC 에 대해 이야기해보자. 먼저 전통적인 file system 의 layout 을 보면 아래와 같이 구성되어 있다.

 

1. B : boot block, OS 가 부팅됐을 때 실행되는 boot code 들이 포함되어 있다.

2. S : super block, file system 의 metadata 가 들어 있다. i.e: block size, inode 갯수 등

3. Inode(Index node 라고도 함) : data block 에 저장된 file 에 대한 description 을 갖고 있다. file data 가 어디에 저장되어 있고 file owner, create time, access time, permission, file size 등등. file 과 1:1 mapping 관계다. file 만들때마다 inode 가 만들어진다.

4. Data blocks : 실제 file 들이 저장되는 공간이다.

출처: Data-Intensive Computing and Systems Laboratory

 

 

cf) B 에 대해 좀 더 엄밀하게 말하면 OS 가 부팅될 때 boot code 가 MBR(Sector 0 of disk is called Master Boot Record) 을 로드해서 수행시키고 MBR 이 boot block 을 로드하는거다.

출처: Data-Intensive Computing and Systems Laboratory

 

cf) file system layout 에 대해서 좀 더 엄밀히 말해보면(Linux Ext 기준) 아래 그림과 같이 block group 별로 모아놔서 HDD disk head 움직임을 적게 최적화하고, 중요한 super block 은 여러개 copy 시킨다. 그리고 inode 와 data block 이 사용 여부를 확인하는 bitmap 도 존재한다.

출처: Data-Intensive Computing and Systems Laboratory

 

LFS(Log-Structured File System) 는 write 에 특화된 file system 이다.

 

기존 file system 들은 inode block position 이 고정되어 있다. file 이  write 될 때마다 inode 가 update 되야 하므로(access time 등) sequential 한 write 가 안된다. random I/O 는 HDD disk head 오버헤드가 크다.

 

sequential 한게 성능이 좋으니 write 만이라도 sequential 하게 할 순 없을까 라는 아이디에서부터 시작됐다. read 는 고려하지 않았다. read 는 DRAM 에 캐시해서 성능을 끌어올릴 수 있다고 봤다. write 가 더 중요하다고 봤다.

 

하지만 HDD 는 회전하니까 sequential 한 write 가 잘 안됐다. 그래서 write buffering 기법을 써서 in-memory 에 계속 쌓아두고 적절할 때 disk 에 쓰게 했다. 그럼에도 LFS 는 HDD 에서는 그렇게 각광받지는 못했다. 

 

그러나 SSD 에서 대세가 된 F2FS(Flash Friendly File System) 가 LFS 기반이다. 아래 그림을 보면 sequential 한 write 만 하기 때문에 새로운 inode 와 새로운 file 들이 쭉 그대로 쓰여진다. 

출처: Data-Intensive Computing and Systems Laboratory

 

문제는 file 들이 어딨는지 알려주는 inode 들이 가변적으로 바뀐다. sequential write 니까 inode 들도 fixed 하게 있을 수 없게 됐다. data block 이 바뀌면 그에 mapping 된 inode 들도 수정해줘야하는데 기존 table index 로는 찾을 수가 없게 됐다. 결국 아래 두가지가 중요해졌다.

 

1. position 이 계속 변하는 걸 어떻게 tracking 할거냐

2. old 한 것들을 어떻게 제거할거냐

 

inode tracking 에 대한 얘기까지 하자면 너무 길어진다. 이 글의 주제인 GC 와는 달라서 생략하겠다. 간단히 설명하면 imap 이라는 자료구조를 이용해서 inode position 을 follow 한다. 재밌는건 imap 을 disk 맨 앞으로 고정시킨다. LFS 철학과는 맞지 않지만, file 을 찾기 위한 시작점은 있어야하므로 어쩔 수 없다. 

 

다음으로, old 한 것들을 제거하기 위해 GC 가 필요해졌다.

출처: Data-Intensive Computing and Systems Laboratory

 

위 그림에선 segment 단위로 사용량을 파악해서 합치고 GC 하는걸 설명했지만 내부적으로는 각 inode 와 data block 마다 state 를 갖게 된다. 새로운 data 들이 추가되서 old 가 되면 state 가 invalid 가 되고, 나중에 invalid 들만 따로 모여 GC 를 수행하고 compaction 한다.


 

마지막으로 SSD GC 에 대해 이야기해보자. 아래 그래프를 보면 앞으로 2년 뒤엔 HDD 와 SSD 가격이 같아지고 그 후론 더 싸진다. 그래서 앞으로 서버 환경에서 SSD 를 더 많이 쓸 수 있다. 물론 wearout 문제가 있긴하다. wearout 문제는 SSD cell 에 전자를 채웠다 뺐다 계속 반복하면 언젠가 전자가 잘 안채워지는 문제를 뜻한다. 일반 사용자 IT 기기에서는 쉽게 발생하지 않지만, write 작업이 많은 서버 환경에서는 문제가 될 수 있다. cell 이 하나라도 죽으면 SSD 전체가 죽은거다.

 

SSD GC 를 얘기하기전에 먼저 SSD 동작 방식부터 간단히 살펴보자. 전자를 채우는게 비우는 행위다. 요즘 SSD 는 거의 다 아래 그림과 같은 multi level cell 이다. 0과 1만 있는 single level cell 보다 집접도가 2배지만, single level cell 이 더 빠르고 비싸다. multi level cell 은 error 가 많이 발생해서(전자를 채우는 단위가 많아지니 미묘한 차이에 따른 에러) error collecting code 가 있다보니 좀 더 느리다.

출처: Data-Intensive Computing and Systems Laboratory

 

 

공장에서 처음 나오면 아래와 같이 모두 1로 되어 있는(전자가 채워져 있는) 상태다.

출처: Data-Intensive Computing and Systems Laboratory

 

write 를 program operation 이라 하는데, page 단위로 쓴다.

출처: Data-Intensive Computing and Systems Laboratory

 

근데 재밌는건 1 에서 0으로 바꾸는건 page 단위로 되는데, 0 에서 1로 바꾸는(지우는)건 block 단위밖에 안된다. 한 page 에 있는 데이터만 지우고 싶어도 block 전체를 1로 다 채워줘야 한다. 결국 남의것까지 지워지게 되니 이 문제는 SW 로 해결한다.

출처: Data-Intensive Computing and Systems Laboratory

 

FTL(Flash Translation Layer) 를 이용해서 SW 로 해결한다. FTL 은 논리적 메모리 주소와 물리적 NAND 플래시 메모리 주소를 mapping 한다. 그리고 SSD 안에 있는 SRAM 에 mapping 관계를 저장한다.

cf) FTL 은 이외에도 GC process 를 관리하고 cell 에 쓰이는 횟수를 균등하게 분배하는 기능도 담당한다.

 

아래 그림에서 논리적 주소 1에 맵핑된 physical data 를 update 하기 하기 위해서 새로운 Free 영역 page 에 write 한다(in place update 하지 않는다).

출처: Data-Intensive Computing and Systems Laboratory
출처: Data-Intensive Computing and Systems Laboratory

 

그리고 기존 page 를 invalid state 로 바꿔 GC 대상이 되게한다. GC 는 SSD 안에서 자체적으로 수행한다. GC 주기는 제조사마다 다르고 공개하지 않는다. GC 가 수행될 동안 read write operation 이 무조건 멈추는건 아니지만 이 또한 제조사 구현 나름이고 공개하지 않는다. 제조사는 OS 제어를 받는걸 원하지 않는다. 솔루션으로 팔길 원하지 단순히 칩만 팔려고 하지 않기 때문에 영업 기밀인 것이다.

출처: Data-Intensive Computing and Systems Laboratory

 

마지막 결론 : SSD 는 최대한 용량 큰거로 사야된다(거거익선). 용량 다 채워져가면 GC 많이 발생한다.

반응형