Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
<module>url-shortener-demo</module>
<module>jdbc</module>
<module>project-course</module>
<module>slo</module>
<module>slo-workload</module>
</modules>

Expand Down
36 changes: 29 additions & 7 deletions slo-workload/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@ reliability of YDB Java clients under load and chaos using the
[YDB SLO action](https://github.com/ydb-platform/ydb-slo-action).

Each submodule is a self-contained, runnable workload that follows the same
contract as the SDK SLO workload in [`../slo`](../slo): it reads its
configuration from environment variables, runs setup/run/teardown phases, and
pushes OpenTelemetry (OTLP) metrics that the action scrapes and compares
between the current PR run and a baseline run.
contract: it reads its configuration from environment variables, runs
setup/run/teardown phases, and pushes OpenTelemetry (OTLP) metrics that the
action scrapes and compares between the current PR run and a baseline run.

Shared harness code lives in [`core`](core) (`Config`, `Metrics`, KV row
model, rate-limited runner). Every workload plugs a `KvClient` adapter into
that runner so all of them emit the same metric contract.

| Module | Component under test | Description |
| --- | --- | --- |
| [`query`](query) | `ydb-java-sdk` (query client) | Native SDK KV workload |
| [`jdbc`](jdbc) | `ydb-jdbc-driver` | Plain JDBC KV workload (no framework) |
| [`spring-data-jdbc`](spring-data-jdbc) | `ydb-jdbc-driver` + `spring-data-jdbc-ydb` + `spring-ydb-retry` | Spring Data JDBC KV workload |
| [`spring-data-jpa`](spring-data-jpa) | `ydb-jdbc-driver` + Hibernate 6 + `spring-ydb-retry` | Spring Data JPA KV workload |

## How a workload behaves

Expand Down Expand Up @@ -76,11 +82,27 @@ KV tunables are passed on the command line and parsed by JCommander:
--partition-size <int> Auto-partitioning partition size in MB (default 1)
--min-partition-count <int> Minimum number of table partitions (default 6)
--max-partition-count <int> Maximum number of table partitions (default 1000)
--duration <int> Override WORKLOAD_DURATION when > 0
--duration / --time <int> Override WORKLOAD_DURATION when > 0
--shutdown-time <int> Extra grace seconds for in-flight ops on shutdown (default 30)
--max-attempts <int> Per-operation attempt cap, initial + retries (default 10)
--max-workers <int> Hard cap on workers per operation type (default 64)
```

Unknown flags are ignored, so a workload accepts command strings designed for
other SDKs without erroring.
Unknown flags are rejected — a typo in the ydb-slo-action invocation should
fail loudly rather than silently fall back to defaults.

The Spring-backed workloads expose one more knob via the `SLO_HIKARI_POOL_SIZE`
environment variable (default `130`, sized for `2 × max-workers` plus headroom).
Raise it together with `--max-workers` or the workload measures Hikari
contention rather than the JDBC driver.

### Cross-implementation comparability

Every implementation derives the primary-key `hash` column from `id` with the
same client-side mix (`RowGenerator.numericHash`). A table written by the
`query` workload is therefore byte-compatible with the `jdbc` and Spring-Data
workloads — useful when one prefills the table and another reads from it
during cross-driver experiments.

## How CI uses this module

Expand Down
55 changes: 19 additions & 36 deletions slo/pom.xml → slo-workload/core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,83 +6,66 @@

<parent>
<groupId>tech.ydb.examples</groupId>
<artifactId>ydb-sdk-examples</artifactId>
<artifactId>slo-workload</artifactId>
<version>1.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

<artifactId>ydb-slo-workload</artifactId>
<name>YDB SLO workload</name>
<description>SLO workload application for testing YDB Java SDK reliability under load and chaos</description>

<properties>
<jcommander.version>1.82</jcommander.version>
<opentelemetry.version>1.59.0</opentelemetry.version>
<hdrhistogram.version>2.2.2</hdrhistogram.version>
</properties>
<artifactId>slo-workload-core</artifactId>
<name>YDB SLO workload core</name>
<description>
Driver-agnostic core of the YDB SLO workloads: OTLP metrics, env config,
the KV row model and the load-generating runner. Every concrete workload
(native query client, plain JDBC, Spring Data) plugs a KvClient into this
runner so all of them emit the exact same metric contract.
</description>

<dependencies>
<dependency>
<groupId>tech.ydb</groupId>
<artifactId>ydb-sdk-query</artifactId>
</dependency>

<dependency>
<groupId>com.beust</groupId>
<artifactId>jcommander</artifactId>
<version>${jcommander.version}</version>
</dependency>

<dependency>
<groupId>org.hdrhistogram</groupId>
<artifactId>HdrHistogram</artifactId>
<version>${hdrhistogram.version}</version>
</dependency>

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>

<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
<version>${opentelemetry.version}</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
<version>${opentelemetry.version}</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-metrics</artifactId>
<version>${opentelemetry.version}</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
<version>${opentelemetry.version}</version>
</dependency>

<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>

<build>
<finalName>ydb-slo-workload</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>libs/</classpathPrefix>
<mainClass>tech.ydb.slo.Main</mainClass>
</manifest>
</archive>
<release>${maven.compiler.release}</release>
</configuration>
</plugin>
</plugins>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,8 @@
package tech.ydb.slo;

/**
* Configuration for the SLO workload, populated from environment variables
* provided by the YDB SLO action runtime.
*
* <p>The action sets these variables on the workload container:
* <ul>
* <li>{@code YDB_CONNECTION_STRING} or {@code YDB_ENDPOINT} + {@code YDB_DATABASE} — YDB connection</li>
* <li>{@code WORKLOAD_REF} — value used as the {@code ref} label on all metrics</li>
* <li>{@code WORKLOAD_NAME} — workload name (also used as part of the table path)</li>
* <li>{@code WORKLOAD_DURATION} — workload run duration in seconds (0 = unlimited)</li>
* <li>{@code OTEL_EXPORTER_OTLP_ENDPOINT} — OTLP endpoint for pushing metrics</li>
* </ul>
*/
package tech.ydb.slo.core;

public final class Config {
private final String connectionString;
private final String jdbcUrl;
private final String token;
private final String ref;
private final String workloadName;
Expand All @@ -23,24 +11,34 @@ public final class Config {

private Config(
String connectionString,
String jdbcUrl,
String token,
String ref,
String workloadName,
int durationSeconds,
String otlpEndpoint
) {
this.connectionString = connectionString;
this.jdbcUrl = jdbcUrl;
this.token = token;
this.ref = ref;
this.workloadName = workloadName;
this.durationSeconds = durationSeconds;
this.otlpEndpoint = otlpEndpoint;
}



public String connectionString() {
return connectionString;
}



public String jdbcUrl() {
return jdbcUrl;
}

public String token() {
return token;
}
Expand All @@ -61,43 +59,72 @@ public String otlpEndpoint() {
return otlpEndpoint;
}

/**
* Loads configuration from environment variables.
*
* @return configuration instance
* @throws IllegalStateException if required variables are missing or invalid
*/
public static Config fromEnv() {


public static Config fromEnv(String defaultWorkloadName) {
String connectionString = resolveConnectionString();
if (connectionString == null || connectionString.isEmpty()) {
throw new IllegalStateException(
"YDB connection is not configured: set YDB_CONNECTION_STRING or YDB_ENDPOINT + YDB_DATABASE"
"YDB connection is not configured: set YDB_CONNECTION_STRING, "
+ "YDB_JDBC_URL or YDB_ENDPOINT + YDB_DATABASE"
);
}

String token = envOrDefault("YDB_TOKEN", "");
String ref = envOrDefault("WORKLOAD_REF", "unknown");
String workloadName = envOrDefault("WORKLOAD_NAME", "java-slo-workload");
String workloadName = envOrDefault("WORKLOAD_NAME", defaultWorkloadName);
int durationSeconds = parseInt(envOrDefault("WORKLOAD_DURATION", "600"), 600);
String otlpEndpoint = envOrDefault("OTEL_EXPORTER_OTLP_ENDPOINT", "");

return new Config(connectionString, token, ref, workloadName, durationSeconds, otlpEndpoint);
return new Config(
connectionString,
toJdbcUrl(connectionString),
token,
ref,
workloadName,
durationSeconds,
otlpEndpoint
);
}



private static String resolveConnectionString() {
String jdbc = System.getenv("YDB_JDBC_URL");
if (jdbc != null && !jdbc.isEmpty()) {
return stripJdbcPrefix(jdbc);
}

String cs = System.getenv("YDB_CONNECTION_STRING");
if (cs != null && !cs.isEmpty()) {
return cs;
return stripJdbcPrefix(cs);
}

String endpoint = System.getenv("YDB_ENDPOINT");
String database = System.getenv("YDB_DATABASE");
if (endpoint == null || endpoint.isEmpty() || database == null || database.isEmpty()) {
return null;
}
return composeConnectionString(endpoint, database);
}

private static String stripJdbcPrefix(String value) {
if (value.startsWith("jdbc:ydb:")) {
return value.substring("jdbc:ydb:".length());
}
return value;
}



private static String toJdbcUrl(String connectionString) {
if (connectionString.startsWith("jdbc:")) {
return connectionString;
}
return "jdbc:ydb:" + connectionString;
}

// Compose connection string in the form expected by GrpcTransport.forConnectionString:
// grpc://host:port/database
private static String composeConnectionString(String endpoint, String database) {
if (endpoint.endsWith("/") && database.startsWith("/")) {
return endpoint + database.substring(1);
}
Expand Down
Loading
Loading