Перейти к основному содержимому

Регистрация запросов с помощью Spring Data Cassandra

· 7 мин. чтения

1. Обзор

Apache Cassandra — это масштабируемая распределенная база данных NoSQL . Cassandra передает данные между узлами и обеспечивает постоянную доступность без единой точки отказа. Фактически, Cassandra способна обрабатывать большие объемы данных с исключительной производительностью.

При разработке приложения, использующего базу данных, очень важно иметь возможность протоколировать и отлаживать выполненные запросы. В этом руководстве мы рассмотрим, как регистрировать запросы и операторы при использовании Apache Cassandra с Spring Boot.

В наших примерах мы будем использовать абстракцию репозитория Spring Data и библиотеку Testcontainers . Мы увидим, как настроить ведение журнала запросов Cassandra с помощью конфигурации Spring. Кроме того, мы изучим регистратор запросов Datastax. Мы можем настроить этот встроенный компонент для более расширенного ведения журнала.

2. Настройка тестовой среды

Чтобы продемонстрировать ведение журнала запросов, нам нужно настроить тестовую среду. Для начала настроим тестовые данные с помощью Spring Data для Apache Cassandra . Далее мы воспользуемся библиотекой Testcontainers для запуска контейнера базы данных Cassandra для интеграционного тестирования.

2.1. Репозиторий Кассандры

Spring Data позволяет нам создавать репозитории Cassandra на основе общих интерфейсов Spring. Во-первых, давайте начнем с определения простого класса 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
}

Затем мы определим репозиторий Spring Data для нашего DAO, расширив интерфейс CassandraRepository :

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

Наконец, мы добавим два свойства в наш файл application.properties :

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

В результате Spring Data автоматически создаст для нас аннотированную таблицу.

Следует отметить, что опция 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 . Пространство ключей похоже на базу данных в СУБД.

2.3. Интеграционные тесты

Теперь у нас есть все необходимое, чтобы начать писать наши интеграционные тесты.

Нас интересует регистрация запросов на выбор, вставку и удаление . Таким образом, мы напишем пару тестов, которые будут запускать эти разные типы запросов.

Во-первых, мы напишем тест для сохранения и обновления записи человека. Мы ожидаем, что этот тест выполнит две вставки и один выборочный запрос к базе данных:

@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");
}

Затем мы напишем тест для сохранения и удаления существующей записи человека. Мы ожидаем, что этот тест выполнит один запрос на вставку, удаление и выборку базы данных:

@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. Ведение журнала CQL данных Spring

С Spring Data для Apache Cassandra версии 2.0 или выше можно установить уровень журнала для класса CqlTemplate в application.properties :

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 .

Драйвер Java DataStax для Apache Cassandra поставляется с дополнительной реализацией отслеживания запросов, которая регистрирует все запросы.

4.1. Трекер Noop-запросов

Реализация трекера запросов по умолчанию называется NoopRequestTracker . Следовательно, он ничего не делает:

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

Чтобы настроить другой трекер, мы должны указать класс, реализующий RequestTracker , в конфигурации Cassandra или через системные свойства.

4.2. Регистратор запросов

RequestLogger — это встроенная реализация RequestTracker , которая регистрирует каждый запрос .

Мы можем включить его, установив определенные системные свойства драйвера DataStax Java:

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 , чтобы классифицировать успешный запрос как медленный :

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. Вывод

В этой статье мы рассмотрели логирование запросов и операторов при использовании Apache Cassandra с Spring Boot .

В примерах мы рассмотрели настройку уровня журнала в Spring Data для Apache Cassandra. Мы видели, что Spring Data будет регистрировать запросы, но не связанные значения. Наконец, мы изучили средство отслеживания запросов Datastax. Это настраиваемый компонент, который мы можем использовать для регистрации запросов Cassandra вместе с их связанными значениями.

Как всегда, исходный код доступен на GitHub.