Core: Add v4 TrackedFileAdapters to bridge Data/Delete Files#16100
Core: Add v4 TrackedFileAdapters to bridge Data/Delete Files#16100anoopj wants to merge 16 commits into
Conversation
| return new TrackedDeleteFile(file, spec); | ||
| } | ||
|
|
||
| // TODO: TrackedFile will likely get an explicit partition tuple field (using a union partition |
There was a problem hiding this comment.
This will change after the approach to store partition tuple is settled.
There was a problem hiding this comment.
Steven and Russell replied on the dev list that they're good with a tracked partition tuple. I also confirmed with Dan and Amogh, so I think we are probably good to go. Maybe we should start getting the changes to the interfaces and implementations done? This would then only need to run the projection.
| return result.isEmpty() ? null : result; | ||
| } | ||
|
|
||
| static Map<Integer, Long> nullValueCounts(ContentStats stats) { |
There was a problem hiding this comment.
An open question is whether it's worth caching the stats (lazy/eager). I don't see a lot of repeated reads, so may not be worth it.
There was a problem hiding this comment.
It's probably fine. I have a comment about this below.
|
|
||
| private TrackedFileAdapters() {} | ||
|
|
||
| static DataFile asDataFile(TrackedFile file, PartitionSpec spec) { |
There was a problem hiding this comment.
What was your reason not to make this a method of TrackedFile?
Also, PartitionSpec is tracked by the file itself, so is very strange to pass it in here. At a minimum, I would expect this to have a validation that the spec's ID matches the file's spec ID. But it would also be better to have some way to look up spec by ID instead of forcing the caller to do it and then validate that the caller did it correctly.
There was a problem hiding this comment.
I intentionally kept the adapters outside of TrackedFile to avoid coupling it with Data/DeleteFile. It seemed like an adapter concern.
The tracked file keeps track of the spec ID, but not the spec itself. I saw some v3 code paths that followed a similar pattern of passing around specsById. Pretty much open to changing it if you have suggestions.
There was a problem hiding this comment.
I changed the methods to take in specsById map instead.
| // each reported the full file size). | ||
| @Override | ||
| public long fileSizeInBytes() { | ||
| return dv.sizeInBytes(); |
There was a problem hiding this comment.
I appreciate the comment. The decision here seems reasonable to me since we don't know the total Puffin file size.
We should also consider whether we want to have a field in the tracked_file struct for this. We originally wanted to use file_size_in_bytes for the Puffin file size so that we could determine when DVs should be compacted. But variance in the footer was a problem that prevented it from being used as intended.
@aokolnychyi should we revisit this?
There was a problem hiding this comment.
even if we know the total Puffin file size, dv.sizeInBytes still seems the correct value here. A puffin file may contain thousands of DVs. A logical DeleteFile should only contain one DV. The DeleteFile size should be the DV size.
|
|
||
| @Override | ||
| public Map<Integer, ByteBuffer> lowerBounds() { | ||
| return TrackedFileAdapters.lowerBounds(file.contentStats()); |
There was a problem hiding this comment.
This is okay, but a little concerning because the helper method here creates a new map every time it is called. Creating the map is an expensive operation because it allocates buffers to hold each column bound and serializes into that buffer. Then the map is thrown away rather than reused.
The evaluators themselves (for example, InclusiveMetricsEvaluator) only call these methods once to evaluate for the data file, but if multiple evaluators are used then we should expect a performance degredation.
On the other hand, we want to have evaluators that work directly with ContentStats instead of going through these methods. I think I'm fine with leaving this as-is for now, but we need to make sure that we use the right evaluators everywhere.
There was a problem hiding this comment.
Agree. Will build a new evaluator that operates on content stats, as a followup. cc @nastra
There was a problem hiding this comment.
Let's hold off on doing it right now. I think the ContentStats API is going to change.
| return new TrackedDataFile(file, spec); | ||
| } | ||
|
|
||
| static DeleteFile asDVDeleteFile(TrackedFile file, PartitionSpec spec) { |
There was a problem hiding this comment.
We will also need a way to wrap TrackedFile as a v2 DeleteFile for position deletes.
There was a problem hiding this comment.
Ack. I will create a followup PR for this so that the size is manageable.
There was a problem hiding this comment.
Added TrackedPositionDeleteFile
| private final Tracking tracking; | ||
| private final PartitionSpec spec; | ||
|
|
||
| private AbstractTrackedContentFile(TrackedFile file, PartitionSpec spec) { |
There was a problem hiding this comment.
Rather than Abstract, we typically use Base for the prefix. For example, BaseAction or BaseContentStats.
There was a problem hiding this comment.
I think we can even drop the Base here with just TrackedContentFile, because ContentFile is a base class. This is also consistent with other classes like TrackedDataFile, TrackedDVDeleteFile.
There was a problem hiding this comment.
I dropped the Base. @rdblue let me know if thats OK.
| * <p>Subclasses provide {@code content()}, {@code firstRowId()}, {@code equalityFieldIds()}, and | ||
| * the copy methods. | ||
| */ | ||
| private abstract static class AbstractTrackedContentFile<F extends ContentFile<F>> |
There was a problem hiding this comment.
It would be helpful to implement the version for v2 position deletes here as well. That would make it easier to evaluate the implementations here, although I suspect it will be fine.
There was a problem hiding this comment.
Added it. It's actually fairly light.
| tracking.set(3, 11L); | ||
| tracking.set(5, 1000L); | ||
| tracking.setManifestLocation("s3://bucket/manifest.avro"); | ||
| tracking.set(8, 7L); |
There was a problem hiding this comment.
switch to the builder added recently?
There was a problem hiding this comment.
+1 as I feel setting all stuff by position is quite brittle
| private final Tracking tracking; | ||
| private final PartitionSpec spec; | ||
|
|
||
| private AbstractTrackedContentFile(TrackedFile file, PartitionSpec spec) { |
There was a problem hiding this comment.
I think we can even drop the Base here with just TrackedContentFile, because ContentFile is a base class. This is also consistent with other classes like TrackedDataFile, TrackedDVDeleteFile.
| } | ||
| } | ||
|
|
||
| /** Adapts a TrackedFile EQUALITY_DELETES entry to the {@link DeleteFile} interface. */ |
There was a problem hiding this comment.
Is this class only for EQUALITY_DELETES, as the content() method override indicates? if yes, it can probably names as TrackedEqualityDeleteFile
Just to confirm my understanding. This is for new V4 manifest files, where v2 position delete file entries won't exist.
There was a problem hiding this comment.
Is this class only for EQUALITY_DELETES, as the content() method override indicates? if yes, it can probably names as TrackedEqualityDeleteFile
Correct. Renamed.
Just to confirm my understanding. This is for new V4 manifest files, where v2 position delete file entries won't exist.
v2 position delete file entries can also exist, if a table is upgraded from v2. I just added TrackedPositionDeleteFile to handle it. PTAL
| // each reported the full file size). | ||
| @Override | ||
| public long fileSizeInBytes() { | ||
| return dv.sizeInBytes(); |
There was a problem hiding this comment.
even if we know the total Puffin file size, dv.sizeInBytes still seems the correct value here. A puffin file may contain thousands of DVs. A logical DeleteFile should only contain one DV. The DeleteFile size should be the DV size.
e74049c to
832deb0
Compare
gaborkaszab
left a comment
There was a problem hiding this comment.
I went through this for my own education. Left some comments, let me know if they make sense.
|
|
||
| private static PartitionSpec resolveSpec( | ||
| TrackedFile file, Map<Integer, PartitionSpec> specsById) { | ||
| Integer specId = file.specId(); |
There was a problem hiding this comment.
nit: The only thing needed from TrackedFile here is the specId. Can't we pass that instead, and then there is nothing V4 specific in this function (like TrackedFile, etc.) so it might go into some PartitionSpec related util class.
There was a problem hiding this comment.
That is true, but I think this signature makes the call sites a bit more readable.
| return file; | ||
| } | ||
|
|
||
| protected Tracking tracking() { |
There was a problem hiding this comment.
nit: Isn't this redundant? Tracking is part of TrackedFile
There was a problem hiding this comment.
Changed to just delegate to file.tracking()
|
|
||
| /** | ||
| * Shared base for all tracked file adapters. Holds the common fields and implements the methods | ||
| * that delegate to {@link Tracking} and {@link PartitionSpec}. |
There was a problem hiding this comment.
Doesn't this comment mean that practically we shouldn't have a TrackedFile member on this level of abstraction?
There was a problem hiding this comment.
The comment was a bit stale. Fixed. Thanks for the callout!
| } | ||
|
|
||
| @Override | ||
| public List<Integer> equalityFieldIds() { |
There was a problem hiding this comment.
nit: the default implementation already returns null. Isn't this redundant here? Same for content() and some other functions elsewhere like firstRowId()
There was a problem hiding this comment.
Removed the ones that were returning null. Kept content() override to be explicit.
|
|
||
| private static TrackingStruct createTracking(long manifestPos) { | ||
| // Uses the struct-type constructor because the builder creates with BASE_TYPE which does not | ||
| // include ROW_POSITION, so manifest position cannot be set on builder-created structs. |
There was a problem hiding this comment.
This justification doesn't hold. In TrackingStruct.java, BASE_TYPE (L33-43) already includes MetadataColumns.ROW_POSITION as its 9th field, and the builder's build() calls a constructor that does super(BASE_TYPE, BASE_TYPE) (L91), so builder-created structs do support row position projection.
manifestLocation and manifestPos aren't part of the struct projection at all - they're plain instance fields (L55-56), "set by manifest readers, not written to manifests". They're assigned via setManifestLocation(...) and set(8, ...) regardless of which constructor was used.
So the helper can just be:
TrackingStruct tracking = TrackingStruct.builder()
.status(EntryStatus.ADDED)
.snapshotId(42L)
.dataSequenceNumber(10L)
.fileSequenceNumber(11L)
.firstRowId(1000L)
.build();
tracking.setManifestLocation("s3://bucket/manifest.avro");
tracking.set(8, manifestPos);
return tracking;There was a problem hiding this comment.
I had fixed exactly this last week. Sounds like this was a stale comment?
| } | ||
| } | ||
|
|
||
| /** Adapts a TrackedFile POSITION_DELETES entry to the {@link DeleteFile} interface. */ |
There was a problem hiding this comment.
TrackedPositionDeleteFile doesn't override referencedDataFile(), contentOffset(), or contentSizeInBytes(), so all three fall through to the DeleteFile interface defaults of null. But V4Metadata.java (cases 17/18/19, L495-509) writes those slots out for POSITION_DELETES entries, so V4 manifests can carry V2-style position delete metadata — a V2 delete file with referenced_data_file and content offset/size.
The read-side gap: TrackedFile (interface) doesn't expose referencedDataFile/contentOffset/contentSizeInBytes, so this adapter has no way to surface them even if they were in the manifest. So either:
- V4 manifests aren't expected to round-trip V2 position delete metadata (worth a code comment here saying these are intentionally always null), or
TrackedFileis missing fields for position-delete-specific metadata, and this adapter silently drops them when reading V2 position deletes from a V4 manifest.
The test testPositionDeleteFileAdapterDelegatesAllFields doesn't exercise these three fields, so the gap isn't visible there.
Side note while we're here: TrackedFile.java:37 documents content_type as "0=DATA, 2=EQUALITY_DELETES, 3=DATA_MANIFEST, 4=DELETE_MANIFEST" — 1=POSITION_DELETES is omitted, but this PR's asPositionDeleteFile implies it's valid. Worth updating that doc to match (separate file, but the inconsistency is exposed by this PR).
There was a problem hiding this comment.
I don't believe there is a read-side gap here for v2 positional deletes. These three fields (referencedDataFile, contentOffset, contentSizeInBytes) are v3 DV fields right? TrackedPositionDeleteFile doesn't need to surface them because it is for an actual v2-style positional delete. (which can come from a v2 table upgrade).
V3 DVs can't come through this adapter. V3 DVs live in v3 delete manifests and come out as DeleteFile objects directly from the v3 manifest reader, without any adapter. I was just having an offline conversation with @rdblue about this.
In v4, DVs are always colocated with their data file entries as DeletionVector structs, surfaced through TrackedDVDeleteFile.
Did that make sense?
Good catch on TrackedFile. Fixed.
…leteFile APIs This adapter would allow to minimize the v4 related code changes during scan planning and commits.
fc0f4be to
35fbdd8
Compare
The adapter bridges TrackedFile to existing DataFile/DeleteFile APIs and would allow to minimize the v4 related code changes during scan planning and commits.
Fixes #16221