Skip to content

Commit 668992b

Browse files
authored
Merge pull request apache#806 from datastax/java1308
JAVA-1308: CodecRegistry performance improvements
2 parents 3681fde + 1bd0663 commit 668992b

4 files changed

Lines changed: 121 additions & 54 deletions

File tree

changelog/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- [new feature] JAVA-1362: Send query options flags as [int] for Protocol V5+.
88
- [improvement] JAVA-1367: Make protocol negotiation more resilient.
99
- [bug] JAVA-1397: Handle duration as native datatype in protocol v5+.
10+
- [improvement] JAVA-1308: CodecRegistry performance improvements.
1011

1112
Merged from 3.1.x branch:
1213

driver-core/src/main/java/com/datastax/driver/core/CodecRegistry.java

Lines changed: 103 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import com.datastax.driver.core.exceptions.CodecNotFoundException;
1919
import com.google.common.base.Objects;
2020
import com.google.common.cache.*;
21-
import com.google.common.collect.ImmutableSet;
2221
import com.google.common.reflect.TypeToken;
2322
import com.google.common.util.concurrent.UncheckedExecutionException;
2423
import org.slf4j.Logger;
@@ -27,6 +26,7 @@
2726
import java.lang.reflect.ParameterizedType;
2827
import java.lang.reflect.Type;
2928
import java.nio.ByteBuffer;
29+
import java.util.EnumMap;
3030
import java.util.List;
3131
import java.util.Map;
3232
import java.util.Set;
@@ -49,8 +49,7 @@
4949
* The only reason to create your own instances is if you have multiple {@code Cluster} objects that use different
5050
* sets of codecs. In that case, use {@link com.datastax.driver.core.Cluster.Builder#withCodecRegistry(CodecRegistry)}
5151
* to associate the registry with the cluster:
52-
* <pre>
53-
* {@code
52+
* <pre>{@code
5453
* CodecRegistry myCodecRegistry = new CodecRegistry();
5554
* myCodecRegistry.register(myCodec1, myCodec2, myCodec3);
5655
* Cluster cluster = Cluster.builder().withCodecRegistry(myCodecRegistry).build();
@@ -68,8 +67,7 @@
6867
* To create a custom codec, write a class that extends {@link TypeCodec}, create an instance, and pass it to one of
6968
* the {@link #register(TypeCodec) register} methods; for example, one could create a codec that maps CQL
7069
* timestamps to JDK8's {@code java.time.LocalDate}:
71-
* <pre>
72-
* {@code
70+
* <pre>{@code
7371
* class LocalDateCodec extends TypeCodec<java.time.LocalDate> {
7472
* ...
7573
* }
@@ -85,8 +83,7 @@
8583
* </ul>
8684
* <p/>
8785
* Example:
88-
* <pre>
89-
* {@code
86+
* <pre>{@code
9087
* Row row = session.executeQuery("select date from some_table where pk = 1").one();
9188
* java.time.LocalDate date = row.get(0, java.time.LocalDate.class); // uses LocalDateCodec registered above}
9289
* </pre>
@@ -104,7 +101,7 @@
104101
* as well as all {@code Map} types whose keys and/or values are {@code java.time.LocalDate}.
105102
* This works recursively for nested collections;</li>
106103
* <li>{@link UserType user types}, mapped to {@link UDTValue} objects. Custom codecs are available recursively
107-
* to the UDT's fields, so if one of your fields is a {@code timestamp} you can use your {@code LocaDateCodec} to retrieve
104+
* to the UDT's fields, so if one of your fields is a {@code timestamp} you can use your {@code LocalDateCodec} to retrieve
108105
* it as a {@code java.time.LocalDate};</li>
109106
* <li>{@link TupleType tuple types}, mapped to {@link TupleValue} (with the same rules for nested fields);</li>
110107
* <li>{@link com.datastax.driver.core.DataType.CustomType custom types}, mapped to {@code ByteBuffer}.</li>
@@ -129,7 +126,7 @@
129126
* When the registry looks up a codec, the rules of precedence are:
130127
* <ul>
131128
* <li>if a result was previously cached for that mapping, it is returned;</li>
132-
* <li>otherwise, the registry checks the list of "basic" codecs: the default ones, and the ones that were explicitly
129+
* <li>otherwise, the registry checks the list of built-in codecsthe default ones and the ones that were explicitly
133130
* registered (in the order that they were registered). It calls each codec's {@code accepts} methods to determine if
134131
* it can handle the mapping, and if so returns it;</li>
135132
* <li>otherwise, the registry tries to generate a codec, according to the rules outlined above.</li>
@@ -141,29 +138,55 @@ public final class CodecRegistry {
141138

142139
private static final Logger logger = LoggerFactory.getLogger(CodecRegistry.class);
143140

144-
@SuppressWarnings("unchecked")
145-
private static final ImmutableSet<TypeCodec<?>> PRIMITIVE_CODECS = ImmutableSet.of(
146-
TypeCodec.blob(),
147-
TypeCodec.cboolean(),
148-
TypeCodec.smallInt(),
149-
TypeCodec.tinyInt(),
141+
private static final Map<DataType.Name, TypeCodec<?>> BUILT_IN_CODECS_MAP = new EnumMap<DataType.Name, TypeCodec<?>>(DataType.Name.class);
142+
143+
static {
144+
BUILT_IN_CODECS_MAP.put(DataType.Name.ASCII, TypeCodec.ascii());
145+
BUILT_IN_CODECS_MAP.put(DataType.Name.BIGINT, TypeCodec.bigint());
146+
BUILT_IN_CODECS_MAP.put(DataType.Name.BLOB, TypeCodec.blob());
147+
BUILT_IN_CODECS_MAP.put(DataType.Name.BOOLEAN, TypeCodec.cboolean());
148+
BUILT_IN_CODECS_MAP.put(DataType.Name.COUNTER, TypeCodec.counter());
149+
BUILT_IN_CODECS_MAP.put(DataType.Name.DECIMAL, TypeCodec.decimal());
150+
BUILT_IN_CODECS_MAP.put(DataType.Name.DOUBLE, TypeCodec.cdouble());
151+
BUILT_IN_CODECS_MAP.put(DataType.Name.FLOAT, TypeCodec.cfloat());
152+
BUILT_IN_CODECS_MAP.put(DataType.Name.INET, TypeCodec.inet());
153+
BUILT_IN_CODECS_MAP.put(DataType.Name.INT, TypeCodec.cint());
154+
BUILT_IN_CODECS_MAP.put(DataType.Name.TEXT, TypeCodec.varchar());
155+
BUILT_IN_CODECS_MAP.put(DataType.Name.TIMESTAMP, TypeCodec.timestamp());
156+
BUILT_IN_CODECS_MAP.put(DataType.Name.UUID, TypeCodec.uuid());
157+
BUILT_IN_CODECS_MAP.put(DataType.Name.VARCHAR, TypeCodec.varchar());
158+
BUILT_IN_CODECS_MAP.put(DataType.Name.VARINT, TypeCodec.varint());
159+
BUILT_IN_CODECS_MAP.put(DataType.Name.TIMEUUID, TypeCodec.timeUUID());
160+
BUILT_IN_CODECS_MAP.put(DataType.Name.SMALLINT, TypeCodec.smallInt());
161+
BUILT_IN_CODECS_MAP.put(DataType.Name.TINYINT, TypeCodec.tinyInt());
162+
BUILT_IN_CODECS_MAP.put(DataType.Name.DATE, TypeCodec.date());
163+
BUILT_IN_CODECS_MAP.put(DataType.Name.TIME, TypeCodec.time());
164+
BUILT_IN_CODECS_MAP.put(DataType.Name.DURATION, TypeCodec.duration());
165+
}
166+
167+
// roughly sorted by popularity
168+
private static final TypeCodec<?>[] BUILT_IN_CODECS = new TypeCodec<?>[]{
169+
TypeCodec.varchar(), // must be declared before AsciiCodec so it gets chosen when CQL type not available
170+
TypeCodec.uuid(), // must be declared before TimeUUIDCodec so it gets chosen when CQL type not available
171+
TypeCodec.timeUUID(),
172+
TypeCodec.timestamp(),
150173
TypeCodec.cint(),
151174
TypeCodec.bigint(),
152-
TypeCodec.counter(),
175+
TypeCodec.blob(),
153176
TypeCodec.cdouble(),
154177
TypeCodec.cfloat(),
155-
TypeCodec.varint(),
156178
TypeCodec.decimal(),
157-
TypeCodec.varchar(), // must be declared before AsciiCodec so it gets chosen when CQL type not available
158-
TypeCodec.ascii(),
159-
TypeCodec.timestamp(),
179+
TypeCodec.varint(),
180+
TypeCodec.inet(),
181+
TypeCodec.cboolean(),
182+
TypeCodec.smallInt(),
183+
TypeCodec.tinyInt(),
160184
TypeCodec.date(),
161185
TypeCodec.time(),
162-
TypeCodec.uuid(), // must be declared before TimeUUIDCodec so it gets chosen when CQL type not available
163-
TypeCodec.timeUUID(),
164-
TypeCodec.inet(),
165-
TypeCodec.duration()
166-
);
186+
TypeCodec.duration(),
187+
TypeCodec.counter(),
188+
TypeCodec.ascii()
189+
};
167190

168191
/**
169192
* The default {@code CodecRegistry} instance.
@@ -181,7 +204,7 @@ private static final class CacheKey {
181204

182205
private final TypeToken<?> javaType;
183206

184-
public CacheKey(DataType cqlType, TypeToken<?> javaType) {
207+
CacheKey(DataType cqlType, TypeToken<?> javaType) {
185208
this.javaType = javaType;
186209
this.cqlType = cqlType;
187210
}
@@ -209,7 +232,18 @@ public int hashCode() {
209232
private class TypeCodecCacheLoader extends CacheLoader<CacheKey, TypeCodec<?>> {
210233
@Override
211234
public TypeCodec<?> load(CacheKey cacheKey) {
212-
return findCodec(cacheKey.cqlType, cacheKey.javaType);
235+
checkNotNull(cacheKey.cqlType, "Parameter cqlType cannot be null");
236+
if (logger.isTraceEnabled())
237+
logger.trace("Loading codec into cache: [{} <-> {}]",
238+
CodecRegistry.toString(cacheKey.cqlType),
239+
CodecRegistry.toString(cacheKey.javaType));
240+
for (TypeCodec<?> codec : codecs) {
241+
if (codec.accepts(cacheKey.cqlType) && (cacheKey.javaType == null || codec.accepts(cacheKey.javaType))) {
242+
logger.trace("Already existing codec found: {}", codec);
243+
return codec;
244+
}
245+
}
246+
return createCodec(cacheKey.cqlType, cacheKey.javaType);
213247
}
214248
}
215249

@@ -218,7 +252,7 @@ public TypeCodec<?> load(CacheKey cacheKey) {
218252
* Weights are computed mainly according to the CQL type:
219253
* <ol>
220254
* <li>Manually-registered codecs always weigh 0;
221-
* <li>Codecs for primtive types weigh 0;
255+
* <li>Codecs for primitive types weigh 0;
222256
* <li>Codecs for collections weigh the total weight of their inner types + the weight of their level of deepness;
223257
* <li>Codecs for UDTs and tuples weigh the total weight of their inner types + the weight of their level of deepness, but cannot weigh less than 1;
224258
* <li>Codecs for custom (non-CQL) types weigh 1.
@@ -278,9 +312,7 @@ public void onRemoval(RemovalNotification<CacheKey, TypeCodec<?>> notification)
278312
}
279313

280314
/**
281-
* The list of registered codecs.
282-
* This list is initialized with the built-in codecs;
283-
* User-defined codecs are appended to the list.
315+
* The list of user-registered codecs.
284316
*/
285317
private final CopyOnWriteArrayList<TypeCodec<?>> codecs;
286318

@@ -294,13 +326,13 @@ public void onRemoval(RemovalNotification<CacheKey, TypeCodec<?>> notification)
294326
* Creates a new instance initialized with built-in codecs for all the base CQL types.
295327
*/
296328
public CodecRegistry() {
297-
this.codecs = new CopyOnWriteArrayList<TypeCodec<?>>(PRIMITIVE_CODECS);
329+
this.codecs = new CopyOnWriteArrayList<TypeCodec<?>>();
298330
this.cache = defaultCacheBuilder().build(new TypeCodecCacheLoader());
299331
}
300332

301333
private CacheBuilder<CacheKey, TypeCodec<?>> defaultCacheBuilder() {
302334
CacheBuilder<CacheKey, TypeCodec<?>> builder = CacheBuilder.newBuilder()
303-
// 19 primitive codecs + collections thereof = 19*3 + 19*19 = 418 codecs,
335+
// lists, sets and maps of 20 primitive types = 20 + 20 + 20*20 = 440 codecs,
304336
// so let's start with roughly 1/4 of that
305337
.initialCapacity(100)
306338
.maximumWeight(1000)
@@ -323,20 +355,24 @@ private CacheBuilder<CacheKey, TypeCodec<?>> defaultCacheBuilder() {
323355
* @return this CodecRegistry (for method chaining).
324356
*/
325357
public CodecRegistry register(TypeCodec<?> newCodec) {
358+
for (TypeCodec<?> oldCodec : BUILT_IN_CODECS) {
359+
if (oldCodec.accepts(newCodec.getCqlType()) && oldCodec.accepts(newCodec.getJavaType())) {
360+
logger.warn("Ignoring codec {} because it collides with previously registered codec {}", newCodec, oldCodec);
361+
return this;
362+
}
363+
}
326364
for (TypeCodec<?> oldCodec : codecs) {
327365
if (oldCodec.accepts(newCodec.getCqlType()) && oldCodec.accepts(newCodec.getJavaType())) {
328366
logger.warn("Ignoring codec {} because it collides with previously registered codec {}", newCodec, oldCodec);
329367
return this;
330368
}
331369
}
332-
333370
CacheKey key = new CacheKey(newCodec.getCqlType(), newCodec.getJavaType());
334371
TypeCodec<?> existing = cache.getIfPresent(key);
335372
if (existing != null) {
336373
logger.warn("Ignoring codec {} because it collides with previously generated codec {}", newCodec, existing);
337374
return this;
338375
}
339-
340376
this.codecs.add(newCodec);
341377
return this;
342378
}
@@ -474,13 +510,16 @@ public <T> TypeCodec<T> codecFor(DataType cqlType, T value) {
474510
@SuppressWarnings("unchecked")
475511
private <T> TypeCodec<T> lookupCodec(DataType cqlType, TypeToken<T> javaType) {
476512
checkNotNull(cqlType, "Parameter cqlType cannot be null");
513+
TypeCodec<?> codec = BUILT_IN_CODECS_MAP.get(cqlType.getName());
514+
if (codec != null && (javaType == null || codec.accepts(javaType))) {
515+
logger.trace("Returning built-in codec {}", codec);
516+
return (TypeCodec<T>) codec;
517+
}
477518
if (logger.isTraceEnabled())
478519
logger.trace("Querying cache for codec [{} <-> {}]", toString(cqlType), toString(javaType));
479-
CacheKey cacheKey = new CacheKey(cqlType, javaType);
480520
try {
481-
TypeCodec<?> codec = cache.get(cacheKey);
482-
logger.trace("Returning cached codec {}", codec);
483-
return (TypeCodec<T>) codec;
521+
CacheKey cacheKey = new CacheKey(cqlType, javaType);
522+
codec = cache.get(cacheKey);
484523
} catch (UncheckedExecutionException e) {
485524
if (e.getCause() instanceof CodecNotFoundException) {
486525
throw (CodecNotFoundException) e.getCause();
@@ -491,16 +530,28 @@ private <T> TypeCodec<T> lookupCodec(DataType cqlType, TypeToken<T> javaType) {
491530
} catch (ExecutionException e) {
492531
throw new CodecNotFoundException(e.getCause(), cqlType, javaType);
493532
}
533+
logger.trace("Returning cached codec {}", codec);
534+
return (TypeCodec<T>) codec;
494535
}
495536

496537
@SuppressWarnings("unchecked")
497538
private <T> TypeCodec<T> findCodec(DataType cqlType, TypeToken<T> javaType) {
498539
checkNotNull(cqlType, "Parameter cqlType cannot be null");
499540
if (logger.isTraceEnabled())
500541
logger.trace("Looking for codec [{} <-> {}]", toString(cqlType), toString(javaType));
542+
543+
// Look at the built-in codecs first
544+
for (TypeCodec<?> codec : BUILT_IN_CODECS) {
545+
if (codec.accepts(cqlType) && (javaType == null || codec.accepts(javaType))) {
546+
logger.trace("Built-in codec found: {}", codec);
547+
return (TypeCodec<T>) codec;
548+
}
549+
}
550+
551+
// Look at the user-registered codecs next
501552
for (TypeCodec<?> codec : codecs) {
502553
if (codec.accepts(cqlType) && (javaType == null || codec.accepts(javaType))) {
503-
logger.trace("Codec found: {}", codec);
554+
logger.trace("Already registered codec found: {}", codec);
504555
return (TypeCodec<T>) codec;
505556
}
506557
}
@@ -512,9 +563,19 @@ private <T> TypeCodec<T> findCodec(DataType cqlType, T value) {
512563
checkNotNull(value, "Parameter value cannot be null");
513564
if (logger.isTraceEnabled())
514565
logger.trace("Looking for codec [{} <-> {}]", toString(cqlType), value.getClass());
566+
567+
// Look at the built-in codecs first
568+
for (TypeCodec<?> codec : BUILT_IN_CODECS) {
569+
if ((cqlType == null || codec.accepts(cqlType)) && codec.accepts(value)) {
570+
logger.trace("Built-in codec found: {}", codec);
571+
return (TypeCodec<T>) codec;
572+
}
573+
}
574+
575+
// Look at the user-registered codecs next
515576
for (TypeCodec<?> codec : codecs) {
516577
if ((cqlType == null || codec.accepts(cqlType)) && codec.accepts(value)) {
517-
logger.trace("Codec found: {}", codec);
578+
logger.trace("Already registered codec found: {}", codec);
518579
return (TypeCodec<T>) codec;
519580
}
520581
}
@@ -598,7 +659,7 @@ private <T> TypeCodec<T> maybeCreateCodec(DataType cqlType, TypeToken<T> javaTyp
598659
return null;
599660
}
600661

601-
@SuppressWarnings("unchecked")
662+
@SuppressWarnings({"unchecked", "rawtypes"})
602663
private <T> TypeCodec<T> maybeCreateCodec(DataType cqlType, T value) {
603664
checkNotNull(value);
604665

driver-core/src/main/java/com/datastax/driver/core/TypeCodec.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,7 @@ public DataType getCqlType() {
579579
* the given {@code javaType}, and {@code false} otherwise.
580580
* @throws NullPointerException if {@code javaType} is {@code null}.
581581
*/
582-
public boolean accepts(TypeToken javaType) {
582+
public boolean accepts(TypeToken<?> javaType) {
583583
checkNotNull(javaType, "Parameter javaType cannot be null");
584584
return this.javaType.equals(javaType.wrap());
585585
}

0 commit comments

Comments
 (0)