여기서는 Spanner로 수행하는 모든 삽입 및 업데이트 작업 시 커밋 타임스탬프를 기록하는 방법에 대해 설명합니다.
커밋 타임스탬프 개요
TrueTime 기술에 기반을 둔 커밋 타임스탬프는 데이터베이스에서 트랜잭션이 커밋되는 시간입니다. 트랜잭션의 커밋 타임스탬프를 열에 원자적으로 저장할 수 있습니다. 테이블에 저장된 커밋 타임스탬프를 사용하여 변형의 정확한 순서를 결정하고 변경 로그와 같은 기능을 빌드할 수 있습니다.
데이터베이스에 커밋 타임스탬프를 삽입하려면 다음 단계를 완료하세요.
SPANNER.COMMIT_TIMESTAMP
유형의 열을 만듭니다. 예를 들면 다음과 같습니다.CREATE TABLE Performances ( ... LastUpdateTime SPANNER.COMMIT_TIMESTAMP NOT NULL, ... PRIMARY KEY (...) ) ;
DML을 사용하여 삽입 또는 업데이트를 수행하는 경우
SPANNER.PENDING_COMMIT_TIMESTAMP()
함수를 사용하여 커밋 타임스탬프를 씁니다.준비된 문 또는 변형으로 삽입 또는 업데이트를 수행하는 경우 커밋 타임스탬프 열에 자리표시자 문자열
SPANNER.COMMIT_TIMESTAMP()
를 사용합니다. 클라이언트 라이브러리에서 제공하는 커밋 타임스탬프 상수를 사용할 수도 있습니다. 예를 들어 자바 클라이언트의 상수는Value.COMMIT_TIMESTAMP
입니다.
Spanner가 이러한 자리표시자를 열 값으로 사용하여 트랜잭션을 커밋하면 실제 커밋 타임스탬프가 지정된 열에 기록됩니다. 그런 다음 이 열 값을 사용해서 업데이트 기록을 테이블에 만들 수 있습니다.
커밋 타임스탬프 값은 고유한 값이 아닐 수도 있습니다. 중복되지 않는 필드 세트에 기록하는 트랜잭션은 동일한 타임스탬프를 가질 수 있습니다. 중복된 필드 세트에 기록하는 트랜잭션은 고유한 타임스탬프를 가집니다.
Spanner 커밋 타임스탬프는 마이크로초 단위로 기록되며 SPANNER.COMMIT_TIMESTAMP
열에 저장될 때는 나노초로 변환됩니다.
키와 색인
커밋 타임스탬프 열을 기본 키 열이나 키 열이 아닌 열로 사용할 수 있습니다. 기본 키는 ASC
또는 DESC
로 정의될 수 있습니다.
ASC
(기본값) - 오름차순 키는 특정 시간부터 쿼리에 답변하는 데 적합합니다.DESC
- 내림차순 키는 최신 행을 테이블의 상단에 유지합니다. 따라서 최신 레코드에 빨리 액세스할 수 있습니다.
핫스팟 방지
다음 시나리오에서 커밋 타임스탬프를 사용하면 데이터 성능을 저하시키는 핫스팟이 생성됩니다.
커밋 타임스탬프 열을 테이블 기본 키의 첫 번째 부분으로 사용합니다.
CREATE TABLE Users ( LastAccess SPANNER.COMMIT_TIMESTAMP NOT NULL, UserId INT64 NOT NULL, ... PRIMARY KEY (LastAccess, UserId) ) ;
커밋 타임스탬프 기본 키 열을 보조 색인의 첫 번째 부분으로 사용합니다.
CREATE INDEX UsersByLastAccess ON Users(LastAccess)
또는
CREATE INDEX UsersByLastAccessAndName ON Users(LastAccess, FirstName)
핫스팟은 낮은 쓰기 속도에서도 데이터 성능을 저하시킵니다. 색인이 생성되지 않은 키 열이 아닌 열에 커밋 타임스탬프를 사용 설정해도 성능 오버헤드가 발생하지 않습니다.
기존 테이블에 커밋 타임스탬프 열 추가하기
기존 테이블에 커밋 타임스탬프 열을 추가하려면 ALTER TABLE
문을 사용합니다. 예를 들어 LastUpdateTime
열을 Performances
테이블에 추가하려면 다음 문을 사용합니다.
ALTER TABLE Performances ADD COLUMN LastUpdateTime SPANNER.COMMIT_TIMESTAMP
NOT NULL;
DML 문을 사용하여 커밋 타임스탬프 쓰기
SPANNER.PENDING_COMMIT_TIMESTAMP()
함수를 사용하여 DML 문에서 커밋 타임스탬프를 씁니다. Spanner는 트랜잭션이 커밋될 때 커밋 타임스탬프를 선택합니다.
다음 DML 문은 Performances
테이블의 LastUpdateTime
열을 커밋 타임스탬프로 업데이트합니다.
UPDATE Performances SET LastUpdateTime = SPANNER.PENDING_COMMIT_TIMESTAMP()
WHERE SingerId=1 AND VenueId=2 AND EventDate="2015-10-21"
변형을 사용하여 행 삽입
행을 삽입할 경우, Spanner는 사용자가 열 목록에 해당 열을 포함시키고 spanner.commit_timestamp()
자리표시자 문자열(또는 클라이언트 라이브러리 상수)을 해당 값으로 전달할 경우에만 커밋 타임스탬프 값을 기록합니다. 예를 들면 다음과 같습니다.
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
여러 테이블의 행에 변형이 있는 경우 각 테이블의 커밋 타임스탬프 열에 spanner.commit_timestamp()
(또는 클라이언트 라이브러리 상수)를 지정해야 합니다.
변형을 사용하여 행 업데이트
행을 업데이트할 경우, Spanner는 사용자가 열 목록에 해당 열을 포함시키고 spanner.commit_timestamp()
자리표시자 문자열(또는 클라이언트 라이브러리 상수)을 해당 값으로 전달할 경우에만 커밋 타임스탬프 값을 기록합니다. 행의 기본 키는 업데이트할 수 없습니다. 기본 키를 업데이트하려면 기존 행을 삭제하고 새 행을 만듭니다.
예를 들어 이름이 LastUpdateTime
인 커밋 타임스탬프 열을 업데이트하려면 다음을 수행합니다.
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
여러 테이블의 행에 변형이 있는 경우 각 테이블의 커밋 타임스탬프 열에 spanner.commit_timestamp()
(또는 클라이언트 라이브러리 상수)를 지정해야 합니다.
커밋 타임스탬프 열 쿼리
다음의 예는 테이블의 커밋 타임스탬프 열을 쿼리합니다.
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
커밋 타임스탬프 열에 고유한 값 지정
코드에서 spanner.commit_timestamp()
(또는 사용 가능한 클라이언트 라이브러리 상수)를 열 값으로 전달하는 대신에 커밋 타임스탬프 열에 고유한 값을 지정할 수 있습니다. 이 값은 과거의 타임스탬프여야 합니다. 이 같은 제한은 타임스탬프 기록 작업을 저렴하고 신속한 작업으로 만들어 줍니다. 과거의 값인지 확인하는 쉬운 방법은 CURRENT_TIMESTAMP
SQL 함수가 반환하는 값과 비교하는 것입니다.
미래의 타임스탬프가 지정되면 서버가 FailedPrecondition
오류를 반환합니다.
변경 로그 만들기
테이블에 발생하는 모든 변형에 대한 변경 로그를 만들어서 감사에 사용하려는 경우를 가정해 보겠습니다. 변경 내역을 워드 프로세싱 문서로 저장하는 테이블을 예로 들 수 있습니다. 커밋 타임스탬프를 사용하면 변경 로그를 만들기가 더 쉽습니다. 타임스탬프는 변경 로그 항목의 순서를 강제할 수 있기 때문입니다. 다음의 예시와 같은 스키마를 사용하여 특정 문서에 대한 변경 내역을 저장하는 변경 로그를 빌드할 수 있습니다.
CREATE TABLE Documents (
UserId int8 NOT NULL,
DocumentId int8 NOT NULL,
Contents text NOT NULL,
PRIMARY KEY (UserId, DocumentId)
);
CREATE TABLE DocumentHistory (
UserId int8 NOT NULL,
DocumentId int8 NOT NULL,
Ts SPANNER.COMMIT_TIMESTAMP NOT NULL,
Delta text,
PRIMARY KEY (UserId, DocumentId, Ts)
) INTERLEAVE IN PARENT Documents;
변경 로그를 만들려면, DocumentHistory
에 행을 삽입하거나 업데이트하는 동일한 트랜잭션에서 Document
에 새 행을 삽입합니다. DocumentHistory
에 새 행을 삽입할 때, 자리표시자 spanner.commit_timestamp()
(또는 클라이언트 라이브러리 상수)를 사용하여 Spanner에게 Ts
열에 커밋 타임스탬프를 기록하라고 명령합니다. DocumentsHistory
테이블과 Documents
테이블을 인터리브 처리하면 데이터 위치 파악과 더 효율적인 삽입 및 업데이트가 가능해집니다. 그러나 상위 행과 하위 행을 함께 삭제해야 한다는 제약도 추가됩니다. Documents
의 행 다음에 있는 DocumentHistory
의 행이 삭제되도록 하려면 이들 테이블을 인터리브 처리하지 마세요.
커밋 타임스탬프로 최근 데이터 쿼리 최적화
커밋 타임스탬프를 사용하면 특정 시간 이후에 작성된 데이터를 검색할 때 쿼리 I/O를 줄일 수 있는 Spanner 최적화가 사용 설정됩니다.
이 최적화를 활성화하려면 쿼리의 WHERE
절에 다음 속성을 사용하여 테이블의 커밋 타임스탬프 열과 사용자가 제공하는 특정 시간 간의 비교를 포함해야 합니다.
특정 시간을 상수 표현식(리터럴, 매개변수 또는 자체 인수가 상수로 평가되는 함수)으로 제공합니다.
>
또는>=
연산자를 사용하여 커밋 타임스탬프가 지정된 시간보다 최신인지 여부를 비교합니다.필요한 경우
AND
를 사용하여WHERE
절에 제약사항을 더 추가합니다.OR
을 사용하여 절을 확장하면 이 최적화에서 쿼리가 유효하지 않습니다.
예를 들어 커밋 타임스탬프 열이 포함된 다음 Performances
테이블을 살펴보겠습니다.
CREATE TABLE Performances (
SingerId bigint NOT NULL,
VenueId bigint NOT NULL,
EventDate timestamp with time zone NOT NULL,
Revenue bigint,
LastUpdateTime spanner.commit_timestamp,
PRIMARY KEY(SingerId, VenueId, EventDate)
);
이 쿼리는 테이블의 커밋 타임스탬프 열과 상수 표현식(이 경우 리터럴) 간에 크거나 같음 비교가 있기 때문에 앞서 설명한 커밋 타임스탬프 최적화의 이점을 얻습니다.
SELECT * FROM Performances WHERE LastUpdateTime >= '2022-01-01';