중급8분 분량

DynamoDB JOIN: 테이블 조인 방법

DynamoDB에는 JOIN가 없습니다. API에는 조인 연산자인 데이터 모델이 없습니다. 외래 키가 없으며 — 대부분의 사람들을 놀라게 하는 부분 — , SQL 기반 쿼리 레이어는 하나도 추가하지 않습니다. PartiQL SELECT은 다음을 읽습니다. 정확히 한 테이블.

관계형 데이터베이스에서 왔다면 이것이 첫 번째 벽입니다. 이 가이드에서는 벽이 존재하는 이유, 개발자가 대신 수행하는 네 가지 작업, 실제 조인이 정말로 필요한 경우와 이를 실행하는 방법입니다.

DynamoDB는 조인을 할 수 있나요?

아니요. DynamoDB는 하위 수준 API(GetItem / Query / Scan / BatchGetItem), 를 통하지 않고, 어떤 것도 통하지 않음 내장된 쿼리 플래너가 없기 때문입니다. 모든 읽기는 하나의 테이블에 매핑되거나 인덱스 중 하나; 일치하는 키에 두 테이블을 결합하는 것은 당신이하는 일입니다 앱에서 after DynamoDB는 항목을 반환하지만 항목 내부에는 반환하지 않습니다.

  • DynamoDB에는 JOIN 연산자가 없습니다. 그런 적이 없습니다.
  • PartiQL의 SELECT단일 테이블 전용입니다. 문법은 말 그대로 SELECT … FROM {{table}}[.{{index}}], 두 테이블을 가리키면 반환됩니다. ValidationException: Only select from a single table or index is supported.
  • AWS에서 권장하는 수정 사항은 조인이 필요하지 않습니다: 또는 다음을 사용하는 것입니다. single-table design 관련 항목이 살고 있습니다. 단일 요청으로 하나의 파티션을 가져옵니다.
  • 실제 크로스 테이블/임시 케이스의 경우 외부 DynamoDB — in에 가입합니다. 귀하의 앱 또는 귀하를 위해 이를 수행하는 도구를 사용합니다.

DynamoDB에 조인이 없는 이유

SQL 10은 데이터베이스에 여러 테이블을 읽고 쿼리 시 이를 조합하도록 요청합니다. 시간. AWS 자체 guide to modeling relational data 비용을 자세히 설명합니다. 다음과 같은 쿼리는

SELECT * FROM Orders
  INNER JOIN Order_Items ON Orders.Order_ID = Order_Items.Order_ID
  INNER JOIN Products    ON Products.Product_ID = Order_Items.Product_ID
  INNER JOIN Inventories ON Products.Product_ID = Inventories.Product_ID
  ORDER BY Quantity_on_Hand DESC

유연하지만 "쿼리의 각 조인은 쿼리의 런타임 복잡성을 증가시킵니다. 왜냐하면 각 테이블의 데이터를 준비한 다음 조합해야 하기 때문입니다." 그 작업에는 제한이 없습니다. 비용은 쿼리가 아닌 데이터에 따라 달라집니다. DynamoDB가 갖기를 거부하는 속성이 바로 그것입니다.

그래서 AWS는 제약 조건을 설계했습니다. DynamoDB는 말하자면 "다음을 위해 구축되었습니다. JOINs을 제거하여 [CPU 및 네트워크] 제약을 모두 최소화합니다(그리고 데이터의 비정규화 장려) 및 데이터베이스 아키텍처 최적화 항목에 대한 단일 요청으로 애플리케이션 쿼리에 완벽하게 응답합니다." 그것들은 어떤 규모에서도 10밀리초 미만의 지연 시간을 확보할 수 있는 특성: DynamoDB 읽기의 런타임 비용은 테이블 크기에 관계없이 일정합니다. 있다 설계상 조인 엔진도 없고 계획할 외래 키 개념도 없습니다.

"하지만 PartiQL은 SQL인데 당연히 결합되나요?"

아니요. PartiQL은 SELECT / INSERT / UPDATE / DELETE 구문을 제공합니다. DynamoDB는 SQL이 아닌 SQL-_호환_입니다. 는 official 4 grammar 는:

SELECT  {{expression}}  [, ...]
FROM    {{table}}[.{{index}}]
[ WHERE {{condition}} ]
[ ORDER BY {{key}} [DESC|ASC], ... ]

FROM하나 테이블(선택적으로 인덱스 중 하나)을 사용합니다. 두 번째는 없습니다 FROM테이블,JOIN 없음, 하위 쿼리 없음, CTE 없음. DynamoDB에 대해 세 가지를 모두 실행했습니다. 각각이 어떻게 실패하는지 정확히 알아보세요.

명시적인 JOIN:

SELECT o.pk FROM "Orders" o JOIN "Customers" c ON o.customerId = c.pk
ValidationException: Only select from a single table or index is supported.

FROM에 있는 두 개의 테이블 — 동일한 거부이므로 엔진은 JOIN을 거부하지 않습니다. 키워드는 두 번째 테이블을 거부합니다.

SELECT * FROM "Orders", "Customers"
ValidationException: Only select from a single table or index is supported.

하위 쿼리는 다르게 실패하므로 디버깅하는 경우 알아두면 좋습니다. PartiQL은 다중 테이블 검사에 전혀 도달하지 않습니다. 즉, IN 피연산자를 거부합니다. 그 전에는 테이블을 언급하지 않는 메시지가 표시됩니다.

SELECT * FROM "Orders" WHERE customerId IN (SELECT pk FROM "Customers")
ValidationException: IN operator must have a left hand argument of type Variable
Reference and right hand argument of type Seq with at least one member

실질적인 결과: 가입을 유도하는 문구가 없습니다. 첫 번째 두 개는 두 번째 테이블에서 죽고 세 번째는 더 일찍 피연산자에서 죽습니다.

PartiQL이 SQL처럼 보이지만 작동할 수 없는 이유에 대한 완전한 추론을 원하는 경우 좋아요, PartiQL vs SQL를 보세요.

개발자가 실제로 사용하는 4가지 해결 방법

1. 비정규화(데이터 복사)

그렇지 않으면 항목에 직접 결합할 필드를 저장하십시오. Order은 다음을 전달합니다. 대신에 customerNameshippingAddress의 스냅샷

customerId 나중에 해결하세요. 한 번만 읽고 조인하지 마세요.

비용은 쓰기 시간 팬아웃입니다. 소스가 변경되면 모든 복사본을 업데이트합니다. (일반적으로 핸들러를 통해). 읽기 복잡성을 희생하고 있습니다. 쓰기 복잡성 - 일반적으로 읽기가 많은 앱에 대한 좋은 거래입니다.

2. 단일 테이블 설계(파티션에 사전 조인)

공유 파티션 키 아래 한 테이블에 관련 엔터티를 넣어 is 결합된 결과입니다. 고객과 고객의 모든 주문이 공유됩니다. PK = "CUSTOMER#42"; 하나 Query는 고객 품목과 모든 주문을 반환합니다. 항목 — 쓰기 시 이미 "조인"이 발생했습니다.

Query  PK = "CUSTOMER#42"
→ CUSTOMER#42 / PROFILE      (the customer)
→ CUSTOMER#42 / ORDER#1001   (an order)
→ CUSTOMER#42 / ORDER#1002   (an order)

이는 일대다 관계에 대한 표준 DynamoDB 답변입니다. 전체 single-table design의 연습입니다.

3. 애플리케이션 측 조인(2회 읽기, 코드 스티치)

테이블 A에서 읽고, 돌려받은 키를 가져오고, 테이블 B에서 읽고, 병합합니다. 애플리케이션에 두 개의 결과 세트가 있습니다. 이는 관계형 조인 논리입니다. 데이터베이스 대신 코드에서 실행:

// "Get each order with its customer name" — the manual join.
const {Items: orders} = await ddb.query({TableName: 'Orders' /* … */});

const customers = await Promise.all(
  orders.map((o) => ddb.get({TableName: 'Customers', Key: {id: o.customerId}}))
);

const joined = orders.map((o, i) => ({
  ...o,
  customerName: customers[i].Item?.name
}));

소규모 팬아웃에 적합합니다. 주문이 많으면 N+1 문제가 됩니다. — 한 번만 읽으면 됩니다. 주문을 나열한 다음 주문당 한 번 읽습니다. 이는 느리고 읽기 용량을 소모합니다. BatchGetItem(next)는 두 번째 물결을 한 번의 왕복으로 축소합니다.

4. BatchGetItem(1회 왕복, 여러 테이블)

1 API가 "한 번에 두 테이블을 터치"하는 데 가장 가까운 것입니다. 요청은 "하나 이상의 항목 에서 하나 이상의 항목 속성을 반환합니다. 테이블,' 호출당 최대 100개 항목 또는 16MB 중 먼저 도달하는 항목입니다. 그것 앱 측 조인의 왕복 횟수를 줄입니다. 하지만 조인은 아닙니다. 당신 "기본 키로 요청된 항목을 식별합니다"; ON 조건도 없고, 관계 매칭. 당신은 여전히 앞의 키를 알고 바느질해야합니다 스스로 반응해보세요.

실제 JOIN이 불가피한 경우

네 가지 해결 방법은 프로덕션 읽기 경로를 잘 다루고 있습니다. 그들이 쓰러지는 곳은 임시, 탐구, 분석 쿼리 — 모델링하지 않은 쿼리:

  • "지난 달에 $500 이상 주문한 EU 고객은 누구입니까?" Orders 테이블과 Customers 테이블.
  • 두 엔터티 유형을 결합하는 일회성 데이터 품질 검사입니다.
  • 보고 및 집계(GROUP BY, SUM, COUNT) - DynamoDB에는 없음 전혀 연산자.

이는 정확히 파티션에 미리 구울 수 없는 쿼리입니다. 당신이 그들에게 물어볼 줄은 몰랐습니다. 관계 본능 - 글쓰기 JOIN — 여기가 맞습니다. DynamoDB는 기본적으로 이를 제공할 수 없습니다. PartiQL도 마찬가지입니다.

일반적인 헤비급 대답은 다음과 같습니다. export to S3 and query with Athena (또는 Athena의 통합 쿼리 커넥터를 사용하여 라이브 테이블에 연결) 또는 파이프를 통해 창고. 규모에 따른 진정한 분석에는 맞는 말이지만, 라이브 테이블에 대해 지금 답변을 원하는 질문에 대한 많은 배관 작업.

DynoTable의 SQL Workbench를 사용하여 실제 JOIN 실행

DynoTableSQL Workbench가 실행되는 데스크톱 DynamoDB 클라이언트입니다. JOIN, GROUP BY 및 집계 함수를 포함한 실제 SQL DynamoDB 테이블. 일반 DynamoDB API를 통해 항목을 읽은 다음 클라이언트에서 쿼리의 관계 부분을 실행합니다. 따라서 다음과 같이 작성할 수 있습니다.

SELECT  c.name, SUM(o.total) AS spend
FROM    Customers c
JOIN    Orders o ON o.customerId = c.id
WHERE   c.region = 'EU'
GROUP BY c.name
HAVING  SUM(o.total) > 500

— 정의된 관계가 없는 테이블에 대해 결과 세트를 얻습니다. JOIN 키워드가 없는 쿼리 엔진.

정직한 주의 사항 — "DynamoDB의 액세스 패턴 규칙 내에서": Workbench 여전히 DynamoDB를 통해 읽으므로 무제한 조인은 무제한 읽기입니다. 는 가장 빠른 쿼리는 WHERE 절(또는 조인의 ON 속성)이 하나 이상의 파티션 키 또는 GSI에 도달합니다. 따라서 DynamoDB는 전체 테이블이 아닌 Query를 실행합니다. scan 조인이 실행되기 전입니다. 워크벤치는 그렇지 않습니다. 이 가이드에서는 제약 조건을 폐지합니다. 단지 _SQL 질문을_할 수 있게 해주기만 하면 됩니다. 스티치를 직접 손으로 쓰는 대신, 그것이 무엇을 하고 있는지 알려줍니다. 아래.

GUI 클라이언트 중에서 실제로 "예, 가입할 수 있습니다"라는 말은 유일하게 사실입니다. PartiQL과 AWS 자체 NoSQL Workbench — 해당 작업 빌더가 단일 테이블 작업 및 PartiQL 문을 실행합니다. (JOIN 없음, 다중 테이블 SELECT 없음) — 대부분과 마찬가지로 둘 다 단일 테이블 벽에서 중지됩니다. 다른 GUI 클라이언트. DynoTable이 어떻게 다른지 비교해보세요 DynamoDB GUI.

FAQ

PartiQL은 JOIN을 지원합니까? 아니요. PartiQL의 SELECT는 단일 테이블(또는 해당 인덱스 중 하나)을 읽습니다. 에이 다중 테이블 쿼리가 ValidationException을 반환합니다. 단일 테이블에서만 선택하거나 인덱스가 지원됩니다. 나머지 API와 동일한 벽입니다.

하나의 쿼리로 두 개의 DynamoDB 테이블을 조인할 수 있습니까? 기본적으로는 아닙니다. DynamoDB API에는 두 개의 테이블을 읽고 일치하는 명령문이 없습니다. 열쇠에. BatchGetItem는 한 번의 요청으로 여러 테이블의 항목을 읽을 수 있습니다. 하지만 ON 조건은 없습니다. 기본 키로 이름을 지정한 항목을 반환하고 매칭은 당신에게 맡깁니다. 실제 JOIN … ON …은 DynamoDB 외부에서만 발생합니다. 앱 또는 DynoTable의 SQL Workbench에서.

테이블을 GSI에 조인할 수 있나요? 아니요 — Global Secondary Index는 별도의 테이블이 아닙니다. 가입하다; 이는 동일한 항목에 대한 대체 키 보기입니다. 당신도 Query 둘 중 하나 테이블 또는 주어진 SELECT의 인덱스. 둘 다 함께 결합되지는 않습니다. GSI 다른 키를 사용하여 항목에 reach 접근할 수 있으므로 종종 먼저 가입하세요.

두 개의 AWS 계정(또는 다른 계정의 두 테이블)에 걸쳐 조인할 수 있습니까? 기본적으로는 아닙니다. 교차 계정 조인 기본 요소가 없습니다. BatchGetItem 도달 가능 해당 테이블의 리소스 기반 정책이 호출자에게 권한을 부여하는 경우 다른 계정의 테이블 액세스(2024년 3월 이후)했지만 여전히 ON 조건이 없으므로 조인이 아닌 다중 테이블 읽기. 당신은 각 측면을 읽고 그 결과를 당신의 애플리케이션이나 DynoTable의 Workbench와 같은 도구를 사용하세요.

비정규화가 조인보다 정말 나은가요? DynamoDB의 대상 워크로드(예측 가능한 대용량 읽기)의 경우 그렇습니다. 당신은 움직인다 시간을 쓰는 대가로(일부 데이터 복제를 허용하는 데 드는 비용) 균일하게 확장되는 단일 요청 읽기. 는 single-table design 가이드에서는 장단점을 다룹니다.


이러한 읽기를 위한 키와 조건을 수동으로 구축하는 것은 까다롭습니다. expression builder는 다음을 생성합니다. KeyConditionExpression / FilterExpression 구문, 그리고 DynoTable은 해결 방법으로 문제가 해결되지 않을 때 실제 SQL을 실행합니다.

_PartiQL 거부 메시지는 DynamoDB 로컬(us-east-1, @aws-sdk/client-dynamodb v3.1096.0), ValidationException.message._에서 그대로 인용됨

업데이트됨