Cloud Datastore 권장사항

Datastore를 사용하는 애플리케이션을 빌드할 때 여기에 나열된 권장사항을 빠른 참조로 활용할 수 있습니다. 이 페이지는 Datastore의 기본적인 사용법을 안내하지 않으므로 Datastore를 시작한 지 얼마 안 되는 사용자에게는 적합하지 않을 수 있습니다. 신규 사용자라면 Datastore 시작하기부터 살펴보는 것이 좋습니다.

일반

  • 네임스페이스 이름, 종류 이름, 속성 이름, 커스텀 키 이름에는 항상 UTF-8 문자를 사용합니다. 이러한 이름에 UTF-8 이외의 문자를 사용하면 Datastore 기능에 지장을 줄 수 있습니다. 예를 들어 속성 이름에 UTF-8 이외의 문자를 사용하면 속성을 사용하는 색인이 생성되지 않을 수 있습니다.
  • 종류 이름 또는 커스텀 키 이름에 슬래시(/)를 사용하지 마세요. 이러한 이름에 슬래시를 사용하면 추후 기능에 지장을 줄 수 있습니다.
  • Cloud Project ID에 민감한 정보를 저장하지 않도록 유의하세요. Cloud Project ID는 프로젝트 수명 이후에도 보관될 수 있습니다.
  • 데이터 규정 준수 권장사항에 따라 Datastore 항목 이름 또는 항목 속성 이름에 민감한 정보를 저장하지 않는 것이 좋습니다.

API 호출

  • 읽기, 쓰기, 삭제 시 단일 작업 대신 일괄 작업을 사용하세요. 일괄 작업은 단일 작업과 동일한 오버헤드로 여러 작업을 수행하기 때문에 보다 효율적입니다.
  • 트랜잭션이 실패하면 트랜잭션 롤백을 시도하세요. 롤백은 트랜잭션 중인 동일한 리소스를 두고 경쟁하는 다른 요청의 재시도 지연 시간을 최소화합니다. 하지만 롤백 자체도 실패할 수 있으므로 한 번 시도해 보는 것에 의미를 둬야 합니다.
  • 가능하다면 동기식 호출 대신 비동기식 호출을 사용하세요. 비동기식 호출은 지연 시간 영향을 최소화합니다. 예를 들어 응답을 렌더링하기 전에 쿼리 결과와 동기식 lookup() 결과가 필요한 애플리케이션을 생각해 보세요. lookup() 및 쿼리에 데이터 종속 항목이 없다면 쿼리를 시작하기 전에 lookup()이 완료될 때까지 동기식으로 대기할 필요가 없습니다.

항목

  • 관련성이 높은 데이터는 항목 그룹으로 그룹화하세요. 항목 그룹은 상위 쿼리를 사용 설정하여 strong consistency를 가지는 결과를 반환합니다. 또한 항목 그룹의 항목은 Datastore 서버에서 물리적으로 가까운 곳에 저장되기 때문에 상위 쿼리를 수행하면 최소한의 I/O로 항목 그룹을 신속하게 스캔할 수 있습니다.
  • 항목 그룹 쓰기 속도가 초당 1회를 넘지 않도록 유의하세요. 쓰기 속도가 계속해서 한도를 초과할 경우 읽기의 eventual consistency 확보 시간이 지연되어 strongconsistency 읽기 시간이 초과되고 애플리케이션의 전반적인 성능이 낮아지는 결과로 이어집니다. 항목 그룹의 배치 또는 트랜잭션 쓰기는 이 한도에서 단일 쓰기로 집계됩니다.
  • 동일한 항목(키별)을 동일한 커밋에 여러 번 포함하지 마세요. 동일한 항목을 동일한 커밋에 여러 번 포함하면 Datastore 지연 시간이 영향을 받을 수 있습니다.

  • 키 이름을 항목 생성 시 제공하지 않으면 자동으로 생성됩니다. 이름은 키스페이스에 균등하게 배포되도록 할당됩니다.
  • 커스텀 이름을 사용하는 키의 경우 항상 슬래시(/)를 제외하고 UTF-8 문자를 사용합니다. UTF-8 이외의 문자는 Datastore 백업을 Google BigQuery로 가져오기와 같은 다양한 프로세스에 지장을 줍니다. 슬래시는 추후 기능에 지장을 줄 수 있습니다.
  • 숫자 ID를 사용하는 키의 경우:
    • ID에 음수를 사용하지 마세요. 음수 ID는 정렬에 지장을 줄 수 있습니다.
    • ID에 0(영) 값을 사용하지 마세요. 이 값을 사용하면 ID가 자동으로 할당됩니다.
    • 항목을 만들 때 원하는 숫자 ID를 수동으로 할당하려면 allocateIds() 메서드를 통해 애플리케이션에 ID 블록을 할당합니다. 그러면 Datastore에서 수동 숫자 ID를 다른 항목에 할당하지 않습니다.
  • 항목을 만들 때 고유한 숫자 ID 또는 커스텀 이름을 할당하려면 다음과 같은 단조 증가 값을 사용하지 마세요.

    1, 2, 3, …,
    "Customer1", "Customer2", "Customer3", ….
    "Product 1", "Product 2", "Product 3", ….
    

    애플리케이션에서 순차 번호 지정과 같은 대량의 트래픽이 생성되면 핫스팟이 발생하여 Datastore 지연 시간이 영향을 받을 수 있습니다. 연속된 숫자 ID 발급을 방지하려면 allocateIds() 메서드로 번호 ID를 가져옵니다. allocateIds() 메서드는 숫자 ID를 적절히 분포된 순서로 생성합니다.

  • 키를 지정하거나 생성된 이름을 저장하면 나중에 항목을 찾는 쿼리를 수행할 필요 없이 관련 항목에 일관된 lookup()을 수행할 수 있습니다.

색인

  • 속성이 쿼리에 필요하지 않은 경우 색인에서 속성을 제외하세요. 속성에 불필요한 색인으로 인해 일관성 확보 시간이 지연되고 색인 항목의 스토리지 비용이 증가합니다.
  • 복합 색인이 지나치게 많지 않도록 하세요. 복합 색인을 과도하게 사용하면 일관성 확보 시간이 지연되고 색인 항목의 스토리지 비용이 증가합니다. 사전 정의된 색인 없이 대량의 데이터세트에 대한 임시 쿼리를 실행해야 하는 경우에는 Google BigQuery를 사용합니다.
  • 단조 증가 값(예: NOW() 타임스탬프)으로 속성 색인을 생성하지 마세요. 이러한 색인을 유지하면 핫스팟이 발생하여 읽기 및 쓰기 속도가 빠른 애플리케이션의 Datastore 지연 시간이 영향을 받을 수 있습니다. 단조로운 속성을 처리하는 방법에 대한 자세한 내용은 아래의 좁은 키 범위에 대한 빠른 읽기/쓰기 속도를 참조하세요.

속성

  • 문자열 유형의 속성에는 항상 UTF-8 문자를 사용하세요. 문자열 유형의 속성에 UTF-8 이외의 문자를 사용하면 쿼리에 지장을 줄 수 있습니다. UTF-8 이외의 문자가 포함된 데이터를 저장해야 하는 경우에는 바이트 문자열을 사용합니다.
  • 속성 이름에 구두점을 사용하지 마세요. 속성 이름에 구두점이 있으면 포함된 항목 속성의 색인 생성이 지장을 받습니다.

쿼리

  • 쿼리 결과 중 키에만 액세스하면 되는 경우에는 키 전용 쿼리를 사용하세요. 키 전용 쿼리는 전체 항목을 검색하는 것보다 낮은 지연 시간과 비용으로 결과를 반환합니다.
  • 항목에서 특정 속성에만 액세스하면 되는 경우에는 프로젝션 쿼리를 사용하세요. 프로젝션 쿼리는 전체 항목 검색보다 낮은 지연 시간과 비용으로 결과를 반환합니다.
  • 마찬가지로 쿼리 필터에 포함된 속성(예: order by 절에 나열된 속성)에만 액세스해야 하는 경우에는 프로젝션 쿼리를 사용합니다.
  • 오프셋 대신 커서를 사용하세요. 오프셋을 사용하면 건너뛴 항목이 애플리케이션에 반환되는 것을 방지할 수 있지만 이러한 항목은 여전히 내부적으로 검색됩니다. 건너뛴 항목은 쿼리의 지연 시간에 영향을 미치며 검색에 필요한 읽기 작업에 해당되는 비용이 애플리케이션에 청구됩니다.
  • 쿼리에 강력한 일관성이 필요한 경우에는 상위 쿼리를 사용하세요. 상위 쿼리를 사용하려면 먼저 strong consistency을 갖도록 데이터를 구조화해야 합니다. 상위 쿼리는 strong consistency를 가지는 결과를 반환합니다. 비상위 키 전용 쿼리에 이어 lookup()을 수행하면 강력한 결과가 반환되지 않습니다. 비상위 키 전용 쿼리가 쿼리 당시 일관되지 않은 색인에서 결과를 가져올 수 있기 때문입니다.

규모 확장을 위한 설계

단일 항목 그룹 업데이트

Datastore의 단일 항목 그룹을 너무 빠르게 업데이트하면 안 됩니다.

Datastore를 사용할 경우 항목 그룹이 초당 1회를 초과해 업데이트되지 않도록 애플리케이션을 설계하는 것이 좋습니다. 상위 및 하위 계층이 없는 항목은 그 자체가 항목 그룹이라는 사실을 기억하세요. 항목 그룹을 너무 빨리 업데이트하면 Datastore 쓰기의 지연 시간, 제한 시간 및 기타 유형의 오류가 늘어납니다. 이를 경합이라고 부릅니다.

단일 항목 그룹에 대한 Datastore 쓰기 속도가 초당 1회 한도를 초과해도 로드 테스트 시 이 문제가 나타나지 않을 수도 있습니다.

좁은 키 범위에 대한 빠른 읽기/쓰기 속도

사전순으로 가까운 Datastore 키에 대한 읽기 또는 쓰기 속도가 빨라지면 안 됩니다.

Datastore는 Google의 NoSQL 데이터베이스인 Bigtable을 기반으로 하므로 Bigtable 성능 특성의 영향을 받습니다. Bigtable은 분리된 태블릿으로 행을 샤딩하는 방식으로 확장하며 이러한 행은 키의 사전순으로 정렬됩니다.

Datastore를 사용하는 경우, 작은 범위의 키에 대한 쓰기 속도가 급증하여 단일 태블릿 서버의 용량을 초과하면 핫 태블릿으로 인해 쓰기 속도가 느려질 수 있습니다. Bigtable은 높은 로드를 지원하기 위해 키 공간을 결국 분할하게 됩니다.

단일 키를 매우 높은 속도로 읽는 경우를 제외하고 읽기 한도는 일반적으로 쓰기 한도보다 높습니다. Bigtable이 단일 키를 2개 이상의 태블릿으로 분할하는 것은 불가능합니다.

핫 태블릿은 항목 키와 색인에 모두 사용되는 키 범위에 적용할 수 있습니다.

경우에 따라 좁은 범위의 키에 대한 읽기 또는 쓰기를 방지하는 것보다 Datastore 핫스팟이 애플리케이션에 더 큰 영향을 미칠 수 있습니다. 예를 들어 인스턴스를 시작하는 동안 핫 키에 대한 읽기 또는 쓰기가 이루어지면 로드 요청이 실패할 수 있습니다.

기본적으로 Datastore는 분산형 알고리즘을 사용하여 키를 할당합니다. 따라서 기본 ID 할당 정책을 사용하여 빠른 쓰기 속도로 신규 항목을 생성하면 일반적으로는 Datastore 쓰기에 핫스팟이 발생하지 않습니다. 이러한 문제가 발생하는 일부 특수한 상황은 다음과 같습니다.

  • 기존의 연속 ID 할당 정책을 사용하여 매우 높은 속도로 신규 항목을 생성하는 경우

  • 매우 높은 속도로 신규 항목을 생성하면서 단조 증가하는 자체 ID를 할당하는 경우

  • 예전에 기존 항목이 거의 없었던 종류의 신규 항목을 매우 높은 속도로 생성하는 경우. Bigtable은 모든 항목이 동일한 태블릿 서버에 있는 상태로 시작하며 키 범위를 분리된 태블릿 서버로 분할하는 데 다소 시간이 소요됩니다.

  • 또한 타임스탬프처럼 색인 속성이 단조 증가하는 신규 항목을 높은 속도로 생성하는 경우에도 이 문제가 나타납니다. 이러한 속성은 Bigtable에서 색인 테이블 행의 키이기 때문입니다.

  • Datastore가 Bigtable row key의 앞에 루트 항목 그룹의 네임스페이스와 종류를 추가하는 경우. 점진적으로 트래픽을 늘리지 않고 새 네임스페이스 또는 종류에 쓰기를 시작하면 핫스팟이 발생할 수 있습니다.

키 또는 색인 생성된 속성이 단조 증가한다면 임의의 해시를 앞에 추가하여 키가 여러 태블릿으로 샤딩되도록 만들 수 있습니다.

마찬가지로 정렬이나 필터를 사용하여 단조 증가(또는 감소)하는 속성을 쿼리해야 한다면 새 속성의 색인을 생성하는 방법을 대신 사용할 수 있습니다. 이 경우 데이터세트 전체에서 카디널리티가 높지만 수행하려는 쿼리 범위 내의 모든 항목에 공통된 값을 단조 값 앞에 추가합니다. 예를 들어 타임스탬프를 기준으로 항목을 쿼리하려고 하지만 한 번에 한 명의 사용자에 대한 결과만 필요하다면 타임스탬프 앞에 사용자 ID를 추가하고 이 신규 속성의 색인을 생성하면 됩니다. 이렇게 하면 사용자에 대한 쿼리 및 정렬된 결과를 계속 얻을 수 있고 사용자 ID가 있으므로 색인 자체가 올바르게 샤딩됩니다.

이 문제에 대한 자세한 내용은 Datastore에서 단조 증가하는 값을 저장하는 방법에 대한 Ikai Lan의 블로그 게시물을 참조하세요.

트래픽 늘리기

키스페이스의 신규 Datastore 종류 또는 부분에 대한 트래픽을 점진적으로 늘리세요.

Bigtable이 트래픽 증가에 따라 태블릿을 분할할 수 있는 시간을 충분히 확보하려면 새로운 Datastore 종류에 대한 트래픽을 점진적으로 늘려야 합니다. 새로운 Datastore 종류인 경우에는 최대 작업 수를 초당 500개로 제한하고 5분마다 50%씩 트래픽을 늘리는 것이 좋습니다. 이 증가 일정에 따르면 이론상으로 90분 후에는 초당 작업 수를 740,000개로 늘릴 수 있습니다. 쓰기 작업이 키 범위 전반에 걸쳐 비교적 균등하게 분산되도록 만드세요. Google SRE에서는 이 법칙을 '500/50/5' 법칙이라 합니다.

이러한 점진적인 증가 패턴은 A 종류 사용을 중단하고 대신 B 종류를 사용하도록 코드를 변경하는 경우에 특히 중요합니다. 단편적으로 보면 B 종류를 읽고 B 종류가 없으면 A 종류를 읽도록 코드를 변경하면 이 마이그레이션을 처리할 수 있다고 생각할 수도 있습니다. 그러나 이렇게 하면 키스페이스 영역이 매우 좁아지므로 새로운 종류에 트래픽이 급증할 수 있습니다. 키스페이스가 부족하면 Bigtable이 효과적으로 태블릿을 분할하지 못할 수 있습니다.

동일한 종류 내에서 다양한 범위의 키를 사용하도록 항목을 마이그레이션하는 경우에도 동일한 문제가 발생할 수 있습니다.

새로운 종류 또는 키로 항목을 마이그레이션하기 위해 사용하는 전략은 데이터 모델에 따라 달라집니다. 아래에서는 '병렬 읽기'로 알려진 예제 전략을 소개합니다. 이 전략이 내 데이터에 효과가 있는지 여부는 스스로 판단해야 합니다. 마이그레이션 도중 병렬 작업이 비용에 미치는 영향을 중요하게 고려하세요.

우선 기존 항목 또는 키를 읽습니다. 기존 항목이 없는 경우에는 신규 항목이나 키에서 읽을 수 있습니다. 부재 항목 읽기 속도가 높으면 핫스팟으로 이어질 수 있으므로 점차적으로 로드를 늘려야 합니다. 기존 항목을 신규 항목에 복사한 후 기존 항목을 삭제하는 전략이 바람직합니다. 새로운 키스페이스가 적절히 분산되도록 병렬 읽기를 점차적으로 증가하세요.

신규 종류에 대한 읽기 또는 쓰기를 점차적으로 늘리려면 임의 비율의 사용자가 신규 항목에 쓰기 작업을 수행할 수 있도록 사용자 ID의 확정 해시를 사용하는 전략을 사용할 수 있습니다. 사용자 ID 해시의 결과가 임의 기능 또는 사용자 행동에 의해 왜곡되지 않았는지 확인하세요.

그동안 Dataflow 작업을 실행하여 기존 항목 또는 키의 모든 데이터를 신규 항목 또는 키에 복사하세요. 일괄 작업 수행 시 Bigtable 핫스팟을 방지하려면 연속 키에 쓰기 작업을 수행하면 안 됩니다. 일괄 작업이 완료되면 새로운 위치에서만 읽기가 가능해집니다.

이 전략을 개선하려면 사용자를 한 번에 소량 배치씩 마이그레이션하면 됩니다. 사용자 항목에 사용자의 마이그레이션 상태를 추적하는 필드를 추가하고 사용자 ID의 해시에 근거하여 마이그레이션할 사용자 배치를 선택합니다. 그러면 맵리듀스 또는 Dataflow 작업이 이 사용자 배치의 키를 마이그레이션합니다. 이때 마이그레이션이 진행 중인 사용자는 병렬 읽기를 사용하게 됩니다.

마이그레이션이 진행되는 동안 기존 및 신규 항목 모두에 이중 쓰기를 수행하지 않으면 롤백을 쉽게 수행할 수 없으며, Datastore 비용이 증가하는 원인이 됩니다.

삭제

작은 범위의 키에서 대량의 Datastore 항목을 삭제하지 않도록 유의하세요.

Bigtable은 삭제된 항목을 없애고, 읽기 및 쓰기 효율성이 높아지도록 데이터를 재구성하기 위해 테이블을 주기적으로 재작성합니다. 이 프로세스를 압축이라고 부릅니다.

작은 범위의 키에서 대량의 Datastore 항목을 삭제하면 압축이 완료될 때까지 이 부분의 색인에 대한 쿼리 속도가 느려집니다. 최악의 경우에는 결과를 반환하기 전에 쿼리 시간이 초과될 수 있습니다.

항목의 만료 시간을 나타내기 위해 색인된 필드에 타임스탬프 값을 사용하는 이 패턴은 되도록 피해야 합니다. 만료된 항목을 검색하려면 이 색인 필드에 대해 쿼리를 수행해야 합니다. 이 필드는 키스페이스의 일부가 가장 최근 삭제한 항목의 색인 항목과 겹친 상태로 상주할 가능성이 높습니다.

만료 타임스탬프 앞에 고정 길이 문자열을 추가하는 '분할 쿼리'를 실행하면 성능을 향상시킬 수 있습니다. 동일한 타임스탬프의 항목이 색인의 키 범위에 걸쳐 검색되도록 전체 문자열에 대해 색인이 정렬됩니다. 각 분할에서 결과를 가져오려면 여러 쿼리를 병행해서 실행하세요.

만료 타임스탬프 문제를 보다 만족스럽게 해결하는 방법은 주기적으로 업데이트되는 글로벌 카운터인 '세대 번호'를 사용하는 것입니다. 세대 번호는 만료 타임스탬프 앞에 추가되므로 쿼리는 세대 번호, 분할, 타임스탬프 순의 기준으로 정렬됩니다. 기존 항목의 삭제는 이전 세대에서 이루어집니다. 삭제되지 않은 모든 항목의 세대 번호는 증분되어야 합니다. 삭제가 완료되면 다음 세대로 이동하세요. 압축이 완료될 때까지 이전 세대에 대한 쿼리는 저조한 성능을 보입니다. 최종 일관성으로 인한 결과 누락의 위험성을 줄이려면 삭제할 항목 목록을 가져오는 색인 쿼리가 이루어지기 전에 여러 세대가 완료되기를 기다려야 할 수 있습니다.

샤딩 및 복제

핫 Datastore 키에는 샤딩이나 복제를 사용하세요.

Bigtable에서 허용하는 것보다 높은 속도로 키 범위 영역을 읽어야 한다면 복제를 사용하면 됩니다. 이 전략을 사용하면 동일한 항목의 N개 사본을 저장할 때 단일 항목에서 지원하는 것보다 읽기 속도가 N배 높아지게 됩니다.

Bigtable에서 허용하는 것보다 높은 속도로 키 범위의 부분에 쓰기 작업을 수행해야 하는 경우에는 분할을 사용할 수 있습니다. 샤딩은 항목을 작은 부분으로 나눕니다.

샤딩 시 일반적인 실수에는 다음이 해당됩니다.

  • 시간 프리픽스를 사용한 분할. 시간이 다음 프리픽스로 넘어가면 분할되지 않은 새로운 부분이 핫스팟이 됩니다. 대신 쓰기 작업의 부분을 신규 프리픽스로 점차적으로 롤오버해야 합니다.

  • 가장 사용률이 높은 항목만 분할. 총 항목 중 소량의 일부만 샤딩하면 사용률이 높은 항목 간의 행이 부족하여 서로 다른 분할에 상주하게 됩니다.

다음 단계