You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@hbase.apache.org by "ASF GitHub Bot (Jira)" <ji...@apache.org> on 2026/06/14 15:48:00 UTC

[jira] [Updated] (HBASE-30222) DefaultCipherProvider re-parses HBase configuration (hbase-default.xml/hbase-site.xml) on every instantiation

     [ https://issues.apache.org/jira/browse/HBASE-30222?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]

ASF GitHub Bot updated HBASE-30222:
-----------------------------------
    Labels: pull-request-available  (was: )

> DefaultCipherProvider re-parses HBase configuration (hbase-default.xml/hbase-site.xml) on every instantiation
> -------------------------------------------------------------------------------------------------------------
>
>                 Key: HBASE-30222
>                 URL: https://issues.apache.org/jira/browse/HBASE-30222
>             Project: HBase
>          Issue Type: Bug
>          Components: encryption
>    Affects Versions: 3.0.0-beta-1, 4.0.0-alpha-1, 2.5.15, 2.6.6
>            Reporter: Hari Krishna Dara
>            Assignee: Hari Krishna Dara
>            Priority: Major
>              Labels: pull-request-available
>
>   h3. Summary
>   {{DefaultCipherProvider}} initializes its {{conf}} field inline:
>     // DefaultCipherProvider.java
>     private Configuration conf = HBaseConfiguration.create();
>   Every time the provider is constructed, this parses hbase-default.xml and
>   hbase-site.xml off the classpath to build a fresh Configuration. That
>   Configuration is then immediately discarded: providers are constructed via
>   {{Encryption.getCipherProvider(conf)}} -> {{ReflectionUtils.newInstance(class, conf)}},
>   which invokes the no-arg constructor (running the field initializer) and *then*
>   calls {{setConf(conf)}} -- CipherProvider extends Configurable -- overwriting the
>   field with the caller's conf. So the default-config parse performed in the
>   constructor is pure waste on every call.
>   {{Encryption.getCipherProvider(conf)}} constructs a new provider instance on every
>   invocation (it does not reuse the {{getInstance()}} singleton), so the wasteful
>   parse happens on every cipher resolution.
>   h3. Impact
>   Each call to {{Encryption.getCipher(conf, name)}} /
>   {{Encryption.getCipherProvider(conf)}} parses the full HBase configuration. A JMH
>   microbenchmark of a single provider construction measured ~2.3 ms latency and
>   ~1.1 MB of allocation, essentially all of it attributable to
>   {{HBaseConfiguration.create()}} -- confirmed by control benchmarks isolating the
>   config parse from cipher construction and the AES operations. (Microbenchmark on
>   Apple M4 Max / JDK 17; the absolute numbers are environment-dependent, but the
>   attribution -- ~100% of the cost is the config parse -- is the point.)
>   For encryption at rest as it exists today the practical impact is small, because
>   cipher resolution happens only at infrequent, amortized points:
>     - per WAL file: AbstractProtobufLogWriter, SecureProtobufLogReader
>     - per HFile / encryption-context creation: SecurityUtil.createEncryptionContext
>       and related paths
>   These are once-per-file events amortized over many cells, so the cost is real but
>   negligible in normal operation. However, the inefficiency is latent: any code path
>   that resolves ciphers at higher frequency (e.g. per-key or per-row) would pay a
>   full config parse each time, turning a harmless cost into a significant
>   latency/allocation problem. Fixing it removes a foot-gun for current and future
>   callers and eliminates needless GC churn.
>   h3. Root cause
>   {{DefaultCipherProvider}} has no Configuration-accepting constructor, so
>   construction *always* calls {{HBaseConfiguration.create()}}, even though every
>   production caller overwrites that value via {{setConf()}} before use. There is no
>   way to suppress the parse.
>   The {{getInstance()}} singleton is used only by tests (TestAES, TestCommonsAES),
>   both of which already call {{setConf(conf)}} immediately afterward; it is
>   effectively a test-only accessor but is not annotated as such.
>   h3. Proposed fix
>   Remove the inline initializer so construction does not parse config:
>     private Configuration conf;   // set via setConf() before use
>   This is safe for production: {{ReflectionUtils.newInstance()}} always calls
>   {{setConf()}} before the provider is used, and {{getConf()}} is first dereferenced
>   later (in the AES/AES256GCM constructor), by which point conf is set.
>   Additionally:
>     - Annotate {{getInstance()}} as @InterfaceAudience.LimitedPrivate(UNITTEST) (or
>       otherwise document it as test-only), since tests are its only callers and they
>       already set conf explicitly.
>     - Optionally add a fail-fast guard in {{getConf()}}
>       (Preconditions.checkState(conf != null, ...)) so a caller that forgets
>       setConf() fails loudly instead of NPE-ing inside cipher construction --
>       preserving the "always non-null" property the field initializer accidentally
>       provided.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)