금번 프로젝트에서 사용했던 Amazon DynamoDB 및 NoSQL에 대한 리뷰가 되겠다.
근간에 잘 나가는 소위 Amazon Serverless Architecture - Amazon API Gateway / AWS Lambda / Amazon DynamoDB 기반으로 프로젝트를 진행하다보니 자연스럽게 마주하게 된 경과.
일단 알려진 특징 먼저 간단히 나열한다
•
NoSQL - document store databaseSON 데이터를 통으로 저장하며, 각 JSON record가 동일한 scheme을 가질 필요가 없다(schema-free. 다만 Hash/Range Key는 필수). 또한, 1 depth에 국한되기는 하지만 JSON document의 각 attribute를 column으로 하여 JSON parsing 없이 direct 접근이 가능하다.
•
Hash / Range Key각 JSON record를 구분 짓는 Key는 Hash Key와 Range Key로 나뉘는데, 이들 둘이 합쳐져 각 record의 식별자가 된다. 이들 Key는 JSON record 내 attribute 중에 지정하며, Hash Key는 Partition 결정에 사용되어 필수이고, Range Key는 optional이다. 당연하게도 Hash Key에 대한 연산은 equality 비교만 가능하며, Range Key는 크기/string 등 여러 연산이 가능하다.
•
LSI(Local Secondary Index) / GSI(Global Secondary Index)query를 통한 모든 검색은 table Key 또는 LSI / GSI에 설정된 key - index를 통해서만 이루어진다. LSI는 해당 table의 Range Key만 다른 것을 뜻하고, GSI는 Hash, Range Key 모두가 다른 index를 뜻한다.
•
Scalability대부분의 NoSQL 주요 특징인 scaling out을 지원한다. data replication을 위해 partition은 3개의 site(AZ)에 분산 저장된다.
•
eventually / strongly consistent readscalability를 지원하면 당연한 이야기인데, data replication 도중에 reading이 발생할 경우 update 전 데이터를 보내줄 것인지(eventually - 하지만 결국에는 update된 데이터를 전달), 무조건 update된 데이터를 전달할 것인지(strongly)를 선택 가능하다. 물론 후자는 부가적인 비용을 요구한다.
•
RCU(Read Capacity Unit) / WCU(Write Capacity Unit)PaaS 솔루션 특징인 과금 모델 - provisioning 전략으로 초당 read / write 성능 지표를 뜻한다. 더 높은 성능을 위해서는 해당 값이 더 크도록 설정해야하고, 물론 크면 클 수록 비용이 더 든다.
•
query / scan 분리read 연산은 두 가지로 query / scan 두 가지로 나뉘는데, 전자는 index 또는 key를 사용한 조건 검색을, scan은 해당 table 또는 GSI 전체에 대한 조회를 뜻한다. 당연하게도 scan은 불필요한 data까지 조회하게 될 경우가 많기에 query를 우선적으로 사용해야 한다. 조회한 만큼 RCU를 소모하므로 성능 / 비용 측면에서도 query가 유리하다.
NoSQL 및 DynamoDB에 대한 개인적인 소감
•
사실 상 본격적으로 사용한 최초 NoSQL
Lannister에 적용했던 Hazelcast 역시 NoSQL로 분류하기도 하던데, Hazelcast는 그들이 In-Memory DataGrid라고 부르기도 하거니와, NoSQL로 칭하기에는 나의 use case 및 일반적으로 알려진 특성 상 좀 거시기하고, CQL로 애를 먹었던 Cassandra는 어디까지나 간단한 리뷰 정도라서리.
이는 그 뒤에서부터 이어질 이야기에 삑사리가 있을 수도 있다는 디펜스이자 독자로의 사전 주의(?).
•
Query-first approach 난감
NoSQL 설계 특징 중 하나가 소위 'Query-first approach', 즉, scheme 이후 query 고려가 아닌 query를 먼저 고려하는 설계인데, 이게 말이 Query first 이지 결국에는 scheme 설계 이후 query를 만들 일이 비일비재하다. 예컨데, 초기 요구 구현, 즉 scheme 설계 확정 이후 추가 요구가 들어왔다고 하자. 이 경우 기 설계된 scheme를 바탕으로 query를 만들어야 할 터인데, 이러한 흔한 상황 자체가 query first가 불가한 조건이다.여기서 DynamoDB에 '거참...'하게 만드는 기능 중 하나가 LSI(Local Secondary Index)인데, LSI는 table 생성되고 나면 추가할 수 없기 때문. 'GSI가 있는데 뭔 불만이냐' 할 수도 있는데, GSI는 사실 상 table 추가하는 것과 사실 상 동일하거니와(LSI와는 달리 table 과는 별도로 과금됨), LSI를 애용(?)하라는 Amazon의 guide 사실 상 따르기가 어렵다(이번 프로젝트에서는 LSI를 그렇게나 사용하려 노력했음에도 적용된 곳이 없다. 좀 더 정확히 말하자면, 적용할 구석을 찾을 수 없었다).
•
쉽지 않은 사용법
NoSQL 특징 중 하나로 쉬운 사용법을 거론하고는 하는데 '결국에는' 공감이 안된다. query의 경우, SQL 대신에 JSON 기반의 자체 query를 사용하는데, 간단하게 line 수만 보더라도 SQL에 비해 훨씬 더 많다. 어찌보면 당연한 게 SQL 자체가 query에 특화된 언어인데 이를 능가하는 것이 만무하다. Cassandra의 CQL이나, Hadoop - Hive의 HiveQL 등 여타 비 RDB 시스템에서 SQL을 지원하려는 노력만 봐도 이해가 될 만한 사항.NoSQL이라 당연하기는 하지만, join 연산이 불가능하다보니 RDMS가 제공하는 폭넓은 검색은 꿈도 못꾼다. 단일 table 내에서도 특정 table attribute를 조건으로 검색하는 것도 index를 통해서만 가능한데, index 생성도 위의 LSI/GSI에서도 보았듯이 관리가 쉽지 않다는 것이 함정. 물론 filtering을 통해서도 검색 효과를 볼 수 있지만, filtering은 이름에서도 (작게나마) 감잡을 수 있듯이, data fetching 이후 filtering하는 것이라 성능이 안나오는 것은 물론이고, RCU 감소 효과도 볼 수 없다.
이 외에도, RDB의 수많은 SQL function에 비해 query function이 몇 개 없다는 점과 효율적인 resource 사용을 위해 특정 partition에 hit이 몰리지 않게 hash key 설계하는 것도 유의 할 점이다(resource - W/RCU는 각 partition에 일괄로 나뉘어 할당된다).
•
WCU / RCU 설정 부담
더 많은 WCU / RCU을 할당할 수록 성능은 좋아지겠지만 그 만큼 더 많은 돈이 나가는지라, 적정선의 값을 할당해야만 하는데, 이게 만만하지 않다. WCU / RCU 조율을 위해 한동안 cloud watch를 항시 모니터링했고, 여전히 하고 있다. 조만간 autoscaling을 지원한다는데, 얼마나 이 부담을 덜어줄 지는 나와봐야 알 일.
•
Transaction 부재 유감(NoSQL이니 당연하다만)
NoSQL에서 transaction을 찾는 것도 이상한 일이다만, 어쨌건 transaction에 대한 요구는 일반적이다. 두 개 이상의 table으로 변경을 이루는 단일 action은 매우 흔한 일인데, 이에 대한 Atomic 연산이 DB 차원에서는 불가하다는 이야기. 이는 DB가 아닌 client library에서 지원을 하는 듯 보이는데, java에만 해당한다는 점이 함정(2017/05/30 기준). 금번 프로젝트는 nodeJS를 기본 언어로 사용했는데 이 덕분에 현재 골머리 중.
결론
어쩌다보니 소감이 아니라 NoSQL / DynamoDB에 대한 불평불만인 듯한 글이 되어버렸는데, 객관/절대적인 평가가 아닌 매우 주관적인 감상이 본질이다. 명시적으로 언급은 안했지만 사실 상 RDB를 염두해두고 비교한 측면이 큰데, 설계 관점이 주로서 운영 및 성능 관점은 거의 빠졌다는 점을 잊어서는 안된다. 예컨데, auto scaling은 차치하더라도 scalability는 보통의 RDB가 감당하지 못하는 대표적인 장점이거니와, case by case이기는 하겠지만 성능 부문에서도 많은 장점을 가지는 것이 사실이다. 또한, 설계 관점에서도 RDB가 훨씬 더 익숙한 상황에서 기술되었다는 점을 감안해야 한다.
첨언
지난 포스트에 이어 얼마 되지도 않아 바로 이렇게 포스팅한 이유는, 역시나 애드센스 땜시 ㅎㅎㅎ. 애드센스가 블로그 평가를 할 때 최근 2~3개를 중점으로 본다는 소문에 1000자를 넘기는 노력의 일환이다. CTR(Click Through Ratio), CPC(Cost Per Click) 등, 영향을 미치는 요소가 좀 있는 듯. 용어에서도 보듯이 좀 신경써서 볼 필요가 있네. 역시... 세상엔 공짜가 없어.