Spanner Graph로 스키마 없는 데이터 관리

이 페이지에서는 Spanner Graph 내에서 스키마 없는 데이터를 관리하는 방법을 설명합니다. 또한 권장사항문제 해결 도움말도 자세히 설명합니다. Spanner Graph 스키마쿼리에 익숙한 것이 좋습니다.

스키마 없는 데이터 관리를 사용하면 스키마를 변경하지 않고도 노드 및 에지 유형 정의를 추가, 업데이트 또는 삭제할 수 있는 유연한 그래프 정의를 만들 수 있습니다. 이 접근 방식은 반복적인 개발과 적은 스키마 관리 오버헤드를 지원하면서도 익숙한 그래프 쿼리 환경을 유지합니다.

스키마 없는 데이터 관리는 다음과 같은 경우에 특히 유용합니다.

  • 요소 라벨 및 속성의 업데이트 및 추가와 같이 자주 변경되는 그래프를 관리합니다.
  • 그래프에 노드 및 에지 유형이 많아 입력 테이블을 만들고 관리하기가 어렵습니다.

스키마 없는 데이터 모델링

Spanner Graph를 사용하면 행이 노드 및 에지에 매핑된 테이블에서 그래프를 만들 수 있습니다. 스키마 없는 데이터 모델링은 일반적으로 각 요소 유형에 별도의 테이블을 사용하는 대신 단일 노드 테이블과 단일 에지 테이블을 사용하며, 라벨에는 STRING 열을, 속성에는 JSON 열을 사용합니다.

입력 테이블 만들기

다음 예시와 같이 단일 GraphNode 테이블과 단일 GraphEdge 테이블을 만들어 스키마 없는 데이터를 저장할 수 있습니다. 테이블 이름은 설명 목적으로 사용되며 원하는 이름을 선택할 수 있습니다.

CREATE TABLE GraphNode (
  id INT64 NOT NULL,
  label STRING(MAX) NOT NULL,
  properties JSON,
) PRIMARY KEY (id);

CREATE TABLE GraphEdge (
  id INT64 NOT NULL,
  dest_id INT64 NOT NULL,
  edge_id INT64 NOT NULL,
  label STRING(MAX) NOT NULL,
  properties JSON,
) PRIMARY KEY (id, dest_id, edge_id),
  INTERLEAVE IN PARENT GraphNode;

이 예시에서는 다음을 수행합니다.

  • 모든 노드를 단일 테이블 GraphNode에 저장하며, id로 고유하게 식별됩니다.

  • 소스(id), 대상(dest_id), 자체 식별자(edge_id)의 조합으로 고유하게 식별되는 단일 테이블 GraphEdge에 모든 에지를 저장합니다. edge_idid에서 dest_id 쌍으로의 두 개 이상의 에지를 허용하기 위해 기본 키의 일부로 포함됩니다.

노드 테이블과 에지 테이블 모두 각각 STRINGJSON 유형의 자체 labelproperties 열을 갖습니다.

속성 그래프 만들기

CREATE PROPERTY GRAPH 문을 사용하면 이전 섹션의 입력 테이블이 노드 및 에지로 매핑됩니다. 스키마 없는 데이터의 라벨과 속성을 정의하려면 다음 절을 사용해야 합니다.

  • DYNAMIC LABEL: 입력 테이블의 STRING 열에서 노드 또는 에지의 라벨을 만듭니다.
  • DYNAMIC PROPERTIES: 입력 테이블의 JSON 열에서 노드 또는 에지의 속성을 만듭니다.

다음 예시는 이러한 절을 사용하여 그래프를 만드는 방법을 보여줍니다.

CREATE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNode
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  )
  EDGE TABLES (
    GraphEdge
      SOURCE KEY (id) REFERENCES GraphNode(id)
      DESTINATION KEY (dest_id) REFERENCES GraphNode(id)
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  );

동적 라벨

DYNAMIC LABEL 절은 라벨 값을 저장할 STRING 데이터 유형 열을 지정합니다.

예를 들어 GraphNode 행에서 label 열에 person 값이 있으면 그래프 내의 Person 노드에 매핑됩니다. 마찬가지로 GraphEdge 행에서 라벨 열의 값이 owns이면 그래프 내의 Owns 에지에 매핑됩니다.

`GraphNode` 라벨을 `GraphEdge` 라벨에 매핑

동적 속성

DYNAMIC PROPERTIES 절은 속성을 저장할 JSON 데이터 유형 열을 지정합니다. JSON 키는 속성 이름이고 JSON 값은 속성 값입니다.

예를 들어 GraphNode 행의 properties 열에 JSON 값 '{"name": "David", "age": 43}'이 있으면 agename 속성이 있는 노드에 매핑되며, 이때 속성 값은 43"David"입니다.

스키마 없는 데이터 관리를 사용하지 않는 경우

다음과 같은 경우에는 스키마 없는 데이터 관리를 사용하지 않는 것이 좋습니다.

  • 그래프 데이터의 노드 및 에지 유형이 잘 정의되어 있거나 라벨 및 속성을 자주 업데이트할 필요가 없는 경우
  • 데이터가 이미 Spanner에 저장되어 있으며, 새로운 전용 노드 및 에지 테이블을 추가하는 대신 기존 테이블에서 그래프를 구성하고자 하는 경우
  • 스키마 없는 데이터의 제한사항으로 인해 채택하지 못했습니다.

또한 워크로드가 쓰기 성능에 매우 민감한 경우, 특히 속성이 자주 업데이트되는 경우 JSON 유형의 동적 속성을 사용하는 것보다 STRING 또는 INT64와 같은 기본 데이터 유형으로 스키마 정의 속성을 사용하는 것이 더 효과적입니다.

동적 데이터 라벨 및 속성을 사용하지 않고 그래프 스키마를 정의하는 방법에 관한 자세한 내용은 Spanner Graph 스키마 개요를 참조하세요.

스키마 없는 그래프 데이터 쿼리

Graph Query Language(GQL)를 사용하여 스키마가 없는 그래프 데이터를 쿼리할 수 있습니다. Spanner Graph 쿼리 개요GQL 참조의 샘플 쿼리를 제한적으로 수정하여 사용할 수 있습니다.

라벨을 사용하여 노드와 에지 일치시키기

GQL에서 라벨 표현식을 사용하여 노드와 에지를 일치시킬 수 있습니다.

다음 쿼리는 라벨 열에 accounttransfers 값이 있는 연결된 노드와 에지를 일치시킵니다.

GRAPH FinGraph
MATCH (a:Account {id: 1})-[t:Transfers]->(d:Account)
RETURN COUNT(*) AS result_count;

속성에 액세스

JSON 데이터 유형의 최상위 키와 값은 다음 예시의 agename와 같은 속성으로 모델링됩니다.

JSON document Properties

   {
     "name": "Tom",
     "age": 43,
   }
"name": "Tom"
"age": 34

다음 예시는 Person 노드에서 name 속성에 액세스하는 방법을 보여줍니다.

GRAPH FinGraph
MATCH (person:Person {id: 1})
RETURN person.name;

그러면 다음과 비슷한 결과가 반환됩니다.

JSON"Tom"

속성 데이터 유형 변환

속성은 JSON 데이터 유형의 값으로 처리됩니다. SQL 유형과 비교하는 경우와 같이 먼저 SQL 유형으로 변환해야 하는 경우도 있습니다.

다음 예시에서 쿼리는 다음과 같은 데이터 유형 변환을 실행합니다.

  • is_blocked 속성을 불리언 유형으로 변환하여 표현식을 평가합니다.
  • order_number_str 속성을 문자열 유형으로 변환하고 리터럴 값 "302290001255747"과 비교합니다.
  • order_number_str을 정수로 안전하게 변환하기 위해 LAX_INT64 함수를 사용하며, 반환 유형은 정수입니다.
GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->()
WHERE BOOL(a.is_blocked) AND STRING(t.order_number_str) = "302290001255747"
RETURN LAX_INT64(t.order_number_str) AS order_number_as_int64;

그러면 다음과 비슷한 결과가 반환됩니다.

+-----------------------+
| order_number_as_int64 |
+-----------------------+
| 302290001255747       |
+-----------------------+

GROUP BYORDER BY와 같은 절에서는 JSON 데이터 유형도 변환해야 합니다. 다음 예시에서는 city 속성을 문자열 유형으로 변환하여 그룹화에 사용할 수 있도록 합니다.

GRAPH FinGraph
MATCH (person:Person {country: "South Korea"})
RETURN STRING(person.city) as person_city, COUNT(*) as cnt
LIMIT 10

JSON 데이터 유형을 SQL 데이터 유형으로 변환하기 위한 도움말:

  • INT64와 같은 엄격한 변환기는 엄격한 유형 및 값 검사를 실행합니다. 이는 JSON 데이터 유형이 알려져 있고 적용되는 경우에 권장됩니다(예: 스키마 제약 조건을 사용하여 속성 데이터 유형을 적용).
  • LAX_INT64와 같은 유연한 변환기는 가능하면 값을 안전하게 변환하고 변환할 수 없는 경우에는 NULL을 반환합니다. 엄격한 검사가 필요하지 않거나 유형을 적용하기 어려운 경우에 권장됩니다.

데이터 변환에 대한 자세한 내용은 문제 해결 도움말을 참조하세요.

속성 값으로 필터링

속성 필터에서 필터 파라미터는 JSON 데이터 유형의 값으로 취급됩니다. 예를 들어 다음 쿼리에서 is_blocked는 JSON boolean로, order_number_str은 JSON string으로 취급됩니다.

GRAPH FinGraph
MATCH (a:Account {is_blocked: false})-[t:Transfers {order_number_str:"302290001255747"}]->()
RETURN a.id AS account_id;

그러면 다음과 비슷한 결과가 반환됩니다.

+-----------------------+
| account_id            |
+-----------------------+
| 7                     |
+-----------------------+

필터 파라미터는 속성 유형 및 값과 일치해야 합니다. 예를 들어 order_number_str 필터 파라미터가 정수인 경우 속성이 JSON string이므로 일치하는 항목이 없습니다.

GRAPH FinGraph
MATCH (a:Account {is_blocked: false})-[t:Transfers {order_number_str: 302290001255747}]->()
RETURN t.order_number_str;

중첩된 JSON 속성에 액세스

중첩된 JSON 키와 값은 속성으로 모델링되지 않습니다. 다음 예시에서 JSON 키 city, state, countrylocation 아래에 중첩되어 있으므로 속성으로 모델링되지 않습니다. 하지만 JSON 필드 액세스 연산자 또는 JSON 하위 스크립트 연산자를 사용하여 액세스할 수 있습니다.

JSON document Properties

   {
     "name": "Tom",
     "age": 43,
     "location": {
       "city": "New York",
       "state": "NY",
       "country": "USA",
     }
   }
"name": "Tom"
"age": 34
"location": {
  "city": "New York",
  "state": "NY",
  "country": "USA",
}

다음 예시에서는 JSON 필드 액세스 연산자를 사용하여 중첩된 속성에 액세스하는 방법을 보여줍니다.

GRAPH FinGraph
MATCH (person:Person {id: 1})
RETURN STRING(person.location.city);

그러면 다음과 비슷한 결과가 반환됩니다.

"New York"

스키마 없는 데이터 수정

Spanner Graph는 테이블의 데이터를 그래프 노드 및 에지에 매핑합니다. 입력 테이블 데이터를 변경하면 해당 그래프 데이터에 직접 변경이 발생합니다. 그래프 데이터 변형에 관한 자세한 내용은 Spanner Graph 데이터 삽입, 업데이트 또는 삭제를 참조하세요.

예시

이 섹션에서는 그래프 데이터를 생성, 업데이트, 삭제하는 방법의 예시를 제공합니다.

그래프 데이터 삽입

다음 예시에서는 person 노드를 삽입합니다. 라벨 및 속성 이름은 소문자를 사용해야 합니다.

INSERT INTO GraphNode (id, label, properties)
VALUES (4, "person", JSON'{"name": "David", "age": 43}');

그래프 데이터 업데이트

다음 예시에서는 Account 노드를 업데이트하고 JSON_SET 함수를 사용하여 is_blocked 속성을 설정합니다.

UPDATE GraphNode
SET properties = JSON_SET(
  properties,
  '$.is_blocked', false
)
WHERE label = "account" AND id = 16;

다음 예시에서는 새 속성 집합으로 person 노드를 업데이트합니다.

UPDATE GraphNode
SET properties = JSON'{"name": "David", "age": 43}'
WHERE label = "person" AND id = 4;

다음 예시에서는 JSON_REMOVE 함수를 사용하여 Account 노드에서 is_blocked 속성을 삭제합니다. 실행 후에는 다른 모든 기존 속성이 변경되지 않습니다.

UPDATE GraphNode
SET properties = JSON_REMOVE(
  properties,
  '$.is_blocked'
)
WHERE label = "account" AND id = 16;

그래프 데이터 삭제

다음 예시에서는 차단된 계정으로 이전된 Account 노드의 Transfers 에지를 삭제합니다.

DELETE FROM GraphEdge
WHERE label = "transfers" and id IN {
  GRAPH FinGraph
  MATCH (a:Account)-[:Transfers]->{1,2}(:Account {is_blocked: TRUE})
  RETURN a.id
}

제한사항

이 섹션에는 스키마 없는 데이터 관리 사용과 관련된 제한사항이 나와 있습니다.

동적 라벨의 단일 테이블 요구사항

정의에 동적 라벨이 사용된 경우, 노드 테이블을 하나만 가질 수 있습니다. 이 제한은 에지 테이블에도 적용됩니다. 다음과 같은 경우는 허용되지 않습니다.

  • 동적 라벨이 있는 노드 테이블을 다른 노드 테이블과 함께 정의하는 경우
  • 동적 라벨이 있는 에지 테이블을 다른 에지 테이블과 함께 정의하는 경우
  • 각각 동적 라벨을 사용하는 여러 개의 노드 테이블 또는 에지 테이블을 정의하는 경우

예를 들어 다음 코드는 동적 라벨이 있는 그래프 노드를 여러 개 만들려고 하기 때문에 실패합니다.

CREATE OR REPLACE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNodeOne
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties),
    GraphNodeTwo
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties),
    Account
      LABEL Account PROPERTIES(create_time)
  )
  EDGE TABLES (
    ...
  );

라벨 이름은 소문자여야 함

라벨 문자열 값은 일치하려면 소문자로 저장되어야 합니다. 애플리케이션 코드나 스키마 제약 조건을 사용하여 이 규칙을 적용하는 것이 좋습니다.

라벨 문자열 값은 소문자로 저장해야 하지만 쿼리에서 참조될 때는 대소문자를 구분하지 않습니다.

다음 예시는 소문자 값으로 라벨을 삽입하는 방법을 보여줍니다.

INSERT INTO GraphNode (id, label) VALUES (1, "account");
INSERT INTO GraphNode (id, label) VALUES (2, "account");

대소문자를 구분하지 않는 라벨을 사용하여 GraphNode 또는 GraphEdge와 일치시킬 수 있습니다.

GRAPH FinGraph
MATCH (accnt:Account {id: 1})-[:Transfers]->(dest_accnt:Account)
RETURN dest_accnt.id;

속성 이름은 소문자여야 함

속성 이름은 소문자로 저장되어야 합니다. 애플리케이션 코드나 스키마 제약 조건을 사용하여 이 규칙을 적용하는 것이 좋습니다.

속성 이름은 소문자로 저장해야 하지만 쿼리에서 참조할 때는 대소문자를 구분하지 않습니다.

다음 예시에서는 소문자를 사용하여 nameage 속성을 삽입합니다.

INSERT INTO GraphNode (id, label, properties)
VALUES (25, "person", JSON '{"name": "Kim", "age": 27}');

쿼리 텍스트에서 속성 이름은 대소문자를 구분하지 않습니다. 예를 들어 Age 또는 age를 사용하여 속성에 액세스할 수 있습니다.

GRAPH FinGraph
MATCH (n:Person {Age: 27})
RETURN n.id;

기타 제한사항

  • JSON 데이터 유형의 최상위 키만 속성으로 모델링됩니다.
  • 속성 데이터 유형은 Spanner JSON 유형 사양을 준수해야 합니다.

권장사항

이 섹션에서는 스키마 없는 데이터를 모델링하기 위한 권장사항을 설명합니다.

노드 및 에지의 기본 키 정의

노드의 키는 모든 그래프 노드에서 고유해야 합니다. 예를 들면 INT64 또는 문자열 UUID 열로 표시할 수 있습니다.

두 노드 사이에 여러 개의 에지가 있는 경우 에지의 고유 식별자를 도입해야 합니다. 스키마 예시에서는 애플리케이션 로직 INT64 edge_id 열을 사용합니다.

노드 및 에지 테이블의 스키마를 만들 때 값이 변경 불가능한 경우 label 열을 기본 키 열로 포함할 수 있습니다. 이렇게 하면 모든 키 열로 구성된 복합 키가 모든 노드 또는 에지에서 고유해야 합니다. 이 기법은 라벨로만 필터링되는 쿼리의 성능을 개선합니다.

기본 키 선택에 관한 자세한 내용은 기본 키 선택을 참조하세요.

자주 액세스하는 속성의 보조 색인

필터에 자주 사용되는 속성의 쿼리 성능을 높이려면 생성된 속성 열에 대해 보조 색인을 만든 다음 그래프 스키마와 쿼리에서 이를 사용하면 됩니다.

다음 예시에서는 person 노드의 GraphNode 테이블에 생성된 age 열을 추가합니다. person 라벨이 없는 노드의 경우 값은 NULL입니다.

ALTER TABLE GraphNode
ADD COLUMN person_age INT64 AS
(IF (label = "person", LAX_INT64(properties.age), NULL));

그런 다음 person_age에 대해 NULL FILTERED INDEX를 만들고 로컬 액세스를 위해 이를 GraphNode 테이블에 인터리브 처리합니다.

CREATE NULL_FILTERED INDEX IdxPersonAge
ON GraphNode(id, label, person_age), INTERLEAVE IN GraphNode;

이제 GraphNode 테이블에 그래프 노드 속성으로 사용할 수 있는 새 열이 포함됩니다. 속성 그래프 정의에 이를 반영하려면 CREATE OR REPLACE PROPERTY GRAPH 문을 사용하세요. 이렇게 하면 정의가 다시 컴파일되고 새 person_age 열이 속성으로 포함됩니다.

다음 문은 정의를 다시 컴파일하고 새 person_age 열을 속성으로 포함합니다.

CREATE OR REPLACE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNode
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  )
  EDGE TABLES (
    GraphEdge
      SOURCE KEY (id) REFERENCES GraphNode (id)
      DESTINATION KEY (dest_id) REFERENCES GraphNode (id)
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  );

다음 예시에서는 색인이 생성된 속성을 사용하여 쿼리를 실행합니다.

GRAPH FinGraph
MATCH (person:Person {person_age: 43})
RETURN person.id, person.name;

원하는 경우 색인을 만든 후 ANALYZE 명령어를 실행하여 쿼리 최적화 도구가 최신 데이터베이스 통계로 업데이트되도록 할 수 있습니다.

데이터 무결성 제약조건 확인

Spanner는 검사 제약 조건과 같은 스키마 객체를 지원하여 라벨 및 속성 데이터 무결성을 적용합니다. 이 섹션에는 스키마 없는 데이터와 함께 사용할 수 있는 검사 제약 조건에 관한 권장사항이 나와 있습니다.

유효한 라벨 값 적용

정의되지 않은 라벨 값을 방지하려면 라벨 열 정의에서 NOT NULL을 사용하는 것이 좋습니다.

CREATE TABLE GraphNode (
  id INT64 NOT NULL,
  label STRING(MAX) NOT NULL,
  properties JSON,
) PRIMARY KEY (id);

소문자 라벨 값 및 속성 이름 적용

라벨 및 속성 이름은 소문자로 저장해야 하므로 다음 중 하나를 실행하는 것이 좋습니다.

  • 애플리케이션 로직에서 검사를 시행합니다.
  • 스키마에 확인 제약 조건을 만듭니다.

쿼리 시 라벨 및 속성 이름은 대소문자를 구분하지 않습니다.

다음 예시에서는 라벨이 소문자여야 하도록 GraphNode 테이블에 노드 라벨 제약조건을 추가합니다.

ALTER TABLE GraphNode ADD CONSTRAINT NodeLabelLowerCaseCheck
CHECK(LOWER(label) = label);

다음 예시에서는 에지 속성 이름에 검사 제약조건을 추가합니다. 검사는 JSON_KEYS를 사용하여 최상위 키에 액세스합니다. COALESCEJSON_KEYSNULL을 반환하면 출력을 빈 배열로 변환한 다음 각 키가 소문자인지 확인합니다.

ALTER TABLE GraphEdge ADD CONSTRAINT EdgePropertiesLowerCaseCheck
CHECK(NOT array_includes(COALESCE(JSON_KEYS(properties, 1), []), key->key<>LOWER(key)));

속성 존재 적용

라벨에 속성이 있는지 확인하는 제약조건을 만들 수 있습니다.

다음 예시에서 제약조건은 person 노드에 name 속성이 있는지 확인합니다.

ALTER TABLE GraphNode
ADD CONSTRAINT NameMustExistForPersonConstraint
CHECK (IF(label = 'person', properties.name IS NOT NULL, TRUE));

속성 고유성 적용

노드 또는 에지의 속성이 동일한 라벨이 지정된 노드 또는 에지에서 고유한지 확인하는 속성 기반 제약 조건을 만들 수 있습니다. 이렇게 하려면 속성의 생성된 열고유 색인을 사용하세요.

다음 예시에서 고유 색인은 결합된 namecountry 속성이 모든 person 노드에 대해 고유한지 확인합니다.

  1. PersonName의 생성 열을 추가합니다.

    ALTER TABLE GraphNode
    ADD COLUMN person_name STRING(MAX)
    AS (IF(label = 'person', STRING(properties.name), NULL)) Hidden;
    
  2. PersonCountry의 생성 열을 추가합니다.

    ALTER TABLE GraphNode
    ADD COLUMN person_country STRING(MAX)
    AS (IF(label = 'person', STRING(properties.country), NULL)) Hidden;
    
  3. PersonNamePersonCountry 속성에 대해 NULL_FILTERED 고유 색인을 만듭니다.

    CREATE UNIQUE NULL_FILTERED INDEX NameAndCountryMustBeUniqueForPerson
    ON GraphNode (person_name, person_country);
    

속성 데이터 유형 적용

다음 예시와 같이 라벨의 속성 값에 데이터 유형 제약조건을 사용하여 속성 데이터 유형을 적용할 수 있습니다. 이 예시에서는 JSON_TYPE 함수를 사용하여 person 라벨의 name 속성이 STRING 유형을 사용하는지 확인합니다.

ALTER TABLE GraphNode
ADD CONSTRAINT PersonNameMustBeStringTypeConstraint
CHECK (IF(label = 'person', JSON_TYPE(properties.name) = 'string', TRUE));

정의된 라벨과 동적 라벨 결합

Spanner를 사용하면 속성 그래프의 노드에 정의된 라벨(스키마에 정의됨)과 동적 라벨(데이터에서 파생됨)을 모두 적용할 수 있습니다. 이 유연성을 활용하려면 라벨을 맞춤설정하면 됩니다.

GraphNode 테이블 생성을 보여주는 다음 스키마를 살펴보세요.

CREATE OR REPLACE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNode
      LABEL Entity -- Defined label
      DYNAMIC LABEL (label) -- Dynamic label from data column 'label'
      DYNAMIC PROPERTIES (properties)
  );

여기서 GraphNode에서 생성된 모든 노드에는 정의된 라벨 Entity가 있습니다. 또한 각 노드에는 라벨 열의 값에 따라 결정되는 동적 라벨이 있습니다.

그런 다음 라벨 유형을 기반으로 노드를 일치시키는 쿼리를 작성할 수 있습니다. 예를 들어 다음 쿼리는 정의된 Entity 라벨을 사용하여 노드를 찾습니다.

GRAPH FinGraph
MATCH (node:Entity {id: 1}) -- Querying by the defined label
RETURN node.name;

이 쿼리는 정의된 라벨 Entity를 사용하지만 일치하는 노드에는 데이터에 기반한 동적 라벨도 포함됩니다.

스키마 예시

이 섹션의 스키마 예시를 템플릿으로 사용하여 자체 스키마를 만들 수 있습니다. 주요 스키마 구성요소는 다음과 같습니다.

  • 그래프 입력 테이블 만들기
  • 속성 그래프 만들기
  • 선택사항: 역방향 에지 순회 색인을 사용하여 역방향 순회 성능을 개선합니다.
  • 선택사항: 라벨별 쿼리 성능을 개선하는 라벨 색인
  • 선택사항: 소문자 라벨속성 이름을 적용하는 스키마 제약 조건

다음 예시는 입력 테이블과 속성 그래프를 만드는 방법을 보여줍니다.

CREATE TABLE GraphNode (
  id INT64 NOT NULL,
  label STRING(MAX) NOT NULL,
  properties JSON
) PRIMARY KEY (id);

CREATE TABLE GraphEdge (
  id INT64 NOT NULL,
  dest_id INT64 NOT NULL,
  edge_id INT64 NOT NULL,
  label STRING(MAX) NOT NULL,
  properties JSON
) PRIMARY KEY (id, dest_id, edge_id),
  INTERLEAVE IN PARENT GraphNode;

CREATE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNode
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  )
  EDGE TABLES (
    GraphEdge
      SOURCE KEY (id) REFERENCES GraphNode(id)
      DESTINATION KEY (dest_id) REFERENCES GraphNode(id)
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  );

다음 예시에서는 색인을 사용하여 역방향 에지 순회를 개선합니다. STORING (properties) 절에는 이러한 속성을 필터링하는 쿼리의 속도를 높이는 에지 속성 사본이 포함됩니다. 쿼리에 STORING (properties) 절이 도움이 되지 않는 경우 생략할 수 있습니다.

CREATE INDEX R_EDGE ON GraphEdge (dest_id, id, edge_id) STORING (properties),
INTERLEAVE IN GraphNode;

다음 예시에서는 라벨 색인을 사용하여 라벨별로 노드를 일치시키는 속도를 높입니다.

CREATE INDEX IDX_NODE_LABEL ON GraphNode (label);

다음 예시에서는 소문자 라벨 및 속성을 적용하는 제약 조건을 추가합니다. 마지막 두 예시에서는 JSON_KEYS 함수를 사용합니다. 원하는 경우 애플리케이션 로직에서 소문자 검사를 적용할 수 있습니다.

ALTER TABLE GraphNode ADD CONSTRAINT node_label_lower_case
CHECK(LOWER(label) = label);

ALTER TABLE GraphEdge ADD CONSTRAINT edge_label_lower_case
CHECK(LOWER(label) = label);

ALTER TABLE GraphNode ADD CONSTRAINT node_property_keys_lower_case
CHECK(
  NOT array_includes(COALESCE(JSON_KEYS(properties, 1), []), key->key<>LOWER(key)));

ALTER TABLE GraphEdge ADD CONSTRAINT edge_property_keys_lower_case
CHECK(
  NOT array_includes(COALESCE(JSON_KEYS(properties, 1), []), key->key<>LOWER(key)));

DML을 사용하여 동적 속성의 일괄 업데이트 최적화

JSON_SETJSON_REMOVE와 같은 함수를 사용하여 동적 속성을 수정하면 읽기-수정-쓰기 작업이 발생합니다. STRING 또는 INT64 유형의 속성을 업데이트하는 것보다 비용이 더 많이 들 수 있습니다.

워크로드에 DML을 사용하여 동적 속성을 일괄 업데이트하는 작업이 포함된 경우 다음 권장사항을 고려하여 성능을 개선하세요.

  • 행을 개별적으로 처리하는 대신 단일 DML 문에서 여러 행을 업데이트합니다.

  • 광범위한 키 범위를 업데이트할 때는 기본 키를 기준으로 영향을 받는 행을 그룹화하고 정렬합니다. 각 DML로 겹치지 않는 범위를 업데이트하면 잠금 경합이 줄어듭니다.

  • 성능을 개선하기 위해 DML 문에서 하드코딩 대신 쿼리 파라미터를 사용하세요.

이러한 제안을 바탕으로 다음 예시에서는 단일 DML 문에 100개 노드의 is_blocked 속성을 업데이트합니다. 쿼리 매개변수에는 다음이 포함됩니다.

  1. @node_ids: ARRAY 파라미터에 저장된 GraphNode 행의 키입니다. 해당하는 경우 DML 전반에서 그룹화하고 정렬하면 성능이 향상됩니다.

  2. @is_blocked_values: 업데이트할 상응하는 값으로, ARRAY 파라미터에 저장됩니다.

UPDATE GraphNode
SET properties = JSON_SET(
  properties,
  '$.is_blocked',
  CASE id
    WHEN @node_ids[OFFSET(0)] THEN @is_blocked_values[OFFSET(0)]
    WHEN @node_ids[OFFSET(1)] THEN @is_blocked_values[OFFSET(1)]
    ...
    WHEN @node_ids[OFFSET(99)] THEN @is_blocked_values[OFFSET(99)]
  END,
  create_if_missing => TRUE)
WHERE id IN UNNEST(@node_ids)

문제 해결

이 섹션에서는 스키마 없는 데이터 관련 문제를 해결하는 방법을 설명합니다.

속성이 TO_JSON 결과에 두 번 이상 표시됨

문제

다음 노드는 birthdayname 속성을 JSON 열의 동적 속성으로 모델링합니다. 그래프 요소 JSON 결과에 birthdayname의 중복 속성이 표시됩니다.

GRAPH FinGraph
MATCH (n: Person {id: 14})
RETURN SAFE_TO_JSON(n) AS n;

그러면 다음과 비슷한 결과가 반환됩니다.

{
  ,
  "properties": {
    "birthday": "1991-12-21 00:00:00",
    "name": "Alex",
    "id": 14,
    "label": "person",
    "properties": {
      "birthday": "1991-12-21 00:00:00",
      "name": "Alex"
    }
  }
  
}

가능한 원인

기본적으로 기본 테이블의 모든 열은 속성으로 정의됩니다. TO_JSON 또는 SAFE_TO_JSON을 사용하여 그래프 요소를 반환하면 중복 속성이 발생합니다. 이는 JSON 열(properties)이 스키마 정의 속성인 반면 JSON의 최상위 키는 동적 속성으로 모델링되기 때문입니다.

추천 솔루션

이 동작을 방지하려면 다음 예시와 같이 스키마에서 속성을 정의할 때 PROPERTIES ALL COLUMNS EXCEPT 절을 사용하여 properties 열을 제외합니다.

CREATE OR REPLACE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNode
      PROPERTIES ALL COLUMNS EXCEPT (properties)
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  );

스키마가 변경된 후에는 JSON 데이터 유형의 반환된 그래프 요소에 중복이 없습니다.

GRAPH FinGraph
MATCH (n: Person {id: 1})
RETURN TO_JSON(n) AS n;

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

{
  
  "properties": {
    "birthday": "1991-12-21 00:00:00",
    "name": "Alex",
    "id": 1,
    "label": "person",
  }
}

속성 값이 제대로 변환되지 않을 때 발생하는 일반적인 문제

다음 문제의 일반적인 해결 방법은 쿼리 표현식 내에서 속성을 사용할 때 항상 속성 값 변환을 사용하는 것입니다.

변환 없이 속성 값 비교

문제

No matching signature for operator = for argument types: JSON, STRING

가능한 원인

쿼리가 속성 값을 올바르게 변환하지 않습니다. 예를 들어 name 속성은 비교 시 STRING 유형으로 변환되지 않습니다.

GRAPH FinGraph
MATCH (p:Person)
WHERE p.name = "Alex"
RETURN p.id;

추천 솔루션

이 문제를 해결하려면 비교 전에 값 변환을 사용하세요.

GRAPH FinGraph
MATCH (p:Person)
WHERE STRING(p.name) = "Alex"
RETURN p.id;

그러면 다음과 비슷한 결과가 반환됩니다.

+------+
| id   |
+------+
| 1    |
+------+

또는 속성 필터를 사용하여 값 변환이 자동으로 이루어지는 등식 비교를 간소화할 수 있습니다. 값 유형('Alex')은 JSON의 속성 STRING 유형과 정확하게 일치해야 합니다.

GRAPH FinGraph
MATCH (p:Person {name: 'Alex'})
RETURN p.id;

그러면 다음과 비슷한 결과가 반환됩니다.

+------+
| id   |
+------+
| 1    |
+------+

변환 없이 RETURN DISTINCT 속성 값 사용

문제

Column order_number_str of type JSON cannot be used in `RETURN DISTINCT

가능한 원인

다음 예시에서 order_number_strRETURN DISTINCT 문에 사용되기 전에 변환되지 않았습니다.

GRAPH FinGraph
MATCH -[t:Transfers]->
RETURN DISTINCT t.order_number_str AS order_number_str;

추천 솔루션

이 문제를 해결하려면 RETURN DISTINCT 전에 값 변환을 사용하세요.

GRAPH FinGraph
MATCH -[t:Transfers]->
RETURN DISTINCT STRING(t.order_number_str) AS order_number_str;

그러면 다음과 비슷한 결과가 반환됩니다.

+-----------------+
| order_number_str|
+-----------------+
| 302290001255747 |
| 103650009791820 |
| 304330008004315 |
| 304120005529714 |
+-----------------+

변환 없이 그룹화 키로 사용되는 속성

문제

Grouping by expressions of type JSON is not allowed.

가능한 원인

다음 예시에서는 t.order_number_str이 JSON 객체를 그룹화하는 데 사용되기 전에 변환되지 않습니다.

GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->(b:Account)
RETURN t.order_number_str, COUNT(*) AS total_transfers;

추천 솔루션

이 문제를 해결하려면 속성을 그룹화 키로 사용하기 전에 값 변환을 사용하세요.

GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->(b:Account)
RETURN STRING(t.order_number_str) AS order_number_str, COUNT(*) AS total_transfers;

그러면 다음과 비슷한 결과가 반환됩니다.

+-----------------+------------------+
| order_number_str | total_transfers |
+-----------------+------------------+
| 302290001255747 |                1 |
| 103650009791820 |                1 |
| 304330008004315 |                1 |
| 304120005529714 |                2 |
+-----------------+------------------+

변환 없이 정렬 키로 사용되는 속성

문제

ORDER BY does not support expressions of type JSON

가능한 원인

다음 예시에서 t.amount 결과 정렬에 사용되기 전에 변환되지 않습니다.

GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->(b:Account)
RETURN a.Id AS from_account, b.Id AS to_account, t.amount
ORDER BY t.amount DESC
LIMIT 1;

추천 솔루션

이 문제를 해결하려면 ORDER BY 절에서 t.amount를 변환합니다.

GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->(b:Account)
RETURN a.Id AS from_account, b.Id AS to_account, t.amount
ORDER BY DOUBLE(t.amount) DESC
LIMIT 1;

그러면 다음과 비슷한 결과가 반환됩니다.

+--------------+------------+--------+
| from_account | to_account | amount |
+--------------+------------+--------+
|           20 |          7 | 500    |
+--------------+------------+--------+

변환 중 유형 불일치

문제

The provided JSON input is not an integer

가능한 원인

다음 예시에서 order_number_str 속성은 JSON STRING 데이터 유형으로 저장됩니다. INT64로 변환하려고 하면 오류가 반환됩니다.

GRAPH FinGraph
MATCH -[e:Transfers]->
WHERE INT64(e.order_number_str) = 302290001255747
RETURN e.amount;

추천 솔루션

이 문제를 해결하려면 값 유형과 일치하는 정확한 값 변환기를 사용하세요.

GRAPH FinGraph
MATCH -[e:Transfers]->
WHERE STRING(e.order_number_str) = "302290001255747"
RETURN e.amount;

그러면 다음과 비슷한 결과가 반환됩니다.

+-----------+
| amount    |
+-----------+
| JSON"200" |
+-----------+

또는 다음 예시와 같이 값을 대상 유형으로 변환할 수 있는 경우 유연한 변환기를 사용하세요.

GRAPH FinGraph
MATCH -[e:Transfers]->
WHERE LAX_INT64(e.order_number_str) = 302290001255747
RETURN e.amount;

그러면 다음과 비슷한 결과가 반환됩니다.

+-----------+
| amount    |
+-----------+
| JSON"200" |
+-----------+

다음 단계