Azure Cosmos DB for PostgreSQL을 사용하여 다중 테넌트 데이터베이스 디자인
적용 대상: Azure Cosmos DB for PostgreSQL(PostgreSQL에 대한 Citus 데이터베이스 확장 기반)
이 자습서에서는 Azure Cosmos DB for PostgreSQL을 사용하여 다음 방법을 알아봅니다.
- 클러스터 생성
- psql 유틸리티를 사용하여 스키마 만들기
- 노드 간 테이블 분할
- 샘플 데이터 수집
- 테넌트 데이터 쿼리
- 테넌트 간 데이터 공유
- 테넌트별 스키마 사용자 지정
필수 조건
Azure 구독이 없는 경우 시작하기 전에 체험 계정을 만듭니다.
클러스터 생성
Azure Portal에 로그인하고 다음 단계에 따라 Azure Cosmos DB for PostgreSQL 클러스터를 만듭니다.
Azure Portal에서 Azure Cosmos DB for PostgreSQL 클러스터용 만들기로 이동합니다.
Azure Cosmos DB for PostgreSQL 클러스터 형식에서 다음을 수행합니다.
기본 사항 탭에서 정보를 작성합니다.
대부분의 옵션은 설명할 수 있지만 다음 사항에 유의하세요.
- 클러스터 이름에 따라 애플리케이션이 연결에 사용하는 DNS 이름이
<node-qualifier>-<clustername>.<uniqueID>.postgres.cosmos.azure.com
형식으로 설정됩니다. - 15와 같은 주 PostgreSQL 버전을 선택할 수 있습니다. Azure Cosmos DB for PostgreSQL은 항상 선택한 주 Postgres 버전에 대한 최신 Citus 버전을 지원합니다.
- 관리 사용자 이름은
citus
값이어야 합니다. - 데이터베이스 이름을 기본값 'citus'로 두거나 유일한 데이터베이스 이름을 정의할 수 있습니다. 클러스터 프로비저닝 후에는 데이터베이스 이름을 바꿀 수 없습니다.
- 클러스터 이름에 따라 애플리케이션이 연결에 사용하는 DNS 이름이
화면 아래쪽에서 다음: 네트워킹을 선택합니다.
네트워킹 화면에서 이 클러스터에 대한 Azure 내 Azure 서비스 및 리소스의 퍼블릭 액세스 허용을 선택합니다.
검토 + 만들기를 선택하고 유효성 검사가 통과되면 만들기를 선택하여 클러스터를 만듭니다.
프로비전하는 데 몇 분이 걸립니다. 이 페이지는 배포를 모니터링하도록 리디렉션됩니다. 상태가 배포 진행 중에서 배포 완료로 변경되면 리소스로 이동을 선택합니다.
psql 유틸리티를 사용하여 스키마 만들기
psql을 사용하여 Azure Cosmos DB for PostgreSQL에 연결되면 몇 가지 기본 작업을 완료할 수 있습니다. 이 자습서에서는 광고주가 자신의 캠페인을 추적할 수 있는 웹앱을 만드는 과정을 안내합니다.
여러 회사에서 이 앱을 사용할 수 있으므로 회사를 보관할 테이블과 자신의 캠페인을 보관할 다른 테이블을 만들어 보겠습니다. psql 콘솔에서 다음 명령을 실행합니다.
CREATE TABLE companies (
id bigserial PRIMARY KEY,
name text NOT NULL,
image_url text,
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL
);
CREATE TABLE campaigns (
id bigserial,
company_id bigint REFERENCES companies (id),
name text NOT NULL,
cost_model text NOT NULL,
state text NOT NULL,
monthly_budget bigint,
blocked_site_urls text[],
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL,
PRIMARY KEY (company_id, id)
);
각 캠페인에는 광고를 실행하기 위한 비용이 지불됩니다. psql에서 위의 코드 뒤에 다음 코드를 실행하여 광고에 대한 테이블도 추가합니다.
CREATE TABLE ads (
id bigserial,
company_id bigint,
campaign_id bigint,
name text NOT NULL,
image_url text,
target_url text,
impressions_count bigint DEFAULT 0,
clicks_count bigint DEFAULT 0,
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL,
PRIMARY KEY (company_id, id),
FOREIGN KEY (company_id, campaign_id)
REFERENCES campaigns (company_id, id)
);
마지막으로, 각 광고에 대한 클릭 횟수와 인상에 대한 통계를 추적합니다.
CREATE TABLE clicks (
id bigserial,
company_id bigint,
ad_id bigint,
clicked_at timestamp without time zone NOT NULL,
site_url text NOT NULL,
cost_per_click_usd numeric(20,10),
user_ip inet NOT NULL,
user_data jsonb NOT NULL,
PRIMARY KEY (company_id, id),
FOREIGN KEY (company_id, ad_id)
REFERENCES ads (company_id, id)
);
CREATE TABLE impressions (
id bigserial,
company_id bigint,
ad_id bigint,
seen_at timestamp without time zone NOT NULL,
site_url text NOT NULL,
cost_per_impression_usd numeric(20,10),
user_ip inet NOT NULL,
user_data jsonb NOT NULL,
PRIMARY KEY (company_id, id),
FOREIGN KEY (company_id, ad_id)
REFERENCES ads (company_id, id)
);
다음을 실행하여 psql의 테이블 목록에서 새로 만든 테이블을 볼 수 있습니다.
\dt
다중 테넌트 애플리케이션은 테넌트별로만 고유성을 적용할 수 있으므로 모든 기본 키와 외래 키에는 회사 ID가 포함됩니다.
노드 간 테이블 분할
Azure Cosmos DB for PostgreSQL 배포는 사용자가 지정한 열 값을 기반으로 다른 노드에 테이블 행을 저장합니다. 이 "배포 열"은 각 테넌트에서 소유하고 있는 열을 나타냅니다.
그럼 배포 열을 테넌트 식별자인 company_id로 설정해 보겠습니다. psql에서 다음 함수를 실행합니다.
SELECT create_distributed_table('companies', 'id');
SELECT create_distributed_table('campaigns', 'company_id');
SELECT create_distributed_table('ads', 'company_id');
SELECT create_distributed_table('clicks', 'company_id');
SELECT create_distributed_table('impressions', 'company_id');
Important
Azure Cosmos DB for PostgreSQL 성능 기능을 활용하려면 테이블을 배포하거나 스키마 기반 분할을 사용하는 것이 필요합니다. 테이블이나 스키마를 배포하지 않으면 작업자 노드는 해당 데이터와 관련된 쿼리를 실행하는 데 도움을 줄 수 없습니다.
샘플 데이터 수집
이제 psql이 아닌 일반 명령줄에서 샘플 데이터 세트를 다운로드합니다.
for dataset in companies campaigns ads clicks impressions geo_ips; do
curl -O https://examples.citusdata.com/mt_ref_arch/${dataset}.csv
done
psql로 다시 돌아가서 데이터를 대량으로 로드합니다. psql은 데이터 파일을 다운로드한 것과 동일한 디렉터리에서 실행해야 합니다.
SET client_encoding TO 'UTF8';
\copy companies from 'companies.csv' with csv
\copy campaigns from 'campaigns.csv' with csv
\copy ads from 'ads.csv' with csv
\copy clicks from 'clicks.csv' with csv
\copy impressions from 'impressions.csv' with csv
이제 이 데이터는 작업자 노드에 분산됩니다.
테넌트 데이터 쿼리
애플리케이션에서 단일 테넌트에 대한 데이터를 요청하면 데이터베이스는 단일 작업자 노드에서 쿼리를 실행할 수 있습니다. 단일 테넌트 쿼리는 단일 테넌트 ID를 기준으로 필터링합니다. 예를 들어 다음 쿼리에서는 광고 및 인상에 대해 company_id = 5
를 필터링합니다. 결과를 보려면 psql에서 실행해 보세요.
SELECT a.campaign_id,
RANK() OVER (
PARTITION BY a.campaign_id
ORDER BY a.campaign_id, count(*) desc
), count(*) as n_impressions, a.id
FROM ads as a
JOIN impressions as i
ON i.company_id = a.company_id
AND i.ad_id = a.id
WHERE a.company_id = 5
GROUP BY a.campaign_id, a.id
ORDER BY a.campaign_id, n_impressions desc;
테넌트 간 데이터 공유
지금까지 모든 테이블은 company_id
에 기반하여 배포되었습니다. 그러나 일부 데이터는 당연히 특정 테넌트에 "속하지 않으며" 공유할 수 있습니다. 예를 들어 광고 예제 플랫폼에 있는 모든 회사에서 대상 그룹에 대해 IP 주소 기반의 지리적 정보를 얻으려고 할 수도 있습니다.
공유 지리적 정보를 보관할 테이블을 만듭니다. psql에서 다음 명령을 실행합니다.
CREATE TABLE geo_ips (
addrs cidr NOT NULL PRIMARY KEY,
latlon point NOT NULL
CHECK (-90 <= latlon[0] AND latlon[0] <= 90 AND
-180 <= latlon[1] AND latlon[1] <= 180)
);
CREATE INDEX ON geo_ips USING gist (addrs inet_ops);
다음으로 geo_ips
를 "참조 테이블"로 만들어 모든 작업자 노드에 테이블 복사본을 저장합니다.
SELECT create_reference_table('geo_ips');
예제 데이터가 있는 테이블을 로드합니다. psql에서 데이터 세트를 다운로드한 디렉터리 내에서 다음 명령을 실행해야 합니다.
\copy geo_ips from 'geo_ips.csv' with csv
clicks 테이블을 geo_ips와 조인하는 것이 모든 노드에서 효율적입니다. 다음은 광고 290을 클릭한 모든 사람의 위치를 찾기 위한 조인입니다. psql에서 쿼리를 실행해 보세요.
SELECT c.id, clicked_at, latlon
FROM geo_ips, clicks c
WHERE addrs >> c.user_ip
AND c.company_id = 5
AND c.ad_id = 290;
테넌트별 스키마 사용자 지정
각 테넌트는 다른 테넌트에 필요하지 않은 특수 정보를 저장해야 할 수도 있습니다. 그러나 모든 테넌트에서 동일한 데이터베이스 스키마를 사용하여 공용 인프라를 공유하고 있습니다. 추가 데이터는 어디에 배치할 수 있을까요?
한 가지 방법은 PostgreSQL의 JSONB와 같이 확장 가능한 열 형식을 사용하는 것입니다. 스키마의 clicks
에는 user_data
라는 JSONB 필드가 있습니다.
회사(예: 회사 ID가 5임)에서 이 열을 사용하여 사용자가 모바일 디바이스에 있는지 여부를 추적할 수 있습니다.
다음은 모바일 또는 기존 방문자 중 누가 더 많이 클릭하는지 확인하는 쿼리입니다.
SELECT
user_data->>'is_mobile' AS is_mobile,
count(*) AS count
FROM clicks
WHERE company_id = 5
GROUP BY user_data->>'is_mobile'
ORDER BY count DESC;
부분 인덱스를 만들어 이 쿼리를 단일 회사에 대해 최적화할 수 있습니다.
CREATE INDEX click_user_data_is_mobile
ON clicks ((user_data->>'is_mobile'))
WHERE company_id = 5;
일반적으로 열 내의 모든 키와 값에 대해 GIN 인덱스를 만들 수 있습니다.
CREATE INDEX click_user_data
ON clicks USING gin (user_data);
-- this speeds up queries like, "which clicks have
-- the is_mobile key present in user_data?"
SELECT id
FROM clicks
WHERE user_data ? 'is_mobile'
AND company_id = 5;
리소스 정리
이전 단계에서 클러스터에 Azure 리소스를 만들었습니다. 이러한 리소스가 더 이상 필요 없으면 클러스터를 삭제합니다. 클러스터의 개요 페이지에서 삭제 단추를 선택합니다. 팝업 페이지에 메시지가 표시되면 클러스터의 이름을 확인하고 최종 삭제 단추를 선택합니다.
다음 단계
이 자습서에서는 클러스터를 프로비저닝하는 방법을 배웠습니다. 그 후 psql을 사용하여 이 서버 그룹에 연결하고, 스키마를 만들고, 데이터를 분산했습니다. 테넌트 내 또는 테넌트 간에 데이터를 쿼리하고, 스키마를 테넌트별로 사용자 지정하는 방법을 알아보았습니다.