Cloud Datastore 색인

App Engine은 항목의 각 속성에 대한 간단한 색인을 사전 정의합니다. App Engine 애플리케이션은 datastore-indexes.xml이라는 색인 구성 파일에 커스텀 색인을 추가로 정의할 수 있으며, 이 파일은 애플리케이션의 /war/WEB-INF/appengine-generated 디렉토리에 생성됩니다. 개발 서버는 기존 색인으로 실행할 수 없는 쿼리를 발견하면 이 파일에 자동으로 제안 항목을 추가합니다. 애플리케이션을 업로드하기 전에 파일을 수정하여 색인을 수동으로 조정할 수 있습니다.

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

색인 정의 및 구조

색인은 지정된 항목 종류의 속성 목록에서 각 속성의 순서대로(오름차순 또는 내림차순) 정의됩니다. 상위 쿼리에 사용할 경우, 색인은 항목의 상위 항목을 선택적으로 포함할 수도 있습니다.

색인 표에는 색인 정의에서 명명된 모든 속성의 열이 포함됩니다. 표의 각 행은 색인을 기준으로 쿼리 결과가 될 수 있는 Cloud Datastore의 항목을 나타냅니다. 색인에 사용된 모든 속성의 색인 생성 값이 설정된 경우에만 항목이 색인에 포함됩니다. 색인 정의가 항목에 값이 없는 속성을 참조하는 경우, 해당 항목이 색인에 나타나지 않으므로 색인 기준의 쿼리 결과로 반환되지 않습니다.

참고: Cloud Datastore는 속성이 없는 항목과 null 값(null)의 속성이 있는 항목을 구분합니다. 명시적으로 null 값을 항목 속성에 지정하면 항목이 이 속성을 참조하는 쿼리 결과에 포함될 수 있습니다.

참고: 여러 속성으로 구성된 색인을 사용하려면 각 속성에 대해 색인 없음으로 설정되어 있지 않아야 합니다.

색인 표의 행은 색인 정의에서 지정된 순서대로 먼저 상위 항목별로 정렬된 후 속성 값별로 정렬됩니다. 쿼리를 가장 효율적으로 실행할 수 있게 해주는 완벽한 색인은 다음 속성의 순서대로 정의됩니다.

  1. 균등 필터에 사용되는 속성
  2. 비균등 필터에 사용되는 속성(1개 이하여야 함)
  3. 정렬 순서에 사용되는 속성

이렇게 하면 실행 가능한 쿼리의 모든 결과가 표의 연속 행에 나타납니다. Cloud Datastore는 다음 단계에 따라 완벽한 색인을 사용하여 쿼리를 실행합니다.

  1. 쿼리의 종류, 필터 속성, 필터 연산자, 정렬 순서에 해당하는 색인을 식별합니다.
  2. 색인 시작 부분부터 쿼리의 필터 조건을 모두 충족하는 첫 번째 항목까지 검색합니다.
  3. 색인 검색을 계속 진행하여 다음 경우가 될 때까지 각 항목을 차례로 반환합니다.
    • 필터 조건을 충족하지 않는 항목을 발견한 경우 또는
    • 색인 끝에 도달한 경우 또는
    • 쿼리에서 요청한 최대 개수의 결과를 수집한 경우

예를 들어, 다음 쿼리를 살펴보겠습니다.

자바 8

Query q1 =
    new Query("Person")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("lastName", FilterOperator.EQUAL, "Smith"),
                new FilterPredicate("height", FilterOperator.EQUAL, 72)))
        .addSort("height", Query.SortDirection.DESCENDING);

자바 7

Query q1 =
    new Query("Person")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("lastName", FilterOperator.EQUAL, "Smith"),
                new FilterPredicate("height", FilterOperator.EQUAL, 72)))
        .addSort("height", Query.SortDirection.DESCENDING);

이 쿼리의 완벽한 색인은 종류가 Person이고 lastNameheight 속성 값 열이 있는 항목의 키 표입니다. 색인은 먼저 lastName을 기준으로 오름차순으로 정렬된 후 height를 기준으로 내림차순으로 정렬됩니다.

형식은 동일하지만 필터 값이 서로 다른 두 쿼리는 동일한 색인을 사용합니다. 예를 들어, 다음 쿼리는 위와 동일한 색인을 사용합니다.

자바 8

Query q2 =
    new Query("Person")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("lastName", FilterOperator.EQUAL, "Jones"),
                new FilterPredicate("height", FilterOperator.EQUAL, 63)))
        .addSort("height", Query.SortDirection.DESCENDING);

자바 7

Query q2 =
    new Query("Person")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("lastName", FilterOperator.EQUAL, "Jones"),
                new FilterPredicate("height", FilterOperator.EQUAL, 63)))
        .addSort("height", Query.SortDirection.DESCENDING);

형식은 서로 다르지만 다음 두 쿼리도 동일한 색인을 사용합니다.

자바 8

Query q3 =
    new Query("Person")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("lastName", FilterOperator.EQUAL, "Friedkin"),
                new FilterPredicate("firstName", FilterOperator.EQUAL, "Damian")))
        .addSort("height", Query.SortDirection.ASCENDING);

자바 7

Query q3 =
    new Query("Person")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("lastName", FilterOperator.EQUAL, "Friedkin"),
                new FilterPredicate("firstName", FilterOperator.EQUAL, "Damian")))
        .addSort("height", Query.SortDirection.ASCENDING);

자바 8

Query q4 =
    new Query("Person")
        .setFilter(new FilterPredicate("lastName", FilterOperator.EQUAL, "Blair"))
        .addSort("firstName", Query.SortDirection.ASCENDING)
        .addSort("height", Query.SortDirection.ASCENDING);

자바 7

Query q4 =
    new Query("Person")
        .setFilter(new FilterPredicate("lastName", FilterOperator.EQUAL, "Blair"))
        .addSort("firstName", Query.SortDirection.ASCENDING)
        .addSort("height", Query.SortDirection.ASCENDING);

색인 구성

기본적으로 Cloud Datastore는 각 항목 종류의 각 속성에 대한 색인을 자동으로 사전 정의합니다. 이렇게 사전 정의된 색인만으로도 균등 전용 쿼리 및 단순 비균등 쿼리와 같은 다수의 단순 쿼리를 수행할 수 있습니다. 다른 모든 쿼리의 경우, 애플리케이션이 datastore-indexes.xml이라는 색인 구성 파일에 필요한 색인을 정의해야 합니다. 애플리케이션이 사용 가능한 색인(색인 구성 파일에 사전 정의되거나 지정됨)을 사용하여 실행할 수 없는 쿼리를 수행하려 하면 쿼리는 실패하고 DatastoreNeedIndexException이 발생합니다.

Datastore는 다음 형식의 쿼리에 대해 자동 색인을 빌드합니다.

  • 상위 및 키 필터만 사용하는 비구분 쿼리
  • 상위 및 균등 필터만 사용하는 쿼리
  • 비균등 필터만 사용하는 쿼리(단일 속성으로 제한됨)
  • 상위 필터, 속성에 균등 필터, 키에 비균등 필터만 사용하는 쿼리
  • 필터가 없고 속성 1개에 정렬 순서 1개(오름차순 또는 내림차순)만 있는 쿼리

다음을 포함한 다른 형식의 쿼리는 색인 구성 파일에서 색인을 지정해야 합니다.

  • 상위 및 비균등 필터가 있는 쿼리
  • 속성 1개에 비균등 필터가 1개 이상 있고 다른 속성에 균등 필터가 1개 이상 있는 쿼리
  • 키에 내림차순 정렬 순서가 있는 쿼리
  • 여러 정렬 순서가 있는 쿼리

색인 및 속성

아래에서는 색인에 대해 고려해야 할 몇 가지 특수 상황과 이러한 상황이 Cloud Datastore의 항목 속성과 어떤 연관이 있는지 알아봅니다.

값 유형이 혼합된 속성

두 항목에 이름이 같지만 값 유형이 서로 다른 속성이 있으면 속성 색인은 항목을 먼저 값 유형별로 정렬한 후 각 유형에 적절한 2차 순서대로 정렬합니다. 예를 들어 두 항목 각각에 이름이 age인 속성이 있고 항목 하나에는 정수 값이 있고 다른 하나에는 문자열 값이 있을 경우, age 속성별로 정렬하면 속성 값 자체에 상관없이 정수 값이 있는 항목이 문자열 값이 있는 항목보다 항상 먼저 나열됩니다.

이는 특히 Cloud Datastore에서 별도의 유형으로 처리되는 정수와 부동 소수점 숫자가 나올 때 기억하면 유용합니다. 모든 정수는 모든 부동 소수점보다 먼저 정렬되므로 정수 값 38이 있는 속성이 부동 소수점 값 37.5가 있는 속성보다 먼저 정렬됩니다.

색인이 생성되지 않는 속성

특정 속성을 기준으로 필터링하거나 정렬할 필요가 없다고 판단되면 색인이 생성되지 않는 속성을 선언하여 Cloud Datastore에서 해당 속성의 색인 항목을 유지하지 않도록 할 수 있습니다. 이렇게 하면 수행해야 하는 Cloud Datastore 쓰기 횟수가 줄어들어 애플리케이션 실행 비용을 절감할 수 있습니다. 색인이 생성되지 않는 속성이 있는 항목은 속성이 설정되지 않은 것처럼 작동합니다. 즉, 색인이 생성되지 않는 속성의 필터 또는 정렬 순서를 사용하는 쿼리는 해당 항목과 일치하지 않습니다.

참고: 여러 속성으로 구성된 색인에 속성이 나타날 경우, 이를 색인이 생성되지 않는 속성으로 설정하면 구성된 색인에서 해당 속성의 색인이 생성되지 않습니다.

예를 들어, 항목에 ab 속성이 있고 WHERE a ="bike" and b="red"와 같은 쿼리를 충족할 수 있는 색인을 만들려고 한다고 가정합니다. 또한 WHERE a="bike"WHERE b="red" 쿼리는 고려하지 않는다고 가정합니다. a를 색인이 생성되지 않는 속성으로 설정하고 ab를 위한 색인을 만들면 Cloud Datastore에서 ab 색인을 위한 색인 항목을 만들지 않으므로 WHERE a="bike" and b="red" 쿼리가 작동하지 않습니다. Cloud Datastore에서 ab 색인을 위한 항목을 만들려면 ab 모두에 대해 색인을 생성해야 합니다.

하위 수준 자바 Datastore API에서 속성은 설정에 사용한 메소드(setProperty() 또는 setUnindexedProperty())에 따라 항목별로 색인이 생성되거나 생성되지 않는 것으로 정의됩니다.

자바 8

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Key acmeKey = KeyFactory.createKey("Company", "Acme");

Entity tom = new Entity("Person", "Tom", acmeKey);
tom.setProperty("name", "Tom");
tom.setProperty("age", 32);
datastore.put(tom);

Entity lucy = new Entity("Person", "Lucy", acmeKey);
lucy.setProperty("name", "Lucy");
lucy.setUnindexedProperty("age", 29);
datastore.put(lucy);

Filter ageFilter = new FilterPredicate("age", FilterOperator.GREATER_THAN, 25);

Query q = new Query("Person").setAncestor(acmeKey).setFilter(ageFilter);

// Returns tom but not lucy, because her age is unindexed
List<Entity> results = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults());

자바 7

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Key acmeKey = KeyFactory.createKey("Company", "Acme");

Entity tom = new Entity("Person", "Tom", acmeKey);
tom.setProperty("name", "Tom");
tom.setProperty("age", 32);
datastore.put(tom);

Entity lucy = new Entity("Person", "Lucy", acmeKey);
lucy.setProperty("name", "Lucy");
lucy.setUnindexedProperty("age", 29);
datastore.put(lucy);

Filter ageFilter = new FilterPredicate("age", FilterOperator.GREATER_THAN, 25);

Query q = new Query("Person").setAncestor(acmeKey).setFilter(ageFilter);

// Returns tom but not lucy, because her age is unindexed
List<Entity> results = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults());

값을 setUnindexedProperty()로 재설정하여 색인이 생성되는 속성을 색인이 생성되지 않는 속성으로 변경하거나, 값을 setProperty()로 재설정하여 색인이 생성되지 않는 속성을 색인이 생성되는 속성으로 변경할 수 있습니다.

색인이 생성되지 않는 속성을 색인이 생성되는 속성으로 변경해도 변경 전에 생성된 기존 항목은 영향을 받지 않습니다. 속성에서 쿼리를 필터링하면 이러한 기존 항목이 반환되지 않습니다. 생성 당시 쿼리의 색인에 이러한 항목을 작성하지 않았기 때문입니다. 향후 쿼리에서 이러한 항목에 액세스할 수 있게 하려면, 항목을 Cloud Datastore에 다시 작성하여 적절한 색인에 입력되도록 해야 합니다. 즉, 이러한 기존 항목 각각에 대해 다음을 수행해야 합니다.

  1. Cloud Datastore에서 항목을 검색합니다(가져오기).
  2. 항목을 Cloud Datastore에 다시 작성합니다(내보내기).

마찬가지로, 색인이 생성되는 속성을 색인이 생성되지 않는 속성으로 변경해도 이후에 Cloud Datastore에 작성된 항목만 영향을 받습니다. 색인이 생성되는 속성이 있는 기존 항목의 색인 항목은 업데이트되거나 삭제될 때까지 계속 그대로 남아있게 됩니다. 원하는 결과를 얻으려면 현재 색인이 생성되지 않는 속성을 기준으로 필터링 또는 정렬하는 모든 쿼리의 코드를 삭제해야 합니다.

색인 제한

Datastore는 단일 항목과 연결할 수 있는 색인 항목의 수 및 전체 크기에 제한을 적용합니다. 이러한 제한 사항은 크며 대부분의 애플리케이션은 영향을 받지 않습니다. 하지만 이러한 제한이 발생할 수 있는 상황이 있습니다.

위에서 설명한 대로 Cloud Datastore는 긴 텍스트 문자열(Text), 긴 바이트 문자열 Blob), 삽입된 항목(EmbeddedEntity)과 색인을 생성하지 않음으로 명시적으로 선언한 항목을 제외한 모든 항목의 모든 속성에 대해 사전에 정의된 색인에서 항목을 만듭니다. 속성은 datastore-indexes.xml 구성 파일에서 선언한 추가 커스텀 색인에 포함될 수도 있습니다. 항목에 목록 속성이 없다면 해당 커스텀 색인(비상위 색인용) 각각에 대해 최대 1개의 항목 또는 항목의 상위 항목(상위 색인용) 각각에 대해 1개의 항목이 있을 것입니다. 속성 값이 변경될 때마다 이러한 색인 항목 각각을 업데이트해야 합니다.

항목마다 단일 값을 포함하는 속성의 경우, 가능한 각각의 값은 속성의 사전 정의된 색인에 항목당 한 번씩만 저장해야 합니다. 이렇게 해도 이러한 단일 값 속성이 많은 항목이 색인 항목 또는 크기 제한을 초과할 수 있습니다. 마찬가지로, 동일한 속성의 여러 값이 있을 수 있는 항목에는 각 값에 대한 별도의 색인 항목이 필요합니다. 여기서도 가능한 값의 수가 많으면 이러한 항목이 항목 제한을 초과할 수 있습니다.

속성이 여러 개인 항목의 경우, 상황이 더 복잡해질 수 있습니다. 이러한 속성 각각에 여러 값이 사용될 수 있기 때문입니다. 이러한 항목을 수용하려면 속성 값의 모든 가능한 조합을 위한 항목이 색인에 포함되어야 합니다. 각각의 값이 여러 개인 여러 속성을 참조하는 커스텀 색인은 조합을 통해 '과도'해질 수 있습니다. 이 경우, 상대적으로 적은 수의 가능한 속성 값만 있는 항목에 대한 다수의 항목이 필요합니다. 이러한 과도 색인의 경우 업데이트해야 하는 색인 항목이 많으므로 Cloud Datastore에 항목을 작성하는 비용이 크게 증가할 수 있으며, 항목이 색인 항목 또는 크기 제한을 쉽게 초과할 수도 있습니다.

쿼리 살펴보기

자바 8

Query q =
    new Query("Widget")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("x", FilterOperator.EQUAL, 1),
                new FilterPredicate("y", FilterOperator.EQUAL, 2)))
        .addSort("date", Query.SortDirection.ASCENDING);

자바 7

Query q =
    new Query("Widget")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("x", FilterOperator.EQUAL, 1),
                new FilterPredicate("y", FilterOperator.EQUAL, 2)))
        .addSort("date", Query.SortDirection.ASCENDING);

이 쿼리를 실행하면 SDK가 다음 색인을 제안합니다.

자바 8

<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes autoGenerate="false">
  <datastore-index kind="Widget" ancestor="false" source="manual">
    <property name="x" direction="asc"/>
    <property name="y" direction="asc"/>
    <property name="date" direction="asc"/>
  </datastore-index>
</datastore-indexes>

자바 7

<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes autoGenerate="false">
  <datastore-index kind="Widget" ancestor="false" source="manual">
    <property name="x" direction="asc"/>
    <property name="y" direction="asc"/>
    <property name="date" direction="asc"/>
  </datastore-index>
</datastore-indexes>

이 색인에는 각 항목에 대한 색인 항목이 총 |x| * |y| * |date|개 필요합니다. 여기서 |x|x 속성에 대해 항목과 연결된 값의 수를 나타냅니다. 다음 코드를 예로 들어 보겠습니다.

자바 8

Entity widget = new Entity("Widget");
widget.setProperty("x", Arrays.asList(1, 2, 3, 4));
widget.setProperty("y", Arrays.asList("red", "green", "blue"));
widget.setProperty("date", new Date());
datastore.put(widget);

자바 7

Entity widget = new Entity("Widget");
widget.setProperty("x", Arrays.asList(1, 2, 3, 4));
widget.setProperty("y", Arrays.asList("red", "green", "blue"));
widget.setProperty("date", new Date());
datastore.put(widget);

이 코드는 x 속성 값이 4개이고, y 속성 값이 3개이며, date가 현재 날짜로 설정된 항목을 만듭니다. 이 경우, 가능한 속성 값 조합마다 1개씩, 총 12개의 색인 항목이 필요합니다.

(1, "red", <now>) (1, "green", <now>) (1, "blue", <now>)

(2, "red", <now>) (2, "green", <now>) (2, "blue", <now>)

(3, "red", <now>) (3, "green", <now>) (3, "blue", <now>)

(4, "red", <now>) (4, "green", <now>) (4, "blue", <now>)

동일한 속성이 여러 번 반복되면 Cloud Datastore에서 과도 색인을 감지하고 대체 색인을 제안할 수 있습니다. 하지만 다른 모든 상황(예: 이 예에 정의된 쿼리)에서는 Cloud Datastore가 과도 색인을 생성합니다. 이 경우, 색인 구성 파일에서 수동으로 색인을 구성하여 과도 색인을 피할 수 있습니다.

자바 8

<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes autoGenerate="false">
  <datastore-index kind="Widget">
    <property name="x" direction="asc" />
    <property name="date" direction="asc" />
  </datastore-index>
  <datastore-index kind="Widget">
    <property name="y" direction="asc" />
    <property name="date" direction="asc" />
  </datastore-index>
</datastore-indexes>

자바 7

<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes autoGenerate="false">
  <datastore-index kind="Widget">
    <property name="x" direction="asc" />
    <property name="date" direction="asc" />
  </datastore-index>
  <datastore-index kind="Widget">
    <property name="y" direction="asc" />
    <property name="date" direction="asc" />
  </datastore-index>
</datastore-indexes>

이렇게 하면 필요한 항목 수가 12개가 아닌 7개(|x| * |date| + |y| * |date|)로 줄어듭니다.

(1, <now>) (2, <now>) (3, <now>) (4, <now>)

("red", <now>) ("green", <now>) ("blue", <now>)

색인 항목 또는 크기 제한을 초과하는 색인을 만드는 모든 내보내기 작업은 실패하고 IllegalArgumentException이 발생합니다. 예외 텍스트를 통해 초과한 제한("Too many indexed properties" 또는 "Index entries too large")과 원인으로 작용한 커스텀 색인을 확인할 수 있습니다. 빌드 시 항목 제한을 초과하는 새 색인을 만들면 해당 색인에 대한 쿼리가 실패하고 색인은 GCP 콘솔에서 Error 상태로 표시됩니다. Error 상태의 색인을 해결하려면 다음을 따르세요.

  1. datastore-indexes.xml 파일에서 Error 상태의 색인을 삭제합니다.

  2. datastore-indexes.xml이 위치한 디렉토리에서 다음 명령어를 실행하여 Cloud Datastore에서 해당 색인을 삭제합니다.

    appcfg.sh vacuum_indexes [YOUR_APP_DIR]
    
  3. 오류의 원인을 해결합니다. 예를 들면 다음과 같습니다.

    • 색인 정의와 해당 쿼리를 다시 작성합니다.
    • 과도 색인을 발생시키는 항목을 삭제합니다.
  4. 색인을 datastore-indexes.xml 파일에 다시 추가합니다.

  5. datastore-indexes.xml이 위치한 디렉토리에서 다음 명령어를 실행하여 Cloud Datastore에서 해당 색인을 만듭니다.

    appcfg.sh update_indexes datastore-indexes.xml
    

목록 속성을 사용하는 커스텀 색인이 필요한 쿼리를 사용하지 않으면 과도 색인을 방지할 수 있습니다. 위에서 설명한 대로 여기에는 여러 정렬 순서를 사용하는 쿼리나 균등 필터와 비균등 필터가 혼합된 쿼리가 포함됩니다.

이 페이지가 도움이 되었나요? 평가를 부탁드립니다.

다음에 대한 의견 보내기...

App Engine standard environment for Java