zipkin.storage.cassandra.DeduplicatingExecutor Maven / Gradle / Ivy
/**
* Copyright 2015-2017 The OpenZipkin Authors
*
* 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 zipkin.storage.cassandra;
import com.datastax.driver.core.BoundStatement;
import com.datastax.driver.core.Session;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Ticker;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import com.google.common.util.concurrent.UncheckedExecutionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import zipkin.internal.Nullable;
import static zipkin.internal.Util.checkNotNull;
/**
* This reduces load on cassandra by preventing semantically equivalent requests from being invoked,
* subject to a local TTL.
*
* Ex. If you want to test that you don't repeatedly send bad data, you could send a 400 back.
*
*
{@code
* ttl = 60 * 1000; // 1 minute
* deduper = new DeduplicatingExecutor(session, ttl);
*
* // the result of the first execution against "foo" is returned to other callers
* // until it expires a minute later.
* deduper.maybeExecute(bound, "foo");
* deduper.maybeExecute(bound, "foo");
* }
*/
class DeduplicatingExecutor { // not final for testing
private final Session session;
private final LoadingCache> cache;
/**
* @param session which conditionally executes bound statements
* @param ttl how long the results of statements are remembered, in milliseconds.
*/
DeduplicatingExecutor(Session session, long ttl) {
this.session = session;
this.cache = CacheBuilder.newBuilder()
.expireAfterWrite(ttl, TimeUnit.MILLISECONDS)
.ticker(new Ticker() {
@Override public long read() {
return nanoTime();
}
})
// TODO: maximum size or weight
.build(new CacheLoader>() {
@Override public ListenableFuture load(final BoundStatementKey key) {
ListenableFuture> cassandraFuture = executeAsync(key.statement);
// Drop the cassandra future so that we don't hold references to cassandra state for
// long periods of time.
final SettableFuture disconnectedFuture = SettableFuture.create();
Futures.addCallback(cassandraFuture, new FutureCallback