Package voldemort.store.readonly

Source Code of voldemort.store.readonly.ReadOnlyStorageEngine$KeyValueLocation

/*
* Copyright 2008-2009 LinkedIn, Inc
*
* 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 voldemort.store.readonly;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.log4j.Logger;

import voldemort.VoldemortException;
import voldemort.annotations.jmx.JmxGetter;
import voldemort.annotations.jmx.JmxOperation;
import voldemort.store.NoSuchCapabilityException;
import voldemort.store.StorageEngine;
import voldemort.store.StoreCapabilityType;
import voldemort.store.StoreUtils;
import voldemort.utils.ByteArray;
import voldemort.utils.ByteUtils;
import voldemort.utils.ClosableIterator;
import voldemort.utils.Pair;
import voldemort.utils.Utils;
import voldemort.versioning.Version;
import voldemort.versioning.Versioned;

import com.google.common.collect.Lists;

/**
* A read-only store that fronts a big file
*
* @author jay
*
*/
public class ReadOnlyStorageEngine implements StorageEngine<ByteArray, byte[]> {

    private static Logger logger = Logger.getLogger(ReadOnlyStorageEngine.class);

    /*
     * The overhead for each cache element is the key size + 4 byte array length
     * + 12 byte object overhead + 8 bytes for a 64-bit reference to the thing
     */
    public static final int MEMORY_OVERHEAD_PER_KEY = ReadOnlyUtils.KEY_HASH_SIZE + 4 + 12 + 8;

    private final String name;
    private final int numBackups;
    private final File storeDir;
    private final ReadWriteLock fileModificationLock;
    private final SearchStrategy searchStrategy;
    private volatile ChunkedFileSet fileSet;
    private volatile boolean isOpen;

    /**
     * Create an instance of the store
     *
     * @param name The name of the store
     * @param storageDir The directory in which the .data and .index files
     *        reside
     * @param numBackups The number of backups of these files to retain
     * @param numFileHandles The number of file descriptors to keep pooled for
     *        each file
     * @param bufferWaitTimeoutMs The maximum time to wait to acquire a file
     *        handle
     * @param maxCacheSizeBytes The maximum size of the cache, in bytes. The
     *        actual size of the cache will be the largest power of two lower
     *        than this number
     */
    public ReadOnlyStorageEngine(String name,
                                 SearchStrategy searchStrategy,
                                 File storeDir,
                                 int numBackups) {
        this.storeDir = storeDir;
        this.numBackups = numBackups;
        this.name = Utils.notNull(name);
        this.searchStrategy = searchStrategy;
        this.fileSet = null;
        /*
         * A lock that blocks reads during swap(), open(), and close()
         * operations
         */
        this.fileModificationLock = new ReentrantReadWriteLock();
        this.isOpen = false;
        open();
    }

    /**
     * Open the store
     */
    public void open() {
        /* acquire modification lock */
        fileModificationLock.writeLock().lock();
        try {
            /* check that the store is currently closed */
            if(isOpen)
                throw new IllegalStateException("Attempt to open already open store.");

            File version0 = new File(storeDir, "version-0");
            version0.mkdirs();
            this.fileSet = new ChunkedFileSet(version0);
            isOpen = true;
        } finally {
            fileModificationLock.writeLock().unlock();
        }
    }

    /**
     * Close the store.
     */
    public void close() throws VoldemortException {
        logger.debug("Close called for read-only store.");
        this.fileModificationLock.writeLock().lock();

        try {
            if(isOpen) {
                this.isOpen = false;
                fileSet.close();
            } else {
                logger.debug("Attempt to close already closed store " + getName());
            }
        } finally {
            this.fileModificationLock.writeLock().unlock();
        }
    }

    /**
     * Swap the current index and data files for a new pair
     *
     * @param newIndexFile The path to the new index file
     * @param newDataFile The path to the new data file
     */
    @JmxOperation(description = "swapFiles(newIndexFile, newDataFile) changes this store "
                                + " to use the given index and data file.")
    public void swapFiles(String newStoreDirectory) {
        logger.info("Swapping files for store '" + getName() + "' from " + newStoreDirectory);
        File newDataDir = new File(newStoreDirectory);
        if(!newDataDir.exists())
            throw new VoldemortException("File " + newDataDir.getAbsolutePath()
                                         + " does not exist.");

        logger.info("Acquiring write lock on '" + getName() + "':");
        fileModificationLock.writeLock().lock();
        boolean success = false;
        try {
            close();
            logger.info("Renaming data and index files for '" + getName() + "':");
            shiftBackupsRight();
            // copy in new files
            logger.info("Setting primary files for store '" + getName() + "' to "
                        + newStoreDirectory);
            File destDir = new File(storeDir, "version-0");
            success = newDataDir.renameTo(destDir);

            // open the new store
            if(success) {
                try {
                    open();
                } catch(Exception e) {
                    logger.error(e);
                    success = false;
                }
            } else {
                logger.error("Renaming " + newDataDir.getAbsolutePath() + " to "
                             + destDir.getAbsolutePath() + " failed!");
            }
        } finally {
            try {
                // we failed to do the swap, attempt a rollback
                if(!success)
                    rollback();
            } finally {
                fileModificationLock.writeLock().unlock();
                if(success)
                    logger.info("Swap operation completed successfully on store " + getName()
                                + ", releasing lock.");
                else
                    logger.error("Swap operation failed.");
            }
        }
        // okay we have released the lock and the store is now open again, it is
        // safe to do a potentially slow delete if we have one too many backups
        File extraBackup = new File(storeDir, "version-" + (numBackups + 1));
        if(extraBackup.exists())
            deleteAsync(extraBackup);
    }

    /**
     * Delete the given file in a seperate thread
     *
     * @param file The file to delete
     */
    public void deleteAsync(final File file) {
        new Thread(new Runnable() {

            public void run() {
                try {
                    logger.info("Deleting file " + file);
                    Utils.rm(file);
                    logger.info("Delete completed successfully.");
                } catch(Exception e) {
                    logger.error(e);
                }
            }
        }, "background-file-delete").start();
    }

    @JmxOperation(description = "Rollback to the most recent backup of the current store.")
    public void rollback() {
        logger.info("Rolling back store '" + getName() + "' to version 1.");
        fileModificationLock.writeLock().lock();
        try {
            if(isOpen)
                close();
            File backup = new File(storeDir, "version-1");
            if(!backup.exists())
                throw new VoldemortException("Version 1 does not exists, nothing to roll back to.");
            shiftBackupsLeft();
            open();
        } finally {
            fileModificationLock.writeLock().unlock();
            logger.info("Rollback operation completed on '" + getName() + "', releasing lock.");
        }
    }

    /**
     * Shift all store versions so that 1 becomes 0, 2 becomes 1, etc.
     */
    private void shiftBackupsLeft() {
        if(isOpen)
            throw new VoldemortException("Can't move backup files while store is open.");

        // Turn the current data into a .bak so we can take a look at it
        // manually if we want
        File primary = new File(storeDir, "version-0");
        DateFormat df = new SimpleDateFormat("MM-dd-yyyy");
        if(primary.exists())
            Utils.move(primary, new File(storeDir, "version-0." + df.format(new Date()) + ".bak"));

        shiftBackupsLeft(0);
    }

    private void shiftBackupsLeft(int beginShift) {
        File source = new File(storeDir, "version-" + Integer.toString(beginShift + 1));
        File dest = new File(storeDir, "version-" + Integer.toString(beginShift));

        // if the source file doesn't exist there is nothing to shift
        if(!source.exists())
            return;

        // rename the file
        source.renameTo(dest);

        // now rename any remaining files
        shiftBackupsLeft(beginShift + 1);
    }

    /**
     * Shift all store versions so that 0 becomes 1, 1 becomes 2, etc.
     */
    private void shiftBackupsRight() {
        if(isOpen)
            throw new VoldemortException("Can't move backup files while store is open.");
        shiftBackupsRight(0);
    }

    private void shiftBackupsRight(int beginShift) {
        if(isOpen)
            throw new VoldemortException("Can't move backup files while store is open.");

        File source = new File(storeDir, "version-" + Integer.toString(beginShift));

        // if the source file doesn't exist there is nothing to shift
        if(!source.exists())
            return;

        // if the dest file exists, it will need to be shifted too
        File dest = new File(storeDir, "version-" + Integer.toString(beginShift + 1));
        if(dest.exists())
            shiftBackupsRight(beginShift + 1);

        // okay finally do the rename
        source.renameTo(dest);
    }

    public ClosableIterator<ByteArray> keys() {
        throw new UnsupportedOperationException("Iteration is not supported for "
                                                + getClass().getName());
    }

    public ClosableIterator<Pair<ByteArray, Versioned<byte[]>>> entries() {
        throw new UnsupportedOperationException("Iteration is not supported for "
                                                + getClass().getName());
    }

    public List<Versioned<byte[]>> get(ByteArray key) throws VoldemortException {
        StoreUtils.assertValidKey(key);
        byte[] keyMd5 = ByteUtils.md5(key.get());
        int chunk = fileSet.getChunkForKey(keyMd5);
        int location = searchStrategy.indexOf(fileSet.indexFileFor(chunk),
                                              keyMd5,
                                              fileSet.getIndexFileSize(chunk));
        if(location >= 0) {
            byte[] value = readValue(chunk, location);
            return Collections.singletonList(Versioned.value(value));
        } else {
            return Collections.emptyList();
        }
    }

    public Map<ByteArray, List<Versioned<byte[]>>> getAll(Iterable<ByteArray> keys)
            throws VoldemortException {
        StoreUtils.assertValidKeys(keys);
        Map<ByteArray, List<Versioned<byte[]>>> results = StoreUtils.newEmptyHashMap(keys);
        try {
            fileModificationLock.readLock().lock();
            List<KeyValueLocation> keysAndValueLocations = Lists.newArrayList();
            for(ByteArray key: keys) {
                byte[] keyMd5 = ByteUtils.md5(key.get());
                int chunk = fileSet.getChunkForKey(keyMd5);
                int valueLocation = searchStrategy.indexOf(fileSet.indexFileFor(chunk),
                                                           keyMd5,
                                                           fileSet.getIndexFileSize(chunk));
                if(valueLocation >= 0)
                    keysAndValueLocations.add(new KeyValueLocation(chunk, key, valueLocation));
            }
            Collections.sort(keysAndValueLocations);

            for(KeyValueLocation keyVal: keysAndValueLocations) {
                byte[] value = readValue(keyVal.getChunk(), keyVal.getValueLocation());
                results.put(keyVal.getKey(), Collections.singletonList(Versioned.value(value)));
            }
            return results;
        } finally {
            fileModificationLock.readLock().unlock();
        }
    }

    private byte[] readValue(int chunk, int valueLocation) {
        FileChannel dataFile = fileSet.dataFileFor(chunk);
        try {
            ByteBuffer sizeBuffer = ByteBuffer.allocate(4);
            dataFile.read(sizeBuffer, valueLocation);
            int size = sizeBuffer.getInt(0);
            ByteBuffer valueBuffer = ByteBuffer.allocate(size);
            dataFile.read(valueBuffer, valueLocation + 4);
            return valueBuffer.array();
        } catch(IOException e) {
            throw new VoldemortException(e);
        }
    }

    /**
     * Not supported, throws UnsupportedOperationException if called
     */
    public boolean delete(ByteArray key, Version version) throws VoldemortException {
        throw new UnsupportedOperationException("Delete is not supported on this store, it is read-only.");
    }

    /**
     * Not supported, throws UnsupportedOperationException if called
     */
    public void put(ByteArray key, Versioned<byte[]> value) throws VoldemortException {
        throw new UnsupportedOperationException("Put is not supported on this store, it is read-only.");
    }

    @JmxGetter(name = "name", description = "The name of the store.")
    public String getName() {
        return name;
    }

    public Object getCapability(StoreCapabilityType capability) {
        throw new NoSuchCapabilityException(capability, getName());
    }

    private final static class KeyValueLocation implements Comparable<KeyValueLocation> {

        private final int chunk;
        private final ByteArray key;
        private final int valueLocation;

        private KeyValueLocation(int chunk, ByteArray key, int valueLocation) {
            super();
            this.chunk = chunk;
            this.key = key;
            this.valueLocation = valueLocation;
        }

        public int getChunk() {
            return chunk;
        }

        public ByteArray getKey() {
            return key;
        }

        public int getValueLocation() {
            return valueLocation;
        }

        public int compareTo(KeyValueLocation kvl) {
            if(chunk == kvl.getChunk()) {
                if(valueLocation == kvl.getValueLocation())
                    return ByteUtils.compare(getKey().get(), kvl.getKey().get());
                else
                    return Integer.signum(valueLocation - kvl.getValueLocation());
            } else {
                return getChunk() - kvl.getChunk();
            }
        }
    }

    public List<Version> getVersions(ByteArray key) {
        return StoreUtils.getVersions(get(key));
    }
}
TOP

Related Classes of voldemort.store.readonly.ReadOnlyStorageEngine$KeyValueLocation

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.