Skip to content

HBASE-29585 Add row-level cache for the get operation - #7291

Open
EungsopYoo wants to merge 22 commits into
apache:masterfrom
EungsopYoo:HBASE-29585
Open

HBASE-29585 Add row-level cache for the get operation#7291
EungsopYoo wants to merge 22 commits into
apache:masterfrom
EungsopYoo:HBASE-29585

Conversation

@EungsopYoo

Copy link
Copy Markdown
Contributor

No description provided.

@EungsopYooEungsopYoo changed the title Add row-level cache for the get operationHBASE-29585 Add row-level cache for the get operationSep 10, 2025
@Apache-HBase

This comment has been minimized.

@Apache-HBase

This comment has been minimized.

@Apache-HBase

This comment has been minimized.

@Apache-HBase

This comment has been minimized.

@wchevreuilwchevreuil 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.

This is a great idea, thanks for sharing it. I do have some comments, though:

  1. Can the RowCacheService be an implementation of BlockCache? Maybe a wrapper to the LRUBlockCache. I'm a bit worried about introducing a whole new layer for intercepting all read/write operations at the RPC service with cache specific logic, however this class is not the cache implementation itself. Seems a bit confusing to have a complete separate entry point to the cache.

  2. Are we accepting to have same row data in multiple cache? In the current code, I haven't see any checks to avoid that. Maybe if we implement RowCacheService as a block cache implementation, so that the cache operations happen from the inner layers of the read/write operations, it would be easier to avoid duplication.

  3. Why not simply evict the row that got mutated? I guess we cannot simply override it in the cache because mutation can happen on individual cells.

  4. Are we accepting to have data duplicated over separate caches? I don't see any logic to avoid caching a whole block containing a region for a Get in the L2 cache, still we'll be cache the row in the row cache. Similarly, we might re-cache a row that's in the memstore in the row cache.

  5. One problem of adding such small units (a single row) in the cache is that we need to keep a map index for each entry. So, the smaller the row in size, more rows would fit in the cache, but more key objects would be retained in the map. In your tests, assuming the default block cache size of 40% of the heap, it would give a 12.8GB of block cache. Have you managed to measure the block cache usage by the row cache, in terms of number of rows in the cache, byte size of the L1 cache and the total heap usage? Maybe wort collecting a heapdump to analyse the map index size in the heap.


RegionScannerImpl scanner = getScannerInternal(region, scan, results);

// The row cache is ineffective when the number of store files is small. If the number

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.

Can you elaborate more on this? Is it really a matter of number of files or total store file size? For a single CF table, where a given region, after major compaction, has a 10GB store file, wouldn't this be more efficient?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Get performance is more affected by the number of StoreFiles than by their size. This is because a StoreFileScanner must be created for each StoreFile, and the process of aggregating their results into a single Get result becomes increasingly complex. However, in testing, I found that when there was only one StoreFile, the row cache provided almost no performance benefit. Therefore, I added this condition to prevent the row cache from unnecessarily occupying BlockCache space.

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.

Ahh, right, so the main gain here comes from avoiding the merge of results from different store file scanners. I guess, there could be still benefits on doing this row caching for gets only, even when only having one store file. Say, L2 cache is at capacity already, long client scans could cause evictions for blocks of gets for repeating keys.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Yes, I agree. I’ll remove the condition to cache only when the number of StoreFiles is above a threshold, and always cache the row.

@EungsopYooEungsopYooSep 25, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Fixed in e33db29.

@VladRodionovVladRodionovFeb 13, 2026

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.

Get performance is more affected by the number of StoreFiles than by their size. This is because a StoreFileScanner must be created for each StoreFile, and the process of aggregating their results into a single Get result becomes increasingly complex. However, in testing, I found that when there was only one StoreFile, the row cache provided almost no performance benefit. Therefore, I added this condition to prevent the row cache from unnecessarily occupying BlockCache space.

This is true only when you have a fast I/O access to a whole store file (resides on a server's local NVMe disk or totally cached in RAM). In a disaggregated compute-storage systems (HBase/S3, for example) - this does not hold. Make sure that your tests data is not cached in RAM (limit available RAM per process, flush page cache or have a store file which is much larger than RAM of your computer). Disk-based BucketCache can be wasteful for point queries but there is another obvious benefit of a Row Cache compared to BucketCache: Its Logical vs Physical Cache. BucketCache is a Physical Cache - it caches HFile blocks. RowCache is a Logical Cache - it caches objects. Physical Cache does not survive (or poorly survives) data compactions, Logical Cache is not affected by compactions at all.


private boolean tryGetFromCache(HRegion region, RowCacheKey key, Get get, List<Cell> results) {
RowCells row =
(RowCells) region.getBlockCache().getBlock(key, get.getCacheBlocks(), false, true);

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.

RowCacheKey uses the region encoded name for indexing, whilst BlockCacheKey uses (store file name + offset). If the given row is already cached in a L2 cache block, this call will fail to fetch it and we'll cache it on the L1 too.

@EungsopYooEungsopYooSep 12, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I initially intended for the row cache to reside only in L1 and not be cached in L2, but I haven’t actually implemented that yet. I’ll give further thought to adding this.

@EungsopYoo

Copy link
Copy Markdown
ContributorAuthor

This is a great idea, thanks for sharing it. I do have some comments, though:

Thank you for starting the PR review.

  1. Can the RowCacheService be an implementation of BlockCache? Maybe a wrapper to the LRUBlockCache. I'm a bit worried about introducing a whole new layer for intercepting all read/write operations at the RPC service with cache specific logic, however this class is not the cache implementation itself. Seems a bit confusing to have a complete separate entry point to the cache.

BlockCache operates at the HFile access layer, whereas the row cache needs to function at a higher layer that covers both MemStore and HFile. That’s why I implemented RowCacheService in the RPC service layer.

That said, the row cache does not actually cache HFileBlocks, yet it currently relies on the BlockCache interface. I realize this might not be appropriate. I reused the BlockCache interface to reduce the overhead of creating a separate cache implementation solely for the row cache, but in hindsight, this might not have been the best approach. It may be better to build a dedicated cache implementation specifically for the row cache.

What do you think?

  1. Are we accepting to have same row data in multiple cache? In the current code, I haven't see any checks to avoid that. Maybe if we implement RowCacheService as a block cache implementation, so that the cache operations happen from the inner layers of the read/write operations, it would be easier to avoid duplication.

What exactly does “multiple cache” refer to? Does it mean the L1 and L2 caches in the CombinedBlockCache? If so, I haven’t really considered that aspect yet, but I’ll start looking into it.

  1. Why not simply evict the row that got mutated? I guess we cannot simply override it in the cache because mutation can happen on individual cells.

I didn’t fully understand the intention behind your question. Could you please explain it in more detail?

  1. Are we accepting to have data duplicated over separate caches? I don't see any logic to avoid caching a whole block containing a region for a Get in the L2 cache, still we'll be cache the row in the row cache. Similarly, we might re-cache a row that's in the memstore in the row cache.

This is in the same L1/L2 context as your comment 2, correct? If so, I haven’t considered that aspect yet, but I’ll start thinking about how to handle it.

Since the row cache is only enabled when there are at least two HFiles, rows that exist only in the MemStore are not cached. However, when there are two or more HFiles, rows in MemStore are also added again to the row cache. This is an intentional design choice, aimed at bypassing the process of generating results via SegmentScanner and StoreFileScanner, and instead serving Get requests directly from the cache.

  1. One problem of adding such small units (a single row) in the cache is that we need to keep a map index for each entry. So, the smaller the row in size, more rows would fit in the cache, but more key objects would be retained in the map. In your tests, assuming the default block cache size of 40% of the heap, it would give a 12.8GB of block cache. Have you managed to measure the block cache usage by the row cache, in terms of number of rows in the cache, byte size of the L1 cache and the total heap usage? Maybe wort collecting a heapdump to analyse the map index size in the heap.

I slightly modified the LruBlockCache code to record the row cache size and entry count. The row cache occupies 268.67MB with 338,602 entries. The average size of a single row cache entry is 830 bytes. Within the overall BlockCache, the row cache accounts for 45% by entry count and 2% by size.

2025-09-12T09:08:44,112 INFO [LruBlockCacheStatsExecutor {}] hfile.LruBlockCache: totalSize=12.80 GB, usedSize=12.48 GB, freeSize=329.41 MB, max=12.80 GB, blockCount=752084, accesses=35942999, hits=27403857, hitRatio=76.24%, , cachingAccesses=35942954, cachingHits=27403860, cachingHitsRatio=76.24%, evictions=170, evicted=5806436, evictedPerRun=34155.50588235294, rowBlockCount=338602, rowBlockSize=268.67 MB

@wchevreuil

Copy link
Copy Markdown
Contributor

That said, the row cache does not actually cache HFileBlocks, yet it currently relies on the BlockCache interface. I realize this might not be appropriate. I reused the BlockCache interface to reduce the overhead of creating a separate cache implementation solely for the row cache, but in hindsight, this might not have been the best approach. It may be better to build a dedicated cache implementation specifically for the row cache.

What do you think?

Yeah, I had the same thought while going through the comments. Having a separate cache structure seems the best way to implement this.

  1. Are we accepting to have same row data in multiple cache? In the current code, I haven't see any checks to avoid that. Maybe if we implement RowCacheService as a block cache implementation, so that the cache operations happen from the inner layers of the read/write operations, it would be easier to avoid duplication.

What exactly does “multiple cache” refer to? Does it mean the L1 and L2 caches in the CombinedBlockCache? If so, I haven’t really considered that aspect yet, but I’ll start looking into it.

Nevermind my previous comment. We should focus on the separate cache for rows.

  1. Why not simply evict the row that got mutated? I guess we cannot simply override it in the cache because mutation can happen on individual cells.

I didn’t fully understand the intention behind your question. Could you please explain it in more detail?

Rather than blocking writes to the row cache during updates/bulkload, can we simply make the updates evict/override the row from the cache if it's already there? For puts, we shouldn't need to worry about barries, if we make sure we don't cache the row if it's in the memstore only, but we should to make sure to remove it from the row cache because the cache would now be stale. For bulkloads, I guess we only need to make sure to evict the rows for affected regions after the bulkload has been committed.

  1. Are we accepting to have data duplicated over separate caches? I don't see any logic to avoid caching a whole block containing a region for a Get in the L2 cache, still we'll be cache the row in the row cache. Similarly, we might re-cache a row that's in the memstore in the row cache.

This is in the same L1/L2 context as your comment 2, correct? If so, I haven’t considered that aspect yet, but I’ll start thinking about how to handle it.

Since the row cache is only enabled when there are at least two HFiles, rows that exist only in the MemStore are not cached. However, when there are two or more HFiles, rows in MemStore are also added again to the row cache. This is an intentional design choice, aimed at bypassing the process of generating results via SegmentScanner and StoreFileScanner, and instead serving Get requests directly from the cache.

Per other comments, agree it's fine to have the row in the row cache and its' block also in the block cache. We need to decide if we want to add blocks to the block cache when doing Get, or Get should cache only in the row cache? Also, should we avoid caching if the row is the memstore? Could be challenging in the current design of caching the whole row, because memstore migh have only updates for few cells within a row.

  1. One problem of adding such small units (a single row) in the cache is that we need to keep a map index for each entry. So, the smaller the row in size, more rows would fit in the cache, but more key objects would be retained in the map. In your tests, assuming the default block cache size of 40% of the heap, it would give a 12.8GB of block cache. Have you managed to measure the block cache usage by the row cache, in terms of number of rows in the cache, byte size of the L1 cache and the total heap usage? Maybe wort collecting a heapdump to analyse the map index size in the heap.

I slightly modified the LruBlockCache code to record the row cache size and entry count. The row cache occupies 268.67MB with 338,602 entries. The average size of a single row cache entry is 830 bytes. Within the overall BlockCache, the row cache accounts for 45% by entry count and 2% by size.

2025-09-12T09:08:44,112 INFO [LruBlockCacheStatsExecutor {}] hfile.LruBlockCache: totalSize=12.80 GB, usedSize=12.48 GB, freeSize=329.41 MB, max=12.80 GB, blockCount=752084, accesses=35942999, hits=27403857, hitRatio=76.24%, , cachingAccesses=35942954, cachingHits=27403860, cachingHitsRatio=76.24%, evictions=170, evicted=5806436, evictedPerRun=34155.50588235294, rowBlockCount=338602, rowBlockSize=268.67 MB

What if more rows get cached, over time, as more gets for different rows are executed? It could lead to many rows in the cache, and many more objects in the map to index it. In the recent past. we've seen some heap issues when having very large file based bucket cache and small compressed blocks. I guess we could face similar problems here too.

@Apache9

Copy link
Copy Markdown
Contributor

The design doc looks good. Skimmed the code, seems we put row cache into block cache? Minding explaining more on why we choose to use block cache to implement row cache? What is the benefit?

Thanks.

@EungsopYoo

Copy link
Copy Markdown
ContributorAuthor
  1. Why not simply evict the row that got mutated? I guess we cannot simply override it in the cache because mutation can happen on individual cells.

I didn’t fully understand the intention behind your question. Could you please explain it in more detail?

Rather than blocking writes to the row cache during updates/bulkload, can we simply make the updates evict/override the row from the cache if it's already there? For puts, we shouldn't need to worry about barries, if we make sure we don't cache the row if it's in the memstore only, but we should to make sure to remove it from the row cache because the cache would now be stale. For bulkloads, I guess we only need to make sure to evict the rows for affected regions after the bulkload has been committed.

When the data exists in both the MemStore and the StoreFiles, we need to store it in the row cache to avoid result merging. In that case, due to the following issues, a barrier was introduced.

ThreadTime 1Time 2Time 3Time 4
th1delete row1 from RowCachePut row1 to Regionwrite row1 to RowCache
th2delete row1 from RowCachePut row1 to Regionwrite row1 to RowCache
th3Get for row1 not from RowCache. GoodGet for row1 not from RowCache. GoodGet for row1 from stale RowCache. BadGet for row1 not from RowCache. Good

It would be more efficient to do as you mentioned when doing a bulkload.

  1. Are we accepting to have data duplicated over separate caches? I don't see any logic to avoid caching a whole block containing a region for a Get in the L2 cache, still we'll be cache the row in the row cache. Similarly, we might re-cache a row that's in the memstore in the row cache.

This is in the same L1/L2 context as your comment 2, correct? If so, I haven’t considered that aspect yet, but I’ll start thinking about how to handle it.
Since the row cache is only enabled when there are at least two HFiles, rows that exist only in the MemStore are not cached. However, when there are two or more HFiles, rows in MemStore are also added again to the row cache. This is an intentional design choice, aimed at bypassing the process of generating results via SegmentScanner and StoreFileScanner, and instead serving Get requests directly from the cache.

Per other comments, agree it's fine to have the row in the row cache and its' block also in the block cache. We need to decide if we want to add blocks to the block cache when doing Get, or Get should cache only in the row cache? Also, should we avoid caching if the row is the memstore? Could be challenging in the current design of caching the whole row, because memstore migh have only updates for few cells within a row.

I already answered this in another comment, but I’ll respond here as well.

I think it’s better to put it into the BlockCache when doing a Get, according to the BlockCache setting.

It is more efficient not to create a row cache when the cells to be fetched exist only in the MemStore. However, if the cells to be fetched are in both the MemStore and the StoreFiles, then creating a row cache is efficient to avoid result merging.

I’ll give some more thought on how we can achieve this.

  1. One problem of adding such small units (a single row) in the cache is that we need to keep a map index for each entry. So, the smaller the row in size, more rows would fit in the cache, but more key objects would be retained in the map. In your tests, assuming the default block cache size of 40% of the heap, it would give a 12.8GB of block cache. Have you managed to measure the block cache usage by the row cache, in terms of number of rows in the cache, byte size of the L1 cache and the total heap usage? Maybe wort collecting a heapdump to analyse the map index size in the heap.

I slightly modified the LruBlockCache code to record the row cache size and entry count. The row cache occupies 268.67MB with 338,602 entries. The average size of a single row cache entry is 830 bytes. Within the overall BlockCache, the row cache accounts for 45% by entry count and 2% by size.

2025-09-12T09:08:44,112 INFO [LruBlockCacheStatsExecutor {}] hfile.LruBlockCache: totalSize=12.80 GB, usedSize=12.48 GB, freeSize=329.41 MB, max=12.80 GB, blockCount=752084, accesses=35942999, hits=27403857, hitRatio=76.24%, , cachingAccesses=35942954, cachingHits=27403860, cachingHitsRatio=76.24%, evictions=170, evicted=5806436, evictedPerRun=34155.50588235294, rowBlockCount=338602, rowBlockSize=268.67 MB

What if more rows get cached, over time, as more gets for different rows are executed? It could lead to many rows in the cache, and many more objects in the map to index it. In the recent past. we've seen some heap issues when having very large file based bucket cache and small compressed blocks. I guess we could face similar problems here too.

Okay. Then I’ll take a heap dump and check the size of the map’s index.

@EungsopYoo

Copy link
Copy Markdown
ContributorAuthor

The design doc looks good. Skimmed the code, seems we put row cache into block cache? Minding explaining more on why we choose to use block cache to implement row cache? What is the benefit?

Thanks.

I did it that way because the implementation was simpler. However, it causes confusion and makes it harder to have clear control over the row cache, so I’ve decided to create a separate RowCache implementation.

@EungsopYoo

EungsopYoo commented Sep 15, 2025

Copy link
Copy Markdown
ContributorAuthor

The TODOs are as follows, and I will proceed in order:

  • Separate the row cache implementation
  • Remove the condition that decides whether to put data into the row cache based on the number of StoreFiles
  • Do not use the row cache when the data exists only in the MemStore
  • Invalidate only the row cache of regions that were bulkloaded
  • Take a heap dump to check the index size of the map

@Apache-HBase

This comment has been minimized.

@Apache-HBase

This comment has been minimized.

@Apache-HBase

This comment has been minimized.

@Apache-HBase

This comment has been minimized.

@Apache-HBase

This comment has been minimized.

@Apache-HBase

This comment has been minimized.

- Implement RowCache
- Initially considered modifying LruBlockCache, but the required changes were extensive.
Instead, implemented RowCache using Caffeine cache.
- Add row.cache.size configuration
- Default is 0.0 (disabled); RowCache is enabled only if explicitly set to a value > 0.
- The combined size of BlockCache + MemStore + RowCache must not exceed 80% of the heap.
- Add Row Cache tab to RegionServer Block Cache UI
- RowCache is not a BlockCache, but added here since there is no better place.
- Add RowCache metrics
- Metrics for size, count, eviction, hit, and miss are now exposed.
@Apache-HBase

This comment has been minimized.

@Apache-HBase

This comment has been minimized.

@Apache-HBase

This comment has been minimized.

@EungsopYoo

EungsopYoo commented Sep 25, 2025

Copy link
Copy Markdown
ContributorAuthor
  1. One problem of adding such small units (a single row) in the cache is that we need to keep a map index for each entry. So, the smaller the row in size, more rows would fit in the cache, but more key objects would be retained in the map. In your tests, assuming the default block cache size of 40% of the heap, it would give a 12.8GB of block cache. Have you managed to measure the block cache usage by the row cache, in terms of number of rows in the cache, byte size of the L1 cache and the total heap usage? Maybe wort collecting a heapdump to analyse the map index size in the heap.

I slightly modified the LruBlockCache code to record the row cache size and entry count. The row cache occupies 268.67MB with 338,602 entries. The average size of a single row cache entry is 830 bytes. Within the overall BlockCache, the row cache accounts for 45% by entry count and 2% by size.

2025-09-12T09:08:44,112 INFO [LruBlockCacheStatsExecutor {}] hfile.LruBlockCache: totalSize=12.80 GB, usedSize=12.48 GB, freeSize=329.41 MB, max=12.80 GB, blockCount=752084, accesses=35942999, hits=27403857, hitRatio=76.24%, , cachingAccesses=35942954, cachingHits=27403860, cachingHitsRatio=76.24%, evictions=170, evicted=5806436, evictedPerRun=34155.50588235294, rowBlockCount=338602, rowBlockSize=268.67 MB

What if more rows get cached, over time, as more gets for different rows are executed? It could lead to many rows in the cache, and many more objects in the map to index it. In the recent past. we've seen some heap issues when having very large file based bucket cache and small compressed blocks. I guess we could face similar problems here too.

Okay. Then I’ll take a heap dump and check the size of the map’s index.

I configured the RegionServer with a 4 GB heap, setting hfile.block.cache.size to 0.3 and row.cache.size to 0.1, then reran the same workload as before. Under these settings, the maximum RowCache capacity is approximately 400 MB. After the RowCache was fully populated, I generated and analyzed a heap dump.

  • RowCache Size: 409 MB
  • RowCache Count: 697,234 entries
  • Average RowCache Entry Size: 615 B
    • This is reduced from 830 B previously, mainly due to a simplified RowCacheKey.
  • Retained Heap Size: 622 MB
    • Because of the overhead associated with Caffeine’s key/value structures, the retained size on heap amounts to 52% more than the actual data size for this workload.
    • I believe this is acceptable if the RowCache size is configured relatively smaller than the BlockCache, for example, around 2% of the BlockCache size. The positive impact of RowCache is already noticeable even at this smaller capacity.

@Apache-HBase

This comment has been minimized.

@Apache-HBase

This comment has been minimized.

@Apache-HBase

This comment has been minimized.

@Apache-HBase

This comment has been minimized.

@Apache-HBase

This comment has been minimized.

@Apache-HBase

This comment has been minimized.

@Apache-HBase

Copy link
Copy Markdown

🎊 +1 overall

VoteSubsystemRuntimeLogfileComment
+0 🆗reexec0m 38sDocker mode activated.
_ Prechecks _
+1 💚dupname0m 0sNo case conflicting files found.
+0 🆗codespell0m 0scodespell was not available.
+0 🆗detsecrets0m 0sdetect-secrets was not available.
+1 💚@author0m 0sThe patch does not contain any @author tags.
+1 💚hbaseanti0m 0sPatch does not have any anti-patterns.
_ master Compile Tests _
+0 🆗mvndep0m 52sMaven dependency ordering for branch
+1 💚mvninstall3m 36smaster passed
+1 💚compile8m 36smaster passed
+1 💚checkstyle1m 13smaster passed
+1 💚spotbugs10m 52smaster passed
+0 🆗refguide2m 43sbranch has no errors when building the reference guide. See footer for rendered docs, which you should manually inspect.
+1 💚spotless0m 49sbranch has no errors when running spotless:check.
-0 ⚠️patch1m 24sUsed diff version of patch file. Binary files and potentially other changes not applied. Please rebase and squash commits if necessary.
_ Patch Compile Tests _
+0 🆗mvndep0m 16sMaven dependency ordering for patch
+1 💚mvninstall3m 12sthe patch passed
+1 💚compile8m 37sthe patch passed
+1 💚javac8m 37sthe patch passed
+1 💚blanks0m 0sThe patch has no blanks issues.
-0 ⚠️checkstyle0m 33s/buildtool-patch-checkstyle-root.txtThe patch fails to run checkstyle in root
-0 ⚠️rubocop0m 15s/results-rubocop.txtThe patch generated 2 new + 411 unchanged - 0 fixed = 413 total (was 411)
+1 💚xmllint0m 0sNo new issues.
+1 💚spotbugs11m 28sthe patch passed
+0 🆗refguide2m 9spatch has no errors when building the reference guide. See footer for rendered docs, which you should manually inspect.
+1 💚hadoopcheck12m 31sPatch does not cause any errors with Hadoop 3.3.6 3.4.1.
+1 💚spotless0m 46spatch has no errors when running spotless:check.
_ Other Tests _
+1 💚asflicense0m 55sThe patch does not generate ASF License warnings.
78m 59s
SubsystemReport/Notes
DockerClientAPI=1.43 ServerAPI=1.43 base: https://ci-hbase.apache.org/job/HBase-PreCommit-GitHub-PR/job/PR-7291/14/artifact/yetus-general-check/output/Dockerfile
GITHUB PR#7291
Optional Testsdupname asflicense javac spotbugs checkstyle codespell detsecrets compile hadoopcheck hbaseanti spotless xmllint refguide rubocop
unameLinux b239b0df022b 5.4.0-1103-aws #111~18.04.1-Ubuntu SMP Tue May 23 20:04:10 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
Build toolmaven
Personalitydev-support/hbase-personality.sh
git revisionmaster / 5dad021
Default JavaEclipse Adoptium-17.0.11+9
refguidehttps://nightlies.apache.org/hbase/HBase-PreCommit-GitHub-PR/PR-7291/14/yetus-general-check/output/branch-site/book.html
refguidehttps://nightlies.apache.org/hbase/HBase-PreCommit-GitHub-PR/PR-7291/14/yetus-general-check/output/patch-site/book.html
Max. process+thread count189 (vs. ulimit of 30000)
modulesC: hbase-common hbase-hadoop-compat hbase-client hbase-server hbase-shell . U: .
Console outputhttps://ci-hbase.apache.org/job/HBase-PreCommit-GitHub-PR/job/PR-7291/14/console
versionsgit=2.34.1 maven=3.9.8 spotbugs=4.7.3 rubocop=1.37.1 xmllint=20913
Powered byApache Yetus 0.15.0 https://yetus.apache.org

This message was automatically generated.

@Apache-HBase

Copy link
Copy Markdown

🎊 +1 overall

VoteSubsystemRuntimeLogfileComment
+0 🆗reexec0m 31sDocker mode activated.
-0 ⚠️yetus0m 4sUnprocessed flag(s): --brief-report-file --spotbugs-strict-precheck --author-ignore-list --blanks-eol-ignore-file --blanks-tabs-ignore-file --quick-hadoopcheck
_ Prechecks _
_ master Compile Tests _
+0 🆗mvndep0m 41sMaven dependency ordering for branch
+1 💚mvninstall3m 34smaster passed
+1 💚compile2m 14smaster passed
+1 💚javadoc3m 15smaster passed
+1 💚shadedjars6m 15sbranch has no errors when building our shaded downstream artifacts.
-0 ⚠️patch6m 56sUsed diff version of patch file. Binary files and potentially other changes not applied. Please rebase and squash commits if necessary.
_ Patch Compile Tests _
+0 🆗mvndep0m 16sMaven dependency ordering for patch
+1 💚mvninstall3m 10sthe patch passed
+1 💚compile2m 17sthe patch passed
+1 💚javac2m 17sthe patch passed
+1 💚javadoc3m 16sthe patch passed
+1 💚shadedjars6m 11spatch has no errors when building our shaded downstream artifacts.
_ Other Tests _
+1 💚unit311m 7sroot in the patch passed.
350m 58s
SubsystemReport/Notes
DockerClientAPI=1.43 ServerAPI=1.43 base: https://ci-hbase.apache.org/job/HBase-PreCommit-GitHub-PR/job/PR-7291/14/artifact/yetus-jdk17-hadoop3-check/output/Dockerfile
GITHUB PR#7291
Optional Testsjavac javadoc unit compile shadedjars
unameLinux 43b3bd1d7b5e 5.4.0-1103-aws #111~18.04.1-Ubuntu SMP Tue May 23 20:04:10 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
Build toolmaven
Personalitydev-support/hbase-personality.sh
git revisionmaster / 5dad021
Default JavaEclipse Adoptium-17.0.11+9
Test Resultshttps://ci-hbase.apache.org/job/HBase-PreCommit-GitHub-PR/job/PR-7291/14/testReport/
Max. process+thread count8062 (vs. ulimit of 30000)
modulesC: hbase-common hbase-hadoop-compat hbase-client hbase-server hbase-shell . U: .
Console outputhttps://ci-hbase.apache.org/job/HBase-PreCommit-GitHub-PR/job/PR-7291/14/console
versionsgit=2.34.1 maven=3.9.8
Powered byApache Yetus 0.15.0 https://yetus.apache.org

This message was automatically generated.

@wchevreuil

Copy link
Copy Markdown
Contributor

@Apache9 OK. I’ll create a feature branch and develop the work on sub-branches, merging them step by step. It seems someone who has permission to create branches should make the feature branch, right?

@wchevreuil I’ll address the review comments in a new branch.

I've created a new branch labeled HBASE-29585. @EungsopYoo, please open PRs on that branch. If we are planning to break down this feature development into more granular tasks, please open subtask jira tickets under the original HBASE-29585 one.

@EungsopYoo

Copy link
Copy Markdown
ContributorAuthor

@wchevreuil
I've opened a new PR. Please review it.
#7398

@Apache-HBase

Copy link
Copy Markdown

🎊 +1 overall

VoteSubsystemRuntimeLogfileComment
+0 🆗reexec0m 12sDocker mode activated.
_ Prechecks _
+1 💚dupname0m 1sNo case conflicting files found.
+0 🆗codespell0m 0scodespell was not available.
+0 🆗detsecrets0m 0sdetect-secrets was not available.
+1 💚@author0m 0sThe patch does not contain any @author tags.
+1 💚hbaseanti0m 0sPatch does not have any anti-patterns.
_ master Compile Tests _
+0 🆗mvndep0m 24sMaven dependency ordering for branch
+1 💚mvninstall4m 49smaster passed
+1 💚compile13m 17smaster passed
+1 💚checkstyle2m 34smaster passed
+1 💚spotbugs15m 46smaster passed
+0 🆗refguide3m 20sbranch has no errors when building the reference guide. See footer for rendered docs, which you should manually inspect.
+1 💚spotless1m 5sbranch has no errors when running spotless:check.
-0 ⚠️patch1m 46sUsed diff version of patch file. Binary files and potentially other changes not applied. Please rebase and squash commits if necessary.
_ Patch Compile Tests _
+0 🆗mvndep0m 18sMaven dependency ordering for patch
+1 💚mvninstall4m 40sthe patch passed
+1 💚compile13m 38sthe patch passed
+1 💚javac13m 38sthe patch passed
+1 💚blanks0m 0sThe patch has no blanks issues.
-0 ⚠️checkstyle2m 39s/results-checkstyle-root.txtroot: The patch generated 6 new + 27 unchanged - 0 fixed = 33 total (was 27)
-0 ⚠️rubocop0m 6s/results-rubocop.txtThe patch generated 2 new + 411 unchanged - 0 fixed = 413 total (was 411)
+1 💚xmllint0m 0sNo new issues.
+1 💚spotbugs17m 10sthe patch passed
+0 🆗refguide3m 2spatch has no errors when building the reference guide. See footer for rendered docs, which you should manually inspect.
+1 💚hadoopcheck14m 9sPatch does not cause any errors with Hadoop 3.3.6 3.4.1.
+1 💚spotless0m 57spatch has no errors when running spotless:check.
_ Other Tests _
+1 💚asflicense1m 3sThe patch does not generate ASF License warnings.
109m 3s
SubsystemReport/Notes
DockerClientAPI=1.48 ServerAPI=1.48 base: https://ci-hbase.apache.org/job/HBase-PreCommit-GitHub-PR/job/PR-7291/17/artifact/yetus-general-check/output/Dockerfile
GITHUB PR#7291
Optional Testsdupname asflicense javac spotbugs checkstyle codespell detsecrets compile hadoopcheck hbaseanti spotless xmllint refguide rubocop
unameLinux a49162c0b9f9 6.8.0-1024-aws #26~22.04.1-Ubuntu SMP Wed Feb 19 06:54:57 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
Build toolmaven
Personalitydev-support/hbase-personality.sh
git revisionmaster / 7cc5d2a
Default JavaEclipse Adoptium-17.0.11+9
refguidehttps://nightlies.apache.org/hbase/HBase-PreCommit-GitHub-PR/PR-7291/17/yetus-general-check/output/branch-site/book.html
refguidehttps://nightlies.apache.org/hbase/HBase-PreCommit-GitHub-PR/PR-7291/17/yetus-general-check/output/patch-site/book.html
Max. process+thread count162 (vs. ulimit of 30000)
modulesC: hbase-common hbase-hadoop-compat hbase-client hbase-server hbase-shell . U: .
Console outputhttps://ci-hbase.apache.org/job/HBase-PreCommit-GitHub-PR/job/PR-7291/17/console
versionsgit=2.34.1 maven=3.9.8 spotbugs=4.7.3 rubocop=1.37.1 xmllint=20913
Powered byApache Yetus 0.15.0 https://yetus.apache.org

This message was automatically generated.

@Apache-HBase

Copy link
Copy Markdown

💔 -1 overall

VoteSubsystemRuntimeLogfileComment
+0 🆗reexec0m 12sDocker mode activated.
-0 ⚠️yetus0m 4sUnprocessed flag(s): --brief-report-file --spotbugs-strict-precheck --author-ignore-list --blanks-eol-ignore-file --blanks-tabs-ignore-file --quick-hadoopcheck
_ Prechecks _
_ master Compile Tests _
+0 🆗mvndep0m 22sMaven dependency ordering for branch
+1 💚mvninstall4m 34smaster passed
+1 💚compile2m 55smaster passed
+1 💚javadoc4m 29smaster passed
+1 💚shadedjars6m 40sbranch has no errors when building our shaded downstream artifacts.
-0 ⚠️patch7m 17sUsed diff version of patch file. Binary files and potentially other changes not applied. Please rebase and squash commits if necessary.
_ Patch Compile Tests _
+0 🆗mvndep0m 16sMaven dependency ordering for patch
+1 💚mvninstall4m 13sthe patch passed
+1 💚compile3m 4sthe patch passed
+1 💚javac3m 4sthe patch passed
+1 💚javadoc4m 30sthe patch passed
+1 💚shadedjars6m 43spatch has no errors when building our shaded downstream artifacts.
_ Other Tests _
-1 ❌unit486m 23s/patch-unit-root.txtroot in the patch failed.
530m 4s
SubsystemReport/Notes
DockerClientAPI=1.48 ServerAPI=1.48 base: https://ci-hbase.apache.org/job/HBase-PreCommit-GitHub-PR/job/PR-7291/17/artifact/yetus-jdk17-hadoop3-check/output/Dockerfile
GITHUB PR#7291
Optional Testsjavac javadoc unit compile shadedjars
unameLinux 9aa6c025319d 6.8.0-1024-aws #26~22.04.1-Ubuntu SMP Wed Feb 19 06:54:57 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
Build toolmaven
Personalitydev-support/hbase-personality.sh
git revisionmaster / 7e2718a
Default JavaEclipse Adoptium-17.0.11+9
Test Resultshttps://ci-hbase.apache.org/job/HBase-PreCommit-GitHub-PR/job/PR-7291/17/testReport/
Max. process+thread count3225 (vs. ulimit of 30000)
modulesC: hbase-common hbase-hadoop-compat hbase-client hbase-server hbase-shell . U: .
Console outputhttps://ci-hbase.apache.org/job/HBase-PreCommit-GitHub-PR/job/PR-7291/17/console
versionsgit=2.34.1 maven=3.9.8
Powered byApache Yetus 0.15.0 https://yetus.apache.org

This message was automatically generated.

@Apache-HBase

Copy link
Copy Markdown

🎊 +1 overall

VoteSubsystemRuntimeLogfileComment
+0 🆗reexec0m 15sDocker mode activated.
_ Prechecks _
+1 💚dupname0m 1sNo case conflicting files found.
+0 🆗codespell0m 0scodespell was not available.
+0 🆗detsecrets0m 0sdetect-secrets was not available.
+1 💚@author0m 0sThe patch does not contain any @author tags.
+1 💚hbaseanti0m 0sPatch does not have any anti-patterns.
_ master Compile Tests _
+0 🆗mvndep0m 33sMaven dependency ordering for branch
+1 💚mvninstall5m 55smaster passed
+1 💚compile16m 18smaster passed
+1 💚checkstyle3m 11smaster passed
+1 💚spotbugs19m 26smaster passed
+0 🆗refguide3m 59sbranch has no errors when building the reference guide. See footer for rendered docs, which you should manually inspect.
+1 💚spotless1m 13sbranch has no errors when running spotless:check.
-0 ⚠️patch1m 59sUsed diff version of patch file. Binary files and potentially other changes not applied. Please rebase and squash commits if necessary.
_ Patch Compile Tests _
+0 🆗mvndep0m 20sMaven dependency ordering for patch
+1 💚mvninstall4m 57sthe patch passed
+1 💚compile14m 40sthe patch passed
+1 💚javac14m 40sthe patch passed
+1 💚blanks0m 0sThe patch has no blanks issues.
-0 ⚠️checkstyle2m 50s/results-checkstyle-root.txtroot: The patch generated 6 new + 27 unchanged - 0 fixed = 33 total (was 27)
-0 ⚠️rubocop0m 8s/results-rubocop.txtThe patch generated 2 new + 411 unchanged - 0 fixed = 413 total (was 411)
+1 💚xmllint0m 0sNo new issues.
+1 💚spotbugs19m 6sthe patch passed
+0 🆗refguide3m 43spatch has no errors when building the reference guide. See footer for rendered docs, which you should manually inspect.
+1 💚hadoopcheck16m 54sPatch does not cause any errors with Hadoop 3.3.6 3.4.1.
+1 💚spotless1m 5spatch has no errors when running spotless:check.
_ Other Tests _
+1 💚asflicense1m 20sThe patch does not generate ASF License warnings.
127m 52s
SubsystemReport/Notes
DockerClientAPI=1.48 ServerAPI=1.48 base: https://ci-hbase.apache.org/job/HBase-PreCommit-GitHub-PR/job/PR-7291/18/artifact/yetus-general-check/output/Dockerfile
GITHUB PR#7291
Optional Testsdupname asflicense javac spotbugs checkstyle codespell detsecrets compile hadoopcheck hbaseanti spotless xmllint refguide rubocop
unameLinux 5875f72c3f0e 6.8.0-1024-aws #26~22.04.1-Ubuntu SMP Wed Feb 19 06:54:57 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
Build toolmaven
Personalitydev-support/hbase-personality.sh
git revisionmaster / 7e2718a
Default JavaEclipse Adoptium-17.0.11+9
refguidehttps://nightlies.apache.org/hbase/HBase-PreCommit-GitHub-PR/PR-7291/18/yetus-general-check/output/branch-site/book.html
refguidehttps://nightlies.apache.org/hbase/HBase-PreCommit-GitHub-PR/PR-7291/18/yetus-general-check/output/patch-site/book.html
Max. process+thread count163 (vs. ulimit of 30000)
modulesC: hbase-common hbase-hadoop-compat hbase-client hbase-server hbase-shell . U: .
Console outputhttps://ci-hbase.apache.org/job/HBase-PreCommit-GitHub-PR/job/PR-7291/18/console
versionsgit=2.34.1 maven=3.9.8 spotbugs=4.7.3 rubocop=1.37.1 xmllint=20913
Powered byApache Yetus 0.15.0 https://yetus.apache.org

This message was automatically generated.

@VladRodionov

Copy link
Copy Markdown
Contributor

While reading through the issue and thinking about possible design trade-offs, a few questions came to mind that may be worth considering as the implementation evolves:

  • Heap pressure: using Java-heap–resident structures (or reusing BucketCache metadata paths) may increase GC pressure under some workloads. ZGC certainly helps here, but I’m curious how this is expected to behave for row-sized objects with high churn.
  • Metadata overhead: for cache entries on the order of a single row, the relative metadata overhead can become significant compared to the payload. It would be interesting to understand how this is being evaluated or measured in the current approach.
  • Sparse row access: some applications primarily care about a subset of row data (for example, only the latest versions of selected cells). Google Bigtable’s row cache supports sparse rows — are similar access patterns in scope here, or is the focus on full-row caching?
  • In-place mutation: supporting sparse rows often implies updating cached entries in place so the cache always reflects the most recent version of the row. I’m curious whether this is within the intended scope or something to consider later.

These aren’t meant as blockers — just questions around the design space and trade-offs. Looking forward to following the progress on this issue.

@EungsopYoo

Copy link
Copy Markdown
ContributorAuthor

@VladRodionov
Thank you for your comments. I’d like to respond to the questions you raised.

  • Heap pressure: using Java-heap–resident structures (or reusing BucketCache metadata paths) may increase GC pressure under some workloads. ZGC certainly helps here, but I’m curious how this is expected to behave for row-sized objects with high churn.

Although it will be clearer once we run some tests, under high-churn workloads I expect RowCache, like BlockCache, to experience significant GC pressure. In such cases, it would likely be more appropriate not to use RowCache.

  • Metadata overhead: for cache entries on the order of a single row, the relative metadata overhead can become significant compared to the payload. It would be interesting to understand how this is being evaluated or measured in the current approach.

That’s correct. Because the overhead is large, I plan to set the default size of RowCache to 2% of the heap, which is relatively much smaller than the 40% heap size typically allocated to BlockCache.

  • Sparse row access: some applications primarily care about a subset of row data (for example, only the latest versions of selected cells). Google Bigtable’s row cache supports sparse rows — are similar access patterns in scope here, or is the focus on full-row caching?
  • In-place mutation: supporting sparse rows often implies updating cached entries in place so the cache always reflects the most recent version of the row. I’m curious whether this is within the intended scope or something to consider later.

In this PR, I am not considering caching for sparse rows and am focusing only on caching full rows. Support for sparse row caching can be considered later.

Sign up for freeto 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.

6 participants

@EungsopYoo@Apache-HBase@wchevreuil@Apache9@VladRodionov@terence-yoo