Public Access
Sync: mirror a pulled page in one write and read the outbox once per page, not twice per change
This commit is contained in:
1 parent
f997e87c5f
commit
873405e591
4 files changed
+173
-13
No files matched your search
@@ -154,9 +154,10 @@ public sealed class SyncEngine
|
||||
continue;
|
||||
}
|
||||
|
||||
await ApplyPageAsync(vaultId, response.Changes, report, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
foreach (var change in response.Changes)
|
||||
{
|
||||
await ApplyAsync(vaultId, change, report, cancellationToken).ConfigureAwait(false);
|
||||
report.Pulled++;
|
||||
|
||||
// Counted apart for the same reason the pushed ones are: a machine reads back the log
|
||||
@@ -310,6 +311,61 @@ public sealed class SyncEngine
|
||||
private static string PulledTypes { get; } =
|
||||
string.Join(',', ItemKinds.SyncedTypes.Select(type => (int)type).Order());
|
||||
|
||||
/// <summary>Applies one page of pulled changes, in order, with as few writes as that allows.</summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Each change used to cost a write to mirror it and a read to look for a local edit of the same item —
|
||||
/// two or more SQLite transactions apiece, which on a first sync of a large keychain was most of the time
|
||||
/// the sync took. The outbox is read once per page instead, and every run of changes with no local edit
|
||||
/// waiting is mirrored in a single write.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// ◆ A change that does have an edit waiting goes through <see cref="ApplyAsync"/> exactly as before, with
|
||||
/// the run in front of it written first: the reconciler reads the mirror and the outbox as they stand at
|
||||
/// that change, and both are what the one-by-one loop would have left them as. The one-read snapshot only
|
||||
/// decides which path a change takes; the path that matters re-reads the outbox for itself.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
private async Task ApplyPageAsync(
|
||||
Guid vaultId,
|
||||
IReadOnlyList<SyncChange> changes,
|
||||
SyncReportBuilder report,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (changes.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var waiting = (await outbox.ListAllAsync(vaultId, cancellationToken).ConfigureAwait(false))
|
||||
.Select(pending => (pending.EntityType, pending.EntityId))
|
||||
.ToHashSet();
|
||||
|
||||
var run = new List<SyncChange>();
|
||||
|
||||
foreach (var change in changes)
|
||||
{
|
||||
if (!reconcilers.ContainsKey(change.EntityType))
|
||||
{
|
||||
// See ApplyAsync: a type this build cannot sync is neither mirrored nor reconciled.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!waiting.Contains((change.EntityType, change.EntityId)))
|
||||
{
|
||||
run.Add(change);
|
||||
continue;
|
||||
}
|
||||
|
||||
await ItemMirror.WriteManyAsync(items, vaultId, run, cancellationToken).ConfigureAwait(false);
|
||||
run.Clear();
|
||||
|
||||
await ApplyAsync(vaultId, change, report, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await ItemMirror.WriteManyAsync(items, vaultId, run, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task ApplyAsync(
|
||||
Guid vaultId,
|
||||
SyncChange change,
|
||||
|
||||
Reference in new issue
Block a user