Author: chetanm
Date: Fri Nov 18 12:25:15 2016
New Revision: 1770371

URL: http://svn.apache.org/viewvc?rev=1770371&view=rev
Log:
OAK-4836 - Avoid excessive logging in case of corrupt index or mis-configured 
index defnition

Adding BadIndexTracker which tracks bad indexes and controls access to them

Added:
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/BadIndexTracker.java
   (with props)
    
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/BadIndexTrackerTest.java
   (with props)
    
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/VirtualTicker.java
   (with props)
Modified:
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexTracker.java
    
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexTrackerTest.java

Added: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/BadIndexTracker.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/BadIndexTracker.java?rev=1770371&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/BadIndexTracker.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/BadIndexTracker.java
 Fri Nov 18 12:25:15 2016
@@ -0,0 +1,158 @@
+/*
+ * 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.jackrabbit.oak.plugins.index.lucene;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Throwables;
+import com.google.common.base.Ticker;
+import com.google.common.collect.Maps;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class BadIndexTracker {
+    /**
+     * Time interval in millis after which a bad index would be accessed again
+     * to check if it has been fixed
+     */
+    private static final long DEFAULT_RECHECK_INTERVAL = 
TimeUnit.MINUTES.toMillis(15);
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private final Map<String, BadIndexInfo> badIndexes = 
Maps.newConcurrentMap();
+    private final long recheckIntervalMillis;
+    private Ticker ticker = Ticker.systemTicker();
+    private int indexerCycleCount;
+
+    public BadIndexTracker() {
+        this(DEFAULT_RECHECK_INTERVAL);
+    }
+
+    public BadIndexTracker(long recheckIntervalMillis) {
+        this.recheckIntervalMillis = recheckIntervalMillis;
+        log.info("Bad Index recheck interval set to {} seconds",
+                TimeUnit.MILLISECONDS.toSeconds(recheckIntervalMillis));
+    }
+
+    public void markGoodIndexes(Set<String> updatedIndexPaths) {
+        indexerCycleCount++;
+        for (String indexPath : updatedIndexPaths) {
+            markGoodIndex(indexPath);
+        }
+    }
+
+    public void markGoodIndex(String indexPath) {
+        BadIndexInfo info = badIndexes.remove(indexPath);
+        if (info != null) {
+            log.info("Index [{}] which was not working {} is found to be 
healthy again",
+                    indexPath, info.getStats());
+        }
+    }
+
+    public void markBadIndex(String path, Throwable e) {
+        BadIndexInfo badIndex = badIndexes.get(path);
+        if (badIndex == null) {
+            badIndexes.put(path, new BadIndexInfo(path, e));
+            log.error("Could not access the Lucene index at [{}]", path, e);
+        } else {
+            badIndex.failedAccess(e);
+            log.error("Could not access the Lucene index at [{}] . {}",
+                    path, badIndex.getStats(), e);
+        }
+    }
+
+    public boolean isIgnoredBadIndex(String path) {
+        BadIndexInfo badIdx = badIndexes.get(path);
+        if (badIdx == null) {
+            return false;
+        }
+        return !badIdx.tryAgain();
+    }
+
+    public Set<String> getIndexPaths() {
+        return badIndexes.keySet();
+    }
+
+    public long getRecheckIntervalMillis() {
+        return recheckIntervalMillis;
+    }
+
+    BadIndexInfo getInfo(String indexPath){
+        return badIndexes.get(indexPath);
+    }
+
+    void setTicker(Ticker ticker) {
+        this.ticker = ticker;
+    }
+
+    class BadIndexInfo {
+        final String path;
+        final int lastIndexerCycleCount = indexerCycleCount;
+        private final Stopwatch created = Stopwatch.createStarted(ticker);
+        private final Stopwatch watch = Stopwatch.createStarted(ticker);
+        private String exception;
+        private int accessCount;
+        private int failedAccessCount;
+
+        public BadIndexInfo(String path, Throwable e) {
+            this.path = path;
+            this.exception = Throwables.getStackTraceAsString(e);
+        }
+
+        public boolean tryAgain() {
+            accessCount++;
+
+            if (watch.elapsed(TimeUnit.MILLISECONDS) > recheckIntervalMillis) {
+                watch.reset().start();
+                return true;
+            }
+
+            if (log.isDebugEnabled()) {
+                log.debug("Ignoring index [{}] which is not working correctly 
{}", path, getStats());
+            }
+            return false;
+        }
+
+        public String getStats() {
+            return String.format("since %s ,%d indexing cycles, accessed %d 
times",
+                    created, getCycleCount(), accessCount);
+        }
+
+        public int getFailedAccessCount() {
+            return failedAccessCount;
+        }
+
+        public int getAccessCount() {
+            return accessCount;
+        }
+
+        private int getCycleCount() {
+            return indexerCycleCount - lastIndexerCycleCount;
+        }
+
+        public void failedAccess(Throwable e) {
+            failedAccessCount++;
+            exception = Throwables.getStackTraceAsString(e);
+        }
+    }
+
+}

Propchange: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/BadIndexTracker.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexTracker.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexTracker.java?rev=1770371&r1=1770370&r2=1770371&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexTracker.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexTracker.java
 Fri Nov 18 12:25:15 2016
@@ -37,6 +37,7 @@ import java.util.Set;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 
+import com.google.common.collect.Sets;
 import org.apache.jackrabbit.oak.commons.PathUtils;
 import org.apache.jackrabbit.oak.plugins.index.lucene.hybrid.NRTIndexFactory;
 import 
org.apache.jackrabbit.oak.plugins.index.lucene.reader.DefaultIndexReaderFactory;
@@ -64,6 +65,7 @@ public class IndexTracker {
 
     private final LuceneIndexReaderFactory readerFactory;
     private final NRTIndexFactory nrtFactory;
+    private final BadIndexTracker badIndexTracker = new BadIndexTracker();
 
     private NodeState root = EMPTY_NODE;
 
@@ -116,10 +118,12 @@ public class IndexTracker {
         Map<String, IndexNode> original = indices;
         final Map<String, IndexNode> updates = newHashMap();
 
-        List<Editor> editors = newArrayListWithCapacity(original.size());
-        for (Map.Entry<String, IndexNode> entry : original.entrySet()) {
-            final String path = entry.getKey();
+        Set<String> indexPaths = Sets.newHashSet();
+        indexPaths.addAll(original.keySet());
+        indexPaths.addAll(badIndexTracker.getIndexPaths());
 
+        List<Editor> editors = newArrayListWithCapacity(indexPaths.size());
+        for (final String path : indexPaths) {
             editors.add(new SubtreeEditor(new DefaultEditor() {
                 @Override
                 public void leave(NodeState before, NodeState after) {
@@ -144,6 +148,8 @@ public class IndexTracker {
                     .putAll(filterValues(updates, notNull()))
                     .build();
 
+            badIndexTracker.markGoodIndexes(updates.keySet());
+
             //This might take some time as close need to acquire the
             //write lock which might be held by current running searches
             //Given that Tracker is now invoked from a BackgroundObserver
@@ -151,7 +157,9 @@ public class IndexTracker {
             for (String path : updates.keySet()) {
                 IndexNode index = original.get(path);
                 try {
-                    index.close();
+                    if (index != null) {
+                        index.close();
+                    }
                 } catch (IOException e) {
                     log.error("Failed to close Lucene index at " + path, e);
                 }
@@ -187,6 +195,10 @@ public class IndexTracker {
         return indices.keySet();
     }
 
+    BadIndexTracker getBadIndexTracker() {
+        return badIndexTracker;
+    }
+
     private synchronized IndexNode findIndexNode(String path) {
         // Retry the lookup from acquireIndexNode now that we're
         // synchronized. The acquire() call is guaranteed to succeed
@@ -197,6 +209,10 @@ public class IndexTracker {
             return index;
         }
 
+        if (badIndexTracker.isIgnoredBadIndex(path)){
+            return null;
+        }
+
         NodeState node = root;
         for (String name : PathUtils.elements(path)) {
             node = node.getChildNode(name);
@@ -211,15 +227,18 @@ public class IndexTracker {
                             .putAll(indices)
                             .put(path, index)
                             .build();
+                    badIndexTracker.markGoodIndex(path);
                     return index;
                 }
             } else if (node.exists()) {
                 log.warn("Cannot open Lucene Index at path {} as the index is 
not of type {}", path, TYPE_LUCENE);
             }
-        } catch (IOException e) {
-            log.error("Could not access the Lucene index at " + path, e);
+        } catch (Throwable e) {
+            badIndexTracker.markBadIndex(path, e);
         }
 
         return null;
     }
+
+
 }

Added: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/BadIndexTrackerTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/BadIndexTrackerTest.java?rev=1770371&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/BadIndexTrackerTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/BadIndexTrackerTest.java
 Fri Nov 18 12:25:15 2016
@@ -0,0 +1,75 @@
+/*
+ * 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.jackrabbit.oak.plugins.index.lucene;
+
+import java.util.Collections;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.hasItem;
+import static org.junit.Assert.*;
+
+public class BadIndexTrackerTest {
+
+    private VirtualTicker ticker = new VirtualTicker();
+    private BadIndexTracker tracker = new BadIndexTracker();
+
+    @Test
+    public void basics() throws Exception{
+        tracker.markBadIndex("foo", new Exception());
+        assertThat(tracker.getIndexPaths(), hasItem("foo"));
+
+        assertTrue(tracker.isIgnoredBadIndex("foo"));
+
+        tracker.markGoodIndex("foo");
+        assertFalse(tracker.isIgnoredBadIndex("foo"));
+    }
+
+    @Test
+    public void updatedIndexesMakesGood() throws Exception{
+        tracker.markBadIndex("foo", new Exception());
+        assertTrue(tracker.isIgnoredBadIndex("foo"));
+
+        tracker.markGoodIndexes(Collections.singleton("foo"));
+        assertFalse(tracker.isIgnoredBadIndex("foo"));
+    }
+
+    @Test
+    public void recheckDelay() throws Exception{
+        tracker = new BadIndexTracker(100);
+        tracker.setTicker(ticker);
+        tracker.markBadIndex("foo", new Exception());
+        ticker.addTime(50, TimeUnit.MILLISECONDS);
+
+        assertTrue(tracker.isIgnoredBadIndex("foo"));
+
+        ticker.addTime(30, TimeUnit.MILLISECONDS);
+        assertTrue(tracker.isIgnoredBadIndex("foo"));
+
+        //Now cross the threshold
+        ticker.addTime(30, TimeUnit.MILLISECONDS);
+        assertFalse(tracker.isIgnoredBadIndex("foo"));
+
+        //However index is still considered bad
+        assertThat(tracker.getIndexPaths(), hasItem("foo"));
+    }
+
+}
\ No newline at end of file

Propchange: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/BadIndexTrackerTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexTrackerTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexTrackerTest.java?rev=1770371&r1=1770370&r2=1770371&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexTrackerTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexTrackerTest.java
 Fri Nov 18 12:25:15 2016
@@ -19,19 +19,31 @@
 
 package org.apache.jackrabbit.oak.plugins.index.lucene;
 
+import java.util.Collections;
+import java.util.concurrent.TimeUnit;
+
 import com.google.common.collect.ImmutableSet;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.commons.PathUtils;
 import org.apache.jackrabbit.oak.plugins.index.IndexUpdateProvider;
+import org.apache.jackrabbit.oak.plugins.memory.ArrayBasedBlob;
+import org.apache.jackrabbit.oak.plugins.memory.PropertyStates;
 import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
 import org.apache.jackrabbit.oak.spi.commit.EditorHook;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
+import 
org.apache.jackrabbit.oak.plugins.index.lucene.BadIndexTracker.BadIndexInfo;
 import org.junit.Test;
 
 import static 
org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME;
 import static 
org.apache.jackrabbit.oak.plugins.index.lucene.util.LuceneIndexHelper.newLucenePropertyIndexDefinition;
 import static 
org.apache.jackrabbit.oak.plugins.nodetype.write.InitialContent.INITIAL_CONTENT;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
+@SuppressWarnings("UnusedAssignment")
 public class IndexTrackerTest {
     private static final EditorHook HOOK = new EditorHook(
             new IndexUpdateProvider(
@@ -69,4 +81,99 @@ public class IndexTrackerTest {
         assertEquals(0, tracker.getIndexNodePaths().size());
     }
 
+    @Test
+    public void badIndexAccess() throws Exception{
+        createIndex("foo");
+
+        //1. Create and populate index
+        NodeState before = builder.getNodeState();
+        builder.setProperty("foo", "bar");
+        NodeState after = builder.getNodeState();
+
+        NodeState indexed = HOOK.processCommit(before, after, 
CommitInfo.EMPTY);
+        tracker.update(indexed);
+
+        IndexNode indexNode = tracker.acquireIndexNode("/oak:index/foo");
+        indexNode.release();
+
+        assertTrue(tracker.getBadIndexTracker().getIndexPaths().isEmpty());
+
+        //2. Corrupt the index
+        builder = indexed.builder();
+        indexed = corruptIndex("/oak:index/foo");
+
+        //3. Recreate the tracker as we cannot push corrupt index in existing 
tracker
+        //As diffAndUpdate would fail and existing IndexNode would not be 
changed
+        tracker = new IndexTracker();
+        tracker.update(indexed);
+
+        VirtualTicker ticker = new VirtualTicker();
+        tracker.getBadIndexTracker().setTicker(ticker);
+
+        indexNode = tracker.acquireIndexNode("/oak:index/foo");
+
+        //Index must be corrupted hence it must be null
+        assertNull(indexNode);
+        
assertTrue(tracker.getBadIndexTracker().getIndexPaths().contains("/oak:index/foo"));
+
+        BadIndexInfo badIdxInfo = 
tracker.getBadIndexTracker().getInfo("/oak:index/foo");
+        assertNotNull(badIdxInfo);
+        assertEquals(0, badIdxInfo.getAccessCount());
+
+        //Try to access again
+        indexNode = tracker.acquireIndexNode("/oak:index/foo");
+        assertEquals(1, badIdxInfo.getAccessCount());
+        assertEquals(0, badIdxInfo.getFailedAccessCount());
+
+        indexNode = tracker.acquireIndexNode("/oak:index/foo");
+        assertEquals(2, badIdxInfo.getAccessCount());
+        assertEquals(0, badIdxInfo.getFailedAccessCount());
+
+        //5. Move clock forward
+        ticker.addTime(tracker.getBadIndexTracker().getRecheckIntervalMillis() 
+ 1, TimeUnit.MILLISECONDS);
+
+        //Now index access must be attempted again
+        indexNode = tracker.acquireIndexNode("/oak:index/foo");
+        assertEquals(3, badIdxInfo.getAccessCount());
+        assertEquals(1, badIdxInfo.getFailedAccessCount());
+
+        //6. Now lets reindex to fix the corruption
+        builder = indexed.builder();
+        before = indexed;
+        after = reindex("/oak:index/foo");
+
+        indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY);
+        tracker.update(indexed);
+
+        //7. Now indexNode should be accessible
+        indexNode = tracker.acquireIndexNode("/oak:index/foo");
+        assertNotNull(indexNode);
+
+        //And this index would not be considered bad
+        badIdxInfo = tracker.getBadIndexTracker().getInfo("/oak:index/foo");
+        assertNull(badIdxInfo);
+    }
+
+    private NodeState corruptIndex(String indexPath) {
+        NodeBuilder dir = TestUtil.child(builder, PathUtils.concat(indexPath, 
":data"));
+        for (String name : dir.getChildNodeNames()){
+            if (!"segments.gen".equals(name)){
+                
dir.getChildNode(name).setProperty(PropertyStates.createProperty("jcr:data", 
Collections
+                        .singletonList(new ArrayBasedBlob("foo".getBytes())), 
Type.BINARIES));
+            }
+        }
+        return builder.getNodeState();
+    }
+
+    private NodeState reindex(String indexPath){
+        NodeBuilder dir = TestUtil.child(builder, indexPath);
+        dir.setProperty("reindex", true);
+        return builder.getNodeState();
+    }
+
+    private void createIndex(String propName){
+        NodeBuilder index = builder.child(INDEX_DEFINITIONS_NAME);
+        newLucenePropertyIndexDefinition(index, propName, 
ImmutableSet.of(propName), null);
+    }
+
 }
\ No newline at end of file

Added: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/VirtualTicker.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/VirtualTicker.java?rev=1770371&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/VirtualTicker.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/VirtualTicker.java
 Fri Nov 18 12:25:15 2016
@@ -0,0 +1,41 @@
+/*
+ * 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.jackrabbit.oak.plugins.index.lucene;
+
+import java.util.concurrent.TimeUnit;
+
+import com.google.common.base.Ticker;
+
+public class VirtualTicker extends Ticker{
+    private long time;
+
+    @Override
+    public long read() {
+        return time;
+    }
+
+    public void setTime(long time, TimeUnit unit){
+        this.time = unit.toNanos(time);
+    }
+
+    public void addTime(long delta, TimeUnit unit){
+        this.time += unit.toNanos(delta);
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/VirtualTicker.java
------------------------------------------------------------------------------
    svn:eol-style = native


Reply via email to