Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
32609b8
feat: Strategy Interface Definition and Implementation
suni72 Jun 10, 2026
daaeb1a
feat: File System Integration and Routing Mechanics
suni72 Jun 10, 2026
e53dea2
refactor: fix import paths and rmeove redundant gcsFilesystemOptions
suni72 Jun 16, 2026
1d36799
refactor: remove unused filesystem operations from GcsFileSystem
suni72 Jun 17, 2026
1ca0bed
test: add HNS support validation tests
suni72 Jun 17, 2026
b4f8daa
refactor: encapsulate namespace strategy accessors
suni72 Jun 17, 2026
d7ccf9c
refactor: migrate BucketCapabilities to an AutoValue class
suni72 Jun 17, 2026
7890c16
refactor: replace BucketCapabilities with BucketProperties
suni72 Jun 30, 2026
58c3cf1
refactor: Move NamespaceStrategy to client package and functionally i…
suni72 Jul 7, 2026
c5316b6
doc: add HNS API configuration in doc and annotate resolveStrategy wi…
suni72 Jul 7, 2026
8903dd0
refactor: inject GcsClient into namespace strategies and improve code…
suni72 Jul 14, 2026
55a147a
feat: optimize namespace strategy resolution and add javadoc for name…
suni72 Jul 14, 2026
62864bd
Merge branch 'main' into dir-metadata-ops-2
suni72 Jul 28, 2026
6a44024
refactor: remove BucketPropertiesLoader dependency in favor of GcsCli…
suni72 Jul 29, 2026
5d37642
refactor: update NamespaceStrategy interface methods for improved dir…
suni72 Jul 29, 2026
04c50da
Merge branch 'main' into dir-metadata-ops-2
suni72 Jul 29, 2026
bf608db
test: add unit tests for GcsClientImpl.isHnsBucket method
suni72 Jul 29, 2026
b2185df
Merge branch 'main' into dir-metadata-ops-2
suni72 Aug 3, 2026
102fbfd
address reviewer comments:
suni72 Aug 3, 2026
b2a3c6f
test: refactor GcsFileSystemImplTest to improve test readability and …
suni72 Aug 5, 2026
21def19
Merge branch 'main' into dir-metadata-ops-2
suni72 Aug 5, 2026
5e53b88
fix: wrap IOException in UncheckedIOException during bucket property …
suni72 Aug 5, 2026
fa451d6
feat: enable HNS API by default in GcsFileSystemOptions
suni72 Aug 5, 2026
6e2ea01
Merge branch 'main' into dir-metadata-ops-2
suni72 Aug 6, 2026
368252e
feat: introduce LazyExecutorService and configurable parallel list op…
suni72 Jul 7, 2026
f870571
refactor: improve LazyExecutorService, add GcsFileSystem input valida…
suni72 Jul 7, 2026
6670498
refactor: encapsulate FutureTask logic into LazyFutureTask and update…
suni72 Jul 7, 2026
8b1caba
refactor: improve code readability
suni72 Jul 9, 2026
9d5e8b1
refactor: simplify LazyExecutorService testing and remove unused impo…
suni72 Jul 9, 2026
93d872e
Revert telemetry tests changes from dir-metadata-ops-2
suni72 Jul 14, 2026
3ac4453
refactor: remove executor service dependency from FlatNamespaceStrate…
suni72 Aug 3, 2026
f289813
fix: handle thread interruption in LazyExecutorService get methods to…
suni72 Aug 5, 2026
2934ad2
fix: add timeout validation to LazyExecutorService and rename executo…
suni72 Aug 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ These parameters fine-tune the low-level data streaming behavior. They allow you
| `analytics-core.adaptive-read.sequential-read-threshold` | Threshold for number of sequential reads to switch to sequential mode. | `3` |
| `analytics-core.random-read.min-request-size` | Minimum request size for random reads. If the requested read size is smaller, it reads up to this size. | `131072` (128 KB) |


### Telemetry and Monitoring

These settings enable the emission of deep internal metrics—such as cache hit rates, operational durations, and throughput—to local logging consoles or distributed OpenTelemetry backends like Google Cloud Monitoring.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2026 Google LLC
*
* Licensed 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 com.google.cloud.gcs.analyticscore.client;

final class FlatNamespaceStrategyImpl implements NamespaceStrategy {

private final GcsClient gcsClient;

FlatNamespaceStrategyImpl(GcsClient gcsClient) {
this.gcsClient = gcsClient;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ WritableByteChannel createWriteChannel(GcsItemId itemId, GcsWriteOptions options
/** Fetches object metadata. */
GcsItemInfo getGcsItemInfo(GcsItemId itemId) throws IOException;

boolean isHnsBucket(String bucketName) throws IOException;

/** Close the client. */
void close();
}
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,11 @@ BucketProperties getBucketProperties(String bucketName) throws IOException {
}
}

@Override
public boolean isHnsBucket(String bucketName) throws IOException {
return getBucketProperties(bucketName).isHnsEnabled();
}

@Override
public void close() {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.nio.channels.WritableByteChannel;
import java.util.Collections;
Expand All @@ -44,16 +45,44 @@

public class GcsFileSystemImpl implements GcsFileSystem {

/**
* Status or list calls (e.g., getting file info or listing a directory) block on I/O. A core pool
* size of 2 allows basic concurrency without significant resource overhead.
*/
private static final int CACHED_EXECUTOR_CORE_POOL_SIZE = 2;

/**
* Using a 30-second keep-alive enables efficient thread reuse during intermittent spikes in
* status and list requests, while ensuring rapid resource cleanup during periods of inactivity.
*/
private static final int CACHED_EXECUTOR_KEEP_ALIVE_SECONDS = 30;

/**
* Status and list calls block on I/O. An unbounded maximum pool size, combined with a
* zero-capacity SynchronousQueue, ensures tasks are never queued and new threads are immediately
* allocated to handle concurrent operations.
*/
private static final int CACHED_EXECUTOR_MAX_POOL_SIZE = Integer.MAX_VALUE;

/**
* The maximum amount of time in seconds to wait for background thread pools to gracefully
* terminate upon file system closure.
*/
private static final int EXECUTOR_SHUTDOWN_TIMEOUT_SECONDS = 10;

private final GcsClient gcsClient;
private final GcsFileSystemOptions fileSystemOptions;
private final Supplier<ExecutorService> executorServiceSupplier;

private final Supplier<ExecutorService> readExecutorServiceSupplier;
private final Supplier<ExecutorService> listExecutorServiceSupplier;
private final Telemetry telemetry;
private final AnalyticsCacheManager cacheManager;
private final FlatNamespaceStrategyImpl flatStrategy;
private final HierarchicalNamespaceStrategyImpl hnsStrategy;

public GcsFileSystemImpl(GcsFileSystemOptions fileSystemOptions) {
this.fileSystemOptions = fileSystemOptions;
this.executorServiceSupplier = initializeExecutionServiceSupplier();
this.readExecutorServiceSupplier = initializeReadExecutionServiceSupplier();
this.listExecutorServiceSupplier = initializeListExecutionServiceSupplier();
this.telemetry = createTelemetry(fileSystemOptions.getAnalyticsCoreTelemetryOptions());
this.cacheManager = new AnalyticsCacheManager(fileSystemOptions.getGcsCacheOptions());
this.gcsClient =
Expand All @@ -63,12 +92,17 @@ public GcsFileSystemImpl(GcsFileSystemOptions fileSystemOptions) {
Collections.emptyMap(),
recorder ->
new GcsClientImpl(
fileSystemOptions.getGcsClientOptions(), executorServiceSupplier, telemetry));
fileSystemOptions.getGcsClientOptions(),
readExecutorServiceSupplier,
telemetry));
this.flatStrategy = new FlatNamespaceStrategyImpl(this.gcsClient);
this.hnsStrategy = new HierarchicalNamespaceStrategyImpl(this.gcsClient);
}

public GcsFileSystemImpl(Credentials credentials, GcsFileSystemOptions fileSystemOptions) {
this.fileSystemOptions = fileSystemOptions;
this.executorServiceSupplier = initializeExecutionServiceSupplier();
this.readExecutorServiceSupplier = initializeReadExecutionServiceSupplier();
this.listExecutorServiceSupplier = initializeListExecutionServiceSupplier();
this.telemetry = createTelemetry(fileSystemOptions.getAnalyticsCoreTelemetryOptions());
this.cacheManager = new AnalyticsCacheManager(fileSystemOptions.getGcsCacheOptions());
this.gcsClient =
Expand All @@ -80,8 +114,10 @@ public GcsFileSystemImpl(Credentials credentials, GcsFileSystemOptions fileSyste
new GcsClientImpl(
credentials,
fileSystemOptions.getGcsClientOptions(),
executorServiceSupplier,
readExecutorServiceSupplier,
telemetry));
this.flatStrategy = new FlatNamespaceStrategyImpl(this.gcsClient);
this.hnsStrategy = new HierarchicalNamespaceStrategyImpl(this.gcsClient);
}

@VisibleForTesting
Expand All @@ -101,9 +137,36 @@ public GcsFileSystemImpl(Credentials credentials, GcsFileSystemOptions fileSyste
AnalyticsCacheManager cacheManager) {
this.gcsClient = gcsClient;
this.fileSystemOptions = fileSystemOptions;
this.executorServiceSupplier = initializeExecutionServiceSupplier();
this.readExecutorServiceSupplier = initializeReadExecutionServiceSupplier();
this.listExecutorServiceSupplier = initializeListExecutionServiceSupplier();
this.telemetry = telemetry;
this.cacheManager = cacheManager;
this.flatStrategy = new FlatNamespaceStrategyImpl(this.gcsClient);
this.hnsStrategy = new HierarchicalNamespaceStrategyImpl(this.gcsClient);
}

@VisibleForTesting
NamespaceStrategy resolveStrategy(String bucketName) throws IOException {
checkNotNull(bucketName, "bucketName cannot be null");
if (!fileSystemOptions.isHnsApiEnabled()) {
return flatStrategy;
}

BucketProperties properties =
cacheManager.getBucketProperties(
bucketName,
name -> {
try {
return BucketProperties.create(gcsClient.isHnsBucket(name));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});

if (properties.isHnsEnabled()) {
return hnsStrategy;
}
return flatStrategy;
}

@Override
Expand Down Expand Up @@ -163,16 +226,39 @@ public AnalyticsCacheManager getCacheManager() {
return cacheManager;
}

@VisibleForTesting
FlatNamespaceStrategyImpl getFlatStrategy() {
return flatStrategy;
}

@VisibleForTesting
HierarchicalNamespaceStrategyImpl getHnsStrategy() {
return hnsStrategy;
}

@Override
public void close() {
ExecutorService executorService = executorServiceSupplier.get();
executorService.shutdown();
ExecutorService readExecutorService = readExecutorServiceSupplier.get();
ExecutorService listExecutorService = listExecutorServiceSupplier.get();
Comment thread
suni72 marked this conversation as resolved.
readExecutorService.shutdown();
listExecutorService.shutdown();
try {
if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
executorService.shutdownNow();
// Wait a total of LIST_EXECUTOR_SHUTDOWN_TIMEOUT_SECONDS for both thread pools to terminate.
long deadline =
System.nanoTime() + TimeUnit.SECONDS.toNanos(EXECUTOR_SHUTDOWN_TIMEOUT_SECONDS);
// First, wait for the read executor service to terminate.
if (!readExecutorService.awaitTermination(
EXECUTOR_SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
readExecutorService.shutdownNow();
}
// Then, wait for the cached executor service to terminate, with the remaining time.
if (!listExecutorService.awaitTermination(
Math.max(0, deadline - System.nanoTime()), TimeUnit.NANOSECONDS)) {
listExecutorService.shutdownNow();
}
} catch (InterruptedException e) {
executorService.shutdownNow();
readExecutorService.shutdownNow();
listExecutorService.shutdownNow();
Thread.currentThread().interrupt();
}
gcsClient.close();
Expand Down Expand Up @@ -206,7 +292,7 @@ static Telemetry createTelemetry(TelemetryOptions telemetryOptions) {
}

@VisibleForTesting
Supplier<ExecutorService> initializeExecutionServiceSupplier() {
Supplier<ExecutorService> initializeReadExecutionServiceSupplier() {
return Suppliers.memoize(
() ->
new ThreadPoolExecutor(
Expand All @@ -220,4 +306,33 @@ Supplier<ExecutorService> initializeExecutionServiceSupplier() {
.setDaemon(true)
.build()));
}

@VisibleForTesting
Supplier<ExecutorService> initializeListExecutionServiceSupplier() {
return Suppliers.memoize(
() -> {
if (fileSystemOptions.isListParallelEnabled()) {
return createCachedExecutor();
}
return new LazyExecutorService();
});
}

private static ExecutorService createCachedExecutor() {
ThreadPoolExecutor service =
new ThreadPoolExecutor(
/* corePoolSize= */ CACHED_EXECUTOR_CORE_POOL_SIZE,
/* maximumPoolSize= */ CACHED_EXECUTOR_MAX_POOL_SIZE,
/* keepAliveTime= */ CACHED_EXECUTOR_KEEP_ALIVE_SECONDS,
TimeUnit.SECONDS,
new java.util.concurrent.SynchronousQueue<>(),
new ThreadFactoryBuilder()
.setNameFormat("gcs-filesystem-cached-pool-%d")
.setDaemon(true)
.build());
// allowCoreThreadTimeOut needs to be enabled for cases where the encapsulating class does not
// properly shut down the executor, preventing thread leaks.
service.allowCoreThreadTimeOut(true);
return service;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public abstract class GcsFileSystemOptions {

private static final String READ_THREAD_COUNT_KEY = "analytics-core.read.thread.count";
private static final String CLIENT_TYPE_KEY = "client.type";
private static final String HNS_API_ENABLED_KEY = "analytics-core.hierarchical.namespace.enable";
private static final String LIST_PARALLEL_ENABLED_KEY = "analytics-core.list.parallel.enabled";

/** Cloud Storage client to use. */
public enum ClientType {
Expand All @@ -43,12 +45,18 @@ public enum ClientType {

public abstract TelemetryOptions getAnalyticsCoreTelemetryOptions();

public abstract boolean isHnsApiEnabled();

public abstract boolean isListParallelEnabled();

public abstract Builder toBuilder();

public static Builder builder() {
return new AutoValue_GcsFileSystemOptions.Builder()
.setReadThreadCount(16)
.setClientType(ClientType.HTTP_CLIENT)
.setHnsApiEnabled(true)
.setListParallelEnabled(true)
.setGcsClientOptions(GcsClientOptions.builder().build())
.setGcsCacheOptions(GcsCacheOptions.builder().build())
.setAnalyticsCoreTelemetryOptions(TelemetryOptions.builder().build());
Expand All @@ -65,6 +73,15 @@ public static GcsFileSystemOptions createFromOptions(
optionsBuilder.setClientType(
ClientType.valueOf(analyticsCoreOptions.get(prefix + CLIENT_TYPE_KEY)));
}
if (analyticsCoreOptions.containsKey(prefix + HNS_API_ENABLED_KEY)) {
optionsBuilder.setHnsApiEnabled(
Boolean.parseBoolean(analyticsCoreOptions.get(prefix + HNS_API_ENABLED_KEY)));
}
if (analyticsCoreOptions.containsKey(prefix + LIST_PARALLEL_ENABLED_KEY)) {
optionsBuilder.setListParallelEnabled(
Boolean.parseBoolean(analyticsCoreOptions.get(prefix + LIST_PARALLEL_ENABLED_KEY)));
}

optionsBuilder.setGcsClientOptions(
GcsClientOptions.createFromOptions(analyticsCoreOptions, prefix));
optionsBuilder.setGcsCacheOptions(
Expand All @@ -84,6 +101,10 @@ public abstract static class Builder {

public abstract Builder setReadThreadCount(int readThreadCount);

public abstract Builder setHnsApiEnabled(boolean isHnsApiEnabled);

public abstract Builder setListParallelEnabled(boolean isListParallelEnabled);

public abstract Builder setGcsClientOptions(GcsClientOptions gcsClientOptions);

/** Sets the configuration options for the GCS caching layer. */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2026 Google LLC
*
* Licensed 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 com.google.cloud.gcs.analyticscore.client;

final class HierarchicalNamespaceStrategyImpl implements NamespaceStrategy {
private final GcsClient gcsClient;

HierarchicalNamespaceStrategyImpl(GcsClient gcsClient) {
this.gcsClient = gcsClient;
}
}
Loading