blob: 5edd5f8e2a2be8096d9f9601068fe58df5e1785d [file] [log] [blame]
/*
* 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.document.secondary;
import com.google.common.collect.EvictingQueue;
import org.apache.jackrabbit.oak.plugins.document.AbstractDocumentNodeState;
import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStateCache;
import org.apache.jackrabbit.oak.plugins.document.NodeStateDiffer;
import org.apache.jackrabbit.oak.plugins.document.Path;
import org.apache.jackrabbit.oak.plugins.document.RevisionVector;
import org.apache.jackrabbit.oak.spi.filter.PathFilter;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.apache.jackrabbit.oak.spi.state.NodeStateUtils;
import org.apache.jackrabbit.oak.spi.state.NodeStore;
import org.apache.jackrabbit.oak.stats.MeterStats;
import org.apache.jackrabbit.oak.stats.StatisticsProvider;
import org.apache.jackrabbit.oak.stats.StatsOptions;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SecondaryStoreCache implements DocumentNodeStateCache, SecondaryStoreRootObserver {
private final Logger log = LoggerFactory.getLogger(getClass());
private static final AbstractDocumentNodeState[] EMPTY = new AbstractDocumentNodeState[0];
private final NodeStore store;
private final PathFilter pathFilter;
private final NodeStateDiffer differ;
private final MeterStats unknownPaths;
private final MeterStats knownMissed;
private final MeterStats knownMissedOld;
private final MeterStats knownMissedNew;
private final MeterStats knownMissedInRange;
private final MeterStats headRevMatched;
private final MeterStats prevRevMatched;
private final int maxSize = 10000;
private final EvictingQueue<AbstractDocumentNodeState> queue;
private volatile AbstractDocumentNodeState[] previousRoots = EMPTY;
public SecondaryStoreCache(NodeStore nodeStore, NodeStateDiffer differ, PathFilter pathFilter,
StatisticsProvider statisticsProvider) {
this.differ = differ;
this.store = nodeStore;
this.pathFilter = pathFilter;
this.unknownPaths = statisticsProvider.getMeter("DOCUMENT_CACHE_SEC_UNKNOWN", StatsOptions.DEFAULT);
this.knownMissed = statisticsProvider.getMeter("DOCUMENT_CACHE_SEC_KNOWN_MISSED", StatsOptions.DEFAULT);
this.knownMissedOld = statisticsProvider.getMeter("DOCUMENT_CACHE_SEC_KNOWN_MISSED_OLD", StatsOptions.DEFAULT);
this.knownMissedNew = statisticsProvider.getMeter("DOCUMENT_CACHE_SEC_KNOWN_MISSED_NEW", StatsOptions.DEFAULT);
this.knownMissedInRange = statisticsProvider.getMeter("DOCUMENT_CACHE_SEC_KNOWN_MISSED_IN_RANGE", StatsOptions
.DEFAULT);
this.headRevMatched = statisticsProvider.getMeter("DOCUMENT_CACHE_SEC_HEAD", StatsOptions.DEFAULT);
this.prevRevMatched = statisticsProvider.getMeter("DOCUMENT_CACHE_SEC_OLD", StatsOptions.DEFAULT);
this.queue = EvictingQueue.create(maxSize);
}
@Nullable
@Override
public AbstractDocumentNodeState getDocumentNodeState(Path path, RevisionVector rootRevision,
RevisionVector lastRev) {
//TODO We might need skip the calls if they occur due to SecondaryStoreObserver
//doing the diff or in the startup when we try to sync the state
String p = path.toString();
PathFilter.Result result = pathFilter.filter(p);
if (result != PathFilter.Result.INCLUDE) {
unknownPaths.mark();
return null;
}
if (!DelegatingDocumentNodeState.hasMetaProps(store.getRoot())){
return null;
}
AbstractDocumentNodeState currentRoot = DelegatingDocumentNodeState.wrap(store.getRoot(), differ);
//If the root rev is < lastRev then secondary store is lagging and would
//not have the matching result
if (lastRev.compareTo(currentRoot.getLastRevision()) > 0){
return null;
}
AbstractDocumentNodeState nodeState = findByMatchingLastRev(currentRoot, path, lastRev);
if (nodeState != null){
headRevMatched.mark();
return nodeState;
}
AbstractDocumentNodeState matchingRoot = findMatchingRoot(rootRevision);
if (matchingRoot != null){
NodeState state = NodeStateUtils.getNode(matchingRoot, p);
if (state.exists()){
AbstractDocumentNodeState docState = asDocState(state);
prevRevMatched.mark();
return docState;
}
}
knownMissed.mark();
return null;
}
@Override
public boolean isCached(Path path) {
return pathFilter.filter(path.toString()) == PathFilter.Result.INCLUDE;
}
@Nullable
private AbstractDocumentNodeState findByMatchingLastRev(AbstractDocumentNodeState root, Path path,
RevisionVector lastRev){
NodeState state = root;
for (String name : path.elements()) {
state = state.getChildNode(name);
if (!state.exists()){
return null;
}
//requested lastRev is > current node lastRev then no need to check further
if (lastRev.compareTo(asDocState(state).getLastRevision()) > 0){
return null;
}
}
AbstractDocumentNodeState docState = asDocState(state);
if (lastRev.equals(docState.getLastRevision())) {
headRevMatched.mark();
return docState;
}
return null;
}
@Nullable
private AbstractDocumentNodeState findMatchingRoot(RevisionVector rr) {
if (isEmpty()){
return null;
}
//Use a local variable as the array can get changed in process
AbstractDocumentNodeState[] roots = previousRoots;
AbstractDocumentNodeState latest = roots[roots.length - 1];
AbstractDocumentNodeState oldest = roots[0];
if (rr.compareTo(latest.getRootRevision()) > 0){
knownMissedNew.mark();
return null;
}
if (rr.compareTo(oldest.getRootRevision()) < 0){
knownMissedOld.mark();
return null;
}
AbstractDocumentNodeState result = findMatchingRoot(roots, rr);
if (result != null){
return result;
}
knownMissedInRange.mark();
return null;
}
@Override
public void contentChanged(@NotNull AbstractDocumentNodeState root) {
synchronized (queue){
//TODO Possibly can be improved
queue.add(root);
previousRoots = queue.toArray(EMPTY);
}
}
private boolean isEmpty() {
return previousRoots.length == 0;
}
static AbstractDocumentNodeState findMatchingRoot(AbstractDocumentNodeState[] roots, RevisionVector key) {
int low = 0;
int high = roots.length - 1;
//Perform a binary search as the array is sorted in ascending order
while (low <= high) {
int mid = (low + high) >>> 1;
AbstractDocumentNodeState midVal = roots[mid];
int cmp = midVal.getRootRevision().compareTo(key);
if (cmp < 0) {
low = mid + 1;
} else if (cmp > 0) {
high = mid - 1;
} else {
return midVal; // key found
}
}
return null; // key not found.
}
private static AbstractDocumentNodeState asDocState(NodeState state) {
return (AbstractDocumentNodeState)state;
}
}