일괄 쓰기를 사용하여 데이터 수정

이 페이지에서는 Spanner 일괄 쓰기 요청과 이를 사용하여 Spanner 데이터를 수정하는 방법을 설명합니다.

Spanner 일괄 쓰기를 사용하여 Spanner 테이블에서 여러 행을 삽입, 업데이트, 삭제할 수 있습니다. Spanner 일괄 쓰기는 읽기 작업 없이 지연 시간이 짧은 쓰기를 지원하며, 변형이 일괄 적용될 때 응답을 반환합니다. 일괄 쓰기를 사용하려면 서로 관련된 변형을 그룹화합니다. 그룹의 모든 변형은 원자적으로 커밋됩니다. 그룹 간의 변형은 지정되지 않은 순서로 적용되며 서로 독립되어 있습니다(비원자적). Spanner는 응답을 보내기 전에 모든 변형이 적용될 때까지 기다릴 필요가 없습니다. 즉, 일괄 쓰기에서 부분 오류가 발생할 수 있습니다. 한 번에 여러 일괄 쓰기를 실행할 수도 있습니다. 자세한 내용은 일괄 쓰기 사용 방법을 참조하세요.

사용 사례

Spanner 일괄 쓰기는 읽기 작업 없이 많은 쓰기 작업을 커밋하면서 모든 변형에 대해 원자적 트랜잭션이 필요하지는 않은 경우에 특히 유용합니다.

DML 요청을 일괄 처리하려면 일괄 DML을 사용하여 Spanner 데이터를 수정합니다. DML과 변형의 차이점에 대한 자세한 내용은 DML과 변형 비교를 참조하세요.

단일 변형 요청의 경우 읽기-쓰기 잠금 트랜잭션을 사용하는 것이 좋습니다.

제한사항

Spanner 일괄 쓰기에는 다음과 같은 제한사항이 있습니다.

  • Google Cloud 콘솔 또는 Google Cloud CLI를 사용하여 Spanner 일괄 쓰기를 사용할 수 없습니다. REST 및 RPC API와 Spanner Java 클라이언트 라이브러리를 통해서만 사용할 수 있습니다.

  • 재생 보호는 일괄 쓰기를 지원하지 않습니다. 변형은 두 번 이상 적용될 수 있으며 변형이 두 번 이상 적용되는 경우 오류가 발생할 수 있습니다. 예를 들어 삽입 변형이 재생되면 이미 존재함 오류가 발생할 수 있으며 변형에서 생성된 타임스탬프 기반 키를 사용하거나 커밋하면 테이블에 추가 행이 추가될 수 있습니다. 이 문제를 방지하려면 쓰기가 멱등성을 갖도록 구조화하는 것이 좋습니다.

  • 완료된 일괄 쓰기 요청은 롤백할 수 없습니다. 진행 중인 일괄 쓰기 요청을 취소할 수 있습니다. 진행 중인 일괄 쓰기를 취소하면 완료되지 않은 그룹의 변형이 롤백됩니다. 완료된 그룹의 변형은 데이터베이스에 커밋됩니다.

  • 일괄 쓰기 요청의 최대 크기는 커밋 요청의 한도와 동일합니다. 자세한 내용은 데이터 만들기, 읽기, 업데이트, 삭제 한도를 참조하세요.

일괄 쓰기 사용 방법

일괄 쓰기를 사용하려면 수정하려는 데이터베이스에 대한 spanner.databases.write 권한이 있어야 합니다. REST 또는 RPC API 요청 호출을 사용하여 단일 호출에서 변형을 비원자적으로 일괄 처리할 수 있습니다.

일괄 쓰기를 사용할 때는 다음 변형 유형을 함께 그룹화해야 합니다.

  • 상위 및 하위 테이블 모두에 기본 키 프리픽스가 동일한 행을 삽입합니다.
  • 테이블 간에 외래 키 관계가 있는 테이블에 행을 삽입합니다.
  • 데이터베이스 스키마 및 애플리케이션 로직에 따라 다른 유형의 관련 변형

Spanner Java 클라이언트 라이브러리를 사용하여 일괄 쓰기를 수행할 수도 있습니다. 다음 코드 예시에서는 Singers 테이블을 새 행으로 업데이트합니다.

Java


import com.google.api.gax.rpc.ServerStream;
import com.google.cloud.spanner.DatabaseClient;
import com.google.cloud.spanner.DatabaseId;
import com.google.cloud.spanner.Mutation;
import com.google.cloud.spanner.MutationGroup;
import com.google.cloud.spanner.Options;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerOptions;
import com.google.common.collect.ImmutableList;
import com.google.rpc.Code;
import com.google.spanner.v1.BatchWriteResponse;

public class BatchWriteAtLeastOnceSample {

  /***
   * Assume DDL for the underlying database:
   * <pre>{@code
   *   CREATE TABLE Singers (
   *     SingerId   INT64 NOT NULL,
   *     FirstName  STRING(1024),
   *     LastName   STRING(1024),
   *   ) PRIMARY KEY (SingerId)
   *
   *   CREATE TABLE Albums (
   *     SingerId     INT64 NOT NULL,
   *     AlbumId      INT64 NOT NULL,
   *     AlbumTitle   STRING(1024),
   *   ) PRIMARY KEY (SingerId, AlbumId),
   *   INTERLEAVE IN PARENT Singers ON DELETE CASCADE
   * }</pre>
   */

  private static final MutationGroup MUTATION_GROUP1 =
      MutationGroup.of(
          Mutation.newInsertOrUpdateBuilder("Singers")
              .set("SingerId")
              .to(16)
              .set("FirstName")
              .to("Scarlet")
              .set("LastName")
              .to("Terry")
              .build());
  private static final MutationGroup MUTATION_GROUP2 =
      MutationGroup.of(
          Mutation.newInsertOrUpdateBuilder("Singers")
              .set("SingerId")
              .to(17)
              .set("FirstName")
              .to("Marc")
              .build(),
          Mutation.newInsertOrUpdateBuilder("Singers")
              .set("SingerId")
              .to(18)
              .set("FirstName")
              .to("Catalina")
              .set("LastName")
              .to("Smith")
              .build(),
          Mutation.newInsertOrUpdateBuilder("Albums")
              .set("SingerId")
              .to(17)
              .set("AlbumId")
              .to(1)
              .set("AlbumTitle")
              .to("Total Junk")
              .build(),
          Mutation.newInsertOrUpdateBuilder("Albums")
              .set("SingerId")
              .to(18)
              .set("AlbumId")
              .to(2)
              .set("AlbumTitle")
              .to("Go, Go, Go")
              .build());

  static void batchWriteAtLeastOnce() {
    // TODO(developer): Replace these variables before running the sample.
    final String projectId = "my-project";
    final String instanceId = "my-instance";
    final String databaseId = "my-database";
    batchWriteAtLeastOnce(projectId, instanceId, databaseId);
  }

  static void batchWriteAtLeastOnce(String projectId, String instanceId, String databaseId) {
    try (Spanner spanner =
        SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) {
      DatabaseId dbId = DatabaseId.of(projectId, instanceId, databaseId);
      final DatabaseClient dbClient = spanner.getDatabaseClient(dbId);

      // Creates and issues a BatchWrite RPC request that will apply the mutation groups
      // non-atomically and respond back with a stream of BatchWriteResponse.
      ServerStream<BatchWriteResponse> responses =
          dbClient.batchWriteAtLeastOnce(
              ImmutableList.of(MUTATION_GROUP1, MUTATION_GROUP2),
              Options.tag("batch-write-tag"));

      // Iterates through the results in the stream response and prints the MutationGroup indexes,
      // commit timestamp and status.
      for (BatchWriteResponse response : responses) {
        if (response.getStatus().getCode() == Code.OK_VALUE) {
          System.out.printf(
              "Mutation group indexes %s have been applied with commit timestamp %s",
              response.getIndexesList(), response.getCommitTimestamp());
        } else {
          System.out.printf(
              "Mutation group indexes %s could not be applied with error code %s and "
                  + "error message %s", response.getIndexesList(),
              Code.forNumber(response.getStatus().getCode()), response.getStatus().getMessage());
        }
      }
    }
  }
}

다음 단계