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

com.aerospike.client.cluster.PartitionParser Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2012-2022 Aerospike, Inc.
 *
 * Portions may be licensed to Aerospike, Inc. under one or more contributor
 * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
 *
 * 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 com.aerospike.client.cluster;

import java.util.HashMap;
import java.util.concurrent.atomic.AtomicReferenceArray;

import com.aerospike.client.AerospikeException;
import com.aerospike.client.Info;
import com.aerospike.client.Log;
import com.aerospike.client.command.Buffer;
import com.aerospike.client.util.Crypto;

/**
 * Parse node's master (and optionally prole) partitions.
 */
public final class PartitionParser extends Info {
	static final String PartitionGeneration = "partition-generation";
	static final String Replicas = "replicas";

	private HashMap map;
	private final int partitionCount;
	private final int generation;
	private boolean copied;
	private boolean regimeError;

	public PartitionParser(Connection conn, Node node, HashMap map, int partitionCount) {
		// Send format 1:  partition-generation\nreplicas\n
		super(conn, PartitionGeneration, Replicas);
		this.partitionCount = partitionCount;
		this.map = map;

		if (length == 0) {
			throw new AerospikeException.Parse("Partition info is empty");
		}

		this.generation = parseGeneration();
		parseReplicasAll(node, Replicas);
	}

	public int getGeneration() {
		return generation;
	}

	public boolean isPartitionMapCopied() {
		return copied;
	}

	public HashMap getPartitionMap() {
		return map;
	}

	private int parseGeneration() {
		parseName(PartitionGeneration);
		int gen = parseInt();
		expect('\n');
		return gen;
	}

	private void parseReplicasAll(Node node, String command) throws AerospikeException {
		// Use low-level info methods and parse byte array directly for maximum performance.
		// Receive format: replicas-all\t
		//                 :[regime],,,...;
		//                 :[regime],,,...;\n
		parseName(command);

		int begin = offset;
		int regime = 0;

		while (offset < length) {
			if (buffer[offset] == ':') {
				// Parse namespace.
				String namespace = Buffer.utf8ToString(buffer, begin, offset - begin, sb).trim();

				if (namespace.length() <= 0 || namespace.length() >= 32) {
					String response = getTruncatedResponse();
					throw new AerospikeException.Parse("Invalid partition namespace " +
						namespace + ". Response=" + response);
				}
				begin = ++offset;

				// Parse regime.
				if (command == Replicas) {
					while (offset < length) {
						byte b = buffer[offset];

						if (b == ',') {
							break;
						}
						offset++;
					}
					regime = Integer.parseInt(new String(buffer, begin, offset - begin));
					begin = ++offset;
				}

				// Parse replica count.
				while (offset < length) {
					byte b = buffer[offset];

					if (b == ',') {
						break;
					}
					offset++;
				}
				int replicaCount = Integer.parseInt(new String(buffer, begin, offset - begin));

				// Ensure replicaCount is uniform.
				Partitions partitions = map.get(namespace);

				if (partitions == null) {
					// Create new replica array.
					partitions = new Partitions(partitionCount, replicaCount, regime != 0);
					copyPartitionMap();
					map.put(namespace, partitions);
				}
				else if (partitions.replicas.length != replicaCount) {
					if (Log.infoEnabled()) {
						Log.info("Namespace " + namespace + " replication factor changed from " + partitions.replicas.length + " to " + replicaCount);
					}

					// Resize partition map.
					Partitions tmp = new Partitions(partitions, replicaCount);

					copyPartitionMap();
					partitions = tmp;
					map.put(namespace, partitions);
				}

				// Parse partition bitmaps.
				for (int i = 0; i < replicaCount; i++) {
					begin = ++offset;

					// Find bitmap endpoint
					while (offset < length) {
						byte b = buffer[offset];

						if (b == ',' || b == ';') {
							break;
						}
						offset++;
					}

					if (offset == begin) {
						String response = getTruncatedResponse();
						throw new AerospikeException.Parse("Empty partition id for namespace " +
							namespace + ". Response=" + response);
					}

					// Log.info("Map: " + namespace + '[' + i + "] " + node);
					decodeBitmap(node, partitions, i, regime, begin);
				}
				begin = ++offset;
			}
			else {
				offset++;
			}
		}
	}

	private void decodeBitmap(Node node, Partitions partitions, int index, int regime, int begin) {
		AtomicReferenceArray nodeArray = partitions.replicas[index];
		int[] regimes = partitions.regimes;
		byte[] restoreBuffer = Crypto.decodeBase64(buffer, begin, offset - begin);

		for (int i = 0; i < partitionCount; i++) {
			Node nodeOld = nodeArray.get(i);

			if ((restoreBuffer[i >> 3] & (0x80 >> (i & 7))) != 0) {
				// Node owns this partition.
				int regimeOld = regimes[i];

				if (regime >= regimeOld) {
					// Log.info("Map: " + i);
					if (regime > regimeOld) {
						regimes[i] = regime;
					}

					if (nodeOld != null && nodeOld != node) {
						// Force previously mapped node to refresh it's partition map on next cluster tend.
						nodeOld.partitionGeneration = -1;
					}

					// Use lazy set because there is only one producer thread. In addition,
					// there is a one second delay due to the cluster tend polling interval.
					// An extra millisecond for a node change will not make a difference and
					// overall performance is improved.
					nodeArray.lazySet(i, node);
				}
				else {
					if (!regimeError) {
						if (Log.infoEnabled()) {
							Log.info(node.toString() + " regime(" + regime + ") < old regime(" + regimeOld + ")");
						}
						regimeError = true;
					}
				}
			}
		}
	}

	private void copyPartitionMap() {
		if (! copied) {
			// Make shallow copy of map.
			map = new HashMap(map);
			copied = true;
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy