All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.jackrabbit.oak.segment.OnlineCompactor Maven / Gradle / Ivy

/*
 * 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.segment;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Lists.newArrayList;
import static org.apache.jackrabbit.oak.api.Type.BINARIES;
import static org.apache.jackrabbit.oak.api.Type.BINARY;
import static org.apache.jackrabbit.oak.plugins.memory.BinaryPropertyState.binaryProperty;
import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
import static org.apache.jackrabbit.oak.plugins.memory.MultiBinaryPropertyState.binaryPropertyFromBlob;
import static org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.google.common.base.Supplier;
import org.apache.jackrabbit.oak.api.Blob;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeBuilder;
import org.apache.jackrabbit.oak.spi.blob.BlobStore;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.apache.jackrabbit.oak.spi.state.NodeStateDiff;

// FIXME OAK-6399 unify with Compactor (progress tracker? eager-flush, content based binary deduplication, unit tests, etc...)
/**
 * Instances of this class can be used to compact a node state. I.e. to create a clone
 * of a given node state without value sharing except for binaries. Binaries that are
 * stored in a list of bulk segments will still value share the bulk segments (but not
 * the list records).
 * A node can either be compacted on its own or alternatively the difference between
 * two nodes can be compacted on top of an already compacted node.
 */
public class OnlineCompactor {

    /**
     * Number of content updates that need to happen before the updates
     * are automatically purged to the underlying segments.
     */
    static final int UPDATE_LIMIT =
            Integer.getInteger("compaction.update.limit", 10000);

    @Nonnull
    private final SegmentWriter writer;

    @Nonnull
    private final SegmentReader reader;

    @Nullable
    private final BlobStore blobStore;

    @Nonnull
    private final Supplier cancel;

    @Nonnull
    private final Runnable onNode;

    /**
     * Create a new instance based on the passed arguments.
     * @param reader     segment reader used to read from the segments
     * @param writer     segment writer used to serialise to segments
     * @param blobStore  the blob store or {@code null} if none
     * @param cancel     a flag that can be used to cancel the compaction process
     * @param onNode     notification call back for each compacted node
     */
    public OnlineCompactor(
            @Nonnull SegmentReader reader,
            @Nonnull SegmentWriter writer,
            @Nullable BlobStore blobStore,
            @Nonnull Supplier cancel,
            @Nonnull Runnable onNode) {
        this.writer = checkNotNull(writer);
        this.reader = checkNotNull(reader);
        this.blobStore = blobStore;
        this.cancel = checkNotNull(cancel);
        this.onNode = checkNotNull(onNode);
    }

    /**
     * Compact a given {@code state}
     * @param state  the node state to compact
     * @return       the compacted node state or {@code null} if cancelled.
     * @throws IOException
     */
    @CheckForNull
    public SegmentNodeState compact(@Nonnull NodeState state) throws IOException {
        return compact(EMPTY_NODE, state, EMPTY_NODE);
    }

    /**
     * compact the differences between {@code after} and {@code before} on top of {@code ont}.
     * @param before   the node state to diff against from {@code after}
     * @param after    the node state diffed against {@code before}
     * @param onto     the node state compacted onto
     * @return         the compacted node state or {@code null} if cancelled.
     * @throws IOException
     */
    @CheckForNull
    public SegmentNodeState compact(
            @Nonnull NodeState before,
            @Nonnull NodeState after,
            @Nonnull NodeState onto)
    throws IOException {
        checkNotNull(before);
        checkNotNull(after);
        checkNotNull(onto);
        return new CompactDiff(onto).diff(before, after);
    }

    @CheckForNull
    private static ByteBuffer getStableIdBytes(NodeState state) {
        if (state instanceof SegmentNodeState) {
            return ((SegmentNodeState) state).getStableIdBytes();
        } else {
            return null;
        }
    }

    private class CompactDiff implements NodeStateDiff {
        @Nonnull
        private MemoryNodeBuilder builder;

        @Nonnull
        private final NodeState base;

        @CheckForNull
        private IOException exception;

        private long modCount;

        private void updated() throws IOException {
            if (++modCount % UPDATE_LIMIT == 0) {
                RecordId newBaseId = writer.writeNode(builder.getNodeState(), null);
                SegmentNodeState newBase = new SegmentNodeState(reader, writer, blobStore, newBaseId);
                builder = new MemoryNodeBuilder(newBase);
            }
        }

        CompactDiff(@Nonnull NodeState base) {
            this.builder = new MemoryNodeBuilder(checkNotNull(base));
            this.base = base;
        }

        @CheckForNull
        SegmentNodeState diff(@Nonnull NodeState before, @Nonnull NodeState after) throws IOException {
            boolean success = after.compareAgainstBaseState(before, new CancelableDiff(this, cancel));
            if (exception != null) {
                throw new IOException(exception);
            } else if (success) {
                NodeState nodeState = builder.getNodeState();
                checkState(modCount == 0 || !(nodeState instanceof SegmentNodeState));
                RecordId nodeId = writer.writeNode(nodeState, getStableIdBytes(after));
                onNode.run();
                return new SegmentNodeState(reader, writer, blobStore, nodeId);
            } else {
                return null;
            }
        }

        @Override
        public boolean propertyAdded(@Nonnull PropertyState after) {
            builder.setProperty(compact(after));
            return true;
        }

        @Override
        public boolean propertyChanged(@Nonnull PropertyState before, @Nonnull PropertyState after) {
            builder.setProperty(compact(after));
            return true;
        }

        @Override
        public boolean propertyDeleted(PropertyState before) {
            builder.removeProperty(before.getName());
            return true;
        }

        @Override
        public boolean childNodeAdded(@Nonnull String name, @Nonnull NodeState after) {
            try {
                SegmentNodeState compacted = compact(after);
                if (compacted != null) {
                    updated();
                    builder.setChildNode(name, compacted);
                    return true;
                } else {
                    return false;
                }
            } catch (IOException e) {
                exception = e;
                return false;
            }
        }

        @Override
        public boolean childNodeChanged(@Nonnull String name, @Nonnull NodeState before, @Nonnull NodeState after) {
            try {
                SegmentNodeState compacted = compact(before, after, base.getChildNode(name));
                if (compacted != null) {
                    updated();
                    builder.setChildNode(name, compacted);
                    return true;
                } else {
                    return false;
                }
            } catch (IOException e) {
                exception = e;
                return false;
            }
        }

        @Override
        public boolean childNodeDeleted(String name, NodeState before) {
            try {
                updated();
                builder.getChildNode(name).remove();
                return true;
            } catch (IOException e) {
                exception = e;
                return false;
            }
        }
    }

    @Nonnull
    private static PropertyState compact(@Nonnull PropertyState property) {
        String name = property.getName();
        Type type = property.getType();
        if (type == BINARY) {
            return binaryProperty(name, property.getValue(Type.BINARY));
        } else if (type == BINARIES) {
            List blobs = newArrayList();
            for (Blob blob : property.getValue(BINARIES)) {
                blobs.add(blob);
            }
            return binaryPropertyFromBlob(name, blobs);
        } else {
            return createProperty(name, property.getValue(type), type);
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy