우선 fluentd의 가장 큰 특징은 log를 time/tag/record형식 의 semi-structured 형식으로 저장한다는 것이다.
시간은 event가 발생한 시간으로 event를 fluentd로 넘겨줄 때 시간을 같이 넘겨주지 않으면, fluentd에서 받은 시간을 기록하게 된다.
tag는 이벤트를 만들 때 넘기게 되어 있는데, fluentd에서 사용하는 값이다. 이에 대해서는 config를 어떻게 하는지 설명하면서 설명하도록 하겠다.
record는 사용자가 저장하려고 했던 값들로 json 형식의 key/value pair로 저장된다.
semi-structured라고 해도 record가 json 형식으로 저장되기 때문에 원하는 형식대로 저장할 수 있다.
Use case
fluentd는 config파일을 바꾸는 것만으로도 여러 머신들 간의 설정을 쉽게 바꿀 수 있다.
위의 그림은 backup server를 두는 방식이다. fluentd는 내부적으로 버퍼를 가지고 있어 일정 시간 서버에 문제가 생기는 것에 대응할 수 있게 되어 있지만, 기본적으로 로그를 저장하기 위해서 쓰이고, 버퍼가 버틸 수 있는 것 이상으로 서버의 문제가 복구되지 않는다면 로그를 버리도록 설계되어 있다.
그럴 때를 대비하여 backup server를 둘 수 있다. backup server는 보통 때에는 사용하지 않지만, main server에 로그를 남길 수 없을 때 기록을 남긴다.
fluentd는 크게 plugin을 붙일 수 있는 3부분과 plugin을 이용하는 engine으로 구성되어 있다. engine은 config를 읽어서 사용할 plugin을 결정하고, 설정하는 역할을 한다. 외부의 input을 받고 output으로 내보내는 역할은 전부 plugin에서 하도록 되어 있다. 그렇기 때문에 fluentd의 동작을 이해하려면 각 plugin들이 어떻게 동작하는지 아는 것이 중요하다.
Input plugin
Input plugin은 외부로부터 이벤트를 받아오거나 외부의 파일을 읽어서 이벤트를 만들어 주는 역할을 한다.
fluentd 이외의 다른 log aggregator들이 가장 취약한 부분이 이곳이다. 반대로 말하면 fluentd의 최고 장점이 되는 부분이기도 하다.
scribe같은 경우는 event를 만들어 보내주는 부분을 완전히 새로 작성해야 한다. flume은 이미 구현된 몇 개의 방법을 이용해서 통신하거나, 새로운 plugin을 작성해야 하는데, flume은 plugin을 만들기 쉽게 되어 있지 않다.
반면에 fluentd의 경우는 이미 많은 plugin들이 만들어져 있어서 필요한 대부분의 plugin을 찾을 수 있고, 찾지 못하더라도 쉽게 plugin을 만들 수 있다.
Buffer plugin
buffer가 해주는 중요한 기능이 2가지 있다.
그 중 하나는 output을 효율적으로 내보내는 것이다. log aggregator는 실시간으로 로그를 모아주지만, 모은 로그를 바로 바로 output으로 보낼 이유는 없다. 그래서 fluentd를 비롯한 대부분의 aggregator는 서버에서 일정량의 로그를 모았다가 처리하도록 해준다.
fluentd에서는 이 단위를 chunk라고 부른다. chunk는 log의 tag 별로 분류되어 저장된다.
output plugin은 우선 chunk를 queue에 집어넣지 않고 들어오는 log를 chunk에 적는다. 그러다가 chunk의 크기가 일정 이상 커지거나, chunk가 생긴지 일정 시간 이상 지나면 queue에 들어간다.
chunk는 tag를 key로 하므로 buffer에 들어가지 않고 있는 chunk가 한 개 이상일 수도 있다. queue의 크기를 일정 이상 키우지 않기 위해 queue에 chunk를 집어넣을 때, queue에서 chunk를 1개 빼서 output으로 내보낸다.
buffer가 해주는 또 다른 중요한 기능은 서버(중개 node이건 최종 저장소이건)에 문제가 생기더라도 log의 유실을 최소화하는 것이다. 하지만 buffer를 사용한다고 해도 메모리가 무한한 것이 아니므로 서버가 오랫동안 문제 있으면 버려지는 데이터가 생긴다.
fluentd에는 재시도를 하고 그래도 안 되면 버리는 것을 정책으로 삼는다. 정확히는 output으로 나가야 하는 data가 나가지 못했을 때 일정 시간이 지난 후 다시 시도한다. 그래도 실패한다면, 기다렸던 시간의 2배만큼 더 기다리고 다시 시도하기를 반복한다. 일정 횟수를 기다려도 보내는 것에 실패하면 이 데이터는 다음으로 보내지지 않고 버려진다. 이때 기다리는 시간을 retry_wait, 다시 시도하는 횟수를 retry_limit으로 설정할 수 있다.
이 경우 외에도 fluentd 자체가 문제가 생겨서 꺼지는 경우도 있다. fluentd는 이를 위해서도 buffer의 plugin으로 원하는 종류를 써서 해결할 수 있다. 기본적으로 fluentd가 buffer에 사용하는 것은 buf_memory라는 plugin으로 chunk를 memory에 기록하는 plugin이다. 하지만 서버가 죽었다 살아날 때도 보장하고 싶다면 buf_file plugin을 이용하면 된다. buf_file plugin을 사용하면 chunk의 내용을 file에 보관해 주기 때문에 서버가 다시 켜질 때 file을 읽어와 buffer를 복구해준다. file에 쓰는 만큼 속도가 느려지지만, 안정성이 증가하기도 하고, 사용할 수 있는 buffer의 크기도 커진다.
Output plugin
위에서 architecture를 설명한 그림에는 input -> buffer -> output 순으로 메시지가 전달되는 것처럼 그렸지만, 사실 정확한 구조는 다음과 같다.
입력은 input plugin을 통해서 들어와 engine을 거쳐서 buffer plugin을 거치지 않고 output plugin으로 나간다. buffer는 engine에서 사용되는 것이 아니라 output plugin 내부에서 사용된다. 왜냐하면, output의 종류에 따라서 buffer가 필요하지 않은 경우가 있어, buffer의 사용 여부를 output plugin이 결정해야 하기 때문이다.
out_null의 경우 들어오는 입력을 전부 버리는 plugin이고, out_stdout은 들어오는 입력을 커맨드창에 띄워주는 plugin이다.
또 다른 경우는 out_copy다. 이 plugin은 하나의 fluentd로 들어온 event를 2개 이상의 output으로 보낼 때 쓰인다. 따라서 뒤에 다른 output plugin이 있고, 이 output plugin이 적절한 buffer를 사용하기 때문에 자체적으로 buffer를 이용할 이유가 없다.
time sliced output plugin은 buffer를 사용하지만, chunk의 key로 tag가 아닌 시간을 사용한다는 것만이 다르다.
Configuration
마지막으로 fluentd를 실제로 어떻게 설정하는지에 대해 설명하면서 마무리하도록 하겠다.
fluentd의 config문법은 어렵지 않다. 일단 실제 config파일 예시를 한번 보자.
위에서 보았듯이 fluentd의 설정은 들과 들로 구성되어 있다.
source 하나는 하나의 input plugin을 의미하고, 하나의 fluentd에 1개 이상의 source가 있을 수 있다. 위의 예시는 forward plugin과 http plugin을 사용하는 경우다.
위와 같이 설정되어 있으면, forward를 통해서 받을 수 도 있고 http protocol을 이용해서 8888번 포트로 입력을 받을 수도 있다.
fluentd는 tag별로 다른 output을 사용할 수 있는데, 그 부분을 설정해 주는 것이 match이다.
쓰여진 순서대로 tag를 match시켜 그 중 첫 번째로 맞는 match에 맞는 output plugin을 이용한다.
output plugin 중에서 out_copy와 out_roundrobin는 라는 항목이 필요하다.
out_copy와 out_roundrobin 둘 다 하나의 log를 둘 이상으로 나눠주는 것이기 때문에 실제 사용할 output plugin을 설정해줘야 하는데 그 설정을 하는 부분이 다.
이 외의 plugin별로 설정해야 할 값들이 있는데, plugin별로 다르므로 하나하나 설명하기는 어렵고, 이에 관해서는 사용할 plugin들에 관해서 reference를 읽고 설정하는 것이 좋다.
p.s. 글에서 설명한 내용이라서 딱히 필요 없을 것 같지만, fluentd에 대해서 회사에서 발표하며 사용했던 자료를 첨부한다.
3줄 요약 Chromium OS에서 PlayStore 실행시키는 거 보고 반함 집에서 놀던 노트북에 설치함 포맷 거의 2주 정도 삽질을 했다. 시작은 우연히 보게 된 위의 영상 때문이었다. Chrome OS의 고질적인 문제인 쓸만한 앱이 없다는 문제를 해결하기 위해 ChromeOS에서 안드로이드 앱을 실행할 수 있게 했다는 것이다. 안 그래도 개발용 데스크톱을 새로 장만하면서, 전에 사용하던 리눅스 노트북의 용도가 단순 인터넷 서핑 정도가 됐기 때문에 망설임 없이 노트북을 포맷하고 Chrome OS의 오픈소스 버전인 Chromium OS를 설치했다. Chromium OS를 설치하면서 기대했던 것은 대략 다음과 같다. Play Store를 이용해서 노트북으로 안드로이드 게임 플레이하기 어차피 인터넷밖에 안 할 거 크롬 이외의 UI 부하를 줄여서 조금이라도 저전력으로 만들기 필요하면 개발자 모드로 터미널 사용하기 설치하는 게 쉽지는 않았다. Chromium OS는 공식적으로 바이너리 배포를 하지 않는다. Chromium OS를 사용하는 공식적인 방법은 ChromeOS가 설치된 크롬북을 구매하는 것뿐이다. 그 외의 개인적으로 사용하고 싶은 사람들은 Chromium OS를 소스에서 빌드해서 설치해야 한다. 그래서 소스를 클론 받았는데, 소스만 22GB다. 구글 프로젝트 대부분이 그렇듯이, 내부적으로 의존하고 있는 라이브러리의 소스까지 전부 포함하고 있기 때문인듯하다. 어쨌든 소스 받는 데만 하루가 걸렸다. 그리고 빌드를 시작했는데 빌드 중 중간결과물만 100GB가 넘는 크기가 나왔다. 그래도 어찌어찌 빌드하여 설치했는데, 실행이 안 된다. 찾아보니 기본적으로는 지원되는 하드웨어가 적고, 다양한 하드웨어를 지원하기 위해는 빌드 시 설정을 바꿔야 한다고 한다. 그래서 이것저것 뒤져가면서 빌드를 다시 해봤는데 여전히 실행이 안 된다. 그래서 포기하려던 찰나에 비공식적으로 Chromium OS 이미지를 배포하는 사람 이 있다는 것을 발견하고
C++ 03 까지의 enum 은 여러 가지 문제를 가지고 있었다. 그래서 그 문제들을 해결하기 위해 C++ 11은 enum class 라는 것을 새로 만들었다. 이제부터 기존의 enum 에 어떤 문제가 있었고, 이것을 enum class 에서 어떻게 해결하였는지 살펴볼 것이다. 우선 기존의 enum 은 전방 선언할 수 없었다. 그 이유는 enumerator에 어떤 값이 들어있을지 알 수 없으면 그 크기를 정할 수 없기 때문 이다. 하지만 enum class 는 underlying type을 명시하지 않으면 int 타입과 같은 크기의 변수로 선언되고, int 값 안에 들어가지 못할 값을 지정하면 컴파일 에러를 발생시킨다. 만약 int 를 벗어난 범위의 값을 사용하고 싶다면, underlying type을 명시해주어야 한다. 기존 enum 의 또 다른 문제는 enumerator의 이름의 범위가 한정되지 않는다는 것이다. 예를 들어 아래와 같은 코드를 보자. IO 함수의 결과와 Parse 함수의 결과를 enum 으로 표현해 보았다. 하지만 이 코드는 컴파일되지 않는다. IOResult 의 Error , Ok 가 ParseResult 의 Error , Ok 와 겹치기 때문이다. 이를 해결하기 위해서는 다음과 같이 enumerator의 이름을 다르게 하거나 아래와 같이 namespace를 이용해야 했다. 하지만 enum class 는 enumerator의 이름이 enum class 안으로 한정되기 때문에 이런 복잡한 과정이 필요 없이 그저 enum class 를 선언하여 사용하면 된다. 무엇보다 기존 enum 의 가장 큰 문제는 정수형 변수로 암시적으로 변환되는 약 타입(weak type) 변수라는 것이다. 하지만 enum class 는 정수형 변수로 암시적 변환이 되지 않는다. enum class 를 정수형 변수처럼 사용하려고 하면 컴파일 에러를 발생시킨다. 만약 정수형 변수로 사용하고 싶으면 static_cas
지난번에 shard key를 설명한 글 을 썼을 때 댓글 로 ObjectId 에 관한 얘기가 나왔었다. 그래서 sharding과 큰 연관은 없지만, 이번 기회에 ObjectId에 관해서 먼저 설명하고 가는 게 좋을 것 같아서 sharding에 관한 것은 뒤로 미루고 이번에는 ObjectId에 대해 먼저 설명하고 가도록 하겠다. ObjectId란 무엇인가 ObjectId는 같은 document 내에서 유일함이 보장되는 12 byte binary data다. 전통적인 centralized 되어 있는 시스템이라면 한 collection 내에서 유일함을 보장하는 것을 쉽게 할 수 있다. 하지만 sharding을 하는 MongoDB에서 유일함을 보장하는 것은 기존과는 다른 솔루션이 필요하다. 그리고 이 방법을 설명하는 게 사실상 ObjectId의 모든 것을 설명하는 것이다. 왜 ObjectId를 사용하는가 전통적인 RDBMS 에서 Primary key 를 만들 때는 DB 서버로 data를 보내서 중복되지 않는 key를 골라서 1) 그 값을 key로 저장하는 방식을 이용한다. 하지만 MongoDB와 같은 분산 database에서는 key를 서버에서 만들지 않고 클라이언트에서 만든다. 그 이유는 MongoDB가 query를 날릴 shard를 결정하는 방식 을 보면 알 수 있다. MongoDB는 자신이 필요한 shard에게만 query를 요청한다. 다시 말해서 client에 해당하는 mongos 2) 가 config server 의 data를 토대로 어떤 shard가 어느 범위의 값을 가졌는지를 저장하고 있다가 query를 요청할 때 자신이 필요로 하는 shard에게만 요청한다. 따라서 shard key 에 해당하는 data가 미완성인 상태로 서버에 저장하도록 요청할 수 없고, client가 ObjectId를 생성하여 값을 저장하도록 요청한다. 3) ObjectId의 구성 ObjectId는 크게 4부분으로 구성되어 있다. Ob
댓글
댓글 쓰기