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 22:54:22 UTC

[couchdb] 05/05: simplify the index management

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 32a5000afabd4d1138baf9b941ee7203e03fbd43
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..696097daa 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,46 @@ 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()) {
+                    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 +111,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 +147,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 +162,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 +225,18 @@ 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())
+                .evictionListener(indexRemovalListener)
+                .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());