AWS 비용
초기에는 EC2 2개를 사용해서 Web Server, WAS를 사용해 서비스를 하려고 했다.
그런데 비용이 많이 나와서 프리티어 한도 내에서 사용을 할 수 있지 않을까를 고민 중
Lambda를 찾았다. 서버리스가 궁금했고 어차피 이 서비스는 내가 사용하려고 만든거라
비용 절감이 1순위였기에 Lambda를 채택했다.
2025-01-16
현재는 다시 EC2 와 Lambda를 혼용해서 사용하는 방식으로 사용을 하고 있고 ec2 2개를 각각 하루의 12시간씩 띄어놓는 상황으로 프리티어 시간을 해결했다. node로 백엔드를 사용하고있는 api는 그대로 lambda를 사용하고 있다.
Lambda 웹 스크래핑 브라우저 미지원
초기에는 Java + Seleninum + EC2 조합으로 동적 웹 스크래핑을 해결했다.
이를 Lambda로 전환 하니 Lambda에서 브라우저 지원이 그렇게 좋지 않았다.
브라우저를 직접 설치해 줘야 하고 그 과정에 다양한 것들 , 웹 드라이버도 브라우저 버전에 맞는
버전을 찾아서 설치해줘야하는데 이게 버전관리도 이상하게되어서 너무 힘들었다.(2주 넘게 매일 이거만 찾았다,,,)
잘 찾아보니 node에서는 브라우저와 웹드라이버 호환성을 잘 관리해주는 프로젝트를 찾아서 이를 사용했다.
아래의 프로젝트는 기존의 AWS LAMBDA에서 브라우저 버전을 관리편하게해주는 프로젝트이다가 관리가되지않아서 직접 포크를 해서 유지보수 해주고 있는 프로젝트입니다.
파이썬을 사용하지않고 node로 웹 스크래핑을 할 경우 아래의 프로젝트를 참고하면 좋을 것 같습니다.
Sparticuz/chromium: Chromium (x86-64) for Serverless Platforms (github.com)
GitHub - Sparticuz/chromium: Chromium (x86-64) for Serverless Platforms
Chromium (x86-64) for Serverless Platforms. Contribute to Sparticuz/chromium development by creating an account on GitHub.
github.com
lambda 웹 스크래핑 속도
스크래핑 방식 변경
초기에는 브라우저를 띄워서 동적 웹스크래핑을 사용하다 보니 모든 전자도서관을 검색할 경우 너무 느렸다. 10초가 넘게 걸렸다. 지금도 느린데 도서관이 추가될 경우 더 느려지기에 시간을 단축할 방법을 찾아야 했다.
처음 구현한 경기도 사이버 도서관 제외하고는 모두 정적 콘텐츠를 반환하기에
전부 Rest Client로 호출 후 결과 값에서 원하는 값을 필터링 거쳐서 반환하는 방식으로 속도를 매우 감소했다.
콜스 스타트 문제
Lambda를 사용 시 요청단위로 서버가 켰다 꺼지기에 일정 시간이 지나면 aws에서 서버를 끈다.
이렇기에 일정 시간이 지나면 요청이 오면 다시 서버를 기동하는 시간이 필요하다.
이를 해결하기 위해 2가지의 방식을 찾았다.
1.Provisioned Concurrency (프로비저닝된 동시성)
AWS Lambda 함수, Provisioned Concurrency를 통해 빠른 성능 제공 (서울 리전 포함) | Amazon Web Services 한국 블로그
aws 공식으로 권장하는 방식이다.
Provisioned Concurrency를 사용하면, Lambda 함수가 미리 설정한 만큼 계속 실행 상태로 유지된다. 이 경우, 호출이 들어올 때 콜드 스타트가 발생하지 않는다. 즉, 특정 개수의 was 가 항상기동이 되어 있기에 콜드 스타트가 발생하지 않는다.
2. polling 방식으로 Lambda 함수를 주기적으로 호출
프론트엔드에서 주기적으로 계속 호출을 한다.
이 경우에도 was가 항상 띄어져있기에 콜드스타트 문제가 발생하지 않는다.
나는 비용면이 가장 우선순위 이고 Lambda 의 프리티어 한도 내에서 충분히
2번방식으로 해결을 할수 있다고 생각하기에 2번을 채택했지만 아직 구현은 하지 않았습니다.
Lambda 사용시 RDS Too many connections
Lambda 사용시 RDS를 연결한 jar를 실행시 커넥션을 맺게된다.
한번에 많은 Lambda 함수를 실행시키면 그만큼의 JAR가 실행되어서 RDS의 많은 커넥션을 요청하게 되는 상황이 생긴다.
2024-12-30T06:19:34.338Z
2024-12-30T06:19:34.338Z INFO 8 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2024-12-30T06:19:35.359Z
2024-12-30T06:19:35.358Z ERROR 8 --- [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Exception during pool initialization.
2024-12-30T06:19:35.358Z ERROR 8 --- [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Exception during pool initialization.
2024-12-30T06:19:35.359Z
java.sql.SQLNonTransientConnectionException: Too many connections
java.sql.SQLNonTransientConnectionException: Too many connections
2024-12-30T06:19:35.359Z
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:111) ~[mysql-connector-j-8.1.0.jar:8.1.0]
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:111) ~[mysql-connector-j-8.1.0.jar:8.1.0]
2024-12-30T06:19:35.359Z
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-j-8.1.0.jar:8.1.0]
그럴 경우 위와같은 Too many connections 에러로그가나오면서 정상적으로 실행이안되는 상황이 생긴다.
이는 RDS의 파라미터를 설정하면 해결된다.
max_connection : 연결 가능한 최대 커넥션 개수
t2.micro 기준 최대 85인 것을 확인 후 설정
wait_timeout : 커넥션 연결후 사용하지않을 경우 닫히는 시간 설정
기본이 8시간이라고 한다.
나는 10분으로 설정했다.
백엔드 서버리스 프레임워크
AWS Server less Java Container , Spring Cloud Function
위의 두 개의 프레임워크 샘플 프로젝트를 만들어보면서 비교를 하는데 Spring Cloud Function만이 정상 작동을 했다.
큰 차이점은 없었고 Spring Cloud 진영의 공식지원도 있었기에 Spring Cloud Function을 채택하고 개발을 했다.
(개발 중 기여를 하며 컨트리뷰터가 되어서 조금 더 애착이 간 프로젝트이기도 하다 ㅎㅎ)
서버리스 자동 배포 프레임워크
ec2의 자동배포는 github actions를 사용했지만
모든 백 엔드는 서버리스로 구현되어있기에 api게이트웨이 도 설정 가능하고 편한 자동배포툴을 사용하고 싶었다.
aws sam vs serverless framwork 둘 중 고민을 했다가 AWS에서 공식지원을 하는 aws sam로 선택했다.
메인 DB를 Redis 선택
당시 AWS 아키텍처는 AZ를 하나로 만들었다. 트래픽도 나밖에 사용하지 않을 거라 이중화도 안 해놓았고 해서 RDS 생성기준이 AZ 2개 이상이었기에 생성을 못 하길래 이참에 Redis 를 공부할 겸 메인 DB를 Redis (ElastiCache)로 선택을 하고 구현을 시작했다. 하다 보니 미친 짓이란 걸 알았다.
데이터 무결성을 유지하기 위해 백업방식을 고민하는 것과 key : value DB 상 데이터가 덮어쓰기 형식이 돼버리는데 이걸 기존 값을 보존하면서 새로운 값을 추가하는 형식이 너무 작업소요가 길어져서 다시 RDS로 복귀했다.
올바른 기술 선택이 얼마나 중요한지 다시 배운 중요한 계기였다.
도메인 모델과 JPA 엔티티 모델 분리 후 다시 결합
처음 프로젝트를 시작할떄 DDD 와 헥사고날아키텍처를 공부한뒤의 적용을 해보고싶어서 순수 코틀린 클래스의 도메인 모델과 엔티티모델을 분리해 도메인 모델의 로직을 추가해주고 JPA 엔티티는 순전히 DAO의 역할을 수행해주며 개발을 했다. 확실히 개발을 해보니 작은 프로젝트에는 맞지 않았다. 개발생산성이 너무 낮아진다. 내 데이터베이스의 설계능력이 부족한거일지도 모르지만 크게 도메인 모델과 엔티티모델간의 필드차이도 그렇게 차이가 나지 않고 매핑할 레이어만 추가된 느낌이 들었다. 너무 개발생산성이 낮아진다는 느낌이 들어 jpa 엔티티모델과 도메인모델을 합치는걸로 하기로했다.