Metadata#getTokenMap returns information about the tokens used for data replication. It is used internally by the driver to send requests to the optimal coordinator when token-aware routing is enabled. Another typical use case is data analytics clients, for example fetching a large range of keys in parallel by sending sub-queries to each replica.
Because token metadata can be disabled, the resulting TokenMap object is wrapped in an Optional
; to access it, you can use either a functional pattern, or more traditionally test first with isPresent
and then unwrap:
Metadata metadata = session.getMetadata(); metadata.getTokenMap().ifPresent(tokenMap -> { // do something with the map }); if (metadata.getTokenMap().isPresent()) { TokenMap tokenMap = metadata.getTokenMap().get(); // do something with the map }
TokenMap
methodsFor illustration purposes, let's consider a fictitious ring with 6 tokens, and a cluster of 3 nodes that each own two tokens:
node1 /---\ /=---+ 12+---=\ : \---/ : | | /-+-\ /-+-\ node3 | 10| | 2 | node2 \-+-/ \-+-/ : : | | /---\ /-+-\ node2 | 8 | | 4 | node3 \-+-/ \-+-/ : : | /---\ | \=---+ 6 +---=/ \---/ node1
The first thing you can do is retrieve all the ranges, in other words describe the ring:
Set<TokenRange> ring = tokenMap.getTokenRanges(); // Returns [Murmur3TokenRange(Murmur3Token(12), Murmur3Token(2)), // Murmur3TokenRange(Murmur3Token(2), Murmur3Token(4)), // Murmur3TokenRange(Murmur3Token(4), Murmur3Token(6)), // Murmur3TokenRange(Murmur3Token(6), Murmur3Token(8)), // Murmur3TokenRange(Murmur3Token(8), Murmur3Token(10)), // Murmur3TokenRange(Murmur3Token(10), Murmur3Token(12))]
Note: Murmur3Token
is an implementation detail. The actual class depends on the partitioner you configured in Cassandra, but in general you don't need to worry about that. TokenMap
provides a few utility methods to parse tokens and create new instances: parse
, format
, newToken
and newTokenRange
.
You can also retrieve the ranges and tokens owned by a specific replica:
tokenMap.getTokenRanges(node1); // [Murmur3TokenRange(Murmur3Token(10), Murmur3Token(12)), // Murmur3TokenRange(Murmur3Token(4), Murmur3Token(6))] tokenMap.getTokens(node1); // [Murmur3Token(12)), Murmur3Token(6))]
As shown here, the node owns the ranges that end with its tokens; this is because ranges are start-exclusive and end-inclusive: ]10, 12]
and ]4, 6]
.
Next, you can retrieve keyspace-specific information. To illustrate this, let's use two keyspaces with different replication settings:
// RF = 1: each range is only stored on the primary replica CREATE KEYSPACE ks1 WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}; // RF = 2: each range is stored on the primary replica, and replicated on the next node in the ring CREATE KEYSPACE ks2 WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 2};
getReplicas
finds the nodes that have the data in a given range:
TokenRange firstRange = tokenMap.getTokenRanges().iterator().next(); // Murmur3TokenRange(Murmur3Token(12), Murmur3Token(2)) Set<Node> nodes1 = tokenMap.getReplicas(CqlIdentifier.fromCql("ks1"), firstRange); // [node2] (only the primary replica) Set<Node> nodes2 = tokenMap.getReplicas(CqlIdentifier.fromCql("ks2"), firstRange); // [node2, node3] (the primary replica, and the next node on the ring)
There is a also a variant that takes a primary key, to find the replicas for a particular row. In the following example, let's assume that the key hashes to the token “1” with the current partitioner:
String pk = "johndoe@example.com"; // You need to manually encode the key as binary: ByteBuffer encodedPk = TypeCodecs.TEXT.encode(pk, session.getContext().getProtocolVersion()); Set<Node> nodes1 = tokenMap.getReplicas(CqlIdentifier.fromInternal("ks1"), encodedPk); // Assuming the key hashes to "1", it is in the ]12, 2] range // => [node2] (only the primary replica) Set<Node> nodes2 = tokenMap.getReplicas(CqlIdentifier.fromCql("ks2"), encodedPk); // [node2, node3] (the primary replica, and the next node on the ring)
Finally, you can go the other way, and find the token ranges that a node stores for a given keyspace:
Set<TokenRange> ranges1 = tokenMap.getTokenRanges(CqlIdentifier.fromCql("ks1"), node1); // [Murmur3TokenRange(Murmur3Token(4), Murmur3Token(6)), // Murmur3TokenRange(Murmur3Token(10), Murmur3Token(12))] // (only its primary ranges) Set<TokenRange> ranges2 = tokenMap.getTokenRanges(CqlIdentifier.fromCql("ks2"), node1); // [Murmur3TokenRange(Murmur3Token(2), Murmur3Token(4)), // Murmur3TokenRange(Murmur3Token(4), Murmur3Token(6)), // Murmur3TokenRange(Murmur3Token(8), Murmur3Token(10)), // Murmur3TokenRange(Murmur3Token(10), Murmur3Token(12))] // (its primary ranges, and a replica of the primary ranges of node3, the previous node on the ring)
You can disable token metadata globally from the configuration:
datastax-java-driver.advanced.metadata.token-map.enabled = false
If it is disabled at startup, Metadata#getTokenMap will stay empty, and token-aware routing won't work (requests will be sent to a non-optimal coordinator). If you disable it at runtime, it will keep the value of the last refresh, and token-aware routing might operate on stale data.
The keyspace-specific information in TokenMap
(all methods with a CqlIdentifier
argument) relies on schema metadata. If schema metadata is disabled or filtered, token metadata will also be unavailable for the excluded keyspaces.