1. 概要

このチュートリアルでは、Cassandraバッチクエリとそのさまざまなユースケースについて学習します。 単一パーティションと複数パーティションテーブルの両方のバッチクエリを分析します。

CqlshおよびJavaアプリケーションでのバッチ処理について説明します。

2. カサンドラバッチの基礎

Cassandraのような分散データベースは、リレーショナルデータベースとは異なり、ACID(Atomicity、Consistency、Isolation、および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;

まず、UNLOGGEDUSINGTIMESTAMPなどのオプションのパラメーターを指定せずにBEGINBATCH ステートメントを使用してバッチクエリを開始し、次にすべてのDML操作を含めます。 、productテーブルの挿入ステートメント。

最後に、 APPLYBATCHステートメントを使用してバッチを実行します。

t バッチクエリはロールバック機能をサポートしていないため、バッチクエリを元に戻すことはできないことに注意してください。

2.1. シングルパーティション

バッチステートメントは、単一のパーティション内のすべてのDMLステートメントを適用し、原子性と分離を保証します。

単一のパーティションを対象とする適切に設計されたバッチは、クライアント/サーバートラフィックを削減し、単一行の変更でテーブルをより効率的に更新できます。 これは、バッチ操作が単一のパーティションに書き込んでいる場合にのみバッチ分離が発生するためです。

単一のパーティションバッチには、同じパーティションキーを持ち、同じキースペースに存在する2つの異なるテーブルを含めることもできます。

シングルパーティションバッチ操作はデフォルトでログに記録されないため、ログ記録によるパフォーマンスの低下に悩まされることはありません。

次の図は、調整ノードHからパーティションノードBおよびそのレプリケーションノードCDへの単一パーティションバッチ要求フローを示しています。 ]:

礼儀:Datastax

2.2. 複数のパーティション

複数のパーティションを含むバッチは、複数のノード間の調整を伴うため、適切に設計する必要があります。 マルチパーティションバッチの最良の使用例は、同じデータを2つの関連するテーブル、つまり、異なるパーティションキーを持つ同じ列を持つ2つのテーブルに書き込むことです。

複数パーティションバッチ操作は、バッチログメカニズムを使用して原子性を保証します。 調整ノードは、バッチログ要求をバッチログノードに送信し、確認済みの受信を取得すると、バッチステートメントを実行します。 次に、ノードから batchlog を削除し、クライアントに確認を送信します。

複数のパーティションのバッチクエリを使用しないことをお勧めします。 これは、このようなクエリが調整ノードに大きなプレッシャーをかけ、そのパフォーマンスに深刻な影響を与えるためです。

他に実行可能なオプションがない場合にのみ、複数パーティションバッチを使用する必要があります。

次の図は、調整ノードHからパーティションノードBEおよびそれぞれのレプリケーションノードC[への複数パーティションバッチ要求フローを示しています。 X194X]、 D 、および F G

礼儀:Datastax

3. Cqlshでのバッチ実行

まず、 product テーブルを作成して、いくつかのバッチクエリを実行してみましょう。

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

 3.1. タイムスタンプなしの単一パーティションバッチ

product テーブルの単一パーティションを対象とした以下のバッチクエリを実行し、タイムスタンプを提供しません。

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を返す場合、バッチ全体が未処理です。

上記のクエリを実行すると、以下の正常な確認応答が得られます。

次に、バッチ実行後にデータのwritetimeが同じであるかどうかを確認しましょう。

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。 シングルパーティションタイムスタンプ付きのバッチ

USING TIMESTAMP オプションを使用して、タイムスタンプを epich 時間形式、つまりマイクロ秒で提供するバッチクエリの例を次に示します。

以下は、すべての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;

カスタムタイムスタンプとcompare-and-set(CAS)ロジックの両方を持つ無効なバッチクエリが表示されます。つまり、 IF NOT EXISTS clause

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。 複数のパーティションバッチクエリ

複数のパーティションでのバッチの最適な使用例は、正確なデータを2つの関連するテーブルに挿入することです。

異なるパーティションキーを持つproduct_by_nameテーブルとproduct_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_volCounterデータ型として格納するテーブルproduct_by_salesを作成しましょう。

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

以下のcounterバッチクエリは、sales_volを100倍に2回増やします。

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. Mavenの依存関係

まず、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 enumおよびBoundStatementインスタンスを使用してインスタンス化されます。

まず、Product属性をPreparedStatement insertクエリにバインドすることにより、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 バッチを使用して、同じパーティションキーに2つの製品バリアントを挿入します。

4.3. 複数パーティションバッチ

次に、同じデータを2つの関連するテーブル[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());
}

ここで、同じ Product 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_idテーブルとproduct_by_nameテーブルに挿入されます。

5. 結論

この記事では、Cassandraバッチクエリと、BatchStatementを使用してCqlshおよびJavaに適用する方法について学習しました。

いつものように、例の完全なソースコードは、GitHubから入手できます。