From 8a3fdbfc5b76047b4463935a44de6ff300d37cd3 Mon Sep 17 00:00:00 2001 From: Vladimir Rodionov Date: Mon, 15 Jun 2026 20:44:01 -0700 Subject: [PATCH] HBASE-30206 Add test factory helpers for CacheAccessService-backed cache instances --- .../io/hfile/cache/CacheAccessServices.java | 31 + .../cache/CacheAccessServiceTestFactory.java | 643 ++++++++++++++++++ .../cache/CacheAccessServiceTestInstance.java | 64 ++ .../TestCacheAccessServiceTestFactory.java | 215 ++++++ 4 files changed, 953 insertions(+) create mode 100644 hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/cache/CacheAccessServiceTestFactory.java create mode 100644 hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/cache/CacheAccessServiceTestInstance.java create mode 100644 hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/cache/TestCacheAccessServiceTestFactory.java diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/cache/CacheAccessServices.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/cache/CacheAccessServices.java index c98dd2275d85..a4de6035728e 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/cache/CacheAccessServices.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/cache/CacheAccessServices.java @@ -18,7 +18,9 @@ package org.apache.hadoop.hbase.io.hfile.cache; import java.util.Objects; +import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.io.hfile.BlockCache; +import org.apache.hadoop.hbase.io.hfile.BlockCacheFactory; import org.apache.yetus.audience.InterfaceAudience; /** @@ -56,6 +58,35 @@ public static CacheAccessService fromBlockCache(BlockCache blockCache) { Objects.requireNonNull(blockCache, "blockCache must not be null")); } + /** + * Creates a {@link CacheAccessService} from the block cache configuration. + *

+ * This method is a compatibility factory for tests and transitional code paths that want to + * obtain a {@link CacheAccessService} directly from {@link Configuration}, while still using the + * existing {@link BlockCacheFactory} and legacy {@link BlockCache} implementations underneath. + *

+ *

+ * The method delegates block-cache construction to + * {@link BlockCacheFactory#createBlockCache(Configuration)}. If the legacy factory creates a + * {@link BlockCache}, the returned service is backed by that cache through + * {@link BlockCacheBackedCacheAccessService}. If the legacy factory does not create a cache, this + * method returns the disabled/no-op cache access service. + *

+ *

+ * This method does not introduce new cache-engine or topology-based runtime wiring. It is + * intended only as a bridge while existing HBase tests and integration paths migrate from direct + * {@link BlockCache} usage to {@link CacheAccessService}. + *

+ * @param conf configuration used by {@link BlockCacheFactory} + * @return cache access service created from the configured legacy block cache, or disabled when + * no block cache is configured + * @throws NullPointerException if {@code conf} is {@code null} + */ + public static CacheAccessService fromConfiguration(Configuration conf) { + Objects.requireNonNull(conf, "conf must not be null"); + return fromBlockCache(BlockCacheFactory.createBlockCache(conf)); + } + /** * Creates a {@link CacheAccessService} backed by a {@link CacheTopology} and placement/admission * policy. diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/cache/CacheAccessServiceTestFactory.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/cache/CacheAccessServiceTestFactory.java new file mode 100644 index 000000000000..069fc6fb6bc1 --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/cache/CacheAccessServiceTestFactory.java @@ -0,0 +1,643 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.io.hfile.cache; + +import java.io.IOException; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.Executor; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.io.hfile.BlockCache; +import org.apache.hadoop.hbase.io.hfile.LruAdaptiveBlockCache; +import org.apache.hadoop.hbase.io.hfile.LruBlockCache; +import org.apache.hadoop.hbase.io.hfile.TinyLfuBlockCache; +import org.apache.hadoop.hbase.io.hfile.bucket.BucketCache; +import org.apache.hadoop.hbase.regionserver.HRegion; +import org.apache.yetus.audience.InterfaceAudience; + +/** + * Test-only factory helpers for creating {@link CacheAccessService} instances backed by legacy + * {@link BlockCache} implementations. + *

+ * These helpers are intended to keep integration-style tests concise while the block cache + * architecture migrates toward {@link CacheAccessService}. They deliberately do not replace + * implementation-specific tests for {@link LruBlockCache}, {@link LruAdaptiveBlockCache}, + * {@link TinyLfuBlockCache}, {@link BucketCache}, or other concrete cache implementations. + *

+ *

+ * Each helper mirrors a public constructor on one of the legacy cache implementations and returns + * either: + *

+ * + *

+ * The factory currently creates legacy {@link BlockCache} implementations and wraps them using + * {@link CacheAccessServices#fromBlockCache(BlockCache)}. Later, when cache engines and topologies + * are wired directly, the internals of this factory can change without forcing integration-style + * tests to be rewritten again. + *

+ */ +@InterfaceAudience.Private +public final class CacheAccessServiceTestFactory { + + private CacheAccessServiceTestFactory() { + } + + /** + * Wraps an existing {@link BlockCache} in a {@link CacheAccessService}. + * @param blockCache block cache to wrap + * @return cache access service backed by the supplied block cache + * @throws NullPointerException if {@code blockCache} is {@code null} + */ + public static CacheAccessService fromBlockCache(BlockCache blockCache) { + return CacheAccessServices.fromBlockCache(blockCache); + } + + /** + * Creates a {@link CacheAccessService} from the block cache configuration. + *

+ * This method delegates to {@link CacheAccessServices#fromConfiguration(Configuration)} and is + * useful for tests that want the normal legacy + * {@link org.apache.hadoop.hbase.io.hfile.BlockCacheFactory} construction path while receiving a + * {@link CacheAccessService} facade. + *

+ * @param conf configuration used to create the legacy block cache + * @return cache access service created from configuration, or disabled when no cache is + * configured + * @throws NullPointerException if {@code conf} is {@code null} + */ + public static CacheAccessService fromConfiguration(Configuration conf) { + return CacheAccessServices.fromConfiguration(conf); + } + + /** + * Returns a disabled/no-op cache access service. + * @return disabled cache access service + */ + public static CacheAccessService disabled() { + return CacheAccessServices.disabled(); + } + + /** + * Creates a {@link CacheAccessService} backed by {@link LruBlockCache}. + * @param maxSize maximum size of cache, in bytes + * @param blockSize approximate size of each block, in bytes + * @return cache access service backed by {@link LruBlockCache} + */ + public static CacheAccessService lru(long maxSize, long blockSize) { + return lruInstance(maxSize, blockSize).service(); + } + + /** + * Creates a test instance containing {@link LruBlockCache} and its {@link CacheAccessService} + * facade. + * @param maxSize maximum size of cache, in bytes + * @param blockSize approximate size of each block, in bytes + * @return test instance + */ + public static CacheAccessServiceTestInstance lruInstance(long maxSize, + long blockSize) { + return new CacheAccessServiceTestInstance<>(new LruBlockCache(maxSize, blockSize)); + } + + /** + * Creates a {@link CacheAccessService} backed by {@link LruBlockCache}. + * @param maxSize maximum size of cache, in bytes + * @param blockSize approximate size of each block, in bytes + * @param evictionThread whether to start the eviction thread + * @return cache access service backed by {@link LruBlockCache} + */ + public static CacheAccessService lru(long maxSize, long blockSize, boolean evictionThread) { + return lruInstance(maxSize, blockSize, evictionThread).service(); + } + + /** + * Creates a test instance containing {@link LruBlockCache} and its {@link CacheAccessService} + * facade. + * @param maxSize maximum size of cache, in bytes + * @param blockSize approximate size of each block, in bytes + * @param evictionThread whether to start the eviction thread + * @return test instance + */ + public static CacheAccessServiceTestInstance lruInstance(long maxSize, + long blockSize, boolean evictionThread) { + return new CacheAccessServiceTestInstance<>( + new LruBlockCache(maxSize, blockSize, evictionThread)); + } + + /** + * Creates a {@link CacheAccessService} backed by {@link LruBlockCache}. + * @param maxSize maximum size of cache, in bytes + * @param blockSize approximate size of each block, in bytes + * @param evictionThread whether to start the eviction thread + * @param conf configuration used by the cache constructor + * @return cache access service backed by {@link LruBlockCache} + * @throws NullPointerException if {@code conf} is {@code null} + */ + public static CacheAccessService lru(long maxSize, long blockSize, boolean evictionThread, + Configuration conf) { + return lruInstance(maxSize, blockSize, evictionThread, conf).service(); + } + + /** + * Creates a test instance containing {@link LruBlockCache} and its {@link CacheAccessService} + * facade. + * @param maxSize maximum size of cache, in bytes + * @param blockSize approximate size of each block, in bytes + * @param evictionThread whether to start the eviction thread + * @param conf configuration used by the cache constructor + * @return test instance + * @throws NullPointerException if {@code conf} is {@code null} + */ + public static CacheAccessServiceTestInstance lruInstance(long maxSize, + long blockSize, boolean evictionThread, Configuration conf) { + Objects.requireNonNull(conf, "conf must not be null"); + return new CacheAccessServiceTestInstance<>( + new LruBlockCache(maxSize, blockSize, evictionThread, conf)); + } + + /** + * Creates a {@link CacheAccessService} backed by {@link LruBlockCache}. + * @param maxSize maximum size of cache, in bytes + * @param blockSize approximate size of each block, in bytes + * @param conf configuration used by the cache constructor + * @return cache access service backed by {@link LruBlockCache} + * @throws NullPointerException if {@code conf} is {@code null} + */ + public static CacheAccessService lru(long maxSize, long blockSize, Configuration conf) { + return lruInstance(maxSize, blockSize, conf).service(); + } + + /** + * Creates a test instance containing {@link LruBlockCache} and its {@link CacheAccessService} + * facade. + * @param maxSize maximum size of cache, in bytes + * @param blockSize approximate size of each block, in bytes + * @param conf configuration used by the cache constructor + * @return test instance + * @throws NullPointerException if {@code conf} is {@code null} + */ + public static CacheAccessServiceTestInstance lruInstance(long maxSize, + long blockSize, Configuration conf) { + Objects.requireNonNull(conf, "conf must not be null"); + return new CacheAccessServiceTestInstance<>(new LruBlockCache(maxSize, blockSize, conf)); + } + + /** + * Creates a {@link CacheAccessService} backed by a fully configured {@link LruBlockCache}. + * @param maxSize maximum size of this cache, in bytes + * @param blockSize expected average size of blocks, in bytes + * @param evictionThread whether to run evictions in a background thread + * @param mapInitialSize initial size of the backing concurrent map + * @param mapLoadFactor initial load factor of the backing concurrent map + * @param mapConcurrencyLevel initial concurrency level of the backing concurrent map + * @param minFactor percentage of total size to evict down to + * @param acceptableFactor percentage of total size that triggers eviction + * @param singleFactor percentage of total size for single-access blocks + * @param multiFactor percentage of total size for multi-access blocks + * @param memoryFactor percentage of total size for in-memory blocks + * @param hardLimitFactor hard capacity limit factor + * @param forceInMemory whether in-memory HFile data blocks have higher priority + * @param maxBlockSize maximum block size accepted by the cache + * @return cache access service backed by {@link LruBlockCache} + */ + public static CacheAccessService lru(long maxSize, long blockSize, boolean evictionThread, + int mapInitialSize, float mapLoadFactor, int mapConcurrencyLevel, float minFactor, + float acceptableFactor, float singleFactor, float multiFactor, float memoryFactor, + float hardLimitFactor, boolean forceInMemory, long maxBlockSize) { + return lruInstance(maxSize, blockSize, evictionThread, mapInitialSize, mapLoadFactor, + mapConcurrencyLevel, minFactor, acceptableFactor, singleFactor, multiFactor, memoryFactor, + hardLimitFactor, forceInMemory, maxBlockSize).service(); + } + + /** + * Creates a test instance containing a fully configured {@link LruBlockCache} and its + * {@link CacheAccessService} facade. + * @param maxSize maximum size of this cache, in bytes + * @param blockSize expected average size of blocks, in bytes + * @param evictionThread whether to run evictions in a background thread + * @param mapInitialSize initial size of the backing concurrent map + * @param mapLoadFactor initial load factor of the backing concurrent map + * @param mapConcurrencyLevel initial concurrency level of the backing concurrent map + * @param minFactor percentage of total size to evict down to + * @param acceptableFactor percentage of total size that triggers eviction + * @param singleFactor percentage of total size for single-access blocks + * @param multiFactor percentage of total size for multi-access blocks + * @param memoryFactor percentage of total size for in-memory blocks + * @param hardLimitFactor hard capacity limit factor + * @param forceInMemory whether in-memory HFile data blocks have higher priority + * @param maxBlockSize maximum block size accepted by the cache + * @return test instance + */ + public static CacheAccessServiceTestInstance lruInstance(long maxSize, + long blockSize, boolean evictionThread, int mapInitialSize, float mapLoadFactor, + int mapConcurrencyLevel, float minFactor, float acceptableFactor, float singleFactor, + float multiFactor, float memoryFactor, float hardLimitFactor, boolean forceInMemory, + long maxBlockSize) { + return new CacheAccessServiceTestInstance<>( + new LruBlockCache(maxSize, blockSize, evictionThread, mapInitialSize, mapLoadFactor, + mapConcurrencyLevel, minFactor, acceptableFactor, singleFactor, multiFactor, memoryFactor, + hardLimitFactor, forceInMemory, maxBlockSize)); + } + + /** + * Creates a {@link CacheAccessService} backed by {@link LruAdaptiveBlockCache}. + * @param maxSize maximum size of cache, in bytes + * @param blockSize approximate size of each block, in bytes + * @return cache access service backed by {@link LruAdaptiveBlockCache} + */ + public static CacheAccessService lruAdaptive(long maxSize, long blockSize) { + return lruAdaptiveInstance(maxSize, blockSize).service(); + } + + /** + * Creates a test instance containing {@link LruAdaptiveBlockCache} and its + * {@link CacheAccessService} facade. + * @param maxSize maximum size of cache, in bytes + * @param blockSize approximate size of each block, in bytes + * @return test instance + */ + public static CacheAccessServiceTestInstance + lruAdaptiveInstance(long maxSize, long blockSize) { + return new CacheAccessServiceTestInstance<>(new LruAdaptiveBlockCache(maxSize, blockSize)); + } + + /** + * Creates a {@link CacheAccessService} backed by {@link LruAdaptiveBlockCache}. + * @param maxSize maximum size of cache, in bytes + * @param blockSize approximate size of each block, in bytes + * @param evictionThread whether to start the eviction thread + * @return cache access service backed by {@link LruAdaptiveBlockCache} + */ + public static CacheAccessService lruAdaptive(long maxSize, long blockSize, + boolean evictionThread) { + return lruAdaptiveInstance(maxSize, blockSize, evictionThread).service(); + } + + /** + * Creates a test instance containing {@link LruAdaptiveBlockCache} and its + * {@link CacheAccessService} facade. + * @param maxSize maximum size of cache, in bytes + * @param blockSize approximate size of each block, in bytes + * @param evictionThread whether to start the eviction thread + * @return test instance + */ + public static CacheAccessServiceTestInstance + lruAdaptiveInstance(long maxSize, long blockSize, boolean evictionThread) { + return new CacheAccessServiceTestInstance<>( + new LruAdaptiveBlockCache(maxSize, blockSize, evictionThread)); + } + + /** + * Creates a {@link CacheAccessService} backed by {@link LruAdaptiveBlockCache}. + * @param maxSize maximum size of cache, in bytes + * @param blockSize approximate size of each block, in bytes + * @param evictionThread whether to start the eviction thread + * @param conf configuration used by the cache constructor + * @return cache access service backed by {@link LruAdaptiveBlockCache} + * @throws NullPointerException if {@code conf} is {@code null} + */ + public static CacheAccessService lruAdaptive(long maxSize, long blockSize, boolean evictionThread, + Configuration conf) { + return lruAdaptiveInstance(maxSize, blockSize, evictionThread, conf).service(); + } + + /** + * Creates a test instance containing {@link LruAdaptiveBlockCache} and its + * {@link CacheAccessService} facade. + * @param maxSize maximum size of cache, in bytes + * @param blockSize approximate size of each block, in bytes + * @param evictionThread whether to start the eviction thread + * @param conf configuration used by the cache constructor + * @return test instance + * @throws NullPointerException if {@code conf} is {@code null} + */ + public static CacheAccessServiceTestInstance + lruAdaptiveInstance(long maxSize, long blockSize, boolean evictionThread, Configuration conf) { + Objects.requireNonNull(conf, "conf must not be null"); + return new CacheAccessServiceTestInstance<>( + new LruAdaptiveBlockCache(maxSize, blockSize, evictionThread, conf)); + } + + /** + * Creates a {@link CacheAccessService} backed by {@link LruAdaptiveBlockCache}. + * @param maxSize maximum size of cache, in bytes + * @param blockSize approximate size of each block, in bytes + * @param conf configuration used by the cache constructor + * @return cache access service backed by {@link LruAdaptiveBlockCache} + * @throws NullPointerException if {@code conf} is {@code null} + */ + public static CacheAccessService lruAdaptive(long maxSize, long blockSize, Configuration conf) { + return lruAdaptiveInstance(maxSize, blockSize, conf).service(); + } + + /** + * Creates a test instance containing {@link LruAdaptiveBlockCache} and its + * {@link CacheAccessService} facade. + * @param maxSize maximum size of cache, in bytes + * @param blockSize approximate size of each block, in bytes + * @param conf configuration used by the cache constructor + * @return test instance + * @throws NullPointerException if {@code conf} is {@code null} + */ + public static CacheAccessServiceTestInstance + lruAdaptiveInstance(long maxSize, long blockSize, Configuration conf) { + Objects.requireNonNull(conf, "conf must not be null"); + return new CacheAccessServiceTestInstance<>( + new LruAdaptiveBlockCache(maxSize, blockSize, conf)); + } + + /** + * Creates a {@link CacheAccessService} backed by a fully configured + * {@link LruAdaptiveBlockCache}. + * @param maxSize maximum size of this cache, in bytes + * @param blockSize expected average size of blocks, in bytes + * @param evictionThread whether to run evictions in a background thread + * @param mapInitialSize initial size of the backing concurrent map + * @param mapLoadFactor initial load factor of the backing concurrent map + * @param mapConcurrencyLevel initial concurrency level of the backing concurrent map + * @param minFactor percentage of total size to evict down to + * @param acceptableFactor percentage of total size that triggers eviction + * @param singleFactor percentage of total size for single-access blocks + * @param multiFactor percentage of total size for multi-access blocks + * @param memoryFactor percentage of total size for in-memory blocks + * @param hardLimitFactor hard capacity limit factor + * @param forceInMemory whether in-memory HFile data blocks have higher + * priority + * @param maxBlockSize maximum block size accepted by the cache + * @param heavyEvictionCountLimit threshold count for heavy-eviction adaptive behavior + * @param heavyEvictionMbSizeLimit threshold size for heavy-eviction adaptive behavior + * @param heavyEvictionOverheadCoefficient coefficient controlling adaptive reduction + * aggressiveness + * @return cache access service backed by {@link LruAdaptiveBlockCache} + */ + public static CacheAccessService lruAdaptive(long maxSize, long blockSize, boolean evictionThread, + int mapInitialSize, float mapLoadFactor, int mapConcurrencyLevel, float minFactor, + float acceptableFactor, float singleFactor, float multiFactor, float memoryFactor, + float hardLimitFactor, boolean forceInMemory, long maxBlockSize, int heavyEvictionCountLimit, + long heavyEvictionMbSizeLimit, float heavyEvictionOverheadCoefficient) { + return lruAdaptiveInstance(maxSize, blockSize, evictionThread, mapInitialSize, mapLoadFactor, + mapConcurrencyLevel, minFactor, acceptableFactor, singleFactor, multiFactor, memoryFactor, + hardLimitFactor, forceInMemory, maxBlockSize, heavyEvictionCountLimit, + heavyEvictionMbSizeLimit, heavyEvictionOverheadCoefficient).service(); + } + + /** + * Creates a test instance containing a fully configured {@link LruAdaptiveBlockCache} and its + * {@link CacheAccessService} facade. + * @param maxSize maximum size of this cache, in bytes + * @param blockSize expected average size of blocks, in bytes + * @param evictionThread whether to run evictions in a background thread + * @param mapInitialSize initial size of the backing concurrent map + * @param mapLoadFactor initial load factor of the backing concurrent map + * @param mapConcurrencyLevel initial concurrency level of the backing concurrent map + * @param minFactor percentage of total size to evict down to + * @param acceptableFactor percentage of total size that triggers eviction + * @param singleFactor percentage of total size for single-access blocks + * @param multiFactor percentage of total size for multi-access blocks + * @param memoryFactor percentage of total size for in-memory blocks + * @param hardLimitFactor hard capacity limit factor + * @param forceInMemory whether in-memory HFile data blocks have higher + * priority + * @param maxBlockSize maximum block size accepted by the cache + * @param heavyEvictionCountLimit threshold count for heavy-eviction adaptive behavior + * @param heavyEvictionMbSizeLimit threshold size for heavy-eviction adaptive behavior + * @param heavyEvictionOverheadCoefficient coefficient controlling adaptive reduction + * aggressiveness + * @return test instance + */ + public static CacheAccessServiceTestInstance lruAdaptiveInstance( + long maxSize, long blockSize, boolean evictionThread, int mapInitialSize, float mapLoadFactor, + int mapConcurrencyLevel, float minFactor, float acceptableFactor, float singleFactor, + float multiFactor, float memoryFactor, float hardLimitFactor, boolean forceInMemory, + long maxBlockSize, int heavyEvictionCountLimit, long heavyEvictionMbSizeLimit, + float heavyEvictionOverheadCoefficient) { + return new CacheAccessServiceTestInstance<>( + new LruAdaptiveBlockCache(maxSize, blockSize, evictionThread, mapInitialSize, mapLoadFactor, + mapConcurrencyLevel, minFactor, acceptableFactor, singleFactor, multiFactor, memoryFactor, + hardLimitFactor, forceInMemory, maxBlockSize, heavyEvictionCountLimit, + heavyEvictionMbSizeLimit, heavyEvictionOverheadCoefficient)); + } + + /** + * Creates a {@link CacheAccessService} backed by {@link TinyLfuBlockCache}. + * @param maximumSizeInBytes maximum size of this cache, in bytes + * @param avgBlockSize expected average size of blocks, in bytes + * @param executor executor used by the underlying Caffeine cache + * @param conf configuration used by the cache constructor + * @return cache access service backed by {@link TinyLfuBlockCache} + * @throws NullPointerException if {@code executor} or {@code conf} is {@code null} + */ + public static CacheAccessService tinyLfu(long maximumSizeInBytes, long avgBlockSize, + Executor executor, Configuration conf) { + return tinyLfuInstance(maximumSizeInBytes, avgBlockSize, executor, conf).service(); + } + + /** + * Creates a test instance containing {@link TinyLfuBlockCache} and its {@link CacheAccessService} + * facade. + * @param maximumSizeInBytes maximum size of this cache, in bytes + * @param avgBlockSize expected average size of blocks, in bytes + * @param executor executor used by the underlying Caffeine cache + * @param conf configuration used by the cache constructor + * @return test instance + * @throws NullPointerException if {@code executor} or {@code conf} is {@code null} + */ + public static CacheAccessServiceTestInstance tinyLfuInstance( + long maximumSizeInBytes, long avgBlockSize, Executor executor, Configuration conf) { + Objects.requireNonNull(executor, "executor must not be null"); + Objects.requireNonNull(conf, "conf must not be null"); + return new CacheAccessServiceTestInstance<>( + new TinyLfuBlockCache(maximumSizeInBytes, avgBlockSize, executor, conf)); + } + + /** + * Creates a {@link CacheAccessService} backed by {@link TinyLfuBlockCache}. + * @param maximumSizeInBytes maximum size of this cache, in bytes + * @param avgBlockSize expected average size of blocks, in bytes + * @param maxBlockSize maximum size of a block, in bytes + * @param executor executor used by the underlying Caffeine cache + * @return cache access service backed by {@link TinyLfuBlockCache} + * @throws NullPointerException if {@code executor} is {@code null} + */ + public static CacheAccessService tinyLfu(long maximumSizeInBytes, long avgBlockSize, + long maxBlockSize, Executor executor) { + return tinyLfuInstance(maximumSizeInBytes, avgBlockSize, maxBlockSize, executor).service(); + } + + /** + * Creates a test instance containing {@link TinyLfuBlockCache} and its {@link CacheAccessService} + * facade. + * @param maximumSizeInBytes maximum size of this cache, in bytes + * @param avgBlockSize expected average size of blocks, in bytes + * @param maxBlockSize maximum size of a block, in bytes + * @param executor executor used by the underlying Caffeine cache + * @return test instance + * @throws NullPointerException if {@code executor} is {@code null} + */ + public static CacheAccessServiceTestInstance tinyLfuInstance( + long maximumSizeInBytes, long avgBlockSize, long maxBlockSize, Executor executor) { + Objects.requireNonNull(executor, "executor must not be null"); + return new CacheAccessServiceTestInstance<>( + new TinyLfuBlockCache(maximumSizeInBytes, avgBlockSize, maxBlockSize, executor)); + } + + /** + * Creates a {@link CacheAccessService} backed by {@link BucketCache}. + * @param ioEngineName IO engine name + * @param capacity cache capacity, in bytes + * @param blockSize expected block size, in bytes + * @param bucketSizes configured bucket sizes, or {@code null} to use defaults + * @param writerThreadNum number of writer threads + * @param writerQLen writer queue length + * @param persistencePath persistence path, or {@code null} for non-persistent cache + * @return cache access service backed by {@link BucketCache} + * @throws IOException if the bucket cache cannot be created + * @throws NullPointerException if {@code ioEngineName} is {@code null} + */ + public static CacheAccessService bucket(String ioEngineName, long capacity, int blockSize, + int[] bucketSizes, int writerThreadNum, int writerQLen, String persistencePath) + throws IOException { + return bucketInstance(ioEngineName, capacity, blockSize, bucketSizes, writerThreadNum, + writerQLen, persistencePath).service(); + } + + /** + * Creates a test instance containing {@link BucketCache} and its {@link CacheAccessService} + * facade. + * @param ioEngineName IO engine name + * @param capacity cache capacity, in bytes + * @param blockSize expected block size, in bytes + * @param bucketSizes configured bucket sizes, or {@code null} to use defaults + * @param writerThreadNum number of writer threads + * @param writerQLen writer queue length + * @param persistencePath persistence path, or {@code null} for non-persistent cache + * @return test instance + * @throws IOException if the bucket cache cannot be created + * @throws NullPointerException if {@code ioEngineName} is {@code null} + */ + public static CacheAccessServiceTestInstance bucketInstance(String ioEngineName, + long capacity, int blockSize, int[] bucketSizes, int writerThreadNum, int writerQLen, + String persistencePath) throws IOException { + Objects.requireNonNull(ioEngineName, "ioEngineName must not be null"); + return new CacheAccessServiceTestInstance<>(new BucketCache(ioEngineName, capacity, blockSize, + bucketSizes, writerThreadNum, writerQLen, persistencePath)); + } + + /** + * Creates a {@link CacheAccessService} backed by {@link BucketCache}. + * @param ioEngineName IO engine name + * @param capacity cache capacity, in bytes + * @param blockSize expected block size, in bytes + * @param bucketSizes configured bucket sizes, or {@code null} to use defaults + * @param writerThreadNum number of writer threads + * @param writerQLen writer queue length + * @param persistencePath persistence path, or {@code null} for non-persistent cache + * @param ioErrorsTolerationDuration duration for tolerating IO engine errors + * @param conf configuration used by the bucket cache + * @return cache access service backed by {@link BucketCache} + * @throws IOException if the bucket cache cannot be created + * @throws NullPointerException if {@code ioEngineName} or {@code conf} is {@code null} + */ + public static CacheAccessService bucket(String ioEngineName, long capacity, int blockSize, + int[] bucketSizes, int writerThreadNum, int writerQLen, String persistencePath, + int ioErrorsTolerationDuration, Configuration conf) throws IOException { + return bucketInstance(ioEngineName, capacity, blockSize, bucketSizes, writerThreadNum, + writerQLen, persistencePath, ioErrorsTolerationDuration, conf).service(); + } + + /** + * Creates a test instance containing {@link BucketCache} and its {@link CacheAccessService} + * facade. + * @param ioEngineName IO engine name + * @param capacity cache capacity, in bytes + * @param blockSize expected block size, in bytes + * @param bucketSizes configured bucket sizes, or {@code null} to use defaults + * @param writerThreadNum number of writer threads + * @param writerQLen writer queue length + * @param persistencePath persistence path, or {@code null} for non-persistent cache + * @param ioErrorsTolerationDuration duration for tolerating IO engine errors + * @param conf configuration used by the bucket cache + * @return test instance + * @throws IOException if the bucket cache cannot be created + * @throws NullPointerException if {@code ioEngineName} or {@code conf} is {@code null} + */ + public static CacheAccessServiceTestInstance bucketInstance(String ioEngineName, + long capacity, int blockSize, int[] bucketSizes, int writerThreadNum, int writerQLen, + String persistencePath, int ioErrorsTolerationDuration, Configuration conf) throws IOException { + Objects.requireNonNull(ioEngineName, "ioEngineName must not be null"); + Objects.requireNonNull(conf, "conf must not be null"); + return new CacheAccessServiceTestInstance<>(new BucketCache(ioEngineName, capacity, blockSize, + bucketSizes, writerThreadNum, writerQLen, persistencePath, ioErrorsTolerationDuration, conf)); + } + + /** + * Creates a {@link CacheAccessService} backed by {@link BucketCache}. + * @param ioEngineName IO engine name + * @param capacity cache capacity, in bytes + * @param blockSize expected block size, in bytes + * @param bucketSizes configured bucket sizes, or {@code null} to use defaults + * @param writerThreadNum number of writer threads + * @param writerQLen writer queue length + * @param persistencePath persistence path, or {@code null} for non-persistent cache + * @param ioErrorsTolerationDuration duration for tolerating IO engine errors + * @param conf configuration used by the bucket cache + * @param onlineRegions online regions map used by the bucket cache, or {@code null} + * @return cache access service backed by {@link BucketCache} + * @throws IOException if the bucket cache cannot be created + * @throws NullPointerException if {@code ioEngineName} or {@code conf} is {@code null} + */ + public static CacheAccessService bucket(String ioEngineName, long capacity, int blockSize, + int[] bucketSizes, int writerThreadNum, int writerQLen, String persistencePath, + int ioErrorsTolerationDuration, Configuration conf, Map onlineRegions) + throws IOException { + return bucketInstance(ioEngineName, capacity, blockSize, bucketSizes, writerThreadNum, + writerQLen, persistencePath, ioErrorsTolerationDuration, conf, onlineRegions).service(); + } + + /** + * Creates a test instance containing {@link BucketCache} and its {@link CacheAccessService} + * facade. + * @param ioEngineName IO engine name + * @param capacity cache capacity, in bytes + * @param blockSize expected block size, in bytes + * @param bucketSizes configured bucket sizes, or {@code null} to use defaults + * @param writerThreadNum number of writer threads + * @param writerQLen writer queue length + * @param persistencePath persistence path, or {@code null} for non-persistent cache + * @param ioErrorsTolerationDuration duration for tolerating IO engine errors + * @param conf configuration used by the bucket cache + * @param onlineRegions online regions map used by the bucket cache, or {@code null} + * @return test instance + * @throws IOException if the bucket cache cannot be created + * @throws NullPointerException if {@code ioEngineName} or {@code conf} is {@code null} + */ + public static CacheAccessServiceTestInstance bucketInstance(String ioEngineName, + long capacity, int blockSize, int[] bucketSizes, int writerThreadNum, int writerQLen, + String persistencePath, int ioErrorsTolerationDuration, Configuration conf, + Map onlineRegions) throws IOException { + Objects.requireNonNull(ioEngineName, "ioEngineName must not be null"); + Objects.requireNonNull(conf, "conf must not be null"); + return new CacheAccessServiceTestInstance<>( + new BucketCache(ioEngineName, capacity, blockSize, bucketSizes, writerThreadNum, writerQLen, + persistencePath, ioErrorsTolerationDuration, conf, onlineRegions)); + } +} diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/cache/CacheAccessServiceTestInstance.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/cache/CacheAccessServiceTestInstance.java new file mode 100644 index 000000000000..b9d8789f3c1d --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/cache/CacheAccessServiceTestInstance.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.io.hfile.cache; + +import java.util.Objects; +import org.apache.hadoop.hbase.io.hfile.BlockCache; +import org.apache.yetus.audience.InterfaceAudience; + +/** + * Test helper that keeps a concrete {@link BlockCache} together with the {@link CacheAccessService} + * facade backed by that cache. + *

+ * This is useful for tests that need to exercise production code through + * {@link CacheAccessService}, while still inspecting implementation-specific state through the + * concrete cache instance. + *

+ * @param concrete block cache type + */ +@InterfaceAudience.Private +public final class CacheAccessServiceTestInstance { + + private final T blockCache; + private final CacheAccessService cacheAccessService; + + CacheAccessServiceTestInstance(T blockCache) { + this.blockCache = Objects.requireNonNull(blockCache, "blockCache must not be null"); + this.cacheAccessService = CacheAccessServices.fromBlockCache(blockCache); + } + + /** + * Returns the concrete block cache instance. + *

+ * Tests should use this only when they need implementation-specific inspection or assertions. + * Normal cache access should go through {@link #service()}. + *

+ * @return concrete block cache + */ + public T blockCache() { + return blockCache; + } + + /** + * Returns the {@link CacheAccessService} facade backed by the concrete block cache. + * @return cache access service + */ + public CacheAccessService service() { + return cacheAccessService; + } +} diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/cache/TestCacheAccessServiceTestFactory.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/cache/TestCacheAccessServiceTestFactory.java new file mode 100644 index 000000000000..ac0b09bee21b --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/cache/TestCacheAccessServiceTestFactory.java @@ -0,0 +1,215 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.io.hfile.cache; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.concurrent.Executor; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseConfiguration; +import org.apache.hadoop.hbase.io.hfile.LruAdaptiveBlockCache; +import org.apache.hadoop.hbase.io.hfile.LruBlockCache; +import org.apache.hadoop.hbase.io.hfile.TinyLfuBlockCache; +import org.apache.hadoop.hbase.io.hfile.bucket.BucketCache; +import org.apache.hadoop.hbase.testclassification.IOTests; +import org.apache.hadoop.hbase.testclassification.SmallTests; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Tests for {@link CacheAccessServiceTestFactory}. + */ +@Tag(IOTests.TAG) +@Tag(SmallTests.TAG) +public class TestCacheAccessServiceTestFactory { + + private static final long MAX_SIZE = 1024L * 1024L; + private static final long BUCKET_CACHE_SIZE = 64L * 1024L * 1024L; + private static final long BLOCK_SIZE = 4096L; + private static final Executor DIRECT_EXECUTOR = Runnable::run; + + @Test + void testDisabled() { + CacheAccessService service = CacheAccessServiceTestFactory.disabled(); + + assertNotNull(service); + assertFalse(service.isCacheEnabled()); + } + + @Test + void testFromBlockCacheRejectsNull() { + assertThrows(NullPointerException.class, + () -> CacheAccessServiceTestFactory.fromBlockCache(null)); + } + + @Test + void testFromConfigurationRejectsNull() { + assertThrows(NullPointerException.class, + () -> CacheAccessServiceTestFactory.fromConfiguration(null)); + } + + @Test + void testLruServiceOnlyFactory() { + Configuration conf = HBaseConfiguration.create(); + CacheAccessService service = + CacheAccessServiceTestFactory.lru(MAX_SIZE, BLOCK_SIZE, true, conf); + + try { + assertNotNull(service); + assertTrue(service.isCacheEnabled()); + } finally { + service.shutdown(); + } + } + + @Test + void testLruInstanceFactory() { + Configuration conf = HBaseConfiguration.create(); + CacheAccessServiceTestInstance instance = + CacheAccessServiceTestFactory.lruInstance(MAX_SIZE, BLOCK_SIZE, true, conf); + + try { + assertNotNull(instance); + assertNotNull(instance.blockCache()); + assertNotNull(instance.service()); + assertInstanceOf(LruBlockCache.class, instance.blockCache()); + assertTrue(instance.service().isCacheEnabled()); + } finally { + instance.service().shutdown(); + } + } + + @Test + void testLruAdaptiveServiceOnlyFactory() { + Configuration conf = HBaseConfiguration.create(); + CacheAccessService service = + CacheAccessServiceTestFactory.lruAdaptive(MAX_SIZE, BLOCK_SIZE, true, conf); + + try { + assertNotNull(service); + assertTrue(service.isCacheEnabled()); + } finally { + service.shutdown(); + } + } + + @Test + void testLruAdaptiveInstanceFactory() { + Configuration conf = HBaseConfiguration.create(); + CacheAccessServiceTestInstance instance = + CacheAccessServiceTestFactory.lruAdaptiveInstance(MAX_SIZE, BLOCK_SIZE, true, conf); + + try { + assertNotNull(instance); + assertNotNull(instance.blockCache()); + assertNotNull(instance.service()); + assertInstanceOf(LruAdaptiveBlockCache.class, instance.blockCache()); + assertTrue(instance.service().isCacheEnabled()); + } finally { + instance.service().shutdown(); + } + } + + @Test + void testTinyLfuServiceOnlyFactory() { + Configuration conf = HBaseConfiguration.create(); + CacheAccessService service = + CacheAccessServiceTestFactory.tinyLfu(MAX_SIZE, BLOCK_SIZE, DIRECT_EXECUTOR, conf); + + try { + assertNotNull(service); + assertTrue(service.isCacheEnabled()); + } finally { + service.shutdown(); + } + } + + @Test + void testTinyLfuInstanceFactory() { + Configuration conf = HBaseConfiguration.create(); + CacheAccessServiceTestInstance instance = + CacheAccessServiceTestFactory.tinyLfuInstance(MAX_SIZE, BLOCK_SIZE, DIRECT_EXECUTOR, conf); + + try { + assertNotNull(instance); + assertNotNull(instance.blockCache()); + assertNotNull(instance.service()); + assertInstanceOf(TinyLfuBlockCache.class, instance.blockCache()); + assertTrue(instance.service().isCacheEnabled()); + } finally { + instance.service().shutdown(); + } + } + + @Test + void testTinyLfuInstanceFactoryRejectsNullExecutor() { + assertThrows(NullPointerException.class, () -> CacheAccessServiceTestFactory + .tinyLfuInstance(MAX_SIZE, BLOCK_SIZE, null, HBaseConfiguration.create())); + } + + @Test + void testTinyLfuInstanceFactoryRejectsNullConfiguration() { + assertThrows(NullPointerException.class, () -> CacheAccessServiceTestFactory + .tinyLfuInstance(MAX_SIZE, BLOCK_SIZE, DIRECT_EXECUTOR, null)); + } + + @Test + void testBucketServiceOnlyFactory() throws IOException { + CacheAccessService service = CacheAccessServiceTestFactory.bucket("offheap", BUCKET_CACHE_SIZE, + (int) BLOCK_SIZE, null, 1, 1, null); + + try { + assertNotNull(service); + assertTrue(service.isCacheEnabled()); + } finally { + service.shutdown(); + } + } + + @Test + void testBucketInstanceFactory() throws IOException { + CacheAccessServiceTestInstance instance = CacheAccessServiceTestFactory + .bucketInstance("offheap", BUCKET_CACHE_SIZE, (int) BLOCK_SIZE, null, 1, 1, null); + + try { + assertNotNull(instance); + assertNotNull(instance.blockCache()); + assertNotNull(instance.service()); + assertInstanceOf(BucketCache.class, instance.blockCache()); + assertTrue(instance.service().isCacheEnabled()); + } finally { + instance.service().shutdown(); + } + } + + @Test + void testBucketInstanceFactoryRejectsNullIoEngineName() { + assertThrows(NullPointerException.class, () -> CacheAccessServiceTestFactory + .bucketInstance(null, MAX_SIZE, (int) BLOCK_SIZE, null, 1, 1, null)); + } + + @Test + void testInstanceRejectsNullBlockCache() { + assertThrows(NullPointerException.class, () -> new CacheAccessServiceTestInstance<>(null)); + } +}