검색 색인

이 페이지에서는 테이블에 검색 색인을 추가하는 방법을 설명합니다. 검색 색인은 Spanner에서 전체 텍스트 검색을 허용하기 위해 필요한 테이블 구조를 만드는 데 필요합니다.

전체 텍스트 검색 색인

전체 텍스트 검색에 사용할 열에 검색 색인을 만들 수 있습니다. 검색 색인을 만들려면 CREATE SEARCH INDEX DDL 문을 사용하거나 ALTER SEARCH INDEX DDL 문을 사용하여 업데이트합니다. Spanner는 데이터베이스에서 데이터가 변경되는 즉시 검색 색인에서 데이터를 추가 및 업데이트하는 등 검색 색인을 자동으로 빌드하고 유지보수합니다.

검색 색인은 가속하려는 쿼리의 유형에 따라 파티션을 나눈 색인 또는 파티션을 나누지 않은 색인일 수 있습니다.

  • 파티션을 나눈 색인이 가장 적합한 예는 애플리케이션이 이메일 편지함을 쿼리하는 경우입니다. 각 쿼리가 특정 편지함으로 제한됩니다.

  • 파티션을 나누지 않은 쿼리가 가장 적합한 예는 제품 카탈로그의 모든 제품 카테고리에 대한 쿼리가 있는 경우입니다.

Spanner 검색 색인은 전체 텍스트 검색 외에도 다음을 지원합니다.

  • 하위 문자열 검색: 더 긴 텍스트 본문 내에서 더 짧은 문자열(하위 문자열)을 찾는 쿼리 유형입니다.
  • 숫자 및 일치검색 표현식이 있는 쿼리의 가속화
  • 색인이 생성된 데이터의 하위 집합에 대한 조건을 단일 색인 스캔으로 결합

검색 색인은 숫자, 일치검색 문자열과 같이 텍스트가 아닌 데이터의 사용을 지원하지만 검색 색인의 가장 일반적인 사용 사례는 문서의 텍스트에 대한 색인을 생성하는 것입니다.

검색 색인의 기능을 설명하기 위해 음악 앨범에 대한 정보를 저장하는 테이블이 있다고 가정해 보겠습니다.

CREATE TABLE Albums (
  AlbumId STRING(MAX) NOT NULL,
  AlbumTitle STRING(MAX),
) PRIMARY KEY(AlbumId);

Spanner에는 토큰을 만드는 여러 토큰화 함수가 있습니다. 사용자가 전체 텍스트 검색을 실행하여 앨범 제목을 찾을 수 있도록 이전 표를 수정하려면 TOKENIZE_FULLTEXT 함수를 사용하여 앨범 제목으로 토큰을 만듭니다. 그런 다음 TOKENLIST 데이터 유형을 사용하여 TOKENIZE_FULLTEXT에서의 토큰화 출력을 저장하는 열을 만듭니다. 이 예시에서는 AlbumTitle_Tokens 열을 만듭니다.

ALTER TABLE Albums
  ADD COLUMN AlbumTitle_Tokens TOKENLIST
  AS (TOKENIZE_FULLTEXT(AlbumTitle)) HIDDEN;

다음은 CREATE SEARCH INDEX DDL을 사용하여 AlbumTitle 토큰(AlbumTitle_Tokens)에 대한 검색 색인(AlbumsIndex)을 만듭니다.

CREATE SEARCH INDEX AlbumsIndex
  ON Albums(AlbumTitle_Tokens);

검색 색인을 추가한 후 SQL 쿼리를 사용하여 검색 기준과 일치하는 앨범을 찾습니다. 예를 들면 다음과 같습니다.

SELECT AlbumId
FROM Albums
WHERE SEARCH(AlbumTitle_Tokens, "fifth symphony")

데이터 일관성

색인이 생성되면 Spanner는 자동 프로세스를 사용하여 데이터를 백필하여 일관성을 보장합니다. 쓰기가 커밋되면 색인이 동일한 트랜잭션에서 업데이트됩니다. Spanner에서 자동으로 데이터 일관성 검사를 수행합니다.

검색 색인 스키마 정의

검색 색인은 테이블의 하나 이상의 TOKENLIST 열에 정의됩니다. 검색 색인에는 다음과 같은 구성요소가 있습니다.

  • 기본 테이블: 색인이 필요한 Spanner 테이블입니다.
  • TOKENLIST: 색인이 필요한 토큰을 정의하는 열 모음입니다. 이러한 열의 순서는 중요하지 않습니다.

예를 들어 다음 문에서 기본 테이블은 Albums입니다. TOKENLIST 열은 AlbumTitle(AlbumTitle_Tokens) 및 Rating(Rating_Tokens)에 생성됩니다.

CREATE TABLE Albums (
  AlbumId STRING(MAX) NOT NULL,
  SingerId INT64 NOT NULL,
  ReleaseTimestamp INT64 NOT NULL,
  AlbumTitle STRING(MAX),
  Rating FLOAT64,
  AlbumTitle_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(AlbumTitle)) HIDDEN,
  Rating_Tokens TOKENLIST AS (TOKENIZE_NUMBER(Rating)) HIDDEN,
) PRIMARY KEY(AlbumId);

다음 CREATE SEARCH INDEX 문을 사용하여 AlbumTitleRating에 대한 토큰으로 검색 색인을 만듭니다.

CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens, Rating_Tokens)
PARTITION BY SingerId
ORDER BY ReleaseTimestamp DESC

검색 색인에는 다음과 같은 옵션이 있습니다.

  • 파티션: 검색 색인을 분할하는 열 그룹(선택사항)입니다. 파티션을 나눈 색인을 쿼리하는 것이 파티션을 나누지 않은 색인을 쿼리하는 것보다 훨씬 효율적일 때가 많습니다. 자세한 내용은 파티션 검색 색인을 참조하세요.
  • 정렬 순서 열: 검색 색인에서 검색 순서를 설정하는 INT64 열(선택사항)입니다. 자세한 내용은 검색 색인 정렬 순서를 참고하세요.
  • 인터리브 처리: 보조 색인과 마찬가지로 검색 색인도 인터리브 처리할 수 있습니다. 인터리브 처리된 검색 색인은 쓰기 및 기본 테이블 조인에 더 적은 리소스를 사용합니다. 자세한 내용은 인터리브 처리된 검색 색인을 참고하세요.
  • 옵션 절: 검색 색인의 기본 설정을 재정의하는 키-값 쌍 목록입니다.

자세한 내용은 CREATE SEARCH INDEX 참조 문서를 확인하세요.

검색 색인의 내부 레이아웃

검색 색인의 내부 표현에서 중요한 요소는 docid입니다. 이 요소는 임의의 길이가 될 수 있는 기본 테이블의 기본 키를 스토리지에 효율적인 표현 방식으로 나타냅니다. 또한 CREATE SEARCH INDEX 절의 사용자 제공 ORDER BY 열에 따라 내부 데이터 레이아웃의 순서를 생성합니다. 1개 또는 2개의 64비트 정수로 표현됩니다.

검색 색인은 내부적으로 2가지 수준의 매핑으로 구현됩니다.

  1. docid에 대한 토큰
  2. 기본 테이블 기본 키에 대한 docid

이러한 스키마를 사용하면 Spanner가 각 <token, document> 쌍에 대해 전체 기본 테이블 기본 키를 저장할 필요가 없으므로 상당한 스토리지를 절약할 수 있습니다.

두 가지 매핑 수준을 구현하는 물리적 색인에는 두 가지 유형이 있습니다.

  1. 파티션 키와 docid를 기본 테이블 기본 키에 매핑하는 보조 색인. 이전 섹션의 예시에서 {SingerId, ReleaseTimestamp, uid}{SingerId, AlbumId}에 매핑합니다. 보조 색인은 CREATE SEARCH INDEXSTORING 절에 지정된 모든 열도 저장합니다.
  2. 토큰 색인은 정보 검색 문헌의 역색인과 마찬가지로 토큰을 docid에 매핑합니다. Spanner는 검색 색인의 각 TOKENLIST에 대해 개별 토큰 색인을 유지합니다. 논리적으로 토큰 색인은 각 파티션 내의 각 토큰에 대한 docid 목록을 유지합니다(정보 검색에서 게시 목록이라고 함). 이 목록은 빠른 검색을 위해 토큰별로 정렬되며 목록 내에서는 정렬을 위해 docid가 사용됩니다. 개별 토큰 색인은 Spanner API를 통해 노출되지 않는 구현 세부정보입니다.

Spanner는 docid에 대해 다음 4가지 옵션을 지원합니다.

검색 색인 docid 동작
검색 색인에서 ORDER BY 절이 생략됨 { uid } Spanner는 각 행을 식별하기 위해 숨겨진 고유한 값(UID)을 추가합니다.
ORDER BY column { column, uid } Spanner는 파티션 내에서 column 값이 동일한 행 간에 UID 열을 결정자로 추가합니다.
ORDER BY column ... OPTIONS (disable_automatic_uid_column=true) { column } UID 열이 추가되지 않았습니다. column 값은 파티션 내에서 고유해야 합니다.
ORDER BY column1, column2 ... OPTIONS (disable_automatic_uid_column=true) { column1, column2 } UID 열이 추가되지 않았습니다. column1, column2 값의 조합은 파티션 내에서 고유해야 합니다.

사용 참고사항:

  • 내부 UID 열은 Spanner API를 통해 노출되지 않습니다.
  • UID가 추가되지 않은 색인에서는 (파티션, 정렬 순서가) 동일한 행 두 개가 생성되면 모든 트랜잭션이 실패합니다.

예를 들어 다음 데이터를 살펴보세요.

AlbumId SingerId ReleaseTimestamp AlbumTitle
a1 1 997 대형견
a2 1 743 대형 고양이과 동물

사전 정렬 열이 오름차순이라고 가정하면 SingerId로 파티션을 나눈 토큰 색인의 콘텐츠는 다음과 같은 방법으로 토큰 색인의 콘텐츠를 파티션으로 나눕니다.

SingerId _token ReleaseTimestamp uid
1 743 uid1
1 997 uid2
1 고양이 743 uid1
1 997 uid2

검색 색인 샤딩

Spanner는 테이블을 분할할 때 특정 기본 테이블 행에 있는 모든 토큰이 동일한 분할에 있도록 검색 색인 데이터를 분산합니다. 즉, 검색 색인이 문서로 샤딩됩니다. 이 샤딩 전략은 성능에 상당한 영향을 미칩니다.

  1. 각 트랜잭션이 통신하는 서버 수는 토큰 수 또는 색인이 생성된 TOKENLIST 열 수와 관계없이 일정하게 유지됩니다.
  2. 여러 조건부 표현식이 포함된 검색어는 각 분할에서 독립적으로 실행되므로 분산 조인과 관련된 성능 오버헤드를 방지할 수 있습니다.

검색 색인에는 두 가지 배포 모드가 있습니다.

  • 균일 샤딩(기본값). 균일 샤딩에서는 각 기본 테이블 행의 색인 생성된 데이터가 파티션의 색인 분할에 무작위로 할당됩니다.
  • 정렬 순서 샤딩. 정렬 순서 샤딩에서 각 기본 테이블 행의 데이터는 ORDER BY 열을 기준으로 파티션의 색인 분할에 할당됩니다. 예를 들어 내림차순 정렬 순서의 경우 정렬 순서 값이 가장 높은 모든 행이 파티션의 첫 번째 색인 분할에 표시되고 그 다음으로 높은 정렬 순서 값 그룹이 다음 분할에 표시됩니다.

이러한 샤딩 모드로 부하 집중 위험과 쿼리 비용 사이에 절충이 이루어집니다.

  • 정렬 순서 샤딩된 검색 색인은 색인이 타임스탬프를 기준으로 정렬될 때 부하 집중이 발생하기 쉽습니다. 자세한 내용은 기본 키 선택으로 핫스팟 방지를 참조하세요. 반면에 여러 문서에 대한 쓰기 부하가 증가하면 균일한 샤딩을 통해 증가량이 여러 샤드에 균일하게 분산됩니다.
  • 표준 부하 기반 분할은 부하 집중으로부터 적절한 보호를 제공하는 추가 분할을 만듭니다. 균일한 샤딩의 단점은 일부 유형의 쿼리에 더 많은 리소스를 사용할 수 있다는 것입니다.

검색 색인의 샤딩 모드는 OPTIONS 절을 사용하여 구성됩니다.

CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens, Rating_Tokens)
PARTITION BY SingerId
ORDER BY ReleaseTimestamp DESC
OPTIONS (sort_order_sharding = true);

sort_order_sharding=false를 설정하거나 지정하지 않으면 균일한 샤딩을 사용하여 검색 색인이 생성됩니다.

인터리브 처리된 검색 색인

보조 색인과 마찬가지로 기본 테이블의 상위 테이블에서 검색 색인을 인터리브 처리할 수 있습니다. 인터리브 처리된 검색 색인을 사용하는 주된 이유는 작은 파티션의 색인 데이터와 기본 테이블 데이터를 같은 위치에 배치하기 위함입니다. 이러한 상황별 공동 배치에는 다음과 같은 이점이 있습니다.

  • 쓰기가 2단계 커밋을 실행할 필요가 없습니다.
  • 검색 색인과 기본 테이블의 백 조인이 배포되지 않습니다.

인터리브 처리된 검색 색인에는 다음과 같은 제한사항이 있습니다.

  1. 정렬 순서 샤딩된 색인만 인터리브 처리할 수 있습니다.
  2. 검색 색인은 하위 테이블이 아닌 최상위 테이블에서만 인터리브 처리될 수 있습니다.
  3. 인터리브 처리된 테이블 및 보조 색인과 마찬가지로 상위 테이블의 키를 인터리브 처리된 검색 색인의 PARTITION BY 열의 프리픽스로 만듭니다.

다음 예시에서는 인터리브 처리된 검색 색인을 정의하는 방법을 보여줍니다.

CREATE TABLE Singers (
  SingerId INT64 NOT NULL,
) PRIMARY KEY(SingerId);

CREATE TABLE Albums (
  SingerId INT64 NOT NULL,
  AlbumId STRING(MAX) NOT NULL,
  AlbumTitle STRING(MAX),
  AlbumTitle_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(AlbumTitle)) HIDDEN,
) PRIMARY KEY(SingerId, AlbumId),
  INTERLEAVE IN PARENT Singers ON DELETE CASCADE;

CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens)
PARTITION BY SingerId
INTERLEAVE IN Singers
OPTIONS (sort_order_sharding = true);

NULL 필터링 검색 색인

검색 색인은 WHERE column IS NOT NULL 구문을 사용하여 기본 테이블 행을 제외할 수 있습니다. NULL 필터링은 파티셔닝 키, 정렬 순서 열, 저장된 열에 적용할 수 있습니다. 저장된 배열 열에 대한 NULL 필터링은 허용되지 않습니다.

예시

CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens)
STORING Genre
WHERE Genre IS NOT NULL

쿼리에서 WHERE 절에 NULL 필터링 조건(이 예시의 경우 Genre IS NOT NULL)을 지정해야 합니다. 그렇지 않으면 쿼리 최적화 도구가 검색 색인을 사용할 수 없습니다. 자세한 내용은 SQL 쿼리 요구사항을 참조하세요.

생성된 열에서 NULL 필터링을 사용하여 임의의 기준에 따라 행을 제외합니다. 자세한 내용은 생성된 열을 사용하여 부분 색인 만들기를 참고하세요.

다음 단계