Celery를 활용한 로깅 시스템 , 회고
최근 .csv, .tsv 등의 형태로 전송되는 로깅 파일을 저장하기 위한 시스템을 구축했다.
Celery 또한, rabbitmq 모니터링 툴을 사용하여 관찰한 결과
안정적으로 처리할 수 있었다.
- message queue 시스템을 사용하면서 관리해야 할 서비스들이 늘었다.
환경 변수 관리는 물론이고, 모니터링 해야 할 것이 늘었다.
WAS 코드에만 집중할 수 있었던 시절이 그리워지기 시작한다.
프로젝트를 진행하면서 사용한 기술에 대한 회고를 정리했다.
(프레임워크에 종속되는 특성보다는 처리 로직에 초점을 맞추었다.)
(프레임워크에 종속되는 특성보다는 처리 로직에 초점을 맞추었다.)
Before deploying Celery
테스트 서버에서 로깅 파일 처리 부하로 인해 서비스들이 멈추기 전까지는
메시지 큐를 활용하여 처리할 필요성을 느끼지 못했다.
그야 동시 요청 수도 없다시피하고
이상적인 환경이니까 그렇다.
이런 환경에서는 웹 애플리케이션 서버가
요청 검증, 파일 파싱, 데이터 전처리, DB I/O 모두 처리할 수 있다.
이 경우에는 파일로 만들어 저장하지 않고
Request 객체로 받은 binary를 파싱했다.
요청 검증, 파일 파싱, 데이터 전처리, DB I/O 모두 처리할 수 있다.
이 경우에는 파일로 만들어 저장하지 않고
Request 객체로 받은 binary를 파싱했다.
그러나 동시 요청수가 굉장히 많고
언제 Peak request를 찍을 지 알 수 없는 환경에서
안정적인 아키텍쳐는 아니다.
언제 Peak request를 찍을 지 알 수 없는 환경에서
안정적인 아키텍쳐는 아니다.
부하 발생 - 모니터링 툴 Locust를 사용하여 측정했을 때,
500명 동시 접속 환경까지 증가시키며
1초에 1번 계속해서 리퀘스트를 보냈을 때
WAS Worker가 2개인 환경에서 금방 리소스가 바닥나서
실패 응답을 받기 시작했다.
지연 응답이 아닌 실패 응답이 나타나기 시작하면
아키텍쳐든 코드든 효율성을 고려하여 수정해야한다.
1초에 1번 계속해서 리퀘스트를 보냈을 때
WAS Worker가 2개인 환경에서 금방 리소스가 바닥나서
실패 응답을 받기 시작했다.
지연 응답이 아닌 실패 응답이 나타나기 시작하면
아키텍쳐든 코드든 효율성을 고려하여 수정해야한다.
After deploying Celery
실시간에 가깝게 로그 파일을 처리하지 않고, 조금의 지연이 생기더라도
로그 파일을 제대로 처리한다는 보장이 있으면
로그 파일을 제대로 처리한다는 보장이 있으면
파일 저장/파일 처리 로직을 담당하는 프로세스로 나누어서
각 프로세스의 작업의 무게를 줄일 수 있다.
각 프로세스의 작업의 무게를 줄일 수 있다.
이렇게 구성한다면
요청 인증/파일 저장/파일 처리 로직을 전부 맡아서 하던 WAS는
요청 인증/파일 저장/파일 처리 로직을 전부 맡아서 하던 WAS는
요청 인증/파일 저장까지만 하고
그 뒤의 일은 Background worker에게 맡긴다.
이렇게 한다면 메모리 누수와 같은 결함으로
시스템 전체의 자원이 동나지 않는 이상
WAS의 파일 처리량을 늘릴 수 있다.
그 뒤의 일은 Background worker에게 맡긴다.
이렇게 한다면 메모리 누수와 같은 결함으로
시스템 전체의 자원이 동나지 않는 이상
WAS의 파일 처리량을 늘릴 수 있다.
그렇다면 Backgorund worker는 어떻게
자신이 처리해야 할 일을 알 수 있을까?
이 use-case에서는 자신이 처리해야 할
Background worker 고유의 작업 로직 뿐 아니라
자신이 처리해야 할 일을 알 수 있을까?
이 use-case에서는 자신이 처리해야 할
Background worker 고유의 작업 로직 뿐 아니라
대상 파일 또한 알아야한다.
Celery에서는 broker 시스템을 참조함으로서 이를 해결한다.
WAS에서는 파일의 저장이 끝남과 동시에(나는 파일 저장을 올바르게 끝마쳤을 때 등록했다.)
celery가 처리해야 할 작업에 필요한 정보를 broker에 넘긴다.
WAS에서는 파일의 저장이 끝남과 동시에(나는 파일 저장을 올바르게 끝마쳤을 때 등록했다.)
celery가 처리해야 할 작업에 필요한 정보를 broker에 넘긴다.
broker는 메세지 큐 구조로 구성되어 있으며
rabbitmq, redis 등 다양한 broker를 사용할 수 있다.
여기서 producer는 was가 발행하는 작업이 되겠고
consumer는 celery worker가 되겠다.
celery worker는 celery 서비스가 시작될 때
프로그래머가 시스템 스펙에 맞추어 지정한 옵션에 따라 계산되어 실행된다.
프로그래머가 시스템 스펙에 맞추어 지정한 옵션에 따라 계산되어 실행된다.
이 use-case에서
celery는 broker에 등록된 메시지를 읽어서
자신이 어떤 파일을 처리할 지 알아낸다.
그리고 celery에 등록된 task는
그 파일을 filesystem으로부터 읽어서 task를 수행한다.
말단에는 nosql database인 mongodb가 있으므로
파일 전처리나 DB I/O를 수행한다.
자신이 어떤 파일을 처리할 지 알아낸다.
그리고 celery에 등록된 task는
그 파일을 filesystem으로부터 읽어서 task를 수행한다.
말단에는 nosql database인 mongodb가 있으므로
파일 전처리나 DB I/O를 수행한다.
Celery 이후의 부하
WAS는 많은 동시 요청에 잘 대응하여 파일을 저장할 수 있었고Celery 또한, rabbitmq 모니터링 툴을 사용하여 관찰한 결과
안정적으로 처리할 수 있었다.
회고
- 한정된 자원으로 처리하기 위해 message queue를 도입하고
task 코드를 손 봤음에도
큐에 쌓인 작업을 제 때 소화하지 못하거나 할 때는
producer 코드, 그리고 더 나아가서는 Client와 합의하여
단위시간 당 요청 수를 줄일 수 있는 방법도 생각해 볼 수 있다.
큐에 쌓인 작업을 제 때 소화하지 못하거나 할 때는
producer 코드, 그리고 더 나아가서는 Client와 합의하여
단위시간 당 요청 수를 줄일 수 있는 방법도 생각해 볼 수 있다.
message queue는 만능이 아니다.
- message queue 시스템을 사용하면서 관리해야 할 서비스들이 늘었다.
환경 변수 관리는 물론이고, 모니터링 해야 할 것이 늘었다.
WAS 코드에만 집중할 수 있었던 시절이 그리워지기 시작한다.
- Celery task 처리가 실패했을 때 broker에 작업이 남아있기 위해서는
@task 함수 안에서 raise돼야 실패로 인식하여
broker의 큐에서 비워지지 않는다.
task 안에서 호출하는 모듈이나 패키지에서 아무리 raise를 일으켜도
task 함수가 '적절히' error가 아닌 것으로 처리해버리면
이것은 잘 처리된 것이므로 broker는 ack 응답을 받고
메시지 큐에서 작업을 삭제한다.
@task 함수 안에서 raise돼야 실패로 인식하여
broker의 큐에서 비워지지 않는다.
task 안에서 호출하는 모듈이나 패키지에서 아무리 raise를 일으켜도
task 함수가 '적절히' error가 아닌 것으로 처리해버리면
이것은 잘 처리된 것이므로 broker는 ack 응답을 받고
메시지 큐에서 작업을 삭제한다.
이는 데이터 유실과도 연관이 있는 중요한 로직이니 신경쓰자.
- Celery task가 수행하는 작업들은 거의 다 동기 코드다.
Celery가 동기 처리, 동기 작업 프레임워크와 맞게 설계되었는데
여기서 비동기 처리를 구현하려면
비동기 처리를 위한 이벤트 loop을 스스로 구현해야한다.
Celery가 동기 처리, 동기 작업 프레임워크와 맞게 설계되었는데
여기서 비동기 처리를 구현하려면
비동기 처리를 위한 이벤트 loop을 스스로 구현해야한다.
댓글
댓글 쓰기