QCon San Francisco 2015 Tracks의 마지막 날. 아직도 Jet lag에 적응이 되지 않았는지 오후가 되면 졸음이 쏟아지는데, 중간 중간 쉬는 시간 (20분)에 호텔에 돌아와서 잠시나마 눈을 붙였더니 그나마 나았다.
오늘의 키노트. Shuman Ghosemajumder은 전직장인 Google에서 click fraud를 방어하는 것을 담당했다고 한다. Botnet이 IP 기반의 방어를 무력화시켰고, 도구화되어서 click fraud, login form, tax fraud, online banking fraud 등에 활용되고 있다는 상황을 소개하면서 이를 방어하기 위해서는 수작업으로는 불가능하고 ‘robotic defences’를 구축해야한다고 얘기했다. 이러한 공격이 쉬운 이유는 웹사이트 자체가 일종의 API이기 때문이라고 설명. 방어를 위한 주요한 방법 중 하나로 웹 사이트 액세스의 수많은 특성 screen resolution, user agent, time zone 등을 추적해서 어떤 aspect에서의 spike 등이 존재하는 가를 식별하는 것을 들었다. 액세스의 특성이 되는 aspect들이 상대적으로 적은 API액세스 등에 대해서는 어떻게 해야하는지 잘 생각이 안나지만, 이러한 시스템이 방어를 위한 기초적인 시스템임에는 동의한다. 방어를 위한 방법들을 prevention, realtime, near-realtime, batch, reactive defence 등으로 분류하고 여러 관점에서 방법을 구축해야한다고… 하지만 디테일에 대해서는 그다지 다루지는 않았다.
Charlie Hunt는 Oracle의 JVM Engineer로 2001년 정도에 출판되었던 Java Performance란 책의 저자라고 한다. 여기서 말하는 3 legs란 throughput, latency, memory footprint를 말하고, 이 중 어느 하나를 개선하려고 하면 나머지 하나 또는 둘을 희생해야한다는 이야기를 Generational GC 상황에 따라 설명을 해주었다. Java GC에 대해 어느 정도 지식을 가지고 있는 엔지니어라면 익숙할만한 이야기.
JDK 9의 feature가 될 Compact strings라는 feature를 개발하기 위한 ‘String density’ 프로젝트에 대해 설명을 해주었는데, 결과만 놓고보면 String의 internal representation에서 char[]를 byte[]로 바꾸고 ISO-8859를 위한 encoding을 추가한 것 뿐이지만, 이를 위해 JVM Engineer들이 어떤 개발 비용을 들이는지 자세하게 설명을 해주었다. 여러 애플리케이션들로부터 heap dump들을 수집해서 footprint를 줄이기 위한 방법을 탐색하고, 각 JVM platform별로 memory layout을 모두 분석하고, performance regression이 없도록 하기 위해 microbenchmark를 각 platform별로 모두 확인하는 과정 등, 프로젝트에는 10명의 엔지니어가 1.5년 정도가 걸렸다고 하니, JVM 엔지니어링은 굉장히 엄밀하게 진행되는 것 같다. Compact strings에 UTF-8을 사용하지 않은 이유는 String의 많은 수의 메서드들은 랜덤 액세스를 사용하는데 UTF-8의 특성 상 랜덤 액세스를 위한 비용이 커지기 때문이라고 한다. 또 하나 재미있었던 것은 기존의 String을 바꾸지 않고 왜 새로운 String 클래스를 만들어서 쓰지 않는가에 대해서는, Hotspot은 55개나 되는 String에 대한 JIT compiler최적화가 들어가있기 때문이라고 설명했다.
JVM Engineer의 GC에 관련한 세션이라서 나름대로 G1 GC의 현재 상황 등 최신의 내부 정보를 얻을 수 있을까 해서 들었는데, Abstract와 조금 다른 방향의 이야기가 나와서 안타까웠다.
애초에는 Confluent의 co-founder들 중 유일한 여성인 Neha Narkhede를 한번 만나보고 싶어서 그녀의 Kafka 세션을 들으려고 했는데, Neha Narkhede가 인기인 것인지 Kafka가 인기인 것인지 룸이 꽉차버려서 안타깝게 발길을 돌려야 했다.
오늘 들은 토크 중에서는 최고의 토크였다. 풀어야 할 비즈니스 문제들을 명확하게 보여주고, 풀어야할 기술적인 문제들을 정의하고, 후보 솔루션들을 선택하지 않은 이유를 제시한 후, 선택한 솔루션들을 설명했다. 그리고, 그 솔루션들로부터 다시 확장되는 문제들과 다시 솔루션을 제시하는 방식도 꽤 탁월했던 것 같다.
우버에서는 승객과 드라이버들을 더욱 잘 매치해주기 위해 수요와 공급을 분석, 예측해야하고, 이로부터 요금도 동적으로 결정해야하는 요구사항이 있다. 또한 서비스의 문제로 인한 비효율적이거나 이상한 패턴들을 찾아내거나 fraud 등을 탐지해야하는 문제도 가지고 있다. 토크의 시작은 지도 상의 수요 공급을 나타내는 히트맵과 여러 metric들의 trend가 그 오른쪽에 함께 보여지는 아름다운 대시보드를 보여주는 것으로 시작했다. 그리고, 쿼리 입력 필드에서 특정 승객이나 특정 드라이버의 상태 변화를 상태를 node로하는 그래프로 보여주는 뷰도 보여주었다.
이러한 비즈니스적인 요구사항을 만족시키기 위해서는 애플리케이션을로부터 수집된 이벤트들이 소실되지 않도록 저장하고 쉽게 확장 가능한 스토리지가 필요하고 이를 위해 Kafka를 이용한다고 설명했다.
또한 승객과 드라이버가 가진 수많은 필드 – 차원들에 따른 쿼리가 가능하고, 여러가지 형태의 aggregation을 지원하는 스토리지도 필요한데, 우선 Redis나 HBase 등과 같은 KV store 계열은 모든 키의 조합을 미리 계산해야하기 때문에 사용이 불가능하고 (‘불가능’이라는 단어에 대해서 항상 조심스러울 수 밖에 없다는 이야기도 함께 함.) RDB의 경우 여러 인덱스를 관리하는 것이 고통스럽고 스캐닝이 충분히 빠르지 않기 때문이라는 이유로 솔루션이 될 수 없다고 했다. 결론은 이쯤에서 예상했지만 Elastic Search였고, 장점으로 제시한 것은 매우 효율적인 역인덱스들과 자동적으로 여러 노드에 쿼리가 분산되고 다시 통합되는 분산쿼리 기능이었다.
여기에 더해서 이벤트의 데이터들은 여러가지 normalization이나 precalculation, 여러 스트림의 join, sessionization, state 관리 등이 필요하기 때문에 이를 처리하기 위한 layer로 Apache Samza를 선택했다고 한다. Samza는 YARN 위에서 동작하고, Kafka와의 integration이 매우 뛰어나고, built-in checkpointing이나 state management를 가지고 있는 것을 장점으로 제시했다.
여기에 더해서 Storage가 down되거나 프로세싱이 오래 걸리는 경우를 위한 배치 프레임워크로는 Spark를 선택했다고 한다. 결과적으로는 Kafka – Samza/Spark – Elastic Search로 구성되는 전형적인 Lambda architecture를 구성했다고 한다.
지역들을 헥사곤으로 쪼개서 수요 공급을 보여주기 위해서는 주위 hexagon의 데이터들과의 smoothing이 필요하고 이를 위해서 쿼리 결과의 Post processing도 필요하다고 한다. 이러한 처리는 순서한 function과 combinator로 이루어지는데, 이를 paralleize하고 pipelining하는 layer를 가지고 있는 듯하다.
Elastic search는 cardinality가 높은 쿼리를 하면 오랫동안 실행하다가 그대로 죽어버리는 문제를 가지고 있기 때문에, Pipelining, Validation, Throttling 등을 수행하는 query layer도 따로 구현하고 있다고 한다. 지금의 아키텍쳐는 상당히 타이트한 스케줄 내에 만들어냈어야 했기 때문에 외부의 도구들을 가져다 썼지만, 지금으로서는 Elastic Search 대신 자신들의 요구사항에 맞는 것을 만드는 것도 가능할 것 같다고 얘기했다.
One more thing으로 어떤 쿼리를 지정해두면 이에 해당하는 이벤트들이 특정 채널 (이를테면 Hipchat)로 전달되는 CEP를 가지고 있는 것도 보여주었다.
토크 중에 ‘사람은 기다릴 수 있지만 기계는 기다릴 수 없다’라는 말을 했는데, 사람과 기계에게 모두 analytic data를 제공하는 아키텍쳐를 가지고 있고, 단순히 외부의 프로덕트들을 가져다 조립한 것이 아니라 요구사항에 필요한 부분들을 채워넣고, 훌륭한 비주얼라이제이션과 응답시간을 가진 도구를 개발한 것도 타이트한 일정에 쫓기는 서비스 회사로서는 정말 굉장하다고 느꼈다.
Twitter에서는 수천개의 머신에서 JVM을 사용하고 있다고 한다. (생각보다 규모가 크지 않다는 인상을 받았다.)
주요한 stack은 Finagle, Netty, TwitterJDK, Mesos, CentOS이고, 서버 사이드의 언어는 Scala가 메이저에 해당하고 Java, Ruby, Python 등이라고 한다.
Twitter의 VM Team은 TwitterJDK를 개발하는 것을 담당하고 있는데, OracleJDK와는 달리 OpenJDK에 패치를 더한 형태라고 한다. 소스 리파지터리의 구성도 OpenJDK의 리파지터리로부터 hg-git을 해오고 TwitterJDK를 릴리즈할 때마다 최신의 OpenJDK 릴리즈로 업데이트한다고 한다. 릴리즈는 1달에 1번 정도씩 이루어지고, 2주간의 Canary 기간을 걸친다고 한다. Deployment는 Packer를 이용하고 Mesos 상의 서비스에 적용된다고 한다. (VM 이미지 안에 JVM이 함께 배포되고, Mesos 클러스터를 구성하게 된다는 이야기?)
주요한 개선은 Heap profiling, Binary logging framework (for GC logs), Intermediate generation for G1 등이라고 한다. GC log를 시스템화 함으로써 여러가지 GC에 관련된 문제들도 찾아내고 해결할 수 있었는데, Neopotism (Old gen의 dead object로부터 참조된 young gen의 object가 collection되지 않는 현상을 가리키는 듯), TLAB이 full이 되었을 때 object allocation이 느려지는 문제 (새로운 TLAB의 pre-allocation으로 해결?), DirectBuffer cache가 계속 자라나서 leak처럼 되는 문제 (최대 크기를 제한해서 해결) 등을 해결했다고 한다. 이것들에 대한 자세한 내용은 완벽하게 이해하지 못해서 비디오가 나오면 다시 한번 봐야할 것 같다.
희승님과 함께 Netty의 주요 개발자 중 하나라고 할 수 있고 Netty in Action의 저자이기도 한 Norman Maurer의 토크.
Apple에서는 무려 40만개나 되는 Netty 인스턴스들이 동작하고 있고, 초당 수천만개의 리퀘스트를 처리하고 있다고 한다. 직접적으로 언급하지는 않았지만 많은 주요한 Apple 서비스들에 사용되고 있는 것 같고… 이러한 배경으로 인해 Apple 엔지니어들이 Netty에 contribution할만한 요구사항들과 가치들도 생겨나는 것 같다.
우선 JDK NIO의 비효율적인 인터페이스 (Selector.selectedKeyes()가 항상 새로운 collection을 만들어서 리턴하는 것), NIO 구현 내에 concurrency에 대한 충분한 고려가 없이 synchronized 키워드가 너무 많이 사용된 점, 주요한 플랫폼이라고 할 수 있는 리눅스에 대한 최적화가 불가능한 것, copy가 많이 일어나는 점을 들면서, 이러한 문제들을 해결하기 위해 도입한 Native transports에 대해 설명했다. Linux의 epoll을 이용하고 있고, 여러가지 유용한 TCP 옵션들 (TCP_CORK, TCP_NOTSENT_LOWAT, TCP_FASTOPEN, …)을 지원하며, sync를 줄이는 등의 개선들을 활용할 수 있다고 한다. 이 정도가 되면 여타의 JVM기반 네트워크 프레임워크의 수준을 넘어서는 것이 아닐까 생각이 들었다.
DirectBuffer의 allocation 비용이 Heap buffer에 비해 높은 것은 잘 알려져있는데, 이러한 이유 중 하나로 allocation/deallocation 내부 코드에 heap usage를 체크하기 위한 코드 등에 syncronization들이 들어가 있기 때문이라고 한다. PooledByteBufAllocator를 이용해서 DirectBuffer를 pooling하는데 jmalloc과 유사하게 thread-local cache를 이용하고 arena별로 sync를 하는 approach를 취해서 성능을 개선하고 있다고 한다.
이 외에도 JDK SSL, Optimization, Thread model, Backpressure, Connection pooling 등의 내용들을 언급했는데, 자세한 내용은 나중에 슬라이드와 관련된 이슈를 읽어보아야 할 것 같다.
Netty는 나름대로 성숙한 프레임워크였지만 지금도 굉장히 많은 개선들이 지속적으로 이루어지고 있는 점은 정말 대단한 것 같다. 한편, 이 토크 자체는 Netty 4.0이나 그 주변의 개선들을 언급하고 있는 것 같고, Apple에 직접적으로 관련된 내용은 처음의 숫자들 밖에 없었는데, 토크의 제목이 왜 Netty @ Apple인지는 조금 의문이 들었다.
Facebook의 stream processing이라고 해서 나름대로 기대하고 들었는데, 토크 자체는 현재 시점에서는 보편적인 프로세싱 모델을 다루는 데에 시간을 많이 할애한 것 같아서 실망스러웠다.
잘 알려진 Scribe가 Kafka와 같은 Event Stream이라면, Stylus는 Imperative processing을 담당하고 있고, Puma라는 프로덕트는 SQL-like 인터페이스를 제공하고 있다고 한다. Stream processor로서 일반적인 keyed tuple을 처리하는 모델이라고 할 수 있는데, 특이한 점은 key에 대한 State가 외부의 DB로부터 관리된다는 점이다. 이 state는 local DB로 관리되기도 하는데 성능을 위해서 remote DB로도 제공되는 것 같다.
Fault tolerance를 위해서 state의 저장은 checkpointing을 사용하고 있으며 guarantee에 따라서 checkpointing과 state의 저장 순서가 바뀌는 방식이다. (at-most-once라면 checkpointing을 먼저, at-least-once라면 checkpointing을 나중에)
Backfill이라고 해서 오래된 데이터를 다시 읽어오는 방법도 제공하고 있는 것 같고, 중복된 코딩을 막기 위해 Stylus processor의 logic을 그대로 Batch로 실행하는 방법도 제공하는 것 같다. Mobile 클라이언트 이벤트 로그들을 처리해서 in-memory query storage인 Scuba에 집어넣는 역할, 페이지의 trend를 계산하기 위해서 scoring이나 ranking을 하는 사례를 설명했다.
Twitter의 stream processor로서 Storm을 대체한 Heron에 대해서 설명하는 토크였는데, Storm의 여러가지 문제점들을 설명하고 이를 Heron에서 어떻게 해결했는지 설명했다.
Storm의 아키텍쳐는 마스터에 해당하는 Nimbus, ZooKeeper 클러스터, Supervisor와 Worker들로 이루어진다. Nimbus는 Worker가 실행할 작업들을 scheduling하고 monitoring하는 역할을 하는데, 그 자체가 SPOF일 뿐만 아니라, resource의 reservation이나 isolation 개념이 없기 때문에 작업의 성격에 따라서 예측불가능한 성능 이슈가 자주 발생한다고 한다. ZooKeeper 클러스터는 Kafka spout의 offset/parition의 체크포인팅과 Storm Worker들의 heatbeat으로 인해 contention이 발생하기 쉽다고 한다. 실제로 작업을 수행하는 Worker들은 하나의 JVM 내에 여러 Worker들이 실행되기 때문에 디버깅이 어렵고 튜닝하기도 어렵다고 한다. 또한 데이터들이 거치게 되는 input queue와 output queue가 공유되기 때문에 여기서 발생하는 contention 문제도 언급하고 있다. 한편 Storm 자체는 Clojure로 쓰여져있지만 작업을 개발하는 개발자들은 Java 등을 사용하고, Storm의 커뮤니케이션 layer라고 할 수 있는 ZeroMQ는 C++를 사용하고 있기 때문에 이로 인한 유지보수의 어려움도 문제라고 이야기 하고 있다. 이 외에도 Backpressure 개념의 부재나 Efficiency에 관련된 문제들도 언급하고 있다.
Heron의 아키텍쳐를 설명하며 Heron은 Storm의 이러한 문제들을 해결하고 있다고 하고 성능도 몇 배 이상 좋아졌다고 얘기하고 있는데, 자세한 내용은 Heron paper를 읽어보는 편이 좋을 듯 하다.