Cassandra 사용자를 위한 Spanner

이 문서에서는 Apache Cassandra와 Spanner 개념 및 관행을 비교합니다. 이 문서는 Cassandra에 익숙하며 Spanner를 데이터베이스로 사용하면서 기존 애플리케이션을 마이그레이션하거나 새 애플리케이션을 설계하려는 개발자를 대상으로 합니다.

Cassandra와 Spanner 모두 높은 확장성과 짧은 지연 시간이 필요한 애플리케이션을 위해 빌드된 대규모 분산 데이터베이스입니다. 두 데이터베이스 모두 까다로운 NoSQL 워크로드를 지원할 수 있지만 Spanner는 데이터 모델링, 쿼리, 트랜잭션 작업에 사용되는 고급 기능을 제공합니다. Spanner가 NoSQL 데이터베이스 기준을 충족하는 방법에 대한 자세한 내용은 비관계형 워크로드용 Spanner를 참조하세요.

Cassandra에서 Spanner로 마이그레이션

Cassandra에서 Spanner로 마이그레이션하려면 Cassandra to Spanner Proxy Adapter를 사용하면 됩니다. 이 오픈소스 도구를 사용하면 애플리케이션 로직을 변경하지 않고도 Cassandra 또는 DataStax Enterprise(DSE)에서 Spanner로 워크로드를 마이그레이션할 수 있습니다.

핵심 개념

이 섹션에서는 주요 Cassandra 및 Spanner 개념을 비교합니다.

용어

Cassandra Spanner
클러스터 인스턴스

Cassandra 클러스터는 서버 및 스토리지 리소스 모음인 Spanner 인스턴스와 같습니다. Spanner는 관리형 서비스이므로 기본 하드웨어나 소프트웨어를 구성할 필요가 없습니다. 인스턴스에 예약할 노드 수를 지정하거나 인스턴스가 자동을 확장되도록 자동 확장을 선택하기만 하면 됩니다. 인스턴스는 데이터베이스용 컨테이너처럼 작동하며 데이터 복제 토폴로지(리전, 이중 리전 또는 멀티 리전)는 인스턴스 수준에서 선택됩니다.
키스페이스 데이터베이스

Cassandra 키스페이스는 테이블과 기타 스키마 요소(예: 색인 및 역할)의 모음인 Spanner 데이터베이스와 같습니다. 키스페이스와 달리 복제 인수를 구성할 필요가 없습니다. Spanner는 데이터를 자동으로 인스턴스에 지정된 리전에 복제합니다.
테이블

Cassandra와 Spanner 모두에서 테이블은 테이블 스키마에 지정된 기본 키로 식별되는 행의 모음입니다.
파티션 분할

Cassandra와 Spanner 모두 데이터 샤딩을 통해 확장됩니다. Cassandra에서는 각 샤드를 파티션이라고 하며 Spanner에서는 각 샤드를 분할이라고 합니다. Cassandra는 해시 파티셔닝을 사용합니다. 즉, 각 행은 기본 키의 해시를 기준으로 스토리지 노드에 독립적으로 할당됩니다. Spanner는 범위로 샤딩됩니다. 즉, 기본 키 공간에서 연속된 행은 스토리지에서도 연속됩니다(분할 경계 제외). Spanner는 부하와 스토리지를 기준으로 분할과 병합을 처리하며 이는 애플리케이션에 투명합니다. 중요한 의미는 Cassandra와 달리 기본 키의 프리픽스를 통한 범위 스캔이 Spanner에서 효율적인 작업이라는 점입니다.


Cassandra와 Spanner 모두에서 행은 기본 키로 고유하게 식별되는 열의 모음입니다. Cassandra와 마찬가지로 Spanner는 복합 기본 키를 지원합니다. Cassandra와 달리 데이터가 범위로 샤딩되므로 Spanner는 파티션 키와 정렬 키를 구분하지 않습니다. Spanner는 정렬 키만 있고 파티셔닝은 백그라운드에서 관리된다고 생각하면 됩니다.


Cassandra와 Spanner 모두에서 열은 같은 유형의 데이터 값 집합입니다. 테이블의 각 행에는 값 하나가 있습니다. Cassandra 열 유형을 Spanner와 비교하는 방법에 대한 자세한 내용은 데이터 유형을 참조하세요.

아키텍처

Cassandra 클러스터는 서버와 이러한 서버와 함께 배치된 스토리지의 집합으로 구성됩니다. 해시 함수는 파티션 키 공간의 행을 가상 노드(vnode)에 매핑합니다. 그런 다음 클러스터 키 공간의 일부를 처리하기 위해 일련의 vnode가 각 서버에 무작위로 할당됩니다. vnode의 스토리지는 서빙 노드에 로컬로 연결됩니다. 클라이언트 드라이버는 서빙 노드에 직접 연결되고 부하 분산과 쿼리 라우팅을 처리합니다.

Spanner 인스턴스는 복제 토폴로지의 서버 집합으로 구성됩니다. Spanner는 CPU 및 디스크 사용량을 기준으로 동적으로 각 테이블을 행 범위로 샤딩합니다. 샤드는 서빙을 위해 컴퓨팅 노드에 할당됩니다. 데이터는 컴퓨팅 노드와는 별개로 Google의 분산 파일 시스템인 Colossus에 물리적으로 저장됩니다. 클라이언트 드라이버는 요청 라우팅과 부하 분산을 수행하는 Spanner의 프런트엔드 서버에 연결됩니다. 자세한 내용은 Spanner 읽기 및 쓰기 수명 백서를 참조하세요.

대략적으로 두 아키텍처 모두 리소스가 기본 클러스터에 추가될 때 확장됩니다. Spanner의 컴퓨팅 및 스토리지 분리를 통해 워크로드 변경에 따라 로드가 컴퓨팅 노드 간에 더 빠르게 다시 분산됩니다. Cassandra와 달리 데이터가 Colossus에 유지되므로 샤드가 이동할 때 데이터가 이동하지 않습니다. 또한 데이터가 파티션 키로 정렬될 것으로 예상되는 애플리케이션에는 Spanner의 범위 기반 파티셔닝이 더 자연스러울 수 있습니다. 범위 기반 파티셔닝의 단점은 키 공간의 한쪽 끝에 쓰는 워크로드(예: 현재 타임스탬프로 키가 지정된 테이블)가 추가 스키마 설계 고려 없이 부하 집중이 발생할 수 있다는 점입니다. 부하 집중을 해결하는 기법에 대한 자세한 내용은 스키마 설계 권장사항을 참조하세요.

일관성

Cassandra에서는 작업마다 일관성 수준을 지정해야 합니다. 쿼럼 일관성 수준을 사용하는 경우 복제본 노드 대다수가 작업의 조정자 노드에 응답해야 성공한 것으로 간주됩니다. 일관성 수준 1을 사용하는 경우 Cassandra에서 작업이 성공한 것으로 간주되려면 단일 복제본 노드가 응답해야 합니다.

Spanner는 strong consistency를 제공합니다. Spanner API는 복제본을 클라이언트에 노출하지 않습니다. Spanner 클라이언트는 Spanner가 단일 머신 데이터베이스인 것처럼 Spanner와 상호작용합니다. 쓰기는 항상 사용자가 확인하기 전에 대부분의 복제본에 작성됩니다. 이후의 읽기는 새로 작성된 데이터를 반영합니다. 애플리케이션은 과거 특정 시점의 데이터베이스 스냅샷을 읽을 수 있으며 이는 강력한 읽기에 비해 성능 이점을 가질 수 있습니다. Spanner의 일관성 속성에 대한 자세한 내용은 트랜잭션 개요를 참조하세요.

Spanner는 대규모 애플리케이션에 필요한 일관성과 가용성을 지원하도록 빌드되었습니다. Spanner는 대규모로 고성능 strong consistency를 제공합니다. 필요한 사용 사례의 경우 Spanner는 최신 요구사항을 완화하는 스냅샷 읽기를 지원합니다.

데이터 모델링

이 섹션에서는 Cassandra와 Spanner 데이터 모델을 비교합니다.

테이블 선언

테이블 선언 문법은 Cassandra와 Spanner에서 매우 유사합니다. 테이블 이름, 열 이름, 유형과 행을 고유하게 식별하는 기본 키를 지정합니다. 주요 차이점은 Cassandra는 해시로 파티셔닝되고 파티션 키와 정렬 키를 구분하는 반면 Spanner는 범위로 파티셔닝된다는 점입니다. Spanner에는 정렬 키만 있고 파티션은 백그라운드에서 자동으로 유지된다고 생각하면 됩니다. Cassandra와 마찬가지로 Spanner는 복합 기본 키를 지원합니다.

단일 기본 키 부분

Cassandra와 Spanner의 차이점은 유형 이름과 기본 키 절 위치입니다.

Cassandra Spanner
CREATE TABLE users (
  user_id    bigint,
  first_name text,
  last_name  text,
  PRIMARY KEY (user_id)
)
    
CREATE TABLE users (
  user_id    int64,
  first_name string(max),
  last_name  string(max),
) PRIMARY KEY (user_id)
    

여러 기본 키 부분

Cassandra의 경우 첫 번째 기본 키 부분은 '파티션 키'이고 후속 기본 키 부분은 '정렬 키'입니다. Spanner의 경우 별도의 파티션 키가 없습니다. 데이터는 전체 복합 기본 키를 기준으로 정렬되어 저장됩니다.

Cassandra Spanner
CREATE TABLE user_items (
  user_id    bigint,
  item_id    bigint,
  first_name text,
  last_name  text,
  PRIMARY KEY (user_id, item_id)
)
    
CREATE TABLE user_items (
  user_id    int64,
  item_id    int64,
  first_name string(max),
  last_name  string(max),
) PRIMARY KEY (user_id, item_id)
    

복합 파티션 키

Cassandra의 경우 파티션 키가 복합 키일 수 있습니다. Spanner에는 별도의 파티션 키가 없습니다. 데이터는 전체 복합 기본 키를 기준으로 정렬되어 저장됩니다.

Cassandra Spanner
CREATE TABLE user_category_items (
  user_id     bigint,
  category_id bigint,
  item_id     bigint,
  first_name  text,
  last_name   text,
  PRIMARY KEY ((user_id, category_id), item_id)
)
    
CREATE TABLE user_category_items (
  user_id     int64,
  category_id int64,
  item_id     int64,
  first_name  string(max),
  last_name   string(max),
) PRIMARY KEY (user_id, category_id, item_id)
    

데이터 유형

이 섹션에서는 Cassandra와 Spanner 데이터 유형을 비교합니다. Spanner 유형에 대한 자세한 내용은 GoogleSQL의 데이터 유형을 참조하세요.

Cassandra Spanner
숫자 유형 표준 정수:

bigint(64비트 부호 있는 정수)
int(32비트 부호 있는 정수)
smallint(16비트 부호 있는 정수)
tinyint(8비트 부호 있는 정수)
int64(64비트 부호 있는 정수)

Spanner는 부호 있는 정수에 단일 64비트 전체 데이터 유형을 지원합니다.
표준 부동 소수점:

double(64비트 IEEE-754 부동 소수점)
float(32비트 IEEE-754 부동 소수점)
float64(64비트 IEEE-754 부동 소수점)
float32(32비트 IEEE-754 부동 소수점)
가변 정밀도 숫자:

varint(가변 정밀도 정수)
decimal(가변 정밀도 소수점)
고정 정밀도 십진수의 경우 numeric(정밀도 38, 비율 9)을 사용합니다. 그렇지 않으면 애플리케이션 계층 변수 정밀도 정수 라이브러리와 함께 string을 사용합니다.
문자열 유형 text
varchar
string(max)

textvarchar 모두 UTF-8 문자열을 저장하고 유효성을 검사합니다. Spanner에서는 string 열의 최대 길이를 지정해야 합니다(스토리지에는 영향을 미치지 않으며 유효성 검사용).
blob bytes(max)

바이너리 데이터를 저장하려면 bytes 데이터 유형을 사용합니다.
날짜 및 시간 유형 date date
duration int64

Spanner에서는 전용 기간 데이터 유형을 지원하지 않습니다. int64를 사용하여 나노초 기간을 저장합니다.
time int64

Spanner에서는 전용 하루 이내 시간 데이터 유형을 지원하지 않습니다. int64를 사용하여 하루 이내의 나노초 오프셋을 저장합니다.
timestamp timestamp
컨테이너 유형 사용자 정의 유형 json 또는 proto
list array

array를 사용하여 유형이 지정된 객체 목록을 저장합니다.
map json 또는 proto

Spanner에서는 전용 지도 유형을 지원하지 않습니다. json 또는 proto 열을 사용하여 지도를 나타냅니다. 자세한 내용은 대규모 지도를 인터리브 처리된 테이블로 저장을 참조하세요.
set array

Spanner에서는 전용 세트 유형을 지원하지 않습니다. array 열을 사용하여 애플리케이션에서 세트 고유성을 관리하는 set을 나타냅니다. 자세한 내용은 대규모 세트를 저장하는 데도 사용할 수 있는 대규모 지도를 인터리브 처리된 테이블로 저장을 참조하세요.

기본 사용 패턴

다음 코드 예시에서는 Go에서의 Cassandra 및 Spanner 클라이언트 코드의 차이점을 보여줍니다. 자세한 내용은 Spanner 클라이언트 라이브러리를 참조하세요.

클라이언트 초기화

Cassandra 클라이언트에서는 기본 Cassandra 클러스터를 나타내는 클러스터 객체를 만들고 클러스터에 대한 연결을 추상화하는 세션 객체를 인스턴스화하며 세션에서 쿼리를 실행합니다. Spanner에서는 특정 데이터베이스에 바인딩된 클라이언트 객체를 만들고 클라이언트 객체에서 데이터베이스 요청을 수행합니다.

Cassandra 예시

Go

import "github.com/gocql/gocql"

...

cluster := gocql.NewCluster("<address>")
cluster.Keyspace = "<keyspace>"
session, err := cluster.CreateSession()
if err != nil {
  return err
}
defer session.Close()

// session.Query(...)

Spanner 예시

Go

import "cloud.google.com/go/spanner"

...

client, err := spanner.NewClient(ctx,
    fmt.Sprintf("projects/%s/instances/%s/databases/%s", project, instance, database))
defer client.Close()

// client.Apply(...)

데이터 읽기

키-값 스타일 API쿼리 API 모두를 통해 Spanner에서 읽기를 수행할 수 있습니다. Cassandra 사용자는 쿼리 API가 더 익숙할 수 있습니다. 쿼리 API의 주요 차이점은 Cassandra의 위치 인수 ?와 달리 Spanner에는 이름이 지정된 인수가 필요하다는 점입니다. Spanner 쿼리의 인수 이름에는 @ 프리픽스가 추가되어야 합니다.

Cassandra 예시

Go

stmt := `SELECT
           user_id, first_name, last_name
         FROM
           users
         WHERE
           user_id = ?`

var (
  userID    int
  firstName string
  lastName  string
)

err := session.Query(stmt, 1).Scan(&userID, &firstName, &lastName)

Spanner 예시

Go

stmt := spanner.Statement{
  SQL: `SELECT
          user_id, first_name, last_name
        FROM
          users
        WHERE
          user_id = @user_id`,
  Params: map[string]any{"user_id": 1},
}

var (
  userID    int64
  firstName string
  lastName  string
)

err := client.Single().Query(ctx, stmt).Do(func(row *spanner.Row) error {
  return row.Columns(&userID, &firstName, &lastName)
})

데이터 삽입

Cassandra INSERT는 Spanner INSERT OR UPDATE와 같습니다. 삽입의 전체 기본 키를 지정해야 합니다. Spanner는 DML과 키-값 스타일 변형 API를 모두 지원합니다. 키-값 스타일 변형 API는 지연 시간이 짧으므로 사소한 쓰기에 권장됩니다. Spanner DML API는 전체 SQL 노출 영역(DML 문에 표현식 사용 포함)을 지원하므로 더 많은 기능을 제공합니다.

Cassandra 예시

Go

stmt := `INSERT INTO
           users (user_id, first_name, last_name)
         VALUES
           (?, ?, ?)`
err := session.Query(stmt, 1, "John", "Doe").Exec()

Spanner 예시

Go

_, err := client.Apply(ctx, []*spanner.Mutation{
  spanner.InsertOrUpdateMap(
    "users", map[string]any{
      "user_id":    1,
      "first_name": "John",
      "last_name":  "Doe",
    }
  )})

데이터 일괄 삽입

Cassandra에서는 배치 문을 사용하여 행을 여러 개 삽입할 수 있습니다. Spanner에서는 커밋 작업에 여러 변형이 포함될 수 있습니다. Spanner는 이러한 변형을 데이터베이스에 원자적으로 삽입합니다.

Cassandra 예시

Go

stmt := `INSERT INTO
           users (user_id, first_name, last_name)
         VALUES
           (?, ?, ?)`
b := session.NewBatch(gocql.UnloggedBatch)
b.Entries = []gocql.BatchEntry{
  {Stmt: stmt, Args: []any{1, "John", "Doe"}},
  {Stmt: stmt, Args: []any{2, "Mary", "Poppins"}},
}
err = session.ExecuteBatch(b)

Spanner 예시

Go

_, err := client.Apply(ctx, []*spanner.Mutation{
  spanner.InsertOrUpdateMap(
    "users", map[string]any{
       "user_id":    1,
       "first_name": "John",
       "last_name":  "Doe"
    },
  ),
  spanner.InsertOrUpdateMap(
    "users", map[string]any{
       "user_id":    2,
       "first_name": "Mary",
       "last_name":  "Poppins",
    },
  ),
})

데이터 삭제

Cassandra에서 삭제하려면 삭제할 행의 기본 키를 지정해야 합니다. 이는 Spanner의 DELETE 변형과 유사합니다.

Cassandra 예시

Go

stmt := `DELETE FROM
           users
         WHERE
           user_id = ?`
err := session.Query(stmt, 1).Exec()

Spanner 예시

Go

_, err := client.Apply(ctx, []*spanner.Mutation{
  spanner.Delete("users", spanner.Key{1}),
})

심화 주제

이 섹션에는 Spanner에서 고급 Cassandra 기능을 사용하는 방법에 대한 정보가 포함되어 있습니다.

타임스탬프 쓰기

Cassandra에서는 변형이 USING TIMESTAMP 절을 사용하여 특정 셀의 쓰기 타임스탬프를 명시적으로 지정할 수 있습니다. 일반적으로 이 기능은 Cassandra의 last-writer-wins 시맨틱스를 조작하는 데 사용됩니다.

Spanner에서는 클라이언트가 각 쓰기의 타임스탬프를 지정할 수 없습니다. 각 셀은 셀 값이 커밋될 때 내부적으로 TrueTime 타임스탬프로 표시됩니다. Spanner는 strong consistency와 엄격하게 직렬화 가능한 인터페이스를 제공하므로 대부분의 애플리케이션에는 USING TIMESTAMP 기능이 필요하지 않습니다.

애플리케이션별 로직에 Cassandra의 USING TIMESTAMP를 사용하면 Spanner 스키마에 TIMESTAMP 열을 추가하여 애플리케이션 수준에서 수정 시간을 추적할 수 있습니다. 그런 다음 행 업데이트를 읽기-쓰기 트랜잭션으로 래핑할 수 있습니다. 예를 들면 다음과 같습니다.

Cassandra 예시

Go

stmt := `INSERT INTO
           users (user_id, first_name, last_name)
         VALUES
           (?, ?, ?)
         USING TIMESTAMP
           ?`
err := session.Query(stmt, 1, "John", "Doe", ts).Exec()

Spanner 예시

  1. 명시적 업데이트 타임스탬프 열이 있는 스키마를 만듭니다.

    GoogleSQL

    CREATE TABLE users (
      user_id    INT64,
      first_name STRING(MAX),
      last_name  STRING(MAX),
      update_ts  TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true),
    ) PRIMARY KEY (user_id)
  2. 행을 업데이트하고 타임스탬프를 포함하도록 로직을 맞춤설정합니다.

    Go

    func ShouldUpdateRow(ctx context.Context, txn *spanner.ReadWriteTransaction, updateTs time.Time) (bool, error) {
      // Read the existing commit timestamp.
      row, err := txn.ReadRow(ctx, "users", spanner.Key{1}, []string{"update_ts"})
    
      // Treat non-existent row as NULL timestamp - the row should be updated.
      if spanner.ErrCode(err) == codes.NotFound {
        return true, nil
      }
    
      // Propagate unexpected errors.
      if err != nil {
        return false, err
      }
    
      // Check if the committed timestamp is newer than the update timestamp.
      var committedTs *time.Time
      err = row.Columns(&committedTs)
      if err != nil {
        return false, err
      }
      if committedTs != nil && committedTs.Before(updateTs) {
        return false, nil
      }
    
      // Committed timestamp is older than update timestamp - the row should be updated.
      return true, nil
    }
  3. 행을 업데이트하기 전에 커스텀 조건을 확인합니다.

    Go

    _, err := client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
      // Check if the row should be updated.
      ok, err := ShouldUpdateRow(ctx, txn, time.Now())
      if err != nil {
        return err
      }
      if !ok {
        return nil
      }
    
      // Update the row.
      txn.BufferWrite([]*spanner.Mutation{
        spanner.InsertOrUpdateMap("users", map[string]any{
          "user_id":    1,
          "first_name": "John",
          "last_name":  "Doe",
          "update_ts":  spanner.CommitTimestamp,
        })})
    
      return nil
    })

조건부 변형

Cassandra의 INSERT ... IF EXISTS 문은 Spanner의 INSERT 문과 동일합니다. 두 경우 모두 행이 이미 있으면 삽입이 실패합니다.

Cassandra에서는 조건을 지정하는 DML 문을 만들 수 있으며 조건이 false로 평가되면 문은 실패합니다. Spanner에서는 읽기-쓰기 트랜잭션에서 조건부 UPDATE 변형을 사용할 수 있습니다. 예를 들어 특정 조건이 있는 경우에만 행을 업데이트하려면 다음 안내를 따르세요.

Cassandra 예시

Go

stmt := `UPDATE
           users
         SET
           last_name = ?
         WHERE
           user_id = ?
         IF
           first_name = ?`
err := session.Query(stmt, 1, "Smith", "John").Exec()

Spanner 예시

  1. 행을 업데이트하고 조건을 포함하도록 로직을 맞춤설정합니다.

    Go

    func ShouldUpdateRow(ctx context.Context, txn *spanner.ReadWriteTransaction) (bool, error) {
      row, err := txn.ReadRow(ctx, "users", spanner.Key{1}, []string{"first_name"})
      if err != nil {
        return false, err
      }
    
      var firstName *string
      err = row.Columns(&firstName)
      if err != nil {
        return false, err
      }
      if firstName != nil && firstName == "John" {
        return false, nil
      }
      return true, nil
    }
  2. 행을 업데이트하기 전에 커스텀 조건을 확인합니다.

    Go

    _, err := client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
      ok, err := ShouldUpdateRow(ctx, txn, time.Now())
      if err != nil {
        return err
      }
      if !ok {
        return nil
      }
    
      txn.BufferWrite([]*spanner.Mutation{
        spanner.InsertOrUpdateMap("users", map[string]any{
          "user_id":    1,
          "last_name":  "Smith",
          "update_ts":  spanner.CommitTimestamp,
        })})
    
      return nil
    })

TTL

Cassandra에서는 행 또는 열 수준에서 TTL(수명) 값을 설정할 수 있습니다. Spanner에서는 TTL이 행 수준에서 구성되므로 개발자는 이름이 지정된 열을 행의 만료 시간으로 지정합니다. 자세한 내용은 TTL(수명) 개요를 참조하세요.

Cassandra 예시

Go

stmt := `INSERT INTO
           users (user_id, first_name, last_name)
         VALUES
           (?, ?, ?)
         USING TTL 86400
           ?`
err := session.Query(stmt, 1, "John", "Doe", ts).Exec()

Spanner 예시

  1. 명시적 업데이트 타임스탬프 열이 있는 스키마를 만듭니다.

    GoogleSQL

    CREATE TABLE users (
      user_id    INT64,
      first_name STRING(MAX),
      last_name  STRING(MAX),
      update_ts  TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true),
    ) PRIMARY KEY (user_id),
      ROW DELETION POLICY (OLDER_THAN(update_ts, INTERVAL 1 DAY));
  2. 커밋 타임스탬프가 있는 행을 삽입합니다.

    Go

    _, err := client.Apply(ctx, []*spanner.Mutation{
      spanner.InsertOrUpdateMap("users", map[string]any{
                  "user_id":    1,
                  "first_name": "John",
                  "last_name":  "Doe",
                  "update_ts":  spanner.CommitTimestamp}),
    })

대규모 지도를 인터리브 처리된 테이블로 저장합니다.

Cassandra는 순서가 지정된 키-값 쌍을 저장할 수 있도록 map 유형을 지원합니다. Spanner에 소량의 데이터가 포함된 map 유형을 저장하려면 각각 반구조화된 데이터와 구조화된 데이터를 저장할 수 있는 JSON 또는 PROTO 유형을 사용하면 됩니다. 이러한 열을 업데이트하려면 전체 열 값을 다시 작성해야 합니다. Cassandra map에 대량의 데이터가 저장되어 있고 map의 일부만 업데이트해야 하는 경우에는 INTERLEAVED 테이블을 사용하는 것이 좋습니다. 예를 들어 대량의 키-값 데이터를 특정 사용자와 연결하려면 다음 안내를 따르세요.

Cassandra 예시

CREATE TABLE users (
  user_id     bigint,
  attachments map<string, string>,
  PRIMARY KEY (user_id)
)

Spanner 예시

CREATE TABLE users (
  user_id  INT64,
) PRIMARY KEY (user_id);

CREATE TABLE user_attachments (
  user_id        INT64,
  attachment_key STRING(MAX),
  attachment_val STRING(MAX),
) PRIMARY KEY (user_id, attachment_key);

이 경우 사용자 연결 행은 해당 사용자 행과 함께 저장되며 사용자 행과 함께 효율적으로 검색되고 업데이트될 수 있습니다. Spanner의 읽기-쓰기 API를 사용하여 인터리브 처리된 테이블과 상호작용할 수 있습니다. 인터리브 처리에 대한 자세한 내용은 상위 및 하위 테이블 만들기를 참조하세요.

개발자 환경

이 섹션에서는 Spanner와 Cassandra 개발자 도구를 비교합니다.

로컬 개발

개발 및 단위 테스트를 위해 Cassandra를 로컬에서 실행할 수 있습니다. Spanner는 Spanner 에뮬레이터를 통해 유사한 로컬 개발용 환경을 제공합니다. 에뮬레이터는 대화형 개발과 단위 테스트를 위한 충실도가 높은 환경을 제공합니다. 자세한 내용은 로컬에서 Spanner 에뮬레이션을 참조하세요.

명령줄

Cassandra의 nodetool에 해당하는 Spanner는 Google Cloud CLI입니다. gcloud spanner를 사용하여 컨트롤 플레인 작업과 데이터 영역 작업을 수행할 수 있습니다. 자세한 내용은 Google Cloud CLI Spanner 참조 가이드를 참조하세요.

cqlsh와 유사하게 Spanner에 쿼리를 실행하는 REPL 인터페이스가 필요한 경우 spanner-cli 도구를 사용하면 됩니다. Go에서 spanner-cli를 설치하고 실행하려면 다음 안내를 따르세요.

go install github.com/cloudspannerecosystem/spanner-cli@latest

$(go env GOPATH)/bin/spanner-cli

자세한 내용은 spanner-cli GitHub 저장소를 참조하세요.