1. 개요

이 사용방법(예제)에서는 Cassandra 일괄 쿼리 및 다양한 사용 사례 에 대해 알아봅니다 . 단일 파티션 및 다중 파티션 테이블 배치 쿼리를 모두 분석합니다.

Cqlsh 와 Java 애플리케이션에서 일괄 처리를 살펴보겠습니다 .

2. 카산드라 배치 기초

Cassandra 와 같은 분산 데이터베이스 는 관계형 데이터베이스와 달리 ACID (Atomicity, Consistency, Isolation, and Durability) 속성을 지원하지 않습니다. 그러나 경우에 따라 원자적이거나 격리된 작업이 되기 위해 여러 데이터 수정이 필요합니다.

일괄 처리 문은 여러 데이터 수정 언어 문(예: INSERT, UPDATE 및 DELETE)을 결합하여 단일 파티션을 대상으로 할 때 원자성 및 격리를 달성하거나 여러 파티션을 대상으로 할 때 원자성만 달성합니다.

일괄 쿼리 구문은 다음과 같습니다.

BEGIN [ ( UNLOGGED | COUNTER ) ] BATCH
[ USING TIMESTAMP [ epoch_microseconds ] ]
dml_statement [ USING TIMESTAMP [ epoch_microseconds ] ] ;
[ dml_statement [ USING TIMESTAMP [ epoch_microseconds ] ] [ ; ... ] ]
APPLY BATCH;

예를 들어 위의 구문을 살펴보겠습니다.

BEGIN BATCH 

INSERT INTO product (product_id, variant_id, product_name) 
VALUES (2c11bbcd-4587-4d15-bb57-4b23a546bd7f,0e9ef8f7-d32b-4926-9d37-27225933a5f3,'banana'); 

INSERT INTO product (product_id, variant_id, product_name) 
VALUES (2c11bbcd-4587-4d15-bb57-4b23a546bd7f,0e9ef8f7-d32b-4926-9d37-27225933a5f5,'banana'); 

APPLY BATCH;

먼저 UNLOGGED  또는 USING TIMESTAMP 와 같은 선택적 매개 변수 없이  BEGIN BATCH 문을 사용 하여 일괄 쿼리를 시작한 다음 모든 DML 작업, 즉 제품 테이블에 대한 삽입 문을 포함합니다.

마지막으로 APPLY BATCH 문을 사용하여 배치를 실행합니다.

일괄 쿼리는 롤백 기능을 지원하지 않기 때문에 일괄 쿼리를 실행 취소할 수 없습니다.

2.1. 단일 파티션

배치 문은 단일 파티션 내의 모든 DML 문을 적용하여 원자성 및 격리를 보장합니다.

단일 파티션을 대상으로 하는 잘 설계된 배치는 클라이언트-서버 트래픽을 줄이고 단일 행 변형으로 테이블을 보다 효율적으로 업데이트할 수 있습니다. 이는 일괄 작업이 단일 파티션에 쓰는 경우에만 일괄 격리가 발생하기 때문입니다.

단일 파티션 배치에는 동일한 파티션 키가 있고 동일한 키스페이스에 있는 두 개의 서로 다른 테이블이 포함될 수도 있습니다.

단일 파티션 배치 작업 은 기본적으로 기록되지 않으므로 기록으로 인한 성능 저하가 발생하지 않습니다.

아래 다이어그램은 조정 노드 H 에서 파티션 노드 B 및 해당 복제 노드 C , D 로의 단일 파티션 배치 요청 흐름을 보여줍니다 .

단일 파티션

예의: Datastax

2.2. 다중 파티션

여러 파티션을 포함하는 일괄 처리는 여러 노드 간의 조정을 포함하므로 잘 설계되어야 합니다. 다중 파티션 배치의 가장 좋은 사용 사례는 동일한 데이터를 두 개의 관련 테이블, 즉 파티션 키가 다른 동일한 열이 있는 두 개의 테이블에 쓰는 것입니다.

다중 파티션 배치 작업 은 원자성을 보장하기 위해 배치 로그 메커니즘을 사용합니다 . 조정 노드는 배치 로그 노드에 배치 로그 요청을 보내고 수신 확인을 받으면 배치 문을 실행합니다. 그런 다음 노드에서 배치 로그를 제거하고 클라이언트  에 확인을 보냅니다.

여러 파티션 일괄 쿼리를 사용하지 않는 것이 좋습니다. 이러한 쿼리는 조정 노드에 큰 부담을 주고 성능에 심각한 영향을 미치기 때문입니다.

실행 가능한 다른 옵션이 없을 때만 다중 파티션 배치를 사용해야 합니다.

아래 다이어그램은 조정 노드 H 에서 파티션 노드 B , E 및 해당 복제 노드 C , DF , G 로의 다중 파티션 배치 요청 흐름을 보여줍니다 .

멀티 파티션

예의: Datastax

3. Cqlsh 에서 일괄 실행

먼저  일부 일괄 쿼리를 통해 실행할 제품 테이블을 만들어 보겠습니다.

CREATE TABLE product (
  product_id UUID,
  variant_id UUID,
  product_name text,
  description text,
  price float,
  PRIMARY KEY (product_id, variant_id)
  );

 3.1. 타임스탬프가 없는 단일 파티션 배치

제품 테이블 의 단일 파티션을 대상으로 아래 배치 쿼리를 실행하고 타임스탬프를 제공하지 않습니다.

BEGIN BATCH 

INSERT INTO product (product_id, variant_id, product_name) 
VALUES (2c11bbcd-4587-4d15-bb57-4b23a546bd7f,0e9ef8f7-d32b-4926-9d37-27225933a5f3,'banana') IF NOT EXISTS; 

INSERT INTO product (product_id, variant_id, product_name) 
VALUES (2c11bbcd-4587-4d15-bb57-4b23a546bd7f,0e9ef8f7-d32b-4926-9d37-27225933a5f5,'banana') IF NOT EXISTS; 

UPDATE product SET price = 7.12, description = 'banana v1' 
WHERE product_id = 2c11bbcd-4587-4d15-bb57-4b23a546bd7f AND variant_id=0e9ef8f7-d32b-4926-9d37-27225933a5f3; 

UPDATE product SET price = 11.90, description = 'banana v2' 
WHERE product_id = 2c11bbcd-4587-4d15-bb57-4b23a546bd7f AND variant_id=0e9ef8f7-d32b-4926-9d37-27225933a5f5; 

APPLY BATCH;

위의 질의 는 비교 및 ​​설정(CAS) 논리,IF NOT EXISTS  절을 사용하며 이러한 모든 조건문 은 일괄 처리를 실행하기 위해 true 를 반환해야 합니다. 그러한 문이 false 를 반환하면 전체 일괄 처리가 처리되지 않은 것입니다.

위의 쿼리를 실행하면 다음과 같은 성공적인 승인을 받게 됩니다.

useBatchExamplesAck

이제 배치 실행 후 데이터 쓰기 시간이 동일한지 확인하겠습니다 .

cqlsh:testkeyspace> select product_id, variant_id, product_name, description, price, writetime(product_name) from product;

@ Row 1
-------------------------+--------------------------------------
product_id | 3a043b68-20ee-4ece-8f4b-a07e704bc9f5
variant_id | b84b9366-9998-4b2d-9a96-7e9a59a94ae5
product_name | Banana
description | banana v1
price | 12
writetime(product_name) | 1639275574653000

@ Row 2
-------------------------+--------------------------------------
product_id | 3a043b68-20ee-4ece-8f4b-a07e704bc9f5
variant_id | facc3997-299d-419b-b133-a54b5d4dfc3b
product_name | Banana
description | banana v2
price | 12
writetime(product_name) | 1639275574653000

 3.2. 타임스탬프가 있는 단일 파티션 배치

이제 epoch 시간 형식(예: 마이크로초)으로 타임스탬프를 제공하는 USING TIMESTAMP 옵션이 있는 일괄 쿼리의 예를 볼 수 있습니다.

다음은 모든 DML 문에 동일한 타임스탬프를 적용하는 일괄 쿼리입니다.

BEGIN BATCH USING TIMESTAMP 1638810270 

INSERT INTO product (product_id, variant_id, product_name) 
VALUES (2c11bbcd-4587-4d15-bb57-4b23a546bd7f,0e9ef8f7-d32b-4926-9d37-27225933a5f3,'banana'); 

INSERT INTO product (product_id, variant_id, product_name) 
VALUES (2c11bbcd-4587-4d15-bb57-4b23a546bd7f,0e9ef8f7-d32b-4926-9d37-27225933a5f5,'banana'); 

UPDATE product SET price = 7.12, description = 'banana v1' 
WHERE product_id = 2c11bbcd-4587-4d15-bb57-4b23a546bd7f AND variant_id=0e9ef8f7-d32b-4926-9d37-27225933a5f3; 

UPDATE product SET price = 11.90, description = 'banana v2' 
WHERE product_id = 2c11bbcd-4587-4d15-bb57-4b23a546bd7f AND variant_id=0e9ef8f7-d32b-4926-9d37-27225933a5f5; 

APPLY BATCH;

이제 개별 DML 문에 사용자 지정 타임스탬프를 지정해 보겠습니다.

BEGIN BATCH 

INSERT INTO product (product_id, variant_id, product_name) 
VALUES (2c11bbcd-4587-4d15-bb57-4b23a546bd7f,0e9ef8f7-d32b-4926-9d37-27225933a5f3,'banana');

INSERT INTO product (product_id, variant_id, product_name) 
VALUES (2c11bbcd-4587-4d15-bb57-4b23a546bd7f,0e9ef8f7-d32b-4926-9d37-27225933a5f5,'banana') USING TIMESTAMP 1638810270; 

UPDATE product SET price = 7.12, description = 'banana v1' 
WHERE product_id = 2c11bbcd-4587-4d15-bb57-4b23a546bd7f AND variant_id=0e9ef8f7-d32b-4926-9d37-27225933a5f3 USING TIMESTAMP 1638810270; 

UPDATE product SET price = 11.90, description = 'banana v2' 
WHERE product_id = 2c11bbcd-4587-4d15-bb57-4b23a546bd7f AND variant_id=0e9ef8f7-d32b-4926-9d37-27225933a5f5; 

APPLY BATCH;

이제 Custom 타임스탬프와 비교 및 ​​설정(CAS) 논리(예: IF NOT EXISTS 절 )가 있는 잘못된 일괄 쿼리가 표시됩니다 .

BEGIN BATCH USING TIMESTAMP 1638810270 

INSERT INTO product (product_id, variant_id, product_name) 
VALUES (2c11bbcd-4587-4d15-bb57-4b23a546bd7f,0e9ef8f7-d32b-4926-9d37-27225933a5f3,'banana') IF NOT EXISTS; 

INSERT INTO product (product_id, variant_id, product_name) 
VALUES (2c11bbcd-4587-4d15-bb57-4b23a546bd7f,0e9ef8f7-d32b-4926-9d37-27225933a5f5,'banana') IF NOT EXISTS; 

UPDATE product SET price = 7.12, description = 'banana v1' 
WHERE product_id = 2c11bbcd-4587-4d15-bb57-4b23a546bd7f AND variant_id=0e9ef8f7-d32b-4926-9d37-27225933a5f3; 

UPDATE product SET price = 11.90, description = 'banana v2' 
WHERE product_id = 2c11bbcd-4587-4d15-bb57-4b23a546bd7f AND variant_id=0e9ef8f7-d32b-4926-9d37-27225933a5f5; 

APPLY BATCH;

위의 쿼리를 실행하면 아래와 같은 오류가 발생합니다.

InvalidRequest: Error from server: code=2200 [Invalid query]
message="Cannot provide custom timestamp for conditional BATCH"

위의 오류는 조건부 삽입 또는 업데이트에 대해 클라이언트 측 타임스탬프가 금지되기 때문입니다.

3.3. 다중 파티션 배치 쿼리

여러 파티션에 대한 일괄 처리의 가장 좋은 사용 사례는 개의 관련 테이블에 정확한 데이터를 삽입하는 것 입니다.

파티션 키가 다른 product_by_nameproduct_by_id 테이블 에 동일한 데이터를 삽입해 보겠습니다 .

BEGIN BATCH 

INSERT INTO product_by_name (product_name, product_id, description, price) 
VALUES ('banana',2c11bbcd-4587-4d15-bb57-4b23a546bd7f,'banana',12.00); 

INSERT INTO product_by_id (product_id, product_name, description, price) 
VALUES (2c11bbcd-4587-4d15-bb57-4b23a546bd7f,'banana','banana',12.00); 

APPLY BATCH;

이제 위 쿼리에 대해 UNLOGGED 옵션을 활성화해 보겠습니다 .

BEGIN UNLOGGED BATCH 

INSERT INTO product_by_name (product_name, product_id, description, price) 
VALUES ('banana',2c11bbcd-4587-4d15-bb57-4b23a546bd7f,'banana',12.00); 

INSERT INTO product_by_id (product_id, product_name, description, price) 
VALUES (2c11bbcd-4587-4d15-bb57-4b23a546bd7f,'banana','banana',12.00); 

APPLY BATCH;

위의 UNLOGGED 배치 쿼리는 원자성 또는 격리 를 보장하지 않으며 배치 로그  를 사용하여 데이터를 쓰지 않습니다.

3.4. 카운터 업데이트 일괄 처리

카운터 업데이트 작업은 멱등성이 아니므 로 모든 카운터 열에 대해 COUNTER 옵션 을 사용해야 합니다 .

카운터 데이터 유형 으로 sales_vol저장 하는 product_by_sales 테이블을 생성해 보겠습니다 .

CREATE TABLE product_by_sales (
  product_id UUID,
  sales_vol counter,
  PRIMARY KEY (product_id)
);

아래 카운터 배치 쿼리는 sales_vol 을 100씩 두 배로 늘립니다.

BEGIN COUNTER BATCH

UPDATE product_by_sales
SET sales_vol = sales_vol + 100
WHERE product_id = 6ab09bec-e68e-48d9-a5f8-97e6fb4c9b47;

UPDATE product_by_sales
SET sales_vol = sales_vol + 100
WHERE product_id = 6ab09bec-e68e-48d9-a5f8-97e6fb4c9b47;

APPLY BATCH

4. Java에서의 배치 작업

Java 애플리케이션에서 배치 쿼리를 작성하고 실행하는 몇 가지 예를 살펴보겠습니다.

4.1. 메이븐 의존성

먼저 DataStax 관련 Maven 의존성을 포함해야 합니다.

<dependency>
    <groupId>com.datastax.oss</groupId>
    <artifactId>java-driver-core</artifactId>
    <version>4.1.0</version>
</dependency>
<dependency>
   <groupId>com.datastax.oss</groupId>
   <artifactId>java-driver-query-builder</artifactId>
   <version>4.1.0</version>
</dependency>

4.2. 단일 파티션 배치

단일 파티션 데이터로 일괄 처리를 실행하는 방법을 알아보기 위해 예제를 살펴보겠습니다.

BatchStatement 인스턴스 를 사용하여 배치 쿼리를 작성합니다. BatchStatement  는 DefaultBatchType enumBoundStatement 인스턴스 를 사용하여 인스턴스화 됩니다 .

먼저, Product 특성을 PreparedStatement 삽입 쿼리 에 바인딩하여 BoundStatement 인스턴스 를 가져오는 메서드를 만듭니다.

BoundStatement getProductVariantInsertStatement(Product product, UUID productId) {
    String insertQuery = new StringBuilder("") 
      .append("INSERT INTO ")
      .append(PRODUCT_TABLE_NAME)
      .append("(product_id, variant_id, product_name, description, price) ")
      .append("VALUES (")
      .append(":product_id")
      .append(", ")
      .append(":variant_id")
      .append(", ")
      .append(":product_name")
      .append(", ")
      .append(":description")
      .append(", ")
      .append(":price")
      .append(");")
      .toString();

    PreparedStatement preparedStatement = session.prepare(insertQuery);
        
    return preparedStatement.bind(
      productId, 
      UUID.randomUUID(),
      product.getProductName(), 
      product.getDescription(),
      product.getPrice());
}

이제 동일한 Product UUID 를 사용하여 위에서 만든 BoundStatement 에 대해 BatchStatement 를 실행합니다 .

UUID productId = UUID.randomUUID();
BoundStatement productBoundStatement1 = this.getProductVariantInsertStatement(productVariant1, productId);
BoundStatement productBoundStatement2 = this.getProductVariantInsertStatement(productVariant2, productId);
        
BatchStatement batch = BatchStatement.newInstance(DefaultBatchType.UNLOGGED,
            productBoundStatement1, productBoundStatement2);

session.execute(batch);

위의 코드는 UNLOGGED 배치 를 사용하여 동일한 파티션 키에 두 개의 제품 변형을 삽입합니다 .

4.3. 다중 파티션 배치

이제 동일한 데이터를 두 개의 관련 테이블인 product_by_idproduct_by_name 에 삽입하는 방법을 살펴보겠습니다 .

먼저 PreparedStatement 삽입 쿼리 에 대한 BoundStatement 인스턴스 를 가져오는 재사용 가능한 메서드를 만듭니다.

BoundStatement getProductInsertStatement(Product product, UUID productId, String productTableName) {
    String cqlQuery1 = new StringBuilder("")
      .append("INSERT INTO ")
      .append(productTableName)
      .append("(product_id, product_name, description, price) ")
      .append("VALUES (")
      .append(":product_id")
      .append(", ")
      .append(":product_name")
      .append(", ")
      .append(":description")
      .append(", ")
      .append(":price")
      .append(");")
      .toString();

    PreparedStatement preparedStatement = session.prepare(cqlQuery1);
        
    return preparedStatement.bind(
      productId,
      product.getProductName(),
      product.getDescription(),
      product.getPrice());
}

이제 동일한 제품 UUID 를 사용하여 BatchStatement 를 실행합니다.

UUID productId = UUID.randomUUID();
        
BoundStatement productBoundStatement1 = this.getProductInsertStatement(product, productId, PRODUCT_BY_ID_TABLE_NAME);
BoundStatement productBoundStatement2 = this.getProductInsertStatement(product, productId, PRODUCT_BY_NAME_TABLE_NAME);
        
BatchStatement batch = BatchStatement.newInstance(DefaultBatchType.LOGGED,
            productBoundStatement1,productBoundStatement2);

session.execute(batch);

이렇게 하면 LOGGED 배치 를 사용 하여 동일한 제품 데이터를 product_by_idproduct_by_name 테이블 에 삽입합니다.

5. 결론

이 기사에서는 Cassandra 배치 쿼리와 BatchStatement를 사용하여 이를 Cqlsh 및 Java에 적용하는 방법에 대해 배웠  습니다  .

항상 그렇듯이 예제의 전체 소스 코드는 GitHub에서 사용할 수 있습니다 .

Generic footer banner