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

Reply via email to