/** * Copyright 2015-2016 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.cassandra3; import com.datastax.driver.core.Session; import com.datastax.driver.core.querybuilder.QueryBuilder; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import java.io.IOException; import java.util.LinkedList; import java.util.List; import zipkin.internal.LazyCloseable; import zipkin.internal.Nullable; import zipkin.storage.QueryRequest; import zipkin.storage.StorageComponent; import zipkin.storage.guava.LazyGuavaStorageComponent; import static java.lang.String.format; import static zipkin.internal.Util.checkNotNull; /** * CQL3 implementation of zipkin storage. * * <p>Queries are logged to the category "com.datastax.driver.core.QueryLogger" when debug or trace * is enabled via SLF4J. Trace level includes bound values. * * <p>Schema is installed by default from "/cassandra3-schema.cql" */ // This component is named Cassandra3Storage as it correlates to "cassandra3" storage types, and // makes health-checks more obvious. Note: this is the only public type in the package. public final class Cassandra3Storage extends LazyGuavaStorageComponent<CassandraSpanStore, CassandraSpanConsumer> { // @FunctionalInterface, except safe for lower language levels public interface SessionFactory { SessionFactory DEFAULT = new DefaultSessionFactory(); Session create(Cassandra3Storage storage); } public static Builder builder() { return new Builder(); } public static final class Builder implements StorageComponent.Builder { boolean strictTraceId = true; String keyspace = Schema.DEFAULT_KEYSPACE; String contactPoints = "localhost"; String localDc; int maxConnections = 8; boolean ensureSchema = true; boolean useSsl = false; String username; String password; int maxTraceCols = 100000; int indexFetchMultiplier = 3; SessionFactory sessionFactory = SessionFactory.DEFAULT; /** {@inheritDoc} */ @Override public Builder strictTraceId(boolean strictTraceId) { this.strictTraceId = strictTraceId; return this; } /** Override to control how sessions are created. */ public Builder sessionFactory(SessionFactory sessionFactory) { this.sessionFactory = checkNotNull(sessionFactory, "sessionFactory"); return this; } /** Keyspace to store span and index data. Defaults to "zipkin3" */ public Builder keyspace(String keyspace) { this.keyspace = checkNotNull(keyspace, "keyspace"); return this; } /** Comma separated list of hosts / IPs part of Cassandra cluster. Defaults to localhost */ public Builder contactPoints(String contactPoints) { this.contactPoints = checkNotNull(contactPoints, "contactPoints"); return this; } /** * Name of the datacenter that will be considered "local" for latency load balancing. When * unset, load-balancing is round-robin. */ public Builder localDc(@Nullable String localDc) { this.localDc = localDc; return this; } /** Max pooled connections per datacenter-local host. Defaults to 8 */ public Builder maxConnections(int maxConnections) { this.maxConnections = maxConnections; return this; } /** * Ensures that schema exists, if enabled tries to execute script io.zipkin:zipkin-cassandra-core/cassandra-schema-cql3.txt. * Defaults to true. */ public Builder ensureSchema(boolean ensureSchema) { this.ensureSchema = ensureSchema; return this; } /** * Use ssl for driver * Defaults to false. */ public Builder useSsl(boolean useSsl) { this.useSsl = useSsl; return this; } /** Will throw an exception on startup if authentication fails. No default. */ public Builder username(@Nullable String username) { this.username = username; return this; } /** Will throw an exception on startup if authentication fails. No default. */ public Builder password(@Nullable String password) { this.password = password; return this; } /** * Spans have multiple values for the same id. For example, a client and server contribute to * the same span id. When searching for spans by id, the amount of results may be larger than * the ids. This defines a threshold which accommodates this situation, without looking for an * unbounded number of results. */ public Builder maxTraceCols(int maxTraceCols) { this.maxTraceCols = maxTraceCols; return this; } /** * How many more index rows to fetch than the user-supplied query limit. Defaults to 3. * * <p>Backend requests will request {@link QueryRequest#limit} times this factor rows from * Cassandra indexes in attempts to return {@link QueryRequest#limit} traces. * * <p>Indexing in cassandra will usually have more rows than trace identifiers due to factors * including table design and collection implementation. As there's no way to DISTINCT out * duplicates server-side, this over-fetches client-side when {@code indexFetchMultiplier} > 1. */ public Builder indexFetchMultiplier(int indexFetchMultiplier) { this.indexFetchMultiplier = indexFetchMultiplier; return this; } @Override public Cassandra3Storage build() { return new Cassandra3Storage(this); } Builder() { } } final int maxTraceCols; final String contactPoints; final int maxConnections; final String localDc; final String username; final String password; final boolean ensureSchema; final boolean useSsl; final String keyspace; final int indexFetchMultiplier; final boolean strictTraceId; final LazyCloseable<Session> session; Cassandra3Storage(Builder builder) { this.contactPoints = builder.contactPoints; this.maxConnections = builder.maxConnections; this.localDc = builder.localDc; this.username = builder.username; this.password = builder.password; this.ensureSchema = builder.ensureSchema; this.useSsl = builder.useSsl; this.keyspace = builder.keyspace; this.maxTraceCols = builder.maxTraceCols; this.indexFetchMultiplier = builder.indexFetchMultiplier; this.strictTraceId = builder.strictTraceId; final SessionFactory sessionFactory = builder.sessionFactory; this.session = new LazyCloseable<Session>() { @Override protected Session compute() { return sessionFactory.create(Cassandra3Storage.this); } }; } /** Lazy initializes or returns the session in use by this storage component. */ public Session session() { return session.get(); } @Override protected CassandraSpanStore computeGuavaSpanStore() { return new CassandraSpanStore(session.get(), maxTraceCols, indexFetchMultiplier, strictTraceId); } @Override protected CassandraSpanConsumer computeGuavaSpanConsumer() { return new CassandraSpanConsumer(session.get(), strictTraceId); } @Override public CheckResult check() { try { session.get().execute(QueryBuilder.select("trace_id").from("traces").limit(1)); } catch (RuntimeException e) { return CheckResult.failed(e); } return CheckResult.OK; } @Override public void close() throws IOException { session.close(); } /** Truncates all the column families, or throws on any failure. */ @VisibleForTesting void clear() { List<ListenableFuture<?>> futures = new LinkedList<>(); for (String cf : ImmutableList.of( Schema.TABLE_TRACES, Schema.TABLE_TRACE_BY_SERVICE_SPAN, Schema.TABLE_SERVICE_SPANS, Schema.TABLE_DEPENDENCIES )) { futures.add(session.get().executeAsync(format("TRUNCATE %s", cf))); } Futures.getUnchecked(Futures.allAsList(futures)); } }