Hi all,
We ran into a raw-scan behavior we’d like to sanity-check with the
community: should merely attaching a Filter change which cells are returned
by a RAW=true, maxVersions=N scan?
The minimal case is one column containing a newer Put over an older
DeleteColumn:
-
Put@T2
-
DeleteColumn@T1
-
T2 > T1
With RAW=true and maxVersions=1:
-
*No filter:* returns both Put@T2 and DeleteColumn@T1.
The delete marker does not count toward maxVersions.
-
*Any filter attached, even one that includes every cell:* returns only
Put@T2.
The older delete marker is dropped.
-
*maxVersions=all:* both cells are returned again.
Minimal shell repro on branch-2.5:
create 'demo', {
NAME => 'f',
VERSIONS => 2147483647,
KEEP_DELETED_CELLS => 'TRUE'
}
delete 'demo', 'r1', 'f:c', 100
put 'demo', 'r1', 'f:c', 'v', 200
flush 'demo'
scan 'demo', {RAW => true, VERSIONS => 1}
# => Put@200 + DeleteColumn@100
scan 'demo', {
RAW => true,
VERSIONS => 1,
FILTER => "PrefixFilter('r1')"
}
# => Put@200 only
PrefixFilter is only there as a row-matching filter; it includes all cells
in this example. The interesting part is that the presence of a filter
changes the result, rather than anything the filter itself does.
The same code shape appears on master.
Where the difference comes from
For a raw scan, ScanQueryMatcher#getTrackers handles version limits
differently depending on userScan.hasFilter().
With a filter present, HBASE-22710 causes the column tracker’s version cap
to be hoisted to Integer.MAX_VALUE. The requested version limit is then
enforced later in UserScanQueryMatcher#mergeFilterResponse.
That path increments its version counter for every included cell and does
not exclude delete markers. Once the count exceeds versionsAfterFilter —
which, for this raw scan, is the requested maxVersions — it returns
SEEK_NEXT_COL.
So in this path, a delete marker counts as a version.
Without a filter, version enforcement stays in
ScanWildcardColumnTracker#checkVersion, which contains the equivalent of:
if (!PrivateCellUtil.isDelete(type)) {
currentCount++;
}
So in that path, delete markers do not count toward maxVersions.
In other words, the two version-counting paths disagree on whether delete
markers count, and the path is selected purely by hasFilter().
Related context
A few existing issues seem relevant:
-
*HBASE-22710* introduced the hasFilter()-based tracker-cap change. Its
repro used only Puts, so this delete-marker interaction does not appear to
have been exercised.
-
*HBASE-16113* appears to establish the no-filter behavior — delete
markers not counting toward the version limit — as intentional.
-
*HBASE-21596 / HBASE-16322* cover nearby delete/version semantics and
were left Won’t Fix because of compatibility concerns.
Questions
1.
Is it intentional that the returned cell set of a raw scan can change
simply because a cell-including filter is attached?
2.
Is counting delete markers in mergeFilterResponse intentional, or is it
an unintended difference from ScanWildcardColumnTracker#checkVersion?
3.
If these paths should be aligned, would excluding delete markers from
the mergeFilterResponse version counter — matching checkVersion — be the
right fix? Or is there a compatibility concern, similar to HBASE-21596,
that should be considered first?
If this looks like a bug worth fixing, I’m happy to file a JIRA with a
minimal test. We also have a self-contained JUnit minicluster repro in
addition to the shell example above.
Thanks,
Shubham Roy