saihemanth-cloudera commented on code in PR #6595:
URL: https://github.com/apache/hive/pull/6595#discussion_r4353600170


##########
standalone-metastore/metastore-search/src/main/java/org/apache/hive/search/search/Searcher.java:
##########
@@ -0,0 +1,293 @@
+/*
+ * 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.hive.search.search;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hadoop.hive.common.TableName;
+import org.apache.hadoop.hive.metastore.api.Table;
+import org.apache.hive.search.config.SearchOptions;
+import org.apache.hive.search.exception.InferenceException;
+import org.apache.hive.search.exception.SearchException;
+import org.apache.hive.search.mapping.FieldSchema;
+import org.apache.hive.search.mapping.IndexMapping;
+import org.apache.hive.search.mapping.TableDocument;
+import org.apache.hive.search.mapping.TextFieldSchema;
+import org.apache.hive.search.metastore.MetastoreTableMapper;
+import org.apache.hive.search.metastore.TableBlobCodec;
+import org.apache.hive.search.index.IndexManager;
+import org.apache.hive.search.inference.Embedder;
+import org.apache.hive.search.inference.EmbedderRegistry;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.IndexableField;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.queryparser.simple.SimpleQueryParser;
+import org.apache.lucene.search.BayesianScoreEstimator;
+import org.apache.lucene.search.BayesianScoreQuery;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.BoostQuery;
+import org.apache.lucene.search.ConstantScoreQuery;
+import org.apache.lucene.search.DisjunctionMaxQuery;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.KnnFloatVectorQuery;
+import org.apache.lucene.search.LogOddsFusionQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.SearcherManager;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.PrefixQuery;
+import org.apache.lucene.search.TopDocs;
+
+public final class Searcher implements AutoCloseable {
+  private final EmbedderRegistry embedderRegistry;
+  private final IndexSearcher indexSearcher;
+  private final SearcherManager searcherManager;
+  private final IndexMapping mapping;
+  private final SearchOptions searchConfig;
+  private final BayesianScoreEstimator.Parameters parameters;
+  private final long processedEventId;
+
+  public Searcher(SearcherManager manager,
+      IndexManager indexManager,
+      EmbedderRegistry registry,
+      SearchOptions searchConfig,
+      BayesianScoreEstimator.Parameters parameters) throws IOException {
+    this.searcherManager = manager;
+    this.processedEventId = indexManager.getProcessedEventId();
+    this.indexSearcher = manager.acquire();
+    this.mapping = indexManager.mapping();
+    this.searchConfig = searchConfig;
+    this.parameters = parameters;
+    this.embedderRegistry = Objects.requireNonNull(registry, "Embedder 
registry");
+  }
+
+  public TableSearchResult search(SearchQuery request)
+      throws SearchException, IOException {
+    if (StringUtils.isBlank(request.body().queryText())) {
+      throw new SearchException("query text is required");
+    }
+    Query filter = compileScopeFilter(request.catalogName(), 
request.databaseName());
+    int size = request.limit() > 0 ? request.limit() : 
searchConfig.getDefaultLimit();
+    Query query =
+        switch (request.body()) {
+          case MatchQuery match -> {
+            Query matchQuery = compileMatchQuery(match.resolve(mapping));
+            if (filter == null) {
+              yield matchQuery;
+            }
+            yield new BooleanQuery.Builder()
+                .add(matchQuery, BooleanClause.Occur.MUST)
+                .add(filter, BooleanClause.Occur.FILTER)
+                .build();
+          }
+          case SemanticQuery semantic -> 
compileSemanticQuery(semantic.resolve(mapping), size, filter);
+          case HybridQuery hybrid -> 
compileFusionQuery(hybrid.resolve(mapping), size, filter);
+        };
+    TopDocs topDocs = indexSearcher.search(query, size);
+    List<TableSearchHit> hits = new ArrayList<>();
+    for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
+      hits.add(readHit(scoreDoc));
+    }
+    return new TableSearchResult(hits, topDocs.totalHits.value(), 
processedEventId);
+  }
+
+  public TableSearchResult loadTables(List<TableName> tableNames) throws 
SearchException, IOException {
+    if (tableNames == null || tableNames.isEmpty()) {
+      return new TableSearchResult(List.of(), 0, processedEventId);
+    }
+    List<String> ids = new ArrayList<>(tableNames.size());
+    for (TableName table : tableNames) {
+      ids.add(MetastoreTableMapper.tableId(table));
+    }
+    BooleanQuery.Builder builder = new BooleanQuery.Builder();
+    for (String id : ids) {
+      builder.add(
+          new TermQuery(new Term("_id" + TableDocument.FILTER_SUFFIX, id)),
+          BooleanClause.Occur.SHOULD);
+    }
+    TopDocs hits = indexSearcher.search(builder.build(), ids.size());
+    List<TableSearchHit> searchHits = new ArrayList<>(hits.scoreDocs.length);
+    for (ScoreDoc scoreDoc : hits.scoreDocs) {
+      searchHits.add(readHit(scoreDoc));
+    }
+    return new TableSearchResult(searchHits, hits.totalHits.value(), 
processedEventId);
+  }
+
+  private Query compileFusionQuery(HybridQuery query, int semanticK, Query 
filter)
+      throws SearchException {
+    Query matchQuery = new BayesianScoreQuery(

Review Comment:
   The scope filter only reaches the semantic branch. Hybrid lexical hits can 
escape the requested catalog/database right?



##########
standalone-metastore/metastore-search/src/main/java/org/apache/hive/search/metastore/MetastoreIndexer.java:
##########
@@ -0,0 +1,359 @@
+/*
+ * 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.hive.search.metastore;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hive.common.DatabaseName;
+import org.apache.hadoop.hive.metastore.IMetaStoreClient;
+import org.apache.hadoop.hive.metastore.RetryingMetaStoreClient;
+import org.apache.hadoop.hive.metastore.api.NotificationEventRequest;
+import org.apache.hadoop.hive.metastore.leader.LeaderElection;
+import org.apache.hive.search.mapping.TableDocument;
+import org.apache.hive.search.exception.IndexNotHealthyException;
+import org.apache.hive.search.config.IndexOptions;
+import org.apache.hive.search.index.Indexer;
+import org.apache.hive.search.index.IndexManager;
+import org.apache.hive.search.index.store.IndexManifest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class MetastoreIndexer implements Closeable {
+  private static final Logger LOG = 
LoggerFactory.getLogger(MetastoreIndexer.class);
+
+  private final IMetaStoreClient client;
+  private final long lastEventId;
+  private final Indexer indexer;
+  private final MetastoreCluster cluster;
+  private final MetastoreEventHandler handler;
+  private final IndexManager indexManager;
+  private final FlushIndexListener flushIndexListener;
+  private final boolean shareBootstrapFetchClient;
+
+  public MetastoreIndexer(Configuration configuration, IndexManager 
indexManager, Indexer indexer)
+      throws Exception {
+    this(configuration, indexManager, indexer,
+        RetryingMetaStoreClient.getProxy(configuration, true), false);
+  }
+
+  MetastoreIndexer(Configuration configuration, IndexManager indexManager, 
Indexer indexer,
+      IMetaStoreClient client) throws Exception {
+    this(configuration, indexManager, indexer, client, true);
+  }
+
+  MetastoreIndexer(Configuration configuration, IndexManager indexManager, 
Indexer indexer,
+      IMetaStoreClient client, boolean shareBootstrapFetchClient) throws 
Exception {
+    this.client = client;
+    this.shareBootstrapFetchClient = shareBootstrapFetchClient;
+    this.indexer = indexer;
+    this.indexManager = indexManager;
+    this.flushIndexListener = new FlushIndexListener(configuration);
+    this.handler = new MetastoreEventHandler(configuration, client);
+    this.handler.addListeners(flushIndexListener, indexManager);
+    this.cluster = new MetastoreCluster(configuration, flushIndexListener);
+    this.lastEventId = initialize();
+    this.indexManager.setNid(lastEventId);
+  }
+
+  private boolean rebuildIndex() throws Exception {
+    indexManager.resolveInterruptedRestore();
+    if (isIndexValid(indexManager.readLocalManifest().orElse(null))) {
+      return false;
+    }
+    if (isIndexValid(indexManager.readRemoteManifest().orElse(null))) {
+      if (indexManager.restoreBackup()) {
+        return false;
+      }
+      LOG.warn("Failed to restore index from remote backup; clearing partial 
local index");
+      indexManager.clearLocalIndex();
+      return true;
+    }
+    indexManager.clearLocalIndex();
+    return true;
+  }
+
+  private boolean isIndexValid(IndexManifest indexManifest)
+      throws Exception {
+    if (indexManifest == null) {
+      return false;
+    }
+    String current = indexManager.mapping().inference().embedderName();
+    String embedderName = indexManifest.embedder();
+    if (!current.equals(embedderName)) {
+      return false;
+    }
+    long notificationId = indexManifest.lastEventId();
+    return canCatchUp(notificationId);
+  }
+
+  private boolean canCatchUp(long notificationId) throws Exception {
+    if (notificationId < 0) {
+      return false;
+    }
+    try {
+      NotificationEventRequest request = new 
NotificationEventRequest(notificationId);
+      request.setMaxEvents(10);
+      client.getNextNotification(request, false, null);
+      return true;
+    } catch (IllegalStateException ignored) {
+      LOG.debug("Current index lags too behind, will rebuild");
+    }
+    return false;
+  }
+
+  private long initialize() throws Exception {
+    boolean rebuild = rebuildIndex();
+    if (rebuild && !cluster.isLeader()) {
+      long notificationId = waitForRemoteBackup();
+      indexer.initialize();
+      if (notificationId >= 0) {
+        return notificationId;
+      }
+      return rebuildLeaderIndex();
+    }
+    indexer.initialize();
+    if (rebuild) {
+      return rebuildLeaderIndex();
+    }
+    return indexManager.readLocalManifest().get().lastEventId();
+  }
+
+  private long rebuildLeaderIndex() throws Exception {
+    indexManager.clearRemoteIndex();
+    long notificationId = client.getCurrentNotificationEventId().getEventId();
+    new BootstrapIndexer(
+        indexManager,
+        indexer,
+        client,
+        shareBootstrapFetchClient).run(notificationId);
+    return notificationId;
+  }
+
+  public void start() throws Exception {
+    handler.start(lastEventId);

Review Comment:
   Please start the flush listener first. The poller can process an event 
before line 153 resets its checkpoint.



##########
standalone-metastore/metastore-search/src/main/java/org/apache/hive/search/metastore/MetastoreIndexer.java:
##########
@@ -0,0 +1,359 @@
+/*
+ * 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.hive.search.metastore;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hive.common.DatabaseName;
+import org.apache.hadoop.hive.metastore.IMetaStoreClient;
+import org.apache.hadoop.hive.metastore.RetryingMetaStoreClient;
+import org.apache.hadoop.hive.metastore.api.NotificationEventRequest;
+import org.apache.hadoop.hive.metastore.leader.LeaderElection;
+import org.apache.hive.search.mapping.TableDocument;
+import org.apache.hive.search.exception.IndexNotHealthyException;
+import org.apache.hive.search.config.IndexOptions;
+import org.apache.hive.search.index.Indexer;
+import org.apache.hive.search.index.IndexManager;
+import org.apache.hive.search.index.store.IndexManifest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class MetastoreIndexer implements Closeable {
+  private static final Logger LOG = 
LoggerFactory.getLogger(MetastoreIndexer.class);
+
+  private final IMetaStoreClient client;
+  private final long lastEventId;
+  private final Indexer indexer;
+  private final MetastoreCluster cluster;
+  private final MetastoreEventHandler handler;
+  private final IndexManager indexManager;
+  private final FlushIndexListener flushIndexListener;
+  private final boolean shareBootstrapFetchClient;
+
+  public MetastoreIndexer(Configuration configuration, IndexManager 
indexManager, Indexer indexer)
+      throws Exception {
+    this(configuration, indexManager, indexer,
+        RetryingMetaStoreClient.getProxy(configuration, true), false);
+  }
+
+  MetastoreIndexer(Configuration configuration, IndexManager indexManager, 
Indexer indexer,
+      IMetaStoreClient client) throws Exception {
+    this(configuration, indexManager, indexer, client, true);
+  }
+
+  MetastoreIndexer(Configuration configuration, IndexManager indexManager, 
Indexer indexer,
+      IMetaStoreClient client, boolean shareBootstrapFetchClient) throws 
Exception {
+    this.client = client;
+    this.shareBootstrapFetchClient = shareBootstrapFetchClient;
+    this.indexer = indexer;
+    this.indexManager = indexManager;
+    this.flushIndexListener = new FlushIndexListener(configuration);
+    this.handler = new MetastoreEventHandler(configuration, client);
+    this.handler.addListeners(flushIndexListener, indexManager);
+    this.cluster = new MetastoreCluster(configuration, flushIndexListener);
+    this.lastEventId = initialize();
+    this.indexManager.setNid(lastEventId);
+  }
+
+  private boolean rebuildIndex() throws Exception {
+    indexManager.resolveInterruptedRestore();
+    if (isIndexValid(indexManager.readLocalManifest().orElse(null))) {
+      return false;
+    }
+    if (isIndexValid(indexManager.readRemoteManifest().orElse(null))) {
+      if (indexManager.restoreBackup()) {
+        return false;
+      }
+      LOG.warn("Failed to restore index from remote backup; clearing partial 
local index");
+      indexManager.clearLocalIndex();
+      return true;
+    }
+    indexManager.clearLocalIndex();
+    return true;
+  }
+
+  private boolean isIndexValid(IndexManifest indexManifest)
+      throws Exception {
+    if (indexManifest == null) {
+      return false;
+    }
+    String current = indexManager.mapping().inference().embedderName();
+    String embedderName = indexManifest.embedder();
+    if (!current.equals(embedderName)) {
+      return false;
+    }
+    long notificationId = indexManifest.lastEventId();
+    return canCatchUp(notificationId);
+  }
+
+  private boolean canCatchUp(long notificationId) throws Exception {
+    if (notificationId < 0) {
+      return false;
+    }
+    try {
+      NotificationEventRequest request = new 
NotificationEventRequest(notificationId);
+      request.setMaxEvents(10);
+      client.getNextNotification(request, false, null);
+      return true;
+    } catch (IllegalStateException ignored) {
+      LOG.debug("Current index lags too behind, will rebuild");
+    }
+    return false;
+  }
+
+  private long initialize() throws Exception {
+    boolean rebuild = rebuildIndex();
+    if (rebuild && !cluster.isLeader()) {
+      long notificationId = waitForRemoteBackup();
+      indexer.initialize();
+      if (notificationId >= 0) {
+        return notificationId;
+      }
+      return rebuildLeaderIndex();
+    }
+    indexer.initialize();
+    if (rebuild) {
+      return rebuildLeaderIndex();
+    }
+    return indexManager.readLocalManifest().get().lastEventId();
+  }
+
+  private long rebuildLeaderIndex() throws Exception {
+    indexManager.clearRemoteIndex();
+    long notificationId = client.getCurrentNotificationEventId().getEventId();
+    new BootstrapIndexer(
+        indexManager,
+        indexer,
+        client,
+        shareBootstrapFetchClient).run(notificationId);
+    return notificationId;
+  }
+
+  public void start() throws Exception {
+    handler.start(lastEventId);
+    flushIndexListener.start(lastEventId);
+  }
+
+  /** Package-private for integration tests. */
+  int pollEvents(int count) throws IndexNotHealthyException {
+    return handler.getNextMetastoreEvents(count);
+  }
+
+  /** Package-private for integration tests. */
+  void flushCheckpoint() throws IOException {
+    try {
+      indexer.flush(client.getCurrentNotificationEventId().getEventId(), true);
+    } catch (Exception e) {
+      if (e instanceof IOException ioException) {
+        throw ioException;
+      }
+      throw new IOException("Failed to flush index checkpoint", e);
+    }
+  }
+
+  /** Package-private for integration tests. */
+  void syncBackup() throws IOException {
+    indexer.syncBackup();
+  }
+
+  /**
+   * Wait for a follower-ready remote backup and restore it before the 
IndexWriter is opened.
+   *
+   * @return restored notification id, or -1 if this instance became leader 
while waiting
+   */
+  private long waitForRemoteBackup() throws Exception {
+    String current = indexManager.mapping().inference().embedderName();
+    for (; !cluster.isLeader(); Thread.sleep(3000)) {
+      Optional<IndexManifest> indexManifest = 
indexManager.readRemoteManifest();
+      if (indexManifest.isEmpty()) {
+        continue;
+      }
+      IndexManifest manifest = indexManifest.get();
+      if (!current.equals(manifest.embedder())) {
+        LOG.debug("Remote index model {} does not match configured model {}, 
waiting",
+            manifest.embedder(), current);
+        continue;
+      }
+      long notificationId = manifest.lastEventId();
+      if (notificationId > 0 && canCatchUp(notificationId)) {

Review Comment:
   1st hypothetical case: Event ID 0 is valid for an empty metastore; this 
condition leaves followers waiting forever.



##########
standalone-metastore/metastore-search/src/main/java/org/apache/hive/search/metastore/MetastoreIndexer.java:
##########
@@ -0,0 +1,359 @@
+/*
+ * 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.hive.search.metastore;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hive.common.DatabaseName;
+import org.apache.hadoop.hive.metastore.IMetaStoreClient;
+import org.apache.hadoop.hive.metastore.RetryingMetaStoreClient;
+import org.apache.hadoop.hive.metastore.api.NotificationEventRequest;
+import org.apache.hadoop.hive.metastore.leader.LeaderElection;
+import org.apache.hive.search.mapping.TableDocument;
+import org.apache.hive.search.exception.IndexNotHealthyException;
+import org.apache.hive.search.config.IndexOptions;
+import org.apache.hive.search.index.Indexer;
+import org.apache.hive.search.index.IndexManager;
+import org.apache.hive.search.index.store.IndexManifest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class MetastoreIndexer implements Closeable {
+  private static final Logger LOG = 
LoggerFactory.getLogger(MetastoreIndexer.class);
+
+  private final IMetaStoreClient client;
+  private final long lastEventId;
+  private final Indexer indexer;
+  private final MetastoreCluster cluster;
+  private final MetastoreEventHandler handler;
+  private final IndexManager indexManager;
+  private final FlushIndexListener flushIndexListener;
+  private final boolean shareBootstrapFetchClient;
+
+  public MetastoreIndexer(Configuration configuration, IndexManager 
indexManager, Indexer indexer)
+      throws Exception {
+    this(configuration, indexManager, indexer,
+        RetryingMetaStoreClient.getProxy(configuration, true), false);
+  }
+
+  MetastoreIndexer(Configuration configuration, IndexManager indexManager, 
Indexer indexer,
+      IMetaStoreClient client) throws Exception {
+    this(configuration, indexManager, indexer, client, true);
+  }
+
+  MetastoreIndexer(Configuration configuration, IndexManager indexManager, 
Indexer indexer,
+      IMetaStoreClient client, boolean shareBootstrapFetchClient) throws 
Exception {
+    this.client = client;
+    this.shareBootstrapFetchClient = shareBootstrapFetchClient;
+    this.indexer = indexer;
+    this.indexManager = indexManager;
+    this.flushIndexListener = new FlushIndexListener(configuration);
+    this.handler = new MetastoreEventHandler(configuration, client);
+    this.handler.addListeners(flushIndexListener, indexManager);
+    this.cluster = new MetastoreCluster(configuration, flushIndexListener);

Review Comment:
   2nd hypothetical case: If initialization fails, the constructor leaks this 
client and leader election because the object never becomes closeable.



-- 
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]


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

Reply via email to