This is an automated email from the ASF dual-hosted git repository.

kgyrtkirk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/master by this push:
     new 0a936211fb5 feat: use aws-crt by default for async s3 operations 
(#19249)
0a936211fb5 is described below

commit 0a936211fb5236ba7e37831b180cd9587bd504eb
Author: Zoltan Haindrich <[email protected]>
AuthorDate: Thu Apr 2 16:43:36 2026 +0200

    feat: use aws-crt by default for async s3 operations (#19249)
---
 docs/development/extensions-core/s3.md             |   1 +
 embedded-tests/pom.xml                             |   2 +-
 .../druid/testing/embedded/minio/S3PerfTest.java   | 311 +++++++++++++++++++++
 extensions-core/s3-extensions/pom.xml              |   5 +
 .../druid/storage/s3/S3StorageDruidModule.java     |  48 +++-
 .../apache/druid/storage/s3/S3TransferConfig.java  |  17 ++
 licenses.yaml                                      |  14 +
 pom.xml                                            |   5 +
 .../testing/embedded/EmbeddedDruidServer.java      |  13 +-
 website/.spelling                                  |   4 +
 10 files changed, 411 insertions(+), 9 deletions(-)

diff --git a/docs/development/extensions-core/s3.md 
b/docs/development/extensions-core/s3.md
index 6ad71adc74d..f56861e613e 100644
--- a/docs/development/extensions-core/s3.md
+++ b/docs/development/extensions-core/s3.md
@@ -57,6 +57,7 @@ To use S3 for Deep Storage, you must supply [connection 
information](#configurat
 |`druid.storage.transfer.useTransferManager`| If true, use AWS S3 Transfer 
Manager to upload segments to S3.|true|
 |`druid.storage.transfer.minimumUploadPartSize`| Minimum size (in bytes) of 
each part in a multipart upload.|20971520 (20 MB)|
 |`druid.storage.transfer.multipartUploadThreshold`| The file size threshold 
(in bytes) above which a file upload is converted into a multipart upload 
instead of a single PUT request.| 20971520 (20 MB)|
+|`druid.storage.transfer.asyncHttpClientType`| Async HTTP client 
implementation used by the S3 Transfer Manager. Accepted values: `crt` (Amazon 
CRT) or `netty` (Netty NIO).|`crt`|
 
 ## Configuration
 
diff --git a/embedded-tests/pom.xml b/embedded-tests/pom.xml
index 43b0c68de92..acccca71644 100644
--- a/embedded-tests/pom.xml
+++ b/embedded-tests/pom.xml
@@ -861,7 +861,7 @@
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
-          <excludedGroups>docker-test</excludedGroups>
+          <excludedGroups>docker-test,perf</excludedGroups>
         </configuration>
       </plugin>
       <plugin>
diff --git 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/minio/S3PerfTest.java
 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/minio/S3PerfTest.java
new file mode 100644
index 00000000000..4753c50ab14
--- /dev/null
+++ 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/minio/S3PerfTest.java
@@ -0,0 +1,311 @@
+/*
+ * 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.druid.testing.embedded.minio;
+
+import org.apache.druid.common.aws.AWSModule;
+import org.apache.druid.indexing.common.task.IndexTask;
+import org.apache.druid.indexing.common.task.TaskBuilder;
+import org.apache.druid.java.util.common.DateTimes;
+import org.apache.druid.java.util.common.logger.Logger;
+import org.apache.druid.storage.s3.S3StorageDruidModule;
+import org.apache.druid.testing.embedded.EmbeddedBroker;
+import org.apache.druid.testing.embedded.EmbeddedClusterApis;
+import org.apache.druid.testing.embedded.EmbeddedCoordinator;
+import org.apache.druid.testing.embedded.EmbeddedDruidCluster;
+import org.apache.druid.testing.embedded.EmbeddedHistorical;
+import org.apache.druid.testing.embedded.EmbeddedIndexer;
+import org.apache.druid.testing.embedded.EmbeddedOverlord;
+import org.apache.druid.testing.embedded.EmbeddedRouter;
+import org.apache.druid.testing.embedded.junit5.EmbeddedClusterTestBase;
+import org.joda.time.DateTime;
+import org.junit.jupiter.api.Assumptions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * S3 segment-upload performance tests.
+ *
+ * <p>Each {@link Nested} inner class runs a full embedded cluster with a 
different
+ * storage backend / HTTP client combination:
+ * <ul>
+ *   <li>MinIO container — CRT (default), Netty NIO, synchronous PutObject</li>
+ *   <li>Real AWS S3 (opt-in via {@value BUCKET_PROPERTY}) — same three 
variants</li>
+ * </ul>
+ *
+ * <p>Real-S3 tests are skipped unless {@code -Ds3.test.bucket=<bucket>} is 
set.
+ * Data is written under {@code perf-test/<run-timestamp>/} and must be 
cleaned up manually.
+ */
+@Tag("perf")
+public class S3PerfTest
+{
+  private static final Logger log = new Logger(S3PerfTest.class);
+
+  private static final int TASK_COUNT = 5;
+  private static final int ROWS_PER_TASK = 200_000;
+  private static final long MS_PER_DAY = 86_400_000L;
+
+  private static final String BUCKET_PROPERTY = "s3.test.bucket";
+  private static final String REGION_PROPERTY = "s3.test.region";
+  private static final String DEFAULT_REGION = "us-east-1";
+
+  abstract static class PerfTestBase extends EmbeddedClusterTestBase
+  {
+    private final List<File> tempFiles = new ArrayList<>();
+
+    final EmbeddedOverlord overlord = new EmbeddedOverlord();
+    final EmbeddedCoordinator coordinator = new EmbeddedCoordinator();
+    final EmbeddedIndexer indexer = new 
EmbeddedIndexer().addProperty("druid.worker.capacity", "10");
+    final EmbeddedHistorical historical = new EmbeddedHistorical();
+    final EmbeddedBroker broker = new EmbeddedBroker();
+
+    EmbeddedDruidCluster newBaseCluster()
+    {
+      return EmbeddedDruidCluster.withEmbeddedDerbyAndZookeeper()
+                                 .useLatchableEmitter()
+                                 .useDefaultTimeoutForLatchableEmitter(300);
+    }
+
+    EmbeddedDruidCluster addServers(EmbeddedDruidCluster cluster)
+    {
+      return cluster.addServer(overlord)
+                    .addServer(coordinator)
+                    .addServer(indexer)
+                    .addServer(historical)
+                    .addServer(broker)
+                    .addServer(new EmbeddedRouter());
+    }
+
+    @BeforeAll
+    void generateInputFiles(@TempDir Path tempDir) throws IOException
+    {
+      final DateTime baseDay = DateTimes.of("2025-01-01");
+      for (int i = 0; i < TASK_COUNT; i++) {
+        final File f = generateLargeCsvFile(tempDir, baseDay.plusDays(i), 
ROWS_PER_TASK);
+        tempFiles.add(f);
+        log.info("Generated %s (%d MB)", f.getName(), f.length() / (1024 * 
1024));
+      }
+    }
+
+    @Test
+    @Timeout(value = 20, unit = TimeUnit.MINUTES)
+    public void test_concurrentUploads()
+    {
+      final List<String> taskIds = new ArrayList<>();
+      for (int i = 0; i < TASK_COUNT; i++) {
+        taskIds.add(EmbeddedClusterApis.newTaskId(dataSource));
+      }
+
+      log.info("Starting %d concurrent tasks (%d rows each)", TASK_COUNT, 
ROWS_PER_TASK);
+      final long startNanos = System.nanoTime();
+
+      for (int i = 0; i < taskIds.size(); i++) {
+        final String taskId = taskIds.get(i);
+        final File csvFile = tempFiles.get(i);
+        final IndexTask task = TaskBuilder
+            .ofTypeIndex()
+            .isoTimestampColumn("time")
+            .csvInputFormatWithColumns("time", "item", "value", "description")
+            .localInputSourceWithFiles(csvFile)
+            .segmentGranularity("DAY")
+            .dimensions()
+            .dataSource(dataSource)
+            .withId(taskId);
+        cluster.callApi().onLeaderOverlord(o -> o.runTask(taskId, task));
+      }
+
+      for (String taskId : taskIds) {
+        cluster.callApi().waitForTaskToSucceed(taskId, overlord);
+      }
+
+      final long elapsedMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - 
startNanos);
+      log.info(
+          "[%s] tasks=[%d] rowsPerTask=[%d] fileSizeMb=[%d] elapsed=[%d ms]",
+          getClass().getSimpleName(), TASK_COUNT, ROWS_PER_TASK, 
tempFiles.get(0).length() / (1024 * 1024), elapsedMs
+      );
+    }
+  }
+
+  abstract static class MinIOPerfBase extends PerfTestBase
+  {
+    abstract void configureCluster(EmbeddedDruidCluster cluster);
+
+    @Override
+    public EmbeddedDruidCluster createCluster()
+    {
+      final EmbeddedDruidCluster cluster = 
addServers(newBaseCluster().addResource(new MinIOStorageResource()));
+      configureCluster(cluster);
+      return cluster;
+    }
+  }
+
+  /** MinIO + Amazon CRT async HTTP client (default). */
+  @Nested
+  static class MinIO_Crt extends MinIOPerfBase
+  {
+    @Override
+    void configureCluster(EmbeddedDruidCluster cluster)
+    {
+    }
+  }
+
+  /** MinIO + Netty NIO async HTTP client. */
+  @Nested
+  static class MinIO_Netty extends MinIOPerfBase
+  {
+    @Override
+    void configureCluster(EmbeddedDruidCluster cluster)
+    {
+      cluster.addCommonProperty("druid.storage.transfer.asyncHttpClientType", 
"netty");
+    }
+  }
+
+  /** MinIO + synchronous PutObject (transfer manager disabled). */
+  @Nested
+  static class MinIO_Sync extends MinIOPerfBase
+  {
+    @Override
+    void configureCluster(EmbeddedDruidCluster cluster)
+    {
+      cluster.addCommonProperty("druid.storage.transfer.useTransferManager", 
"false");
+    }
+  }
+
+  abstract static class RealS3PerfBase extends PerfTestBase
+  {
+    abstract void configureCluster(EmbeddedDruidCluster cluster);
+
+    @Override
+    public EmbeddedDruidCluster createCluster()
+    {
+      final String bucket = System.getProperty(BUCKET_PROPERTY);
+      Assumptions.assumeTrue(
+          bucket != null,
+          "Skipping real S3 perf test: set -D" + BUCKET_PROPERTY + "=<bucket> 
to enable"
+      );
+
+      final String region = System.getProperty(REGION_PROPERTY, 
DEFAULT_REGION);
+      final String baseKey = "perf-test/" + System.currentTimeMillis();
+
+      final EmbeddedDruidCluster cluster = newBaseCluster()
+          .addExtension(S3StorageDruidModule.class)
+          .addExtension(AWSModule.class);
+
+      cluster.addCommonProperty("druid.storage.type", "s3");
+      cluster.addCommonProperty("druid.storage.bucket", bucket);
+      cluster.addCommonProperty("druid.storage.baseKey", baseKey);
+      cluster.addCommonProperty("druid.indexer.logs.type", "s3");
+      cluster.addCommonProperty("druid.indexer.logs.s3Bucket", bucket);
+      cluster.addCommonProperty("druid.indexer.logs.s3Prefix", baseKey + 
"/logs");
+      cluster.addCommonProperty("druid.s3.endpoint.signingRegion", region);
+
+      configureCluster(cluster);
+      addServers(cluster);
+      return cluster;
+    }
+  }
+
+  /** Real S3 + Amazon CRT async HTTP client (default). */
+  @Nested
+  static class RealS3_Crt extends RealS3PerfBase
+  {
+    @Override
+    void configureCluster(EmbeddedDruidCluster cluster)
+    {
+    }
+  }
+
+  /** Real S3 + Netty NIO async HTTP client. */
+  @Nested
+  static class RealS3_Netty extends RealS3PerfBase
+  {
+    @Override
+    void configureCluster(EmbeddedDruidCluster cluster)
+    {
+      cluster.addCommonProperty("druid.storage.transfer.asyncHttpClientType", 
"netty");
+    }
+  }
+
+  /** Real S3 + synchronous PutObject (transfer manager disabled). */
+  @Nested
+  static class RealS3_Sync extends RealS3PerfBase
+  {
+    @Override
+    void configureCluster(EmbeddedDruidCluster cluster)
+    {
+      cluster.addCommonProperty("druid.storage.transfer.useTransferManager", 
"false");
+    }
+  }
+
+  private static File generateLargeCsvFile(Path dir, DateTime day, int 
rowCount) throws IOException
+  {
+    final File file = dir.resolve("perf-" + day.toString("yyyy-MM-dd") + 
".csv").toFile();
+    final Random rng = new Random(day.getMillis());
+    try (BufferedWriter w = Files.newBufferedWriter(file.toPath(), 
StandardCharsets.UTF_8)) {
+      for (int i = 0; i < rowCount; i++) {
+        w.write(rowTimestamp(day, i, rowCount));
+        w.write(',');
+        w.write("item_");
+        w.write(Integer.toString(i));
+        w.write(',');
+        w.write(Long.toString(randomValue(rng)));
+        w.write(',');
+        w.write(randomDescription(rng));
+        w.write('\n');
+      }
+    }
+    return file;
+  }
+
+  private static String rowTimestamp(DateTime day, int row, int rowCount)
+  {
+    return day.plusMillis((int) (row * MS_PER_DAY / rowCount)).toString();
+  }
+
+  private static long randomValue(Random rng)
+  {
+    return (rng.nextLong() >>> 1) % 1_000_000L;
+  }
+
+  /** 13 × 16 hex chars = 208 chars of random data, resistant to compression. 
*/
+  private static String randomDescription(Random rng)
+  {
+    final StringBuilder sb = new StringBuilder(208);
+    for (int j = 0; j < 13; j++) {
+      sb.append(String.format(Locale.ROOT, "%016x", rng.nextLong()));
+    }
+    return sb.toString();
+  }
+}
diff --git a/extensions-core/s3-extensions/pom.xml 
b/extensions-core/s3-extensions/pom.xml
index 74b64c43c2b..0989ee41293 100644
--- a/extensions-core/s3-extensions/pom.xml
+++ b/extensions-core/s3-extensions/pom.xml
@@ -112,6 +112,11 @@
       <artifactId>s3-transfer-manager</artifactId>
       <version>${aws.sdk.v2.version}</version>
     </dependency>
+    <dependency>
+      <groupId>software.amazon.awssdk</groupId>
+      <artifactId>aws-crt-client</artifactId>
+      <version>${aws.sdk.v2.version}</version>
+    </dependency>
     <dependency>
       <groupId>software.amazon.awssdk</groupId>
       <artifactId>netty-nio-client</artifactId>
diff --git 
a/extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/S3StorageDruidModule.java
 
b/extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/S3StorageDruidModule.java
index 54d275f7818..27fc537e34b 100644
--- 
a/extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/S3StorageDruidModule.java
+++ 
b/extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/S3StorageDruidModule.java
@@ -36,10 +36,13 @@ import org.apache.druid.guice.Binders;
 import org.apache.druid.guice.JsonConfigProvider;
 import org.apache.druid.guice.LazySingleton;
 import org.apache.druid.initialization.DruidModule;
+import org.apache.druid.java.util.common.ISE;
 import org.apache.druid.java.util.common.logger.Logger;
 import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
 import software.amazon.awssdk.http.apache.ApacheHttpClient;
 import software.amazon.awssdk.http.apache.ProxyConfiguration;
+import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
+import software.amazon.awssdk.http.crt.AwsCrtAsyncHttpClient;
 import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;
 import software.amazon.awssdk.regions.Region;
 import software.amazon.awssdk.services.s3.S3AsyncClient;
@@ -191,15 +194,12 @@ public class S3StorageDruidModule implements DruidModule
     };
 
     // Create async client supplier for S3TransferManager
+    final AsyncHttpClientType asyncHttpClientType =
+        
AsyncHttpClientType.fromString(storageConfig.getS3TransferConfig().getAsyncHttpClientType());
     final Supplier<S3AsyncClient> s3AsyncClientSupplier = () -> {
-      NettyNioAsyncHttpClient.Builder asyncHttpClientBuilder = 
NettyNioAsyncHttpClient.builder()
-          
.connectionTimeout(Duration.ofMillis(clientConfig.getConnectionTimeoutMillis()))
-          
.readTimeout(Duration.ofMillis(clientConfig.getSocketTimeoutMillis()))
-          .maxConcurrency(clientConfig.getMaxConnections());
-
       S3AsyncClientBuilder s3AsyncClientBuilder = S3AsyncClient.builder()
           .credentialsProvider(provider)
-          .httpClientBuilder(asyncHttpClientBuilder)
+          .httpClientBuilder(asyncHttpClientType.buildBuilder(clientConfig))
           .forcePathStyle(clientConfig.isEnablePathStyleAccess())
           .crossRegionAccessEnabled(clientConfig.isCrossRegionAccessEnabled())
           .multipartEnabled(true);
@@ -221,6 +221,42 @@ public class S3StorageDruidModule implements DruidModule
                                        .setS3StorageConfig(storageConfig);
   }
 
+  public enum AsyncHttpClientType
+  {
+    CRT {
+      @Override
+      public SdkAsyncHttpClient.Builder<?> buildBuilder(AWSClientConfig 
clientConfig)
+      {
+        // AwsCrtAsyncHttpClient.Builder does not expose readTimeout in SDK 
2.40.0.
+        return AwsCrtAsyncHttpClient.builder()
+                                    
.connectionTimeout(Duration.ofMillis(clientConfig.getConnectionTimeoutMillis()))
+                                    
.maxConcurrency(clientConfig.getMaxConnections());
+      }
+    },
+    NETTY {
+      @Override
+      public SdkAsyncHttpClient.Builder<?> buildBuilder(AWSClientConfig 
clientConfig)
+      {
+        return NettyNioAsyncHttpClient.builder()
+                                      
.connectionTimeout(Duration.ofMillis(clientConfig.getConnectionTimeoutMillis()))
+                                      
.readTimeout(Duration.ofMillis(clientConfig.getSocketTimeoutMillis()))
+                                      
.maxConcurrency(clientConfig.getMaxConnections());
+      }
+    };
+
+    public abstract SdkAsyncHttpClient.Builder<?> buildBuilder(AWSClientConfig 
clientConfig);
+
+    public static AsyncHttpClientType fromString(String value)
+    {
+      for (AsyncHttpClientType type : values()) {
+        if (type.name().equals(StringUtils.upperCase(value))) {
+          return type;
+        }
+      }
+      throw new ISE("Invalid druid.storage.transfer.asyncHttpClientType[%s]. 
Must be 'crt' or 'netty'.", value);
+    }
+  }
+
   @Nullable
   private static URI buildEndpointOverride(AWSEndpointConfig endpointConfig, 
boolean useHttps)
   {
diff --git 
a/extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/S3TransferConfig.java
 
b/extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/S3TransferConfig.java
index 4df62b83342..5cd13e20b4e 100644
--- 
a/extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/S3TransferConfig.java
+++ 
b/extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/S3TransferConfig.java
@@ -38,6 +38,13 @@ public class S3TransferConfig
   @Min(1)
   private long multipartUploadThreshold = 20 * 1024 * 1024L;
 
+  /**
+   * Async HTTP client implementation to use with the S3 transfer manager.
+   * Accepted values: {@code "crt"} (Amazon CRT, default) or {@code "netty"} 
(Netty NIO).
+   */
+  @JsonProperty
+  private String asyncHttpClientType = "crt";
+
   public void setUseTransferManager(boolean useTransferManager)
   {
     this.useTransferManager = useTransferManager;
@@ -53,6 +60,11 @@ public class S3TransferConfig
     this.multipartUploadThreshold = multipartUploadThreshold;
   }
 
+  public void setAsyncHttpClientType(String asyncHttpClientType)
+  {
+    this.asyncHttpClientType = asyncHttpClientType;
+  }
+
   public boolean isUseTransferManager()
   {
     return useTransferManager;
@@ -68,4 +80,9 @@ public class S3TransferConfig
     return multipartUploadThreshold;
   }
 
+  public String getAsyncHttpClientType()
+  {
+    return asyncHttpClientType;
+  }
+
 }
diff --git a/licenses.yaml b/licenses.yaml
index befab3095a0..2d20bc227eb 100644
--- a/licenses.yaml
+++ b/licenses.yaml
@@ -247,6 +247,7 @@ libraries:
   - software.amazon.awssdk: arns
   - software.amazon.awssdk: auth
   - software.amazon.awssdk: aws-core
+  - software.amazon.awssdk: aws-crt-client
   - software.amazon.awssdk: aws-query-protocol
   - software.amazon.awssdk: aws-xml-protocol
   - software.amazon.awssdk: checksums
@@ -303,6 +304,19 @@ notice: |
 
 ---
 
+name: AWS CRT
+license_category: binary
+module: extensions-core/s3-extensions
+license_name: Apache License version 2.0
+version: 0.40.1
+libraries:
+  - software.amazon.awssdk.crt: aws-crt
+notice: |
+  AWS Common Runtime for Java
+  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+---
+
 name: Jackson
 license_category: binary
 module: java-core
diff --git a/pom.xml b/pom.xml
index b1c3daf8327..be5e1fcf8df 100644
--- a/pom.xml
+++ b/pom.xml
@@ -415,6 +415,11 @@
                 <artifactId>airline</artifactId>
                 <version>2.8.4</version>
             </dependency>
+            <dependency>
+                <groupId>software.amazon.awssdk</groupId>
+                <artifactId>aws-crt-client</artifactId>
+                <version>${aws.sdk.v2.version}</version>
+            </dependency>
             <dependency>
                 <groupId>net.minidev</groupId>
                 <artifactId>json-smart</artifactId>
diff --git 
a/services/src/test/java/org/apache/druid/testing/embedded/EmbeddedDruidServer.java
 
b/services/src/test/java/org/apache/druid/testing/embedded/EmbeddedDruidServer.java
index 5f2ff75fe82..2c4a6185a83 100644
--- 
a/services/src/test/java/org/apache/druid/testing/embedded/EmbeddedDruidServer.java
+++ 
b/services/src/test/java/org/apache/druid/testing/embedded/EmbeddedDruidServer.java
@@ -73,8 +73,12 @@ public abstract class EmbeddedDruidServer<T extends 
EmbeddedDruidServer<T>> impl
     );
     beforeStartHooks.add(
         (cluster, self) -> {
-          // Add properties for temporary directories used by the servers
-          final String logsDirectory = 
cluster.getTestFolder().getOrCreateFolder("indexer-logs").getAbsolutePath();
+          // Add properties for temporary directories used by the servers.
+          // If task.logs.dir is set, write indexer logs there so they survive 
TestFolder cleanup.
+          final String taskLogsDir = System.getProperty("task.logs.dir");
+          final String logsDirectory = taskLogsDir != null
+              ? taskLogsDir
+              : 
cluster.getTestFolder().getOrCreateFolder("indexer-logs").getAbsolutePath();
           final String taskDirectory = 
cluster.getTestFolder().newFolder().getAbsolutePath();
           final String storageDirectory = 
cluster.getTestFolder().getOrCreateFolder("deep-store").getAbsolutePath();
           log.info(
@@ -91,6 +95,11 @@ public abstract class EmbeddedDruidServer<T extends 
EmbeddedDruidServer<T>> impl
           self.addProperty("druid.indexer.logs.directory", logsDirectory);
           self.addProperty("druid.storage.storageDirectory", storageDirectory);
 
+          // When task.logs.dir is set, force file-based task logging
+          if (taskLogsDir != null) {
+            self.addProperty("druid.indexer.logs.type", "file");
+          }
+
           // Add properties for RuntimeInfoModule
           self.addProperty(RuntimeInfoModule.SERVER_MEMORY_PROPERTY, 
String.valueOf(serverMemory));
           self.addProperty(RuntimeInfoModule.SERVER_DIRECT_MEMORY_PROPERTY, 
String.valueOf(serverDirectMemory));
diff --git a/website/.spelling b/website/.spelling
index ccb105a010a..a5b1e7ec5fa 100644
--- a/website/.spelling
+++ b/website/.spelling
@@ -2628,6 +2628,7 @@ TableSpec
 
 - ../docs/development/extensions-contrib/prometheus.md
 TTL
+
 - ../docs/development/extensions-contrib/consul.md
 minimum
 than
@@ -2635,3 +2636,6 @@ than
 maxWatchRetries
 cleartext
 nginx
+
+- ../docs/development/extensions-core/s3.md
+NIO


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to