Skip to content

Commit d31e4be

Browse files
Merge pull request #372 from MPCoreDeveloper/perf/bulk-delete-index-drop
perf: whole-file resolution for batch DELETE (A1 starter)
2 parents df85a8b + fec15c3 commit d31e4be

1 file changed

Lines changed: 88 additions & 11 deletions

File tree

‎src/SharpCoreDB/DataStructures/Table.CRUD.cs‎

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3062,6 +3062,10 @@ internal void DeleteMultipleKeys(List<(string Column, string Literal)> condition
30623062
// B1: decode only the columns the delete core touches (PK + loaded hash-index columns).
30633063
int[] deleteKeyColumns = BuildDeleteKeyColumns();
30643064

3065+
// A1: when the (plaintext, legacy variable-length) data file is small enough, read it
3066+
// ONCE and resolve every target from memory instead of one pread pair per deleted row.
3067+
byte[]? wholeFile = TryLoadWholeFileForDeleteResolution();
3068+
30653069
var recordsToDelete = new List<(long storagePosition, Dictionary<string, object> row)>();
30663070

30673071
foreach (var (col, literal) in conditions)
@@ -3076,16 +3080,25 @@ internal void DeleteMultipleKeys(List<(string Column, string Literal)> condition
30763080
var fastSearch = this.Index.Search(value);
30773081
if (fastSearch.Found)
30783082
{
3079-
var fastData = engine.Read(Name, fastSearch.Value);
3080-
if (fastData != null)
3083+
Dictionary<string, object>? fastRow = null;
3084+
if (wholeFile != null)
30813085
{
3082-
var fastRow = DeserializeDeleteKeyRow(fastData, deleteKeyColumns) ?? DeserializeRowFromSpan(fastData);
3083-
if (fastRow != null)
3086+
fastRow = DeserializeDeleteKeyRowFromFile(wholeFile, fastSearch.Value, deleteKeyColumns);
3087+
}
3088+
else
3089+
{
3090+
var fastData = engine.Read(Name, fastSearch.Value);
3091+
if (fastData != null)
30843092
{
3085-
recordsToDelete.Add((fastSearch.Value, fastRow));
3093+
fastRow = DeserializeDeleteKeyRow(fastData, deleteKeyColumns) ?? DeserializeRowFromSpan(fastData);
30863094
}
30873095
}
30883096

3097+
if (fastRow != null)
3098+
{
3099+
recordsToDelete.Add((fastSearch.Value, fastRow));
3100+
}
3101+
30893102
continue;
30903103
}
30913104
}
@@ -3127,12 +3140,21 @@ _btreeManager is null &&
31273140

31283141
foreach (var pos in hashIndex.LookupPositionsUnsafe(key))
31293142
{
3130-
var data = engine.Read(Name, pos);
3131-
if (data != null)
3143+
Dictionary<string, object>? row = null;
3144+
if (wholeFile != null)
31323145
{
3133-
var row = DeserializeDeleteKeyRow(data, deleteKeyColumns) ?? DeserializeRowFromSpan(data);
3134-
if (row != null) recordsToDelete.Add((pos, row));
3146+
row = DeserializeDeleteKeyRowFromFile(wholeFile, pos, deleteKeyColumns);
3147+
}
3148+
else
3149+
{
3150+
var data = engine.Read(Name, pos);
3151+
if (data != null)
3152+
{
3153+
row = DeserializeDeleteKeyRow(data, deleteKeyColumns) ?? DeserializeRowFromSpan(data);
3154+
}
31353155
}
3156+
3157+
if (row != null) recordsToDelete.Add((pos, row));
31363158
}
31373159

31383160
continue;
@@ -3237,12 +3259,29 @@ private int[] BuildDeleteKeyColumns()
32373259
/// </summary>
32383260
private Dictionary<string, object>? DeserializeDeleteKeyRow(byte[] data, int[] wanted)
32393261
{
3240-
if (_fixedWidthRecords || data == null || data.Length == 0 || wanted.Length == 0)
3262+
if (data == null)
3263+
{
3264+
return null;
3265+
}
3266+
3267+
return DeserializeDeleteKeyRow(data.AsSpan(), wanted);
3268+
}
3269+
3270+
/// <summary>
3271+
/// B1: decodes only the columns listed in <paramref name="wanted"/> (ascending) from a legacy
3272+
/// variable-length serialized row, skipping the unneeded columns' payload parsing entirely.
3273+
/// Returns a minimal dictionary (pk + hash-index columns only) so <see cref="DeleteRecordsCore"/>
3274+
/// performs the identical PK/hash lookups without materializing the full row. Returns null for
3275+
/// fixed-width layouts (those go through the fixed-width codec) or corrupt rows — callers fall
3276+
/// back to full-row deserialization in that case.
3277+
/// </summary>
3278+
private Dictionary<string, object>? DeserializeDeleteKeyRow(ReadOnlySpan<byte> span, int[] wanted)
3279+
{
3280+
if (_fixedWidthRecords || span.IsEmpty || wanted.Length == 0)
32413281
{
32423282
return null;
32433283
}
32443284

3245-
ReadOnlySpan<byte> span = data.AsSpan();
32463285
int offset = 0;
32473286
Dictionary<string, object>? row = null;
32483287
int wi = 0;
@@ -3274,6 +3313,44 @@ private int[] BuildDeleteKeyColumns()
32743313
return row;
32753314
}
32763315

3316+
// A1: whole-file delete resolution reads the (small) data file once instead of one pread pair
3317+
// per deleted row. Guarded to plaintext legacy variable-length files below this size.
3318+
private const long WholeFileDeleteResolutionLimitBytes = 32 * 1024 * 1024;
3319+
3320+
private byte[]? TryLoadWholeFileForDeleteResolution()
3321+
{
3322+
if (_fixedWidthRecords ||
3323+
this.storage is null ||
3324+
this.storage.AreRecordsEncrypted(DataFile))
3325+
{
3326+
return null;
3327+
}
3328+
3329+
var fi = new System.IO.FileInfo(DataFile);
3330+
if (!fi.Exists || fi.Length <= 0 || fi.Length > WholeFileDeleteResolutionLimitBytes)
3331+
{
3332+
return null;
3333+
}
3334+
3335+
return this.storage.ReadBytesRange(DataFile, 0, (int)fi.Length);
3336+
}
3337+
3338+
private Dictionary<string, object>? DeserializeDeleteKeyRowFromFile(byte[] wholeFile, long position, int[] wanted)
3339+
{
3340+
if (position < 0 || position + 4 > wholeFile.Length)
3341+
{
3342+
return null;
3343+
}
3344+
3345+
int recordLength = System.Buffers.Binary.BinaryPrimitives.ReadInt32LittleEndian(wholeFile.AsSpan((int)position, 4));
3346+
if (recordLength <= 0 || position + 4 + recordLength > wholeFile.Length)
3347+
{
3348+
return null;
3349+
}
3350+
3351+
return DeserializeDeleteKeyRow(wholeFile.AsSpan((int)position + 4, recordLength), wanted);
3352+
}
3353+
32773354
/// <summary>
32783355
/// Shared B8/B9 probe: resolves each key's record position through the PK B-tree, requires the
32793356
/// positions to be physically adjacent at the fixed-width stride, reads the whole contiguous

0 commit comments

Comments
 (0)