논리적 데이터 모델링

논리적 데이터 모델링 (Logical data modeling)

쿼리를 정의했으니 이제 Cassandra 테이블 설계를 시작할 준비가 됐어요. 먼저 각 쿼리에 대한 테이블을 포함하고, 개념적 모델의 개체와 관계를 포착한 논리적 모델을 만들어요.

출처: Logical data modeling

본문

각 테이블 이름을 지을 때는 쿼리 대상이 되는 주요 개체 타입을 식별하고 그것으로 개체 이름을 시작해요. 다른 관련 개체의 속성으로 조회한다면 그 속성을 _by_로 구분해 테이블 이름에 추가해요. 예를 들어 hotels_by_poi처럼요.

다음으로 테이블의 기본 키를 식별하는데, 필요한 쿼리 속성에 따라 파티션 키 컬럼을 추가하고, 고유성을 보장하고 원하는 정렬 순서를 지원하기 위해 클러스터링 컬럼을 추가해요.

기본 키 설계는 매우 중요해요. 각 파티션에 저장될 데이터 양과 디스크에서 데이터가 어떻게 구성될지 결정하며, 이는 Cassandra가 읽기를 얼마나 빨리 처리할지에도 영향을 주기 때문이에요.

쿼리로 식별된 추가 속성을 더해 각 테이블을 완성해요. 파티션 키의 모든 인스턴스에 대해 이 추가 속성이 같다면 그 컬럼을 static으로 표시해요.

방금 꽤 복잡한 과정을 빠르게 설명했으니, 상세한 예제를 통해 살펴보는 것이 가치 있을 거예요. 먼저 논리적 모델을 표현하는 데 사용할 표기법을 소개할게요.

Cassandra 커뮤니티의 여러 사람들이 데이터 모델을 다이어그램 형태로 포착하는 표기법을 제안했어요. 이 문서는 Artem Chebotko가 대중화한 표기법을 사용하며, 설계에서 쿼리와 테이블 간의 관계를 시각화하는 간단하고 유익한 방법을 제공해요. 아래 그림은 논리적 데이터 모델에 대한 Chebotko 표기법이에요.

Chebotko 논리적 표기법

각 테이블은 제목과 컬럼 목록으로 표시돼요. 기본 키 컬럼은 파티션 키 컬럼의 K, 클러스터링 컬럼의 C↑ 또는 C↓ 같은 기호로 식별돼요. 각 테이블이 지원하도록 설계된 쿼리를 나타내기 위해 테이블 안이나 테이블 사이에 선이 표시돼요.

호텔 논리적 데이터 모델

아래 그림은 호텔, 관심 지점, 객실, 편의 시설을 다루는 쿼리에 대한 Chebotko 논리적 데이터 모델이에요. 곧바로 알 수 있는 한 가지는 Cassandra 설계에 관계형 설계에 있었던 것처럼 객실이나 편의 시설 전용 테이블이 없다는 점이에요. 워크플로가 이 직접 접근을 요구하는 쿼리를 식별하지 않았기 때문이에요.

호텔 논리적 모델

이 각 테이블의 세부사항을 살펴볼게요.

첫 번째 쿼리 Q1은 관심 지점 근처의 호텔을 찾는 것이므로, 이 테이블을 hotels_by_poi라고 부를게요. 이름 있는 관심 지점으로 검색한다는 것은 관심 지점이 기본 키의 일부여야 한다는 단서예요. 워크플로에 따르면 사용자가 검색을 시작하는 방식이기 때문에 관심 지점을 이름으로 참조해요.

주어진 관심 지점 근처에 호텔이 여러 개 있을 수 있으므로, 각 호텔에 대해 고유한 파티션을 갖도록 기본 키에 또 다른 구성 요소가 필요하다는 것을 알게 돼요. 그래서 호텔 키를 클러스터링 컬럼으로 추가해요.

테이블 기본 키 설계에서 중요한 고려 사항은 고유한 데이터 요소를 정의하는지 확인하는 것이에요. 그렇지 않으면 실수로 데이터를 덮어쓸 위험이 있어요.

두 번째 쿼리(Q2)를 위해 특정 호텔에 대한 정보를 얻는 테이블이 필요해요. 한 가지 접근 방식은 호텔의 모든 속성을 hotels_by_poi 테이블에 넣는 것이었겠지만, 애플리케이션 워크플로가 요구한 속성만 추가했어요.

워크플로 다이어그램에서 hotels_by_poi 테이블이 각 호텔의 기본 정보와 함께 호텔 목록을 표시하는 데 사용되고, 애플리케이션이 반환된 호텔의 고유 식별자를 알고 있다는 것을 알 수 있어요. 사용자가 상세 정보를 보기 위해 호텔을 선택하면, 호텔에 대한 세부 정보를 얻는 데 사용되는 Q2를 사용할 수 있어요. 이미 Q1에서 hotel_id를 얻었으므로 찾고 있는 호텔에 대한 참조로 그것을 사용해요. 따라서 두 번째 테이블은 그냥 hotels라고 불러요.

또 다른 옵션은 hotels 테이블에 poi_names 집합을 저장하는 것이었을 거예요. 이것도 똑같이 유효한 접근 방식이에요. 어떤 접근 방식이 애플리케이션에 가장 좋은지는 경험을 통해 배우게 될 거예요.

Q3은 Q1의 반대, 즉 관심 지점 근처의 호텔이 아니라 호텔 근처의 관심 지점을 찾는 것이에요. 하지만 이번에는 pois_by_hotel 테이블로 표현되는 각 관심 지점의 세부 정보에 접근해야 해요. 앞서와 같이 고유성을 보장하기 위해 관심 지점 이름을 클러스터링 키로 추가해요.

이제 사용자가 선택한 호텔에서 관심 있는 밤 동안 예약 가능한 객실을 찾는 데 도움이 되는 쿼리 Q4를 지원하는 방법을 고려해 볼게요. 이 쿼리는 시작 날짜와 종료 날짜를 모두 포함한다는 점에 주목하세요. 단일 날짜가 아닌 범위에 대해 조회하므로 날짜를 클러스터링 키로 사용해야 한다는 것을 알 수 있어요. 각 호텔의 객실 데이터를 단일 파티션에 그룹화하기 위해 hotel_id를 기본 키로 사용하며, 이는 검색을 매우 빠르게 만드는 데 도움이 돼요. 이 테이블을 available_rooms_by_hotel_date라고 부를게요.

범위 검색을 지원하려면 clustering columns를 사용해 범위 쿼리에서 접근해야 할 속성을 저장해요. 클러스터링 컬럼의 순서가 중요하다는 점을 기억하세요.

available_rooms_by_hotel_date 테이블의 설계는 넓은 파티션(wide partition) 패턴의 한 예예요. 이 패턴은 유사한 모델을 지원하는 데이터베이스를 논할 때 넓은 행(wide row) 패턴이라고 부르기도 하지만, Cassandra 관점에서는 넓은 파티션이 더 정확한 설명이에요. 패턴의 핵심은 단일 쿼리에서 파티션 내 여러 행에 빠르게 접근할 수 있도록 여러 관련 행을 파티션에 그룹화하는 것이에요.

데이터 모델의 쇼핑 부분을 마무리하기 위해 Q5를 지원하는 amenities_by_room 테이블을 추가해요. 이를 통해 사용자가 원하는 숙박 날짜에 예약 가능한 객실 중 하나의 편의 시설을 볼 수 있게 돼요.

예약 논리적 데이터 모델

이제 예약 쿼리를 살펴볼게요. 그림은 예약에 대한 논리적 데이터 모델을 보여줘요. 이 테이블들이 비정규화된 설계를 나타낸다는 것을 알 수 있어요. 같은 데이터가 서로 다른 키로 여러 테이블에 나타나요.

예약 논리적 모델

Q6을 충족하기 위해 reservations_by_guest 테이블로 손님 이름으로 예약을 조회할 수 있어요. Q7은 셀프서비스 웹사이트의 손님을 대신해 또는 손님 지원을 시도하는 콜센터 상담원을 위해 사용될 것이라고 상상할 수 있어요. 손님 이름이 고유하지 않을 수 있으므로 여기에도 손님 ID를 클러스터링 컬럼으로 포함해요.

특히 Q8과 Q9는 애플리케이션의 다양한 이해관계자를 지원하는 쿼리를 만드는 것을 상기시켜 줘요. 고객뿐 아니라 직원, 어쩌면 분석 팀, 공급업체 등도 지원해야 해요.

호텔 직원은 호텔이 어떻게 운영되고 있는지(예: 어떤 날짜에 호텔이 매진되거나 덜 판매되는지) 통찰을 얻기 위해 날짜별로 다가오는 예약 기록을 보고 싶어할 수 있어요. Q8은 주어진 호텔에 대한 예약을 날짜별로 검색하는 것을 지원해요.

마지막으로 guests 테이블을 만들어요. 이것은 손님 정보를 저장하는 데 사용되는 단일 위치를 제공해요. 이 경우 손님 기록에 대해 별도의 고유 식별자를 지정하는데, 손님들이 같은 이름을 가진 경우가 드물지 않기 때문이에요. 많은 조직에서 guests 테이블 같은 고객 데이터베이스는 별도의 고객 관리 애플리케이션의 일부일 수 있으며, 그래서 다른 손님 접근 패턴은 예제에서 생략됐어요.

패턴과 안티패턴 (Patterns and Anti-Patterns)

다른 유형의 소프트웨어 설계와 마찬가지로 Cassandra의 데이터 모델링에도 잘 알려진 패턴과 안티패턴이 있어요. 이 호텔 모델에서 가장 흔한 패턴 중 하나인 넓은 파티션 패턴을 이미 사용했어요.

시계열(time series) 패턴은 넓은 파티션 패턴의 확장이에요. 이 패턴에서 특정 시간 간격의 일련의 측정값이 넓은 파티션에 저장되며, 측정 시간이 파티션 키의 일부로 사용돼요. 이 패턴은 비즈니스 분석, 센서 데이터 관리, 과학 실험을 포함한 영역에서 자주 사용돼요.

시계열 패턴은 측정값 외의 데이터에도 유용해요. 은행 애플리케이션의 예를 생각해 보세요. 각 고객의 잔액을 행에 저장할 수 있지만, 다양한 고객이 잔액을 확인하거나 거래를 하면서 많은 읽기/쓰기 경합이 발생할 수 있어요. 잔액이 잘못 갱신되지 않도록 쓰기 주위에 트랜잭션을 감싸고 싶을 수도 있어요. 대조적으로 시계열 스타일 설계는 각 거래를 타임스탬프된 행으로 저장하고 현재 잔액을 계산하는 작업은 애플리케이션에 맡겨요.

많은 새 사용자가 빠지는 설계 함정 중 하나는 Cassandra를 큐로 사용하려는 것이에요. 큐의 각 항목은 타임스탬프와 함께 넓은 파티션에 저장돼요. 항목은 큐의 끝에 추가되고 앞에서 읽히며, 읽힌 후 삭제돼요. 이는 특히 시계열 패턴과의 명백한 유사성 때문에 매력적으로 보이는 설계예요. 이 접근 방식의 문제는 삭제된 항목이 이제 Cassandra가 큐의 앞에서 읽기 위해 스캔해야 하는 tombstones라는 점이에요. 시간이 지남에 따라 증가하는 tombstone 수는 읽기 성능을 저하시키기 시작해요.

큐 안티패턴은 데이터 삭제에 의존하는 모든 설계가 잠재적으로 성능이 나쁜 설계라는 점을 상기시켜 줘요.

이 자료는 Cassandra, The Definitive Guide에서 각색한 내용이에요. O'Reilly Media, Inc. 발행. Copyright © 2020 Jeff Carpenter, Eben Hewitt. All rights reserved. 허가를 받아 사용했어요.

더 알아보기 (Learn more)