Package voldemort.utils

Source Code of voldemort.utils.AbstractConsistencyFixer$ProcessReadRepliesResult

/*
* Copyright 2013 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.utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import voldemort.VoldemortException;
import voldemort.client.protocol.admin.AdminClient;
import voldemort.client.protocol.admin.QueryKeyResult;
import voldemort.routing.StoreRoutingPlan;
import voldemort.store.routed.NodeValue;
import voldemort.store.routed.ReadRepairer;
import voldemort.utils.ConsistencyFix.BadKey;
import voldemort.utils.ConsistencyFix.Status;
import voldemort.versioning.VectorClock;
import voldemort.versioning.Versioned;

import com.google.common.collect.Lists;

/**
* Base class that contains logic to read the state of a key on a cluster and
* resolve it using read repair
*
* NOTE : For now, there is only one subclass extending this class, to perform
* Consistency fixing by writing the resolved versions back to the cluster. Any
* future tool that needs similar functionality can extend this class and
* implement logic to do whatever it wants to do with the resolved versions.
*
*/
abstract class AbstractConsistencyFixer {

    private static final Logger logger = Logger.getLogger(AbstractConsistencyFixer.class);
    private static final int fakeNodeID = Integer.MIN_VALUE;

    protected final BadKey badKey;
    protected final StoreRoutingPlan storeInstance;
    protected final AdminClient adminClient;
    protected final QueryKeyResult orphanedValues;

    /**
     * Normal use case constructor.
     *
     * @param keyInHexFormat
     * @param consistencyFix
     * @param badKeyQOut
     */
    AbstractConsistencyFixer(BadKey badKey, StoreRoutingPlan storeInstance, AdminClient adminClient) {
        this(badKey, storeInstance, adminClient, null);
    }

    /**
     * Constructor for "orphaned values" use case. I.e., there are values for
     * the specific key that exist somewhere and may need to be written to the
     * nodes which actually host the key.
     *
     * @param keyInHexFormat
     * @param consistencyFix
     * @param badKeyQOut
     * @param orphanedValues Set to null if no orphaned values to be included.
     */
    AbstractConsistencyFixer(BadKey badKey,
                             StoreRoutingPlan storeInstance,
                             AdminClient adminClient,
                             QueryKeyResult orphanedValues) {
        this.badKey = badKey;
        this.storeInstance = storeInstance;
        this.adminClient = adminClient;
        this.orphanedValues = orphanedValues;
    }

    public Status doConsistencyFix() {
        // Initialization.
        byte[] keyInBytes;
        List<Integer> nodeIdList = null;
        int masterPartitionId = -1;
        try {
            keyInBytes = ByteUtils.fromHexString(badKey.getKeyInHexFormat());
            masterPartitionId = this.storeInstance.getMasterPartitionId(keyInBytes);
            nodeIdList = this.storeInstance.getReplicationNodeList(masterPartitionId);
        } catch(Exception exception) {
            logger.info("Aborting fixKey due to bad init.");
            if(logger.isDebugEnabled()) {
                exception.printStackTrace();
            }
            return Status.BAD_INIT;
        }
        ByteArray keyAsByteArray = new ByteArray(keyInBytes);

        // Do the reads
        Map<Integer, QueryKeyResult> nodeIdToKeyValues = doReads(nodeIdList,
                                                                 keyInBytes,
                                                                 badKey.getKeyInHexFormat());

        // Process read replies (i.e., nodeIdToKeyValues)
        ProcessReadRepliesResult result = processReadReplies(nodeIdList,
                                                             keyAsByteArray,
                                                             badKey.getKeyInHexFormat(),
                                                             nodeIdToKeyValues);
        if(result.status != Status.SUCCESS) {
            return result.status;
        }

        // Resolve conflicts indicated in nodeValues
        List<NodeValue<ByteArray, byte[]>> toReadRepair = resolveReadConflicts(result.nodeValues);
        if(logger.isTraceEnabled()) {
            if(toReadRepair.size() == 0) {
                logger.trace("Nothing to repair");
            }
            for(NodeValue<ByteArray, byte[]> nodeValue: toReadRepair) {
                logger.trace(nodeValue.getNodeId() + " --- " + nodeValue.getKey().toString());
            }
        }

        // Do the repairs
        Status status = doRepairPut(toReadRepair);

        // return status of last operation (success or otherwise)
        return status;
    }

    /**
     *
     * @param nodeIdList
     * @param keyInBytes
     * @param keyInHexFormat
     * @return
     */
    private Map<Integer, QueryKeyResult> doReads(final List<Integer> nodeIdList,
                                                 final byte[] keyInBytes,
                                                 final String keyInHexFormat) {
        Map<Integer, QueryKeyResult> nodeIdToKeyValues = new HashMap<Integer, QueryKeyResult>();

        ByteArray key = new ByteArray(keyInBytes);
        for(int nodeId: nodeIdList) {
            List<Versioned<byte[]>> values = null;
            try {
                values = this.adminClient.storeOps.getNodeKey(this.storeInstance.getStoreDefinition()
                                                                                .getName(),
                                                              nodeId,
                                                              key);
                nodeIdToKeyValues.put(nodeId, new QueryKeyResult(key, values));
            } catch(VoldemortException ve) {
                nodeIdToKeyValues.put(nodeId, new QueryKeyResult(key, ve));
            }
        }

        return nodeIdToKeyValues;
    }

    /**
     * Result of an invocation of processReadReplies
     */
    private class ProcessReadRepliesResult {

        public final Status status;
        public final List<NodeValue<ByteArray, byte[]>> nodeValues;

        /**
         * Constructor for error status
         */
        ProcessReadRepliesResult(Status status) {
            this.status = status;
            this.nodeValues = null;
        }

        /**
         * Constructor for success
         */
        ProcessReadRepliesResult(List<NodeValue<ByteArray, byte[]>> nodeValues) {
            this.status = Status.SUCCESS;
            this.nodeValues = nodeValues;
        }
    }

    /**
     *
     * @param nodeIdList
     * @param keyAsByteArray
     * @param keyInHexFormat
     * @param nodeIdToKeyValues
     * @param nodeValues Effectively the output of this method. Must pass in a
     *        non-null object to be populated by this method.
     * @return
     */
    private ProcessReadRepliesResult processReadReplies(final List<Integer> nodeIdList,
                                                        final ByteArray keyAsByteArray,
                                                        final String keyInHexFormat,
                                                        final Map<Integer, QueryKeyResult> nodeIdToKeyValues) {
        List<NodeValue<ByteArray, byte[]>> nodeValues = new ArrayList<NodeValue<ByteArray, byte[]>>();
        boolean exceptionsEncountered = false;
        for(int nodeId: nodeIdList) {
            QueryKeyResult keyValue;
            if(nodeIdToKeyValues.containsKey(nodeId)) {
                keyValue = nodeIdToKeyValues.get(nodeId);

                if(keyValue.hasException()) {
                    logger.debug("Exception encountered while fetching key " + keyInHexFormat
                                 + " from node with nodeId " + nodeId + " : "
                                 + keyValue.getException().getMessage());
                    exceptionsEncountered = true;
                } else {
                    if(keyValue.getValues().isEmpty()) {
                        Versioned<byte[]> versioned = new Versioned<byte[]>(null);
                        nodeValues.add(new NodeValue<ByteArray, byte[]>(nodeId,
                                                                        keyValue.getKey(),
                                                                        versioned));

                    } else {
                        for(Versioned<byte[]> value: keyValue.getValues()) {
                            nodeValues.add(new NodeValue<ByteArray, byte[]>(nodeId,
                                                                            keyValue.getKey(),
                                                                            value));
                        }
                    }
                }
            } else {
                logger.debug("No key-value returned from node with id:" + nodeId);
                Versioned<byte[]> versioned = new Versioned<byte[]>(null);
                nodeValues.add(new NodeValue<ByteArray, byte[]>(nodeId, keyAsByteArray, versioned));
            }
        }
        if(exceptionsEncountered) {
            logger.info("Aborting fixKey because exceptions were encountered when fetching key-values.");
            return new ProcessReadRepliesResult(Status.FETCH_EXCEPTION);
        }

        if(logger.isDebugEnabled()) {
            for(NodeValue<ByteArray, byte[]> nkv: nodeValues) {
                logger.debug("\tRead NodeKeyValue : " + ByteUtils.toHexString(nkv.getKey().get())
                             + " on node with id " + nkv.getNodeId() + " for version "
                             + nkv.getVersion());
            }
        }

        return new ProcessReadRepliesResult(nodeValues);
    }

    /**
     * Decide on the specific key-value to write everywhere.
     *
     * @param nodeValues
     * @return The subset of entries from nodeValues that need to be repaired.
     */
    private List<NodeValue<ByteArray, byte[]>> resolveReadConflicts(final List<NodeValue<ByteArray, byte[]>> nodeValues) {

        if(logger.isTraceEnabled()) {
            logger.trace("NodeValues passed into resolveReadConflicts.");
            if(nodeValues.size() == 0) {
                logger.trace("Empty nodeValues passed to resolveReadConflicts");
            }
            for(NodeValue<ByteArray, byte[]> nodeValue: nodeValues) {
                logger.trace("\t" + nodeValue.getNodeId() + " - " + nodeValue.getKey().toString()
                             + " - " + nodeValue.getVersion().toString());
            }
        }

        // If orphaned values exist, add them to fake nodes to be processed by
        // "getRepairs"
        int currentFakeNodeId = fakeNodeID;
        if(this.orphanedValues != null) {
            for(Versioned<byte[]> value: this.orphanedValues.getValues()) {
                nodeValues.add(new NodeValue<ByteArray, byte[]>(currentFakeNodeId,
                                                                this.orphanedValues.getKey(),
                                                                value));
                currentFakeNodeId++;
            }
        }

        // Some cut-paste-and-modify coding from
        // store/routed/action/AbstractReadRepair.java and
        // store/routed/ThreadPoolRoutedStore.java
        ReadRepairer<ByteArray, byte[]> readRepairer = new ReadRepairer<ByteArray, byte[]>();
        List<NodeValue<ByteArray, byte[]>> nodeKeyValues = readRepairer.getRepairs(nodeValues);

        if(logger.isTraceEnabled()) {
            if(nodeKeyValues.size() == 0) {
                logger.trace("\treadRepairer returned an empty list.");
            }
            for(NodeValue<ByteArray, byte[]> nodeKeyValue: nodeKeyValues) {
                logger.trace("\tNodeKeyValue result from readRepairer.getRepairs : "
                             + ByteUtils.toHexString(nodeKeyValue.getKey().get())
                             + " on node with id " + nodeKeyValue.getNodeId() + " for version "
                             + nodeKeyValue.getVersion());
            }
        }

        List<NodeValue<ByteArray, byte[]>> toReadRepair = Lists.newArrayList();
        for(NodeValue<ByteArray, byte[]> v: nodeKeyValues) {
            if(v.getNodeId() > currentFakeNodeId) {
                // Only copy repairs intended for real nodes.
                Versioned<byte[]> versioned = Versioned.value(v.getVersioned().getValue(),
                                                              ((VectorClock) v.getVersion()).clone());
                toReadRepair.add(new NodeValue<ByteArray, byte[]>(v.getNodeId(),
                                                                  v.getKey(),
                                                                  versioned));
            } else {
                if(logger.isDebugEnabled()) {
                    logger.debug("\tIgnoring repair to fake node: "
                                 + ByteUtils.toHexString(v.getKey().get()) + " on node with id "
                                 + v.getNodeId() + " for version " + v.getVersion());
                }
            }

        }

        if(logger.isTraceEnabled()) {
            if(toReadRepair.size() == 0) {
                logger.trace("\ttoReadRepair is empty.");
            }
            for(NodeValue<ByteArray, byte[]> nodeKeyValue: toReadRepair) {
                logger.trace("\tRepair key " + ByteUtils.toHexString(nodeKeyValue.getKey().get())
                             + " on node with id " + nodeKeyValue.getNodeId() + " for version "
                             + nodeKeyValue.getVersion());

            }
        }
        return toReadRepair;
    }

    /**
     * Override this method to place whatever logic you have to handle the
     * resolved value
     *
     * @param toReadRepair Effectively the output of this method. Must pass in a
     *        non-null object to be populated by this method.
     * @return
     */
    public abstract Status doRepairPut(final List<NodeValue<ByteArray, byte[]>> toReadRepair);
}
TOP

Related Classes of voldemort.utils.AbstractConsistencyFixer$ProcessReadRepliesResult

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.