정말 zookeeper에서는 SSD를 쓰지 않아야 할까?

Misconfiguration-Zookeeper를 읽다가 ZK Transaction Log를 저장하는데 SSD를 쓰지 말라는 내용을 보고 이에 대해 제 의견을 덧붙여봅니다.

먼저 SSD를 쓰지 말라는 이유를 살펴보면 다음과 같습니다.

  • 주키퍼는 디스크의 순차 IO에 최적화되어 있다.
  • SSD를 사용함으로써 얻는 이득은 적고, high latency spike를 일으킬 수 있다.

이 중 high latency spike는 SSD의 특성으로, 흔히 알려진 프리징 현상과 관계가 있습니다.
SSD는 페이지 단위로 기록을 하는데, 오직 비어있는 페이지에만 기록을 할 수 있습니다. 만약 데이터가 남아있는 페이지에 기록을 하려면 기존의 내용을 지운(ERASE) 다음 기록을 해야 합니다. 여기서 발생하는 문제는 특정 페이지를 overwirte 하려면 해당 페이지를 포함한 블럭 전체를 삭제하고 재기록을 해야 한다는 점이죠.
SSD의 특성상 READ/WRITE는 빠르지만 상대적으로 ERASE는 느립니다. (블럭 크기에 따라 10배 이상 차이가 나기도 합니다.) 프리징 현상도 이런 ERASE와 관련이 있으며, Misconfiguration-Zookeeper에서는 40초 동안 SSD disk가 멈출 수 있다고 하지만 실제로는 이보다 짧을 수도, 더 길 수도 있습니다. 다시 말하자면 SSD 프리징 현상은 주키퍼만에 국한된 문제는 아니라는 얘기입니다.

그렇다면 TRIM을 사용하면 되지 않을까? 네, TRIM을 사용하면 Misconfiguration-Zookeeper에서 얘기한 high latency spike를 일으키는 프리징 문제는 해결할 수 있습니다. 프리징으로 인한 타임아웃이 논-이슈가 되겠죠. 하지만 이왕 글을 시작했으니 몇 가지 더 살펴보도록 합시다. (다만, RAID를 사용하면 SSD TRIM을 사용하는데 제약이 많습니다.)

UPDATE: TRIM만이 해결책인 것은 아닙니다. 서비스 운영 환경은 천차만별이고 문제와 해결 방식은 여러가지가 있습니다. 지적을 해주신 분이 계시지만 OS/RAID 컨트롤러에서 제어하는 방식도 있고 Backed Buffer 를 사용하는 등등 여러 방법이 있다고 합니다. 제가 말하고 싶은 것은 SSD를 제대로 설정하고 사용해야 한다는 것입니다.

UPDATE 2: 서버 환경이라면 RAID로 디스크를 구성하면 좋겠지만 꼭 그렇지 않은 경우도 있다고 생각합니다. 저는 RAID로 SSD를 구성하지 않고 단일 SSD로 주키퍼 노드를 운영했습니다. 환경에 따라 다르긴 하겠지만 제 경우 주키퍼 노드 운영에서 가장 크리티컬한 문제는 타임아웃 발생이었고, 디스크 장애로 zookeeper 노드 하나가 클러스터에서 빠져나가는 것은 얼마든지 감당할 수 있다고 보았습니다. 이런 상황이라면 굳이 RAID로 디스크를 구성할 필요는 없다고 봅니다.

UPDATE 4: 서버가 재부팅되는 상황 등에서 zookeeper 상태를 잃어버려도 상관없다면 ramdisk에 zookeeper transaction log를 저장하는 방식도 괜찮습니다. 실제로 512MB RAMDISK에 transaction log를 저장하면서 6개월간 문제없이 운영을 하기도 했었구요. (forceSync=no 옵션 사용). 그리고 경험상 한번 클러스터에서 이탈한 노드는, 다시 참여할 때 이전 정보를 토대로 업데이트를 하는 것보다는 참여한 시점에 새롭게 모든 데이터를 받아오는 것이 더 낫다고 보고 있습니다. 만약 데이터를 받아오는 시간이 너무 오래 걸려 문제가 된다면 zookeeper를 과도하게 쓰고 있는게 아닐까라는 생각을 해봅니다.

Zookeeper의 Transaction Log 파일과 관련있는 설정 중 zookeeper.preAllocSize가 있습니다. 기본 값은 64MB이고, 이 값의 크기만큼 처음에 Transaction Log파일이 만들어지고, 파일의 (개념적인) 잔여 공간이 4KB 미만이라면 preAllocSize 만큼 파일의 크기를 늘립니다. 즉, Transaction Log가 계속 쌓이면 preAllocSize만큼 새로운 디스크 공간 할당이 일어나고, 새로 할당된 공간에 개별 Transaction Log의 내용이 기록됩니다. (Zookeeper 소스의 server/persistence/Util.java 참고)
이 과정은 기존 디스크라면 Sequential IO를 최대한 활용하여 성능을 높일 수 있는 방식이지만, SSD의 경우 새로운 블럭을 할당하는 빈도가 높아지고, 경우에 따라서는 기존 블럭을 삭제하고 재기록을 해야합니다. 따라서 프리징으로 인한 Client timeout을 유발하는 원인이 되기도 합니다. preAllocSize를 작게 설정하는 것도 이런 이슈 해결에 도움이 되지는 않습니다. preAllocSize를 작게 설정할 수록 오히려 재기록이 자주 발생할테니까요.
해당 글에서 왜 Trim을 언급하지 않았는지에 대한 이유를 나름대로 추측해보면, 서버의 디스크 장비는 RAID로 구성을 하는 경우가 많습니다. 하둡 같이 replication 메커니즘의 클러스터의 서버는 raid 구성을 하지 않기도 하지만, 그래도 보통은 RAID 구성을 하는게 좋겠지요. 그러나 RAID SSD Trim을 지원하기 시작한 시점이 2012년 중반인 것으로 알고 있고 그것도 Windows, 인텔 기반의 RAID-0, RAID-1에 한해서라고 합니다.(이 부분에 대해서 정확한 내용을 알고 계신 분은 첨언 부탁드립니다.) 해당 글이 발표된 시점이 2012년 중순이라는 점을 감안하면 그 시점에서는 SSD RAID TRIM이 제대로 작동하지 않았고, 현재도 그렇습니다.

UPDATE 3: 비 Windows 운영체제에서는 아직도 RAID SSD TRIM을 제대로 활용할 수 없다고 합니다. 인텔 RST 11.6 + Intel SSD 조합으로는 성공적으로 SSD TRIM(RAID 1)을 사용하는 사례가 있다고 합니다. 제보해주신 gilbert님 감사드립니다.

UPDATE 5: preAllocSize를 SSD의 블럭, 페이지 크기를 고려하여 설정하더라도 대부분의 SSD는 wear leveling을 우선하기 때문에 디스크 사용량이 일정량에 이르면 결국 GC/write gathering 문제를 피할 수 없습니다. 다만, 쓰기와 partial erase를 동시에 수행하는 일부 SSD(인텔 SSD중 DC 코드가 붙은 모델 등)에서는 partial erase/GC 대상 블럭의 내용은 버퍼로 옮겨져서 access 되고, 이 버퍼의 데이터에 access하는 것은 write gathering/GC의 영향을 받지 않고 모든 작업이 끝나면 블럭 단위로 저장되기 때문에, zookeeper 노드에서 SSD에 다른 쓰기 작업이 없는 상황에서 SSD 블럭/페이지를 고려하여 preAllocSize를 설정하는 것은 의미가 있다고 합니다.

또한, 비용적인 측면에서 생각을 해보면, Zookeeper는 disk 보다는 memory를 더 주요하게 사용하며 상대적으로 디스크 성능 향상을 통해 얻는 이득은 적습니다. SSD 도입 비용을 생각해보면 HDD 가 더 효율적인 선택이 될 수 있다는 것입니다.

하지만 zookeeper 운영 매뉴얼에서도 밝히고 있듯이, IO 측면에서 transaction log와 data, application log를 서로 분리하는 것은 실제 운영 환경에서는 아주 중요한 권고 사항입니다. 일부 운영 환경에서 스왑이 발생했음에도 불구하고 SSD의 성능 때문에 Zookeeper timeout이 발생하지 않은 사례도 있고, 문제 원인과 해결 방식이 명확히 있기 때문에 주키퍼에서 SSD를 무조건 쓰지 말라는 것은 조금 지나친 가이드가 아닌가 생각을 해봅니다.