728x90

서버 로그를 그라파나에 보여지게 하려는데, 에러가 나를 몇시간 동안 괴롭혔다.

 

해당 에러를 해석 해보자면 연결은 됐지만 라벨을 받지 못했으니 Loki, Promtail 설정을 잘해봐라. 라는 뜻으로 이해했다.

 

처음에는 Docker compose 파일에 다음과 같이 Promtail 컨테이너를 세팅했다.

 

 

문제는 Promtail 설정이 담긴 promtail-config.yml 파일명이었다. 하이픈을 제거해서 promtail.yml로 변경하니 됐다.

 

 

728x90

다음과 같은 에러가 나와 고생좀 했다.

github.com/prometheus/prometheus/promql.NewActiveQueryTracker({0x3053abc, 0x5}, 0x14, {0x39f12e0, 0xc000099400})
	/app/promql/query_logger.go:121 +0x3cd
main.main()
	/app/cmd/prometheus/main.go:597 +0x6713
ts=2024-10-13T13:46:01.358Z caller=main.go:491 level=info msg="No time or size retention was set so using the default time retention" duration=15d
ts=2024-10-13T13:46:01.358Z caller=main.go:535 level=info msg="Starting Prometheus Server" mode=server version="(version=2.37.6, branch=HEAD, revision=8ade24a23af6be0f35414d6e8ce09598446c29a2)"
ts=2024-10-13T13:46:01.358Z caller=main.go:540 level=info build_context="(go=go1.19.6, user=root@5f96027a7c3e, date=20230220-09:36:40)"
ts=2024-10-13T13:46:01.358Z caller=main.go:541 level=info host_details="(Linux 6.5.0-28-generic #29~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Apr  4 14:39:20 UTC 2 x86_64 40ea8565bbf0 (none))"
ts=2024-10-13T13:46:01.358Z caller=main.go:542 level=info fd_limits="(soft=1048576, hard=1048576)"
ts=2024-10-13T13:46:01.359Z caller=main.go:543 level=info vm_limits="(soft=unlimited, hard=unlimited)"
ts=2024-10-13T13:46:01.359Z caller=query_logger.go:113 level=error component=activeQueryTracker msg="Failed to create directory for logging active queries"
ts=2024-10-13T13:46:01.359Z caller=query_logger.go:91 level=error component=activeQueryTracker msg="Error opening query log file" file=/prometheus/data/queries.active err="open data/queries.active: no such file or directory"
panic: Unable to create mmap-ed active query log

goroutine 1 [running]:
github.com/prometheus/prometheus/promql.NewActiveQueryTracker({0x3053abc, 0x5}, 0x14, {0x39f12e0, 0xc0004b9540})
	/app/promql/query_logger.go:121 +0x3cd
main.main()
	/app/cmd/prometheus/main.go:597 +0x6713

 

Prometheus가 /prometheus/data/queries.active 파일을 생성하려고 할 때 권한이 부족하다는 문제인 것 같았다. 그래서 찾다가 다음링크를 참고해서 해결했다.

 

https://github.com/prometheus/prometheus/issues/5976#issuecomment-535455952

 

err="open /prometheus/queries.active: permission denied" · Issue #5976 · prometheus/prometheus

Proposal Use case. Why is this important? centos7 + docker-ce + prometheus Bug Report no change docker default: Docker Root Dir i can docker run prometheus right when i change Docker Root Dir eg:Do...

github.com

 

 

이 설정은 호스트의 파일 시스템과 컨테이너 간 권한 문제를 해결하는 방법이다. 특히 공유된 볼륨에서 컨테이너 내부에서 파일을 작성하고 수정할 때 발생할 수 있는 충돌을 방지한다. UID 1000:1000로 실행하면 호스트의 동일한 사용자 권한을 가진 것처럼 컨테이너가 동작하게 되어, 파일 시스템에 접근할 때 권한 문제가 없어진다.

728x90

람다 SnapStart 를 적용하며 

람다에 자바로 된 함수를 띄었다. 자바 파일은 아무래도 node.js에 비해 사이즈가 컸다. 
기존에 회사 넥서스에 올라가 있는 라이브러리를 사용하려다 보니 자바로 어쩔 수 없이 해야 했던 상황이다. (다시 js로 구현 해도 되지만 알림톡쪽은 급하게 개선 해야 하는 비즈니스 로직이라 그건 추후로 미뤘다)
그래서 50mb 정도 되는 자바 코드를 그대로 올려서 cold start 로 실행 시켰다.
그러다 보니. 람다 자체내에서 코드를 다운로드 하고, 컨테이너를 띄우고, 전역 코드를 실행 후 내부 함수를 실행하는 순서 까지가 일반적인 방식으로 40000~50000ms(4~50s) 정도가 걸렸다.

처음에 ColdStart로 40초 정도 걸리니 방법을 모색 해보았다. 처음에는 다음과 같은 방법을 찾았다.
- 타임아웃 늘리기
- node.js 로 다시 구현
- 프로비저닝 동시성 구성
- 해당 함수 실행 예상 시간 전에 health check를 통해 warm up 해놓기

 

타임아웃 늘리기

근본적인 해결책은 아닌 것 같았다. 해당 함수가 띄어질 때 까지 40~50초 가량 걸리는데, 빌드될 때 까지 기다리고 처리될 때 까지 함수가 내려지지 않을 정도의 시간을 주는 것이었다.


node.js 로 다시 구현

위에서 말했듯이 다시 js로 구현 해도 되지만 알림톡쪽은 급하게 개선 해야 하는 비즈니스 로직이라 그건 추후로 미뤘다. 그리고 해당 라이브러리에 은근 로직들이 많아 새로 다른 언어로 로직을 만드는데 리스크를 안아야 하는점도 있었다.


프로비저닝 동시성 구성

항상 함수를 띄어 놓으면 그만큼 과금이 된다. 하루에 특정 시간대에 한 두번 도는 함수인데, 함수가 무거워서 컨테이너가 올라갈 때 시간이 많이 걸린다는 이유만으로 해당 금액을 지불하기엔 돈이 아까웠다.


해당 함수 실행 예상 시간 전에 health check를 통해 warm up 해놓기

해당 로직을 처리 해야 하는 예상 시간을 추측 해서 5분 마다 헬스체크 하면서 warm up 시켜놓는 방식도 괜찮았지만, 근본적인 해결책이라고는 볼 수 없었다. 헬스체크를 보내야 하는데, 이를 보내는 주체에 의존하는 모양이 되기 때문에, 주체에 문제가 생기면 warm up이 안되기 때문이다.


지금까지의 고민을 SnapStart이 모든것을 해결해 주었다.

 

SnapStart 란 해당 버전의 빌드 파일의 스냅샷을 따서 캐싱 하고(스냅샷은 Amazon EBS 에 저장), Cold Start를 할 때 이를 활용하여 실행하는 기능이다. 그래서 람다 컨테이너를 올릴 때 아무리 큰 함수라도 빠르게 올릴 수 있다.

 

스냅샷은 구체적으로 함수 코드가 로드되고 초기화된 상태를 저장한다.

 

메모리상태
- Java 런타임에 의해 로드되고 초기화된 모든 클래스
- 정적 변수
- 초기화된 객체
- JIT 컴파일된 코드
- 의존성
- 캐시된 데이터
- 연결 풀
- 스레드 상태


디스크 상태
- 함수 코드와 의존성 라이브러리들이 로드된 상태
- 런타임 환경에 의해 생성된 파일 시스템 구조


그러나 SnapStart에 제약사항은 존재한다.


런타임 제약사항

- 자바 전용이며 Java 11, 17을 지원.
- x86 아키텍처만 동작.
- 프로비저닝된 동시성 불가.
- EFS(Amazon Elastic File System) 연동 불가
- 512MB 이상의 임시 스토리지 불가
- 배포시간 증가 - 개발 생산성에 영향을 줌 (배포 파이프라인에 영향)
- 고유성에 영향을 줌 - 글로벌 변수에 난수 생성은 스냅샷에 포함되어 동일한 값이 사용되게 되기 때문이다. (함수에서 생성하거나 hook을 통해 처리 해야 함)

 

참고자료

 

728x90

master - slave 구조로 변경하려고 했다.

 

https://velog.io/@ch4570/MySQL-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%EC%82%B0-%EC%B2%98%EB%A6%AC%EB%A5%BC-%EC%9C%84%ED%95%9C-Master-Slave-%EC%9D%B4%EC%A4%91%ED%99%94%EB%A5%BC-%EA%B5%AC%EC%84%B1%ED%95%98%EB%8B%A4MySQL-Replication-%EC%84%A4%EC%A0%95%EA%B3%BC-%EA%B5%AC%EC%84%B1

 

[Pet-Hub] MySQL 데이터 분산 처리를 위한 Master-Slave 이중화 구성(MySQL Replication 설정)

지금껏 프로젝트를 진행하면서 대부분 RDS 서비스를 이용해 한개의 데이터베이스만 사용해왔습니다.이번 프로젝트의 중점 목표 중 하나인 고가용성의 설계와 확장성 있는 구조를 가진 서버를

velog.io

https://velog.io/@zvyg1023/mysql-master-slave

 

[MySQL] Master-Slave 구축

MySQL 데이터 분산 처리를 위한 Master-Slave 이중화 구축 이중화를 하는 이유 데이터베이스의 트랜잭션을 readOnly로 셋팅할경우 성능이 상승하는 이점을 누릴 수 있습니다. 데이터베이스에서 일어나

velog.io

 

이 글들을 참고 해서 계속 시도를 해보았는데 조금 어려움을 겪었다.

 

하라는 대로 했는데 replication이 안됐다. 

 

SHOW SLAVE STATUS\G 명령을 실행하여 레플리케이션 상태를 확인 하는데, Slave_IO_Running Slave_SQL_Running이 모두 "Yes"였다. 잘 설정 되었다고 생각 했는데 안되는 것이었다.

 

그래서 생각 해보니 이런 현상이 발생한 주요 원인은 레플리케이션 설정 과정에서 초기 데이터 동기화가 제대로 이루어지지 않았기 때문일 가능성이 높다고 생각했다.

  1. 초기 데이터 불일치:
    레플리케이션을 설정할 때 마스터의 기존 데이터를 슬레이브로 복사하지 않았을 가능성.
  2. 바이너리 로그 포지션 오류:
    슬레이브가 마스터의 바이너리 로그에서 잘못된 위치부터 복제를함. 이 경우, 새로운 변경사항만 복제되고 이전 데이터는 누락 될 가능성이 있음.

그래서 master를 덤프하고 slave에 덮어 씌워 해결 했다.

 

1. 마스터 데이터베이스에서 덤프를 한다.

docker exec -it hoppang_database_master bash

 

mysqldump -u root -p --all-databases --master-data=2 --single-transaction --flush-logs --hex-blob > /master_data.sql

 

그러면 덤프 파일이 생긴 것을 확인할 수 있다.

 

덤프 파일을 호스트서버로 이동 시킨 후

docker cp hoppang_database_master:/master_data.sql /home/twj/master_data.sql

 

호스서버에서 슬레이브 데이터베이스로 이동시킨다.

docker cp ./master_data.sql hoppang_database_slave:/tmp/master_data.sql

 

슬레이브 데이터베이스로 이동 후.

docker exec -it hoppang_database_slave bash

 

덤프를 덮어 씌운다. ( 여기에서 hoppang은 스키마 이름이다. ) 저 명령어를 입력 하면 root로 가는 패스워드를 입력하면 성공한다.

mysql -u root -p hoppang < /tmp/master_data.sql

 

 

이 이후에 성공적으로 replication이 이루어졌다.

728x90

 

들어가며

프록시 (Proxy) 란 한 마디로 대리 기사라고 표현할 수 있다. 프록시 서버는 클라이언트가 자신을 통해서 다른 네트워크 서비스에 간접적으로 점속할 수 있게 해주는 컴퓨터 시스템이나 응용 프로그램을 가리킨다.
 
 
 

Forward Proxy

클라이언트가 프록시 서버에 요청을 보내고, 프록시 서버가 요청을 받아 실제 서버로 전달한다.
 

Reverse Proxy

클라이언트가 실제 서버(또는 서비스)의 주소를 모르고, 대신 프록시 서버의 주소로 요청을 보내면, 프록시 서버가 요청을 적절한 백엔드 서버로 전달한다.
 
 
 

AS-IS

 
변경 전 구조는 위 그림과 같다. 공유기에서 바로 해당하는 프록시가 있는 포트로 포트포워딩 해주는 구조였다. 
 
해당 구조는 작업하기에 편했지만, 서버의 포트를 많이 열어 주어야 하기 때문에 생길 보안이슈로 부담스러웠다.
 

  • 공격 표면 증가: 여러 포트를 열어 두면 그만큼 공격자가 침입할 수 있는 경로가 많아져서, 해커가 특정 포트를 타겟으로 하는 공격(예: 포트 스캐닝, DoS 공격)을 시도할 가능성이 높아짐.

  • 취약점 노출: 열려 있는 포트가 사용하는 서비스나 소프트웨어에 보안 취약점이 있다면, 이를 통해 쉽게 침입할 수 있음. 특히 포트가 열려 있으면 버전 정보나 서비스 정보가 노출될 가능성이 있어 공격에 취약함.

 
뿐만 아니라 추가적인 이슈로는 유지보수, 서버 리소스, 스케일링 복잡성, 인증서 관리의 복잡성 등의 추가적인 이슈가 예상됐다.
 
첫 번째로, 유지보수성에 단점이 있다. 각각의 프로젝트에서 docker-compose 파일로 관리를 해야 하고 nginx.conf 파일도 따로 관리를 하는 부분이 번거로웠다. 설정 변경할 때 또한 일관성을 유지 하기에 어려움이 있었다. 각 프로젝트에서 일일히 수정 작업이 들어가니 애를 먹었다. 

그리고 두 번째로, 이 구조는 네트워크 오버헤드 발생할 수 있을 것 같다. 각 서버 마다 Nginx를 통해 통신을 해야 하는데 이는 불필요한 통신이 많아지기 때문에 대용량 트래픽 처리에 좋지 않아 보인다.

세 번째로, 서버 리소스 낭비이다. Nginx 인스턴스가 WAS마다 별도로 존재하면 서버 자원을 많이 소비한다.
 
네 번째로, 스케일링 복잡성이다. 이는 유지보수성이랑도 연관 되는데 시스템 확장 시 각 Nginx를 별도로 조정해야 하므로, 스케일링이 번거롭고 관리 포인트가 늘어날 수 있다.
 
다섯 번째로, 인증서 관리의 복잡성이다. 각각의 Nginx에 적용을 해야 하는 공수가 있다. 공유 스토리지를 사용하는 방법도 있는데 덕지덕지 붙여지는 느낌이라 꺼려졌다.

그래서 다음과 같이 단일 Nginx로 통합되는 구조로 변경 해보았다.
 
 
 

TO-BE

 
 
이렇게 변환하니 많은 변화가 생겼다.
 
CI/CD의 변화, 단일 프록시로 통합, 설정 중앙화, 유연한 스케일링, 유지보수성 등 좋아짐.
 
기존 CI/CD는 각 애플리케이션 저장소에서 각각 이미지화 해서 Nginx와 함께 서버가 띄어졌다면, 변경 된 후 하나의 docker compose 파일로 관리 되어서 배포 될 때 마다 해당 파일을 참조하여 빌드할 수 있게 됐다.
 
하나의 docker-compose 파일로 관리 하게 되어서 서버 구조를 한 눈에 볼 수 있어서 더 편했다. 더 나아가 각각 컨테이너가 논리적으로 하나의 network으로 묶인 상태이면 각각이 내부적으로 container name으로 호출할 수 있는 구조가 되어서 외부와 단절시킬 수 있는 큰 장점이 있다고 생각 되었다. 그래서 보안적으로도 큰 이점이 있다고 생각했다. (도커 내부적으로 DNS를 운영 하기 때문에 가능한 부분이다) 그리고 단일  docker-compose 파일로 관리 하는 것은 scale up/out 다 용이했다.
 
단일 프록시로 통합 되어서 서버 리소스를 절약할 수 있었고, 분산 된 설정을 한 곳에서 유지보수 할 수 있어서 훨 편했다.
 

728x90

 

배경

이 포스팅에서는 DLQ로 SQS의 fifo queue로 채택을 했다. 해당 DLQ는 source queue와 lambda를 바라보고 있다. 해당 DLQ에 이벤트가 쌓이는 케이스는 대표적으로 source queue와 lambda 사이에 통신이 안될 때, lambda에서 처리 로직 중 에러가 발생 했을 때 가 된다고 생각해서 DLQ의 필요성을 느껴서 비치했다.

실패한 이벤트에 대해서 후처리를 해야 하는 거는 분명히 필요하지만, lambda 이후에 fifo DLQ를 비치하는것에 문제가 있었다. SQS fifo 타입의 장점 중 하나인 이벤트 중복 제거를 해친다는 점에서 문제가 있었다.

 

https://aws.amazon.com/ko/blogs/compute/new-for-aws-lambda-sqs-fifo-as-an-event-source

 

 

해결

람다에서 재시도를 할 때 중복 이벤트가 DLQ에 들어갈 수 있다. 여기서 의문이 들 수 있는 부분이 DLQ는 fifo 타입인데 어떻게 중복으로 들어가냐는 말인데 이벤트 content가 아무리 같다고 하더라도 fifo queue에 들어가는 이벤트가 중복인지 아닌지를 판단하는 부분이 GroupId, Deduplication Id으로 판단 한다. 그러나 해당 이벤트의 content를 기반으로 중복 이벤트 처리를 할 수도 있다. Content-based de-duplication 을 활성화 하면 되긴 한다. 그러나 굳이 DLQ가 있어야 할까? 라는 생각이 들었다. 더 cost가 저렴한 RDB/NoSQL 등으로도 처리가 가능하다고 생각했다. 그리고 후처리 까지 써드파티에 의존하기가 꺼려졌다. 그래서 실패한 이벤트에 대한 후처리는 DB로 처리하기로 결정했다.

해당 처리는 복잡한 조인이나 집계 쿼리 같은 것들이 필요가 없고 빠른 읽기/쓰기가 요구되어서 RDB 보다 NoSQL인 MongoDB로 하기로 했다. 데이터의 id를 실패한 이벤트의 messageId 로 설정하며 데이터 정합성을 더했다. 그리고 DLQ의 리드라이브를 대체하여 애플리케이션에서 특정 API 호출 시 dead letter 가 쌓인 MongoDB에 아직 미해결 된 이벤트들을 찾아서 다시 호출 해서 source queue로 다시 넣어 프로세스를 흐름에 편승하게끔 하게 해결했다.

 

AS-IS TO-BE

 

 

결론

최고 50% 가까이 개선 됐다.

배포 전 배포 후


 

(* 비슷한 데이터 처리량을 토대로 비교 했음.)

 

회사에서 또 다른 유의미한 발전을 이루어서 뿌듯했다.

 

728x90

이슈

https://ydontustudy.tistory.com/193 해당 글에서, 알림톡 발송 후처리 로직이 있는 람다 함수에 Reserved Concurrency/Provisioning Concurrency 를 적용 해서 백앤드 어플리케이션 내부 큐를 사용하면 될 것 같다는 의견을 냈었다. 그러나 이 방법은 서버 배포 단계에서 문제가 있었다. 그래서 해결 방법을 고안한 과정과 그 중 적용한 방법을 소개해보겠다.

 

 

해결 방법 모색

  • 배포 전에 해당 EC2 서버에 내부 queue에 있는 이벤트 모두 소모 시키는 API 호출 후, graceful shutdown 시키는 방법

CD(Continuous Deployment) 과정에서 서버를 내리기 전에 서버 내부 큐에 쌓여 있는 이벤트 들을 모두 처리 해라는 API 를 호출 후 새로운 버전의 서버를 빌드/실행 시키는 방법을 생각 해봤다. 장점은 외부 서비스를 사용하지 않고 코드로만 문제를 해결할 수 있고, 그에 따라 비용절감에 도움이 될거라고 생각한다. 반대로 단점은 많은 공수가 들어간다. Graceful shutdown 적용, Jenkins 스크립트 파일 수정, 내부 큐 이벤트 모두 소모 API 추가 등 많은 공수가 들 것으로 판단되었다. 또한 서버에 부하를 준다.

 

  • 메시지 큐 사용

메시지 큐를 사용하면 producer인 서버의 상황에 따라 유실되는 이벤트에 대한 걱정을 덜 수 있다. 그리고 무엇 보다 꼭 메시지 큐를 사용 하고자 했던 이유는 서버에 부하를 주지 않기 위함이다. 각각의 역할을 분리해서 부하를 줄이는 것이 목표 이었기 때문에, 알림톡 이벤트 발행의 역할을 지닌 서버와 해당 이벤트를 처리하는 메시지 큐의 역할을 분리 함으로서 자바 서버의 부하가 현저히 줄어들 수 있다고 생각했다. 단점은 비용이 들고 외부에 의존적으로 된다는 점을 생각해볼 수 있었다. 그러나 백 앤드 퍼포먼스를 높일 수 있기 때문에 트레이드 오프가 가능하다는 생각이었다. 

 

 

해결

API gateway 와 consumer인 람다 사이에 메시지 큐를 사용하였고, 실패한 이벤트는 Dead Letter Queue 에 보관 후 처리 되는 프로세스로 개선 하였다.

 

프로세스 종류는 다음과 같다.

  1. Source Queue 적재
  2. 람다 함수 (Consumer) 실행 하여 들어 오는 이벤트 처리
  3. Dead Letter Queue에 실패 한 이벤트 적재

 

Source Queue 에서 람다 함수로 가는 동안 람다 함수에 문제가 생겨 NACK 발생하면 해당 실패 이벤트가 DLQ 적재 되는 반면, 만약 ACK 발생하면 람다 함수에 이벤트가 전달 되어서 처리 한다. 혹시 람다 함수에서 처리 중에 에러가 발생하면, DLQ 적재 되는 프로세스이다.
DLQ 쌓인 이벤트 처리는 서버에 API 뚫어 놓고, 버튼 하나로 팀원 전체가 시스템문의에서 처리가 가능하게끔 범용적인 기능을 제공 예정이다. (발송 실패한 알림톡은 버튼 하나로 발송 가능하게끔 추상화) 해당 버튼이 트리거가 되고, 트리거가 발생 하면 SQS 에서 리드라이브(redrive)가 수행되어서 DLQ 에 있는 모든 이벤트가 다시 Source Queue 로 들어 가 다시 처리하는 방식이다.

 

마무리

끝 없는 고민의 반복이다. 꼬리에 꼬리를 물고 문제점들이 생각난다. 그러다가 모든 전력이 꺼져서 이벤트 유실이 발생하면 어떡하지? 라는 고민까지 갈 수 있을 것 같다. 이러다 심하게 탈모 올 것 같다. 가끔은 머리를 식혀줘야겠다.

아직까지 V1.7 까지의 설계이고 V4 까지 설계 해놓았다. 회사로 들어 오는 트래픽은 V4 까지 하기엔 오버엔지니어링이다. 현재 상황으로는 설계로 족할 것 같다. 그래서 토이프로젝트에 미리 적용 해보고 회사에 추후 적용 해볼 수 있는 시간이 있는 것 같다.

728x90

배경

최근에 회사에서 알림톡 프로세스를 개선했다. 자세한 내용: 여기

 

알림톡 기능 퍼포먼스 개선

배경기존에 A 서버에서 B 서버로 호출 하면 써드파티 알림톡 발송업체로 호출 하고 다시 A 서버로 콜백 하는 방식이었다. (A 서버 에서는 해당 발송 결과 내역을 저장한다.) N번의 알림톡 발송 요

ydontustudy.tistory.com

 

 

알림톡 발송 후 콜백을 받아 람다에서 후처리 하는 프로세스까지는 성공적이었다. 람다에 대해 더 알아보며 Reserved Concurrency/Provisioning Concurrency 가 있었다. 여기서 파생해서 람다에 대해 더 알게 된 이후로 현재 설계로는 서비스가 장기적으로 잘 돌아가지 않을 것이고 큰 기술부채로 남겨질 것 같다고 생각해서 더 고민을 해보았다.

 

해결 방법 모색

 

Reserved Concurrency 에 대한 고민

 

람다는 동시에 1000개의 함수가 실행될 수 있는데, 해당 Region의 모든 람다가 통틀어 1000개 라고 한다. 즉, 여러개의 함수가 있을 때, 중요하지 않은 함수의 요청이 갑자기 많아지만 정작 중요한 함수에 대한 요청이 거부되면서, 실행하지 못하는 이슈가 발생할 수 있다. 그래서 전체 1000개의 pool에서 특정 중요한 함수에 할당 해놓기 위해서 Reserved Concurrency 를 설정 해둘 필요성을 느꼈다.

(추가적으로, 해당 설정으로 인해 디도스나 비용절감에도 도움이 될 것 같다.)

 

그래서 알림톡 후처리를 하는 람다 함수에 Reserved Concurrency를 300개 정도 설정 해놓고, 해당 함수에 대한 전체 요청은 어플리케이션내에서 처리하는 방향이 낫지 않을까 생각 해보았다. 해당 기능은 LB 환경에 대한 고려도 하지 않아도 된다. 왜냐하면 람다로 보내질 이벤트에 담길 정보들은 Notify가 아닌 다른 서버에서 요청을 받기 때문에 데이터 정합성이 이미 확보 돼있고, 순서에 크게 상관없기 때문이다.

 

그리고 알림톡 프로세스 개선을 V4 까지 잡아 놓았는데, V2 부터는 람다에서 메시지 큐에 들어갈 이벤트를 발행 할 예정이다. 이러한 부분에서도 내부 큐에서 번들로 람다에 요청 보내는것이 필요하다고 생각한다. 메시지 큐 이벤트 하나하나 발행할 때마다 람다에 요청하기엔

람다의 컨넥션 풀이 한정적이고 Reserved Concurrency 에 설정 된 임계값이 넘어가면 throttling이 일어나기 때문에 내부적으로 번들링 하는거는 현재 상황에서 필수불가결하지 않을까 생각해본다.

 

 

Provisioned Concurrency 에 대한 고민

 

람다를 사용하는 많은 분들이 고민하는 부분중 하나는 Cold start 일것이다. 요청을 받고 인스턴스가 띄워지는 시간 동안은 요청을 처리하지 못하기 때문에 그만큼 고객은 기다리는 시간도 정비례한다. 이거를 해결하기 위해서는 Provisioned Concurency 를 설정하면 된다. 해당 기능은 미리 Warm up 될 람다 인스턴스의 개수를 설정 하면, 바로 처리 할 수 있게 그만큼 함수를 warm up 시켜놓는다. 그러나 해당 기능은 비용이 발생한다.


개선 하려고 하는 프로세스는 실시간성을 띄고있지 않기 때문에 비용을 부담하면서 까지 해당 기능이 필요하지 않을 것 같다.

 

 

마무리

기술 하나를 도입할 때 양보다 질이라고 생각이 든다. 좋다고 하는 기술들을 이것저것 덕지덕지 붙이다 보면 미래에는 큰 부채가 될 가능성이 많을 것 같다. 그래서 최대한 많이 알아보고 테스트도 거쳐 보아야 할 것 같다.

728x90
findDateBetween(LocalDateTime arg1, LocalDateTime arg2)

 

 

이렇게 조회 했더니 안됐다. 이유를 찾아 보니

 

byte[], byte[] 로 인식한다고 한다.

 

해결 방법은

 

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-java8</artifactId>
    <version>${hibernate.version}</version>
</dependency>

 

 

이걸 추가 하면 된다고 한다.

 

그래서 추가 했더니 잘 됐다.

 

그러나 굳이 추가 해서 프로젝트 용량을 더 늘릴 필요성을 못느껴서 LocalDateTime을 Date 타입으로 컨버팅 하는 정적 유틸 메소드가 있어서 해당 메소드를 사용했다.

 

 

 

참조

https://developer-syubrofo.tistory.com/289 [공부는 관성이다.:티스토리]

 

728x90

배경

기존에 A 서버에서 B 서버로 호출 하면 써드파티 알림톡 발송업체로 호출 하고 다시 A 서버로 콜백 하는 방식이었다. (A 서버 에서는 해당 발송 결과 내역을 저장한다.) N번의 알림톡 발송 요청이 생기면 A서버가 즉시 N번의 콜백 요청을 받아 수행했었다. 이 부분에서 회사 트래픽이 올라가면서 cpu 사용량이 많이 올라가기 시작했다.

그래서 회사 동료분중 한 분이 해당 부분을 개선하기 위해 A 서버에서 콜백을 받고 바로 처리 하는게 아니라, 콜백을 받으면 카프카에 이벤트를 저장하는 방식으로 바꾸셨다. 그런데 여기서도 cpu 부하가 걸렸다. (A 서버에서 카프카로 이벤트를 발행만 하는데도 부하가 생겼다.) 그래서 내가 해당 이슈에 대해서 곰곰히 생각해 보았다. 처음에 해당 로직의 성격상 A 서버에서 처리를 하는게 맞는가를 따져 보았을 때, 그건 맞았다. 그래서 해당 레거시 코드를 작성하신 분을 어느정도 이해할 수 있었다. 
그러나 이미 A 서버는 무거워서 간당간당한 상황이었다. 이벤트를 발행 하는 역할은 AWS Lambda를 사용 해봐도 괜찮겠다는 생각이 들어 말씀 드렸었다. 이후 자연스럽게 내가 프로젝트에 참여하게 되었다. 

 

해결 방법 모색

처음에 이벤트 발행을 Lambda 에서 하는 것을 시작으로 생각을 이어 나갔다. 그 와중에 Produce/Consume 둘 다 람다에서 해도 되겠다는 생각이 들었다. 공유를 드리고 개발을 해보았다. 지금까지 나는 이 회사에 들어와서 카프카를 접해볼 시간이 없었다. 이번에 처음 접해 보니 회사 넥서스에 회사 전용 카프카 전용 객체가 있었고 기존에 카프카는 이와 함께 맞물려 돌아 가고 있었다. 이를 처음으로 node.js 환경에서 돌아갈 수 있게 새로 개발 해야 하는 공수를 들여야 했다. 하면 되겠지만 이미 할 일도 많고 굳이 이렇게 까지 할 필요가 있을까 라는 의견들이 있었다. 그리고 무엇 보다 더 크리티컬한 이유는 기존에 카프카를 사용할 때 실패한 이벤트에 대한 전략이 세워져 있지않아서 이 상태로 람다에다가 적용 할 npm 라이브러리를 만들면 기술 부채를 그대로 옮겨 놓는 것 이라고 판단 했다.
결론은 현재 트래픽으로는 굳이 카프카를 사용 할만한 처리량을 갖고 있지 않은 상태에서 적용 하는건 오버엔지니어링이고 실패한 이벤트에 대한 전략이 없는 상태에서 npm 라이브러리를 만들면 기술부채를 쌓는 격이라고 생각했다. 그래서 회사의 발전 상황에 따라 조금씩 발전 시키기로 결정 하여 버전으로 나누어서 청사진을 그려 보았다. 

 

- V1
    - Lambda 에서 바로 DB에 쌓기

 


- V2
    - Lambda 에서 Produce하기

 


- V3
    - Lambda 에서 Produce/Consume 하기

 


- V4
    - Transactional Outboxing Pattern 적용하기

 

 

마무리

이번 고민을 통해 여러 방면에서 퍼포먼스 개선을 고민할 수 있었다. 메시지 큐를 직접 다뤄본 적은 이번이 처음이었지만, 많은 회사에서 이를 사용하는 이유를 이해하게 되었다. '대용량'이라는 개념이 사람마다 다르고 회사의 서버 스펙에 따라 달라질 수 있어 명확히 정의하기 어려운 단어라는 점도 느꼈다. 카프카가 대용량 트래픽에 적합하다는 평가를 받지만, 현재 회사 상황에서는 오버엔지니어링이 될 수 있다는 고민을 많이 했다. 또한, 이번 기회를 통해 이벤트 관리의 중요성에 대해서도 깊이 생각해보게 되었다. 전체적으로 메시지 큐의 쓰임새에 대해 더 깊이 이해할 수 있는 유익한 시간이었다.

+ Recent posts