You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 2a70546
Browse filesBrowse the repository at this point in the historyBrowse files
feat(config)!: encrypt table data at rest by default (plan 3-1c deliverable 2)
EnableAtRestRecordEncryption now defaults to true, so a new database protects its table payloads -
records AND the overflow arena - with per-record AES-256-GCM, alongside the metadata and transaction
files that were already encrypted. Before this the default paid AES on the metadata while leaving the
user's data on disk in the clear: the cost of protection without the guarantee (audit 3-1c).
This is the flip the plan called "a work package, not a config flip". It was measured three times: >=45
failures across 12 classes originally, 21 across 10 after 3-1f/3-1g/3-1h/3-1i, and 2 after the second
wave (at-rest index build, GetAllRecords offsets, compaction). The last two are closed here:
- the 3-1c-4 tripwire now expects the default to keep a known inserted value out of the table data
files, instead of expecting plaintext there;
- the compiled-query latency budget turned out to be a read-path defect rather than a cache problem:
ReadAllRecords opened TWO FileStreams per record (one for the length prefix, one for the payload), so
1000 queries over 100 rows meant ~200,000 handle open/close pairs. It now reads the file once into a
buffer and walks it in memory - 1000 compiled queries: >2000 ms -> 552 ms (budget 2000 ms). The
plaintext walk is byte-for-byte unchanged and very large files stay on the incremental path.
Compatibility: a file is encrypted only when it was created with the option on. Existing plaintext
databases stay byte-for-byte readable and are never mixed with encrypted records; their tables are
upgraded to the encrypted format when they are compacted (compaction rewrites them through a brand-new
file, which Storage encrypts). NoEncryptMode=true remains the single documented raw-speed opt-out.
Docs: DatabaseConfig states the new default, the measured cost and the format rule; the CHANGELOG has a
Changed entry; plan 3-1c deliverable 2 is closed with the measurements.
Full SharpCoreDB.Tests: 1834 total / 0 failed / 16 skipped. SharpCoreDB.slnx: 0 errors.
Copy file name to clipboardExpand all lines: docs/CHANGELOG.md
+5-1Lines changed: 5 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -15,12 +15,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
15
15
-**Tuning knobs**: `MaxNeighbors` (R), `ConstructionSearchListSize` (L_build), `QuerySearchListSize` (L_search floor), `Alpha`, `BuildPasses`, plus a per-query beam override `Search(query, k, searchListSize)` so recall/latency can be traded per query without rebuilding the graph.
16
16
-**SQL DDL**: `CREATE VECTOR INDEX … USING DISKANN` is recognised by the parser, stored as table metadata, and now builds a real `DiskAnnIndex` through the optimiser (see Fixed below).
17
17
18
+
### Changed
19
+
20
+
- **Table data is now encrypted at rest by default.** `DatabaseConfig.EnableAtRestRecordEncryption` defaults to `true`, so a new database protects its table payloads — records *and* the overflow arena — with per-record AES-256-GCM, alongside the metadata and transaction files that were already encrypted. Previously the default encrypted the metadata while leaving the user's data on disk in the clear: the cost of protection without the guarantee. `NoEncryptMode = true` stays the single, documented raw-speed opt-out (every file plaintext). Measured cost of the default versus that opt-out: ≈1.11× CREATE/INSERT, no measurable UPDATE penalty on the contiguous paths, roughly double the file size for the per-record GCM framing, and one whole-file decrypt per full-scan-shaped query. Existing plaintext databases remain byte-for-byte readable and are never mixed with encrypted records; their tables are upgraded to the encrypted format when they are compacted. Guarded by `EncryptionCoverageTests`, which fails if the default ever stops protecting table payloads.
21
+
18
22
### Fixed
19
23
20
24
-**An at-rest database could not be scanned** — with `EnableAtRestRecordEncryption = true` a whole-table scan (and `COUNT(*)`) returned **zero rows**, in-session and after a reopen, while primary-key lookups kept working. The scan compared each record's offset in its decrypted buffer against the PK index's physical file offsets, so every row was misread as a superseded version; the parallel scan and the `StructRow` scan had the same shape, and the hash indexes — rebuilt from that scan — came back empty after a reopen. Scans now receive the records' physical offsets, so scans, counts and index rebuilds are correct on encrypted files.
21
25
-**`WHERE <numeric column> = <literal with decimals>` matched nothing** — a simple numeric equality compared the row value's *text* against the literal, so `score = 5.0` could never match a stored 5.0 (`double.ToString()` yields `"5"`) while `score = 5` matched by accident, and the ordering operators parsed literals with the machine's culture (a decimal literal did not even parse under a comma-decimal culture). Numbers are now compared numerically against an invariant-culture parse of the literal; string comparisons are unchanged.
22
26
-**A hash index built on a fixed-width table missed rows** — `CREATE TABLE` registers a hash index for every column, and the lazy build decoded fixed-width records with the variable-length parser, so on a default table only the rows that happened to parse were indexed: `WHERE <non-unique column> = value` returned **a single row instead of every match**, and the build stopped at the first tombstone, hiding every live row behind a deleted one. The build now decodes with the layout the records were written in and skips tombstoned and empty slots.
23
-
-**With `EnableAtRestRecordEncryption = true`, indexed lookups, struct queries and compaction were broken** — three defects of the opt-in flag itself: the lazily built hash index came out **empty** for an at-rest file (the build walked the raw file and read the 8-byte magic header as a record length), so every indexed lookup missed; `GetAllRecords` reported **buffer** offsets where every caller resolves records by **physical** offset, so the `StructRow` numeric/SIMD paths filtered every row away; and `CompactStorage` matched its active set against the decrypted buffer walk, so **compaction dropped nearly every row** of an at-rest table — and would have rewritten it as plaintext. All three now go through the decrypting, physical-offset-aware read path. The flip test for making at-rest the default (plan §3-1c deliverable 2) is down from ≥45 failures to **2**, one of which is the tripwire that fires by design.
27
+
-**With `EnableAtRestRecordEncryption = true`, indexed lookups, struct queries and compaction were broken** — three defects of the opt-in flag itself: the lazily built hash index came out **empty** for an at-rest file (the build walked the raw file and read the 8-byte magic header as a record length), so every indexed lookup missed; `GetAllRecords` reported **buffer** offsets where every caller resolves records by **physical** offset, so the `StructRow` numeric/SIMD paths filtered every row away; and `CompactStorage` matched its active set against the decrypted buffer walk, so **compaction dropped nearly every row** of an at-rest table — and would have rewritten it as plaintext. All three now go through the decrypting, physical-offset-aware read path. Those fixes took the flip of the at-rest default from **≥45 test failures to zero**, which is what made it safe to ship as the default (see **Changed** above).
24
28
-**`USING DISKANN` silently built a `FlatIndex`** — `VectorQueryOptimizer.BuildIndex` fell through to the `Flat` default arm for the `"DISKANN"` string, so the DDL produced an exact scan behind a DiskANN-shaped name. `DISKANN` now maps to `VectorIndexType.DiskAnn`.
0 commit comments