Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
709a1bc
perf: share source session across workers
niteshvijay-ms Aug 18, 2026
f852240
refactor: make session ownership explicit
niteshvijay-ms Aug 18, 2026
a63bb45
refactor: retain session factory name
niteshvijay-ms Aug 18, 2026
8ea0f37
refactor: generalize session factory naming
niteshvijay-ms Aug 18, 2026
9dcc1d3
perf: allow twenty concurrent session opens
niteshvijay-ms Aug 18, 2026
341d8c7
fix: preserve source sessions during token rotation
niteshvijay-ms Aug 18, 2026
1f5c1e9
refactor: defer disposal of rotated sessions
niteshvijay-ms Aug 18, 2026
7a30d26
refactor: extend session disposal grace period
niteshvijay-ms Aug 18, 2026
9da9449
refactor: separate session rotation from token refresh
niteshvijay-ms Aug 18, 2026
25f2cc7
refactor: let session provider refresh itself
niteshvijay-ms Aug 18, 2026
fe1118a
refactor: create initial session in provider
niteshvijay-ms Aug 18, 2026
87d7000
refactor: capture immutable source settings
niteshvijay-ms Aug 18, 2026
7aade0e
refactor: use credential session factory object
niteshvijay-ms Aug 18, 2026
d5d95a9
perf: share source UDT registration cache
niteshvijay-ms Aug 19, 2026
f03fb5c
refactor: consolidate source session wrapper
niteshvijay-ms Aug 19, 2026
7df21ac
refactor: expose typed source session API
niteshvijay-ms Aug 19, 2026
c04db77
fix: harden shared session lifecycle
niteshvijay-ms Aug 19, 2026
9850625
style: expand method implementations
niteshvijay-ms Aug 19, 2026
2a6af55
Encapsulate token refresh in source sessions
niteshvijay-ms Aug 19, 2026
6000e47
Inline source token refresh lifecycle
niteshvijay-ms Aug 19, 2026
d8bd8f5
Centralize retriable operation execution
niteshvijay-ms Aug 19, 2026
974705c
Simplify shared session architecture
niteshvijay-ms Aug 19, 2026
b96e1b3
Group source session lifecycle state
niteshvijay-ms Aug 19, 2026
3971db5
Flatten source session wrapper state
niteshvijay-ms Aug 19, 2026
3cdff29
Use lock-free current session reads
niteshvijay-ms Aug 19, 2026
ca6a58b
Move AAD token ownership to source wrapper
niteshvijay-ms Aug 20, 2026
17403a4
Require explicit source AAD mode
niteshvijay-ms Aug 20, 2026
e484d13
Inline source session settings
niteshvijay-ms Aug 20, 2026
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
45 changes: 45 additions & 0 deletions CassandraMigrationProcessor/CassandraDriver/ISessionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ namespace CassandraMigrationProcessor.CassandraDriver;
/// </summary>
public interface ISessionFactory
{
/// <summary>
/// Whether sessions returned by this factory are owned by the caller.
/// Shared job sessions are owned by the migration runner instead.
/// </summary>
bool CallerOwnsSourceSession => true;
bool CallerOwnsTargetSession => true;

/// <summary>Mint a new keyspace-agnostic source-cluster session.</summary>
ISession CreateSourceSession();

Expand All @@ -25,6 +32,44 @@ public interface ISessionFactory
Task<ISession> CreateTargetSessionAsync();
}

/// <summary>
/// Exposes the runner's job-wide source session while retaining per-worker
/// target sessions. Sharing the source avoids the Cosmos metadata connection
/// storm; independent target sessions preserve the writer capacity required
/// by high-concurrency bulk jobs.
/// </summary>
public sealed class SharedSourceSessionFactory : ISessionFactory
{
private readonly ISession _sourceSession;
private readonly ISessionFactory _targetSessionFactory;
private readonly SemaphoreSlim _targetSessionCreationGate = new(2, 2);

Comment thread
niteshvijay1995 marked this conversation as resolved.
Outdated
public SharedSourceSessionFactory(ISession sourceSession, ISessionFactory targetSessionFactory)
{
_sourceSession = sourceSession ?? throw new ArgumentNullException(nameof(sourceSession));
_targetSessionFactory = targetSessionFactory
?? throw new ArgumentNullException(nameof(targetSessionFactory));
}

public bool CallerOwnsSourceSession => false;

public ISession CreateSourceSession() => _sourceSession;

public async Task<ISession> CreateTargetSessionAsync()
{
await _targetSessionCreationGate.WaitAsync().ConfigureAwait(false);
try
Comment thread
niteshvijay1995 marked this conversation as resolved.
Outdated
{
return await _targetSessionFactory.CreateTargetSessionAsync()
.ConfigureAwait(false);
}
finally
{
_targetSessionCreationGate.Release();
}
}
}

/// <summary>
/// Default <see cref="ISessionFactory"/> bound to a single
/// <see cref="Job"/>. Delegates to <see cref="CassandraClientFactory"/>
Expand Down
6 changes: 4 additions & 2 deletions CassandraMigrationProcessor/DataTransfer/JobPipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ internal sealed class JobPipeline : IDisposable, IAsyncDisposable
private readonly PartitionManager _partitions;
public PipelineContext Context { get; }

public JobPipeline(MigrationLog log, Job job, PipelineConfig pipelineConfig, JobPartitioning partitioning, TokenRefreshManager? tokenRefreshManager, JobControl control)
public JobPipeline(MigrationLog log, Job job, PipelineConfig pipelineConfig,
JobPartitioning partitioning, ISessionFactory sessionFactory,
JobControl control)
{
_log = log;
_pipelineConfig = pipelineConfig;
Expand All @@ -43,7 +45,7 @@ public JobPipeline(MigrationLog log, Job job, PipelineConfig pipelineConfig, Job

Context = new PipelineContext(
_partitions,
new JobSessionFactory(log, job, tokenRefreshManager),
sessionFactory,
readerConfig,
writerConfig,
EnableReplay: enableReplay,
Expand Down
12 changes: 9 additions & 3 deletions CassandraMigrationProcessor/DataTransfer/MigrationJobRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ public class MigrationJobRunner : IAsyncDisposable
/// Runner-wide source / target sessions opened once in
/// <see cref="CreateAsync"/> and reused across wildcard expansion,
/// schema provisioning, and partition discovery. Disposed in
/// <see cref="DisposeAsync"/>. Copy workers mint their own sessions
/// via <see cref="ISessionFactory"/> for throughput isolation.
/// <see cref="DisposeAsync"/>. Copy workers reuse the thread-safe source
/// session to avoid multiplying driver metadata topology/schema handshakes,
/// while retaining independent target sessions for write throughput.
/// For simulated runs the target session is a <see cref="NullSession"/>.
/// </summary>
private readonly ISession _sourceSession;
Expand Down Expand Up @@ -173,7 +174,12 @@ public async Task StartAsync()
var partitioning = await RunPartitioningPhaseAsync(
job, units, cancellationToken);

_pipeline = new JobPipeline(_log, job, _pipelineConfig, partitioning, _tokenRefreshManager, _control);
_pipeline = new JobPipeline(
_log, job, _pipelineConfig, partitioning,
new SharedSourceSessionFactory(
_sourceSession,
new JobSessionFactory(_log, job, _tokenRefreshManager)),
_control);
_pipeline.Start();

await RunCopyPhaseAsync(job, units, partitioning, cancellationToken);
Expand Down
8 changes: 7 additions & 1 deletion CassandraMigrationProcessor/DataTransfer/PageReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ internal class PageReader : IDisposable
private readonly WorkerLog _log;
private readonly CancellationToken _ct;
private readonly ISession _sourceSession;
private readonly bool _ownsSourceSession;
private readonly int _pageSize;
private readonly int _maxReadRetries;
private readonly bool _preserveCellTtl;
Expand Down Expand Up @@ -59,6 +60,7 @@ private PageReader(WorkerLog log, ISessionFactory sessionFactory, ReaderConfig c
_preserveCellTtl = config.PreserveCellTtlAndWritetime;
_useJsonCopy = config.UseJsonCopy;
_sourceSession = sessionFactory.CreateSourceSession();
_ownsSourceSession = sessionFactory.CallerOwnsSourceSession;
}

public static Task<PageReader> CreateAsync(WorkerLog log,
Expand All @@ -68,7 +70,11 @@ public static Task<PageReader> CreateAsync(WorkerLog log,
return Task.FromResult(new PageReader(log, sessionFactory, config, cancellationToken));
}

public void Dispose() => MigrationUtilities.SafeDisposeSession(_sourceSession, "PageReader source session");
public void Dispose()
{
if (_ownsSourceSession)
MigrationUtilities.SafeDisposeSession(_sourceSession, "PageReader source session");
}

/// <summary>
/// Lazy, idempotent UDT registration for typed reads. The first typed
Expand Down
12 changes: 9 additions & 3 deletions CassandraMigrationProcessor/DataTransfer/PageWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ internal sealed class PageWriter : IDisposable
private readonly WorkerLog _log;
private readonly CancellationToken _ct;
private readonly ISession _targetSession;
private readonly bool _ownsTargetSession;
private readonly int _maxWriteRetries;
private readonly ConsistencyLevel _targetWriteConsistencyLevel;
private readonly bool _preserveCellTtl;
Expand All @@ -46,7 +47,7 @@ internal sealed class PageWriter : IDisposable
/// </summary>
internal Exception? LastWriteException { get; private set; }

private PageWriter(WorkerLog log, ISession targetSession,
private PageWriter(WorkerLog log, ISession targetSession, bool ownsTargetSession,
WriterConfig config, CancellationToken cancellationToken)
{
_log = log;
Expand All @@ -56,15 +57,20 @@ private PageWriter(WorkerLog log, ISession targetSession,
_preserveCellTtl = config.PreserveCellTtlAndWritetime;
_useJsonCopy = config.UseJsonCopy;
_targetSession = targetSession;
_ownsTargetSession = ownsTargetSession;
}

public static async Task<PageWriter> CreateAsync(WorkerLog log, ISessionFactory sessionFactory, WriterConfig config, CancellationToken cancellationToken)
{
var targetSession = await sessionFactory.CreateTargetSessionAsync();
return new PageWriter(log, targetSession, config, cancellationToken);
return new PageWriter(log, targetSession, sessionFactory.CallerOwnsTargetSession, config, cancellationToken);
}

public void Dispose() => MigrationUtilities.SafeDisposeSession(_targetSession, "PageWriter target session");
public void Dispose()
{
if (_ownsTargetSession)
MigrationUtilities.SafeDisposeSession(_targetSession, "PageWriter target session");
}

private Task<IRowWriteStrategy> GetStrategyAsync(Partition partition)
{
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A .NET 9 Blazor Server web app that migrates data from **Azure Cosmos DB for Apa

- **Schema auto-sync** — discovers source keyspaces, tables, columns, clustering keys, and static columns; generates DDL on the target automatically.
- **Feed-range partitioned bulk copy** — splits each table into token-range chunks and copies them with configurable parallelism.
- **Per-worker Cassandra sessions** — each parallel worker maintains its own driver session for maximum throughput.
- **Efficient Cassandra sessions** — workers share the thread-safe source session while retaining independent target sessions for write throughput.
- **Checkpoint-based pause / resume** — stop at any time and continue later with zero data loss; state is persisted to disk.
- **Online mode (change feed)** — after the initial bulk copy, replays Cosmos DB change feed events to keep the target in sync until cutover.
- **Wildcard table selection** — migrate all tables in a keyspace with `keyspace.*` or pick individual tables.
Expand Down