Skip to content

Commit 9832c18

Browse files
authored
Ensure commits are no larger than 100 changes (#1606)
* don't include the object twice when serializing `MiniLcmCrdtAdapter` * chunk changes into commits of 100, using the defer commit feature of Harmony * fix a race condition in a migration test. Prevent hiding exceptions when querying entries * update harmony * flush commits server side based on the number of changes or commits
1 parent 8d03d52 commit 9832c18

File tree

12 files changed

+796
-25
lines changed

12 files changed

+796
-25
lines changed

backend/FwLite/LcmCrdt.Tests/Data/MigrationTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ private async Task InitDbFromScripts()
5757
public async Task GetEntries_WorksAfterMigrationFromScriptedDb()
5858
{
5959
await InitDbFromScripts();
60+
//manually open the connection, this prevents a race condition where the entries query runs before the collations are created.
61+
await _asyncScope.ServiceProvider.GetRequiredService<LcmCrdtDbContext>().Database.OpenConnectionAsync();
6062
var api = _asyncScope.ServiceProvider.GetRequiredService<IMiniLcmApi>();
6163
var hasEntries = false;
6264
await foreach (var entry in api.GetEntries(new(Count: 100)))

backend/FwLite/LcmCrdt.Tests/DataModelSnapshotTests.VerifyDbModel.verified.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@
359359
Foreign keys:
360360
ObjectSnapshot {'CommitId'} -> Commit {'Id'} Required Cascade ToDependent: Snapshots ToPrincipal: Commit
361361
Indexes:
362+
EntityId
362363
CommitId, EntityId Unique
363364
Annotations:
364365
DiscriminatorProperty:

backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public class CrdtMiniLcmApi(
2727
CurrentProjectService projectService,
2828
IMiniLcmCultureProvider cultureProvider,
2929
MiniLcmValidators validators,
30+
LcmCrdtDbContext dbContext,
3031
IOptions<LcmCrdtConfig> config) : IMiniLcmApi
3132
{
3233
private Guid ClientId { get; } = projectService.ProjectData.ClientId;
@@ -60,10 +61,24 @@ private async Task<Commit> AddChange(IChange change)
6061
return commit;
6162
}
6263

63-
private async Task<Commit> AddChanges(IEnumerable<IChange> changes)
64+
private async Task AddChanges(IEnumerable<IChange> changes)
6465
{
65-
var commit = await dataModel.AddChanges(ClientId, changes, commitMetadata: NewMetadata());
66-
return commit;
66+
await AddChanges(changes.Chunk(100));
67+
}
68+
69+
/// <summary>
70+
/// use when making a large number of changes at once
71+
/// </summary>
72+
/// <param name="changeChunks"></param>
73+
private async Task AddChanges(IEnumerable<IChange[]> changeChunks)
74+
{
75+
await using var transaction = await dbContext.Database.BeginTransactionAsync();
76+
foreach (var chunk in changeChunks)
77+
{
78+
await dataModel.AddChanges(ClientId, chunk, commitMetadata: NewMetadata(), deferCommit: true);
79+
}
80+
await dataModel.FlushDeferredCommits();
81+
await transaction.CommitAsync();
6782
}
6883

6984
public async Task<WritingSystems> GetWritingSystems()
@@ -403,7 +418,7 @@ private async IAsyncEnumerable<Entry> GetEntries(
403418
var complexFormComparer = cultureProvider.GetCompareInfo(sortWs)
404419
.AsComplexFormComparer();
405420
var entries = queryable.AsAsyncEnumerable();
406-
await foreach (var entry in entries)
421+
await foreach (var entry in EfExtensions.SafeIterate(entries))
407422
{
408423
entry.ApplySortOrder(complexFormComparer);
409424
yield return entry;
@@ -433,12 +448,10 @@ private async IAsyncEnumerable<Entry> GetEntries(
433448
public async Task BulkCreateEntries(IAsyncEnumerable<Entry> entries)
434449
{
435450
var semanticDomains = await SemanticDomains.ToDictionaryAsync(sd => sd.Id, sd => sd);
436-
await AddChanges(
437-
entries.ToBlockingEnumerable()
438-
.SelectMany(entry => CreateEntryChanges(entry, semanticDomains))
439-
//force entries to be created first, this avoids issues where references are created before the entry is created
440-
.OrderBy(c => c is CreateEntryChange ? 0 : 1)
441-
);
451+
await AddChanges(entries.ToBlockingEnumerable()
452+
.SelectMany(entry => CreateEntryChanges(entry, semanticDomains))
453+
//force entries to be created first, this avoids issues where references are created before the entry is created
454+
.OrderBy(c => c is CreateEntryChange ? 0 : 1));
442455
}
443456

444457
private IEnumerable<IChange> CreateEntryChanges(Entry entry, Dictionary<Guid, SemanticDomain> semanticDomains)

0 commit comments

Comments
 (0)