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

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

> ExceedOperationQuota passes writeCapacityUnitConsumed as the readCapacityUnit 
> argument of grabQuota
> ---------------------------------------------------------------------------------------------------
>
>                 Key: HBASE-30485
>                 URL: https://issues.apache.org/jira/browse/HBASE-30485
>             Project: HBase
>          Issue Type: Bug
>          Components: Quotas
>    Affects Versions: 3.0.0-beta-2, 2.6.6
>            Reporter: Dimitri BEKALE
>            Priority: Major
>              Labels: pull-request-available
>
> When exceed-throttle is enabled (\{{Admin.exceedThrottleQuotaSwitch(true)}}),
> {\{RegionServerRpcQuotaManager}} uses \{{ExceedOperationQuota}}. Its 
> \{{checkQuota}} method debits
> the limiters with \{{grabQuota}}, and both calls pass the write capacity-unit 
> estimate in the read
> capacity-unit position
> ([ExceedOperationQuota.java:97-104|https://github.com/apache/hbase/blob/09474ab77bfa8bede96131164fd4451f093a1a10/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/ExceedOperationQuota.java#L97-L104]):
> {code:java}
> regionServerLimiter.grabQuota(numWrites, writeConsumed, numReads + numScans, 
> readConsumed,
>   writeCapacityUnitConsumed, writeCapacityUnitConsumed, isAtomic, 
> handlerUsageTimeConsumed);
> ...
> limiter.grabQuota(numWrites, writeConsumed, numReads + numScans, readConsumed,
>   writeCapacityUnitConsumed, writeCapacityUnitConsumed, isAtomic, 0L);
> {code}
> The signature is {{grabQuota(writeReqs, writeSize, readReqs, readSize, 
> writeCapacityUnit,
> readCapacityUnit, isAtomic, estimateHandlerThreadUsageMs)}} 
> (\{{QuotaLimiter.java:70}}): the fifth
> and sixth arguments are the write and read capacity units. The
> other three call sites in the same class hierarchy pass 
> \{{readCapacityUnitConsumed}} there:
> {\{ExceedOperationQuota.checkQuota}} step 2 (line 82), 
> \{{DefaultOperationQuota.checkQuota}} (line
> 151), and \{{ExceedOperationQuota.close()}} via \{{consumeRead}}.
> h3. Effect
> {\{TimeBasedLimiter.grabQuota}} only consumes \{{readCapacityUnitLimiter}} 
> when the
> {\{readCapacityUnit}} argument is positive, so with exceed-throttle on and 
> capacity-unit quotas set
> on the region server:
> * A pure read (\{{writeCapacityUnitConsumed == 0}}) debits *zero* read 
> capacity units. The quota is
>   checked against the correct estimate in step 2 but never charged in step 4, 
> so read CU throttling
>   does not throttle. When \{{exceed}} is true (step 5) the same happens to 
> the user/table/namespace
>   limiters.
> * A pure write debits the read CU limiter by the *write* CU estimate and the 
> request CU limiter
>   twice, so writes spuriously exhaust the read CU quota.
> h3. Origin and affected versions
> Introduced with the feature in HBASE-21783 (\{{9a55cbb2c1df}}, 2019-02-21, 
> shipped in 2.2.0) — the
> original hunk already had \{{writeCapacityUnitConsumed, 
> writeCapacityUnitConsumed}}. HBASE-29231
> reformatted the lines in 2025 and kept the duplicate. Present on \{{master}}, 
> \{{branch-3}},
> {\{branch-2}}, \{{branch-2.6}} and \{{rel/2.6.6}}.
> {\{TestQuotaThrottle.testExceedThrottleQuota}} only exercises 
> \{{READ_NUMBER}} / \{{WRITE_NUMBER}},
> and the capacity-unit tests never enable exceed-throttle, which is presumably 
> why this has not been
> noticed.
> h3. Reproducer
> A unit test in the style of \{{TestDefaultOperationQuota}}: region-server 
> limiter with
> {\{READ_CAPACITY_UNIT}} soft limit 100/s and an unlimited 
> \{{WRITE_CAPACITY_UNIT}}, an
> {\{ExceedOperationQuota}} with no other limiters, two consecutive 
> \{{checkBatchQuota(0, 1, false)}}
> calls. One read is estimated at one 65536-byte block = 64 CU at the default 
> 1024-byte CU size, so
> the first read fits and should leave 36 CU; the second needs 64 and must throw
> {\{RpcThrottlingException}}. On current master the limiter is debited 0 read 
> CU and the second call
> passes:
> {noformat}
> Tests run: 14, Failures: 1, Errors: 0, Skipped: 0
> TestDefaultOperationQuota.testExceedQuotaGrabsReadCapacityUnits:318
>   Expected org.apache.hadoop.hbase.quotas.RpcThrottlingException to be 
> thrown, but nothing was thrown.
> {noformat}
> With the two-line fix below the same run is 14/14 green (the 13 existing 
> tests pass in both runs).
> Verified on \{{master}} at \{{09474ab77b}} with JDK 21.
> {code:java}
> @Test
> public void testExceedQuotaGrabsReadCapacityUnits() throws 
> RpcThrottlingException {
>   QuotaProtos.Throttle rsThrottle = QuotaProtos.Throttle.newBuilder()
>     .setReadCapacityUnit(QuotaProtos.TimedQuota.newBuilder().setSoftLimit(100)
>       .setTimeUnit(HBaseProtos.TimeUnit.SECONDS).build())
>     
> .setWriteCapacityUnit(QuotaProtos.TimedQuota.newBuilder().setSoftLimit(Long.MAX_VALUE)
>       .setTimeUnit(HBaseProtos.TimeUnit.SECONDS).build())
>     .build();
>   QuotaLimiter rsLimiter = TimeBasedLimiter.fromThrottle(conf, rsThrottle);
>   // no user/table/namespace limiters: only the region server quota applies
>   ExceedOperationQuota quota =
>     new ExceedOperationQuota(new Configuration(), 65536, 
> DEFAULT_REQUESTS_PER_SECOND, rsLimiter);
>   // one read = one 65536-byte block = 64 read CU; fits in 100 and must leave 
> 36
>   quota.checkBatchQuota(0, 1, false);
>   // the second read needs 64 CU but only 36 remain, so it must be throttled
>   assertThrows(RpcThrottlingException.class, () -> quota.checkBatchQuota(0, 
> 1, false));
> }
> {code}
> h3. Proposed fix
> {code}
> -        writeCapacityUnitConsumed, writeCapacityUnitConsumed, isAtomic, 
> handlerUsageTimeConsumed);
> +        writeCapacityUnitConsumed, readCapacityUnitConsumed, isAtomic, 
> handlerUsageTimeConsumed);
> ...
> -            writeCapacityUnitConsumed, writeCapacityUnitConsumed, isAtomic, 
> 0L);
> +            writeCapacityUnitConsumed, readCapacityUnitConsumed, isAtomic, 
> 0L);
> {code}
> I ran only this test class, not the full suite or the integration test 
> \{{TestQuotaThrottle}}.
> Happy to open a PR with the fix and the test if that is welcome.
> h3. Provenance
> This was found during an academic study of LLM-assisted code review at 
> Jeonbuk National University, Jeonju, South Korea. An automated LLM-based 
> reviewer produced the initial analysis and I verified it by reading the 
> source at \{{09474ab77bfa}} and the \{{QuotaLimiter}} signature, and by 
> running the unit test above against the unpatched and patched class. I 
> believe it is a genuine defect and will respect your triage either way.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to