nsivabalan commented on code in PR #19033: URL: https://github.com/apache/hudi/pull/19033#discussion_r3763124242
########## hudi-sync/hudi-hive-sync/src/main/java/org/apache/hudi/hive/util/HiveMetaStoreClientPool.java: ########## @@ -0,0 +1,262 @@ +/* + * 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.hudi.hive.util; + +import org.apache.hudi.exception.HoodieException; +import org.apache.hudi.hive.HiveSyncConfig; + +import org.apache.hadoop.hive.conf.HiveConf; +import org.apache.hadoop.hive.metastore.IMetaStoreClient; +import org.apache.hadoop.hive.metastore.RetryingMetaStoreClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Pool of {@link IMetaStoreClient} instances for parallel partition sync. + * + * <p>Each pooled client wraps an independent Thrift connection to the Hive Metastore. + * Callers borrow a client via {@link #run(ClientAction)}, which blocks until a client + * is available, executes the action, and returns the client to the pool. A worker + * thread pool of the same size is exposed via {@link #executor()} so callers can fan + * out their batches to match the number of available clients. + * + * <p><b>Usage contract:</b> pool clients must be used <i>only</i> for partition-row + * operations — {@code add_partitions}, {@code alter_partitions}, {@code dropPartition}, + * {@code getPartition}. Table-row operations ({@code createTable}, {@code alter_table}, + * {@code getTable} used as the read half of a read-modify-write of table parameters) + * must continue to go through the session client held by + * {@code HoodieHiveSyncClient.client} on the sync driver thread. Mixing the two would + * risk lost updates on table parameters such as the last-commit-time-synced marker. + * + * <p>The pool is gated behind {@code hoodie.datasource.hive_sync.batching.enabled} and + * is constructed for sync mode HIVEQL, where it backs the DROP path only. DROP goes + * through {@code IMetaStoreClient.dropPartition} (Thrift), whereas ADD/UPDATE/TOUCH go + * through the thread-bound Hive {@code Driver} and use {@code HiveDriverPool} instead. + */ +public class HiveMetaStoreClientPool implements AutoCloseable { + + private static final Logger LOG = LoggerFactory.getLogger(HiveMetaStoreClientPool.class); + + private final ArrayBlockingQueue<IMetaStoreClient> available; + private final List<IMetaStoreClient> all; + private final ExecutorService executor; + private final int size; + private volatile boolean closed; + + public HiveMetaStoreClientPool(HiveSyncConfig config, int size) { + this(buildClients(config, size), size); + } + + // Package-private for tests: accepts a pre-built list of clients so we can + // exercise borrow/return/close semantics without a live metastore. + HiveMetaStoreClientPool(List<IMetaStoreClient> clients, int size) { + if (size < 1) { + throw new IllegalArgumentException("Pool size must be >= 1, got " + size); + } + if (clients.size() != size) { + throw new IllegalArgumentException("Expected " + size + " clients, got " + clients.size()); + } + this.size = size; + this.available = new ArrayBlockingQueue<>(size); + this.all = new ArrayList<>(clients); + this.available.addAll(clients); + this.executor = Executors.newFixedThreadPool(size, new PoolThreadFactory()); + LOG.info("Initialized IMetaStoreClient pool with {} clients", size); + } + + private static List<IMetaStoreClient> buildClients(HiveSyncConfig config, int size) { + if (size < 1) { + throw new IllegalArgumentException("Pool size must be >= 1, got " + size); + } + HiveConf hiveConf = config.getHiveConf(); + List<IMetaStoreClient> clients = new ArrayList<>(size); + try { Review Comment: Kept the duplicate here, but you are right that it needed explaining — added a comment and a test in a9d9443. The premise holds for `size == 0` but not for negatives. The public constructor is `this(buildClients(config, size), size)`, so `buildClients` is evaluated *before* the constructor validates anything. Two consequences: - With the guard removed and `size = -1`, `new ArrayList<>(-1)` throws first, and the caller sees `Illegal Capacity: -1` instead of `Pool size must be >= 1`. - The guard also fails before the loop opens any Thrift connection, rather than after. So it is deliberate rather than drift. I documented that at the call site so the next reader does not have to re-derive it: ```java // Duplicated with the constructor deliberately: this runs first (the public // constructor evaluates buildClients before delegating), so it both fails before any // Thrift connection is opened and keeps the message meaningful for a negative size, // which would otherwise surface as ArrayList's "Illegal Capacity". ``` Also added `invalidPoolSizeIsRejectedWithAClearMessage`, covering `0` and `-1` and asserting on the message — there was no coverage for invalid size at all, so the drift you were worried about could genuinely have happened unnoticed. If either guard is dropped later, that test fails. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
