/** * Human-readable String from context. * * @param context counter context. * @return a human-readable String of the context. */ public String toString(ByteBuffer context) { ContextState state = ContextState.wrap(context); StringBuilder sb = new StringBuilder(); sb.append("["); while (state.hasRemaining()) { if (state.getElementIndex() > 0) sb.append(","); sb.append("{"); sb.append(state.getCounterId()).append(", "); sb.append(state.getClock()).append(", "); sb.append(state.getCount()); sb.append("}"); if (state.isGlobal()) sb.append("$"); else if (state.isLocal()) sb.append("*"); state.moveToNext(); } sb.append("]"); return sb.toString(); }
/* * Compares two shards, returns: * - GREATER_THAN if leftState overrides rightState * - LESS_THAN if rightState overrides leftState * - EQUAL for two equal, non-local, shards * - DISJOINT for any two local shards */ private Relationship compare(ContextState leftState, ContextState rightState) { long leftClock = leftState.getClock(); long leftCount = leftState.getCount(); long rightClock = rightState.getClock(); long rightCount = rightState.getCount(); if (leftState.isGlobal() || rightState.isGlobal()) { if (leftState.isGlobal() && rightState.isGlobal()) { if (leftClock == rightClock) { // Can happen if an sstable gets lost and disk failure policy is set to 'best effort' if (leftCount != rightCount && CompactionManager.isCompactionManager.get()) { logger.warn( "invalid global counter shard detected; ({}, {}, {}) and ({}, {}, {}) differ only in " + "count; will pick highest to self-heal on compaction", leftState.getCounterId(), leftClock, leftCount, rightState.getCounterId(), rightClock, rightCount); } if (leftCount > rightCount) return Relationship.GREATER_THAN; else if (leftCount == rightCount) return Relationship.EQUAL; else return Relationship.LESS_THAN; } else { return leftClock > rightClock ? Relationship.GREATER_THAN : Relationship.LESS_THAN; } } else // only one is global - keep that one { return leftState.isGlobal() ? Relationship.GREATER_THAN : Relationship.LESS_THAN; } } if (leftState.isLocal() || rightState.isLocal()) { // Local id and at least one is a local shard. if (leftState.isLocal() && rightState.isLocal()) return Relationship.DISJOINT; else // only one is local - keep that one return leftState.isLocal() ? Relationship.GREATER_THAN : Relationship.LESS_THAN; } // both are remote shards if (leftClock == rightClock) { // We should never see non-local shards w/ same id+clock but different counts. However, if we // do // we should "heal" the problem by being deterministic in our selection of shard - and // log the occurrence so that the operator will know something is wrong. if (leftCount != rightCount && CompactionManager.isCompactionManager.get()) { logger.warn( "invalid remote counter shard detected; ({}, {}, {}) and ({}, {}, {}) differ only in " + "count; will pick highest to self-heal on compaction", leftState.getCounterId(), leftClock, leftCount, rightState.getCounterId(), rightClock, rightCount); } if (leftCount > rightCount) return Relationship.GREATER_THAN; else if (leftCount == rightCount) return Relationship.EQUAL; else return Relationship.LESS_THAN; } else { if ((leftClock >= 0 && rightClock > 0 && leftClock >= rightClock) || (leftClock < 0 && (rightClock > 0 || leftClock < rightClock))) return Relationship.GREATER_THAN; else return Relationship.LESS_THAN; } }
/** * Return a context w/ an aggregated count for each counter id. * * @param left counter context. * @param right counter context. */ public ByteBuffer merge(ByteBuffer left, ByteBuffer right) { boolean leftIsSuperSet = true; boolean rightIsSuperSet = true; int globalCount = 0; int localCount = 0; int remoteCount = 0; ContextState leftState = ContextState.wrap(left); ContextState rightState = ContextState.wrap(right); while (leftState.hasRemaining() && rightState.hasRemaining()) { int cmp = leftState.compareIdTo(rightState); if (cmp == 0) { Relationship rel = compare(leftState, rightState); if (rel == Relationship.GREATER_THAN) rightIsSuperSet = false; else if (rel == Relationship.LESS_THAN) leftIsSuperSet = false; else if (rel == Relationship.DISJOINT) leftIsSuperSet = rightIsSuperSet = false; if (leftState.isGlobal() || rightState.isGlobal()) globalCount += 1; else if (leftState.isLocal() || rightState.isLocal()) localCount += 1; else remoteCount += 1; leftState.moveToNext(); rightState.moveToNext(); } else if (cmp > 0) { leftIsSuperSet = false; if (rightState.isGlobal()) globalCount += 1; else if (rightState.isLocal()) localCount += 1; else remoteCount += 1; rightState.moveToNext(); } else // cmp < 0 { rightIsSuperSet = false; if (leftState.isGlobal()) globalCount += 1; else if (leftState.isLocal()) localCount += 1; else remoteCount += 1; leftState.moveToNext(); } } if (leftState.hasRemaining()) rightIsSuperSet = false; else if (rightState.hasRemaining()) leftIsSuperSet = false; // if one of the contexts is a superset, return it early. if (leftIsSuperSet) return left; else if (rightIsSuperSet) return right; while (leftState.hasRemaining()) { if (leftState.isGlobal()) globalCount += 1; else if (leftState.isLocal()) localCount += 1; else remoteCount += 1; leftState.moveToNext(); } while (rightState.hasRemaining()) { if (rightState.isGlobal()) globalCount += 1; else if (rightState.isLocal()) localCount += 1; else remoteCount += 1; rightState.moveToNext(); } leftState.reset(); rightState.reset(); return merge( ContextState.allocate(globalCount, localCount, remoteCount), leftState, rightState); }