Skip to content

Conversation

@krconv
Copy link

@krconv krconv commented Jan 22, 2026

Summary

Adds RequestAttributesFactory to AsyncTableBuilder for generating request attributes dynamically per-request.

Motivation

It is possible to do this for the HBase 2 Table client by overriding the RpcControllerFactory on the connection, but that doesn't work reliably with AsyncTable because retries happen on Netty threads, losing thread-local context. This change provides a hook that is guaranteed to be called on the initiating thread.

Changes

  • Add RequestAttributesFactory interface with a single create(Map<String, byte[]>) method
  • Add setRequestAttributesFactory() to AsyncTableBuilder
  • Factory is invoked at the start of each operation (get, put, scan, batch, etc.)
  • getRequestAttributes() returns static attributes; factory is only used for actual requests

Usage

AsyncTable<?> table = conn.getTableBuilder(tableName)
  .setRequestAttribute("static.key", value)
  .setRequestAttributesFactory(attrs -> {
    Map<String, byte[]> newAttrs = new HashMap<>(attrs);
    newAttrs.put("dynamic.key", getValueFromThreadLocal());
    return newAttrs;
  })
  .build();

@krconv krconv changed the title HBASE-29850 Add an ability to generate request attributes per request HBASE-29850 Add support for dynamic per-request attributes in AsyncTable Jan 22, 2026
@Apache-HBase

This comment has been minimized.

@Apache-HBase

This comment has been minimized.

@Apache9
Copy link
Contributor

Apache9 commented Jan 23, 2026

So the requirements here is to use different request attribute for different requests?

I think the design of the request attribute API is for static attributes, if we want to support dynamic attribute, we'd better redesign the APIs, like what you propose here, use a factory.

Out of interest, what is your usage for this feature?

Thanks.

@krconv
Copy link
Author

krconv commented Jan 23, 2026

Yes—our requirement is to set a specific request attribute to a value calculated from thread-local context.

We use hbase.quota.user.override.key (which configures throttling to use a request attribute) to throttle based on a logical “upstream caller” rather than the connection user. For example, a high-volume nightly job (EmailJobs-nightlyPurgeJob) calls ObjectsWebService-web, which in this case is the component that actually holds the HBase AsyncTable client and issues deletes to a table objects-1. The service propagates the job identity via thread-local HTTP metadata and sets it as a request attribute so that the job is throttled independently, and can be slowed down without impacting user traffic and other lower-volume jobs. This matters because we share a single AsyncTable per table/JVM—without dynamic attributes, one noisy caller can cause throttling across all traffic handled by that service.

This is exactly what we do with the Table client using the RpcControllerFactory as a workaround, and it works well in our experience. With the AsyncTable though, the RpcControllerFactory is called from various threads during retries, so we don't have a way to propagate any thread-local context reliably.

@Apache9
Copy link
Contributor

Apache9 commented Jan 29, 2026

OK, got it. Seems reasonable.

So what about this:

  1. We add a setRequestAttributeFactory method, as you proposed in this PR, but take a Supplier<Map<String, byte[]>> as parameter.
  2. Introduce a FixedRequestAttributeFactory(not a native English speaker, maybe you can have a better name...) class, which always return the same request attribute map, as the default implementation. Also use Builder pattern for creating this factory class.
  3. Deprecated the old addRequestAttribute method in AsyncTableBuilder, let users use the new setRequestAttributeFactory method. But when users call the old addRequestAttribute method, we internally use FixedRequestAttributeFactory's Builder to create a FixedRequestAttributeFactory in the end.
  4. Do the same for sync client interfaces.

WDYT?

Thanks.

@krconv
Copy link
Author

krconv commented Jan 29, 2026

What do you think of also deprecating AsyncTable#getRequestAttributes() without a replacement, if the behavior of it is no longer well defined (i.e. the return value could change based on which thread it is called from)? I like your suggestion to merge the two request attribute setters on AsyncTableBuilder into one, but the complication I see is that now getRequestAttributes() no longer has any static set of attributes to return. I'm not sure why getRequestAttributes() would be useful to anyone, but nevertheless it might be harder for a user to migrate away from using that.

@Apache9
Copy link
Contributor

Apache9 commented Jan 30, 2026

What do you think of also deprecating AsyncTable#getRequestAttributes() without a replacement, if the behavior of it is no longer well defined (i.e. the return value could change based on which thread it is called from)? I like your suggestion to merge the two request attribute setters on AsyncTableBuilder into one, but the complication I see is that now getRequestAttributes() no longer has any static set of attributes to return. I'm not sure why getRequestAttributes() would be useful to anyone, but nevertheless it might be harder for a user to migrate away from using that.

We can just call request attribute factory's method to get the request attribute for this method? And we can add more javadoc to mention that, since now we allow dynamic request attribute when requesting, the return value may not be the same when you implement a dynamic request attribute factory.

@krconv krconv force-pushed the HBASE-29850-request-attribute-factory branch from 38f36f5 to d708dba Compare January 30, 2026 11:44
Copy link
Author

@krconv krconv left a comment

Choose a reason for hiding this comment

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

I've updated the AsyncTable codepath to reflect the latest suggestions. If this looks good, I'll fix tests and extend to the Table codepath as well. Thank you!

* {@link #setRequestAttributesFactory(RequestAttributesFactory)}.
* @param key the attribute key
* @param value the attribute value
* @deprecated Since 3.0.0, will be removed in 4.0.0. Please use
Copy link
Author

Choose a reason for hiding this comment

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

I think master is 4.0.0; should it get deprecated for a later version or should I remove it from master?

private final Map<String, byte[]> requestAttributes = new HashMap<>();

/**
* Sets a request attribute. If value is null, the attribute is removed.
Copy link
Author

Choose a reason for hiding this comment

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

The underlying Protobuf ByteString will throw a NPE if value is null, so instead this factory removes the entry if it's null.

* @see AsyncTableBuilder#setRequestAttributesFactory(RequestAttributesFactory)
*/
@InterfaceAudience.Public
public final class FixedRequestAttributesFactory implements RequestAttributesFactory {
Copy link
Author

Choose a reason for hiding this comment

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

The other ideas I had were ImmutableRequestAttributesFactory or StaticRequestAttributesFactory, but I think FixedRequestAttributesFactory is most clear because Immutable- might be interpreted as "the returned collection is immutable" instead of that it will return the same collection every time; and Static- clashes with the reserved word in my opinion

* @see AsyncTableBuilder#setRequestAttributesFactory(RequestAttributesFactory)
*/
@InterfaceAudience.Public
public interface RequestAttributesFactory {
Copy link
Author

Choose a reason for hiding this comment

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

I think it still makes sense to have this factory, mainly for documenting it's behavior; open to replacing with a Supplier<String, byte[]> though

@Apache-HBase
Copy link

💔 -1 overall

Vote Subsystem Runtime Logfile Comment
+0 🆗 reexec 1m 33s Docker mode activated.
-0 ⚠️ yetus 0m 3s Unprocessed 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 🆗 mvndep 0m 11s Maven dependency ordering for branch
+1 💚 mvninstall 2m 40s master passed
+1 💚 compile 1m 2s master passed
+1 💚 javadoc 0m 35s master passed
+1 💚 shadedjars 4m 20s branch has no errors when building our shaded downstream artifacts.
_ Patch Compile Tests _
+0 🆗 mvndep 0m 11s Maven dependency ordering for patch
-1 ❌ mvninstall 1m 14s /patch-mvninstall-root.txt root in the patch failed.
-1 ❌ compile 0m 43s /patch-compile-hbase-server.txt hbase-server in the patch failed.
-0 ⚠️ javac 0m 43s /patch-compile-hbase-server.txt hbase-server in the patch failed.
+1 💚 javadoc 0m 34s the patch passed
-1 ❌ shadedjars 3m 12s patch has 24 errors when building our shaded downstream artifacts.
_ Other Tests _
+1 💚 unit 1m 11s hbase-client in the patch passed.
-1 ❌ unit 0m 44s /patch-unit-hbase-server.txt hbase-server in the patch failed.
19m 21s
Subsystem Report/Notes
Docker ClientAPI=1.48 ServerAPI=1.48 base: https://ci-hbase.apache.org/job/HBase-PreCommit-GitHub-PR/job/PR-7665/2/artifact/yetus-jdk17-hadoop3-check/output/Dockerfile
GITHUB PR #7665
Optional Tests javac javadoc unit compile shadedjars
uname Linux 41a285cd464d 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 tool maven
Personality dev-support/hbase-personality.sh
git revision master / d708dba
Default Java Eclipse Adoptium-17.0.11+9
shadedjars https://ci-hbase.apache.org/job/HBase-PreCommit-GitHub-PR/job/PR-7665/2/artifact/yetus-jdk17-hadoop3-check/output/patch-shadedjars.txt
Test Results https://ci-hbase.apache.org/job/HBase-PreCommit-GitHub-PR/job/PR-7665/2/testReport/
Max. process+thread count 286 (vs. ulimit of 30000)
modules C: hbase-client hbase-server U: .
Console output https://ci-hbase.apache.org/job/HBase-PreCommit-GitHub-PR/job/PR-7665/2/console
versions git=2.34.1 maven=3.9.8
Powered by Apache Yetus 0.15.0 https://yetus.apache.org

This message was automatically generated.

@Apache-HBase
Copy link

💔 -1 overall

Vote Subsystem Runtime Logfile Comment
+0 🆗 reexec 0m 29s Docker mode activated.
_ Prechecks _
+1 💚 dupname 0m 0s No case conflicting files found.
+0 🆗 codespell 0m 0s codespell was not available.
+0 🆗 detsecrets 0m 0s detect-secrets was not available.
+1 💚 @author 0m 0s The patch does not contain any @author tags.
+1 💚 hbaseanti 0m 0s Patch does not have any anti-patterns.
_ master Compile Tests _
+0 🆗 mvndep 0m 32s Maven dependency ordering for branch
+1 💚 mvninstall 3m 32s master passed
+1 💚 compile 4m 35s master passed
+1 💚 checkstyle 1m 21s master passed
+1 💚 spotbugs 2m 30s master passed
+1 💚 spotless 0m 50s branch has no errors when running spotless:check.
_ Patch Compile Tests _
+0 🆗 mvndep 0m 11s Maven dependency ordering for patch
-1 ❌ mvninstall 1m 43s /patch-mvninstall-root.txt root in the patch failed.
-1 ❌ compile 1m 53s /patch-compile-hbase-server.txt hbase-server in the patch failed.
-0 ⚠️ javac 1m 53s /patch-compile-hbase-server.txt hbase-server in the patch failed.
+1 💚 blanks 0m 0s The patch has no blanks issues.
+1 💚 checkstyle 1m 15s the patch passed
-1 ❌ spotbugs 0m 50s /patch-spotbugs-hbase-server.txt hbase-server in the patch failed.
-1 ❌ hadoopcheck 2m 8s The patch causes 24 errors with Hadoop v3.3.6.
-1 ❌ hadoopcheck 4m 15s The patch causes 24 errors with Hadoop v3.4.1.
-1 ❌ spotless 0m 18s /patch-spotless.txt patch has 23 errors when running spotless:check, run spotless:apply to fix.
_ Other Tests _
+1 💚 asflicense 0m 15s The patch does not generate ASF License warnings.
27m 56s
Subsystem Report/Notes
Docker ClientAPI=1.43 ServerAPI=1.43 base: https://ci-hbase.apache.org/job/HBase-PreCommit-GitHub-PR/job/PR-7665/2/artifact/yetus-general-check/output/Dockerfile
GITHUB PR #7665
Optional Tests dupname asflicense javac spotbugs checkstyle codespell detsecrets compile hadoopcheck hbaseanti spotless
uname Linux a53f8fc19161 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 tool maven
Personality dev-support/hbase-personality.sh
git revision master / d708dba
Default Java Eclipse Adoptium-17.0.11+9
hadoopcheck https://ci-hbase.apache.org/job/HBase-PreCommit-GitHub-PR/job/PR-7665/2/artifact/yetus-general-check/output/patch-javac-3.3.6.txt
hadoopcheck https://ci-hbase.apache.org/job/HBase-PreCommit-GitHub-PR/job/PR-7665/2/artifact/yetus-general-check/output/patch-javac-3.4.1.txt
Max. process+thread count 87 (vs. ulimit of 30000)
modules C: hbase-client hbase-server U: .
Console output https://ci-hbase.apache.org/job/HBase-PreCommit-GitHub-PR/job/PR-7665/2/console
versions git=2.34.1 maven=3.9.8 spotbugs=4.7.3
Powered by Apache Yetus 0.15.0 https://yetus.apache.org

This message was automatically generated.

@krconv krconv marked this pull request as draft January 30, 2026 13:33
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