https://github.com/doanduyhai/Achilles
JPA avec Cassandra ?
Mission pas impossible !
1
https://github.com/doanduyhai/Achilles
• Développeur Java freelance full-stack
• Bidouilleur Cassandra, créateur d’Achilles
• En mission chez
• doanduyhai.wordpress.com
• @doanduyhai
2
Duy Hai DOAN
https://github.com/doanduyhai/Achilles
Achilles
Présentation
3
https://github.com/doanduyhai/Achilles
Achilles
• Framework de persistance JPA pour C*
• Support des annotations/opérations JPA
• Extensions spécifiques à C*
• 2 implémentations : Thrift & CQL3*
4
https://github.com/doanduyhai/Achilles
Pourquoi Achilles ?
• Voulez-vous écrire ça ?
5
https://github.com/doanduyhai/Achilles
Pourquoi Achilles ?
• ou ça ?
6
https://github.com/doanduyhai/Achilles
Pourquoi Achilles?
• Librairies trop bas niveau (Hector, Thrift)
• Virage vers SQL avec CQL3 par Datastax
• Object mapper nécessaire pour lier
entités/requêtes
• Concepts JPA: bien connus et maîtrisés
7
https://github.com/doanduyhai/Achilles
Mapping d’entités
• Annotations JPA
– @Entity, @Table
– @Id, @EmbededId, @Column, @JoinColumn
– @OneToOne, @OneToMany …
• Spécifiques à Cassandra
– @WideRow
– @Consistency
– @Key (composite)
– @Lazy
8
https://github.com/doanduyhai/Achilles 9
Mapping d’entités
@Entity
public class UserBean
@Id
private Long id;
@Column
private String name;
@Lazy
@Column
private List<Long> friendIds;
@Column
private WideMap<UUID, String> tweets;
@ManyToOne
@JoinColumn
private UserBean referer;
https://github.com/doanduyhai/Achilles
Types spécifiques Cassandra
• WideMap<K,V>: miroir d’une column family
– KeyValue<K,V>: représente une colonne
– KeyValueIterator<K,V>: itérateur
• Counter: compteur distribué
10
puissance
alfred batgirl catwoman lois_lane robin
1 6 7 1 8
WideMap<String,Integer>
KeyValue<String,Integer>
https://github.com/doanduyhai/Achilles 11
Entity Manager
• find(Entity.class,pk)
– retourne entité « managed » (proxy)
• getReference(Entity.class,pk)*
– retourne proxy sans chargement initial
– peut servir pour du direct-update
• persist(entity)
– écrase/met à jour toute valeur existante
– persist(User(fn,ln,age)) puis persist(User(fn,ln)) ?
https://github.com/doanduyhai/Achilles
Entity Manager
• merge(entity)
– dirty check + flush à C*
– retourne entité « managed » si transient
– entité retournée == entité mergée
• remove(entity)
– effacer + cascade delete des types de C*
• refresh(entity)
– recharge l’entité
– identique à find()
12
https://github.com/doanduyhai/Achilles
Persistance avec Thrift
• Persistance des champs « simples »
13
10
firstname lastname age
DuyHai DOAN 32
• Persistance des listes
10
0 1 2 3
Java Cassandra Scala Angular JS
https://github.com/doanduyhai/Achilles 14
Persistance avec Thrift
• Persistance des Sets
10
1265231 68754 546546
#Cassandra #Achilles #Tatami
• Persistance des Maps
10
113131 6543213 51313 65465464
{language:Java} {database:Cassandra} … …
https://github.com/doanduyhai/Achilles
Dirty check
• Interception seulement sur getter (internal calls )
• Pas d’update atomique des collections/maps
• Dirty check des collections/maps coûteux en Thrift
Impl
– (read + delete) + write pour mettre à jour (pas de slice
delete)
– optimisation possible pour Set & Map
– list.add(2,peter) ???
– faible cardinalité conseillée (10 -20)
15
friends
0 1 2 3 4
bob alice john helen richard
https://github.com/doanduyhai/Achilles
Lazy Loading
• @Lazy, typiquement collections/maps
• Champs chargés à l’appel du getter
• Chargement complet des collections/maps par
slice query
• Champs de jointure toujours lazy, par choix de
conception
16
https://github.com/doanduyhai/Achilles
Jointures
• Besoin de cohérence forte au détriment de la
performance
• Join collections/maps ->
– Optimisation avec MultiGet Slice Query au
chargement
• 1 lecture pour n clés primaires dans la collection/map
• 1 multiget slice query pour charger les n jointures (n
lectures côté C*)
– Batch mutation pour sauvegarder
– Jointure possible sur les WideMap
17
https://github.com/doanduyhai/Achilles
Cascading
• No cascade
• PERSIST
– écrase l’entité existante
• MERGE
– écrase les colonnes existantes si modifiées
• REFRESH -> rien
• REMOVE -> interdit/non supporté/mal
• ALL -> tout sauf REMOVE
18
https://github.com/doanduyhai/Achilles
WideMap API
19
@Entity
@Table(name=«user»)
class User
@Id
private Long userId;
@Column
private String firstname;
…..
@Column(table=« tweet »)
WideMap<UUID,String> tweets;
10
067e6162-3b6f-… 54947df8-0e9e-… 38400000-8cf0-…
Hello world! #IppEvent avec #Achilles Ceci est un tweet...
10
firstname lastname age
Julien DUBOIS ???
https://github.com/doanduyhai/Achilles
WideMap API
• insert(K key, V value, int ttl)
• V get(K key)
• remove(K key)
• List<KeyValue<K, V>> find(K start, K end, int count)
• List<KeyValue<K, V>> findBoundsExclusive(K start, K end,
int count)
• List<KeyValue<K, V>> findReverse(K start, K end, int count)
• List<KeyValue<K,V>> findFirst(int count)
• List<KeyValue<K,V>> findLast(int count)
• KeyValueIterator<K,V> iterator(int count)
• ...
20
https://github.com/doanduyhai/Achilles
WideRow
• Entité avec une primary Key + WideMap
• Représente directement une column family
21
@Entity
@WideRow
public class TweetLine
@Id
private Long id;
@Column
private WideMap<UUID, String> tweets;
10
uuid1 uuid2 uuid3 uuid4 uuid5
Test Hellow World Démo Achilles CQL3 Ipp Event
https://github.com/doanduyhai/Achilles
Counter API
• get()
• incr(), incr(n)
• decr(), decr(n)
• Pas de suppression de compteur, c’est mal
• WideMap<xxx,Counter> possible -> column family
counter dédiée
22
@Id
private Long userId;
@Column
Counter tweetsCount;
"User:10"
tweetsCount friendsCount followersCount
150 1000 420
"Tweet:067e6162-3b6f"
likesCount retweetCount
5000 15000
https://github.com/doanduyhai/Achilles
Usage
23
user.getTweets().insert(uuid, «content »);
List<KeyValue<UUID,String>> first10 = user.getTweets().findFirst(10);
List<String> last10Tweets = user.getTweets().findLastValues(10);
user.setTweets(xxx);
• WideMap
Long tweetCount = user.getTweetCount().get();
user.getTweetCount().incr(3);
user.setTweetCount(xxx);
• Counter
https://github.com/doanduyhai/Achilles
Démo
Comment faire un clone de Twitter
avec Achilles
24
https://github.com/doanduyhai/Achilles
Compromis
• La lecture des tweets doit être très très rapide
• L’envoi des tweets doit être relativement
rapide
• La suppression des tweets peut être lente
25
https://github.com/doanduyhai/Achilles
Scénario 1
• Modéliser un utilisateur
• Modéliser la liste des amis/suiveurs
• Implémenter un compteur d’amis/suiveurs
• Implémenter la fonctionnalité « A suit B »
• Donner la liste des amis/suiveurs d’un
utilisateur
• Donner les détails sur un utilisateur
26
https://github.com/doanduyhai/Achilles
Scénario 2
• Modéliser un « Tweet »
• Implémenter l’envoi de tweet
• Dupliquer le tweet
– dans la liste des tweets propres à l’utilisateur
– dans la timeline de l’utilisateur
– dans la timeline de tous ses suiveurs
27
https://github.com/doanduyhai/Achilles
Scénario 3
• Supprimer un tweet
– de la liste des tweets de l’utilisateur
– de la timeline de l’utilisateur
– de la timeline de tous ses suiveurs
28
https://github.com/doanduyhai/Achilles
Scénario 4
• Implémenter la gestion des hashtags
• Créer une tag-line pour les tags
• Gérer l’effacement du tweet de la tagline
lorsque le tweet est effacé
29
https://github.com/doanduyhai/Achilles
Scénario 5
• Donner la possibilité de mettre un tweet en
« favori » -> favoriteline
• Détecter les personnes citées dans les tweets
(@login) -> mentionline
• Gérer l’effacement de la favoriteline et de la
mentionline lorsque le tweet est effacé
30
https://github.com/doanduyhai/Achilles
Clés Composite
• Interface marqueur MultiKey
• Annotation dédiée @Key(order=x)
31
@JoinColumn
private WideMap<UserIndex, User> user;
public class UserIndex implements MultiKey
{
@Key(order=1)
private String login;
@Key(order=2)
private Long id;
}
https://github.com/doanduyhai/Achilles
Consistency Level
• Par annotation
32
@Consistency(read=ONE,write=QUORUM)
public class User
@Column
@Consistency(read=ONE,write=QUORUM)
private Counter tweetsCount;
@Column
@Consistency(read=ONE,write=THREE)
private WideMap<UUID,String> tweets;
• Au runtime
– persist(tweet,QUORUM)
– merge(user,ALL)
– tweetWideMap.insert(uuid1, «a tweet», ONE)
https://github.com/doanduyhai/Achilles
TTL
33
• Sur les opérations JPA
– persist(entity, ttl)
– persist(entity, ttl, writeCL)
– merge(entity, ttl)
– merge(entity, ttl, writeCL)
• Sur les WideMap
– user.getTweets().insert(uuid, «content», ttl)
• Pas de TTL sur les counters (pas de tombstones)
https://github.com/doanduyhai/Achilles
Le future
Que nous réserve Achilles dans
quelques mois ?
34
https://github.com/doanduyhai/Achilles
Projection
• Support complet de CQL3
– @NamedQuery, query data mapper
– Prepared statement (perf)
– Batches « atomiques »
– Row key composite (clustering)
• Interceptors (@PrePersist,@PreRemove…)
• Listeners
• Bean Validation (JSR 303)
• Secondary index
• Callable, Future<>…
35
https://github.com/doanduyhai/Achilles
Retour d’expérience CQL3
• PreparedStatement (select fn,ln,age from User where userId=?)
– Stocké côté serveur (100.000 max) et par Node
– Seules les valeurs sont envoyées à l’exécution
– Valeurs nulles possible -> effacer le champ
– Pas possible de binder CL, TTL, Timestamp,
possible dans C* 2.0
– Pas de batch de PS, possible dans C* 2.0
– Pas de IN (?) binding pour faire du MultiGet
36
https://github.com/doanduyhai/Achilles 37
Retour d’expérience CQL3
• List/Set/Map démystifié
– Nouveaux serializers:
• ColumnToCollectionType
• ListType, SetType,MapType
– List: index = timeUUID généré
• insertion rapide avec append/prepend
• ré-écriture + décalage si insertion par index
• read-before-write pour enlever un élément quelconque
– Set: valeur stockée directement dans ColumnName
– Map: columnName contient la clé
https://github.com/doanduyhai/Achilles
Retour d’expérience CQL3
CREATE TABLE users (
user_id int PRIMARY KEY,
emails set<text>,
top_places list<text>,
todo map<int, text>
);
INSERT INTO users(user_id,emails,top_places,todo)
VALUES(1,{'ddoan@test.com','dhdoan@gmail.com'},['Paris','Chatillon’],{1:'Demo Achilles', 2:'Implement CQL3'});
RowKey: 1
=> (column=, value=, timestamp=1362010585307000)
=> (column=emails:64646f616e40746573742e636f6d,value=, timestamp=1362010585307000)
=> (column=emails:6468646f616e40676d61696c2e636f6d,value=, timestamp=1362010585307000)
=> (column=todo:00000001,value=44656d6f20416368696c6c6573, timestamp=1362010585307000)
=> (column=todo:00000002,value=496d706c656d656e742043514c33,timestamp=1362010585307000)
=> (column=top_places:16ae51c0813c11e289730dd4d81b4c6b,value=5061726973, timestamp=….)
=> (column=top_places:16ae51c1813c11e289730dd4d81b4c6b,value=43686174696c6c6f6e, timestamp=…)
38
https://github.com/doanduyhai/Achilles
Achilles est open-source donc …
39
We need you !!!
github.com/doanduyhai/Achilles

JPA avec Cassandra, grâce à Achilles