Package voldemort.utils

Source Code of voldemort.utils.ConsistencyCheck$HashedValue

/*
* 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.io.BufferedWriter;
import java.io.FileWriter;
import java.io.Writer;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import joptsimple.OptionParser;
import joptsimple.OptionSet;

import org.apache.log4j.Logger;

import voldemort.VoldemortException;
import voldemort.client.ClientConfig;
import voldemort.client.protocol.admin.AdminClient;
import voldemort.client.protocol.admin.AdminClientConfig;
import voldemort.cluster.Cluster;
import voldemort.cluster.Node;
import voldemort.routing.RoutingStrategyFactory;
import voldemort.store.StoreDefinition;
import voldemort.versioning.Occurred;
import voldemort.versioning.VectorClock;
import voldemort.versioning.Version;
import voldemort.versioning.Versioned;

public class ConsistencyCheck {

    private static final String ComparisonTypeArgument = "comparison-type";
    private static Logger logger = Logger.getLogger(ConsistencyCheck.class);
    private final List<String> urls;
    private final String storeName;
    private final Integer partitionId;
    private final ValueFactory valueFactory;
    private final Reporter reporter;

    private Integer retentionDays = null;
    private Integer replicationFactor = 0;
    private Integer requiredWrites = 0;
    private List<AdminClient> adminClients;
    private List<ClusterNode> clusterNodeList = new ArrayList<ClusterNode>();
    private final Map<ByteArray, Map<Value, Set<ClusterNode>>> keyValueNodeSetMap =
            new HashMap<ByteArray, Map<Value, Set<ClusterNode>>>();
    private RetentionChecker retentionChecker;
    private KeyFetchTracker keyFetchTracker;

    public ConsistencyCheck(List<String> urls,
            String storeName,
            int partitionId, Writer badKeyWriter, ComparisonType comparisonType) {
        this.urls = urls;
        this.storeName = storeName;
        this.partitionId = partitionId;
        this.reporter = new Reporter(badKeyWriter);
        this.valueFactory = new ValueFactory(comparisonType);
    }

    /**
     * Connect to the clusters using given urls and start fetching process on
     * correct nodes
     *
     * @throws Exception When no such store is found
     */

    public void connect() throws Exception {
        adminClients = new ArrayList<AdminClient>(urls.size());
        // bootstrap from two urls
        Map<String, Cluster> clusterMap = new HashMap<String, Cluster>(urls.size());
        Map<String, StoreDefinition> storeDefinitionMap = new HashMap<String, StoreDefinition>(urls.size());

        for(String url: urls) {
            /* connect to cluster through admin port */
            if(logger.isInfoEnabled()) {
                logger.info("Connecting to bootstrap server: " + url);
            }
            AdminClient adminClient = new AdminClient(url,
                    new AdminClientConfig(),
                    new ClientConfig());
            adminClients.add(adminClient);
            /* get Cluster */
            Cluster cluster = adminClient.getAdminClientCluster();
            clusterMap.put(url, cluster);

            Integer nodeId = cluster.getNodeIds().iterator().next();
            /* get StoreDefinition */
            Versioned<List<StoreDefinition>> storeDefinitions = adminClient.metadataMgmtOps.getRemoteStoreDefList(nodeId);
            StoreDefinition storeDefinition = StoreDefinitionUtils.getStoreDefinitionWithName(storeDefinitions.getValue(),
                    storeName);
            storeDefinitionMap.put(url, storeDefinition);
        }

        /* confirm same number of partitions in all clusters. */
        int partitionCount = 0;
        for(Entry<String, Cluster> entry: clusterMap.entrySet()) {
            int currentPartitionCount = entry.getValue().getNumberOfPartitions();
            if(partitionCount == 0) {
                partitionCount = currentPartitionCount;
            }
            if(partitionCount != currentPartitionCount) {
                logger.error("Partition count of different clusters is not the same: "
                        + partitionCount + " vs " + currentPartitionCount);
                throw new VoldemortException("Will not connect because partition counts differ among clusters.");
            }
        }

        /* calculate nodes to scan */
        for(String url: urls) {
            StoreDefinition storeDefinition = storeDefinitionMap.get(url);
            Cluster cluster = clusterMap.get(url);
            Map<Integer, Integer> partitionToNodeMap = cluster.getPartitionIdToNodeIdMap();

            /* find list of nodeId hosting partition */
            List<Integer> partitionList = new RoutingStrategyFactory().updateRoutingStrategy(storeDefinition,
                    cluster)
                    .getReplicatingPartitionList(partitionId);
            for(int partition: partitionList) {
                Integer nodeId = partitionToNodeMap.get(partition);
                Node node = cluster.getNodeById(nodeId);
                clusterNodeList.add(new ClusterNode(urls.indexOf(url), node));
            }
        }

        /* print config info */
        if(logger.isInfoEnabled()) {
            StringBuilder configInfo = new StringBuilder();
            configInfo.append("TYPE,Store,PartitionId,Node,ZoneId,BootstrapUrl\n");
            for(ClusterNode clusterNode: clusterNodeList) {
                configInfo.append("CONFIG,");
                configInfo.append(storeName + ",");
                configInfo.append(partitionId + ",");
                configInfo.append(clusterNode.getNode().getId() + ",");
                configInfo.append(clusterNode.getNode().getZoneId() + ",");
                configInfo.append(urls.get(clusterNode.getPrefixId()) + "\n");
            }
            for(String line: configInfo.toString().split("\n")) {
                logger.info(line);
            }
        }

        /* calculate retention days and more */
        for(String url: urls) {
            StoreDefinition storeDefinition = storeDefinitionMap.get(url);
            /* retention */
            int storeRetentionDays = 0;
            if(storeDefinition.getRetentionDays() != null) {
                storeRetentionDays = storeDefinition.getRetentionDays().intValue();
            }
            if(retentionDays == null) {
                retentionDays = storeRetentionDays;
            }
            if(retentionDays != storeRetentionDays) {
                if(storeRetentionDays != 0 && (storeRetentionDays < retentionDays)) {
                    retentionDays = storeRetentionDays;
                }
                logger.warn("Retention-days is not consistent between clusters by urls. Will use the shorter.");
            }

            /* replication writes */
            replicationFactor += storeDefinition.getReplicationFactor();

            /* required writes */
            requiredWrites += storeDefinition.getRequiredWrites();
        }
        if(replicationFactor != clusterNodeList.size()) {
            logger.error("Replication factor is not consistent with number of nodes routed to.");
            throw new VoldemortException("Will not connect because replication factor does not accord with number of nodes routed to.");
        }
        retentionChecker = new RetentionChecker(retentionDays);
    }

    /**
     * Run consistency check on connected key-value iterators
     *
     * @return Results in form of ConsistencyCheckStats
     */
    public Reporter execute() throws IOException {
        Map<ClusterNode, Iterator<Pair<ByteArray, Versioned<byte[]>>>> nodeFetchIteratorMap;
        nodeFetchIteratorMap = new HashMap<ClusterNode, Iterator<Pair<ByteArray, Versioned<byte[]>>>>();
        /* start fetch from each node */
        for(ClusterNode clusterNode: clusterNodeList) {
            AdminClient adminClient = adminClients.get(clusterNode.getPrefixId());
            List<Integer> singlePartition = new ArrayList<Integer>();
            singlePartition.add(partitionId);
            if(logger.isDebugEnabled()) {
                logger.debug("Start fetch request to Node[" + clusterNode.toString()
                        + "] for partition[" + partitionId + "] of store[" + storeName + "]");
            }

            Iterator<Pair<ByteArray, Versioned<byte[]>>> fetchIterator;
            fetchIterator = adminClient.bulkFetchOps.fetchEntries(clusterNode.getNode().getId(),
                    storeName,
                    singlePartition,
                    null,
                    false);
            nodeFetchIteratorMap.put(clusterNode, fetchIterator);
        }
        keyFetchTracker = new KeyFetchTracker(clusterNodeList.size());

        /* start to fetch */
        boolean fetchFinished;
        do {
            fetchFinished = true;
            for(Map.Entry<ClusterNode, Iterator<Pair<ByteArray, Versioned<byte[]>>>> nodeFetchIteratorMapEntry: nodeFetchIteratorMap.entrySet()) {
                ClusterNode clusterNode = nodeFetchIteratorMapEntry.getKey();
                Iterator<Pair<ByteArray, Versioned<byte[]>>> fetchIterator = nodeFetchIteratorMapEntry.getValue();
                if(fetchIterator.hasNext()) {
                    fetchFinished = false;
                    reporter.recordScans(1);

                    Pair<ByteArray, Versioned<byte[]>> fetchedEntry = fetchIterator.next();
                    ByteArray key = fetchedEntry.getFirst();
                    Versioned<byte[]> versioned = fetchedEntry.getSecond();

                    // record fetch
                    recordFetch(clusterNode, key, versioned);

                    // try sweep last key fetched by this iterator
                    keyFetchTracker.recordFetch(clusterNode, key);
                    if(logger.isTraceEnabled()) {
                        logger.trace("fetched " + new String(key.get()));
                        logger.trace("map has keys: " + keyValueNodeSetMap.size());
                    }
                    trySweepAll();
                    if(logger.isTraceEnabled()) {
                        logger.trace("sweeped; keys left: " + keyValueNodeSetMap.size());
                    }
                }
            }

            // stats reporting
            if(logger.isInfoEnabled()) {
                String report = reporter.tryProgressReport();
                if(report != null) {
                    for(String line: report.split("\n")) {
                        logger.info(line);
                    }
                }
            }
        } while(!fetchFinished);

        /* adminClient shutdown */
        for(AdminClient adminClient: adminClients) {
            if(adminClient != null) {
                adminClient.close();
            }
        }

        // clean keys not sufficient for write
        cleanIneligibleKeys(keyValueNodeSetMap, requiredWrites);

        keyFetchTracker.finishAll();
        trySweepAll();

        reporter.processInconsistentKeys(storeName, partitionId, keyValueNodeSetMap);

        return reporter;
    }

    public void trySweepAll() {
        for(ByteArray finishedKey = keyFetchTracker.nextFinished(); finishedKey != null; finishedKey = keyFetchTracker.nextFinished()) {
            if (keyValueNodeSetMap.containsKey(finishedKey)) {
                ConsistencyLevel level = determineConsistency(keyValueNodeSetMap.get(finishedKey),
                        replicationFactor);
                if(level == ConsistencyLevel.FULL || level == ConsistencyLevel.LATEST_CONSISTENT) {
                    keyValueNodeSetMap.remove(finishedKey);
                    reporter.recordGoodKey(1);
                }
            }
        }
    }

    public void recordFetch(ClusterNode clusterNode, ByteArray key, Versioned<byte[]> versioned) {

        Value value = valueFactory.Create(versioned);

        // skip version if expired
        if (retentionChecker.isExpired(value)) {
            reporter.recordExpired(1);
            return;
        }

        // initialize key -> Map<Version, Set<nodeId>>
        if (!keyValueNodeSetMap.containsKey(key)) {
            keyValueNodeSetMap.put(key, new HashMap<Value, Set<ClusterNode>>());
        }
        Map<Value, Set<ClusterNode>> versionNodeSetMap = keyValueNodeSetMap.get(key);

        List<Value> valuesToDiscard = new ArrayList<Value>();
        for(Map.Entry<Value, Set<ClusterNode>> versionNode : versionNodeSetMap.entrySet()) {
            Value existingValue = versionNode.getKey();
            // Check for each existing value, if the existing value happened after current. If so the current value can be discarded
            if(existingValue.compare(value) == Occurred.AFTER) {
                return;
            } else if ( value.compare(existingValue) == Occurred.AFTER) {
            // Check for each existing value, if the current value happened after existing. If so the existing value can be discarded
                valuesToDiscard.add(existingValue);
            }
        }

        for(Value valueToDiscard: valuesToDiscard)
        {
            versionNodeSetMap.remove(valueToDiscard);
        }

        if (!versionNodeSetMap.containsKey(value)) {
            // insert nodeSet into the map
            versionNodeSetMap.put(value, new HashSet<ClusterNode>());
        }

        // add node to set
        versionNodeSetMap.get(value).add(clusterNode);
    }

    /**
     * A class to track what keys have been fetched and what keys will not
     * appear any more. It is used to detect keys that will not show up any more
     * so that existing versions can be processed.
     */
    protected static class KeyFetchTracker {

        private final Integer fetcherCount;
        Map<ByteArray, Set<ClusterNode>> fullyFetchedKeyMap = new HashMap<ByteArray, Set<ClusterNode>>();
        Map<ClusterNode, ByteArray> lastFetchedKey = new HashMap<ClusterNode, ByteArray>();
        List<ByteArray> fullyFetchedKeys = new LinkedList<ByteArray>();

        public KeyFetchTracker(Integer fetcherCount) {
            this.fetcherCount = fetcherCount;
        }

        /**
         * Record a fetched result
         *
         * @param clusterNode The clusterNode from which the key has been
         *        fetched
         * @param key The key itself
         */
        public void recordFetch(ClusterNode clusterNode, ByteArray key) {
            if(lastFetchedKey.containsKey(clusterNode)) {
                ByteArray lastKey = lastFetchedKey.get(clusterNode);
                if(!key.equals(lastKey)) {
                    if(!fullyFetchedKeyMap.containsKey(lastKey)) {
                        fullyFetchedKeyMap.put(lastKey, new HashSet<ClusterNode>());
                    }
                    Set<ClusterNode> lastKeyIterSet = fullyFetchedKeyMap.get(lastKey);
                    lastKeyIterSet.add(clusterNode);

                    // sweep if fully fetched by all iterators
                    if(lastKeyIterSet.size() == fetcherCount) {
                        fullyFetchedKeys.add(lastKey);
                        fullyFetchedKeyMap.remove(lastKey);
                    }
                }
            }
            // remember key fetch states
            lastFetchedKey.put(clusterNode, key);
        }

        /**
         * mark all keys appeared as finished So that they are all in the
         * finished keys queue
         */
        public void finishAll() {
            Set<ByteArray> keySet = new HashSet<ByteArray>();
            keySet.addAll(fullyFetchedKeyMap.keySet());
            keySet.addAll(lastFetchedKey.values());
            fullyFetchedKeys.addAll(keySet);
            fullyFetchedKeyMap.clear();
        }

        /**
         * Get a key that are completed in fetching
         *
         * @return key considered finished; otherwise null
         */
        public ByteArray nextFinished() {
            if(fullyFetchedKeys.size() > 0) {
                return fullyFetchedKeys.remove(0);
            } else {
                return null;
            }
        }
    }

    protected enum ConsistencyLevel {
        FULL,
        LATEST_CONSISTENT,
        INCONSISTENT,
        EXPIRED,
        INSUFFICIENT_WRITE
    }

    public static enum ComparisonType {
        VERSION,
        HASH,
    }

    /**
     * Used to track nodes that may share the same nodeId in different clusters
     *
     */
    protected static class ClusterNode {

        private final Integer clusterId;
        private final Node node;

        /**
         * @param clusterId a prefix to be associated different clusters
         * @param node the real node
         */
        public ClusterNode(Integer clusterId, Node node) {
            this.clusterId = clusterId;
            this.node = node;
        }

        public Integer getPrefixId() {
            return clusterId;
        }

        public Node getNode() {
            return node;
        }

        @Override
        public boolean equals(Object o) {
            if(this == o)
                return true;
            if(!(o instanceof ClusterNode))
                return false;

            ClusterNode n = (ClusterNode) o;
            return clusterId.equals(n.getPrefixId()) && node.equals(n.getNode());
        }

        @Override
        public String toString() {
            return clusterId + "." + node.getId();
        }

    }



    /**
     * A checker to determine if a key is to be cleaned according to retention
     * policy
     *
     */
    protected static class RetentionChecker {

        final private long bufferTimeSeconds = 600; // expire N seconds earlier
        final private long expiredTimeMs;

        /**
         * @param days number of days ago from now to retain keys
         */
        public RetentionChecker(int days) {
            if(days <= 0) {
                expiredTimeMs = 0;
            } else {
                long nowMs = System.currentTimeMillis();
                long expirationTimeS = TimeUnit.DAYS.toSeconds(days) - bufferTimeSeconds;
                expiredTimeMs = nowMs - TimeUnit.SECONDS.toMillis(expirationTimeS);
            }
        }

        /**
         * Determine if a version is expired
         *
         * @param v version to be checked
         * @return if the version is expired according to retention policy
         */
        public boolean isExpired(Value v) {
            return v.isExpired(expiredTimeMs);
        }
    }

    /**
     * Used to report bad keys, progress, and statistics
     *
     */
    protected static class Reporter {

        final Writer badKeyWriter;
        final long reportPeriodMs;
        long lastReportTimeMs = 0;
        long numRecordsScanned = 0;
        long numRecordsScannedLast = 0;
        long numExpiredRecords = 0;
        long numGoodKeys = 0;
        long numTotalKeys = 0;

        /**
         * Will output progress reports every 5 seconds.
         *
         * @param badKeyWriter Writer to which to output bad keys. Null is OK.
         */
        public Reporter(Writer badKeyWriter) {
            this(badKeyWriter, 5000);
        }

        /**
         * @param badKeyWriter Writer to which to output bad keys. Null is OK.
         * @param intervalMs Milliseconds between progress reports.
         */
        public Reporter(Writer badKeyWriter, long intervalMs) {
            this.badKeyWriter = badKeyWriter;
            this.reportPeriodMs = intervalMs;
        }

        public void recordScans(long count) {
            numRecordsScanned += count;
        }

        public void recordExpired(long count) {
            numExpiredRecords += count;
        }

        public String tryProgressReport() {
            if(System.currentTimeMillis() > lastReportTimeMs + reportPeriodMs) {
                long currentTimeMs = System.currentTimeMillis();
                StringBuilder s = new StringBuilder();
                s.append("=====Progress=====\n");
                s.append("Records Scanned: " + numRecordsScanned + "\n");
                s.append("Records Ignored: " + numExpiredRecords + " (Out of Retention)\n");
                s.append("Last Fetch Rate: " + (numRecordsScanned - numRecordsScannedLast)
                        / ((currentTimeMs - lastReportTimeMs) / 1000) + " (records/s)\n");
                lastReportTimeMs = currentTimeMs;
                numRecordsScannedLast = numRecordsScanned;
                return s.toString();
            } else {
                return null;
            }
        }

        public void processInconsistentKeys(String storeName,
                Integer partitionId,
                Map<ByteArray, Map<Value, Set<ClusterNode>>> keyVersionNodeSetMap)
                throws IOException {
            if(logger.isDebugEnabled()) {
                logger.debug("TYPE,Store,ParId,Key,ServerSet,VersionTS,VectorClock[,ValueHash]");
            }
            for (Map.Entry<ByteArray, Map<Value, Set<ClusterNode>>> entry : keyVersionNodeSetMap.entrySet()) {
                ByteArray key = entry.getKey();
                if(badKeyWriter != null) {
                    badKeyWriter.write(ByteUtils.toHexString(key.get()) + "\n");
                }
                if(logger.isDebugEnabled()) {
                    Map<Value, Set<ClusterNode>> versionMap = entry.getValue();
                    logger.debug(keyVersionToString(key, versionMap, storeName, partitionId));
                }
            }

            recordInconsistentKey(keyVersionNodeSetMap.size());
        }

        public void recordGoodKey(long count) {
            numGoodKeys += count;
            numTotalKeys += count;
        }

        public void recordInconsistentKey(long count) {
            numTotalKeys += count;
        }
    }

    /**
     * Return args parser
     *
     * @return program parser
     * */
    private static OptionParser getParser() {
        /* parse options */
        OptionParser parser = new OptionParser();
        parser.accepts("help", "print help information");
        parser.accepts("urls", "[REQUIRED] bootstrap URLs")
                .withRequiredArg()
                .describedAs("bootstrap-url")
                .withValuesSeparatedBy(',')
                .ofType(String.class);
        parser.accepts("partitions", "partition-id")
                .withRequiredArg()
                .describedAs("partition-id")
                .withValuesSeparatedBy(',')
                .ofType(Integer.class);
        parser.accepts("store", "store name")
                .withRequiredArg()
                .describedAs("store-name")
                .ofType(String.class);
        parser.accepts("bad-key-file", "File name to which inconsistent keys are to be written.")
                .withRequiredArg()
                .describedAs("badKeyFileOut")
                .ofType(String.class);
        parser.accepts(ComparisonTypeArgument, "type of comparison to compare the values for the same key")
                .withRequiredArg()
                .describedAs("comparisonType")
                .ofType(String.class);
        return parser;
    }

    /**
     * Print Usage to STDOUT
     */
    private static void printUsage() {
        StringBuilder help = new StringBuilder();
        help.append("ConsistencyCheck Tool\n");
        help.append("  Scan partitions of a store by bootstrap url(s) for consistency and\n");
        help.append("  output inconsistent keys to a file.\n");
        help.append("Options:\n");
        help.append("  Required:\n");
        help.append("    --partitions <partitionId>[,<partitionId>...]\n");
        help.append("    --urls <url>[,<url>...]\n");
        help.append("    --store <storeName>\n");
        help.append("    --bad-key-file <badKeyFileOut>\n");
        help.append("  Optional:\n");
        help.append("    --comparison-type [version | hash ]\n");
        help.append("    --help\n");
        help.append("  Note:\n");
        help.append("    If you have two or more clusters to scan for consistency across them,\n");
        help.append("    You will need to supply multiple bootstrap urls, one for each cluster.\n");
        help.append("    When multiple urls are used, all versions are considered as concurrent.\n");
        help.append("    Versioned objects from different nodes are identified by value hashes,\n");
        help.append("    instead of VectorClocks\n");
        help.append("    If specified clusters do not have the same number of partitions, \n");
        help.append("    checking will fail.\n");
        System.out.print(help.toString());
    }

    /**
     * Determine the consistency level of a key
     *
     * @param versionNodeSetMap A map that maps version to set of PrefixNodes
     * @param replicationFactor Total replication factor for the set of clusters
     * @return ConsistencyLevel Enum
     */
    public static ConsistencyLevel determineConsistency(Map<Value, Set<ClusterNode>> versionNodeSetMap,
            int replicationFactor) {
        boolean fullyConsistent = true;
        Value latestVersion = null;
        for (Map.Entry<Value, Set<ClusterNode>> versionNodeSetEntry : versionNodeSetMap.entrySet()) {
            Value value = versionNodeSetEntry.getKey();
            if (latestVersion == null) {
                latestVersion = value;
            } else if (value.isTimeStampLaterThan(latestVersion)) {
                latestVersion = value;
            }
            Set<ClusterNode> nodeSet = versionNodeSetEntry.getValue();
            fullyConsistent = fullyConsistent && (nodeSet.size() == replicationFactor);
        }
        if (fullyConsistent) {
            return ConsistencyLevel.FULL;
        } else {
            // latest write consistent, effectively consistent
            if (latestVersion != null && versionNodeSetMap.get(latestVersion).size() == replicationFactor) {
                return ConsistencyLevel.LATEST_CONSISTENT;
            }
            // all other states inconsistent
            return ConsistencyLevel.INCONSISTENT;
        }
    }


    /**
     * Determine if a key version is invalid by comparing the version's
     * existance and required writes configuration
     *
     * @param keyVersionNodeSetMap A map that contains keys mapping to a map
     *        that maps versions to set of PrefixNodes
     * @param requiredWrite Required Write configuration
     */
    public static void cleanIneligibleKeys(Map<ByteArray, Map<Value, Set<ClusterNode>>> keyVersionNodeSetMap,
            int requiredWrite) {
        Set<ByteArray> keysToDelete = new HashSet<ByteArray>();
        for (Map.Entry<ByteArray, Map<Value, Set<ClusterNode>>> entry : keyVersionNodeSetMap.entrySet()) {
            Set<Value> valuesToDelete = new HashSet<Value>();

            ByteArray key = entry.getKey();
            Map<Value, Set<ClusterNode>> valueNodeSetMap = entry.getValue();
            // mark version for deletion if not enough writes
            for (Map.Entry<Value, Set<ClusterNode>> versionNodeSetEntry : valueNodeSetMap.entrySet()) {
                Set<ClusterNode> nodeSet = versionNodeSetEntry.getValue();
                if (nodeSet.size() < requiredWrite) {
                    valuesToDelete.add(versionNodeSetEntry.getKey());
                }
            }
            // delete versions
            for (Value v : valuesToDelete) {
                valueNodeSetMap.remove(v);
            }
            // mark key for deletion if no versions left
            if (valueNodeSetMap.size() == 0) {
                keysToDelete.add(key);
            }
        }
        // delete keys
        for (ByteArray k : keysToDelete) {
            keyVersionNodeSetMap.remove(k);
        }
    }


    @SuppressWarnings("unchecked")
    public static void main(String[] args) throws Exception {
        OptionSet options = getParser().parse(args);

        /* validate options */
        if (options.hasArgument("help")) {
            printUsage();
            return;
        }
        if (!options.hasArgument("urls") || !options.hasArgument("partitions")
                || !options.hasArgument("store") || !options.hasArgument("bad-key-file")) {
            printUsage();
            return;
        }

        List<String> urls = (List<String>) options.valuesOf("urls");
        String storeName = (String) options.valueOf("store");
        List<Integer> partitionIds = (List<Integer>) options.valuesOf("partitions");
        String badKeyFile = (String) options.valueOf("bad-key-file");

        ComparisonType comparisonType = ComparisonType.VERSION;
        if (options.hasArgument(ComparisonTypeArgument)) {
            String comparisonArgument = (String) options.valueOf(ComparisonTypeArgument);
            comparisonArgument = comparisonArgument.toUpperCase();
            comparisonType = ComparisonType.valueOf(comparisonArgument) ;
        }

        BufferedWriter badKeyWriter = null;
        try {
            badKeyWriter = new BufferedWriter(new FileWriter(badKeyFile));
        } catch (IOException e) {
            Utils.croak("Failure to open output file : " + e.getMessage());
        }

        Map<Integer, Reporter> partitionStatsMap = new HashMap<Integer, Reporter>();
        /* scan each partitions */
        try {
            for (Integer partitionId : partitionIds) {
                ConsistencyCheck checker = new ConsistencyCheck(urls,
                        storeName,
                        partitionId,
                        badKeyWriter,
                        comparisonType);
                checker.connect();
                Reporter reporter = checker.execute();
                partitionStatsMap.put(partitionId, reporter);
            }
        } catch (Exception e) {
            Utils.croak("Exception during consistency checking : " + e.getMessage());
        } finally {
            badKeyWriter.close();
        }

        /* print stats */
        StringBuilder statsString = new StringBuilder();
        long totalGoodKeys = 0;
        long totalTotalKeys = 0;
        // each partition
        statsString.append("TYPE,Store,ParitionId,KeysConsistent,KeysTotal,Consistency\n");
        for (Map.Entry<Integer, Reporter> entry : partitionStatsMap.entrySet()) {
            Integer partitionId = entry.getKey();
            Reporter reporter = entry.getValue();
            totalGoodKeys += reporter.numGoodKeys;
            totalTotalKeys += reporter.numTotalKeys;
            statsString.append("STATS,");
            statsString.append(storeName + ",");
            statsString.append(partitionId + ",");
            statsString.append(reporter.numGoodKeys + ",");
            statsString.append(reporter.numTotalKeys + ",");
            statsString.append((double) (reporter.numGoodKeys) / (double) reporter.numTotalKeys);
            statsString.append("\n");
        }
        // all partitions
        statsString.append("STATS,");
        statsString.append(storeName + ",");
        statsString.append("aggregate,");
        statsString.append(totalGoodKeys + ",");
        statsString.append(totalTotalKeys + ",");
        statsString.append((double) (totalGoodKeys) / (double) totalTotalKeys);
        statsString.append("\n");

        for (String line : statsString.toString().split("\n")) {
            logger.info(line);
        }
    }

    /**
     * Convert a key-version-nodeSet information to string
     *
     * @param key The key
     * @param versionMap mapping versions to set of PrefixNodes
     * @param storeName store's name
     * @param partitionId partition scanned
     * @return a string that describe the information passed in
     */
    public static String keyVersionToString(ByteArray key,
            Map<Value, Set<ClusterNode>> versionMap,
            String storeName,
            Integer partitionId) {
        StringBuilder record = new StringBuilder();
        for (Map.Entry<Value, Set<ClusterNode>> versionSet : versionMap.entrySet()) {
            Value value = versionSet.getKey();
            Set<ClusterNode> nodeSet = versionSet.getValue();

            record.append("BAD_KEY,");
            record.append(storeName + ",");
            record.append(partitionId + ",");
            record.append(ByteUtils.toHexString(key.get()) + ",");
            record.append(nodeSet.toString().replace(", ", ";") + ",");
            record.append(value.toString());
        }
        return record.toString();
    }

    public static abstract class Value {
        abstract Occurred compare(Value v);

        public abstract boolean equals(Object obj);

        public abstract int hashCode();

        public abstract String toString();

        public abstract boolean isExpired(long expiredTimeMs);

        public abstract boolean isTimeStampLaterThan(Value v);
    }

    public static class VersionValue extends Value {

        protected final Version version;

        protected VersionValue(Versioned<byte[]> versioned) {
            this.version = versioned.getVersion();
        }

        public Occurred compare(Value v) {
            if (!(v instanceof VersionValue)) {
                throw new VoldemortException(" Expected type VersionValue found type " + v.getClass().getCanonicalName());
            }

            VersionValue other = (VersionValue) v;
            return version.compare(other.version);
        }

        @Override
        public boolean equals(Object o) {
            if (o == this) {
                return true;
            } else if (!(o instanceof VersionValue)) {
                return false;
            }

            VersionValue other = (VersionValue) o;
            return version.equals(other.version);
        }

        @Override
        public int hashCode() {
            return version.hashCode();
        }

        @Override
        public String toString() {
            StringBuilder record = new StringBuilder();
            record.append(((VectorClock) version).getTimestamp() + ",");
            record.append(version.toString().replaceAll(", ", ";").replaceAll(" ts:[0-9]*", "")
                    .replaceAll("version\\((.*)\\)", "[$1]"));
            return record.toString();
        }

        public boolean isExpired(long expiredTimeMs) {
            return ((VectorClock) version).getTimestamp() < expiredTimeMs;
        }

        public boolean isTimeStampLaterThan(Value currentLatest) {
            if (!(currentLatest instanceof VersionValue)) {
                throw new VoldemortException(
                        " Expected type VersionValue found type " + currentLatest.getClass().getCanonicalName());
            }

            VersionValue latestVersion = (VersionValue) currentLatest;
            long latestTimeStamp = ((VectorClock) latestVersion.version).getTimestamp();
            long myTimeStamp = ((VectorClock) version).getTimestamp();
            return myTimeStamp > latestTimeStamp;
        }
    }

    /**
     * A class to save version and value hash It is used to compare versions by
     * the value hash
     *
     */
    public static class HashedValue extends Value {

        final private Version innerVersion;
        final private Integer valueHash;

        /**
         * @param versioned Versioned value with version information and value
         *        itself
         */
        public HashedValue(Versioned<byte[]> versioned) {
            innerVersion = versioned.getVersion();
            valueHash = new FnvHashFunction().hash(versioned.getValue());
        }

        @Override
        public boolean equals(Object object) {
            if (this == object) {
                return true;
            }
            if (object == null) {
                return false;
            }
            if (!object.getClass().equals(HashedValue.class)) {
                return false;
            }
            HashedValue hash = (HashedValue) object;
            boolean result = valueHash.equals(hash.hashCode());
            return result;
        }

        public Occurred compare(Value v) {
            // TODO: Return before if they are equal.
            return Occurred.CONCURRENTLY; // always regard as conflict
        }

        @Override
        public int hashCode() {
            return valueHash;
        }

        public boolean isExpired(long expiredTimeMs) {
            return ((VectorClock) innerVersion).getTimestamp() < expiredTimeMs;
        }

        @Override
        public String toString() {
            StringBuilder record = new StringBuilder();
            record.append(((VectorClock) innerVersion).getTimestamp() + ",");
            record.append(innerVersion.toString().replaceAll(", ", ";").replaceAll(" ts:[0-9]*", "")
                    .replaceAll("version\\((.*)\\)", "[$1],"));
            record.append(valueHash);
            return record.toString();
        }


        public boolean isTimeStampLaterThan(Value currentLatest) {
            if (!(currentLatest instanceof HashedValue)) {
                throw new VoldemortException(
                        " Expected type HashedValue found type " + currentLatest.getClass().getCanonicalName());
            }

            HashedValue latestVersion = (HashedValue) currentLatest;
            long latestTimeStamp = ((VectorClock) latestVersion.innerVersion).getTimestamp();
            long myTimeStamp = ((VectorClock) innerVersion).getTimestamp();
            return myTimeStamp > latestTimeStamp;
        }
    }

    public class ValueFactory {
        private  final  ComparisonType type;

        public ValueFactory(ComparisonType type)
        {
            this.type = type;
        }

        public Value Create(Versioned<byte[]> versioned) {
            if (type == ComparisonType.HASH) {
                return new HashedValue(versioned);
            } else if (type == ComparisonType.VERSION) {
                return new VersionValue(versioned);
            } else {
                throw new VoldemortException("ComparisonType:" + type.name() + " is not handled by ValueFactory");
            }
        }
    }
}
TOP

Related Classes of voldemort.utils.ConsistencyCheck$HashedValue

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.