기존 번들 서비스용 App Engine Datastore API

이 문서에서는 Datastore에 저장된 객체의 데이터 모델, API를 사용하여 쿼리를 구성하는 방법, 트랜잭션을 처리하는 방법을 설명합니다. datastore 패키지의 콘텐츠를 보려면 datastore 패키지 참조를 확인하세요.

항목

Datastore의 객체를 항목이라고 합니다. 항목에는 명명된 속성이 한 개 이상 있으며 각 속성에는 값이 한 개 이상 있을 수 있습니다. 속성 값은 정수, 부동 소수점 수, 문자열, 날짜, 바이너리 데이터 등 다양한 데이터 유형에 속할 수 있습니다. 값이 여러 개인 속성에 대한 쿼리는 쿼리 기준을 충족하는 값이 하나라도 있는지 여부를 테스트합니다. 따라서 이러한 속성은 멤버십 테스트에 유용합니다.

종류, 키, 식별자

각 Datastore 항목은 쿼리 목적으로 항목을 분류하는 특정 종류에 속합니다. 예를 들어 인사 관리 애플리케이션은 회사의 각 직원을 종류가 Employee인 항목으로 나타낼 수 있습니다. 또한 각 항목에는 해당 항목을 고유하게 식별하는 자체 가 있습니다. 키는 다음 구성요소로 구성됩니다.

  • 항목의 종류
  • 식별자(다음 중 하나일 수 있음)
    • 키 이름 문자열
    • 정수 ID
  • Datastore 계층 구조에서 항목 위치를 나타내는 선택적 상위 경로

식별자는 항목이 생성될 때 할당됩니다. 식별자는 항목 키의 일부이므로 항목과 영구적으로 연관되며 변경할 수 없습니다. 식별자는 다음의 두 가지 방법으로 할당할 수 있습니다.

  • 애플리케이션에서 항목의 키 이름 문자열을 직접 지정합니다.
  • Datastore에서 항목에 정수 숫자 ID를 자동으로 할당하도록 할 수 있습니다.

식별자 할당

두 가지 자동 ID 정책을 사용하여 자동으로 ID를 생성하도록 런타임 서버를 구성할 수 있습니다.

  • default 정책은 거의 균일하게 배포되는 ID 시퀀스를 생성합니다. 각 ID는 최대 16자리 숫자입니다.
  • legacy 정책은 연속되지 않는 더 작은 정수 ID 시퀀스를 만듭니다.

사용자에게 또는 주문에 따라 항목 ID를 표시하려면 수동으로 할당하는 것이 가장 좋습니다.

상위 경로

Cloud Datastore의 항목은 파일 시스템의 디렉터리 구조와 유사한 계층 구조 형식의 공간을 형성합니다. 항목을 만들 때 선택적으로 다른 항목을 상위 요소로 지정할 수 있으며, 새 항목은 상위 항목의 하위 요소가 됩니다. 파일 시스템과 달리 상위 항목이 실제로 존재할 필요는 없습니다. 상위 요소가 없는 항목은 루트 항목입니다. 항목과 상위 요소 간의 연결은 영구적이며 항목이 생성되면 변경할 수 없습니다. Cloud Datastore는 상위 요소가 동일한 2개의 항목 또는 2개의 루트 항목(상위 요소가 없는 항목)에 동일한 숫자 ID를 할당하지 않습니다.

항목의 상위 요소, 상위 요소의 상위 요소 등은 재귀적으로 해당 항목의 상위가 되고, 항목의 하위 요소, 하위 요소의 하위 요소 등은 해당 항목의 하위가 됩니다. 루트 항목과 그 하위에 있는 모든 항목은 동일한 항목 그룹에 속합니다. 루트 항목에서 시작하여 상위 요소에서 하위 요소로 진행되고 지정된 항목까지 이어지는 항목의 시퀀스를 해당 항목의 상위 경로라고 합니다. 항목을 식별하는 전체 키는 상위 경로를 지정하고 항목 자체로 종료되는 종류-식별자 쌍의 시퀀스로 구성됩니다.

[Person:GreatGrandpa, Person:Grandpa, Person:Dad, Person:Me]

루트 항목의 경우 상위 경로는 비어 있으며 키는 항목의 고유한 종류 및 식별자로만 구성됩니다.

[Person:GreatGrandpa]

이 개념은 다음 다이어그램에서 설명합니다.

항목 그룹에서 루트 항목과 하위 항목의 관계 표시

쿼리 및 색인

애플리케이션은 키를 사용하여 직접 Datastore의 항목을 검색할 뿐만 아니라 속성 값을 사용하여 항목을 검색하는 쿼리를 수행할 수 있습니다. 쿼리는 지정된 종류의 항목을 대상으로 작동하며, 항목의 속성 값, 키, 상위 항목에 필터를 지정하고 0개 이상의 항목을 결과로 반환할 수 있습니다. 또한 정렬 순서를 지정하여 결과를 속성 값에 따라 순차적으로 배열할 수 있습니다. 결과에는 필터와 정렬 순서에 명시된 모든 속성에 대한 값이 하나 이상 있으며 해당 속성 값이 지정된 필터 기준을 모두 충족하는 모든 항목이 포함됩니다. 쿼리는 전체 항목 또는 예상 항목을 반환하거나 항목 만 반환할 수도 있습니다.

일반적인 쿼리에는 다음이 포함됩니다.

  • 쿼리가 적용되는 항목 종류
  • 항목의 속성 값, 키, 상위 요소를 기반으로 하는 0개 이상의 필터
  • 결과를 순차적으로 배열하는 0개 이상의 정렬 순서
쿼리가 실행되면 해당 종류의 모든 항목 중 지정된 필터를 모두 충족하는 항목이 지정된 정렬 순서에 따라 검색됩니다. 쿼리는 읽기 전용으로 실행됩니다.

참고: 메모리를 절약하고 성능을 개선하려면 가능한 경우 쿼리가 반환하는 결과 수를 제한해야 합니다.

지정된 상위 항목으로부터 이어지는 항목 그룹으로만 결과를 제한하는 상위 필터가 쿼리에 포함될 수도 있습니다. 이러한 쿼리를 상위 쿼리라고 합니다. 기본적으로 상위 쿼리는 데이터의 최신 변경사항을 항상 반영하는 strong consistency를 가진 결과를 반환합니다. 반면 상위 쿼리가 아닌 쿼리는 단일 항목 그룹보다는 전체 Datastore를 대상으로 하지만 eventual consistency만 갖도록 보장하므로 오래된 결과를 반환할 수도 있습니다. 강력한 일관성이 중요한 애플리케이션의 경우, 데이터를 구조화할 때 이를 고려하여 서로 관련된 항목을 동일한 항목 그룹에 배치해야 합니다. 이렇게 해야 비상위 쿼리가 아닌 상위 쿼리로 관련 항목을 검색할 수 있습니다.

App Engine은 항목의 각 속성에 대한 간단한 색인을 사전 정의합니다. App Engine 애플리케이션은 index.yaml이라는 색인 구성 파일에서 커스텀 색인을 추가로 정의할 수 있습니다. 개발 서버는 기존 색인으로 실행할 수 없는 쿼리를 발견하면 이 파일에 자동으로 제안 항목을 추가합니다. 애플리케이션을 업로드하기 전에 파일을 수정하여 색인을 수동으로 조정할 수 있습니다.

참고: 색인 기준 쿼리 메커니즘은 다양한 쿼리를 지원하고 대부분의 애플리케이션에 적합하지만 다른 데이터베이스 기술에서 일반적으로 사용되는 몇몇 종류의 쿼리는 지원하지 않습니다. 특히 조인 및 집계 쿼리는 Datastore 쿼리 엔진에서 지원되지 않습니다. Datastore 쿼리의 제한사항은 Datastore 쿼리 페이지를 참조하세요.

트랜잭션

항목을 삽입, 업데이트, 삭제하려는 모든 시도는 트랜잭션의 컨텍스트에서 수행됩니다. 단일 트랜잭션에 포함할 수 있는 이러한 작업의 수에는 제한이 없습니다. 데이터의 일관성을 유지하기 위해 트랜잭션은 포함된 모든 작업을 Datastore에 하나의 단위로 적용합니다. 즉, 작업이 하나라도 실패하면 어떠한 작업에도 적용되지 않습니다.

단일 트랜잭션 내의 항목을 대상으로 여러 작업을 수행할 수 있습니다. 예를 들어 객체의 카운터 필드를 증가시키려면 카운터 값을 읽고 새 값을 계산한 후 다시 저장해야 합니다. 트랜잭션을 사용하지 않으면 값을 읽은 후 업데이트하기 전에 다른 프로세스가 카운터를 증가시킬 수도 있으며, 이러한 경우 애플리케이션은 업데이트된 값을 덮어쓰게 됩니다. 단일 트랜잭션으로 읽기, 계산, 쓰기를 수행하면 값을 증가시킬 때 다른 프로세스가 방해할 수 없습니다.

트랜잭션 및 항목 그룹

트랜잭션 내에서는 상위 쿼리만 허용됩니다. 즉, 각 트랜잭션 쿼리는 단일 항목 그룹으로 제한되어야 합니다. 트랜잭션 자체는 여러 항목에 적용될 수 있습니다. 이러한 항목은 단일 항목 그룹에 속할 수도 있고, 교차 그룹 트랜잭션의 경우 최대 25개의 서로 다른 항목 그룹에 속할 수도 있습니다.

Datastore는 낙관적 동시 실행을 사용하여 트랜잭션을 관리합니다. 두 개 이상의 트랜잭션이 기존 항목을 업데이트하거나 새 항목을 생성하면서 동시에 같은 항목 그룹을 변경하려고 시도하면 가장 먼저 커밋하는 트랜잭션이 성공하고 다른 트랜잭션은 모두 커밋에 실패합니다. 다른 트랜잭션은 업데이트된 데이터를 대상으로 재시도할 수 있습니다. 이로 인해 특정 항목 그룹의 모든 항목에 대해 수행할 수 있는 동시 쓰기 수가 제한됩니다.

교차 그룹 트랜잭션

서로 다른 항목 그룹에 속하는 여러 항목에 대한 트랜잭션을 교차 그룹(XG) 트랜잭션이라고 합니다. 트랜잭션은 최대 25개의 항목 그룹에 걸쳐 적용될 수 있으며, 적용되는 항목 그룹 중 동시 트랜잭션이 발생하는 그룹이 하나도 없다면 트랜잭션이 성공합니다. 따라서 이질적인 여러 데이터를 같은 상위 항목에 속하도록 배치하지 않아도 원자적 쓰기를 수행할 수 있으므로 데이터를 보다 유연하게 구성할 수 있습니다.

단일 그룹 트랜잭션의 경우와 같이 XG 트랜잭션에서는 비상위 쿼리를 수행할 수 없습니다. 그러나 서로 다른 여러 항목 그룹을 대상으로 상위 쿼리를 수행할 수 있습니다. 트랜잭션이 아닌 비상위 쿼리는 이전에 커밋된 트랜잭션의 결과를 모두 인식하거나, 일부만 인식하거나, 전혀 인식하지 못할 수도 있습니다. (이 문제에 대한 자세한 내용은 Datastore 쓰기 및 데이터 가시성을 참조하세요.) 그러나 트랜잭션이 아닌 쿼리는 부분적으로 커밋된 단일 그룹 트랜잭션의 결과보다는 부분적으로 커밋된 XG 트랜잭션의 결과를 인식할 가능성이 더 높습니다.

단일 항목 그룹에만 적용되는 XG 트랜잭션은 단일 그룹에 대한 비XG 트랜잭션과 성능 및 비용이 동일합니다. 여러 항목 그룹에 적용되는 XG 트랜잭션의 작업 비용은 비XG 트랜잭션을 수행할 때와 동일하지만 지연 시간은 더 길 수 있습니다.

Datastore 쓰기 및 데이터 가시성

데이터는 두 단계를 거쳐 Datastore에 기록됩니다.

  1. 커밋 단계에서는 대다수 복제본의 트랜잭션 로그에 항목 데이터가 기록되고, 항목 데이터가 기록되지 않은 복제본은 최신 로그가 없는 것으로 표시됩니다.
  2. 적용 단계는 각 복제본에서 독립적으로 발생하며 두 가지 작업이 병렬로 수행됩니다.
    • 항목 데이터가 해당 복제본에 기록됩니다.
    • 항목의 색인 행이 해당 복제본에 기록됩니다. 데이터 자체를 기록할 때보다 시간이 더 오래 걸릴 수 있습니다.

쓰기 작업은 커밋 단계 직후에 반환됩니다. 적용 단계는 비동기식으로 진행되며, 복제본마다 서로 다른 시간에 진행될 수 있고 커밋 단계가 완료된 후 수백 밀리초 이상 지연될 수 있습니다. 커밋 단계 중에 오류가 발생하면 자동으로 재시도하지만, 오류가 계속되면 Datastore에서 오류 메시지를 반환하고 애플리케이션은 이 오류를 예외로 수신합니다. 커밋 단계는 성공했지만 특정 복제본에서 적용에 실패한 경우 다음 중 하나가 발생할 때 적용 단계가 완료로 롤포워드됩니다.

  • 주기적인 Datastore 정리 작업 시 미완료 커밋 작업을 확인하여 적용합니다.
  • 영향을 받는 항목 그룹을 사용하는 특정 작업(get, put, delete, 상위 쿼리)에서 새 작업을 진행하기 전에 수행한 변경사항이 커밋되었으나 해당 작업이 실행되는 복제본에 적용이 완료되지 않았습니다.

이 쓰기 동작은 커밋 단계와 적용 단계의 여러 부분에서 애플리케이션에 데이터가 표시되는 시점과 방식에 여러 가지 영향을 줄 수 있습니다.

  • 쓰기 작업이 제한 시간 오류를 보고하는 경우 데이터 읽기를 시도하지 않으면 작업의 성공 여부를 확인할 수 없습니다.
  • Datastore 가져오기 및 상위 쿼리는 해당 쿼리가 실행되는 복제본의 미완료 수정사항을 모두 적용하므로, 이러한 작업은 이전에 성공한 모든 트랜잭션을 항상 일관적으로 인식합니다. 즉, 업데이트된 항목을 키로 조회하는 get 작업은 항상 해당 항목의 최신 버전을 표시합니다.
  • 비상위 쿼리는 최신 트랜잭션이 아직 적용되지 않은 복제본에서 실행될 수도 있으므로 오래된 결과를 반환할 수 있습니다. 쿼리가 이전 작업과 다른 복제본에서 실행될 수 있으므로 미완료 트랜잭션을 항상 적용하는 작업을 수행했더라도 이 문제가 발생할 수 있습니다.
  • 동시 변경의 타이밍이 비상위 쿼리의 결과에 영향을 줄 수 있습니다. 항목이 처음에는 쿼리를 충족했지만 이후에 변경되어 쿼리를 충족하지 못하는 경우, 쿼리가 실행된 복제본의 색인에 변경사항이 아직 적용되지 않았다면 쿼리의 결과 집합에 해당 항목이 여전히 포함될 수 있습니다.

Datastore 통계

Datastore는 지정된 종류의 항목 수 또는 지정된 유형의 속성 값에서 사용된 공간 등 애플리케이션에 저장된 데이터에 대한 통계를 유지합니다. Google Cloud 콘솔 Datastore 대시보드 페이지에서 이러한 통계를 볼 수 있습니다. 또한 애플리케이션에서 Cloud Datastore API를 사용하여 특정 이름의 항목을 쿼리하면 프로그래매틱 방식으로 이러한 값에 액세스할 수 있습니다. 자세한 내용은 Go 1.11의 Datastore 통계를 참조하세요.

Go 1.11 Datastore 예시

Go에서는 Datastore 항목이 struct 값으로 생성됩니다. 구조체의 필드는 항목의 속성이 됩니다. 새 항목을 만들려면 저장할 값을 설정하고, 키를 만들고, 둘 다 datastore.Put()에 전달합니다. 기존 항목을 업데이트하려면 동일한 키를 사용하여 다른 Put()을 수행해야 합니다. Datastore에서 항목을 검색하려면 우선 항목의 마셜링을 취소할 값을 설정한 후 datastore.Get()에 키 및 해당 값을 가리키는 포인터를 전달합니다.

이 예시에서는 Datastore에서 일부 데이터를 저장하고 검색합니다.

import (
	"fmt"
	"net/http"
	"time"

	"google.golang.org/appengine"
	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/user"
)

type Employee struct {
	Name     string
	Role     string
	HireDate time.Time
	Account  string
}

func handle(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)

	e1 := Employee{
		Name:     "Joe Citizen",
		Role:     "Manager",
		HireDate: time.Now(),
		Account:  user.Current(ctx).String(),
	}

	key, err := datastore.Put(ctx, datastore.NewIncompleteKey(ctx, "employee", nil), &e1)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	var e2 Employee
	if err = datastore.Get(ctx, key, &e2); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	fmt.Fprintf(w, "Stored and retrieved the Employee named %q", e2.Name)
}

자세한 내용은 Datastore 참조를 확인하세요.