Browse Source
feat: add comprehensive debug logging to track Parquet write sequence
feat: add comprehensive debug logging to track Parquet write sequence
Added extensive WARN-level debug messages to trace the exact sequence of: - Every write() operation with position tracking - All getPos() calls with caller stack traces - flush() and flushInternal() operations - Buffer flushes and position updates - Metadata updates BREAKTHROUGH FINDING: - Last getPos() call: returns 1252 bytes (at writeCall #465) - 5 more writes happen: add 8 bytes → buffer.position()=1260 - close() flushes all 1260 bytes to disk - But Parquet footer records offsets based on 1252! Result: 8-byte offset mismatch in Parquet footer metadata → Causes EOFException: 'Still have: 78 bytes left' The 78 bytes is NOT missing data - it's a metadata calculation error due to Parquet footer offsets being stale by 8 bytes.pull/7526/head
2 changed files with 138 additions and 18 deletions
-
74other/java/client/src/main/java/seaweedfs/client/SeaweedOutputStream.java
-
82test/java/spark/DEBUG_BREAKTHROUGH.md
@ -0,0 +1,82 @@ |
|||||
|
# Debug Breakthrough: Root Cause Identified |
||||
|
|
||||
|
## Complete Event Sequence |
||||
|
|
||||
|
### 1. Write Pattern |
||||
|
``` |
||||
|
- writeCalls 1-465: Writing Parquet data |
||||
|
- Last getPos() call: writeCalls=465, returns 1252 |
||||
|
→ flushedPosition=0 + bufferPosition=1252 = 1252 |
||||
|
|
||||
|
- writeCalls 466-470: 5 more writes (8 bytes total) |
||||
|
→ These are footer metadata bytes |
||||
|
→ Parquet does NOT call getPos() after these writes |
||||
|
|
||||
|
- close() called: |
||||
|
→ buffer.position()=1260 (1252 + 8) |
||||
|
→ All 1260 bytes flushed to disk |
||||
|
→ File size set to 1260 bytes |
||||
|
``` |
||||
|
|
||||
|
### 2. The Problem |
||||
|
|
||||
|
**Parquet's write sequence:** |
||||
|
1. Write column chunk data, calling `getPos()` after each write → records offsets |
||||
|
2. **Last `getPos()` returns 1252** |
||||
|
3. Write footer metadata (8 bytes) → **NO getPos() call!** |
||||
|
4. Close file → flushes all 1260 bytes |
||||
|
|
||||
|
**Result**: Parquet footer says data ends at **1252**, but file actually has **1260** bytes. |
||||
|
|
||||
|
### 3. The Discrepancy |
||||
|
|
||||
|
``` |
||||
|
Last getPos(): 1252 bytes (what Parquet recorded in footer) |
||||
|
Actual file: 1260 bytes (what was flushed) |
||||
|
Missing: 8 bytes (footer metadata written without getPos()) |
||||
|
``` |
||||
|
|
||||
|
### 4. Why It Fails on Read |
||||
|
|
||||
|
When Parquet tries to read the file: |
||||
|
- Footer says column chunks end at offset 1252 |
||||
|
- Parquet tries to read from 1252, expecting more data |
||||
|
- But the actual data structure is offset by 8 bytes |
||||
|
- Results in: `EOFException: Still have: 78 bytes left` |
||||
|
|
||||
|
### 5. Key Insight: The "78 bytes" |
||||
|
|
||||
|
The **78 bytes** is NOT missing data — it's a **metadata mismatch**: |
||||
|
- Parquet footer contains incorrect offsets |
||||
|
- These offsets are off by 8 bytes (the final footer writes) |
||||
|
- When reading, Parquet calculates it needs 78 more bytes based on wrong offsets |
||||
|
|
||||
|
## Root Cause |
||||
|
|
||||
|
**Parquet assumes `getPos()` reflects ALL bytes written, even buffered ones.** |
||||
|
|
||||
|
Our implementation is correct: |
||||
|
```java |
||||
|
public long getPos() { |
||||
|
return position + buffer.position(); // ✅ Includes buffered data |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
BUT: Parquet writes footer metadata AFTER the last `getPos()` call, so those 8 bytes |
||||
|
are not accounted for in the footer's offset calculations. |
||||
|
|
||||
|
## Why Unit Tests Pass but Spark Fails |
||||
|
|
||||
|
**Unit tests**: Direct writes → immediate getPos() → correct offsets |
||||
|
**Spark/Parquet**: Complex write sequence → footer written AFTER last getPos() → stale offsets |
||||
|
|
||||
|
## The Fix |
||||
|
|
||||
|
We need to ensure that when Parquet writes its footer, ALL bytes (including those 8 footer bytes) |
||||
|
are accounted for in the file position. Options: |
||||
|
|
||||
|
1. **Force flush on getPos()** - ensures position is up-to-date |
||||
|
2. **Override FSDataOutputStream more deeply** - intercept all write operations |
||||
|
3. **Investigate Parquet's footer writing logic** - understand why it doesn't call getPos() |
||||
|
|
||||
|
Next: Examine how HDFS/S3 FileSystem implementations handle this. |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue