22
22
using Akka . Event ;
23
23
using Akka . Pattern ;
24
24
using Akka . Persistence . Journal ;
25
+ using Akka . Persistence . Sql . Common . Extensions ;
25
26
using Akka . Persistence . Sql . Common . Journal ;
26
27
using Akka . Serialization ;
27
28
using Akka . Util ;
@@ -210,9 +211,10 @@ public abstract class BatchingSqlJournalSetup
210
211
public TimeSpan ConnectionTimeout { get ; }
211
212
212
213
/// <summary>
213
- /// Isolation level of transactions used during query execution.
214
+ /// Isolation level of transactions used during write query execution.
214
215
/// </summary>
215
- public IsolationLevel IsolationLevel { get ; }
216
+ [ Obsolete ( "Use WriteIsolationLevel property instead" ) ]
217
+ public IsolationLevel IsolationLevel => WriteIsolationLevel ;
216
218
217
219
/// <summary>
218
220
/// Settings specific to <see cref="CircuitBreaker"/>, which is used internally
@@ -241,6 +243,16 @@ public abstract class BatchingSqlJournalSetup
241
243
/// The fully qualified name of the type that should be used as timestamp provider.
242
244
/// </summary>
243
245
public string TimestampProviderTypeName { get ; }
246
+
247
+ /// <summary>
248
+ /// Isolation level of transactions used during read query execution.
249
+ /// </summary>
250
+ public IsolationLevel ReadIsolationLevel { get ; }
251
+
252
+ /// <summary>
253
+ /// Isolation level of transactions used during write query execution.
254
+ /// </summary>
255
+ public IsolationLevel WriteIsolationLevel { get ; }
244
256
245
257
/// <summary>
246
258
/// Initializes a new instance of the <see cref="BatchingSqlJournalSetup" /> class.
@@ -276,30 +288,26 @@ protected BatchingSqlJournalSetup(Config config, QueryConfiguration namingConven
276
288
if ( string . IsNullOrWhiteSpace ( connectionString ) )
277
289
throw new ConfigurationException ( "No connection string for Sql Event Journal was specified" ) ;
278
290
279
- IsolationLevel level ;
280
- switch ( config . GetString ( "isolation-level" , "unspecified" ) )
281
- {
282
- case "chaos" : level = IsolationLevel . Chaos ; break ;
283
- case "read-committed" : level = IsolationLevel . ReadCommitted ; break ;
284
- case "read-uncommitted" : level = IsolationLevel . ReadUncommitted ; break ;
285
- case "repeatable-read" : level = IsolationLevel . RepeatableRead ; break ;
286
- case "serializable" : level = IsolationLevel . Serializable ; break ;
287
- case "snapshot" : level = IsolationLevel . Snapshot ; break ;
288
- case "unspecified" : level = IsolationLevel . Unspecified ; break ;
289
- default : throw new ConfigurationException ( "Unknown isolation-level value. Should be one of: chaos | read-committed | read-uncommitted | repeatable-read | serializable | snapshot | unspecified" ) ;
290
- }
291
+ ReadIsolationLevel = namingConventions . ReadIsolationLevel ;
292
+ WriteIsolationLevel = namingConventions . WriteIsolationLevel ;
293
+
294
+ // backward compatibility
295
+ var level = config . GetString ( "isolation-level" ) ;
296
+ if ( level is { } )
297
+ WriteIsolationLevel = level . ToIsolationLevel ( ) ;
291
298
292
299
ConnectionString = connectionString ;
293
300
MaxConcurrentOperations = config . GetInt ( "max-concurrent-operations" , 64 ) ;
294
301
MaxBatchSize = config . GetInt ( "max-batch-size" , 100 ) ;
295
302
MaxBufferSize = config . GetInt ( "max-buffer-size" , 500000 ) ;
296
303
AutoInitialize = config . GetBoolean ( "auto-initialize" , false ) ;
297
304
ConnectionTimeout = config . GetTimeSpan ( "connection-timeout" , TimeSpan . FromSeconds ( 30 ) ) ;
298
- IsolationLevel = level ;
299
305
CircuitBreakerSettings = new CircuitBreakerSettings ( config . GetConfig ( "circuit-breaker" ) ) ;
300
306
ReplayFilterSettings = new ReplayFilterSettings ( config . GetConfig ( "replay-filter" ) ) ;
301
307
NamingConventions = namingConventions ;
308
+ #pragma warning disable CS0618
302
309
DefaultSerializer = config . GetString ( "serializer" , null ) ;
310
+ #pragma warning restore CS0618
303
311
TimestampProviderTypeName = config . GetString ( "timestamp-provider" , null ) ;
304
312
}
305
313
@@ -323,19 +331,84 @@ protected BatchingSqlJournalSetup(Config config, QueryConfiguration namingConven
323
331
/// <param name="replayFilterSettings">The settings used when replaying events from database back to the persistent actors.</param>
324
332
/// <param name="namingConventions">The naming conventions used by the database to construct valid SQL statements.</param>
325
333
/// <param name="defaultSerializer">The serializer used when no specific type matching can be found.</param>
326
- protected BatchingSqlJournalSetup ( string connectionString , int maxConcurrentOperations , int maxBatchSize , int maxBufferSize , bool autoInitialize , TimeSpan connectionTimeout , IsolationLevel isolationLevel , CircuitBreakerSettings circuitBreakerSettings , ReplayFilterSettings replayFilterSettings , QueryConfiguration namingConventions , string defaultSerializer )
334
+ [ Obsolete ( "Use constructor with separate read and write isolation level instead. (since v1.5.2)" ) ]
335
+ protected BatchingSqlJournalSetup (
336
+ string connectionString ,
337
+ int maxConcurrentOperations ,
338
+ int maxBatchSize ,
339
+ int maxBufferSize ,
340
+ bool autoInitialize ,
341
+ TimeSpan connectionTimeout ,
342
+ IsolationLevel isolationLevel ,
343
+ CircuitBreakerSettings circuitBreakerSettings ,
344
+ ReplayFilterSettings replayFilterSettings ,
345
+ QueryConfiguration namingConventions ,
346
+ string defaultSerializer )
347
+ : this (
348
+ connectionString : connectionString ,
349
+ maxConcurrentOperations : maxConcurrentOperations ,
350
+ maxBatchSize : maxBatchSize ,
351
+ maxBufferSize : maxBufferSize ,
352
+ autoInitialize : autoInitialize ,
353
+ connectionTimeout : connectionTimeout ,
354
+ writeIsolationLevel : isolationLevel ,
355
+ readIsolationLevel : isolationLevel ,
356
+ circuitBreakerSettings : circuitBreakerSettings ,
357
+ replayFilterSettings : replayFilterSettings ,
358
+ namingConventions : namingConventions ,
359
+ defaultSerializer : defaultSerializer )
360
+ { }
361
+
362
+ /// <summary>
363
+ /// Initializes a new instance of the <see cref="BatchingSqlJournalSetup" /> class.
364
+ /// </summary>
365
+ /// <param name="connectionString">The connection string used to connect to the database.</param>
366
+ /// <param name="maxConcurrentOperations">The maximum number of batch operations allowed to be executed at the same time.</param>
367
+ /// <param name="maxBatchSize">The maximum size of single batch of operations to be executed over a single <see cref="DbConnection"/>.</param>
368
+ /// <param name="maxBufferSize">The maximum size of requests stored in journal buffer.</param>
369
+ /// <param name="autoInitialize">
370
+ /// If set to <c>true</c>, the journal executes all SQL scripts stored under the
371
+ /// <see cref="BatchingSqlJournal{TConnection,TCommand}.Initializers"/> collection prior
372
+ /// to starting executing any requests.
373
+ /// </param>
374
+ /// <param name="connectionTimeout">The maximum time given for executed <see cref="DbCommand"/> to complete.</param>
375
+ /// <param name="readIsolationLevel">The isolation level of transactions used during read query execution.</param>
376
+ /// <param name="writeIsolationLevel">The isolation level of transactions used during write query execution.</param>
377
+ /// <param name="circuitBreakerSettings">
378
+ /// The settings used by the <see cref="CircuitBreaker"/> when for executing request batches.
379
+ /// </param>
380
+ /// <param name="replayFilterSettings">The settings used when replaying events from database back to the persistent actors.</param>
381
+ /// <param name="namingConventions">The naming conventions used by the database to construct valid SQL statements.</param>
382
+ /// <param name="defaultSerializer">The serializer used when no specific type matching can be found.</param>
383
+ protected BatchingSqlJournalSetup (
384
+ string connectionString ,
385
+ int maxConcurrentOperations ,
386
+ int maxBatchSize ,
387
+ int maxBufferSize ,
388
+ bool autoInitialize ,
389
+ TimeSpan connectionTimeout ,
390
+ IsolationLevel readIsolationLevel ,
391
+ IsolationLevel writeIsolationLevel ,
392
+ CircuitBreakerSettings circuitBreakerSettings ,
393
+ ReplayFilterSettings replayFilterSettings ,
394
+ QueryConfiguration namingConventions ,
395
+ string defaultSerializer )
327
396
{
328
397
ConnectionString = connectionString ;
329
398
MaxConcurrentOperations = maxConcurrentOperations ;
330
399
MaxBatchSize = maxBatchSize ;
331
400
MaxBufferSize = maxBufferSize ;
332
401
AutoInitialize = autoInitialize ;
333
402
ConnectionTimeout = connectionTimeout ;
334
- IsolationLevel = isolationLevel ;
403
+ WriteIsolationLevel = writeIsolationLevel ;
404
+ ReadIsolationLevel = readIsolationLevel ;
405
+ ReadIsolationLevel = writeIsolationLevel ;
335
406
CircuitBreakerSettings = circuitBreakerSettings ;
336
407
ReplayFilterSettings = replayFilterSettings ;
337
408
NamingConventions = namingConventions ;
409
+ #pragma warning disable CS0618
338
410
DefaultSerializer = defaultSerializer ;
411
+ #pragma warning restore CS0618
339
412
}
340
413
}
341
414
@@ -547,6 +620,8 @@ public RequestChunk(int chunkId, IJournalRequest[] requests)
547
620
548
621
private readonly Akka . Serialization . Serialization _serialization ;
549
622
private readonly CircuitBreaker _circuitBreaker ;
623
+ private readonly IsolationLevel _writeIsolationLevel ;
624
+ private readonly IsolationLevel _readIsolationLevel ;
550
625
private int _remainingOperations ;
551
626
552
627
/// <summary>
@@ -574,6 +649,9 @@ protected BatchingSqlJournal(BatchingSqlJournalSetup setup)
574
649
maxFailures : Setup . CircuitBreakerSettings . MaxFailures ,
575
650
callTimeout : Setup . CircuitBreakerSettings . CallTimeout ,
576
651
resetTimeout : Setup . CircuitBreakerSettings . ResetTimeout ) ;
652
+
653
+ _writeIsolationLevel = Setup . WriteIsolationLevel ;
654
+ _readIsolationLevel = Setup . ReadIsolationLevel ;
577
655
578
656
var conventions = Setup . NamingConventions ;
579
657
@@ -862,25 +940,24 @@ private void TryProcess()
862
940
{
863
941
_remainingOperations -- ;
864
942
865
- var chunk = DequeueChunk ( _remainingOperations ) ;
943
+ var ( chunk , isWrite ) = DequeueChunk ( _remainingOperations ) ;
866
944
var context = Context ;
867
- _circuitBreaker . WithCircuitBreaker ( ( ) => ExecuteChunk ( chunk , context ) )
945
+ _circuitBreaker . WithCircuitBreaker ( ( ) => ExecuteChunk ( chunk , context , isWrite ) )
868
946
. PipeTo ( Self , failure : ex => new ChunkExecutionFailure ( ex , chunk . Requests , chunk . ChunkId ) ) ;
869
947
}
870
948
}
871
949
872
- private async Task < BatchComplete > ExecuteChunk ( RequestChunk chunk , IActorContext context )
950
+ private async Task < BatchComplete > ExecuteChunk ( RequestChunk chunk , IActorContext context , bool isWriteOperation )
873
951
{
874
952
var writeResults = new Queue < WriteMessagesResult > ( ) ;
875
- var isWriteOperation = false ;
876
953
var stopwatch = new Stopwatch ( ) ;
877
954
using ( var connection = CreateConnection ( Setup . ConnectionString ) )
878
955
{
879
956
await connection . OpenAsync ( ) ;
880
957
881
958
// In the grand scheme of thing, using a transaction in an all read batch operation
882
959
// should not hurt performance by much, because it is done only once at the start.
883
- using ( var tx = connection . BeginTransaction ( Setup . IsolationLevel ) )
960
+ using ( var tx = connection . BeginTransaction ( isWriteOperation ? _writeIsolationLevel : _readIsolationLevel ) )
884
961
using ( var command = ( TCommand ) connection . CreateCommand ( ) )
885
962
{
886
963
command . CommandTimeout = ( int ) Setup . ConnectionTimeout . TotalSeconds ;
@@ -895,11 +972,9 @@ private async Task<BatchComplete> ExecuteChunk(RequestChunk chunk, IActorContext
895
972
switch ( req )
896
973
{
897
974
case WriteMessages msg :
898
- isWriteOperation = true ;
899
975
writeResults . Enqueue ( await HandleWriteMessages ( msg , command ) ) ;
900
976
break ;
901
977
case DeleteMessagesTo msg :
902
- isWriteOperation = true ;
903
978
await HandleDeleteMessagesTo ( msg , command ) ;
904
979
break ;
905
980
case ReplayMessages msg :
@@ -1331,7 +1406,7 @@ protected virtual void PreAddParameterToCommand(TCommand command, DbParameter pa
1331
1406
/// Select the buffer that has the smallest id on its first item, retrieve a maximum Setup.MaxBatchSize
1332
1407
/// items from it, and return it as a chunk that needs to be batched
1333
1408
/// </summary>
1334
- private RequestChunk DequeueChunk ( int chunkId )
1409
+ private ( RequestChunk chunk , bool isWrite ) DequeueChunk ( int chunkId )
1335
1410
{
1336
1411
var currentBuffer = _buffers
1337
1412
. Where ( q => q . Count > 0 )
@@ -1350,18 +1425,16 @@ private RequestChunk DequeueChunk(int chunkId)
1350
1425
if ( operations . Count == Setup . MaxBatchSize )
1351
1426
break ;
1352
1427
}
1428
+ return ( new RequestChunk ( chunkId , operations . ToArray ( ) ) , true ) ;
1353
1429
}
1354
- else
1430
+
1431
+ while ( currentBuffer . Count > 0 )
1355
1432
{
1356
- while ( currentBuffer . Count > 0 )
1357
- {
1358
- operations . Add ( currentBuffer . Dequeue ( ) . request ) ;
1359
- if ( operations . Count == Setup . MaxBatchSize )
1360
- break ;
1361
- }
1433
+ operations . Add ( currentBuffer . Dequeue ( ) . request ) ;
1434
+ if ( operations . Count == Setup . MaxBatchSize )
1435
+ break ;
1362
1436
}
1363
-
1364
- return new RequestChunk ( chunkId , operations . ToArray ( ) ) ;
1437
+ return ( new RequestChunk ( chunkId , operations . ToArray ( ) ) , false ) ;
1365
1438
}
1366
1439
1367
1440
private void CompleteBatch ( BatchComplete msg )
0 commit comments