From 494b47dc325152866ead8fd8f514fd35aa839998 Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Fri, 11 Jul 2025 22:30:10 +0300 Subject: [PATCH 1/7] init commit --- src/Ydb.Sdk/src/{ => Ado}/Pool/ChannelPool.cs | 2 +- .../src/{ => Ado}/Pool/EndpointPool.cs | 2 +- src/Ydb.Sdk/src/Ado/Pool/SessionPool.cs | 6 + src/Ydb.Sdk/src/Ado/PoolManager.cs | 2 +- src/Ydb.Sdk/src/Ado/Session/IAdoSession.cs | 17 +++ src/Ydb.Sdk/src/Ado/YdbConnection.cs | 4 +- .../src/Ado/YdbConnectionStringBuilder.cs | 37 ++++++ src/Ydb.Sdk/src/Driver.cs | 3 +- src/Ydb.Sdk/src/IDriver.cs | 2 +- src/Ydb.Sdk/src/Properties/AssemblyInfo.cs | 1 + .../Auth/StaticCredentialsAuthClient.cs | 2 +- .../test/Ydb.Sdk.Ado.Benchmarks/Program.cs | 12 ++ .../test/Ydb.Sdk.Ado.Benchmarks/README.md | 1 + .../SessionPoolBenchmark.cs | 114 ++++++++++++++++++ .../Ydb.Sdk.Ado.Benchmarks.csproj | 19 +++ .../Pool/ChannelPoolTests.cs | 2 +- .../Pool/EndpointPoolTests.cs | 2 +- .../YdbConnectionStringBuilderTests.cs | 15 ++- src/YdbSdk.sln | 7 ++ 19 files changed, 235 insertions(+), 15 deletions(-) rename src/Ydb.Sdk/src/{ => Ado}/Pool/ChannelPool.cs (99%) rename src/Ydb.Sdk/src/{ => Ado}/Pool/EndpointPool.cs (99%) create mode 100644 src/Ydb.Sdk/src/Ado/Pool/SessionPool.cs create mode 100644 src/Ydb.Sdk/src/Ado/Session/IAdoSession.cs create mode 100644 src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/Program.cs create mode 100644 src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/README.md create mode 100644 src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/SessionPoolBenchmark.cs create mode 100644 src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/Ydb.Sdk.Ado.Benchmarks.csproj diff --git a/src/Ydb.Sdk/src/Pool/ChannelPool.cs b/src/Ydb.Sdk/src/Ado/Pool/ChannelPool.cs similarity index 99% rename from src/Ydb.Sdk/src/Pool/ChannelPool.cs rename to src/Ydb.Sdk/src/Ado/Pool/ChannelPool.cs index 84a3c50d..34c27608 100644 --- a/src/Ydb.Sdk/src/Pool/ChannelPool.cs +++ b/src/Ydb.Sdk/src/Ado/Pool/ChannelPool.cs @@ -6,7 +6,7 @@ using Grpc.Net.Client; using Microsoft.Extensions.Logging; -namespace Ydb.Sdk.Pool; +namespace Ydb.Sdk.Ado.Pool; internal class ChannelPool : IAsyncDisposable where T : ChannelBase, IDisposable { diff --git a/src/Ydb.Sdk/src/Pool/EndpointPool.cs b/src/Ydb.Sdk/src/Ado/Pool/EndpointPool.cs similarity index 99% rename from src/Ydb.Sdk/src/Pool/EndpointPool.cs rename to src/Ydb.Sdk/src/Ado/Pool/EndpointPool.cs index feff114e..61a5eff7 100644 --- a/src/Ydb.Sdk/src/Pool/EndpointPool.cs +++ b/src/Ydb.Sdk/src/Ado/Pool/EndpointPool.cs @@ -1,7 +1,7 @@ using System.Collections.Immutable; using Microsoft.Extensions.Logging; -namespace Ydb.Sdk.Pool; +namespace Ydb.Sdk.Ado.Pool; internal class EndpointPool { diff --git a/src/Ydb.Sdk/src/Ado/Pool/SessionPool.cs b/src/Ydb.Sdk/src/Ado/Pool/SessionPool.cs new file mode 100644 index 00000000..cc21cd52 --- /dev/null +++ b/src/Ydb.Sdk/src/Ado/Pool/SessionPool.cs @@ -0,0 +1,6 @@ +namespace Ydb.Sdk.Ado.Pool; + +public class SessionPool +{ + +} diff --git a/src/Ydb.Sdk/src/Ado/PoolManager.cs b/src/Ydb.Sdk/src/Ado/PoolManager.cs index 7de299b8..ed2712d6 100644 --- a/src/Ydb.Sdk/src/Ado/PoolManager.cs +++ b/src/Ydb.Sdk/src/Ado/PoolManager.cs @@ -9,7 +9,7 @@ internal static class PoolManager private static readonly SemaphoreSlim SemaphoreSlim = new(1); // async mutex private static readonly ConcurrentDictionary Pools = new(); - internal static async Task GetSession( + internal static async Task GetSession( YdbConnectionStringBuilder connectionString, CancellationToken cancellationToken ) diff --git a/src/Ydb.Sdk/src/Ado/Session/IAdoSession.cs b/src/Ydb.Sdk/src/Ado/Session/IAdoSession.cs new file mode 100644 index 00000000..578d10bf --- /dev/null +++ b/src/Ydb.Sdk/src/Ado/Session/IAdoSession.cs @@ -0,0 +1,17 @@ +using Ydb.Query; +using Ydb.Sdk.Services.Query; +using Ydb.Sdk.Value; + +namespace Ydb.Sdk.Ado.Session; + +internal interface IAdoSession +{ + internal ValueTask> ExecuteQuery( + string query, + Dictionary? parameters, + ExecuteQuerySettings? settings, + TransactionControl? txControl + ); + + +} diff --git a/src/Ydb.Sdk/src/Ado/YdbConnection.cs b/src/Ydb.Sdk/src/Ado/YdbConnection.cs index 63ebbbe0..8cac45c2 100644 --- a/src/Ydb.Sdk/src/Ado/YdbConnection.cs +++ b/src/Ydb.Sdk/src/Ado/YdbConnection.cs @@ -24,7 +24,7 @@ private YdbConnectionStringBuilder ConnectionStringBuilder [param: AllowNull] init => _connectionStringBuilder = value; } - internal Session Session + internal Services.Query.Session Session { get { @@ -35,7 +35,7 @@ internal Session Session private set => _session = value; } - private Session _session = null!; + private Services.Query.Session _session = null!; public YdbConnection() { diff --git a/src/Ydb.Sdk/src/Ado/YdbConnectionStringBuilder.cs b/src/Ydb.Sdk/src/Ado/YdbConnectionStringBuilder.cs index 669c2bf1..ce908ba4 100644 --- a/src/Ydb.Sdk/src/Ado/YdbConnectionStringBuilder.cs +++ b/src/Ydb.Sdk/src/Ado/YdbConnectionStringBuilder.cs @@ -3,6 +3,7 @@ using System.Security.Cryptography.X509Certificates; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Ydb.Sdk.Ado.Internal; using Ydb.Sdk.Auth; using Ydb.Sdk.Pool; using Ydb.Sdk.Transport; @@ -29,6 +30,8 @@ private void InitDefaultValues() _port = YdbAdoDefaultSettings.Port; _database = YdbAdoDefaultSettings.Database; _maxSessionPool = SessionPoolDefaultSettings.MaxSessionPool; + _minSessionPool = 0; + _sessionIdleTimeout = 300; _useTls = YdbAdoDefaultSettings.UseTls; _connectTimeout = GrpcDefaultSettings.ConnectTimeoutSeconds; _keepAlivePingDelay = GrpcDefaultSettings.KeepAlivePingSeconds; @@ -123,6 +126,40 @@ public int MaxSessionPool private int _maxSessionPool; + public int MinSessionPool + { + get => _minSessionPool; + set + { + if (value <= 0) + { + throw new ArgumentOutOfRangeException(nameof(value), value, "Invalid min session pool: " + value); + } + + _minSessionPool = value; + SaveValue(nameof(MinSessionPool), value); + } + } + + private int _minSessionPool; + + public int SessionIdleTimeout + { + get => _sessionIdleTimeout; + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), value, "Invalid session idle timeout: " + value); + } + + _sessionIdleTimeout = value; + SaveValue(nameof(SessionIdleTimeout), value); + } + } + + private int _sessionIdleTimeout; + public bool UseTls { get => _useTls; diff --git a/src/Ydb.Sdk/src/Driver.cs b/src/Ydb.Sdk/src/Driver.cs index de842a76..84e580ca 100644 --- a/src/Ydb.Sdk/src/Driver.cs +++ b/src/Ydb.Sdk/src/Driver.cs @@ -5,8 +5,7 @@ using Ydb.Discovery; using Ydb.Discovery.V1; using Ydb.Sdk.Ado; -using Ydb.Sdk.Ado.Internal; -using Ydb.Sdk.Pool; +using Ydb.Sdk.Ado.Pool; namespace Ydb.Sdk; diff --git a/src/Ydb.Sdk/src/IDriver.cs b/src/Ydb.Sdk/src/IDriver.cs index 7461b704..195f8e8b 100644 --- a/src/Ydb.Sdk/src/IDriver.cs +++ b/src/Ydb.Sdk/src/IDriver.cs @@ -1,9 +1,9 @@ using Grpc.Core; using Grpc.Net.Client; using Microsoft.Extensions.Logging; +using Ydb.Sdk.Ado.Pool; using Ydb.Sdk.Ado; using Ydb.Sdk.Auth; -using Ydb.Sdk.Pool; using Ydb.Sdk.Services.Auth; namespace Ydb.Sdk; diff --git a/src/Ydb.Sdk/src/Properties/AssemblyInfo.cs b/src/Ydb.Sdk/src/Properties/AssemblyInfo.cs index 58886ee6..c972dbd4 100644 --- a/src/Ydb.Sdk/src/Properties/AssemblyInfo.cs +++ b/src/Ydb.Sdk/src/Properties/AssemblyInfo.cs @@ -1,5 +1,6 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("EntityFrameworkCore.Ydb")] +[assembly: InternalsVisibleTo("Ydb.Sdk.Ado.Benchmarks")] [assembly: InternalsVisibleTo("Ydb.Sdk.Ado.Tests")] [assembly: InternalsVisibleTo("Ydb.Sdk.Topic.Tests")] diff --git a/src/Ydb.Sdk/src/Services/Auth/StaticCredentialsAuthClient.cs b/src/Ydb.Sdk/src/Services/Auth/StaticCredentialsAuthClient.cs index e93fa298..7f1c69d2 100644 --- a/src/Ydb.Sdk/src/Services/Auth/StaticCredentialsAuthClient.cs +++ b/src/Ydb.Sdk/src/Services/Auth/StaticCredentialsAuthClient.cs @@ -3,10 +3,10 @@ using Microsoft.Extensions.Logging; using Ydb.Auth; using Ydb.Auth.V1; +using Ydb.Sdk.Ado.Pool; using Ydb.Sdk.Ado; using Ydb.Sdk.Ado.Internal; using Ydb.Sdk.Auth; -using Ydb.Sdk.Pool; namespace Ydb.Sdk.Services.Auth; diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/Program.cs b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/Program.cs new file mode 100644 index 00000000..52b9f833 --- /dev/null +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/Program.cs @@ -0,0 +1,12 @@ +using System.Reflection; +using BenchmarkDotNet.Running; + +Console.WriteLine("YDB .NET SDK Session Pool Benchmarks"); +Console.WriteLine("====================================="); + +var summaries = new BenchmarkSwitcher(typeof(Program).GetTypeInfo().Assembly).Run(args); + +foreach (var summary in summaries) +{ + Console.WriteLine($"Benchmark completed. Results saved to: {summary.ResultsDirectoryPath}"); +} diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/README.md b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/README.md new file mode 100644 index 00000000..4763ac7f --- /dev/null +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/README.md @@ -0,0 +1 @@ +# YDB .NET SDK Session Pool Benchmarks \ No newline at end of file diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/SessionPoolBenchmark.cs b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/SessionPoolBenchmark.cs new file mode 100644 index 00000000..4237d43e --- /dev/null +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/SessionPoolBenchmark.cs @@ -0,0 +1,114 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Ydb.Sdk.Pool; + +namespace Ydb.Sdk.Ado.Benchmarks; + +[SimpleJob(RuntimeMoniker.Net80)] +[MemoryDiagnoser] +[ThreadingDiagnoser] +public class SessionPoolBenchmark +{ + private TestSessionPool _sessionPool = null!; + private const int SessionPoolSize = 50; + private const int ConcurrentTasks = 20; + + [GlobalSetup] + public void Setup() + { + var config = new SessionPoolConfig(MaxSessionPool: SessionPoolSize); + + _sessionPool = new TestSessionPool(NullLogger.Instance, config); + } + + [GlobalCleanup] + public async Task Cleanup() => await _sessionPool.DisposeAsync(); + + [Benchmark] + public async Task SingleThreaded_OpenClose() + { + var session = await _sessionPool.GetSession(); + await session.Release(); + } + + [Benchmark] + public async Task MultiThreaded_OpenClose() + { + var tasks = new Task[ConcurrentTasks]; + + for (var i = 0; i < ConcurrentTasks; i++) + { + tasks[i] = Task.Run(async () => + { + var session = await _sessionPool.GetSession(); + await session.Release(); + }); + } + + await Task.WhenAll(tasks); + } + + [Benchmark] + public async Task HighContention_OpenClose() + { + const int highContentionTasks = 100; + var tasks = new Task[highContentionTasks]; + + for (var i = 0; i < highContentionTasks; i++) + { + tasks[i] = Task.Run(async () => + { + var session = await _sessionPool.GetSession(); + await session.Release(); + }); + } + + await Task.WhenAll(tasks); + } + + [Benchmark] + public async Task SessionReuse_Pattern() + { + const int iterations = 10; + var tasks = new Task[ConcurrentTasks]; + + for (var i = 0; i < ConcurrentTasks; i++) + { + tasks[i] = Task.Run(async () => + { + for (var j = 0; j < iterations; j++) + { + var session = await _sessionPool.GetSession(); + await session.Release(); + } + }); + } + + await Task.WhenAll(tasks); + } +} + +internal class TestSessionPool(ILogger logger, SessionPoolConfig config) + : SessionPool(logger, config) +{ + private volatile int _sessionIdCounter; + + protected override Task CreateSession(CancellationToken cancellationToken = default) + { + var sessionId = $"test-session-{Interlocked.Increment(ref _sessionIdCounter)}"; + var session = new TestSession(this, sessionId, nodeId: 1); + return Task.FromResult(session); + } +} + +internal class TestSession : SessionBase +{ + internal TestSession(SessionPool sessionPool, string sessionId, long nodeId) + : base(sessionPool, sessionId, nodeId, NullLogger.Instance) + { + } + + internal override Task DeleteSession() => Task.FromResult(Status.Success); +} diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/Ydb.Sdk.Ado.Benchmarks.csproj b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/Ydb.Sdk.Ado.Benchmarks.csproj new file mode 100644 index 00000000..bf0f3e33 --- /dev/null +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/Ydb.Sdk.Ado.Benchmarks.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Pool/ChannelPoolTests.cs b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Pool/ChannelPoolTests.cs index ecf034b6..836543ad 100644 --- a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Pool/ChannelPoolTests.cs +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Pool/ChannelPoolTests.cs @@ -3,8 +3,8 @@ using Grpc.Core; using Moq; using Xunit; +using Ydb.Sdk.Ado.Pool; using Ydb.Sdk.Ado.Tests.Utils; -using Ydb.Sdk.Pool; namespace Ydb.Sdk.Ado.Tests.Pool; diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Pool/EndpointPoolTests.cs b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Pool/EndpointPoolTests.cs index b9c5ab3e..80c861aa 100644 --- a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Pool/EndpointPoolTests.cs +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Pool/EndpointPoolTests.cs @@ -1,8 +1,8 @@ using System.Collections.Immutable; using Moq; using Xunit; +using Ydb.Sdk.Ado.Pool; using Ydb.Sdk.Ado.Tests.Utils; -using Ydb.Sdk.Pool; namespace Ydb.Sdk.Ado.Tests.Pool; diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionStringBuilderTests.cs b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionStringBuilderTests.cs index 2b51b663..8911ca8f 100644 --- a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionStringBuilderTests.cs +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionStringBuilderTests.cs @@ -13,6 +13,7 @@ public void InitDefaultValues_WhenEmptyConstructorInvoke_ReturnDefaultConnection Assert.Equal("localhost", ydbConnectionStringBuilder.Host); Assert.Equal("/local", ydbConnectionStringBuilder.Database); Assert.Equal(100, ydbConnectionStringBuilder.MaxSessionPool); + Assert.Equal(0, ydbConnectionStringBuilder.MinSessionPool); Assert.Null(ydbConnectionStringBuilder.User); Assert.Null(ydbConnectionStringBuilder.Password); Assert.Equal(5, ydbConnectionStringBuilder.ConnectTimeout); @@ -25,6 +26,8 @@ public void InitDefaultValues_WhenEmptyConstructorInvoke_ReturnDefaultConnection Assert.False(ydbConnectionStringBuilder.DisableDiscovery); Assert.False(ydbConnectionStringBuilder.DisableServerBalancer); Assert.Equal(5, ydbConnectionStringBuilder.CreateSessionTimeout); + Assert.Equal(300, ydbConnectionStringBuilder.SessionIdleTimeout); + Assert.False(ydbConnectionStringBuilder.UseTls); } [Fact] @@ -45,7 +48,8 @@ public void InitConnectionStringBuilder_WhenExpectedKeys_ReturnUpdatedConnection "ConnectTimeout=30;KeepAlivePingDelay=30;KeepAlivePingTimeout=60;" + "EnableMultipleHttp2Connections=true;CreateSessionTimeout=30;" + "MaxSendMessageSize=1000000;MaxReceiveMessageSize=1000000;" + - "DisableDiscovery=true;DisableServerBalancer=true" + "DisableDiscovery=true;DisableServerBalancer=true;" + + "SessionIdleTimeout=240;MinSessionPool=10;MaxSessionPool=50" ); Assert.Equal(2135, connectionString.Port); @@ -64,10 +68,14 @@ public void InitConnectionStringBuilder_WhenExpectedKeys_ReturnUpdatedConnection "ConnectTimeout=30;KeepAlivePingDelay=30;KeepAlivePingTimeout=60;" + "EnableMultipleHttp2Connections=True;CreateSessionTimeout=30;" + "MaxSendMessageSize=1000000;MaxReceiveMessageSize=1000000;" + - "DisableDiscovery=True;DisableServerBalancer=True", connectionString.ConnectionString); + "DisableDiscovery=True;DisableServerBalancer=True" + + "SessionIdleTimeout=240;MinSessionPool=10;MaxSessionPool=50", connectionString.ConnectionString); Assert.True(connectionString.DisableDiscovery); Assert.True(connectionString.DisableServerBalancer); Assert.Equal(30, connectionString.CreateSessionTimeout); + Assert.Equal(240, connectionString.SessionIdleTimeout); + Assert.Equal(10, connectionString.MinSessionPool); + Assert.Equal(50, connectionString.MaxSessionPool); } [Fact] @@ -79,8 +87,7 @@ public void Host_WhenSetInProperty_ReturnUpdatedConnectionString() connectionString.Host = "new_server"; Assert.Equal("new_server", connectionString.Host); - Assert.Equal("Host=new_server;Port=2135;Database=/my/path;User=Kirill", - connectionString.ConnectionString); + Assert.Equal("Host=new_server;Port=2135;Database=/my/path;User=Kirill", connectionString.ConnectionString); } [Fact] diff --git a/src/YdbSdk.sln b/src/YdbSdk.sln index 46eb69ab..9803346a 100644 --- a/src/YdbSdk.sln +++ b/src/YdbSdk.sln @@ -35,6 +35,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ydb.Sdk.Topic.Tests", "Ydb. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ydb.Sdk.Ado.Dapper.Tests", "Ydb.Sdk\test\Ydb.Sdk.Ado.Dapper.Tests\Ydb.Sdk.Ado.Dapper.Tests.csproj", "{AABFEDAE-1AEE-4ACF-804F-7F3C4D610CD8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ydb.Sdk.Ado.Benchmarks", "Ydb.Sdk\test\Ydb.Sdk.Ado.Benchmarks\Ydb.Sdk.Ado.Benchmarks.csproj", "{3C20B44A-9649-4927-9D4A-8605CC1D1271}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -69,6 +71,10 @@ Global {AABFEDAE-1AEE-4ACF-804F-7F3C4D610CD8}.Debug|Any CPU.Build.0 = Debug|Any CPU {AABFEDAE-1AEE-4ACF-804F-7F3C4D610CD8}.Release|Any CPU.ActiveCfg = Release|Any CPU {AABFEDAE-1AEE-4ACF-804F-7F3C4D610CD8}.Release|Any CPU.Build.0 = Release|Any CPU + {3C20B44A-9649-4927-9D4A-8605CC1D1271}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C20B44A-9649-4927-9D4A-8605CC1D1271}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C20B44A-9649-4927-9D4A-8605CC1D1271}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C20B44A-9649-4927-9D4A-8605CC1D1271}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -85,6 +91,7 @@ Global {5F30D97C-E6D2-451D-B2FB-F9F7BA052A52} = {316B82EF-019D-4267-95A9-5E243086B240} {0FF9D790-72A9-46DC-96DA-7EF1BE685A08} = {316B82EF-019D-4267-95A9-5E243086B240} {AABFEDAE-1AEE-4ACF-804F-7F3C4D610CD8} = {316B82EF-019D-4267-95A9-5E243086B240} + {3C20B44A-9649-4927-9D4A-8605CC1D1271} = {316B82EF-019D-4267-95A9-5E243086B240} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0AB27123-0C66-4E43-A75F-D9EAB9ED0849} From 8f0406da93e7d57863e9dc9d4a1c11a079d8f4ca Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Tue, 15 Jul 2025 14:42:17 +0300 Subject: [PATCH 2/7] dev: prepare --- src/Ydb.Sdk/src/Ado/Pool/SessionPool.cs | 6 - src/Ydb.Sdk/src/Ado/Session/IAdoSession.cs | 17 -- src/Ydb.Sdk/src/Ado/Session/ISession.cs | 23 ++ src/Ydb.Sdk/src/Ado/Session/ISessionSource.cs | 8 + .../src/Ado/Session/ImplicitSession.cs | 56 ++++ .../src/Ado/Session/PoolingSessionSource.cs | 17 ++ src/Ydb.Sdk/src/Ado/Session/Session.cs | 240 ++++++++++++++++++ src/Ydb.Sdk/src/Client/Response.cs | 4 +- src/Ydb.Sdk/src/Driver.cs | 3 +- src/Ydb.Sdk/src/IDriver.cs | 6 +- src/Ydb.Sdk/src/{Ado => }/Pool/ChannelPool.cs | 2 +- .../src/{Ado => }/Pool/EndpointPool.cs | 2 +- .../Auth/StaticCredentialsAuthClient.cs | 2 +- src/Ydb.Sdk/src/Services/Query/SessionPool.cs | 15 +- src/Ydb.Sdk/src/Services/Query/Settings.cs | 4 +- .../src/Services/Table/ExecuteScanQuery.cs | 2 +- src/Ydb.Sdk/src/Services/Table/ReadTable.cs | 2 +- .../Pool/ChannelPoolTests.cs | 2 +- .../Pool/EndpointPoolTests.cs | 7 +- 19 files changed, 373 insertions(+), 45 deletions(-) delete mode 100644 src/Ydb.Sdk/src/Ado/Pool/SessionPool.cs delete mode 100644 src/Ydb.Sdk/src/Ado/Session/IAdoSession.cs create mode 100644 src/Ydb.Sdk/src/Ado/Session/ISession.cs create mode 100644 src/Ydb.Sdk/src/Ado/Session/ISessionSource.cs create mode 100644 src/Ydb.Sdk/src/Ado/Session/ImplicitSession.cs create mode 100644 src/Ydb.Sdk/src/Ado/Session/PoolingSessionSource.cs create mode 100644 src/Ydb.Sdk/src/Ado/Session/Session.cs rename src/Ydb.Sdk/src/{Ado => }/Pool/ChannelPool.cs (99%) rename src/Ydb.Sdk/src/{Ado => }/Pool/EndpointPool.cs (99%) diff --git a/src/Ydb.Sdk/src/Ado/Pool/SessionPool.cs b/src/Ydb.Sdk/src/Ado/Pool/SessionPool.cs deleted file mode 100644 index cc21cd52..00000000 --- a/src/Ydb.Sdk/src/Ado/Pool/SessionPool.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Ydb.Sdk.Ado.Pool; - -public class SessionPool -{ - -} diff --git a/src/Ydb.Sdk/src/Ado/Session/IAdoSession.cs b/src/Ydb.Sdk/src/Ado/Session/IAdoSession.cs deleted file mode 100644 index 578d10bf..00000000 --- a/src/Ydb.Sdk/src/Ado/Session/IAdoSession.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Ydb.Query; -using Ydb.Sdk.Services.Query; -using Ydb.Sdk.Value; - -namespace Ydb.Sdk.Ado.Session; - -internal interface IAdoSession -{ - internal ValueTask> ExecuteQuery( - string query, - Dictionary? parameters, - ExecuteQuerySettings? settings, - TransactionControl? txControl - ); - - -} diff --git a/src/Ydb.Sdk/src/Ado/Session/ISession.cs b/src/Ydb.Sdk/src/Ado/Session/ISession.cs new file mode 100644 index 00000000..bf5522b3 --- /dev/null +++ b/src/Ydb.Sdk/src/Ado/Session/ISession.cs @@ -0,0 +1,23 @@ +using Ydb.Query; +using Ydb.Sdk.Value; +using TransactionControl = Ydb.Query.TransactionControl; + +namespace Ydb.Sdk.Ado.Session; + +internal interface ISession +{ + ValueTask> ExecuteQuery( + string query, + Dictionary parameters, + GrpcRequestSettings settings, + TransactionControl? txControl + ); + + Task CommitTransaction(string txId, CancellationToken cancellationToken = default); + + Task RollbackTransaction(string txId, CancellationToken cancellationToken = default); + + void OnNotSuccessStatusCode(StatusCode code); + + void Close(); +} diff --git a/src/Ydb.Sdk/src/Ado/Session/ISessionSource.cs b/src/Ydb.Sdk/src/Ado/Session/ISessionSource.cs new file mode 100644 index 00000000..db81e3ba --- /dev/null +++ b/src/Ydb.Sdk/src/Ado/Session/ISessionSource.cs @@ -0,0 +1,8 @@ +namespace Ydb.Sdk.Ado.Session; + +internal interface ISessionSource where TSession : ISession +{ + ValueTask OpenSession(); + + void Return(TSession session); +} diff --git a/src/Ydb.Sdk/src/Ado/Session/ImplicitSession.cs b/src/Ydb.Sdk/src/Ado/Session/ImplicitSession.cs new file mode 100644 index 00000000..c873055a --- /dev/null +++ b/src/Ydb.Sdk/src/Ado/Session/ImplicitSession.cs @@ -0,0 +1,56 @@ +using Ydb.Query; +using Ydb.Query.V1; +using Ydb.Sdk.Value; + +namespace Ydb.Sdk.Ado.Session; + +internal class ImplicitSession : ISession +{ + private readonly IDriver _driver; + + public ImplicitSession(IDriver driver) + { + _driver = driver; + } + + public ValueTask> ExecuteQuery( + string query, + Dictionary parameters, + GrpcRequestSettings settings, + TransactionControl? txControl + ) + { + if (txControl is not null && !txControl.CommitTx) + { + throw NotSupportedTransaction; + } + + var request = new ExecuteQueryRequest + { + ExecMode = ExecMode.Execute, + QueryContent = new QueryContent { Text = query, Syntax = Syntax.YqlV1 }, + StatsMode = StatsMode.None, + TxControl = txControl + }; + request.Parameters.Add(parameters.ToDictionary(p => p.Key, p => p.Value.GetProto())); + + return _driver.ServerStreamCall(QueryService.ExecuteQueryMethod, request, settings); + } + + public Task CommitTransaction(string txId, CancellationToken cancellationToken = default) => + throw NotSupportedTransaction; + + public Task RollbackTransaction(string txId, CancellationToken cancellationToken = default) => + throw NotSupportedTransaction; + + public void OnNotSuccessStatusCode(StatusCode code) + { + } + + public void Close() + { + } + + private static YdbException NotSupportedTransaction => + new(StatusCode.BadRequest, "Transactions are not supported in implicit sessions"); +} diff --git a/src/Ydb.Sdk/src/Ado/Session/PoolingSessionSource.cs b/src/Ydb.Sdk/src/Ado/Session/PoolingSessionSource.cs new file mode 100644 index 00000000..24f49e4a --- /dev/null +++ b/src/Ydb.Sdk/src/Ado/Session/PoolingSessionSource.cs @@ -0,0 +1,17 @@ +namespace Ydb.Sdk.Ado.Session; + +internal class PoolingSessionSource : ISessionSource +{ + public ValueTask OpenSession() => throw new NotImplementedException(); + + public void Return(IPoolingSession session) => throw new NotImplementedException(); +} + +internal interface IPoolingSession : ISession +{ + bool IsActive { get; } + + Task Open(CancellationToken cancellationToken); + + Task DeleteSession(); +} diff --git a/src/Ydb.Sdk/src/Ado/Session/Session.cs b/src/Ydb.Sdk/src/Ado/Session/Session.cs new file mode 100644 index 00000000..5a40c326 --- /dev/null +++ b/src/Ydb.Sdk/src/Ado/Session/Session.cs @@ -0,0 +1,240 @@ +using Microsoft.Extensions.Logging; +using Ydb.Query; +using Ydb.Query.V1; +using Ydb.Sdk.Ado.Internal; +using Ydb.Sdk.Value; +using CommitTransactionRequest = Ydb.Query.CommitTransactionRequest; +using TransactionControl = Ydb.Query.TransactionControl; + +namespace Ydb.Sdk.Ado.Session; + +internal class Session : IPoolingSession +{ + private const string SessionBalancer = "session-balancer"; + + private static readonly TimeSpan DeleteSessionTimeout = TimeSpan.FromSeconds(5); + private static readonly CreateSessionRequest CreateSessionRequest = new(); + + private readonly IDriver _driver; + private readonly PoolingSessionSource _poolingSessionSource; + private readonly YdbConnectionStringBuilder _settings; + private readonly ILogger _logger; + + private volatile bool _isActive; + + private string SessionId { get; set; } = string.Empty; + private long NodeId { get; set; } + + public bool IsActive => _isActive; + + internal Session( + IDriver driver, + PoolingSessionSource poolingSessionSource, + YdbConnectionStringBuilder settings, + ILogger logger + ) + { + _driver = driver; + _poolingSessionSource = poolingSessionSource; + _settings = settings; + _logger = logger; + } + + public ValueTask> ExecuteQuery( + string query, + Dictionary parameters, + GrpcRequestSettings settings, + TransactionControl? txControl + ) + { + settings.NodeId = NodeId; + + var request = new ExecuteQueryRequest + { + SessionId = SessionId, + ExecMode = ExecMode.Execute, + QueryContent = new QueryContent { Text = query, Syntax = Syntax.YqlV1 }, + StatsMode = StatsMode.None, + TxControl = txControl + }; + request.Parameters.Add(parameters.ToDictionary(p => p.Key, p => p.Value.GetProto())); + + return _driver.ServerStreamCall(QueryService.ExecuteQueryMethod, request, settings); + } + + public async Task CommitTransaction( + string txId, + CancellationToken cancellationToken = default + ) + { + var response = await _driver.UnaryCall( + QueryService.CommitTransactionMethod, + new CommitTransactionRequest { SessionId = SessionId, TxId = txId }, + new GrpcRequestSettings { CancellationToken = cancellationToken, NodeId = NodeId } + ); + + if (response.Status.IsNotSuccess()) + { + throw YdbException.FromServer(response.Status, response.Issues); + } + } + + public async Task RollbackTransaction( + string txId, + CancellationToken cancellationToken = default + ) + { + var response = await _driver.UnaryCall( + QueryService.RollbackTransactionMethod, + new RollbackTransactionRequest { SessionId = SessionId, TxId = txId }, + new GrpcRequestSettings { CancellationToken = cancellationToken, NodeId = NodeId } + ); + + if (response.Status.IsNotSuccess()) + { + throw YdbException.FromServer(response.Status, response.Issues); + } + } + + public void OnNotSuccessStatusCode(StatusCode code) + { + if (code is + StatusCode.Cancelled or + StatusCode.BadSession or + StatusCode.SessionBusy or + StatusCode.InternalError or + StatusCode.ClientTransportTimeout or + StatusCode.Unavailable or + StatusCode.ClientTransportUnavailable) + { + _logger.LogWarning("Session[{SessionId}] is deactivated. Reason StatusCode: {Code}", SessionId, code); + + _isActive = false; + } + } + + public async Task Open(CancellationToken cancellationToken) + { + var requestSettings = new GrpcRequestSettings { CancellationToken = cancellationToken }; + + if (!_settings.DisableServerBalancer) + { + requestSettings.ClientCapabilities.Add(SessionBalancer); + } + + var response = await _driver.UnaryCall(QueryService.CreateSessionMethod, CreateSessionRequest, requestSettings); + + if (response.Status.IsNotSuccess()) + { + throw YdbException.FromServer(response.Status, response.Issues); + } + + TaskCompletionSource completeTask = new(); + + SessionId = response.SessionId; + NodeId = response.NodeId; + + _ = Task.Run(async () => + { + try + { + using var stream = await _driver.ServerStreamCall( + QueryService.AttachSessionMethod, + new AttachSessionRequest { SessionId = SessionId }, + new GrpcRequestSettings { NodeId = NodeId } + ); + + if (!await stream.MoveNextAsync(cancellationToken)) + { + // Session wasn't started! + completeTask.SetException(new YdbException(StatusCode.Cancelled, "Attach stream is not started!")); + + return; + } + + var initSessionState = stream.Current; + + if (initSessionState.Status.IsNotSuccess()) + { + throw YdbException.FromServer(initSessionState.Status, initSessionState.Issues); + } + + completeTask.SetResult(); + + try + { + // ReSharper disable once MethodSupportsCancellation + while (await stream.MoveNextAsync()) + { + var sessionState = stream.Current; + + var statusCode = sessionState.Status.Code(); + + _logger.LogDebug( + "Session[{SessionId}] was received the status from the attach stream: {StatusMessage}", + SessionId, statusCode.ToMessage(sessionState.Issues)); + + OnNotSuccessStatusCode(statusCode); + + if (!IsActive) + { + return; + } + } + + _logger.LogDebug("Session[{SessionId}]: Attached stream is closed", SessionId); + + // attach stream is closed + } + catch (YdbException e) + { + if (e.Code == StatusCode.Cancelled) + { + _logger.LogDebug("AttachStream is cancelled (possible grpcChannel is closing)"); + + return; + } + + _logger.LogWarning(e, "Session[{SessionId}] is deactivated by transport error", SessionId); + } + } + catch (Exception e) + { + completeTask.SetException(e); + } + finally + { + _isActive = false; + } + }, cancellationToken); + + await completeTask.Task; + } + + public async Task DeleteSession() + { + try + { + _isActive = false; + + var deleteSessionResponse = await _driver.UnaryCall( + QueryService.DeleteSessionMethod, + new DeleteSessionRequest { SessionId = SessionId }, + new GrpcRequestSettings { TransportTimeout = DeleteSessionTimeout, NodeId = NodeId } + ); + + if (deleteSessionResponse.Status.IsNotSuccess()) + { + _logger.LogWarning("Failed to delete session[{SessionId}], {StatusMessage}", SessionId, + deleteSessionResponse.Status.Code().ToMessage(deleteSessionResponse.Issues)); + } + } + catch (Exception e) + { + _logger.LogWarning(e, "Error occurred while deleting session[{SessionId}] (NodeId = {NodeId})", + SessionId, NodeId); + } + } + + public void Close() => _poolingSessionSource.Return(this); +} diff --git a/src/Ydb.Sdk/src/Client/Response.cs b/src/Ydb.Sdk/src/Client/Response.cs index 22051085..b34a2720 100644 --- a/src/Ydb.Sdk/src/Client/Response.cs +++ b/src/Ydb.Sdk/src/Client/Response.cs @@ -61,11 +61,11 @@ public abstract class StreamResponse where TProtoResponse : class where TResponse : class { - private readonly ServerStream _iterator; + private readonly IServerStream _iterator; private TResponse? _response; private bool _transportError; - internal StreamResponse(ServerStream iterator) + internal StreamResponse(IServerStream iterator) { _iterator = iterator; } diff --git a/src/Ydb.Sdk/src/Driver.cs b/src/Ydb.Sdk/src/Driver.cs index 84e580ca..de842a76 100644 --- a/src/Ydb.Sdk/src/Driver.cs +++ b/src/Ydb.Sdk/src/Driver.cs @@ -5,7 +5,8 @@ using Ydb.Discovery; using Ydb.Discovery.V1; using Ydb.Sdk.Ado; -using Ydb.Sdk.Ado.Pool; +using Ydb.Sdk.Ado.Internal; +using Ydb.Sdk.Pool; namespace Ydb.Sdk; diff --git a/src/Ydb.Sdk/src/IDriver.cs b/src/Ydb.Sdk/src/IDriver.cs index 195f8e8b..9599284f 100644 --- a/src/Ydb.Sdk/src/IDriver.cs +++ b/src/Ydb.Sdk/src/IDriver.cs @@ -1,9 +1,9 @@ using Grpc.Core; using Grpc.Net.Client; using Microsoft.Extensions.Logging; -using Ydb.Sdk.Ado.Pool; using Ydb.Sdk.Ado; using Ydb.Sdk.Auth; +using Ydb.Sdk.Pool; using Ydb.Sdk.Services.Auth; namespace Ydb.Sdk; @@ -17,7 +17,7 @@ public Task UnaryCall( where TRequest : class where TResponse : class; - public ValueTask> ServerStreamCall( + public ValueTask> ServerStreamCall( Method method, TRequest request, GrpcRequestSettings settings) @@ -126,7 +126,7 @@ public async Task UnaryCall( } } - public async ValueTask> ServerStreamCall( + public async ValueTask> ServerStreamCall( Method method, TRequest request, GrpcRequestSettings settings) diff --git a/src/Ydb.Sdk/src/Ado/Pool/ChannelPool.cs b/src/Ydb.Sdk/src/Pool/ChannelPool.cs similarity index 99% rename from src/Ydb.Sdk/src/Ado/Pool/ChannelPool.cs rename to src/Ydb.Sdk/src/Pool/ChannelPool.cs index 34c27608..84a3c50d 100644 --- a/src/Ydb.Sdk/src/Ado/Pool/ChannelPool.cs +++ b/src/Ydb.Sdk/src/Pool/ChannelPool.cs @@ -6,7 +6,7 @@ using Grpc.Net.Client; using Microsoft.Extensions.Logging; -namespace Ydb.Sdk.Ado.Pool; +namespace Ydb.Sdk.Pool; internal class ChannelPool : IAsyncDisposable where T : ChannelBase, IDisposable { diff --git a/src/Ydb.Sdk/src/Ado/Pool/EndpointPool.cs b/src/Ydb.Sdk/src/Pool/EndpointPool.cs similarity index 99% rename from src/Ydb.Sdk/src/Ado/Pool/EndpointPool.cs rename to src/Ydb.Sdk/src/Pool/EndpointPool.cs index 61a5eff7..feff114e 100644 --- a/src/Ydb.Sdk/src/Ado/Pool/EndpointPool.cs +++ b/src/Ydb.Sdk/src/Pool/EndpointPool.cs @@ -1,7 +1,7 @@ using System.Collections.Immutable; using Microsoft.Extensions.Logging; -namespace Ydb.Sdk.Ado.Pool; +namespace Ydb.Sdk.Pool; internal class EndpointPool { diff --git a/src/Ydb.Sdk/src/Services/Auth/StaticCredentialsAuthClient.cs b/src/Ydb.Sdk/src/Services/Auth/StaticCredentialsAuthClient.cs index 7f1c69d2..e93fa298 100644 --- a/src/Ydb.Sdk/src/Services/Auth/StaticCredentialsAuthClient.cs +++ b/src/Ydb.Sdk/src/Services/Auth/StaticCredentialsAuthClient.cs @@ -3,10 +3,10 @@ using Microsoft.Extensions.Logging; using Ydb.Auth; using Ydb.Auth.V1; -using Ydb.Sdk.Ado.Pool; using Ydb.Sdk.Ado; using Ydb.Sdk.Ado.Internal; using Ydb.Sdk.Auth; +using Ydb.Sdk.Pool; namespace Ydb.Sdk.Services.Auth; diff --git a/src/Ydb.Sdk/src/Services/Query/SessionPool.cs b/src/Ydb.Sdk/src/Services/Query/SessionPool.cs index 15ff1060..1fe7959c 100644 --- a/src/Ydb.Sdk/src/Services/Query/SessionPool.cs +++ b/src/Ydb.Sdk/src/Services/Query/SessionPool.cs @@ -54,7 +54,7 @@ protected override async Task CreateSession( Status.FromProto(response.Status, response.Issues).EnsureSuccess(); - TaskCompletionSource completeTask = new(); + TaskCompletionSource completeTask = new(); var sessionId = response.SessionId; var nodeId = response.NodeId; @@ -74,12 +74,17 @@ protected override async Task CreateSession( if (!await stream.MoveNextAsync(cancellationToken)) { // Session wasn't started! - completeTask.SetResult(new Status(StatusCode.Cancelled, "Attach stream is not started!")); + completeTask.SetException(new YdbException(StatusCode.Cancelled, "Attach stream is not started!")); return; } - completeTask.SetResult(Status.FromProto(stream.Current.Status, stream.Current.Issues)); + if (stream.Current.Status.IsNotSuccess()) + { + completeTask.SetException(YdbException.FromServer(stream.Current.Status, stream.Current.Issues)); + } + + completeTask.SetResult(); try { @@ -128,7 +133,7 @@ protected override async Task CreateSession( } }, cancellationToken); - (await completeTask.Task).EnsureSuccess(); + await completeTask.Task; return session; } @@ -151,7 +156,7 @@ ILogger logger Driver = driver; } - internal ValueTask> ExecuteQuery( + internal ValueTask> ExecuteQuery( string query, Dictionary? parameters, ExecuteQuerySettings? settings, diff --git a/src/Ydb.Sdk/src/Services/Query/Settings.cs b/src/Ydb.Sdk/src/Services/Query/Settings.cs index 4938558d..fc9cf6ae 100644 --- a/src/Ydb.Sdk/src/Services/Query/Settings.cs +++ b/src/Ydb.Sdk/src/Services/Query/Settings.cs @@ -86,10 +86,10 @@ internal ExecuteQueryPart(ExecuteQueryResponsePart part) public sealed class ExecuteQueryStream : IAsyncEnumerator, IAsyncEnumerable { - private readonly ServerStream _stream; + private readonly IServerStream _stream; private readonly Action _onTxId; - internal ExecuteQueryStream(ServerStream stream, Action? onTx = null) + internal ExecuteQueryStream(IServerStream stream, Action? onTx = null) { _stream = stream; _onTxId = onTx ?? (_ => { }); diff --git a/src/Ydb.Sdk/src/Services/Table/ExecuteScanQuery.cs b/src/Ydb.Sdk/src/Services/Table/ExecuteScanQuery.cs index a9ce437a..045c5b64 100644 --- a/src/Ydb.Sdk/src/Services/Table/ExecuteScanQuery.cs +++ b/src/Ydb.Sdk/src/Services/Table/ExecuteScanQuery.cs @@ -32,7 +32,7 @@ internal static ResultData FromProto(ExecuteScanQueryPartialResult resultProto) public class ExecuteScanQueryStream : StreamResponse { - internal ExecuteScanQueryStream(ServerStream iterator) + internal ExecuteScanQueryStream(IServerStream iterator) : base(iterator) { } diff --git a/src/Ydb.Sdk/src/Services/Table/ReadTable.cs b/src/Ydb.Sdk/src/Services/Table/ReadTable.cs index 39a58f29..a704aff1 100644 --- a/src/Ydb.Sdk/src/Services/Table/ReadTable.cs +++ b/src/Ydb.Sdk/src/Services/Table/ReadTable.cs @@ -36,7 +36,7 @@ internal ResultData(Value.ResultSet resultSet) public class ReadTableStream : StreamResponse { - internal ReadTableStream(ServerStream iterator) + internal ReadTableStream(IServerStream iterator) : base(iterator) { } diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Pool/ChannelPoolTests.cs b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Pool/ChannelPoolTests.cs index 836543ad..ecf034b6 100644 --- a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Pool/ChannelPoolTests.cs +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Pool/ChannelPoolTests.cs @@ -3,8 +3,8 @@ using Grpc.Core; using Moq; using Xunit; -using Ydb.Sdk.Ado.Pool; using Ydb.Sdk.Ado.Tests.Utils; +using Ydb.Sdk.Pool; namespace Ydb.Sdk.Ado.Tests.Pool; diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Pool/EndpointPoolTests.cs b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Pool/EndpointPoolTests.cs index 80c861aa..000c13e4 100644 --- a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Pool/EndpointPoolTests.cs +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Pool/EndpointPoolTests.cs @@ -1,20 +1,21 @@ using System.Collections.Immutable; using Moq; using Xunit; -using Ydb.Sdk.Ado.Pool; using Ydb.Sdk.Ado.Tests.Utils; +using Ydb.Sdk.Pool; namespace Ydb.Sdk.Ado.Tests.Pool; public class EndpointPoolTests { - private static readonly ImmutableArray EndpointSettingsList = ImmutableArray.Create( + private static readonly ImmutableArray EndpointSettingsList = + [ new EndpointSettings(1, "n1.ydb.tech", "MAN"), new EndpointSettings(2, "n2.ydb.tech", "VLA"), new EndpointSettings(3, "n3.ydb.tech", "SAS"), new EndpointSettings(4, "n4.ydb.tech", "SAS"), new EndpointSettings(5, "n5.ydb.tech", "VLA") - ); + ]; public class MockRandomUnitTests { From a88d072cefff5139f4ed507af4faec22d2337d17 Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Tue, 15 Jul 2025 15:35:52 +0300 Subject: [PATCH 3/7] revert YdbConnectionStringBuilder.cs --- .../src/Ado/YdbConnectionStringBuilder.cs | 37 ------------------- src/Ydb.Sdk/src/Services/Query/SessionPool.cs | 4 +- .../Pool/EndpointPoolTests.cs | 10 ++--- .../YdbConnectionStringBuilderTests.cs | 4 -- .../test/Ydb.Sdk.Ado.Tests/YdbSchemaTests.cs | 34 ++++++++--------- 5 files changed, 24 insertions(+), 65 deletions(-) diff --git a/src/Ydb.Sdk/src/Ado/YdbConnectionStringBuilder.cs b/src/Ydb.Sdk/src/Ado/YdbConnectionStringBuilder.cs index ce908ba4..669c2bf1 100644 --- a/src/Ydb.Sdk/src/Ado/YdbConnectionStringBuilder.cs +++ b/src/Ydb.Sdk/src/Ado/YdbConnectionStringBuilder.cs @@ -3,7 +3,6 @@ using System.Security.Cryptography.X509Certificates; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Ydb.Sdk.Ado.Internal; using Ydb.Sdk.Auth; using Ydb.Sdk.Pool; using Ydb.Sdk.Transport; @@ -30,8 +29,6 @@ private void InitDefaultValues() _port = YdbAdoDefaultSettings.Port; _database = YdbAdoDefaultSettings.Database; _maxSessionPool = SessionPoolDefaultSettings.MaxSessionPool; - _minSessionPool = 0; - _sessionIdleTimeout = 300; _useTls = YdbAdoDefaultSettings.UseTls; _connectTimeout = GrpcDefaultSettings.ConnectTimeoutSeconds; _keepAlivePingDelay = GrpcDefaultSettings.KeepAlivePingSeconds; @@ -126,40 +123,6 @@ public int MaxSessionPool private int _maxSessionPool; - public int MinSessionPool - { - get => _minSessionPool; - set - { - if (value <= 0) - { - throw new ArgumentOutOfRangeException(nameof(value), value, "Invalid min session pool: " + value); - } - - _minSessionPool = value; - SaveValue(nameof(MinSessionPool), value); - } - } - - private int _minSessionPool; - - public int SessionIdleTimeout - { - get => _sessionIdleTimeout; - set - { - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value), value, "Invalid session idle timeout: " + value); - } - - _sessionIdleTimeout = value; - SaveValue(nameof(SessionIdleTimeout), value); - } - } - - private int _sessionIdleTimeout; - public bool UseTls { get => _useTls; diff --git a/src/Ydb.Sdk/src/Services/Query/SessionPool.cs b/src/Ydb.Sdk/src/Services/Query/SessionPool.cs index 1fe7959c..2c496b06 100644 --- a/src/Ydb.Sdk/src/Services/Query/SessionPool.cs +++ b/src/Ydb.Sdk/src/Services/Query/SessionPool.cs @@ -81,9 +81,9 @@ protected override async Task CreateSession( if (stream.Current.Status.IsNotSuccess()) { - completeTask.SetException(YdbException.FromServer(stream.Current.Status, stream.Current.Issues)); + completeTask.SetException(YdbException.FromServer(stream.Current.Status, stream.Current.Issues)); } - + completeTask.SetResult(); try diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Pool/EndpointPoolTests.cs b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Pool/EndpointPoolTests.cs index 000c13e4..9840b761 100644 --- a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Pool/EndpointPoolTests.cs +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Pool/EndpointPoolTests.cs @@ -10,11 +10,11 @@ public class EndpointPoolTests { private static readonly ImmutableArray EndpointSettingsList = [ - new EndpointSettings(1, "n1.ydb.tech", "MAN"), - new EndpointSettings(2, "n2.ydb.tech", "VLA"), - new EndpointSettings(3, "n3.ydb.tech", "SAS"), - new EndpointSettings(4, "n4.ydb.tech", "SAS"), - new EndpointSettings(5, "n5.ydb.tech", "VLA") + new(1, "n1.ydb.tech", "MAN"), + new(2, "n2.ydb.tech", "VLA"), + new(3, "n3.ydb.tech", "SAS"), + new(4, "n4.ydb.tech", "SAS"), + new(5, "n5.ydb.tech", "VLA") ]; public class MockRandomUnitTests diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionStringBuilderTests.cs b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionStringBuilderTests.cs index 8911ca8f..5e63d6aa 100644 --- a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionStringBuilderTests.cs +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionStringBuilderTests.cs @@ -13,7 +13,6 @@ public void InitDefaultValues_WhenEmptyConstructorInvoke_ReturnDefaultConnection Assert.Equal("localhost", ydbConnectionStringBuilder.Host); Assert.Equal("/local", ydbConnectionStringBuilder.Database); Assert.Equal(100, ydbConnectionStringBuilder.MaxSessionPool); - Assert.Equal(0, ydbConnectionStringBuilder.MinSessionPool); Assert.Null(ydbConnectionStringBuilder.User); Assert.Null(ydbConnectionStringBuilder.Password); Assert.Equal(5, ydbConnectionStringBuilder.ConnectTimeout); @@ -26,7 +25,6 @@ public void InitDefaultValues_WhenEmptyConstructorInvoke_ReturnDefaultConnection Assert.False(ydbConnectionStringBuilder.DisableDiscovery); Assert.False(ydbConnectionStringBuilder.DisableServerBalancer); Assert.Equal(5, ydbConnectionStringBuilder.CreateSessionTimeout); - Assert.Equal(300, ydbConnectionStringBuilder.SessionIdleTimeout); Assert.False(ydbConnectionStringBuilder.UseTls); } @@ -73,8 +71,6 @@ public void InitConnectionStringBuilder_WhenExpectedKeys_ReturnUpdatedConnection Assert.True(connectionString.DisableDiscovery); Assert.True(connectionString.DisableServerBalancer); Assert.Equal(30, connectionString.CreateSessionTimeout); - Assert.Equal(240, connectionString.SessionIdleTimeout); - Assert.Equal(10, connectionString.MinSessionPool); Assert.Equal(50, connectionString.MaxSessionPool); } diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbSchemaTests.cs b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbSchemaTests.cs index 49357e42..3cf9852b 100644 --- a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbSchemaTests.cs +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbSchemaTests.cs @@ -186,23 +186,23 @@ public async Task GetSchema_WhenAllTypesTable_ReturnAllTypes() void CheckAllColumns(DataTable pDataTable, bool isNullableTable) { - CheckColumn(pDataTable.Rows[0], "TimestampColumn", 0, isNullableTable); - CheckColumn(pDataTable.Rows[1], "Int32Column", 1, isNullableTable); - CheckColumn(pDataTable.Rows[2], "BoolColumn", 2, isNullableTable); - CheckColumn(pDataTable.Rows[3], "Int64Column", 3, isNullableTable); - CheckColumn(pDataTable.Rows[4], "Int16Column", 4, isNullableTable); - CheckColumn(pDataTable.Rows[5], "Int8Column", 5, isNullableTable); - CheckColumn(pDataTable.Rows[6], "FloatColumn", 6, isNullableTable); - CheckColumn(pDataTable.Rows[7], "DoubleColumn", 7, isNullableTable); - CheckColumn(pDataTable.Rows[8], "DefaultDecimalColumn", 8, isNullableTable, "Decimal(22, 9)"); - CheckColumn(pDataTable.Rows[9], "Uint8Column", 9, isNullableTable); - CheckColumn(pDataTable.Rows[10], "Uint16Column", 10, isNullableTable); - CheckColumn(pDataTable.Rows[11], "Uint32Column", 11, isNullableTable); - CheckColumn(pDataTable.Rows[12], "Uint64Column", 12, isNullableTable); - CheckColumn(pDataTable.Rows[13], "TextColumn", 13, isNullableTable); - CheckColumn(pDataTable.Rows[14], "BytesColumn", 14, isNullableTable); - CheckColumn(pDataTable.Rows[15], "DateColumn", 15, isNullableTable); - CheckColumn(pDataTable.Rows[16], "DatetimeColumn", 16, isNullableTable); + CheckColumn(pDataTable.Rows[0], "Int32Column", 0, isNullableTable); + CheckColumn(pDataTable.Rows[1], "BoolColumn", 1, isNullableTable); + CheckColumn(pDataTable.Rows[2], "Int64Column", 2, isNullableTable); + CheckColumn(pDataTable.Rows[3], "Int16Column", 3, isNullableTable); + CheckColumn(pDataTable.Rows[4], "Int8Column", 4, isNullableTable); + CheckColumn(pDataTable.Rows[5], "FloatColumn", 5, isNullableTable); + CheckColumn(pDataTable.Rows[6], "DoubleColumn", 6, isNullableTable); + CheckColumn(pDataTable.Rows[7], "DefaultDecimalColumn", 7, isNullableTable, "Decimal(22, 9)"); + CheckColumn(pDataTable.Rows[8], "Uint8Column", 8, isNullableTable); + CheckColumn(pDataTable.Rows[9], "Uint16Column", 9, isNullableTable); + CheckColumn(pDataTable.Rows[10], "Uint32Column", 10, isNullableTable); + CheckColumn(pDataTable.Rows[11], "Uint64Column", 11, isNullableTable); + CheckColumn(pDataTable.Rows[12], "TextColumn", 12, isNullableTable); + CheckColumn(pDataTable.Rows[13], "BytesColumn", 13, isNullableTable); + CheckColumn(pDataTable.Rows[14], "DateColumn", 14, isNullableTable); + CheckColumn(pDataTable.Rows[15], "DatetimeColumn", 15, isNullableTable); + CheckColumn(pDataTable.Rows[16], "TimestampColumn", 16, isNullableTable); } void CheckColumn(DataRow column, string columnName, int ordinal, bool isNullable, string? dataType = null) From 070f2d97aefdb1780b0b6da4ea3cf5b093e5b38e Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Tue, 15 Jul 2025 15:38:23 +0300 Subject: [PATCH 4/7] fix test --- .../test/Ydb.Sdk.Ado.Tests/YdbConnectionStringBuilderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionStringBuilderTests.cs b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionStringBuilderTests.cs index 5e63d6aa..390c1a6f 100644 --- a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionStringBuilderTests.cs +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionStringBuilderTests.cs @@ -47,7 +47,7 @@ public void InitConnectionStringBuilder_WhenExpectedKeys_ReturnUpdatedConnection "EnableMultipleHttp2Connections=true;CreateSessionTimeout=30;" + "MaxSendMessageSize=1000000;MaxReceiveMessageSize=1000000;" + "DisableDiscovery=true;DisableServerBalancer=true;" + - "SessionIdleTimeout=240;MinSessionPool=10;MaxSessionPool=50" + "MaxSessionPool=50" ); Assert.Equal(2135, connectionString.Port); From 046b387a93762ea17022083cfbbcdea62d1cf29f Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Tue, 15 Jul 2025 15:41:47 +0300 Subject: [PATCH 5/7] fix test --- .../Ydb.Sdk.Ado.Tests/YdbConnectionStringBuilderTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionStringBuilderTests.cs b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionStringBuilderTests.cs index 390c1a6f..c892c896 100644 --- a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionStringBuilderTests.cs +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionStringBuilderTests.cs @@ -53,7 +53,7 @@ public void InitConnectionStringBuilder_WhenExpectedKeys_ReturnUpdatedConnection Assert.Equal(2135, connectionString.Port); Assert.Equal("server", connectionString.Host); Assert.Equal("/my/path", connectionString.Database); - Assert.Equal(100, connectionString.MaxSessionPool); + Assert.Equal(50, connectionString.MaxSessionPool); Assert.Equal("Kirill", connectionString.User); Assert.Equal(30, connectionString.ConnectTimeout); Assert.Equal(30, connectionString.KeepAlivePingDelay); @@ -66,8 +66,8 @@ public void InitConnectionStringBuilder_WhenExpectedKeys_ReturnUpdatedConnection "ConnectTimeout=30;KeepAlivePingDelay=30;KeepAlivePingTimeout=60;" + "EnableMultipleHttp2Connections=True;CreateSessionTimeout=30;" + "MaxSendMessageSize=1000000;MaxReceiveMessageSize=1000000;" + - "DisableDiscovery=True;DisableServerBalancer=True" + - "SessionIdleTimeout=240;MinSessionPool=10;MaxSessionPool=50", connectionString.ConnectionString); + "DisableDiscovery=True;DisableServerBalancer=True;" + + "MaxSessionPool=50", connectionString.ConnectionString); Assert.True(connectionString.DisableDiscovery); Assert.True(connectionString.DisableServerBalancer); Assert.Equal(30, connectionString.CreateSessionTimeout); From d9a669423c3a13481809fc129044c9975402795b Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Tue, 15 Jul 2025 15:47:57 +0300 Subject: [PATCH 6/7] fix YdbMigrationsTest.cs --- .../Migrations/YdbMigrationsTest.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Migrations/YdbMigrationsTest.cs b/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Migrations/YdbMigrationsTest.cs index 29a5e315..e850b0a8 100644 --- a/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Migrations/YdbMigrationsTest.cs +++ b/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Migrations/YdbMigrationsTest.cs @@ -392,11 +392,6 @@ await Test(_ => { }, builder => }, model => Assert.Collection( Assert.Single(model.Tables, t => t.Name == "Contacts").Columns, // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local - c => - { - Assert.Equal("MyComplex_MyNestedComplex_Foo", c.Name); - Assert.True(c.IsNullable); - }, c => Assert.Equal("Id", c.Name), c => Assert.Equal("Discriminator", c.Name), c => Assert.Equal("Name", c.Name), @@ -412,6 +407,11 @@ await Test(_ => { }, builder => { Assert.Equal("MyComplex_MyNestedComplex_Bar", c.Name); Assert.True(c.IsNullable); + }, + c => + { + Assert.Equal("MyComplex_MyNestedComplex_Foo", c.Name); + Assert.True(c.IsNullable); })); AssertSql( From 68bce96fd0428d02e1a0cb5f43a7623565475b27 Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Tue, 15 Jul 2025 16:06:43 +0300 Subject: [PATCH 7/7] added results benchmarks --- src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/README.md b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/README.md index 4763ac7f..558eaac2 100644 --- a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/README.md +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/README.md @@ -1 +1,8 @@ -# YDB .NET SDK Session Pool Benchmarks \ No newline at end of file +# YDB .NET SDK Session Pool Benchmarks + +| Method | Mean | Error | StdDev | Completed Work Items | Lock Contentions | Gen0 | Allocated | +|--------------------------|-------------:|------------:|------------:|---------------------:|-----------------:|-------:|----------:| +| SingleThreaded_OpenClose | 130.2 ns | 0.91 ns | 0.71 ns | 0.0000 | - | 0.0257 | 216 B | +| MultiThreaded_OpenClose | 41,667.8 ns | 1,065.07 ns | 3,140.37 ns | 20.0018 | 0.3466 | 1.0376 | 8851 B | +| HighContention_OpenClose | 130,331.1 ns | 2,569.39 ns | 6,106.44 ns | 100.0000 | 1.9094 | 5.1270 | 43421 B | +| SessionReuse_Pattern | 204,351.2 ns | 4,038.25 ns | 7,485.16 ns | 20.0000 | 3.6716 | 5.6152 | 47762 B | \ No newline at end of file