메타데이터

Datastore는 일부 메타데이터에 프로그래매틱 방식으로 액세스 권한을 제공하여 메타프로그래밍을 통해 백엔드 관리 기능을 구현하고 일관된 캐싱을 단순화합니다. 또한 유사한 목적, 예를 들면 애플리케이션에 커스텀 Datastore 뷰어를 빌드하는 데도 사용할 수 있습니다. 사용 가능한 메타데이터에는 항목 그룹, 네임스페이스, 항목 종류, 애플리케이션에서 사용하는 속성에 대한 정보와 각 속성의 속성 표현이 포함됩니다.

Google Cloud console의 Datastore 대시보드도 애플리케이션에 대한 일부 메타데이터를 제공하지만 대시보드에 표시되는 데이터는 몇 가지 중요한 점에서 이러한 함수가 반환하는 데이터와 다릅니다.

  • 최신 정보. API를 사용한 메타데이터 읽기는 최신 데이터를 가져오며 이때 대시보드의 데이터는 매일 한 번씩만 업데이트됩니다.
  • 콘텐츠. 대시보드 메타데이터 중 일부는 API를 통해 제공되지 않으며 반대의 경우도 마찬가지입니다.
  • 속도. 메타데이터 가져오기 및 쿼리는 Datastore 가져오기 및 쿼리와 동일한 방식으로 청구됩니다. 네임스페이스, 종류, 속성에 정보를 가져오는 메타데이터 쿼리는 일반적으로 느린 속도로 실행됩니다. 일반적으로 N개의 항목을 반환하는 하나의 메타데이터 쿼리는 각각 하나의 항목을 반환하는 N개의 일반 쿼리와 비슷한 시간이 걸립니다. 그뿐만 아니라 속성 표현 쿼리(키 전용이 아닌 속성 쿼리)는 키 전용 속성 쿼리보다 느립니다. 항목 그룹 메타데이터의 메타데이터 가져오기는 일반 항목 가져오기보다 다소 빠르게 실행됩니다.

항목 그룹 메타데이터

Cloud Datastore는 항목 그룹의 '버전'에 대한 액세스를 제공합니다. 버전은 항목 그룹이 변경될 때마다 반드시 증가하는 값이며 전적으로 양수입니다.

항목 그룹 버전은 확실히 양수인 __version__ 속성이 포함된 특별한 유사 항목에서 get()을 호출하여 가져올 수 있습니다. Entities.createEntityGroupKey() 메서드를 사용하여 유사 항목 키를 만들 수 있습니다.

private static long getEntityGroupVersion(DatastoreService ds, Transaction tx, Key entityKey) {
  try {
    return Entities.getVersionProperty(ds.get(tx, Entities.createEntityGroupKey(entityKey)));
  } catch (EntityNotFoundException e) {
    // No entity group information, return a value strictly smaller than any
    // possible version
    return 0;
  }
}

private static void printEntityGroupVersions(DatastoreService ds, PrintWriter writer) {
  Entity entity1 = new Entity("Simple");
  Key key1 = ds.put(entity1);
  Key entityGroupKey = Entities.createEntityGroupKey(key1);

  // Print entity1's entity group version
  writer.println("version " + getEntityGroupVersion(ds, null, key1));

  // Write to a different entity group
  Entity entity2 = new Entity("Simple");
  ds.put(entity2);

  // Will print the same version, as entity1's entity group has not changed
  writer.println("version " + getEntityGroupVersion(ds, null, key1));

  // Change entity1's entity group by adding a new child entity
  Entity entity3 = new Entity("Simple", entity1.getKey());
  ds.put(entity3);

  // Will print a higher version, as entity1's entity group has changed
  writer.println("version " + getEntityGroupVersion(ds, null, key1));
}

기존 동작

이전 항목 그룹 버전 동작에서는 항목 그룹 버전이 항목 그룹 변경시에만 증가합니다. 예를 들어 이전 항목 그룹 메타데이터 동작은 항목 그룹에서 복잡한 상위 쿼리의 일관된 캐시를 유지하는 데 사용될 수 있습니다.

이 예시에서는 쿼리 결과(일치하는 결과 수)를 캐시하고 항목 그룹 버전의 이전 동작을 사용하여 캐시된 값이 최신 버전의 값일 때만 이 값을 사용합니다.

// A simple class for tracking consistent entity group counts.
private static class EntityGroupCount implements Serializable {

  long version; // Version of the entity group whose count we are tracking
  int count;

  EntityGroupCount(long version, int count) {
    this.version = version;
    this.count = count;
  }

  // Display count of entities in an entity group, with consistent caching
  void showEntityGroupCount(
      DatastoreService ds, MemcacheService cache, PrintWriter writer, Key entityGroupKey) {
    EntityGroupCount egCount = (EntityGroupCount) cache.get(entityGroupKey);
    // Reuses getEntityGroupVersion method from the previous example.
    if (egCount != null && egCount.version == getEntityGroupVersion(ds, null, entityGroupKey)) {
      // Cached value matched current entity group version, use that
      writer.println(egCount.count + " entities (cached)");
    } else {
      // Need to actually count entities. Using a transaction to get a consistent count
      // and entity group version.
      Transaction tx = ds.beginTransaction();
      PreparedQuery pq = ds.prepare(tx, new Query(entityGroupKey));
      int count = pq.countEntities(FetchOptions.Builder.withLimit(5000));
      cache.put(
          entityGroupKey,
          new EntityGroupCount(getEntityGroupVersion(ds, tx, entityGroupKey), count));
      tx.rollback();
      writer.println(count + " entities");
    }
  }
}

__entity_group__ 항목은 이전에 기록되지 않은 항목 그룹에 없을 수 있습니다.

메타데이터 쿼리

com.google.appengine.api.datastore 패키지에서 정의된 자바 클래스 Entities는 메타데이터 쿼리에 예약된 세 가지 특별한 항목 종류를 제공합니다. 이러한 항목은 Entities 클래스의 정적 상수로 구분됩니다.

정적 상수 항목 종류
Entities.NAMESPACE_METADATA_KIND __namespace__
Entities.KIND_METADATA_KIND __kind__
Entities.PROPERTY_METADATA_KIND __property__

이러한 종류는 이미 애플리케이션에 존재할 수 있는 동일한 이름의 다른 항목과 충돌하지 않습니다. 이러한 특수 종류를 쿼리하면 원하는 메타데이터가 포함된 항목을 검색할 수 있습니다.

메타데이터 쿼리에서 반환되는 항목은 Datastore의 현재 상태에 따라 동적으로 생성됩니다. __namespace__, __kind__ 또는 __property__ 종류의 로컬 Entity 객체를 만들 수는 있지만 Datastore에 이를 저장하려고 시도하면 IllegalArgumentException이 발생하면서 실패합니다.

메타데이터 쿼리를 실행하는 가장 쉬운 방법은 하위 수준의 Datastore API를 사용하는 방법입니다. 다음 예에서는 애플리케이션에 있는 모든 네임스페이스의 이름을 출력합니다.

void printAllNamespaces(DatastoreService ds, PrintWriter writer) {
  Query q = new Query(Entities.NAMESPACE_METADATA_KIND);

  for (Entity e : ds.prepare(q).asIterable()) {
    // A nonzero numeric id denotes the default namespace;
    // see <a href="#Namespace_Queries">Namespace Queries</a>, below
    if (e.getKey().getId() != 0) {
      writer.println("<default>");
    } else {
      writer.println(e.getKey().getName());
    }
  }
}

네임스페이스 쿼리

애플리케이션에서 Namespaces API를 사용하는 경우, 네임스페이스 쿼리를 사용하여 애플리케이션의 항목에 사용되는 모든 네임스페이스를 찾을 수 있습니다. 이 방법으로 여러 네임스페이스에 대한 관리 기능과 같은 작업을 수행할 수 있습니다.

네임스페이스 쿼리는 키 이름이 네임스페이스의 이름인 특수한 종류의 __namespace__ 항목을 반환합니다. 단, 빈 문자열 ""로 지정된 기본 네임스페이스는 예외입니다. 빈 문자열이 유효한 키 이름이 아니기 때문에 이 네임스페이스는 대신 숫자 ID 1로 키가 지정됩니다. 이 유형의 쿼리는 값이 항목 키인 특수한 유사 속성 __key__의 범위에서만 필터링을 지원합니다. 결과는 __key__ 값의 오름차순으로 정렬할 수 있습니다(내림차순 불가). __namespace__ 항목에 속성이 없기 때문에 키 전용 및 키 전용이 아닌 쿼리가 모두 동일한 정보를 반환합니다.

다음 예시에서는 지정된 두 개의 이름인 startend 사이의 범위에 있는 애플리케이션 네임스페이스의 목록을 반환합니다.

List<String> getNamespaces(DatastoreService ds, String start, String end) {

  // Start with unrestricted namespace query
  Query q = new Query(Entities.NAMESPACE_METADATA_KIND);
  List<Filter> subFilters = new ArrayList();
  // Limit to specified range, if any
  if (start != null) {
    subFilters.add(
        new FilterPredicate(
            Entity.KEY_RESERVED_PROPERTY,
            FilterOperator.GREATER_THAN_OR_EQUAL,
            Entities.createNamespaceKey(start)));
  }
  if (end != null) {
    subFilters.add(
        new FilterPredicate(
            Entity.KEY_RESERVED_PROPERTY,
            FilterOperator.LESS_THAN_OR_EQUAL,
            Entities.createNamespaceKey(end)));
  }

  q.setFilter(CompositeFilterOperator.and(subFilters));

  // Initialize result list
  List<String> results = new ArrayList<String>();

  // Build list of query results
  for (Entity e : ds.prepare(q).asIterable()) {
    results.add(Entities.getNamespaceFromNamespaceKey(e.getKey()));
  }

  // Return result list
  return results;
}

종류 쿼리

종류 쿼리는 키 이름이 항목 종류의 이름인 __kind__ 종류의 항목을 반환합니다. 이 유형의 쿼리는 현재 네임스페이스로만 암시적으로 제한되며 __key__ 유사 속성을 범위로 하는 필터링만 지원합니다. 결과는 __key__ 값의 오름차순으로 정렬할 수 있습니다(내림차순 불가). __kind__ 항목에 속성이 없기 때문에 키 전용 및 키 전용이 아닌 쿼리가 모두 동일한 정보를 반환합니다.

다음 예시는 이름이 소문자로 시작하는 모든 종류를 표시합니다.

void printLowercaseKinds(DatastoreService ds, PrintWriter writer) {

  // Start with unrestricted kind query
  Query q = new Query(Entities.KIND_METADATA_KIND);

  List<Filter> subFils = new ArrayList();

  // Limit to lowercase initial letters
  subFils.add(
      new FilterPredicate(
          Entity.KEY_RESERVED_PROPERTY,
          FilterOperator.GREATER_THAN_OR_EQUAL,
          Entities.createKindKey("a")));

  String endChar = Character.toString((char) ('z' + 1)); // Character after 'z'

  subFils.add(
      new FilterPredicate(
          Entity.KEY_RESERVED_PROPERTY,
          FilterOperator.LESS_THAN,
          Entities.createKindKey(endChar)));

  q.setFilter(CompositeFilterOperator.and(subFils));

  // Print heading
  writer.println("Lowercase kinds:");

  // Print query results
  for (Entity e : ds.prepare(q).asIterable()) {
    writer.println("  " + e.getKey().getName());
  }
}

속성 쿼리

속성 쿼리는 항목 종류와 연결된 속성을 나타내는 __property__ 종류 항목을 반환합니다. K 종류의 P 속성을 나타내는 항목은 다음과 같이 빌드됩니다.

  • 항목 키에는 __property__ 종류 및 키 이름 P가 포함됩니다.
  • 상위 항목 키에는 __kind__ 종류 및 키 이름 K가 포함됩니다.

속성 쿼리의 동작은 아래 하위 섹션의 설명과 같이 키 전용 쿼리인지 또는 키 전용이 아닌(속성 표현) 쿼리인지에 따라 달라집니다.

속성 쿼리: 키 전용

키 전용 속성 쿼리는 지정된 항목 종류의 색인이 생성된 각 속성의 키를 반환합니다. (색인이 생성되지 않은 속성은 포함되지 않습니다.) 다음 예시에서는 애플리케이션의 모든 항목 종류와 각 항목과 연관된 속성의 이름을 보여줍니다.

void printProperties(DatastoreService ds, PrintWriter writer) {

  // Create unrestricted keys-only property query
  Query q = new Query(Entities.PROPERTY_METADATA_KIND).setKeysOnly();

  // Print query results
  for (Entity e : ds.prepare(q).asIterable()) {
    writer.println(e.getKey().getParent().getName() + ": " + e.getKey().getName());
  }
}

이 유형의 쿼리는 현재 암시적으로 네임스페이스로 제한되고 유사 속성 __key__ 범위의 필터링만 지원합니다. 여기서 키는 __kind__ 또는 __property__ 항목을 나타냅니다. 결과는 __key__ 값의 오름차순으로 정렬할 수 있습니다(내림차순 불가). 필터링은 종류-속성 쌍에 적용되며, 처음에는 종류에 따라 그리고 두 번째는 속성에 따라 정렬됩니다. 예를 들어 다음과 같은 속성을 가진 항목이 있다고 가정합니다.

  • 다음 속성이 있는 Account 종류
    • balance
    • company
  • 다음 속성이 있는 Employee 종류
    • name
    • ssn
  • 다음 속성이 있는 Invoice 종류
    • date
    • amount
  • 다음 속성이 있는 Manager 종류
    • name
    • title
  • 다음 속성이 있는 Product 종류
    • description
    • price

속성 데이터를 반환하는 쿼리는 다음과 같습니다.

void printPropertyRange(DatastoreService ds, PrintWriter writer) {

  // Start with unrestricted keys-only property query
  Query q = new Query(Entities.PROPERTY_METADATA_KIND).setKeysOnly();

  // Limit range
  q.setFilter(
      CompositeFilterOperator.and(
          new FilterPredicate(
              Entity.KEY_RESERVED_PROPERTY,
              Query.FilterOperator.GREATER_THAN_OR_EQUAL,
              Entities.createPropertyKey("Employee", "salary")),
          new FilterPredicate(
              Entity.KEY_RESERVED_PROPERTY,
              Query.FilterOperator.LESS_THAN_OR_EQUAL,
              Entities.createPropertyKey("Manager", "salary"))));
  q.addSort(Entity.KEY_RESERVED_PROPERTY, SortDirection.ASCENDING);

  // Print query results
  for (Entity e : ds.prepare(q).asIterable()) {
    writer.println(e.getKey().getParent().getName() + ": " + e.getKey().getName());
  }
}

위 쿼리는 다음을 반환합니다.

Employee: ssn
Invoice: date
Invoice: amount
Manager: name

결과에는 Employee 종류의 name 속성과 Manager 종류의 title 속성, AccountProduct 종류의 속성이 포함되지 않습니다. 이러한 속성은 쿼리에 지정된 범위 밖에 있기 때문입니다.

속성 쿼리는 __kind__ 또는 __property__ 키에서 상위 필터링도 지원하여 단일 종류 또는 속성으로 쿼리 결과를 제한합니다. 다음 예와 같이 이 점을 사용하여, 예를 들면 지정된 항목 종류와 연관된 속성을 가져올 수 있습니다.

List<String> propertiesOfKind(DatastoreService ds, String kind) {

  // Start with unrestricted keys-only property query
  Query q = new Query(Entities.PROPERTY_METADATA_KIND).setKeysOnly();

  // Limit to specified kind
  q.setAncestor(Entities.createKindKey(kind));

  // Initialize result list
  ArrayList<String> results = new ArrayList<String>();

  //Build list of query results
  for (Entity e : ds.prepare(q).asIterable()) {
    results.add(e.getKey().getName());
  }

  // Return result list
  return results;
}

속성 쿼리: 키 전용이 아닌 쿼리(속성 표현)

키 전용이 아닌 속성 쿼리(속성 표현 쿼리라고 함)는 각 종류-속성 쌍에 사용된 표현에 대한 추가 정보를 반환합니다. (색인이 생성되지 않은 속성은 포함되지 않습니다.) K 종류의 P 속성에 반환된 항목에는 속성의 표현을 반환하는 추가 property_representation 속성과 함께 해당 키 전용 쿼리와 동일한 키가 포함됩니다. 이 속성 값은 K 종류의 항목에서 발견된 P 속성의 각 표현에 대한 문자열 하나를 포함하는 java.util.Collection<String> 클래스의 인스턴스입니다.

표현은 속성 클래스와 동일하지 않으며 여러 속성 클래스가 동일 표현에 매핑될 수 있습니다. 예를 들어 java.lang.Stringcom.google.appengine.api.datastore.PhoneNumber는 모두 STRING 표현을 사용합니다.

다음 표는 속성 클래스와 해당 표현을 보여줍니다.

속성 클래스 표현
java.lang.Byte INT64
java.lang.Short INT64
java.lang.Integer INT64
java.lang.Long INT64
java.lang.Float DOUBLE
java.lang.Double DOUBLE
java.lang.Boolean BOOLEAN
java.lang.String STRING
com.google.appengine.api.datastore.ShortBlob STRING
java.util.Date INT64
com.google.appengine.api.datastore.GeoPt POINT
com.google.appengine.api.datastore.PostalAddress STRING
com.google.appengine.api.datastore.PhoneNumber STRING
com.google.appengine.api.datastore.Email STRING
com.google.appengine.api.users.User USER
com.google.appengine.api.datastore.IMHandle STRING
com.google.appengine.api.datastore.Link STRING
com.google.appengine.api.datastore.Category STRING
com.google.appengine.api.datastore.Rating INT64
com.google.appengine.api.datastore.Key REFERENCE
com.google.appengine.api.blobstore.BlobKey STRING
java.util.Collection<T> T의 표현

다음 예에서는 지정된 항목 종류에 대해 지정된 속성의 모든 표현을 찾습니다.

Collection<String> representationsOfProperty(DatastoreService ds, String kind, String property) {

  // Start with unrestricted non-keys-only property query
  Query q = new Query(Entities.PROPERTY_METADATA_KIND);

  // Limit to specified kind and property
  q.setFilter(
      new FilterPredicate(
          "__key__", Query.FilterOperator.EQUAL, Entities.createPropertyKey(kind, property)));

  // Get query result
  Entity propInfo = ds.prepare(q).asSingleEntity();

  // Return collection of property representations
  return (Collection<String>) propInfo.getProperty("property_representation");
}