Skip to content

Fix Parquet writer data race - #956

Merged
adsharma merged 2 commits into
LadybugDB:mainfrom
Saiteja64:fix/parquet-writer-data-race
Sep 10, 2026
Merged

Fix Parquet writer data race#956
adsharma merged 2 commits into
LadybugDB:mainfrom
Saiteja64:fix/parquet-writer-data-race

Conversation

@Saiteja64

Copy link
Copy Markdown
Contributor

This fixes a TSan data race in Parquet export.

Multiple worker threads could call ParquetWriter::flush() concurrently. The existing mutex only protected flushRowGroup(), while prepareRowGroup() also modifies shared writer state.

Moved the existing lock to cover the full flush operation.

ApiTest.PrepareExport previously reported 3 TSan warnings and now passes cleanly across repeated runs. A full TSan suite run also dropped from 10 race reports to 2, with no remaining Parquet-related races.

Part of #879.

Saiteja64 and others added 2 commits September 9, 2026 23:06
On top of LadybugDB#956 (which fixed the TSan data race by serializing the
whole flush): move the null counter from the shared ColumnWriter into
the thread-local ColumnWriterState, so prepareRowGroup() only reads
shared immutable state (writer layout, schema, codec) and writes to
the per-flush PreparedRowGroup.

flush() now prepares lock-free in parallel and serializes just the
commit (fileOffset / file bytes / fileMetaData) in flushRowGroup().
ft.clear() stays outside the lock since the FactorizedTable is
thread-local.

Side benefit: null_count statistics are now per row group instead of
accumulating across row groups.

Verified: 8-thread COPY round-trips (200k scalar rows with NULLs,
50k struct rows with NULLs) match ground-truth counts/sums.
@adsharma

Copy link
Copy Markdown
Contributor

The fix looks correct. Trying to see if we can keep it correct while restoring the write parallelism that existed before.

@adsharma
adsharma merged commit ea88838 into LadybugDB:main Sep 10, 2026
4 checks passed
adsharma added a commit that referenced this pull request Sep 10, 2026
On top of #956 (which fixed the TSan data race by serializing the
whole flush): move the null counter from the shared ColumnWriter into
the thread-local ColumnWriterState, so prepareRowGroup() only reads
shared immutable state (writer layout, schema, codec) and writes to
the per-flush PreparedRowGroup.

flush() now prepares lock-free in parallel and serializes just the
commit (fileOffset / file bytes / fileMetaData) in flushRowGroup().
ft.clear() stays outside the lock since the FactorizedTable is
thread-local.

Side benefit: null_count statistics are now per row group instead of
accumulating across row groups.

Verified: 8-thread COPY round-trips (200k scalar rows with NULLs,
50k struct rows with NULLs) match ground-truth counts/sums.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants