AWS RDS와 DynamoDB의 비용 및 성능 비교는 데이터베이스 선택 시 중요한 고려사항입니다. 이 분석에서는 두 서비스의 비용 구조, 성능 특성, 그리고 대규모 쓰기 작업 시나리오에서의 비용 효율성을 살펴보았습니다. 또한 고가용성을 위한 중복 구성 시의 비용과 Spring Boot와의 연동 방법에 대해서도 논의하였습니다.
AWS RDS vs DynamoDB 비용비교
AWS RDS와 DynamoDB의 비용 비교에서, 25백만 건의 500바이트 레코드(총 12.5GB)를 기준으로 분석한 결과, DynamoDB가 RDS보다 상당히 높은 비용을 보였습니다. DynamoDB의 월간 비용은 $937.5로 추정되며, 이는 주로 쓰기 작업에 따른 비용입니다. 반면 RDS의 월간 비용은 $198.24로, 인스턴스 비용과 쓰기 작업 비용을 포함합니다. 이러한 차이는 DynamoDB의 쓰기 중심 요금 체계와 RDS의 인스턴스 기반 요금 구조의 차이에서 비롯됩니다.
DynamoDB: 쓰기 작업당 $1.25/백만 건, 스토리지 비용 $0.25/GB/월
RDS: 인스턴스 비용(예: db.t3.medium) + 쓰기 작업 비용 $0.20/백만 건
고가용성 구성 시, DynamoDB의 비용은 $1,406.25로 증가하며, RDS Multi-AZ는 $297.36로 증가합니다
선택은 애플리케이션의 요구사항, 확장성 필요, 그리고 운영 팀의 역량을 고려하여 이루어져야 합니다. DynamoDB는 글로벌 확장성과 관리 용이성에서 우위를 보이며, RDS는 복잡한 쿼리와 트랜잭션 지원에 더 적합합니다.
추가적으로 비용에서 고려할 수 있는 부분
AWS 데이터베이스 서비스의 비용을 고려할 때, 다음과 같은 추가적인 요소들을 염두에 두어야 합니다:
데이터 전송 비용: AWS 리전 간 또는 인터넷으로의 데이터 전송에 따른 추가 비용이 발생할 수 있습니다.
백업 및 복구: RDS의 경우 자동 백업과 수동 스냅샷에 대한 추가 스토리지 비용이 발생할 수 있으며, DynamoDB는 온디맨드 백업과 특정 시점으로의 복구(PITR) 기능에 대한 비용이 추가될 수 있습니다.
성능 최적화: RDS의 경우 쿼리 최적화를 통해 성능을 향상시키고 비용을 절감할 수 있습니다. 예를 들어, AI 기반 최적화 도구를 사용하여 쿼리 성능을 23배까지 향상시킨 사례가 있습니다.
서버리스 옵션: Amazon Athena와 같은 서버리스 쿼리 서비스를 사용하면 데이터 스캔량에 따라 비용이 청구되며, S3 Express One Zone 스토리지 클래스를 활용하여 쿼리 성능을 최대 2.1배 향상시킬 수 있습니다.
이러한 요소들을 고려하여 총소유비용(TCO)을 산정하고, 애플리케이션의 요구사항에 맞는 최적의 데이터베이스 솔루션을 선택해야 합니다.
Spring Boot 환경에서 DynamoDB 를 사용할때 설정해야 하는 부분
Spring Boot 환경에서 DynamoDB를 사용할 때는 다음과 같은 주요 설정을 고려해야 합니다:
=> 이부분은 사실 원격요청을 하지 않고 제공되는 공개키, pem을 통해서 로컬에서 검증이 가능하지만 현재 java로는 직접 구현해야 하는 부분이 많고, 자주는 아니지만 공개키는 변경될 수 있기 때문에 키값을 확인하기 위해서는 결국 원격요청이 필요해서 쉽게 가기로 했다.
-- "cognito:groups" 정보를 이용해서 권한을 세팅한다.
B. 기존 토큰이라면
- secretKey를 이용해서 검증하고
- "roles" 정보를 이용해서 권한을 세팅한다.
<정리>
이렇게 만들면 기존의 JWT Token(HS256) 와 신규 Cognito Token (RSA256) 를 모두 소화할 수 있다.
그러나 만들어 놓고보니 소스가 지저분하다. ResourceServer에서 권한을 확인하여 API접근제어를 해야하는데 좋은 구조는 아니다.
- 로직이 복잡하고(구조 특성상 해당모듈을 그대로 copy해가는 방식이 될텐데 유지보수성이...영), secretKey 공유의 문제도 있다.
- 꼭 두 가지 토큰을 소화할 필요가 있을까?
- 기존 방식은 외부 사용자가 고려되지 않은 방식이고, OAuth 2.0은 출발자체가 3rd Party연계를 간편하게 하기 위한 방법이다.
다양한 확장을 위해서 인증은 각각의 방식을 유지하고 서비스를 사용자에 따라서 분리하는 것이 해결책이라는 판단을 내렸다.
- 내부 사용자가 이용하며 Client, Resource Server가 같은 Boundary에 있는 레거시의 경우 기존 토큰을 사용하고
- 외부 사용자가 이용해야 하는 API 서비스는 별도로 분리 구성하며, 이 서비스에서는 OAuth 2.0만을 활용하여 인가/인증을 하도록 한다.
(이부분은 AWS API Gateway내에서 자격증명으로 Cognito를 연계하는 방식과 시너지를 낼 수 있을 것 같다.)
- 이를 위해서 보다 쉽게 Resource Server를 세팅하는 법을 Spring Security에서 제공하고 있는데 다음글에서 좀 더 자세히 적용해보고 서비스를 어떻게 분리하는 것이 보다 효과적일지 살펴본다.
- 대부분의 Public Cloud는 Endpoint와 HTTP API를 제공하고 있으며, SDK는 이것을 감싸는 구조로 되어있다.
- 따라서 url 인코딩을 주의하여 사용해야 한다.
- 예를 들어서 얼마전에 테스트하다가 파일명에 특이한 문자들을 넣게 되었는데 아래와 같은 오류가 발생하였다.
2022-05-19 10:15:15.447 DEBUG 30235 --- [nio-8900-exec-6] com.amazonaws.request : Received error response: com.amazonaws.services.s3.model.AmazonS3Exception: The request signature we calculated does not match the signature you provided. Check your key and signing method. (Service: Amazon S3; Status Code: 403; Error Code: SignatureDoesNotMatch; Request ID: S80BNXP50DTW9SJM; S3 Extended Request ID: , S3 Extended Request ID:
2022-05-19 10:15:15.449 DEBUG 30235 --- [nio-8900-exec-6] com.amazonaws.retry.ClockSkewAdjuster : Reported server date (from 'Date' header): Thu, 19 May 2022 01:15:15 GMT
.
.
.
at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:5054) [aws-java-sdk-s3-1.11.762.jar:na]
at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:5000) [aws-java-sdk-s3-1.11.762.jar:na]
at com.amazonaws.services.s3.AmazonS3Client.access$300(AmazonS3Client.java:394) [aws-java-sdk-s3-1.11.762.jar:na]
at com.amazonaws.services.s3.AmazonS3Client$PutObjectStrategy.invokeServiceCall(AmazonS3Client.java:5942) [aws-java-sdk-s3-1.11.762.jar:na]
at com.amazonaws.services.s3.AmazonS3Client.uploadObject(AmazonS3Client.java:1808) [aws-java-sdk-s3-1.11.762.jar:na]
at com.amazonaws.services.s3.AmazonS3Client.putObject(AmazonS3Client.java:1768) [aws-java-sdk-s3-1.11.762.jar:na]
- 인증토큰이나 이러한 부분에 문제인줄 알았는데 유사한 문제를 겪은 사람들이 상당히 많았고
<개요> - 다음과 같이 Service #A 에서 Service #B로 데이터 조회 API를 요청하고 값을 받아오는 로직이 있다. - Service #B에서는 AWS Athena를 저장소로 사용하고 있으며 Athena JDBC42 드라이버를 사용 중 이다.
API 호출 후 응답
<현상> - Service #B에서 JdbcTemplate을 통하여 쿼리가 수행된 시간은 11:13:13 이고, 2021-11-04 11:13:13.482 DEBUG 9668 --- [http-nio-8200-exec-9] o.s.jdbc.core.JdbcTemplate : Executing SQL query 2021-11-04 11:13:13.482 DEBUG 9668 --- [http-nio-8200-exec-9] o.s.jdbc.datasource.DataSourceUtils : Fetching JDBC Connection from DataSource - 실제 쿼리 수행결과를 받아온 시간은 11:15:57 로 약 2분44초 가 소요되었다. 2021-11-04 11:15:57.998 INFO 9668 --- [http-nio-8200-exec-9] ...
- Athena 의 경우 동시에 다수의 쿼리가 수행되면 Queue에 의하여 순차적으로 수행될 수 있기 때문에 쿼리 히스토리를 조회하였다.
11:13:13.542초 시작, 수행시간 0.555초대기열시간 1분21초
- 대기열 시간 1분21초 + 수행시간 0.555초를 제외하고 꽤 오랜시간이 소요되었다.
<소스분석> - AthenaJDBC42의경우 일반적인 JDBC드라이버처럼 커넥션을 맺고 Resultset을 처리하는 형태가 아니라 AWS Athena로 Http를 통해서 수행요청을 하고, 리턴값으로 ID를 받아온 뒤 일정시간 Thread Sleep하면서 조회 polling을 요청하고 Status가 Completed가 되었을때 후속처리를 하는 형태로 구성되어 있다.
- 또한 위에도 언급한것처럼 동시에 다수의 요청이 집중될경우 자체적으로 큐에 보관하여 처리하게 된다.
- 부수적으로 Athena JDBC드라이버의 SStatement내 execute, getResultSet등의 메소드를 살펴보면 대부분 synchronized로 선언이 되어있기 때문에 이에 따른 delay도 있지 않을까 예상한다.
<Thread Dump>
10개의 Thread가 같은 위치에서 대기중이다.
"http-nio-8200-exec-9" #44 daemon prio=5 os_prio=31 tid=0x00007ffcc655f800 nid=0x8c03 waiting on condition [0x000070000c638000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method) at com.simba.athena.athena.api.AJClient.executeQuery(Unknown Source) at com.simba.athena.athena.dataengine.AJQueryExecutor.execute(Unknown Source) at com.simba.athena.jdbc.common.SStatement.executeNoParams(Unknown Source) at com.simba.athena.jdbc.common.SStatement.executeNoParams(Unknown Source) at com.simba.athena.jdbc.common.SStatement.executeQuery(Unknown Source) - locked <0x000000078740ccf8> (a com.simba.athena.athena.jdbc42.AJ42Statement) at com.zaxxer.hikari.pool.ProxyStatement.executeQuery(ProxyStatement.java:111) at com.zaxxer.hikari.pool.HikariProxyStatement.executeQuery(HikariProxyStatement.java) at org.springframework.jdbc.core.JdbcTemplate$1QueryStatementCallback.doInStatement(JdbcTemplate.java:439) at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:376) at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:452) at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:462) at org.springframework.jdbc.core.JdbcTemplate.queryForObject(JdbcTemplate.java:473) at org.springframework.jdbc.core.JdbcTemplate.queryForObject(JdbcTemplate.java:480)
<정리> - 다수의 사용자에게서 발생하는 ad-hoc형태 처리는 적합하지 않다.(hive와 동일함)
- Global cache(Redis)를 적절히 활용하여 Service #B Layer에서 처리를 하도록 하면 효율성을 증가시킬수 있다.(일반적인 캐시전략)
- 과거 Hadoop Eco에서도 Hive, Impala, Presto와 같은 다양한 소프트웨어가 있었는데, 수년간 시스템을 구축해본 결과 같은 생태계 내에서 동등한 발전속도를 가지고 있다는 전제라면 자신의 케이스와 매칭이 잘 되는 솔루션을 선택하는 것이 매우 중요합니다.
- 반대로 이야기하면 생태계의 규모가 크고, 발전속도가 빠른 (자주 릴리즈 되는) 소프트웨어는 다 이유가 있다로 설명할 수 있습니다. (다양한 케이스를 흡수하면서, 기존 케이스의 확장성을 보완하는 경우들입니다.)
- 두 제품의 성능 비교는 생략하고 Redshift내에서 선택하는 Node 타입에 대해서만 비교를 해보았습니다. (실무에서 그렇게 까지 사용해 볼만한 비용이나 시간여유가 없었습니다. 다음에 GCP를 사용하게 될 기회가 생기면 비교해보겠습니다.)
<고밀도 컴퓨팅 DC2 vs 고밀도 스토리지 DS2>
고밀도 컴퓨팅 DC2
vCPU
메모리
스토리지 용량
I/O
요금
dc2.large
2
15 GiB
0.16TB SSD
0.60 GB/s
0.30 USDper Hour
dc2.8xlarge
32
244 GiB
2.56TB SSD
7.50 GB/s
5.80 USDper Hour
고밀도 스토리지 DS2
vCPU
메모리
스토리지 용량
I/O
요금
ds2.xlarge
4
31 GiB
2TB HDD
0.40 GB/s
1.15 USDper Hour
ds2.8xlarge
36
244 GiB
16TB HDD
3.30 GB/s
9.05 USDper Hour
- 저장 공간이 많이 필요하다면 DS2, 연산속도가 필요하다면 DC2를 선택하면 됩니다. (클러스터 생성 후 변경가능)
- 개발목적의 경우 large, 스테이징 / 운영의 목적으로 8xlarge를 선택하였습니다.
- RedShift의 경우 노드의 수는 x2 형태로 지정가능합니다.
- large 와 8xlarge의 경우 개당 노드로 단순하게 비용을 비교하면 약 20배 정도 차이가 납니다. (large(40) vs 8xlarge(2))
<속도 및 저장공간>
- dc2.large(4) vs dc2.8xlarge(2) 의 성능을 비교해 보았습니다. 소요된 Cost는 약 10배정도 입니다.
- dc2.large(4)의 경우 640GB 저장공간이 제공되며, dc2.8xlarge(2) 의 경우 5.1TB가 제공됩니다.
- 쿼리별 수행 속도 (캐시, 네트워크/CPU 상황등 고려해야하는 변수들이 있기 때문에 단순참고로만 보시길 바랍니다.)
쿼리 유형
dc2.large(4)
dc2.8xlarge(2)
#1
1.2초
0.01초
#2
6.5초
0.5초
#3
7.1초
1초
#4
7.2초
0.9초
#5
8.5초
1.2초
#6
1분30초
10초
#7
3분
16초
- 대략 300개의 쿼리를 수행하는데 dc2.large(4) 에서 19분정도 소요되던 작업이 dc2.8xlarge(2)내에서 약1분30초 정도에 완료되는것을 확인할 수 있었습니다.
- 정리해보면 제공되는 저장공간은 약 8배, 처리속도는 약 9.5배정도에 근접합니다.
(들어간 비용대비 정직한 결과를 보여줍니다.)
<정리>
AWS Redshift
- 클러스터 생성 후 노드타입을 변경하는 것도 가능합니다. 단, 적재되어 있는 데이터량에 따라서 다운타임이 존재
- Scale out 을 통해서 얻어지는 성능향상 효과도 있으나, 대규모 분산/병렬처리가 필요한 쿼리에만 해당하며
일반적인 쿼리에서는 Scale up을 통한 vCPU, 메모리, I/O 의 성능향상 효과가 눈에 보입니다.
일반적인 선택가이드
- 대량의 비정형/반정형 데이터의 단순 가공의경우 S3로 Pipeline을 구축하고 Athena 로 검색하는 것이 가장 효율적
- Join이 없으며 수천만건 이상의 대량의 데이터를 쉽게 Scale out할 수 있는 구조를 원한다면 NoSQL
- Join이 필요하며 대규모 분석쿼리 및 표준SQL, 다양한 인터페이스가 필요한 경우 OLAP
- 트랜잭션처리가 가장 중요한 경우면 OLTP
<각 Public Cloud에 대한 개인적인 의견 + 동향>
- AWS의 경우 IaaS로 출발했던 구조로 인하여 상대적으로 Azure, GCP 에 비해서 Full manage, serverlees 솔루션이 부족
- GCP의 경우 BigTable, BigQuery가 뛰어난 성능을 가지고 있기 때문에, 최근 데이터처리에 특화된 어플리케이션이 필요한 경우 선택하는 수요가 늘고 있음