Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
72 changes: 63 additions & 9 deletions src/Ydb.Sdk/test/Ydb.Sdk.Ado.Stress.Loader/Cli.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ public static class Cli
"SQL query to execute during stress test"
);

public static readonly RootCommand RootCommand = new("YDB ADO.NET Stress Test Tank - Variable Load Generator")
private static readonly Command StressLoadCommand = new(
"cycle",
"runs workload (read and write to table with sets RPS)"
)
{
ConnectionString,
PeakRps,
Expand All @@ -53,14 +56,30 @@ public static class Cli
TestQuery
};

private static readonly Command LoadCommand = new(
"load",
"Load Mode (With PoolingSessionSource) vs (Without PoolingSessionSource)")
{
TotalTestTimeSeconds,
ConnectionString,
SaFilePath,
TestQuery
};

public static readonly RootCommand RootCommand = new("YDB ADO.NET Stress Test Tank - Variable Load Generator")
{
StressLoadCommand,
LoadCommand
};

static Cli()
{
RootCommand.SetHandler(async config =>
StressLoadCommand.SetHandler(async config =>
{
var stressLoader = new StressTestTank(config);
await stressLoader.RunAsync();
var stressLoader = new StressLoadTank(config);
await stressLoader.Run();
},
new ConfigBinder(
new StressConfigBinder(
ConnectionString,
PeakRps,
MediumRps,
Expand All @@ -73,10 +92,23 @@ static Cli()
TestQuery
)
);

LoadCommand.SetHandler(async config =>
{
var loader = new LoadTank(config);
await loader.Run();
},
new LoadConfigBinder(
ConnectionString,
TotalTestTimeSeconds,
SaFilePath,
TestQuery
)
);
}
}

public class ConfigBinder(
public class StressConfigBinder(
Argument<string> connectionString,
Option<int> peakRps,
Option<int> mediumRps,
Expand All @@ -87,9 +119,9 @@ public class ConfigBinder(
Option<int> totalTestTimeSeconds,
Option<string?> saFilePath,
Option<string> testQuery
) : BinderBase<StressTestConfig>
) : BinderBase<StressConfig>
{
protected override StressTestConfig GetBoundValue(BindingContext bindingContext) => new(
protected override StressConfig GetBoundValue(BindingContext bindingContext) => new(
ConnectionString: bindingContext.ParseResult.GetValueForArgument(connectionString),
PeakRps: bindingContext.ParseResult.GetValueForOption(peakRps),
MediumRps: bindingContext.ParseResult.GetValueForOption(mediumRps),
Expand All @@ -103,7 +135,22 @@ Option<string> testQuery
);
}

public record StressTestConfig(
public class LoadConfigBinder(
Argument<string> connectionString,
Option<int> totalTestTimeSeconds,
Option<string?> saFilePath,
Option<string> testQuery
) : BinderBase<LoadConfig>
{
protected override LoadConfig GetBoundValue(BindingContext bindingContext) => new LoadConfig(
bindingContext.ParseResult.GetValueForArgument(connectionString),
bindingContext.ParseResult.GetValueForOption(totalTestTimeSeconds),
bindingContext.ParseResult.GetValueForOption(saFilePath),
bindingContext.ParseResult.GetValueForOption(testQuery)!
);
}

public record StressConfig(
string ConnectionString,
int PeakRps,
int MediumRps,
Expand All @@ -115,3 +162,10 @@ public record StressTestConfig(
string? SaFilePath,
string TestQuery
);

public record LoadConfig(
string ConnectionString,
int TotalTestTimeSeconds,
string? SaFilePath,
string TestQuery
);
2 changes: 1 addition & 1 deletion src/Ydb.Sdk/test/Ydb.Sdk.Ado.Stress.Loader/LoadPattern.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Ydb.Sdk.Ado.Stress.Loader;

public class LoadPattern(StressTestConfig config, ILogger logger)
public class LoadPattern(StressConfig config, ILogger logger)
{
public async IAsyncEnumerable<int> GetLoadStepsAsync(
[EnumeratorCancellation] CancellationToken cancellationToken = default
Expand Down
104 changes: 104 additions & 0 deletions src/Ydb.Sdk/test/Ydb.Sdk.Ado.Stress.Loader/LoadTank.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using System.Data;
using Microsoft.Extensions.Logging;
using Ydb.Sdk.Yc;

namespace Ydb.Sdk.Ado.Stress.Loader;

public class LoadTank
{
private readonly ILogger<LoadTank> _logger;
private readonly YdbConnectionStringBuilder _settings;
private readonly LoadConfig _config;

public LoadTank(LoadConfig config)
{
var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Information));

_logger = loggerFactory.CreateLogger<LoadTank>();
_settings = new YdbConnectionStringBuilder(config.ConnectionString)
{
CredentialsProvider = config.SaFilePath != null
? new ServiceAccountProvider(config.SaFilePath, loggerFactory)
: new MetadataProvider(loggerFactory),
ServerCertificates = YcCerts.GetYcServerCertificates()
};
_config = config;
}

public async Task Run()
{
_logger.LogInformation(
"""
Starting YDB ADO.NET Stress Test Tank
Configuration:
Total Test Time: {TotalTime}s
Test Query: {TestQuery}
""",
_config.TotalTestTimeSeconds,
_config.TestQuery
);

_logger.LogInformation("[{Now}] Starting shooting with PoolingSessionSource...", DateTime.Now);
var ctsStep1 = new CancellationTokenSource();
var workers = new List<Task>();
ctsStep1.CancelAfter(_config.TotalTestTimeSeconds * 500);

for (var i = 0; i < _settings.MaxSessionPool; i++)
{
workers.Add(Task.Run(async () =>
{
while (!ctsStep1.IsCancellationRequested)
{
try
{
await using var ydbConnection = new YdbConnection(_settings);
await ydbConnection.OpenAsync(ctsStep1.Token);
await new YdbCommand(ydbConnection) { CommandText = _config.TestQuery }
.ExecuteNonQueryAsync(ctsStep1.Token);
}
catch (Exception)
{
// ignored
}
}
}, ctsStep1.Token));
}

await Task.WhenAll(workers);
workers.Clear();
_logger.LogInformation("Phase 1 stopped shooting");
await Task.Delay(10_000);

_logger.LogInformation("[{Now}] Starting shooting without PoolingSessionSource...", DateTime.Now);
var ctsStep2 = new CancellationTokenSource();
ctsStep2.CancelAfter(_config.TotalTestTimeSeconds * 500);
for (var i = 0; i < _settings.MaxSessionPool; i++)
{
workers.Add(Task.Run(async () =>
{
await using var ydbConnection = new YdbConnection(_settings);

while (!ctsStep2.IsCancellationRequested)
{
try
{
if (ydbConnection.State != ConnectionState.Open)
{
await ydbConnection.OpenAsync(ctsStep2.Token);
}

await new YdbCommand(ydbConnection) { CommandText = _config.TestQuery }
.ExecuteNonQueryAsync(ctsStep2.Token);
}
catch (Exception)
{
// ignored
}
}
}, ctsStep2.Token));
}

await Task.WhenAll(workers);
_logger.LogInformation("Phase 2 stopped shooting");
}
}
12 changes: 11 additions & 1 deletion src/Ydb.Sdk/test/Ydb.Sdk.Ado.Stress.Loader/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Variable load generator for testing YDB ADO.NET driver performance with step-like pattern.

## Description
## Description Cycle Mode

The stress test "tank" generates cyclical load using a step-like pattern:

Expand All @@ -17,3 +17,13 @@ Peak RPS (1000) ─┐
│ Medium Duration
└─ (cycle repeats)
```

## Description Load Mode

```
Loading With PoolingSourceSession

sleep 10s

Loading Without PoolingSourceSession
```
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@

namespace Ydb.Sdk.Ado.Stress.Loader;

public class StressTestTank
public class StressLoadTank
{
private readonly StressTestConfig _config;
private readonly ILogger<StressTestTank> _logger;
private readonly StressConfig _config;
private readonly ILogger<StressLoadTank> _logger;
private readonly YdbConnectionStringBuilder _settings;

public StressTestTank(StressTestConfig config)
public StressLoadTank(StressConfig config)
{
var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Information));

_config = config;
_logger = loggerFactory.CreateLogger<StressTestTank>();
_logger = loggerFactory.CreateLogger<StressLoadTank>();
_settings = new YdbConnectionStringBuilder(config.ConnectionString)
{
LoggerFactory = loggerFactory,
Expand All @@ -28,7 +28,7 @@ public StressTestTank(StressTestConfig config)
ValidateConfig();
}

public async Task RunAsync()
public async Task Run()
{
_logger.LogInformation(
"""
Expand Down Expand Up @@ -98,9 +98,11 @@ private async Task StartWorkersForRpsAsync(int targetRps, CancellationToken canc
{
if (targetRps <= 0) return;

targetRps = targetRps / 10 + targetRps % 10;

await using var rateLimiter = new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions
{
Window = TimeSpan.FromSeconds(1),
Window = TimeSpan.FromMilliseconds(100),
PermitLimit = targetRps,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = targetRps * 2
Expand Down
Loading