1. 概要

Apache Cassandraは、スケーラブルな分散型NoSQLデータベースです。 Cassandraはノード間でデータをストリーミングし、単一障害点のない継続的な可用性を提供します。 実際、Cassandraは、並外れたパフォーマンスで大量のデータを処理できます。

データベースを使用するアプリケーションを開発する場合、実行されたクエリをログに記録してデバッグできることが非常に重要です。 このチュートリアルでは、Spring BootでApacheCassandraを使用するときにクエリとステートメントをログに記録する方法を見ていきます。

この例では、Spring Dataリポジトリの抽象化とTestcontainersライブラリを利用します。 Spring構成を介してCassandraクエリログを構成する方法を説明します。 さらに、DatastaxRequestLoggerについても説明します。 この組み込みコンポーネントを構成して、より高度なロギングを行うことができます。

2. テスト環境のセットアップ

クエリロギングを示すために、テスト環境をセットアップする必要があります。 まず、ApacheCassandraSpring Dataを使用してテストデータを設定します。 次に、 Testcontainers ライブラリを使用して、統合テスト用のCassandraデータベースコンテナを実行します。

2.1. Cassandraリポジトリ

Spring Dataを使用すると、一般的なSpringインターフェイスに基づいてCassandraリポジトリを作成できます。 まず、単純なDAOクラスを定義することから始めましょう。

@Table
public class Person {

    @PrimaryKey
    private UUID id;
    private String firstName;
    private String lastName;

    public Person(UUID id, String firstName, String lastName) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
    }

    // getters, setters, equals and hash code
}

次に、 CassandraRepository インターフェイスを拡張して、DAOのSpring Dataリポジトリを定義します。

@Repository
public interface PersonRepository extends CassandraRepository<Person, UUID> {}

最後に、application.propertiesファイルに2つのプロパティを追加します。

spring.data.cassandra.schema-action=create_if_not_exists
spring.data.cassandra.local-datacenter=datacenter1

その結果、SpringDataは自動的に注釈付きテーブルを作成します。

create_if_not_exists オプションは、実稼働システムには推奨されないことに注意してください。

別の方法として、標準のルートクラスパスからschema.sqlスクリプトをロードすることでテーブルを作成できます。

2.2. カサンドラコンテナ

次のステップとして、特定のポートでCassandraコンテナを構成して公開しましょう。

@Container
public static final CassandraContainer cassandra = 
  (CassandraContainer) new CassandraContainer("cassandra:3.11.2").withExposedPorts(9042);

統合テストにコンテナを使用する前に、Spring Dataがコンテナとの接続を確立するために必要なテストプロパティをオーバーライドする必要があります。

TestPropertyValues.of(
  "spring.data.cassandra.keyspace-name=" + KEYSPACE_NAME,
  "spring.data.cassandra.contact-points=" + cassandra.getContainerIpAddress(),
  "spring.data.cassandra.port=" + cassandra.getMappedPort(9042)
).applyTo(configurableApplicationContext.getEnvironment());

createKeyspace(cassandra.getCluster());

最後に、オブジェクト/テーブルを作成する前に、Cassandraキースペースを作成する必要があります。 キースペースは、RDBMSのデータベースに似ています。

2.3. 統合テスト

これで、統合テストの作成を開始するための準備が整いました。

クエリの選択、挿入、削除のログに関心があります。 したがって、これらのさまざまなタイプのクエリをトリガーするいくつかのテストを作成します。

まず、人の記録を保存および更新するためのテストを作成します。 このテストでは、2つの挿入と1つの選択データベースクエリが実行されると予想されます。

@Test
void givenExistingPersonRecord_whenUpdatingIt_thenRecordIsUpdated() {
    UUID personId = UUIDs.timeBased();
    Person existingPerson = new Person(personId, "Luka", "Modric");
    personRepository.save(existingPerson);
    existingPerson.setFirstName("Marko");
    personRepository.save(existingPerson);

    List<Person> savedPersons = personRepository.findAllById(List.of(personId));
    assertThat(savedPersons.get(0).getFirstName()).isEqualTo("Marko");
}

次に、既存の人物レコードを保存および削除するためのテストを作成します。 このテストでは、データベースクエリの挿入、削除、および選択を1回実行する必要があります。

@Test
void givenExistingPersonRecord_whenDeletingIt_thenRecordIsDeleted() {
    UUID personId = UUIDs.timeBased();
    Person existingPerson = new Person(personId, "Luka", "Modric");

    personRepository.delete(existingPerson);

    List<Person> savedPersons = personRepository.findAllById(List.of(personId));
    assertThat(savedPersons.isEmpty()).isTrue();
}

デフォルトでは、コンソールに記録されたデータベースクエリは監視されません。

3. SpringDataCQLロギング

Spring Data for Apache Cassandraバージョン2.0以降では、application.propertiesでCqlTemplateクラスのログレベルを設定できます。

logging.level.org.springframework.data.cassandra.core.cql.CqlTemplate=DEBUG

したがって、ログレベルをDEBUGに設定することで、実行されたすべてのクエリとプリペアドステートメントのログを有効にします。

2021-09-25 12:41:58.679 DEBUG 17856 --- [           main] o.s.data.cassandra.core.cql.CqlTemplate:
  Executing CQL statement [CREATE TABLE IF NOT EXISTS person
  (birthdate date, firstname text, id uuid, lastname text, lastpurchaseddate timestamp, lastvisiteddate timestamp, PRIMARY KEY (id));]
2021-09-25 12:42:01.204 DEBUG 17856 --- [           main] o.s.data.cassandra.core.cql.CqlTemplate:
  Preparing statement [INSERT INTO person (birthdate,firstname,id,lastname,lastpurchaseddate,lastvisiteddate)
  VALUES (?,?,?,?,?,?)] using org.springframework.data.cassandra.core.CassandraTemplate$PreparedStatementHandler@4d16975b
2021-09-25 12:42:01.253 DEBUG 17856 --- [           main] o.s.data.cassandra.core.cql.CqlTemplate:
  Executing prepared statement [INSERT INTO person (birthdate,firstname,id,lastname,lastpurchaseddate,lastvisiteddate) VALUES (?,?,?,?,?,?)]
2021-09-25 12:42:01.279 DEBUG 17856 --- [           main] o.s.data.cassandra.core.cql.CqlTemplate:
  Preparing statement [INSERT INTO person (birthdate,firstname,id,lastname,lastpurchaseddate,lastvisiteddate)
  VALUES (?,?,?,?,?,?)] using org.springframework.data.cassandra.core.CassandraTemplate$PreparedStatementHandler@539dd2d0
2021-09-25 12:42:01.290 DEBUG 17856 --- [           main] o.s.data.cassandra.core.cql.CqlTemplate:
  Executing prepared statement [INSERT INTO person (birthdate,firstname,id,lastname,lastpurchaseddate,lastvisiteddate) VALUES (?,?,?,?,?,?)]
2021-09-25 12:42:01.351 DEBUG 17856 --- [           main] o.s.data.cassandra.core.cql.CqlTemplate:
  Preparing statement [SELECT * FROM person WHERE id IN (371bb4a0-1ded-11ec-8cad-934f1aec79e6)]
  using org.springframework.data.cassandra.core.CassandraTemplate$PreparedStatementHandler@3e61cffd
2021-09-25 12:42:01.370 DEBUG 17856 --- [           main] o.s.data.cassandra.core.cql.CqlTemplate:
  Executing prepared statement [SELECT * FROM person WHERE id IN (371bb4a0-1ded-11ec-8cad-934f1aec79e6)]

残念ながら、このソリューションを使用すると、ステートメントで使用されているバインドされた値の出力は表示されません。

4. Datastaxリクエストトラッカー

DataStax リクエストトラッカーは、セッション全体のコンポーネントであり、すべてのCassandraリクエストの結果について通知を受けます。

ApacheCassandra用のDataStaxJavaドライバーには、すべてのリクエストをログに記録するオプションのリクエストトラッカー実装が付属しています。

4.1. Noopリクエストトラッカー

デフォルトのリクエストトラッカーの実装はNoopRequestTrackerと呼ばれます。 したがって、何もしません。

System.setProperty("datastax-java-driver.advanced.request-tracker.class", "NoopRequestTracker");

別のトラッカーを設定するには、Cassandra構成またはシステムプロパティを介してRequestTrackerを実装するクラスを指定する必要があります。

4.2. ロガーを要求する

RequestLogger は、すべてのリクエストをログに記録するRequestTrackerの組み込み実装です。

特定のDataStaxJavaドライバーシステムプロパティを設定することで、これを有効にできます。

System.setProperty("datastax-java-driver.advanced.request-tracker.class", "RequestLogger");
System.setProperty("datastax-java-driver.advanced.request-tracker.logs.success.enabled", "true");
System.setProperty("datastax-java-driver.advanced.request-tracker.logs.slow.enabled", "true");
System.setProperty("datastax-java-driver.advanced.request-tracker.logs.error.enabled", "true");

この例では、成功したリクエスト、遅いリクエスト、失敗したリクエストすべてのログ記録を有効にしました。

ここで、テストを実行すると、実行されたすべてのデータベースクエリがログに記録されます。

2021-09-25 13:06:31.799  INFO 11172 --- [        s0-io-4] c.d.o.d.i.core.tracker.RequestLogger:
  [s0|90232530][Node(endPoint=localhost/[0:0:0:0:0:0:0:1]:49281, hostId=c50413d5-03b6-4037-9c46-29f0c0da595a, hashCode=68c305fe)]
  Success (6 ms) [6 values] INSERT INTO person (birthdate,firstname,id,lastname,lastpurchaseddate,lastvisiteddate)
  VALUES (?,?,?,?,?,?) [birthdate=NULL, firstname='Luka', id=a3ad6890-1df0-11ec-a295-7d319da1858a, lastname='Modric', lastpurchaseddate=NULL, lastvisiteddate=NULL]
2021-09-25 13:06:31.811  INFO 11172 --- [        s0-io-4] c.d.o.d.i.core.tracker.RequestLogger:
  [s0|778232359][Node(endPoint=localhost/[0:0:0:0:0:0:0:1]:49281, hostId=c50413d5-03b6-4037-9c46-29f0c0da595a, hashCode=68c305fe)]
  Success (4 ms) [6 values] INSERT INTO person (birthdate,firstname,id,lastname,lastpurchaseddate,lastvisiteddate)
  VALUES (?,?,?,?,?,?) [birthdate=NULL, firstname='Marko', id=a3ad6890-1df0-11ec-a295-7d319da1858a, lastname='Modric', lastpurchaseddate=NULL, lastvisiteddate=NULL]
2021-09-25 13:06:31.847  INFO 11172 --- [        s0-io-4] c.d.o.d.i.core.tracker.RequestLogger:
  [s0|1947131919][Node(endPoint=localhost/[0:0:0:0:0:0:0:1]:49281, hostId=c50413d5-03b6-4037-9c46-29f0c0da595a, hashCode=68c305fe)]
  Success (5 ms) [0 values] SELECT * FROM person WHERE id IN (a3ad6890-1df0-11ec-a295-7d319da1858a)

すべてのリクエストがカテゴリcom.datastax.oss.driver.internal.core.tracker.RequestLoggerの下に記録されていることがわかります。

さらに、ステートメントで使用されるすべてのバインドされた値も、デフォルトでログに記録されます

4.3. バインドされた値

組み込みのRequestLoggerは、高度にカスタマイズ可能なコンポーネントです。 次のシステムプロパティを使用して、バインドされた値の出力を構成できます。

System.setProperty("datastax-java-driver.advanced.request-tracker.logs.show-values", "true");
System.setProperty("datastax-java-driver.advanced.request-tracker.logs.max-value-length", "100");
System.setProperty("datastax-java-driver.advanced.request-tracker.logs.max-values", "100");

max-value-length プロパティで定義された値よりも長い場合、フォーマットされた値の表現は切り捨てられます。

max-values プロパティを使用して、ログに記録するバインドされた値の最大数を定義できます。

4.4. 追加オプション

最初の例では、遅いリクエストのロギングを有効にしました。 thresholdプロパティを使用して、成功したリクエストをslowとして分類できます。

System.setProperty("datastax-java-driver.advanced.request-tracker.logs.slow.threshold ", "1 second");

デフォルトでは、失敗したすべてのリクエストのスタックトレースがログに記録されます。 それらを無効にした場合、ログには例外の文字列表現のみが表示されます。

System.setProperty("datastax-java-driver.advanced.request-tracker.logs.show-stack-trace", "true");

成功したリクエストと遅いリクエストは、INFOログレベルを使用します。 一方、失敗した要求はERRORレベルを使用します。

5. 結論

この記事では、SpringBootでApacheCassandraを使用する場合のクエリとステートメントのロギングについて説明しました。

例では、ApacheCassandraのSpringDataでのログレベルの構成について説明しました。 Spring Dataはクエリをログに記録しますが、バインドされた値はログに記録しないことがわかりました。 最後に、DatastaxRequestTrackerについて説明しました。 これは高度にカスタマイズ可能なコンポーネントであり、Cassandraクエリをバインドされた値とともにログに記録するために使用できます。

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