중급6분 분량

DynamoDB GSI가 최종 일관성을 유지하는 이유

항목을 작성하고 즉시 Global Secondary Index에 쿼리하여 가져옵니다. 아무것도 돌아오지 않음 — 쓰기가 성공하고 기본 테이블이 GetItem인 경우에도 물건을 잘 돌려받았습니다.

아무것도 깨지지 않았습니다. GSI의 가장 놀라운 특성인 읽을 때마다 GSI의 결과는 일관성이 있습니다. 쓰기 후에 짧은 창이 나타납니다. 지수는 아직 따라잡지 못했습니다.

DynamoDB GSI는 최종 일관성을 유지합니까?

예 — Global Secondary Index의 모든 읽기는 결과적으로 일관성이 있습니다. 선택 해제 방법. 쓰기는 먼저 기본 테이블에 커밋된 다음 전파됩니다. 인덱스에 비동기적으로, 쓰기 직후에 발행된 는 오래되었거나 누락된 행을 반환합니다. DynamoDB는 GSI에 대해 ConsistentRead 플래그를 제공하지 않습니다.

  • GSI는 별도의 비동기식 복제 테이블입니다 — 쓰기 커밋 먼저 기본 테이블로 이동한 다음 인덱스로 전파합니다.
  • GSI에는 ConsistentRead 플래그가 없습니다. 기본 테이블과 달리 간격을 줄이기 위해 강력한 읽기를 강제합니다.
  • GSI가 아닌 기본 테이블에서 직접 읽기. 당신은 이미 보유하고 쓰기 직후.
  • GSI 쿼리가 아닌 조건부 쓰기로 고유성을 적용합니다. 전파 간격은 "이거 찍혔나요?"로 변합니다. 경주를 확인하세요.

증상 : "자체를 찾을 수 없는" 가입이 뜹니다

사용자 계정 서비스에 대한 Members 테이블을 선택합니다. 기본 테이블의 키는 다음과 같습니다. 내부 ID이지만 사용자는 이메일로 로그인하므로 이메일 조회 GSI가 있습니다.

Members (base table)
PKSKemaildisplayName
ACC#a1f9cPROFILEada@northwind.testAda L.
EmailIndex (GSI)
GSI1PKGSI1SK
ada@northwind.testACC#a1f9c

가입 흐름은 연속적으로 두 가지 작업을 수행합니다. 즉, 새 회원, 그 다음 Query EmailIndex WHERE GSI1PK = "ada@northwind.test" 다른 사람은 확인하지 마세요 해당 주소를 요청하고 프로필을 로드합니다.

이 두 호출을 몇 밀리초 간격으로 실행하면 Query0을 반환할 수 있습니다. 항목. 1초 후에 다시 수행하면 행이 거기에 있습니다. 쓰기는 실패하지 않았습니다 — 색인이 아직 업데이트되지 않았습니다.

이런 일이 발생하는 이유: GSI는 비동기식으로 복제됩니다.

GSI는 자체 파티션과 자신의 키 스키마. 귀하와 동일한 거래 내에서 유지되지 않습니다. 기본 테이블 쓰기.

PutItem하면 DynamoDB는 기본 테이블에 지속적으로 커밋하고 작성한 후 그런 다음 변경사항을 각 GSI에 비동기식으로 전파합니다. AWS GSI documentation 명확하게 설명합니다. GSI는 최종적 일관된 읽기만 지원합니다.

기본 테이블 쓰기와 인덱스 업데이트 간의 전파 지연은 일반적으로 몇 분의 1초 — 그러나 부하 상태에서는 보장되지 않으며 제한되지 않습니다. 제한된 것처럼 디자인하는 것은 함정이다.

이것은 버그가 아닙니다. 이는 원래 Dynamo 디자인의 절충안입니다. 2007년 Amazon Dynamo paper 강력한 일관성보다 가용성과 파티션 허용성을 선택했습니다.

GSI는 해당 계보를 상속받습니다. 느슨한 결합은 인덱스의 확장 및 유지를 가능하게 합니다. 기본 테이블과 독립적으로 쓰기 가능합니다.

EmailIndex기본 테이블AppEmailIndex기본 테이블App비동기 전파PutItem (신규 멤버)200 OK이메일로 Query0개 항목 (오래됨)변경 사항 복제이메일로 Query1개 항목 (동기화됨)

200 OK과 "변경 사항 복제" 사이의 간격이 색인이 생성되는 창입니다. 읽기가 오래되었습니다. 이를 닫는 일관 읽기 플래그가 없습니다.

기본 테이블과 달리 — ConsistentRead = true을 전달하여 GetItem/Query — GSI는 해당 옵션을 단호히 거부합니다.

LSI는 기본 테이블의 파티션을 공유하기 때문에 강력하게 읽을 수 있습니다. 참조 GSI vs LSI 왜 그런 구별이 존재하는지 알아보겠습니다.

GSI의 읽기 비용

GSI 쿼리는 다른 읽기와 마찬가지로 인덱스의 주문형 RCU에 us-east-1를 청구합니다. — 4KB당 0.5RCU 최종 일관성 블록 — 그리고 두 배의 비용을 지불할 수 없습니다. ConsistentRead가 기각되었기 때문에 강한 일관성을 갖습니다. 전파지연은 무료; 인덱스 읽기가 아닙니다. 기본 테이블과 GSI 읽기 속도를 비교하세요. pricing calculator.

더 미묘한 함정: 새로운 값이 누락되는 것이 아니라 오래된 오래된

누락된 행의 경우가 가장 명백합니다. 더 조용한 버그는 오래된 내용을 읽는 것입니다. 이전 값.

Ada가 이메일을 ada@northwind.test에서 ada.l@northwind.test로 변경했다고 가정해 보겠습니다. 는 기본 테이블은 원자적으로 업데이트되지만 잠시 동안 GSI는 계속해서 이전 색인 항목입니다.

새 값에 대한 조회가 누락되었지만 버려진 값은 여전히 확인됩니다.

더 나쁜 점은 GSI에 쿼리하고 읽은 내용을 바탕으로 다시 작성하는 경우 다음과 같은 조치를 취할 수 있다는 것입니다. 더 이상 존재하지 않는 가치. GSI 읽기를 현실에 뒤처질 수 있는 스냅샷으로 취급하세요.

이를 중심으로 디자인 - 싸우지 마세요

전파 창은 실제이므로 수정 사항은 재시도 손잡이가 아닌 구조적입니다. 토글. 4가지 패턴(대략 선호도 순서):

  1. 기본 테이블에서 자신이 쓴 내용을 읽습니다. 쓰기 직후에 이미 기본 키(ACC#a1f9c)를 누르고 있으므로 강력하게 일관된 GetItem를 수행합니다. GSI를 쿼리하는 대신 기본 테이블을 사용합니다.

    GSI는 other 액세스 패턴에 대한 것입니다. — "이메일이 있습니다. 계정을 찾으세요." — 방금 작성한 내용을 확인하기 위한 것이 아닙니다.

  2. GSI가 아닌 가드 항목으로 고유성을 적용합니다. GSI 쿼리를 절대 신뢰하지 마세요. 이메일이 청구되지 않았음을 증명하기 위해 — 전파 격차로 인해 경쟁이 2가 됩니다. 동시 가입은 둘 다 잃을 수 있습니다.

    대신 이메일 자체에 키가 포함된 전용 고유 항목을 작성하세요. (PK = "EMAIL#ada@northwind.test")가 있는 TransactWriteItems 내부 attribute_not_exists(PK)ConditionExpression.

    원자적으로 적용되는 강력하게 일관된 기본 테이블 조건은 실제로 고유성을 강화합니다.

    TransactWriteItems:
      - Put member item    (PK = ACC#a1f9c, SK = PROFILE)
      - Put uniqueness item (PK = EMAIL#ada@northwind.test)
          ConditionExpression: attribute_not_exists(PK)

    두 번째 가입이 동일한 주소로 경합하는 경우 해당 조건은 실패하고 전체 가 거부됩니다. GSI도 없고 전파 지연도 없고 이중 청구도 없습니다.

    다음을 사용하여 attribute_not_exists 조건을 구축하고 미리 봅니다. DynamoDB 표현식 빌더 코드에 연결하기 전에.

  3. UX의 지연을 허용합니다. GSI가 실제로 읽은 것이 올바른 도구인 경우 (기존 사용자의 경우 이메일로 로그인), 창은 1초 미만이며 무해합니다 — 오래 전에 전파된 확립된 계정.

    쓰기 후 읽기 순간을 위해 강력하게 일관된 기본 테이블 경로를 예약합니다. 만.

  4. 가정하지 말고 다시 쿼리하세요. 워크플로에서 새 항목을 관찰해야 하는 경우 GSI에서는 빈 결과를 '아직 표시되지 않음'이 아닌 '존재하지 않음'으로 처리합니다. 짧은 백오프 후에 다시 쿼리하세요.

    그러나 추측을 완전히 제거하는 패턴 1과 2를 선호합니다.

전파 격차를 직접 확인하세요

직관을 키우는 가장 빠른 방법은 그것이 일어나는 것을 지켜보는 것입니다. DynoTable에는 항목을 기본 테이블에 추가하고 두 번째 탭에서 즉시 GSI를 쿼리합니다.

로드된 테이블에서 기본 데이터 뒤의 인덱스를 발견하는 경우가 종종 있습니다. 다음 새로 고침에서 수렴되는 것을 지켜보세요.

자신의 데이터로 지연을 확인하면 "기본에서 자신이 쓴 내용을 읽을 수 있습니다. 테이블" 규칙은 어떤 다이어그램보다 훨씬 더 좋습니다.

함정과 다음 단계

  • GSI 쓰기 후 읽기에서 논리를 게이트하지 마세요. 고유성 확인, "내가 썼나요?" 토지" 확인 및 읽기-수정-쓰기 루프는 강력하게 일관된 기본 테이블.
  • GSI에서 ConsistentRead에 도달하지 마세요 - 허용되지 않으며 오류가 발생합니다.
  • 기본 키가 이미 응답한 경우 액세스 패턴을 GSI로 모델링하지 마세요. 기본 키에서 읽기를 제공하고 전파 창을 완전히 건너뜁니다.

올바른 키 모양을 선택하는 것이 전체 게임입니다. single-table design; Query가 언제 a를 이길지 아는 것 Scan는 애초에 지수에서 벗어나게 해줍니다. (Query vs Scan).

귀하의 독창성을 구축하고 테스트해보세요 DynamoDB Expression Builder. 그런 다음 try DynoTable 기본 테이블 쓰기가 실제로 GSI에 전파되는 것을 보는 것 시간을 들여 최종 일관성 창에 문제가 발생하지 않도록 키를 설계하세요.

업데이트됨