Skip to content

HBASE-30225 Performance degradation observed on ycsb reads benchmark after HBASE-2972#8353

Open
wchevreuil wants to merge 1 commit into
apache:masterfrom
wchevreuil:HBASE-30225
Open

HBASE-30225 Performance degradation observed on ycsb reads benchmark after HBASE-2972#8353
wchevreuil wants to merge 1 commit into
apache:masterfrom
wchevreuil:HBASE-30225

Conversation

@wchevreuil

Copy link
Copy Markdown
Contributor

No description provided.

@Apache9 Apache9 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind explaining more about the changes here? I do not fully get what you are trying to fix here...

public BlockCacheKey(Path hfilePath, long offset, boolean isPrimaryReplica, BlockType blockType) {
this(hfilePath.getName(), hfilePath.getParent().getName(),
hfilePath.getParent().getParent().getName(), offset, isPrimaryReplica, blockType,
HFileArchiveUtil.isHFileArchived(hfilePath));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why here we can use false directly?

@wchevreuil wchevreuil Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are actually only using this constructor on tests now. Because the HFileUtils.isArchived call seems too expensive to be performed on every block cache key creation, that check should be delegated to the reader, which can do it once during it's initialisation. Writers don't even need it, since a being written file is guaranteed to be "non-archived", so writers can benefit from constructors that alwas set archived as false. Let me add javadoc to explain this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then why not just remoev this flag in BlockCacheKey if it does not change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might change when it's created by a Reader (see HFileReaderImpl#1352).

this.hfileName = HFILE_NAME_POOL.intern(hfileName);
this.regionName = (regionName != null) ? REGION_NAME_POOL.intern(regionName) : null;
this.cfName = (cfName != null) ? CF_NAME_POOL.intern(cfName) : null;
this.hfileName = hfileName;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do not do intern here, why we still put the above 3 string pools in this class?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are doing intern from readers and writers, we need those to access a single pool per file/region/cf. Since both reader and writer classes already reference BlockCacheKey, and the string re-use is relevant in the BlockCacheKey usage, I found it makes sense to keep the pools in the BlockCacheKey.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not introduce a new class to do this?

Comment thread hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/BlockCacheKey.java Outdated
@wchevreuil

wchevreuil commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

Mind explaining more about the changes here? I do not fully get what you are trying to fix here...

As explained in the jira description:

When executing ycsb read workloads, we observed a ~30% latency degradation (please see flamegrahs attached to the jira).

The problem was that we added logic for parsing the file Path into region name, family name, as well checks for archiving all on the BlockCacheKey constructor used by HFileReaderImpl on the beginning of each block read. As seen on the flame graphs attached covering a five minutes window on one of the RSes, around 30% of the CPU time was spent on the BlockCacheKey constructor, either calling Path.getParent() or HFileUtils.isHFileArchived().

@wchevreuil wchevreuil requested a review from Apache9 June 15, 2026 13:21
@Apache9

Apache9 commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Mind explaining more about the changes here? I do not fully get what you are trying to fix here...

As explained in the jira description:

When executing ycsb read workloads, we observed a ~30% latency degradation (please see flamegrahs attached to the jira).

The problem was that we added logic for parsing the file Path into region name, family name, as well checks for archiving all on the BlockCacheKey constructor used by HFileReaderImpl on the beginning of each block read. As seen on the flame graphs attached covering a five minutes window on one of the RSes, around 30% of the CPU time was spent on the BlockCacheKey constructor, either calling Path.getParent() or HFileUtils.isHFileArchived().

So the intention here, is to not always call intern when creating BlockCacheKey, and move intern to other places?

@wchevreuil

wchevreuil commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

Mind explaining more about the changes here? I do not fully get what you are trying to fix here...

As explained in the jira description:
When executing ycsb read workloads, we observed a ~30% latency degradation (please see flamegrahs attached to the jira).
The problem was that we added logic for parsing the file Path into region name, family name, as well checks for archiving all on the BlockCacheKey constructor used by HFileReaderImpl on the beginning of each block read. As seen on the flame graphs attached covering a five minutes window on one of the RSes, around 30% of the CPU time was spent on the BlockCacheKey constructor, either calling Path.getParent() or HFileUtils.isHFileArchived().

So the intention here, is to not always call intern when creating BlockCacheKey, and move intern to other places?

Sort of. The main problem is not the intern call itself, but the parsing of file name, region and CF name from the path. Doing it inside the BlockCacheKey constructor is too costly, we saw 10% degradation on latency and throughput of ycsb workloads. Moving this parsing to HFileWriterImpl/HFileReaderImpl initialisation means we only need to do it once, as all block cache keys on each writer/reader instance will refer to same values.

We could maybe leave the pools and intern calls private to BlockCacheKey, but we would miss the original parsed strings from getting pooled. Less efficient, but I think it's not a big deal, as we would only be doing it at the reader/writer level. Code-wise, I think it would be cleaner and less confusing. Let me do it.

@VladRodionov VladRodionov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The direction makes sense to me, but I think HFileReaderImpl should also cache the HFile name once.

BlockCacheKey cacheKey =
new BlockCacheKey(path, dataBlockOffset, this.isPrimaryReplicaReader(), expectedBlockType);

BlockCacheKey cacheKey = new BlockCacheKey(path.getName(), family, region, dataBlockOffset,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit off top, I am really surprised that that parsing qualified file name can be expensive at all, but you see that in your benchmarks. Q: why did not you cache file name in this case? path.getName()?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The culprits, as seem by the async profiler flamegraphs, are Path.getParent() and HFileArchiveUtil.isFileArchived(). I didn't think of doing the same for the file name because that didn't seem to cause problems, but yeah, could do it for the sake of code consistency.

* Construct a new BlockCacheKey using a file path. File, column family and region information
* will be extracted from the passed path. This should be used when inserting keys into the cache,
* so that region cache metrics are recorded properly.
* will be extracted from the passed path, so that region cache metrics are recorded properly/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: properly

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.

3 participants