You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by rn...@apache.org on 2023/01/01 23:23:50 UTC

[couchdb] branch import-nouveau-reorg updated (32a5000af -> ea22a6d51)

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

rnewson pushed a change to branch import-nouveau-reorg
in repository https://gitbox.apache.org/repos/asf/couchdb.git


 discard 32a5000af simplify the index management
     new ea22a6d51 simplify the index management

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (32a5000af)
            \
             N -- N -- N   refs/heads/import-nouveau-reorg (ea22a6d51)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../src/main/java/org/apache/couchdb/nouveau/core/IndexManager.java     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)


[couchdb] 01/01: simplify the index management

Posted by rn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rnewson pushed a commit to branch import-nouveau-reorg
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit ea22a6d51f6b50047a67e60ef9d8a9f1f9280c46
Author: Robert Newson <rn...@apache.org>
AuthorDate: Sun Jan 1 21:26:22 2023 +0000

    simplify the index management
---
 .../apache/couchdb/nouveau/NouveauApplication.java |   4 -
 .../core/FileAlreadyExistsExceptionMapper.java     |  35 ------
 .../nouveau/core/FileNotFoundExceptionMapper.java  |  35 ------
 .../org/apache/couchdb/nouveau/core/Index.java     | 119 ++++--------------
 .../apache/couchdb/nouveau/core/IndexManager.java  | 139 ++++++++-------------
 .../apache/couchdb/nouveau/core/Lucene9Index.java  |  16 +--
 .../nouveau/health/IndexManagerHealthCheck.java    |  18 +--
 .../couchdb/nouveau/resources/IndexResource.java   |  23 +---
 .../couchdb/nouveau/resources/SearchResource.java  |   7 +-
 .../apache/couchdb/nouveau/IntegrationTest.java    |   6 +-
 10 files changed, 95 insertions(+), 307 deletions(-)

diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/NouveauApplication.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/NouveauApplication.java
index 215483d84..7a64e89bd 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/NouveauApplication.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/NouveauApplication.java
@@ -13,8 +13,6 @@
 
 package org.apache.couchdb.nouveau;
 
-import org.apache.couchdb.nouveau.core.FileAlreadyExistsExceptionMapper;
-import org.apache.couchdb.nouveau.core.FileNotFoundExceptionMapper;
 import org.apache.couchdb.nouveau.core.IndexManager;
 import org.apache.couchdb.nouveau.core.Lucene9AnalyzerFactory;
 import org.apache.couchdb.nouveau.core.Lucene9IndexFactory;
@@ -73,8 +71,6 @@ public class NouveauApplication extends Application<NouveauApplicationConfigurat
         indexManager.setIndexFactory(indexFactory);
         environment.lifecycle().manage(indexManager);
 
-        environment.jersey().register(new FileNotFoundExceptionMapper());
-        environment.jersey().register(new FileAlreadyExistsExceptionMapper());
         environment.jersey().register(new UpdatesOutOfOrderExceptionMapper());
 
         final AnalyzeResource analyzeResource = new AnalyzeResource(analyzerFactory);
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/FileAlreadyExistsExceptionMapper.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/FileAlreadyExistsExceptionMapper.java
deleted file mode 100644
index e9ef3ed0c..000000000
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/FileAlreadyExistsExceptionMapper.java
+++ /dev/null
@@ -1,35 +0,0 @@
-//
-// 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 org.apache.couchdb.nouveau.core;
-
-import java.nio.file.FileAlreadyExistsException;
-
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.Status;
-import javax.ws.rs.ext.ExceptionMapper;
-
-import io.dropwizard.jersey.errors.ErrorMessage;
-
-public class FileAlreadyExistsExceptionMapper implements ExceptionMapper<FileAlreadyExistsException> {
-
-    @Override
-    public Response toResponse(final FileAlreadyExistsException exception) {
-        return Response.status(Status.EXPECTATION_FAILED)
-            .type(MediaType.APPLICATION_JSON_TYPE)
-            .entity(new ErrorMessage(Status.EXPECTATION_FAILED.getStatusCode(), "Index already exists"))
-            .build();
-    }
-
-}
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/FileNotFoundExceptionMapper.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/FileNotFoundExceptionMapper.java
deleted file mode 100644
index 84e692df7..000000000
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/FileNotFoundExceptionMapper.java
+++ /dev/null
@@ -1,35 +0,0 @@
-//
-// 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 org.apache.couchdb.nouveau.core;
-
-import java.io.FileNotFoundException;
-
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.Status;
-import javax.ws.rs.ext.ExceptionMapper;
-
-import io.dropwizard.jersey.errors.ErrorMessage;
-
-public class FileNotFoundExceptionMapper implements ExceptionMapper<FileNotFoundException> {
-
-    @Override
-    public Response toResponse(final FileNotFoundException exception) {
-        return Response.status(Status.NOT_FOUND)
-            .type(MediaType.APPLICATION_JSON_TYPE)
-            .entity(new ErrorMessage(Status.NOT_FOUND.getStatusCode(), "Index does not exist"))
-            .build();
-    }
-
-}
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Index.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Index.java
index 739768913..880b2c983 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Index.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Index.java
@@ -15,7 +15,6 @@ package org.apache.couchdb.nouveau.core;
 
 import java.io.Closeable;
 import java.io.IOException;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 import org.apache.couchdb.nouveau.api.DocumentDeleteRequest;
 import org.apache.couchdb.nouveau.api.DocumentUpdateRequest;
@@ -25,152 +24,78 @@ import org.apache.couchdb.nouveau.api.SearchResults;
 
 public abstract class Index implements Closeable {
 
-    /*
-     * The close lock is to ensure there are no readers/searchers when
-     * we want to close the index.
-     */
-    private ReentrantReadWriteLock closeLock = new ReentrantReadWriteLock();
-
-    /*
-     * The update lock ensures serial updates to the index.
-     */
-    private ReentrantReadWriteLock updateLock = new ReentrantReadWriteLock();
-
     private long updateSeq;
 
     private boolean deleteOnClose = false;
 
-    private boolean closed = false;
-
     protected Index(final long updateSeq) {
         this.updateSeq = updateSeq;
     }
 
     public final IndexInfo info() throws IOException {
-        final long updateSeq = getUpdateSeq();
-        closeLock.readLock().lock();
-        try {
-            final int numDocs = doNumDocs();
-            return new IndexInfo(updateSeq, numDocs);
-        } finally {
-            closeLock.readLock().unlock();
-        }
+        final int numDocs = doNumDocs();
+        return new IndexInfo(updateSeq, numDocs);
     }
 
     protected abstract int doNumDocs() throws IOException;
 
-    public final void update(final String docId, final DocumentUpdateRequest request) throws IOException {
-        updateLock.writeLock().lock();
-        try {
-            assertUpdateSeqIsLower(request.getSeq());
-            closeLock.readLock().lock();
-            try {
-                doUpdate(docId, request);
-            } finally {
-                closeLock.readLock().unlock();
-            }
-            incrementUpdateSeq(request.getSeq());
-        } finally {
-            updateLock.writeLock().unlock();
-        }
+    public final synchronized void update(final String docId, final DocumentUpdateRequest request) throws IOException {
+        assertUpdateSeqIsLower(request.getSeq());
+        doUpdate(docId, request);
+        incrementUpdateSeq(request.getSeq());
     }
 
     protected abstract void doUpdate(final String docId, final DocumentUpdateRequest request) throws IOException;
 
-    public final void delete(final String docId, final DocumentDeleteRequest request) throws IOException {
-        updateLock.writeLock().lock();
-        try {
-            assertUpdateSeqIsLower(request.getSeq());
-            closeLock.readLock().lock();
-            try {
-                doDelete(docId, request);
-            } finally {
-                closeLock.readLock().unlock();
-            }
-            incrementUpdateSeq(request.getSeq());
-        } finally {
-            updateLock.writeLock().unlock();
-        }
+    public final synchronized void delete(final String docId, final DocumentDeleteRequest request) throws IOException {
+        assertUpdateSeqIsLower(request.getSeq());
+        doDelete(docId, request);
+        incrementUpdateSeq(request.getSeq());
     }
 
     protected abstract void doDelete(final String docId, final DocumentDeleteRequest request) throws IOException;
 
     public final SearchResults search(final SearchRequest request) throws IOException, QueryParserException {
-        closeLock.readLock().lock();
-        try {
-            return doSearch(request);
-        } finally {
-            closeLock.readLock().unlock();
-        }
+        return doSearch(request);
     }
 
     protected abstract SearchResults doSearch(final SearchRequest request) throws IOException, QueryParserException;
 
     public final boolean commit() throws IOException {
-        final long updateSeq = getUpdateSeq();
-        closeLock.readLock().lock();
-        try {
-            return doCommit(updateSeq);
-        } finally {
-            closeLock.readLock().unlock();
+        final long updateSeq;
+        synchronized (this) {
+            updateSeq = this.updateSeq;
         }
+        return doCommit(updateSeq);
     }
 
     protected abstract boolean doCommit(final long updateSeq) throws IOException;
 
     public final void close() throws IOException {
-        closeLock.writeLock().lock();
-        try {
-            doClose(deleteOnClose);
-            closed = true;
-        } finally {
-            closeLock.writeLock().unlock();
-        }
+        doClose();
     }
 
-    protected abstract void doClose(final boolean deleteOnClose) throws IOException;
+    protected abstract void doClose() throws IOException;
 
-    public final void lock() {
-        closeLock.readLock().lock();
-    }
-
-    public final void unlock() {
-        closeLock.readLock().unlock();
-    }
+    public abstract boolean isOpen();
 
-    public final boolean isClosed() {
-        return closed;
+    public boolean isDeleteOnClose() {
+        return deleteOnClose;
     }
 
-    public final void setDeleteOnClose(final boolean deleteOnClose) {
-        closeLock.writeLock().lock();
-        try {
-            this.deleteOnClose = true;
-        } finally {
-            closeLock.writeLock().unlock();
-        }
+    public void setDeleteOnClose(final boolean deleteOnClose) {
+        this.deleteOnClose = deleteOnClose;
     }
 
     protected final void assertUpdateSeqIsLower(final long updateSeq) throws UpdatesOutOfOrderException {
-        assert updateLock.isWriteLockedByCurrentThread();
         if (!(updateSeq > this.updateSeq)) {
             throw new UpdatesOutOfOrderException();
         }
     }
 
     protected final void incrementUpdateSeq(final long updateSeq) throws IOException {
-        assert updateLock.isWriteLockedByCurrentThread();
         assertUpdateSeqIsLower(updateSeq);
         this.updateSeq = updateSeq;
     }
 
-    private long getUpdateSeq() {
-        updateLock.readLock().lock();
-        try {
-            return this.updateSeq;
-        } finally {
-            updateLock.readLock().unlock();
-        }
-    }
-
 }
\ No newline at end of file
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/IndexManager.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/IndexManager.java
index a5bb2970b..4d7ef07e0 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/IndexManager.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/IndexManager.java
@@ -18,7 +18,6 @@ import java.nio.file.FileAlreadyExistsException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.time.Duration;
-import java.util.concurrent.CompletionException;
 import java.util.stream.Stream;
 
 import javax.validation.constraints.Min;
@@ -28,6 +27,7 @@ import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.Response.Status;
 
 import org.apache.couchdb.nouveau.api.IndexDefinition;
+import org.apache.lucene.util.IOUtils;
 import org.checkerframework.checker.nullness.qual.NonNull;
 import org.checkerframework.checker.nullness.qual.Nullable;
 import org.slf4j.Logger;
@@ -46,49 +46,47 @@ import com.github.benmanes.caffeine.cache.Scheduler;
 
 import io.dropwizard.lifecycle.Managed;
 
-public class IndexManager implements Managed {
+public final class IndexManager implements Managed {
 
-    private static final int RETRY_LIMIT = 500;
-    private static final int RETRY_SLEEP_MS = 5;
     private static final Logger LOGGER = LoggerFactory.getLogger(IndexManager.class);
 
     private class IndexLoader implements CacheLoader<String, Index> {
 
         @Override
         public @Nullable Index load(@NonNull String name) throws Exception {
-            return openExistingIndex(name);
+            final Path path = indexPath(name);
+            final IndexDefinition indexDefinition = objectMapper.readValue(indexDefinitionPath(name).toFile(),
+                    IndexDefinition.class);
+            return indexFactory.open(path, indexDefinition);
         }
 
         @Override
         public @Nullable Index reload(@NonNull String name, @NonNull Index index) throws Exception {
-            try {
-                if (index.commit()) {
-                    LOGGER.info("{} committed.", index);
-                }
-            } catch (final IOException e) {
-                LOGGER.error(index + " threw exception when committing.", e);
-                index.close();
-                return openExistingIndex(name);
+            if (index.commit()) {
+                LOGGER.info("{} committed.", name);
             }
             return index;
         }
 
     }
 
-    private static class IndexCloser implements RemovalListener<String, Index> {
+    private class IndexRemovalListener implements RemovalListener<String, Index> {
 
         public void onRemoval(String name, Index index, RemovalCause cause) {
             try {
-                index.close();
-            } catch (IOException e) {
+                if (index.isOpen()) {
+                    LOGGER.info("{} closing.", name);
+                    index.close();
+                    if (index.isDeleteOnClose()) {
+                        IOUtils.rm(indexRootPath(name));
+                    }
+                }
+            } catch (final IOException e) {
                 LOGGER.error(index + " threw exception when closing", e);
             }
         }
     }
 
-    private static final IndexCloser INDEX_CLOSER = new IndexCloser();
-
-
     @Min(1)
     private int maxIndexesOpen;
 
@@ -114,33 +112,34 @@ public class IndexManager implements Managed {
     private LoadingCache<String, Index> cache;
 
     public Index acquire(final String name) throws IOException {
-        for (int i = 0; i < RETRY_LIMIT; i++) {
-            final Index result = getFromCache(name);
-
-            // Check if we're in the middle of closing.
-            result.lock();
-            if (!result.isClosed()) {
-                return result;
-            }
-            result.unlock();
-
-            // Retry after a short sleep.
-            try {
-                Thread.sleep(RETRY_SLEEP_MS);
-            } catch (InterruptedException e) {
-                Thread.interrupted();
-                break;
-            }
+        if (!exists(name)) {
+            throw new WebApplicationException("Index does not exist", Status.NOT_FOUND);
         }
-        throw new IOException("Failed to acquire " + name);
+        return cache.get(name);
     }
 
-    public void release(final Index index) throws IOException {
-        index.unlock();
+    public void invalidate(final String name) {
+        cache.invalidate(name);
     }
 
     public void create(final String name, IndexDefinition indexDefinition) throws IOException {
-        createNewIndex(name, indexDefinition);
+        if (exists(name)) {
+            throw new WebApplicationException("Index already exists", Status.EXPECTATION_FAILED);
+        }
+        // Validate index definiton
+        analyzerFactory.fromDefinition(indexDefinition);
+
+        // Persist definition
+        final Path path = indexDefinitionPath(name);
+        if (Files.exists(path)) {
+            throw new FileAlreadyExistsException(name + " already exists");
+        }
+        Files.createDirectories(path.getParent());
+        objectMapper.writeValue(path.toFile(), indexDefinition);
+    }
+
+    public boolean exists(final String name) {
+        return Files.exists(indexDefinitionPath(name));
     }
 
     public void deleteAll(final String path) throws IOException {
@@ -149,7 +148,7 @@ public class IndexManager implements Managed {
             return;
         }
         Stream<Path> stream = Files.find(rootPath, 100,
-            (p, attr) -> attr.isDirectory() && isIndex(p));
+                (p, attr) -> attr.isDirectory() && isIndex(p));
         try {
             stream.forEach((p) -> {
                 try {
@@ -164,12 +163,12 @@ public class IndexManager implements Managed {
     }
 
     private void deleteIndex(final String name) throws IOException {
-        final Index index = acquire(name);
-        try {
+        final Index index = cache.getIfPresent(name);
+        if (index == null) {
+            IOUtils.rm(indexRootPath(name));
+        } else {
             index.setDeleteOnClose(true);
             cache.invalidate(name);
-        } finally {
-            release(index);
         }
     }
 
@@ -227,17 +226,17 @@ public class IndexManager implements Managed {
 
     @Override
     public void start() throws IOException {
+        final IndexRemovalListener indexRemovalListener = new IndexRemovalListener();
         cache = Caffeine.newBuilder()
-            .recordStats(() -> new MetricsStatsCounter(metricRegistry, "IndexManager"))
-            .initialCapacity(maxIndexesOpen)
-            .maximumSize(maxIndexesOpen)
-            .expireAfterAccess(Duration.ofSeconds(idleSeconds))
-            .expireAfterWrite(Duration.ofSeconds(idleSeconds))
-            .refreshAfterWrite(Duration.ofSeconds(commitIntervalSeconds))
-            .scheduler(Scheduler.systemScheduler())
-            .removalListener(INDEX_CLOSER)
-            .evictionListener(INDEX_CLOSER)
-            .build(new IndexLoader());
+                .recordStats(() -> new MetricsStatsCounter(metricRegistry, "IndexManager"))
+                .initialCapacity(maxIndexesOpen)
+                .maximumSize(maxIndexesOpen)
+                .expireAfterAccess(Duration.ofSeconds(idleSeconds))
+                .expireAfterWrite(Duration.ofSeconds(idleSeconds))
+                .refreshAfterWrite(Duration.ofSeconds(commitIntervalSeconds))
+                .scheduler(Scheduler.systemScheduler())
+                .removalListener(indexRemovalListener)
+                .build(new IndexLoader());
     }
 
     @Override
@@ -245,36 +244,6 @@ public class IndexManager implements Managed {
         cache.invalidateAll();
     }
 
-    private Index getFromCache(final String name) throws IOException {
-        try {
-            return cache.get(name);
-        } catch (CompletionException e) {
-            if (e.getCause() instanceof IOException) {
-                throw (IOException) e.getCause();
-            }
-            throw e;
-        }
-    }
-
-    private void createNewIndex(final String name, final IndexDefinition indexDefinition) throws IOException {
-        // Validate index definiton
-        analyzerFactory.fromDefinition(indexDefinition);
-
-        // Persist definition
-        final Path path = indexDefinitionPath(name);
-        if (Files.exists(path)) {
-            throw new FileAlreadyExistsException(name + " already exists");
-        }
-        Files.createDirectories(path.getParent());
-        objectMapper.writeValue(path.toFile(), indexDefinition);
-    }
-
-    private Index openExistingIndex(final String name) throws IOException {
-        final Path path = indexPath(name);
-        final IndexDefinition indexDefinition = objectMapper.readValue(indexDefinitionPath(name).toFile(), IndexDefinition.class);
-        return indexFactory.open(path, indexDefinition);
-    }
-
     private boolean isIndex(final Path path) {
         return path.resolve("index_definition.json").toFile().exists();
     }
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Lucene9Index.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Lucene9Index.java
index fe86092d4..0c89afef2 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Lucene9Index.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Lucene9Index.java
@@ -81,7 +81,6 @@ class Lucene9Index extends Index {
             throws IOException {
         final Directory dir = new DirectIODirectory(FSDirectory.open(path));
         final IndexWriterConfig config = new IndexWriterConfig(analyzer);
-        config.setCommitOnClose(true);
         config.setUseCompoundFile(false);
         final IndexWriter writer = new IndexWriter(dir, config);
         final long updateSeq = getUpdateSeq(writer);
@@ -122,18 +121,15 @@ class Lucene9Index extends Index {
     }
 
     @Override
-    public void doClose(final boolean deleteOnClose) throws IOException {
-        if (deleteOnClose) {
-            // No need to commit in this case.
-            writer.rollback();
-            final Directory dir = writer.getDirectory();
-            for (final String name : dir.listAll()) {
-                dir.deleteFile(name);
-            }
-        }
+    public void doClose() throws IOException {
         writer.close();
     }
 
+    @Override
+    public boolean isOpen() {
+        return writer.isOpen();
+    }
+
     @Override
     public SearchResults doSearch(final SearchRequest request) throws IOException, QueryParserException {
         final Query query = newQueryParser().parse(request);
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/health/IndexManagerHealthCheck.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/health/IndexManagerHealthCheck.java
index e76d8da9c..315111534 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/health/IndexManagerHealthCheck.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/health/IndexManagerHealthCheck.java
@@ -44,18 +44,12 @@ public class IndexManagerHealthCheck extends HealthCheck {
 
         indexManager.create(name, new IndexDefinition(LUCENE_9, "standard", null));
         final Index index = indexManager.acquire(name);
-        try {
-            final DocumentUpdateRequest request = new DocumentUpdateRequest(1, null, Collections.emptyList());
-            index.update("foo", request);
-            index.commit();
-            index.setDeleteOnClose(true);
-            index.close();
-            return Result.healthy();
-        } catch (final IOException e) {
-            return Result.unhealthy(e);
-        } finally {
-            indexManager.release(index);
-        }
+        final DocumentUpdateRequest request = new DocumentUpdateRequest(1, null, Collections.emptyList());
+        index.update("foo", request);
+        index.commit();
+        index.setDeleteOnClose(true);
+        indexManager.invalidate(name);
+        return Result.healthy();
     }
 
 }
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/IndexResource.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/IndexResource.java
index ca7ee15c0..6cc2c1132 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/IndexResource.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/IndexResource.java
@@ -24,7 +24,9 @@ import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response.Status;
 
 import org.apache.couchdb.nouveau.api.DocumentDeleteRequest;
 import org.apache.couchdb.nouveau.api.DocumentUpdateRequest;
@@ -49,12 +51,7 @@ public class IndexResource {
     @GET
     @SuppressWarnings("resource")
     public IndexInfo indexInfo(@PathParam("name") String name) throws IOException {
-        final Index index = indexManager.acquire(name);
-        try {
-            return index.info();
-        } finally {
-            indexManager.release(index);
-        }
+        return indexManager.acquire(name).info();
     }
 
     @DELETE
@@ -71,24 +68,14 @@ public class IndexResource {
     @Timed
     @Path("/doc/{docId}")
     public void deleteDoc(@PathParam("name") String name, @PathParam("docId") String docId, @NotNull @Valid final DocumentDeleteRequest request) throws IOException {
-        final Index index = indexManager.acquire(name);
-        try {
-            index.delete(docId, request);
-        } finally {
-            indexManager.release(index);
-        }
+        indexManager.acquire(name).delete(docId, request);
     }
 
     @PUT
     @Timed
     @Path("/doc/{docId}")
     public void updateDoc(@PathParam("name") String name, @PathParam("docId") String docId, @NotNull @Valid final DocumentUpdateRequest request) throws IOException {
-        final Index index = indexManager.acquire(name);
-        try {
-            index.update(docId, request);
-        } finally {
-            indexManager.release(index);
-        }
+        indexManager.acquire(name).update(docId, request);
     }
 
 }
\ No newline at end of file
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/SearchResource.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/SearchResource.java
index 03f6c0c20..f93335921 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/SearchResource.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/SearchResource.java
@@ -48,12 +48,7 @@ public class SearchResource {
     @Path("/search")
     public SearchResults searchIndex(@PathParam("name") String name, @NotNull @Valid SearchRequest searchRequest)
             throws IOException, QueryParserException {
-        final Index index = indexManager.acquire(name);
-        try {
-            return index.search(searchRequest);
-        } finally {
-            indexManager.release(index);
-        }
+        return indexManager.acquire(name).search(searchRequest);
     }
 
 }
diff --git a/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/IntegrationTest.java b/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/IntegrationTest.java
index 66f774514..72e3dcb9a 100644
--- a/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/IntegrationTest.java
+++ b/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/IntegrationTest.java
@@ -54,7 +54,7 @@ public class IntegrationTest {
     );
 
     @Test
-    public void indexTest() {
+    public void indexTest() throws Exception{
         final String url = "http://localhost:" + APP.getLocalPort();
         final String indexName = "foo";
         final IndexDefinition indexDefinition = new IndexDefinition(LUCENE_9, "standard", null);
@@ -118,10 +118,6 @@ public class IntegrationTest {
                 .request()
                 .get();
 
-        //healthCheckResponse.bufferEntity();
-        //InputStream in = (InputStream) healthCheckResponse.getEntity();
-        //System.err.printf("health check response: %s\n", new String(in.readAllBytes()));
-
         assertThat(healthCheckResponse)
                 .extracting(Response::getStatus)
                 .isEqualTo(Response.Status.OK.getStatusCode());