PageRenderTime 53ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/test/unit/org/apache/cassandra/service/paxos/ContentionStrategyTest.java

https://github.com/thelastpickle/cassandra
Java | 430 lines | 367 code | 45 blank | 18 comment | 21 complexity | 440cca5102327e833a41f0e907e7f7cf MD5 | raw file
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. package org.apache.cassandra.service.paxos;
  19. import java.util.List;
  20. import java.util.Random;
  21. import java.util.concurrent.ThreadLocalRandom;
  22. import java.util.concurrent.atomic.AtomicReference;
  23. import java.util.function.BiFunction;
  24. import java.util.function.Consumer;
  25. import java.util.function.DoubleSupplier;
  26. import java.util.function.LongBinaryOperator;
  27. import com.google.common.collect.ImmutableList;
  28. import org.junit.Assert;
  29. import org.junit.Test;
  30. import org.slf4j.Logger;
  31. import org.slf4j.LoggerFactory;
  32. import net.nicoulaj.compilecommand.annotations.Inline;
  33. import org.apache.cassandra.config.DatabaseDescriptor;
  34. import static org.apache.cassandra.service.paxos.ContentionStrategy.*;
  35. import static org.apache.cassandra.service.paxos.ContentionStrategy.WaitRandomizerFactory.*;
  36. import static org.apache.cassandra.service.paxos.ContentionStrategyTest.WaitRandomizerType.*;
  37. public class ContentionStrategyTest
  38. {
  39. private static final Logger logger = LoggerFactory.getLogger(ContentionStrategyTest.class);
  40. static
  41. {
  42. DatabaseDescriptor.daemonInitialization();
  43. }
  44. private static final long MAX = maxQueryTimeoutMicros()/2;
  45. private static final WaitParseValidator DEFAULT_WAIT_RANDOMIZER_VALIDATOR = new WaitParseValidator(defaultWaitRandomizer(), QEXP, 1.5);
  46. private static final BoundParseValidator DEFAULT_MIN_VALIDATOR = new BoundParseValidator(defaultMinWait(), true, assertBound(0, MAX, 0, selectors.maxReadWrite(0f).getClass(), 0.50, 0, modifiers.multiply(0f).getClass(), 0.66));
  47. private static final BoundParseValidator DEFAULT_MAX_VALIDATOR = new BoundParseValidator(defaultMaxWait(), false, assertBound(10000, 100000, 100000, selectors.maxReadWrite(0f).getClass(), 0.95, 0, modifiers.multiplyByAttemptsExp(0f).getClass(), 1.8));
  48. private static final BoundParseValidator DEFAULT_MIN_DELTA_VALIDATOR = new BoundParseValidator(defaultMinDelta(), true, assertBound(5000, MAX, 5000, selectors.maxReadWrite(0f).getClass(), 0.50, 0, modifiers.multiply(0f).getClass(), 0.5));
  49. private static List<BoundParseValidator> VALIDATE = ImmutableList.of(
  50. new BoundParseValidator("p95(rw)", false, assertBound(0, MAX, MAX, selectors.maxReadWrite(0f).getClass(), 0.95, 0, modifiers.identity().getClass(), 1)),
  51. new BoundParseValidator("5ms<=p50(rw)*0.66", false, assertBound(5000, MAX, MAX, selectors.maxReadWrite(0f).getClass(), 0.50, 0, modifiers.multiply(0).getClass(), 0.66)),
  52. new BoundParseValidator("5us <= p50(r)*1.66*attempts", true, assertBound(5, MAX, 5, selectors.read(0f).getClass(), 0.50, 0, modifiers.multiplyByAttempts(0f).getClass(), 1.66)),
  53. new BoundParseValidator("0<=p50(w)*0.66^attempts", true, assertBound(0, MAX, 0, selectors.write(0f).getClass(), 0.50, 0, modifiers.multiplyByAttemptsExp(0f).getClass(), 0.66)),
  54. new BoundParseValidator("125us", true, assertBound(125, 125, 125, selectors.constant(0).getClass(), 0.0f, 125, modifiers.identity().getClass(), 1)),
  55. new BoundParseValidator("5us <= p95(r)*1.8^attempts <= 100us", true, assertBound(5, 100, 5, selectors.read(0f).getClass(), 0.95, 0, modifiers.multiplyByAttemptsExp(0f).getClass(), 1.8)),
  56. DEFAULT_MIN_VALIDATOR, DEFAULT_MAX_VALIDATOR, DEFAULT_MIN_DELTA_VALIDATOR
  57. );
  58. private static List<WaitParseValidator> VALIDATE_RANDOMIZER = ImmutableList.of(
  59. new WaitParseValidator("quantizedexponential(0.5)", QEXP, 0.5),
  60. new WaitParseValidator("exponential(2.5)", EXP, 2.5),
  61. new WaitParseValidator("exp(10)", EXP, 10),
  62. new WaitParseValidator("uniform", UNIFORM, 0),
  63. DEFAULT_WAIT_RANDOMIZER_VALIDATOR
  64. );
  65. static class BoundParseValidator
  66. {
  67. final String spec;
  68. final boolean isMin;
  69. final Consumer<Bound> validator;
  70. BoundParseValidator(String spec, boolean isMin, Consumer<Bound> validator)
  71. {
  72. this.spec = spec;
  73. this.isMin = isMin;
  74. this.validator = validator;
  75. }
  76. void validate(Bound bound)
  77. {
  78. validator.accept(bound);
  79. }
  80. }
  81. enum WaitRandomizerType
  82. {
  83. UNIFORM(Uniform.class, (p, f) -> f.uniform()),
  84. EXP(Exponential.class, (p, f) -> f.exponential(p)),
  85. QEXP(QuantizedExponential.class, (p, f) -> f.quantizedExponential(p));
  86. final Class<? extends WaitRandomizer> clazz;
  87. final BiFunction<Double, WaitRandomizerFactory, WaitRandomizer> getter;
  88. WaitRandomizerType(Class<? extends WaitRandomizer> clazz, BiFunction<Double, WaitRandomizerFactory, WaitRandomizer> getter)
  89. {
  90. this.clazz = clazz;
  91. this.getter = getter;
  92. }
  93. }
  94. static class WaitParseValidator
  95. {
  96. final String spec;
  97. final WaitRandomizerType type;
  98. final double power;
  99. WaitParseValidator(String spec, WaitRandomizerType type, double power)
  100. {
  101. this.spec = spec;
  102. this.type = type;
  103. this.power = power;
  104. }
  105. void validate(WaitRandomizer randomizer)
  106. {
  107. Assert.assertSame(type.clazz, randomizer.getClass());
  108. if (AbstractExponential.class.isAssignableFrom(type.clazz))
  109. Assert.assertEquals(power, ((AbstractExponential) randomizer).power, 0.00001);
  110. }
  111. }
  112. private static class WaitRandomizerOutputValidator
  113. {
  114. static void validate(WaitRandomizerType type, long seed, int trials, int samplesPerTrial)
  115. {
  116. Random random = new Random(seed);
  117. WaitRandomizer randomizer = type.getter.apply(2d, new WaitRandomizerFactory()
  118. {
  119. @Override public LongBinaryOperator uniformLongSupplier() { return (min, max) -> min + random.nextInt((int) (max - min)); }
  120. @Override public DoubleSupplier uniformDoubleSupplier() { return random::nextDouble; }
  121. });
  122. for (int i = 0 ; i < trials ; ++i)
  123. {
  124. int min = random.nextInt(1 << 20);
  125. int max = min + 1024 + random.nextInt(1 << 20);
  126. double minMean = minMean(type, min, max);
  127. double maxMean = maxMean(type, min, max);
  128. double sampleMean = sampleMean(samplesPerTrial, min, max, randomizer);
  129. Assert.assertTrue(minMean <= sampleMean);
  130. Assert.assertTrue(maxMean >= sampleMean);
  131. }
  132. }
  133. private static double minMean(WaitRandomizerType type, int min, int max)
  134. {
  135. switch (type)
  136. {
  137. case UNIFORM: return min + (max - min) * (4d/10);
  138. case EXP: case QEXP: return min + (max - min) * (6d/10);
  139. default: throw new IllegalStateException();
  140. }
  141. }
  142. private static double maxMean(WaitRandomizerType type, int min, int max)
  143. {
  144. switch (type)
  145. {
  146. case UNIFORM: return min + (max - min) * (6d/10);
  147. case EXP: case QEXP: return min + (max - min) * (8d/10);
  148. default: throw new IllegalStateException();
  149. }
  150. }
  151. private static double sampleMean(int samples, int min, int max, WaitRandomizer randomizer)
  152. {
  153. double sum = 0;
  154. int attempts = 1;
  155. for (int i = 0 ; i < samples ; ++i)
  156. {
  157. long wait = randomizer.wait(min, max, attempts = (attempts & 15) + 1);
  158. Assert.assertTrue(wait >= min);
  159. Assert.assertTrue(wait <= max);
  160. sum += wait;
  161. }
  162. double mean = sum / samples;
  163. Assert.assertTrue(mean >= min);
  164. Assert.assertTrue(mean <= max);
  165. return mean;
  166. }
  167. }
  168. private static Consumer<Bound> assertBound(
  169. long min, long max, long onFailure,
  170. Class<? extends LatencySelector> selectorClass,
  171. double selectorPercentile,
  172. long selectorConst,
  173. Class<? extends LatencyModifier> modifierClass,
  174. double modifierVal
  175. )
  176. {
  177. return bound -> {
  178. Assert.assertEquals(min, bound.min);
  179. Assert.assertEquals(max, bound.max);
  180. Assert.assertEquals(onFailure, bound.onFailure);
  181. Assert.assertSame(selectorClass, bound.selector.getClass());
  182. if (selectorClass == selectors.constant(0).getClass())
  183. {
  184. LatencySupplier fail = v -> { throw new UnsupportedOperationException(); };
  185. Assert.assertEquals(selectorConst, bound.selector.select(fail, fail));
  186. }
  187. else
  188. {
  189. AtomicReference<Double> percentile = new AtomicReference<>();
  190. LatencySupplier set = v -> { percentile.set(v); return 0; };
  191. bound.selector.select(set, set);
  192. Assert.assertNotNull(percentile.get());
  193. Assert.assertEquals(selectorPercentile, percentile.get(), 0.00001);
  194. }
  195. Assert.assertSame(modifierClass, bound.modifier.getClass());
  196. Assert.assertEquals(1000000L * modifierVal, bound.modifier.modify(1000000, 1), 0.00001);
  197. };
  198. }
  199. private static void assertParseFailure(String spec)
  200. {
  201. try
  202. {
  203. Bound bound = parseBound(spec, false);
  204. Assert.fail("expected parse failure, but got " + bound);
  205. }
  206. catch (IllegalArgumentException e)
  207. {
  208. // expected
  209. }
  210. }
  211. @Test
  212. public void strategyParseTest()
  213. {
  214. for (BoundParseValidator min : VALIDATE.stream().filter(v -> v.isMin).toArray(BoundParseValidator[]::new))
  215. {
  216. for (BoundParseValidator max : VALIDATE.stream().filter(v -> !v.isMin).toArray(BoundParseValidator[]::new))
  217. {
  218. for (BoundParseValidator minDelta : VALIDATE.stream().filter(v -> v.isMin).toArray(BoundParseValidator[]::new))
  219. {
  220. for (WaitParseValidator random : VALIDATE_RANDOMIZER)
  221. {
  222. {
  223. ParsedStrategy parsed = parseStrategy("min=" + min.spec + ",max=" + max.spec + ",delta=" + minDelta.spec + ",random=" + random.spec);
  224. Assert.assertEquals(parsed.min, min.spec);
  225. min.validate(parsed.strategy.min);
  226. Assert.assertEquals(parsed.max, max.spec);
  227. max.validate(parsed.strategy.max);
  228. Assert.assertEquals(parsed.minDelta, minDelta.spec);
  229. minDelta.validate(parsed.strategy.minDelta);
  230. Assert.assertEquals(parsed.waitRandomizer, random.spec);
  231. random.validate(parsed.strategy.waitRandomizer);
  232. }
  233. ParsedStrategy parsed = parseStrategy("random=" + random.spec);
  234. Assert.assertEquals(parsed.min, DEFAULT_MIN_VALIDATOR.spec);
  235. DEFAULT_MIN_VALIDATOR.validate(parsed.strategy.min);
  236. Assert.assertEquals(parsed.max, DEFAULT_MAX_VALIDATOR.spec);
  237. DEFAULT_MAX_VALIDATOR.validate(parsed.strategy.max);
  238. Assert.assertEquals(parsed.minDelta, DEFAULT_MIN_DELTA_VALIDATOR.spec);
  239. DEFAULT_MIN_DELTA_VALIDATOR.validate(parsed.strategy.minDelta);
  240. Assert.assertEquals(parsed.waitRandomizer, random.spec);
  241. random.validate(parsed.strategy.waitRandomizer);
  242. }
  243. ParsedStrategy parsed = parseStrategy("delta=" + minDelta.spec);
  244. Assert.assertEquals(parsed.min, DEFAULT_MIN_VALIDATOR.spec);
  245. DEFAULT_MIN_VALIDATOR.validate(parsed.strategy.min);
  246. Assert.assertEquals(parsed.max, DEFAULT_MAX_VALIDATOR.spec);
  247. DEFAULT_MAX_VALIDATOR.validate(parsed.strategy.max);
  248. Assert.assertEquals(parsed.minDelta, minDelta.spec);
  249. minDelta.validate(parsed.strategy.minDelta);
  250. }
  251. ParsedStrategy parsed = parseStrategy("max=" + max.spec);
  252. Assert.assertEquals(parsed.min, DEFAULT_MIN_VALIDATOR.spec);
  253. DEFAULT_MIN_VALIDATOR.validate(parsed.strategy.min);
  254. Assert.assertEquals(parsed.max, max.spec);
  255. max.validate(parsed.strategy.max);
  256. Assert.assertEquals(parsed.minDelta, DEFAULT_MIN_DELTA_VALIDATOR.spec);
  257. DEFAULT_MIN_DELTA_VALIDATOR.validate(parsed.strategy.minDelta);
  258. }
  259. ParsedStrategy parsed = parseStrategy("min=" + min.spec);
  260. Assert.assertEquals(parsed.min, min.spec);
  261. min.validate(parsed.strategy.min);
  262. Assert.assertEquals(parsed.max, DEFAULT_MAX_VALIDATOR.spec);
  263. DEFAULT_MAX_VALIDATOR.validate(parsed.strategy.max);
  264. Assert.assertEquals(parsed.minDelta, DEFAULT_MIN_DELTA_VALIDATOR.spec);
  265. DEFAULT_MIN_DELTA_VALIDATOR.validate(parsed.strategy.minDelta);
  266. }
  267. }
  268. @Test
  269. public void testParseRoundTrip()
  270. {
  271. LatencySelectorFactory selectorFactory = new LatencySelectorFactory()
  272. {
  273. LatencySelectorFactory delegate = ContentionStrategy.selectors;
  274. public LatencySelector constant(long latency) { return selector(delegate.constant(latency), String.format("%dms", latency)); }
  275. public LatencySelector read(double percentile) { return selector(delegate.read(percentile), String.format("p%d(r)", (int) (percentile * 100))); }
  276. public LatencySelector write(double percentile) { return selector(delegate.write(percentile), String.format("p%d(w)", (int) (percentile * 100))); }
  277. public LatencySelector maxReadWrite(double percentile) { return selector(delegate.maxReadWrite(percentile), String.format("p%d(rw)", (int) percentile * 100)); }
  278. private LatencySelector selector(LatencySelector selector, String str) {
  279. return new LatencySelector()
  280. {
  281. public long select(LatencySupplier read, LatencySupplier write)
  282. {
  283. return selector.select(read, write);
  284. }
  285. public String toString()
  286. {
  287. return str;
  288. }
  289. };
  290. }
  291. };
  292. LatencyModifierFactory modifierFactory = new LatencyModifierFactory()
  293. {
  294. LatencyModifierFactory delegate = ContentionStrategy.modifiers;
  295. public LatencyModifier identity() { return modifier(delegate.identity(), ""); }
  296. public LatencyModifier multiply(double constant) { return modifier(delegate.multiply(constant), String.format(" * %.2f", constant)); }
  297. public LatencyModifier multiplyByAttempts(double multiply) { return modifier(delegate.multiplyByAttempts(multiply), String.format(" * %.2f * attempts", multiply)); }
  298. public LatencyModifier multiplyByAttemptsExp(double base) { return modifier(delegate.multiplyByAttemptsExp(base), String.format(" * %.2f ^ attempts", base)); }
  299. private LatencyModifier modifier(LatencyModifier modifier, String str) {
  300. return new LatencyModifier()
  301. {
  302. @Inline
  303. public long modify(long latency, int attempts)
  304. {
  305. return modifier.modify(latency, attempts);
  306. }
  307. public String toString()
  308. {
  309. return str;
  310. }
  311. };
  312. }
  313. };
  314. LatencyModifier[] latencyModifiers = new LatencyModifier[]{
  315. modifierFactory.multiply(0.5),
  316. modifierFactory.multiplyByAttempts(0.5),
  317. modifierFactory.multiplyByAttemptsExp(0.5)
  318. };
  319. LatencySelector[] latencySelectors = new LatencySelector[]{
  320. selectorFactory.read(0.5),
  321. selectorFactory.write(0.5),
  322. selectorFactory.maxReadWrite(0.99)
  323. };
  324. for (boolean min : new boolean[] { true, false})
  325. {
  326. String left = min ? "10ms <= " : "";
  327. for (boolean max : new boolean[] { true, false})
  328. {
  329. String right = max ? " <= 10ms" : "";
  330. for (LatencySelector selector : latencySelectors)
  331. {
  332. for (LatencyModifier modifier : latencyModifiers)
  333. {
  334. String mid = String.format("%s%s", selector, modifier);
  335. String input = left + mid + right;
  336. Bound bound = parseBound(input, false, selectorFactory, modifierFactory);
  337. Assert.assertTrue(String.format("Bound: %d" , bound.min), !min || bound.min == 10000);
  338. Assert.assertTrue(String.format("Bound: %d" , bound.max), !max || bound.max == 10000);
  339. Assert.assertEquals(selector.toString(), bound.selector.toString());
  340. Assert.assertEquals(modifier.toString(), bound.modifier.toString());
  341. }
  342. }
  343. }
  344. }
  345. }
  346. @Test
  347. public void boundParseTest()
  348. {
  349. VALIDATE.forEach(v -> v.validate(parseBound(v.spec, v.isMin)));
  350. }
  351. @Test
  352. public void waitRandomizerParseTest()
  353. {
  354. VALIDATE_RANDOMIZER.forEach(v -> v.validate(parseWaitRandomizer(v.spec)));
  355. }
  356. @Test
  357. public void waitRandomizerSampleTest()
  358. {
  359. waitRandomizerSampleTest(2);
  360. }
  361. private void waitRandomizerSampleTest(int count)
  362. {
  363. while (count-- > 0)
  364. {
  365. long seed = ThreadLocalRandom.current().nextLong();
  366. logger.info("Seed {}", seed);
  367. for (WaitRandomizerType type : WaitRandomizerType.values())
  368. {
  369. WaitRandomizerOutputValidator.validate(type, seed, 100, 1000000);
  370. }
  371. }
  372. }
  373. @Test
  374. public void boundParseFailureTest()
  375. {
  376. assertParseFailure("10ms <= p95(r) <= 5ms");
  377. assertParseFailure("10 <= p95(r)");
  378. assertParseFailure("10 <= 20 <= 30");
  379. assertParseFailure("p95(r) < 5");
  380. assertParseFailure("p95(x)");
  381. assertParseFailure("p95()");
  382. assertParseFailure("p95");
  383. assertParseFailure("p50(rw)+0.66");
  384. }
  385. }