com.tangosol.persistence.GUIDHelper Maven / Gradle / Ivy
Show all versions of coherence Show documentation
/*
* Copyright (c) 2000, 2021, Oracle and/or its affiliates.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl.
*/
package com.tangosol.persistence;
import com.tangosol.net.CacheFactory;
import com.tangosol.net.Member;
import com.tangosol.net.partition.PartitionSet;
import com.tangosol.util.Base;
import com.tangosol.util.ConverterCollections;
import com.tangosol.util.ImmutableMultiList;
import com.tangosol.util.NullImplementation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import static com.tangosol.util.Base.out;
/**
* Static helper methods to encode and decode the attributes related to the
* storage of a persistent form of a partition.
*
* A persistence GUID must at minimum reflect the partition-id and a monotonically
* increasing partition-version that could be used to determine, given a set
* of GUIDs representing the same partition, a total ordering of the GUIDs over
* time.
*
* @author rhl/jh 2012.07.06
*/
public class GUIDHelper
{
// ----- static methods -------------------------------------------------
/**
* Generate and return a new GUID for the specified partition.
*
* @param nPartition the partition to return a GUID for
* @param lVersion the creation version of the partition
* @param ldt the creation timestamp; informational only
* @param member the member generating the GUID; informational only
*
* @return a new GUID for the specified partition
*/
public static String generateGUID(int nPartition, long lVersion, long ldt, Member member)
{
return String.format("%d-%x-%x-%d", nPartition, lVersion, ldt, member.getId());
}
/**
* Validate the given GUID.
*
* @param sGUID the GUID to validate
*
* @return true if the specified GUID is valid; false otherwise
*/
public static boolean validateGUID(String sGUID)
{
return sGUID != null && sGUID.matches("\\d+-[0-9a-f]+-[0-9a-f]+-\\d+");
}
/**
* Parse the specified GUID and return the partition-id.
*
* @param sGUID the GUID to return the partition-id for
*
* @return the partition-id
*/
public static int getPartition(String sGUID)
{
return Integer.parseInt(parseAttribute(sGUID, 0));
}
/**
* Parse the specified GUID and return the partition-version.
*
* @param sGUID the GUID to return the partition-version for
*
* @return the partition-version
*/
public static long getVersion(String sGUID)
{
return Long.parseLong(parseAttribute(sGUID, 1), 16);
}
/**
* Parse the specified GUID and return the service join time.
*
* @param sGUID the GUID used to return the service join time
*
* @return the service join
*/
public static long getServiceJoinTime(String sGUID)
{
return Long.parseLong(parseAttribute(sGUID, 2), 16);
}
/**
* Parse the specified GUID and return the originating member-id.
*
* @param sGUID the GUID to return the originating member-id for
*
* @return the originating member-id
*/
public static int getMemberId(String sGUID)
{
return Integer.parseInt(parseAttribute(sGUID, 3));
}
/**
* Return a list of the newest GUID for each partition, indexed by the
* partition-id.
* @deprecated
* Replaced by {@link #resolveNewest(Map, Set, int)}.
*
* @param colGUID the collection of GUIDs to resolve
* @param cPartitions the partition-count
*
* @return a list of the newest GUID for each partition
*/
@Deprecated
public static String[] resolveNewest(Collection colGUID, int cPartitions)
{
String[] asGUIDNewest = new String[cPartitions];
for (String sGUID : colGUID)
{
evaluateGUID(sGUID, asGUIDNewest, new HashSet(), cPartitions);
}
return asGUIDNewest;
}
/**
* Return an array of the newest GUID for each partition, indexed by the
* partition-id and collect previous GUIDs into the provided set.
*
* @param mapGUID the map of GUIDs to resolve
* @param setPrevGUIDs the set that holds old GUIDs
* @param cPartitions the partition-count
*
* @return an array of the newest GUID for each partition
*/
protected static String[] resolveNewest(Map mapGUID, Set setPrevGUIDs, int cPartitions)
{
String[] asGUIDNewest = new String[cPartitions];
for (Map.Entry entry : mapGUID.entrySet())
{
String[] asGUIDThis = entry.getValue();
for (int i = 0, c = asGUIDThis.length; i < c; i++)
{
evaluateGUID(asGUIDThis[i], asGUIDNewest, setPrevGUIDs, cPartitions);
}
}
return asGUIDNewest;
}
/**
* Evaluate the provided GUID ensuring {@code asGUIDNewest} references the
* latest and {@code setPrevGUIDs} references previous GUIDs.
*
* @param sGUID the GUID to be evaluated
* @param asGUIDNewest the array that contains the newest GUIDs
* @param setPrevGUIDs the set that contains the old GUIDs
* @param cPartitions the partition-count
*/
protected static void evaluateGUID(String sGUID, String[] asGUIDNewest,
Set setPrevGUIDs, int cPartitions)
{
int iPartition = getPartition(sGUID);
if (iPartition >= cPartitions)
{
// there may be legally named GUIDs that are found, from a
// previous service incarnation with a different partition-count
setPrevGUIDs.add(sGUID);
return;
}
String sGUIDNewest = asGUIDNewest[iPartition];
if (sGUIDNewest == null || getVersion(sGUID) > getVersion(sGUIDNewest))
{
asGUIDNewest[iPartition] = sGUID;
if (sGUIDNewest != null)
{
setPrevGUIDs.add(sGUIDNewest);
}
}
else if (getVersion(sGUID) < getVersion(sGUIDNewest) ||
getMemberId(sGUID) != getMemberId(sGUIDNewest))
{
setPrevGUIDs.add(sGUID);
}
}
/**
* Return a Map containing assignments of member id to stores based on the
* given constraints.
*
* The algorithm will attempt to fairly distribute assignment of stores across
* members while abiding to the given constraints.
* For example:
*
* Member constraints and assignments
* Member Id Constraints Assignments
* 1 {a,b,c,d} {a,c}
* 2 {a,b,c,d} {b,d}
* 3 {e,f,g,h} {e,g}
* 4 {e,f,g,h} {f,h}
*
*
* @param mapConstraints the constraints to perform assignments within
* @param cDistinctStores the number of expected distinct stores
*
* @return a Map containing assignments of member id to stores based on the
* given constraints
*/
public static Map assignStores(Map mapConstraints, int cDistinctStores)
{
if (mapConstraints == null || mapConstraints.size() == 0)
{
throw new IllegalArgumentException("Unexpected empty map of member to persistent stores");
}
// Internal Notes:
// this algorithm implements a trivial round-robin store assignment
// strategy. To achieve this the provided (member-id -> stores)
// data structure is inverted to (stores -> member-ids) thus creating
// sets of members with the same store visibility; each set of members
// is assigned stores from the common pool of stores.
// Assumptions/Comments:
// - assumes a clean slate, a.k.a there are no current assignments
// - in the common (and realistic) case there are non-intersecting
// sets of members; it is unlikely an intersect exists between two
// sets of members such that members have some shared and some
// not-shared stores; the algorithm does accommodate for this scenario
// but does *not* expend effort in achieving even distribution
Map, Integer[]> mapGUIDMembers = new HashMap<>();
Set setSharedGUIDs = new HashSet<>();
Set setAssignedGUIDs = new HashSet<>();
Map.Entry[] aEntry = createSortedEntries(mapConstraints);
for (int i = 0, c = aEntry.length; i < c; ++i)
{
Integer NMemberCurrent = aEntry[i].getKey();
Object[] aoGUIDs = aEntry[i].getValue();
List listGUIDs = (List) Arrays.asList(aoGUIDs);
if (mapGUIDMembers.containsKey(listGUIDs))
{
continue;
}
List listMembers = new ArrayList<>();
listMembers.add(NMemberCurrent);
for (int j = i + 1; j < c; ++j)
{
Object[] aoGUIDThat = aEntry[j].getValue();
List listIntersection = intersects(aoGUIDs, aoGUIDThat);
int cIntersection = listIntersection == null ? 0 : listIntersection.size();
if (cIntersection == aoGUIDs.length &&
cIntersection == aoGUIDThat.length) // equal
{
listMembers.add(aEntry[j].getKey());
if (j == i + 1)
{
++i;
}
}
else if (cIntersection > 0) // !equal but some shared GUIDs
{
setSharedGUIDs.addAll(listIntersection);
}
}
mapGUIDMembers.put(listGUIDs, listMembers.toArray(new Integer[listMembers.size()]));
}
// sort in a such a way the the least 'visible' GUIDs are assigned first
//noinspection unchecked
List[] alistGUIDs = mapGUIDMembers.keySet().toArray(new List[mapGUIDMembers.size()]);
Arrays.sort(alistGUIDs, (listThis, listThat) -> listThis.size() - listThat.size());
Map> mapMemberGUID = new HashMap<>();
for (List listGUIDs : alistGUIDs)
{
Integer[] aNMembers = mapGUIDMembers.get(listGUIDs);
for (int i = 0, c = listGUIDs.size(), cMembers = aNMembers.length; i < c; ++i)
{
String sGUID = listGUIDs.get(i);
if (!setSharedGUIDs.contains(sGUID) || !setAssignedGUIDs.contains(sGUID))
{
Integer NMember = aNMembers[i % cMembers];
mapMemberGUID.computeIfAbsent(NMember, key -> new ArrayList<>());
mapMemberGUID.get(NMember).add(sGUID);
boolean fAdded = setAssignedGUIDs.add(sGUID);
assert fAdded : sGUID + " was assigned twice; 2nd assignment to member " + NMember;
}
}
}
if (setAssignedGUIDs.size() != cDistinctStores) // error checking
{
// two scenarios exist in which we can enter this error state:
// 1. invalid argument - the provided number of distinct stores
// was not as expected
// 2. algorithm error - the algorithm illegally did not assign a
// provided store; add some debug info to the log
Set