Package org.voltdb.export

Source Code of org.voltdb.export.StreamBlockQueue

/* This file is part of VoltDB.
* Copyright (C) 2008-2014 VoltDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with VoltDB.  If not, see <http://www.gnu.org/licenses/>.
*/
package org.voltdb.export;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Iterator;

import org.voltcore.logging.VoltLogger;
import org.voltdb.utils.BinaryDeque.BinaryDequeTruncator;
import org.voltdb.utils.PersistentBinaryDeque;
import org.voltdb.utils.BinaryDeque;
import org.voltdb.utils.VoltFile;

import org.voltcore.utils.DBBPool.BBContainer;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
* A customized queue for StreamBlocks that contain export data. The queue is able to
* overflow to disk when more then two stream blocks are stored
* as well as persist to disk when sync is invoked. Right now sync doesn't actually do an fsync on
* the file unless it is specifically requested. It just pushed the two in memory blocks to the persistent
*
* portion of the queue
*
*/
public class StreamBlockQueue {

    private static final VoltLogger exportLog = new VoltLogger("EXPORT");

    /**
     * Deque containing reference to stream blocks that are in memory. Some of these
     * stream blocks may still be persisted to disk others are stored completely in memory
     */
    private final ArrayDeque<StreamBlock> m_memoryDeque = new ArrayDeque<StreamBlock>();

    /**
     * A deque for persisting data to disk both for persistence and as a means of overflowing storage
     */
    private final BinaryDeque m_persistentDeque;

    private final String m_nonce;

    public StreamBlockQueue(String path, String nonce) throws java.io.IOException {
        m_persistentDeque = new PersistentBinaryDeque( nonce, new VoltFile(path));
        m_nonce = nonce;
    }

    public boolean isEmpty() throws IOException {
        if (m_memoryDeque.isEmpty() && m_persistentDeque.isEmpty()) {
            return true;
        }
        return false;
    }

    /**
     * Wrapper around the common operation of pulling an element out of the persistent deque.
     * The behavior is complicated (and might change) since the persistent deque can throw an IOException.
     * The poll always removes the element from the persistent queue
     * (although not necessarily removing the file backing, that happens at deleteContents) and will add
     * a reference to the block to the in memory deque unless actuallyPoll is true.
     * @param actuallyPoll
     * @return
     */
    private StreamBlock pollPersistentDeque(boolean actuallyPoll) {
        BBContainer cont = null;
        try {
            cont = m_persistentDeque.poll(PersistentBinaryDeque.UNSAFE_CONTAINER_FACTORY);
        } catch (IOException e) {
            exportLog.error(e);
        }

        if (cont == null) {
            return null;
        } else {
            //If the container is not null, unpack it.
            final BBContainer fcont = cont;
            long uso = cont.b().getLong(0);
            //Pass the stream block a subset of the bytes, provide
            //a container that discards the original returned by the persistent deque
            StreamBlock block = new StreamBlock( fcont,
                uso,
                true);

            //Optionally store a reference to the block in the in memory deque
            if (!actuallyPoll) {
                m_memoryDeque.offer(block);
            }
            return block;
        }
    }
    /*
     * Present an iterator that is backed by the blocks
     * that are already loaded as well as blocks that
     * haven't been polled from the persistent deque.
     *
     * The iterator wraps an iterator from the memoryDeque,
     * and regenerates it every time an element is added to the memoryDeque from
     * the persistent deque.
     */
    public Iterator<StreamBlock> iterator() {
        return new Iterator<StreamBlock>() {
            private Iterator<StreamBlock> m_memoryIterator = m_memoryDeque.iterator();
            @Override
            public boolean hasNext() {
                if (m_memoryIterator.hasNext()) {
                    return true;
                } else {
                    if (pollPersistentDeque(false) != null) {
                        m_memoryIterator = m_memoryDeque.iterator();
                        for (int ii = 0; ii < m_memoryDeque.size() - 1; ii++) {
                            m_memoryIterator.next();
                        }
                        return true;
                    }
                }
                return false;
            }

            @Override
            public StreamBlock next() {
                if (m_memoryIterator.hasNext()) {
                    return m_memoryIterator.next();
                }

                StreamBlock block = pollPersistentDeque(false);
                if (block == null) {
                    throw new java.util.NoSuchElementException();
                } else {
                    m_memoryIterator = m_memoryDeque.iterator();
                    for (int ii = 0; ii < m_memoryDeque.size(); ii++) {
                        m_memoryIterator.next();
                    }
                    return block;
                }
            }

            @Override
            public void remove() {
                m_memoryIterator.remove();
            }
        };
    }

    public StreamBlock peek() {
        if (m_memoryDeque.peek() != null) {
            return m_memoryDeque.peek();
        }
        return pollPersistentDeque(false);
    }

    public StreamBlock poll() {
        StreamBlock sb = null;
        if (m_memoryDeque.peek() != null) {
            sb = m_memoryDeque.poll();
        } else {
            sb = pollPersistentDeque(true);
        }
        return sb;
    }

    public StreamBlock pop() {
        if (m_memoryDeque.isEmpty()) {
            StreamBlock sb = pollPersistentDeque(true);
            if (sb == null) {
                throw new java.util.NoSuchElementException();
            }
            return sb;
        } else {
            return m_memoryDeque.pop();
        }
    }

    /*
     * Only allow two blocks in memory, put the rest in the persistent deque
     */
    public void offer(StreamBlock streamBlock) throws IOException {
        //Already have two blocks, put it in the deque
        if (m_memoryDeque.size() > 1) {
            m_persistentDeque.offer(streamBlock.asBBContainer());
        } else {
            //Don't offer into the memory deque if there is anything waiting to be
            //polled out of the persistent deque. Check the persistent deque
            if (pollPersistentDeque(false) != null) {
               m_persistentDeque.offer( streamBlock.asBBContainer());
            } else {
            //Persistent deque is empty put this in memory
               m_memoryDeque.offer(streamBlock);
            }
        }
    }

    /*
     * Push all the buffers that are in memory to disk
     * and then have the persistent deque sync.
     * Skip the fsync for an asynchronous push of the in memory
     * buffers to disk
     */
    public void sync(boolean nofsync) throws IOException {
        if (m_memoryDeque.peek() != null && !m_memoryDeque.peek().isPersisted()) {
            ArrayDeque<BBContainer> buffersToPush = new ArrayDeque<BBContainer>();
            while (m_memoryDeque.peek() != null) {
                StreamBlock sb = m_memoryDeque.peek();
                if (sb.isPersisted()) {
                    break;
                }
                m_memoryDeque.poll();
                buffersToPush.offer(sb.asBBContainer());
            }

            if (!buffersToPush.isEmpty()) {
                m_persistentDeque.push(buffersToPush.toArray(new BBContainer[0]));
            }
            ArrayList<StreamBlock> blocks = new ArrayList<StreamBlock>();
            for (int ii = 0; ii < buffersToPush.size(); ii++) {
                blocks.add(pollPersistentDeque(true));
            }
            for (int ii = blocks.size() - 1; ii >= 0; ii--) {
                m_memoryDeque.offerFirst(blocks.get(ii));
            }
        }

        if (!nofsync) {
            m_persistentDeque.sync();
        }
    }

    public long sizeInBytes() {
        long memoryBlockUsage = 0;
        for (StreamBlock b : m_memoryDeque) {
            memoryBlockUsage += b.unreleasedSize(); //Use only unreleased size, but throw in the USO
                                                    //to make book keeping consistent when flushed to disk
        }
        //Subtract USO from on disk size
        return memoryBlockUsage + m_persistentDeque.sizeInBytes() - (8 * m_persistentDeque.getNumObjects());
    }

    public void close() throws IOException {
        sync(true);
        m_persistentDeque.close();
        for (StreamBlock sb : m_memoryDeque) {
            sb.discard();
        }
        m_memoryDeque.clear();
    }

    public void closeAndDelete() throws IOException {
        m_persistentDeque.closeAndDelete();
        for (StreamBlock sb : m_memoryDeque) {
            sb.discard();
        }
    }

    public void truncateToTxnId(final long txnId, final int nullArrayLength) throws IOException {
        assert(m_memoryDeque.isEmpty());
        m_persistentDeque.parseAndTruncate(new BinaryDequeTruncator() {

        @Override
        public ByteBuffer parse(ByteBuffer b) {
            b.order(ByteOrder.LITTLE_ENDIAN);
            try {
                b.position(b.position() + 8);//Don't need the USO
                while (b.hasRemaining()) {
                    int rowLength = b.getInt();
                    b.position(b.position() + nullArrayLength);
                    long rowTxnId = b.getLong();
                    exportLog.trace("Evaluating row with txnId " + rowTxnId + " for truncation");
                    if (rowTxnId > txnId) {
                        exportLog.debug(
                                "Export stream " + m_nonce + " found export data to truncate at txn " + rowTxnId);
                        //The txnid of this row is the greater then the truncation txnid.
                        //Don't want this row, but want to preserve all rows before it.
                        //Move back before the row length prefix and txnId
                        b.position(b.position() - (12 + nullArrayLength));

                        //If the truncation point was the first row in the block, the entire block is to be discard
                        //We know it is the first row if the position before the row is after the uso (8 bytes)
                        if (b.position() == 8) {
                            return ByteBuffer.allocate(0);
                        } else {
                            //Return everything in the block before the truncation point.
                            //Indicate this is the end of the interesting data.
                            b.limit(b.position());
                            b.position(0);
                            return b;
                        }
                    } else {
                        //Not the row we are looking to truncate at. Skip past it keeping in mind
                        //we read the first 8 bytes for the txn id, and the null array which
                        //is included in the length prefix
                        b.position(b.position() + (rowLength - (8 + nullArrayLength)));
                    }
                }
            } finally {
                b.order(ByteOrder.BIG_ENDIAN);
            }
            return null;
        }
        });
    }


    @Override
    public void finalize() {
        if (!m_memoryDeque.isEmpty()) {
            exportLog.error("Finalized StreamBlockQueue with items in the memory deque");
        }
    }
}
TOP

Related Classes of org.voltdb.export.StreamBlockQueue

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.