"데이터에 어떻게 액세스해야 하나요?" 개발자들이 스토리지 솔루션을 고려할 때 자주 묻는 질문입니다. 이 질문에 답하려면 먼저 고려 중인 애플리케이션을 이해해야 합니다. 가장 중요한 사용자는 누구이며, 어떤 사용 사례, 즉 사용자가 어떤 작업을 많이 하는지에 따라 속도가 빨라야 할까요? 핫 경로는 무엇인가요?
핫 경로를 이해했다면 이제 스토리지를 살펴볼 준비가 된 것입니다.
대용량 파일 업로드와 같은 특정 작업은 다른 작업만큼 지연 시간에 민감하지 않습니다. 예를 들어 전자상거래 사이트에서 '지금 구매하기'를 클릭하거나 소셜 미디어에서 타임라인을 볼 때 고성능 저장 및 검색 기능을 활용하면 훨씬 더 많은 이점을 얻을 수 있습니다.
따라서 각 애플리케이션에 대해 아키텍처의 성능 관점에서 살펴보면 사용자가 많이 머무는 곳, 즉 응답성이 중요한 곳이 있다는 것을 알 수 있습니다. 단순히 빠르기만 한 것이 아니라 부하가 걸린 상태에서도 안정적으로 빠른 것이 중요합니다. 특히 지연 시간에서는 9가 중요합니다.
가장 중요한 사용 사례를 빠르게 처리하고 싶을 것입니다. 메모리는 디스크보다 훨씬 빠르기 때문에 이러한 사용 사례는 당연히 메모리에서 제공되어야 합니다. 메모리 기반 데이터베이스가 각광받는 것은 우연이 아닙니다. 특히 클라우드 환경에서는 메모리에서 서비스를 제공하면 애플리케이션이 일관되지 않은 디스크 성능으로부터 보호됩니다.
핫 경로를 중심으로 스키마 우선 순위 지정하기
스키마가 없는 데이터베이스를 사용하더라도 애플리케이션에는 스키마가 적용됩니다. 그 스키마가 고성능 사용 사례에 유리하도록 만드는 것은 사용자의 몫입니다. 따라서 핫 오브젝트 유형을 식별하고 이를 완전히 상주하는 버킷에 보관하는 것이 한 단계입니다. 따라서 4GB의 실시간 장바구니 데이터가 있는 경우 이를 위해 4GB 이상의 RAM을 따로 확보하세요. 20GB의 구매 내역 데이터가 있는 경우 해당 문서 유형에 5GB의 RAM만 할당해도 괜찮을 수 있습니다. 이렇게 하면 지연 시간에 덜 민감한 데이터에 대해 메모리보다 큰 디스크 효과를 적용하면서 모든 상황에서 가장 많이 사용되는 데이터의 성능을 유지할 수 있습니다.
이제 핫 경로에 대해 생각해 보았으니 뷰에 대해 이야기해 보겠습니다. 뷰는 매우 유연합니다(여기서 핵심은 실시간 데이터를 쿼리에 더 유리한 형태로 투영하는 기능입니다). 핫 경로가 키-값 모델에서 실행되기를 원할 것이므로, 결국 많은 포인터와 멀티 겟을 수행하게 됩니다. 즉, 고성능 애플리케이션은 데이터 모델을 핫 경로에 긴밀하게 바인딩합니다. 이로 인해 덜 중요한 사용 사례를 지원하는 것이 조금 더 어려워질 수 있습니다. 이때 뷰가 필요합니다. 뷰는 이러한 런타임 기반 데이터 구조에 쿼리 가능한 인덱스라는 새로운 생명을 불어넣을 수 있을 만큼 강력합니다. 결론은 핵심 값 성능에 더해 유연한 쿼리 모델을 얻을 수 있다는 것입니다.
구체적인 예로 맥주 샘플 데이터 세트를 살펴보겠습니다. 애플리케이션에서 사용자가 보고 싶어 하는 기본 화면은 맥주 목록과 평점이며, 각 맥주에 대해 최종 사용자가 작성한 맥주 리뷰를 빠르게 로드하는 기능이라고 가정해 보겠습니다. 이 경우 뷰 쿼리가 아닌 일괄 키-값 조회를 수행하여 가능한 최고의 성능으로 해당 화면을 채우고 싶을 것입니다. 두 번째 사용 사례가 도시별로 양조장을 검색하는 것이라면 기초 데이터에 대한 유연한 창을 제공할 수 있는 뷰를 사용하기에 좋은 시기입니다.
문서 자체부터 살펴봅시다. 이 사례에서는 맥주 문서에 직접 평점을 저장하고 있습니다. 평점을 임베드함으로써 쿼리나 추가 조회 없이도 UI에서 간단하게 평점을 표시할 수 있습니다. 또한 맥주 문서에서 리뷰('댓글')에 직접 연결하여 디스크 기반 보기 쿼리로 이동하지 않고도 맥주와 모든 댓글을 가져올 수 있도록 하고 있습니다. 이 기술은 엄청난 트래픽 부하에도 데이터베이스 응답 시간이 빠르다는 것을 보장합니다.
다음은 그림입니다:
파란색 문서는 사용자 프로필 문서로, 스키마의 다른 몇 군데에서 참조됩니다. user_id가 이미 있으면 언제든지 사용자 프로필을 빠르게 조회할 수 있습니다. 따라서 다른 JSON 문서에서 해당 ID를 찾아보세요.
노란색 문서는 맥주에 대한 댓글/리뷰입니다. 여기에는 맥주 ID가 표시되어 있지만 댓글에서 해당 맥주를 찾는 경우는 드뭅니다. 일반적으로 맥주를 손에 들고 리뷰를 찾고자 하는 경우가 더 많습니다. 앞서 가상의 예제에서는 이 부분이 성능이 중요한 섹션이라고 생각했기 때문에 보기를 사용하여 beer_id로 리뷰를 조회할 수도 있지만, 이 경우에는 키 값 인터페이스를 통해 조회를 수행하고자 합니다. 이 인터페이스를 통해 작업하면 인메모리 속도의 이점을 얻을 수 있을 뿐만 아니라 이러한 요청이 더 적은 서버 리소스를 사용하므로 확장성이 향상됩니다.
녹색 문서는 실제 맥주 문서입니다. 각 사용자가 각 맥주를 한 번만 평가할 수 있다는 제약 조건을 적용하기 위해 사용자 아이디 아래에 평점을 인라인으로 저장합니다. 리뷰/댓글의 경우 댓글 배열에서 링크합니다. 따라서 맥주 페이지를 표시하는 데 필요한 모든 데이터를 가져오는 코드는 다음과 같습니다(예제는 특정 프로그래밍 언어로 작성되지 않았습니다).
reviews = couchbase.multiget(beer.comments);
프로필 = couchbase.multiget(reviews.map{|review|review.user_id});
그러면 페이지에는 맥주에 대한 모든 리뷰에 대한 정보와 리뷰를 남긴 사용자에 대한 정보를 표시할 수 있는 충분한 데이터가 있습니다. 전체 작업은 데이터베이스에 3번만 요청하면 되므로 총 경과 시간은 몇 밀리초에 불과하며, 복잡한 쿼리를 데이터베이스에 전송하여 결과 집합을 구성하고 반환하는 다른 스타일보다 빠릅니다.
고성능 스키마 위에 뷰 계층화하기
키-값 상호 작용을 용이하게 하기 위해 설계한 문서 구조에는 매우 풍부한 기능이 있습니다. 최고 등급의 맥주를 찾기 위한 키-값 조회 경로를 유지하지는 않지만, 등급이 문서에 직접 포함되어 있기 때문에 등급별로 맥주 순위를 매기는 뷰를 쉽게 작성할 수 있습니다. 이 뷰를 쿼리하는 것은 직접 키-값 조회만큼 빠르지는 않지만, 최고 평점 맥주와 같은 것을 쉽게 캐시할 수 있으므로 기본 인덱스가 인메모리가 아닌 디스크 기반인 경우에도 사용자에게 고성능 환경을 제공할 수 있습니다.
다음은 평균 등급별로 맥주를 정렬하는 맵 보기입니다.
if (doc.ratings) {
var total = 0, count = 0;
for (var user_id in doc.ratings) {
total = total + doc.ratings[user_id]; count++
}
emit(total/count, null);
}
}
다음과 같이 쿼리할 수 있습니다. ?descending=true 를 클릭해 최고 등급의 맥주를 먼저 확인하세요.
동일한 기본 데이터 세트에서 특정 사용자가 남긴 모든 리뷰를 찾을 수 있는 방법도 제공할 수 있습니다. 이 작업은 (우리가 만든 예제 애플리케이션에서는) 일반적인 작업이 아니므로 엄청나게 빠른 속도가 중요하지 않습니다. 따라서 이를 위해 키-값 조회 경로를 유지하는 것은 큰 고통이었을 것입니다. 예를 들어, 사용자가 자신이 작성한 모든 리뷰를 찾으려는 경우가 거의 없다면 각 사용자 프로필에 첨부된 리뷰 ID 목록을 유지하는 것은 번거로운 일입니다. 대신 리뷰에 사용자 아이디로 태그를 지정하고 인덱스를 사용하여 쿼리를 지원하면 됩니다. 사용자 X에 대한 모든 리뷰를 찾는 보기는 간단합니다:
if (doc.type == "comment" && doc.user_id) {
emit(doc.user_id, null);
}
}
다음과 같이 쿼리합니다. ?key=525 를 클릭하면 525번 사용자가 작성한 모든 리뷰를 확인할 수 있습니다.
결론: 결론: 핫 경로를 위한 설계, 나머지는 NoSQL의 유연성에 맡겨보세요.
이 글을 통해 성능에 중요한 섹션을 위해 애플리케이션을 설계하는 방법에 대해 충분히 명확하게 이해하셨기를 바랍니다. 가장 빠른 속도가 필요한 페이지를 데이터베이스에서 가장 쉽게 로드할 수 있도록 스키마를 사용자 정의하는 것이 가장 좋습니다. 물론 이는 스키마가 특별히 빠르도록 설계되지 않았기 때문에 중요하지 않은 페이지를 로드하는 속도가 그다지 빠르지 않다는 것을 의미합니다.
하지만 카우치베이스 뷰를 사용하면 다른 액세스 패턴에 맞게 데이터의 용도를 쉽게 변경할 수 있습니다. 위의 예제가 성능 제약 조건에 따라 본질적으로 동일한 종류의 쿼리를 두 가지 매우 다른 방식으로 수행하는 방법을 보여 주었기를 바랍니다.
보너스: 조회 수 캐싱에 대해
핫 경로에 보기 쿼리가 포함된 경우(예: 소셜 네트워크 홈 타임라인) 이를 캐시해야 합니다. 즉, 첫 번째 요청은 몇 밀리초가 걸릴 수 있지만 후속 요청은 일관된 고성능을 유지할 수 있습니다. 이것은 mysql과 함께 memcached를 사용하는 일반적인 경우입니다. 이 패턴에서 멤캐시는 체감 성능을 높이고 데이터베이스의 부하를 줄이는 데 사용됩니다. 여기서는 느린 mysql 쿼리의 결과를 memcached에 저장하는 대신 Couchbase 보기 쿼리의 결과를 메모리 버킷에 저장한다는 점을 제외하면 본질적으로 동일한 것을 이야기하고 있습니다.
Couchbase TTL이 캐시 만료를 처리할 수 있습니다. 또는 좀 더 고급 캐시 유효성 검사를 사용할 수도 있습니다.
전략(다른 날의 이야기...)
[...] 모델링 전략도 있지만 이는 이 글의 범위를 벗어납니다. 이에 대한 자세한 내용은 Chris [...]의 "성능 지향 아키텍처"를 참조하세요.