diff --git a/pom.xml b/pom.xml
index 1bfb1e31dc7..5f15bec2533 100644
--- a/pom.xml
+++ b/pom.xml
@@ -431,7 +431,7 @@
net.spy
spymemcached
- 2.11.4
+ 2.11.7
org.antlr
diff --git a/server/src/main/java/io/druid/client/cache/MemcachedCache.java b/server/src/main/java/io/druid/client/cache/MemcachedCache.java
index 42e7e8023a2..96abb239aff 100644
--- a/server/src/main/java/io/druid/client/cache/MemcachedCache.java
+++ b/server/src/main/java/io/druid/client/cache/MemcachedCache.java
@@ -17,16 +17,19 @@
package io.druid.client.cache;
+import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.Maps;
+import com.google.common.hash.HashFunction;
+import com.google.common.hash.Hashing;
import com.google.common.primitives.Ints;
import com.metamx.common.logger.Logger;
import net.spy.memcached.AddrUtil;
import net.spy.memcached.ConnectionFactoryBuilder;
-import net.spy.memcached.DefaultHashAlgorithm;
import net.spy.memcached.FailureMode;
+import net.spy.memcached.HashAlgorithm;
import net.spy.memcached.MemcachedClient;
import net.spy.memcached.MemcachedClientIF;
import net.spy.memcached.internal.BulkFuture;
@@ -49,6 +52,23 @@ public class MemcachedCache implements Cache
{
private static final Logger log = new Logger(MemcachedCache.class);
+ final static HashAlgorithm MURMUR3_128 = new HashAlgorithm()
+ {
+ final HashFunction fn = Hashing.murmur3_128();
+
+ @Override
+ public long hash(String k)
+ {
+ return fn.hashString(k, Charsets.UTF_8).asLong();
+ }
+
+ @Override
+ public String toString()
+ {
+ return fn.toString();
+ }
+ };
+
public static MemcachedCache create(final MemcachedCacheConfig config)
{
try {
@@ -67,18 +87,22 @@ public class MemcachedCache implements Cache
return new MemcachedCache(
new MemcachedClient(
- new ConnectionFactoryBuilder().setProtocol(ConnectionFactoryBuilder.Protocol.BINARY)
- .setHashAlg(DefaultHashAlgorithm.FNV1A_64_HASH)
- .setLocatorType(ConnectionFactoryBuilder.Locator.CONSISTENT)
- .setDaemon(true)
- .setFailureMode(FailureMode.Cancel)
- .setTranscoder(transcoder)
- .setShouldOptimize(true)
- .setOpQueueMaxBlockTime(config.getTimeout())
- .setOpTimeout(config.getTimeout())
- .setReadBufferSize(config.getReadBufferSize())
- .setOpQueueFactory(opQueueFactory)
- .build(),
+ new MemcachedCustomConnectionFactoryBuilder()
+ // 1000 repetitions gives us good distribution with murmur3_128
+ // (approx < 5% difference in counts across nodes, with 5 cache nodes)
+ .setKetamaNodeRepetitions(1000)
+ .setHashAlg(MURMUR3_128)
+ .setProtocol(ConnectionFactoryBuilder.Protocol.BINARY)
+ .setLocatorType(ConnectionFactoryBuilder.Locator.CONSISTENT)
+ .setDaemon(true)
+ .setFailureMode(FailureMode.Cancel)
+ .setTranscoder(transcoder)
+ .setShouldOptimize(true)
+ .setOpQueueMaxBlockTime(config.getTimeout())
+ .setOpTimeout(config.getTimeout())
+ .setReadBufferSize(config.getReadBufferSize())
+ .setOpQueueFactory(opQueueFactory)
+ .build(),
AddrUtil.getAddresses(config.getHosts())
),
config
diff --git a/server/src/main/java/io/druid/client/cache/MemcachedCustomConnectionFactoryBuilder.java b/server/src/main/java/io/druid/client/cache/MemcachedCustomConnectionFactoryBuilder.java
new file mode 100644
index 00000000000..e62929532f9
--- /dev/null
+++ b/server/src/main/java/io/druid/client/cache/MemcachedCustomConnectionFactoryBuilder.java
@@ -0,0 +1,197 @@
+/*
+ * Licensed to Metamarkets Group Inc. (Metamarkets) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Metamarkets licenses this file
+ * to you 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 io.druid.client.cache;
+
+import net.spy.memcached.ArrayModNodeLocator;
+import net.spy.memcached.ConnectionFactory;
+import net.spy.memcached.ConnectionFactoryBuilder;
+import net.spy.memcached.ConnectionObserver;
+import net.spy.memcached.DefaultConnectionFactory;
+import net.spy.memcached.FailureMode;
+import net.spy.memcached.HashAlgorithm;
+import net.spy.memcached.KetamaNodeLocator;
+import net.spy.memcached.MemcachedNode;
+import net.spy.memcached.NodeLocator;
+import net.spy.memcached.OperationFactory;
+import net.spy.memcached.auth.AuthDescriptor;
+import net.spy.memcached.metrics.MetricCollector;
+import net.spy.memcached.metrics.MetricType;
+import net.spy.memcached.ops.Operation;
+import net.spy.memcached.transcoders.Transcoder;
+import net.spy.memcached.util.DefaultKetamaNodeLocatorConfiguration;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+
+class MemcachedCustomConnectionFactoryBuilder extends ConnectionFactoryBuilder
+{
+ private int repetitions = new DefaultKetamaNodeLocatorConfiguration().getNodeRepetitions();
+
+ public MemcachedCustomConnectionFactoryBuilder setKetamaNodeRepetitions(int repetitions)
+ {
+ this.repetitions = repetitions;
+ return this;
+ }
+
+ // borrowed from ConnectionFactoryBuilder to allow setting number of repetitions for KetamaNodeLocator
+ @Override
+ public ConnectionFactory build()
+ {
+ return new DefaultConnectionFactory() {
+ @Override
+ public NodeLocator createLocator(List nodes) {
+ switch (locator) {
+ case ARRAY_MOD:
+ return new ArrayModNodeLocator(nodes, getHashAlg());
+ case CONSISTENT:
+ return new KetamaNodeLocator(
+ nodes,
+ getHashAlg(),
+ new DefaultKetamaNodeLocatorConfiguration()
+ {
+ @Override
+ public int getNodeRepetitions()
+ {
+ return repetitions;
+ }
+ }
+ );
+ default:
+ throw new IllegalStateException("Unhandled locator type: " + locator);
+ }
+ }
+
+ @Override
+ public BlockingQueue createOperationQueue() {
+ return opQueueFactory == null ? super.createOperationQueue()
+ : opQueueFactory.create();
+ }
+
+ @Override
+ public BlockingQueue createReadOperationQueue() {
+ return readQueueFactory == null ? super.createReadOperationQueue()
+ : readQueueFactory.create();
+ }
+
+ @Override
+ public BlockingQueue createWriteOperationQueue() {
+ return writeQueueFactory == null ? super.createReadOperationQueue()
+ : writeQueueFactory.create();
+ }
+
+ @Override
+ public Transcoder