The driver's configuration is composed of options, organized in a hierarchical manner. Optionally, it can define profiles that customize a set of options for a particular kind of request.
- the default implementation is based on the Typesafe Config framework:
- the driver JAR comes with a reference.conf file that defines the defaults.
- you can add an
application.conf
file in the classpath (or an absolute path, or an URL). It only needs to contain the options that you override. - hot reloading is supported out of the box.
- the config mechanism can be completely overridden by implementing a set of driver interfaces (DriverConfig, DriverExecutionProfile and DriverConfigLoader)
Essentially, an option is a path in the configuration with an expected type, for example
basic.request.timeout
, representing a duration.
Imagine an application that does both transactional and analytical requests. Transactional requests are simpler and must return quickly, so they will typically use a short timeout, let's say 100 milliseconds; analytical requests are more complex and less frequent so a higher SLA is acceptable, for example 5 seconds. In addition, maybe you want to use a different consistency level.
Instead of manually adjusting the options on every request, you can create execution profiles:
datastax-java-driver {
profiles {
oltp {
basic.request.timeout = 100 milliseconds
basic.request.consistency = ONE
}
olap {
basic.request.timeout = 5 seconds
basic.request.consistency = QUORUM
}
}
Now each request only needs a profile name:
SimpleStatement s =
SimpleStatement.builder("SELECT name FROM user WHERE id = 1")
.setExecutionProfileName("oltp")
.build();
session.execute(s);
The configuration has an anonymous default profile that is always present. It can define an arbitrary number of named profiles. They inherit from the default profile, so you only need to override the options that have a different value.
Out of the box, the driver uses Typesafe Config.
It looks at the following locations, according to the standard behavior of that library:
- system properties
application.conf
(all resources on the classpath with this name)application.json
(all resources on the classpath with this name)application.properties
(all resources on the classpath with this name)reference.conf
(all resources on the classpath with this name)
The driver ships with a reference.conf that defines sensible defaults for all the options. That
file is heavily documented, so refer to it for details about each option. It is included in the core
driver JAR, so it is in your application's classpath. If you need to customize something, add an
application.conf
to the classpath. There are various ways to do it:
- place the file in a directory that is on your application or application server's classpath (example for Apache Tomcat);
- if you use Maven, place it in the
src/main/resources
directory.
Since application.conf
inherits from reference.conf
, you only need to redeclare what you
override:
# Sample application.conf: overrides one option and adds a profile
datastax-java-driver {
advanced.protocol.version = V4
profiles {
slow {
basic.request.timeout = 10 seconds
}
}
}
.conf
files are in the HOCON format, an improved superset of JSON; refer to the
HOCON spec for details.
By default, configuration files are reloaded regularly, and the driver will adjust to the new values (on a "best effort" basis: some options, like protocol version and policy configurations, cannot be changed at runtime and will be ignored). The reload interval is defined in the configuration:
# To disable periodic reloading, set this to 0.
datastax-java-driver.basic.config-reload-interval = 5 minutes
As mentioned previously, system properties can also be used to override individual options. This is great for temporary changes, for example in your development environment:
# Increase heartbeat interval to limit the amount of debug logs:
java -Ddatastax-java-driver.advanced.heartbeat.interval="5 minutes" ...
For array options, provide each element separately by appending an index to the path:
-Ddatastax-java-driver.basic.contact-points.0="127.0.0.1:9042"
-Ddatastax-java-driver.basic.contact-points.1="127.0.0.2:9042"
We recommend reserving system properties for the early phases of the project; in production, having all the configuration in one place will make it easier to manage and review.
As shown so far, all options live under a datastax-java-driver
prefix. This can be changed, for
example if you need multiple driver instances in the same VM with different configurations. See the
Advanced topics section.
If loading application.conf
from the classpath doesn't work for you, other loader implementations
are available:
- DriverConfigLoader.fromClasspath: still load from the classpath, but use a different resource
name. For example "config" will try to load
config.conf
,config.json
orconfig.properties
. - DriverConfigLoader.fromFile: load from a file on the local filesystem.
- DriverConfigLoader.fromUrl: load from a URL.
To use any of those loaders, pass it to the session builder:
File file = new File("/path/to/application.conf");
CqlSession session = CqlSession.builder()
.withConfigLoader(DriverConfigLoader.fromFile(file))
.build();
Apart from application-specific configuration, they work exactly like the default loader: they
fall back to the driver's built-in reference.conf
for defaults, accept overrides via system
properties, and reload at the interval specified by the basic.config-reload-interval
option.
Alternatively, you can use DriverConfigLoader.programmaticBuilder to specify configuration options programmatically instead of loading them from a static resource:
DriverConfigLoader loader =
DriverConfigLoader.programmaticBuilder()
.withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(5))
.startProfile("slow")
.withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30))
.endProfile()
.build();
CqlSession session = CqlSession.builder().withConfigLoader(loader).build();
This is useful for frameworks and tools that already have their own configuration mechanism.
You don't need the configuration API for everyday usage of the driver, but it can be useful if:
- you're writing custom policies or a custom config implementation;
- use dynamic profiles (see below);
- or simply want to read configuration options at runtime.
The driver's context exposes a DriverConfig instance:
DriverConfig config = session.getContext().getConfig();
DriverExecutionProfile defaultProfile = config.getDefaultProfile();
DriverExecutionProfile olapProfile = config.getProfile("olap");
config.getProfiles().forEach((name, profile) -> ...);
DriverExecutionProfile has typed option getters:
Duration requestTimeout = defaultProfile.getDuration(DefaultDriverOption.REQUEST_TIMEOUT);
int maxRequestsPerConnection = defaultProfile.getInt(DefaultDriverOption.CONNECTION_MAX_REQUESTS);
In addition to periodic reloading, you can trigger a reload programmatically. This returns a
CompletionStage
that you can use for example to register a callback when the reload is complete:
DriverConfigLoader loader = session.getContext().getConfigLoader();
if (loader.supportsReloading()) {
CompletionStage<Boolean> reloaded = loader.reload();
reloaded.whenComplete(
(configChanged, error) -> {
if (error != null) {
// handle error
} else if (configChanged) {
// do something after the config change
}
});
}
Manual reloading is optional, this can be checked with supportsReloading()
; the driver's built-in
loader supports it.
Execution profiles are hard-coded in the configuration, and can't be changed at runtime (except by modifying and reloading the files). What if you want to adjust an option for a single request, without having a dedicated profile for it?
To allow this, you start from an existing profile in the configuration and build a derived profile that overrides a subset of options:
DriverExecutionProfile defaultProfile = session.getContext().getConfig().getDefaultProfile();
DriverExecutionProfile dynamicProfile =
defaultProfile.withString(
DefaultDriverOption.REQUEST_CONSISTENCY, DefaultConsistencyLevel.EACH_QUORUM.name());
SimpleStatement s =
SimpleStatement.builder("SELECT name FROM user WHERE id = 1")
.setExecutionProfile(dynamicProfile)
.build();
session.execute(s);
A derived profile keeps a reference to its base profile, and reflects the change if the configuration gets reloaded.
Do not overuse derived profiles, as they can have an impact on performance: each withXxx
method
creates a new copy, and propagating the changes from the base profile also has an overhead. We
strongly suggest defining all your profiles ahead of time in the configuration file; at the very
least, try to cache derived profiles if you reuse them multiple times.
Note: all the features described in this section use the driver's internal API, which is subject to the restrictions explained in API conventions.
As mentioned earlier, all configuration options are looked up under the datastax-java-driver
prefix. This might be a problem if you have multiple instances of the driver executing in the same
VM, but with different configurations. What you want instead is separate option trees, like this:
# application.conf
session1 {
basic.session-name = "session1"
advanced.protocol-version = V4
// etc.
}
session2 {
basic.session-name = "session2"
advanced.protocol-version = V3
// etc.
}
To achieve that, first write a method that loads the configuration under your prefix, and uses the
driver's reference.conf
as a fallback:
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
private static Config loadConfig(String prefix) {
// Make sure we see the changes when reloading:
ConfigFactory.invalidateCaches();
// Every config file in the classpath, without stripping the prefixes
Config root = ConfigFactory.load();
// The driver's built-in defaults, under the default prefix in reference.conf:
Config reference = root.getConfig("datastax-java-driver");
// Everything under your custom prefix in application.conf:
Config application = root.getConfig(prefix);
return application.withFallback(reference);
}
Next, create a DriverConfigLoader
. This is the component that abstracts the configuration
implementation to the rest of the driver. Here we use the built-in class, but tell it to load the
Typesafe Config object with the previous method:
import com.datastax.oss.driver.api.core.config.DefaultDriverOption;
import com.datastax.oss.driver.api.core.config.DriverConfigLoader;
import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader;
DriverConfigLoader session1ConfigLoader =
new DefaultDriverConfigLoader(
() -> loadConfig("session1"), DefaultDriverOption.values());
Finally, pass the config loader when building the driver:
CqlSession session1 =
CqlSession.builder()
.withConfigLoader(session1ConfigLoader)
.build();
If you don't want to use a config file, you can write custom code to create the Typesafe Config
object (refer to the documentation for more details).
Then reuse the examples from the previous section to merge it with the driver's reference file, and pass it to the driver. Here's a contrived example that loads the configuration from a string:
String configSource = "protocol.version = V3";
DriverConfigLoader loader =
new DefaultDriverConfigLoader(
() -> {
ConfigFactory.invalidateCaches();
Config reference = ConfigFactory.load().getConfig("datastax-java-driver");
Config application = ConfigFactory.parseString(configSource);
return application.withFallback(reference);
},
DefaultDriverOption.values());
CqlSession session = CqlSession.builder().withConfigLoader(loader).build();
If Typesafe Config doesn't work for you, it is possible to get rid of it entirely.
Start by excluding Typesafe Config from the list of dependencies required by the driver; if you are using Maven, this can be achieved as follows:
<dependencies>
<dependency>
<groupId>org.apache.cassandra</groupId>
<artifactId>java-driver-core</artifactId>
<version>...</version>
<exclusions>
<exclusion>
<groupId>com.typesafe</groupId>
<artifactId>config</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
Next, you will need to provide your own implementations of DriverConfig and
DriverExecutionProfile. Then write a DriverConfigLoader and pass it to the session at
initialization, as shown in the previous sections. Study the built-in implementation (package
com.datastax.oss.driver.internal.core.config.typesafe
) for reference.
Reloading is not mandatory: you can choose not to implement it, and the driver will simply keep using the initial configuration.
Note that the option getters (DriverExecutionProfile.getInt
and similar) are invoked very
frequently on the hot code path; if your implementation is slow, consider caching the results
between reloads.
If you're writing your own policies, you might want them to be reactive to configuration changes.
You can register a callback to ConfigChangeEvent
, which gets emitted any time a manual or periodic
reload detects changes since the last reload:
import com.datastax.oss.driver.internal.core.context.InternalDriverContext;
import com.datastax.oss.driver.internal.core.config.ConfigChangeEvent;
InternalDriverContext context = (InternalDriverContext) session.getContext();
Object key =
eventBus.register(
ConfigChangeEvent.class, (e) -> {
System.out.println("The configuration changed");
// re-read the config option(s) you're interested in, and apply changes if needed
});
// If your component has a shorter lifecycle than the driver, make sure to unregister when it closes
eventBus.unregister(key, ConfigChangeEvent.class);
For example, the driver uses this mechanism internally to resize connection pools if you change the
options in advanced.connection.pool
.
The event is emitted by the config loader. If you write a custom loader, study the source of
DefaultDriverConfigLoader
to reproduce the behavior.
The preferred way to instantiate policies (load balancing policy, retry policy, etc.) is via the configuration:
datastax-java-driver {
basic.load-balancing-policy.class = DefaultLoadBalancingPolicy
advanced.reconnection-policy {
class = ExponentialReconnectionPolicy
base-delay = 1 second
max-delay = 60 seconds
}
}
When the driver encounters such a declaration, it will load the class and use reflection to invoke a constructor with the following signature:
-
for policies that can be overridden in a profile (load balancing policy, retry policy, speculative execution policy):
public DefaultLoadBalancingPolicy(DriverContext context, String profileName)
-
for session-wide policies (all the others):
public ExponentialReconnectionPolicy(DriverContext context)
Where DriverContext is the object returned by session.getContext()
, which allows the policy to
access other driver components (for example the configuration).
If you write custom policy implementations, you should follow that same pattern; it provides an elegant way to switch policies without having to recompile the application (if your policy needs custom options, see the next section). Study the built-in implementations for reference.
If for some reason you really can't use reflection, there is a way out; subclass
DefaultDriverContext
and override the corresponding method:
import com.datastax.oss.driver.internal.core.context.DefaultDriverContext;
public class MyDriverContext extends DefaultDriverContext {
public MyDriverContext(DriverConfigLoader configLoader, List<TypeCodec<?>> typeCodecs) {
super(configLoader, typeCodecs);
}
@Override
protected ReconnectionPolicy buildReconnectionPolicy() {
return myReconnectionPolicy;
}
}
Then you'll need to pass an instance of this context to DefaultSession.init
. You can either do so
directly, or subclass SessionBuilder
and override the buildContext
method.
You can add your own options to the configuration. This is useful for custom components, or even as a way to associate arbitrary key/value pairs with the session instance.
First, write an enum that implements DriverOption:
public enum MyCustomOption implements DriverOption {
ADMIN_NAME("admin.name"),
ADMIN_EMAIL("admin.email"),
AWESOMENESS_FACTOR("awesomeness-factor"),
;
private final String path;
MyCustomOption(String path) {
this.path = path;
}
@Override
public String getPath() {
return path;
}
}
You can now add the options to your configuration:
datastax-java-driver {
admin {
name = "Bob"
email = "[email protected]"
}
awesomeness-factor = 11
}
And access them from the code:
DriverConfig config = session.getContext().getConfig();
config.getDefaultProfile().getString(MyCustomOption.ADMIN_EMAIL);
config.getDefaultProfile().getInt(MyCustomOption.AWESOMENESS_FACTOR);