Doing multi-criteria queries on a
Cassandra application
Ippon Technologies © 2015
Qui sommes-nous?
Jérôme Mainaud
○ Architecte Java chez Ippon Technologies
○ DataStax Solution Ar...
Ippon Technologies © 2015
Sommaire
1. Intro
2. Stack technique
3. Configuration du cluster
4. Application d’exemple
5. Rec...
Ippon Technologies © 2014
Stack technique
Ippon Technologies © 2015
Stack technique
● JHipster
○ Générateur d’applications Spring Boot + AngularJS
○ Supporte JPA, M...
Ippon Technologies © 2015
Stack technique
● Spring Boot
○ Basé sur Spring
○ Convention over configuration
○ Nombreux “star...
Ippon Technologies © 2015
Paramétrage du Driver
● Configuration Spring Boot
○ Nous avons réalisé notre propre configuratio...
Ippon Technologies © 2015
Développement des Repositories
● DataStax Java driver utilisé dans un Repository Spring
@Reposit...
Ippon Technologies © 2014
Configuration du cluster
Ippon Technologies © 2015
Hardware
● Hébergement chez Ippon Hosting
● 8 noeuds équivalents
○ 16 Go de RAM
○ Deux disques d...
Ippon Technologies © 2015
DataStax Enterprise
● Utilisation de DataStax Enterprise
● OpsCenter nous a été d’une très grand...
Ippon Technologies © 2014
Application exemple
Système de gestion de factures
Ippon Technologies © 2015
Modèle conceptuel
Ippon Technologies © 2015
Modèle physique
Ippon Technologies © 2015
create table invoice (
invoice_id timeuuid,
user_id uuid static,
firstname text static,
lastname...
Ippon Technologies © 2014
Recherche multi-critère
Ippon Technologies © 2015
Recherche multi-critères
Critères obligatoires
○ User (implicite)
○ Date de la facture (plage de...
Ippon Technologies © 2015
Utiliser Solr ?
Ippon Technologies © 2015
Utiliser Solr ?
● Intégré dans DataStax Enterprise
● Mise à jour atomique et automatique
● Reche...
Ippon Technologies © 2015
Utiliser Solr ?
On cherche sur des colonnes statiques
Solr ne les gère pas
On cherche des partit...
Ippon Technologies © 2015
Utiliser Solr ?
On cherche sur des colonnes statiques
Solr ne les gère pas
On cherche des partit...
Ippon Technologies © 2015
Index secondaires ?
● Ne répondent qu’aux cas de recherche sur un seul champ
● Délicats à utilis...
Ippon Technologies © 2015
Tables d’index
Utilisation de tables d’index
○ Clé de partition : Les critères primaire et un cr...
Ippon Technologies © 2015
Recherche
Q1
Q2
A D J M
A C J L M
A J M
Fusion applicative
en mémoire
Recherches en parallèle
Ippon Technologies © 2015
Recherche
Une page de résultat (id)
8f5b69ee-0ad0-11e5-a6c0-1697f925ec7b
8f5b6d4a-0ad0-11e5-a6c0...
Ippon Technologies © 2015
Recherche
Recherche sur une plage de dates
○ boucle sur les jours en s’arrêtant
dès qu’on a une ...
Ippon Technologies © 2015
Recherche
Nombre de requêtes
○ Pour chaque jour dans la plage de dates
■ 1 requête par critère s...
Ippon Technologies © 2014
JAVA
Ippon Technologies © 2015
Index — instances
@Repository
public class InvoiceByLastNameRepository extends IndexRepository<S...
Ippon Technologies © 2015
Index — classe parente
public class IndexRepository<T> {
@Inject
private Session session;
privat...
Ippon Technologies © 2015
Index — Écriture
@Override
public void insert(Invoice invoice) {
T value = valueGetter.apply(inv...
Ippon Technologies © 2015
Index — Écriture
insertStmt = session.prepare(
QueryBuilder.insertInto(tableName)
.value("user_i...
Ippon Technologies © 2015
Index — Recherche
@Override
public CompletableFuture<Iterator<UUID>> find(Criteria criteria, Loc...
Ippon Technologies © 2015
Index — Recherche
findWithOffsetStmt = session.prepare(
QueryBuilder.select()
.column("invoice_i...
Ippon Technologies © 2015
Index — Recherche (Guava to Java 8)
public static <T> CompletableFuture<T> completableFuture(Lis...
Ippon Technologies © 2014
JAVA
Service de recherche
Ippon Technologies © 2015
Service — Class
@Service
public class InvoiceSearchService {
@Inject
private InvoiceRepository i...
Ippon Technologies © 2015
Service — recherche
public ResultPage findByCriteria(Criteria criteria) {
return byDateInteval(c...
Ippon Technologies © 2015
Service — recherche
CompletableFuture<Iterator<UUID>>[] futures = Stream.<IndexRepository> of(
b...
Ippon Technologies © 2015
Service — Comparaison des UUIDs
/**
* Comparateur de TimeUUID équivalent à celui de Cassandra:
*...
Ippon Technologies © 2015
Service — Boucle sur les jours
@FunctionalInterface
private static interface DayQuery {
Completa...
Ippon Technologies © 2015
Service — Boucle sur les jours
LocalDate day = criteria.getLastDay();
do {
Iterator<UUID> uuidIt...
Ippon Technologies © 2015
Service — chargement des factures
private int loadInvoices(List<Invoice> resultList, Iterator<UU...
Ippon Technologies © 2014
Limitations
Ippon Technologies © 2015
Limitations
La recherche ne fonctionne que sur un texte précis
○ Pas de recherche “plein texte”
...
Ippon Technologies © 2014
Résultats
Ippon Technologies © 2015
Résultats métier
● Gestion d’un an de données, sans limite
○ Nous comptons monter à 3 ans
○ Anci...
Ippon Technologies © 2015
Résultats techniques
● Les tests Gatling ont montré que nous pouvions tenir
5000 utilisateurs co...
Ippon Technologies © 2014
Merci à tous!
Prochain SlideShare
Chargement dans…5
×

Requêtes multi-critères avec Cassandra

1 906 vues

Publié le

Réaliser des requêtes multi-critères avec Cassandra.
Présentation réalisée dans le cadre du Cassandra Day Paris 2015.

Publié dans : Technologie
0 commentaire
1 j’aime
Statistiques
Remarques
  • Soyez le premier à commenter

Aucun téléchargement
Vues
Nombre de vues
1 906
Sur SlideShare
0
Issues des intégrations
0
Intégrations
623
Actions
Partages
0
Téléchargements
53
Commentaires
0
J’aime
1
Intégrations 0
Aucune incorporation

Aucune remarque pour cette diapositive

Requêtes multi-critères avec Cassandra

  1. 1. Doing multi-criteria queries on a Cassandra application
  2. 2. Ippon Technologies © 2015 Qui sommes-nous? Jérôme Mainaud ○ Architecte Java chez Ippon Technologies ○ DataStax Solution Architect Certifié Julien Dubois ○ 15 ans d’expérience en Java ○ Directeur de l’innovation chez Ippon Technologies Ippon Technologies ○ Expertise Java & Big Data: consulting, formation, hébergement ○ 200 personnes ○ Paris, Bordeaux, Nantes, Richmond (USA)
  3. 3. Ippon Technologies © 2015 Sommaire 1. Intro 2. Stack technique 3. Configuration du cluster 4. Application d’exemple 5. Recherche multi-critère 6. Utilisation de CQL3 avec Java 8 7. Limitations 8. Résultats
  4. 4. Ippon Technologies © 2014 Stack technique
  5. 5. Ippon Technologies © 2015 Stack technique ● JHipster ○ Générateur d’applications Spring Boot + AngularJS ○ Supporte JPA, MongoDB et… Cassandra! ● Nous a permis de générer l’application très rapidement ○ Squelette d’application prêt en 5 minutes ○ Ajout de tables avec leur mapping ○ Configuration, build, gestion des logs, etc. ○ Tests Gatling prêts à l’emploi Plus d’informations sur http://jhipster.github.io/
  6. 6. Ippon Technologies © 2015 Stack technique ● Spring Boot ○ Basé sur Spring ○ Convention over configuration ○ Nombreux “starters” prêts à l’emploi ● Services Web ○ CXF vs Spring MVC REST ● Cassandra ○ DataStax Enterprise
  7. 7. Ippon Technologies © 2015 Paramétrage du Driver ● Configuration Spring Boot ○ Nous avons réalisé notre propre configuration du DataStax Java Driver ○ Intégration dans la configuration standard de Spring Boot, utilisant un fichier YAML ● Intégrée dans Spring Boot 1.3 ○ Ce code a été proposé à Pivotal, et intégré dans Spring Boot 1.3 ● Améliorée par PR ○ JHipster a été amélioré depuis, et propose toujours une configuration plus complète que celle de Spring Boot
  8. 8. Ippon Technologies © 2015 Développement des Repositories ● DataStax Java driver utilisé dans un Repository Spring @Repository public class UserRepository { @Inject private Session session; private PreparedStatement findOneByEmailStmt; @PostConstruct public void init() { findOneByEmailStmt = session.prepare( "SELECT id FROM user_by_email WHERE email = :email"); } public Optional<User> findOneByEmail(String email) { … }
  9. 9. Ippon Technologies © 2014 Configuration du cluster
  10. 10. Ippon Technologies © 2015 Hardware ● Hébergement chez Ippon Hosting ● 8 noeuds équivalents ○ 16 Go de RAM ○ Deux disques durs SSD de 256 Go en RAID 0 ● 6 noeuds pour le cluster Cassandra, 2 noeuds pour l’ application
  11. 11. Ippon Technologies © 2015 DataStax Enterprise ● Utilisation de DataStax Enterprise ● OpsCenter nous a été d’une très grande aide ○ Monitoring ○ Services automatisés ○ Gestion du cluster
  12. 12. Ippon Technologies © 2014 Application exemple Système de gestion de factures
  13. 13. Ippon Technologies © 2015 Modèle conceptuel
  14. 14. Ippon Technologies © 2015 Modèle physique
  15. 15. Ippon Technologies © 2015 create table invoice ( invoice_id timeuuid, user_id uuid static, firstname text static, lastname text static, invoice_date timestamp static, payment_date timestamp static, total_amount decimal static, delivery_address text static, delivery_city text static, delivery_zipcode text static, item_id timeuuid, item_label text, item_price decimal, item_qty int, item_total decimal, primary key (invoice_id, item_id) ); Table
  16. 16. Ippon Technologies © 2014 Recherche multi-critère
  17. 17. Ippon Technologies © 2015 Recherche multi-critères Critères obligatoires ○ User (implicite) ○ Date de la facture (plage de dates) Critères supplémentaires ○ Nom du client ○ Prénom du client ○ Ville ○ Code postal
  18. 18. Ippon Technologies © 2015 Utiliser Solr ?
  19. 19. Ippon Technologies © 2015 Utiliser Solr ? ● Intégré dans DataStax Enterprise ● Mise à jour atomique et automatique ● Recherche documentaire
  20. 20. Ippon Technologies © 2015 Utiliser Solr ? On cherche sur des colonnes statiques Solr ne les gère pas On cherche des partitions Solr retourne des lignes
  21. 21. Ippon Technologies © 2015 Utiliser Solr ? On cherche sur des colonnes statiques Solr ne les gère pas On cherche des partitions Solr retourne des lignes
  22. 22. Ippon Technologies © 2015 Index secondaires ? ● Ne répondent qu’aux cas de recherche sur un seul champ ● Délicats à utiliser avec de bonnes performances
  23. 23. Ippon Technologies © 2015 Tables d’index Utilisation de tables d’index ○ Clé de partition : Les critères primaire et un critère secondaire ■ user_id ■ date de facturation (tronqué à la date) ■ le critère secondaire ○ Clustering columns : l’identifiant de la facture
  24. 24. Ippon Technologies © 2015 Recherche Q1 Q2 A D J M A C J L M A J M Fusion applicative en mémoire Recherches en parallèle
  25. 25. Ippon Technologies © 2015 Recherche Une page de résultat (id) 8f5b69ee-0ad0-11e5-a6c0-1697f925ec7b 8f5b6d4a-0ad0-11e5-a6c0-1697f925ec7b 8f5b6e9e-0ad0-11e5-a6c0-1697f925ec7b b3db1a30-0ad0-11e5-a6c0-1697f925ec7b b3db1c88-0ad0-11e5-a6c0-1697f925ec7b b3db202a-0ad0-11e5-a6c0-1697f925ec7b b3db219c-0ad0-11e5-a6c0-1697f925ec7b cac5be94-0ad0-11e5-a6c0-1697f925ec7b cac5c006-0ad0-11e5-a6c0-1697f925ec7b cac5c150-0ad0-11e5-a6c0-1697f925ec7b N recherches unitaires en parallèle
  26. 26. Ippon Technologies © 2015 Recherche Recherche sur une plage de dates ○ boucle sur les jours en s’arrêtant dès qu’on a une page de résultat
  27. 27. Ippon Technologies © 2015 Recherche Nombre de requêtes ○ Pour chaque jour dans la plage de dates ■ 1 requête par critère secondaire (partition by query) ○ 1 requête par élément trouvé (partition by query) Complexité de la recherche ○ partitions by query Exemple: 3 critères, 3 jours, 100 par pages ○ nombre de requêtes ≤ 3 × 3 + 100 = 109
  28. 28. Ippon Technologies © 2014 JAVA
  29. 29. Ippon Technologies © 2015 Index — instances @Repository public class InvoiceByLastNameRepository extends IndexRepository<String> { public InvoiceByLastNameRepository() { super("invoice_by_lastname", "lastname", Invoice::getLastName, Criteria::getLastName); } } @Repository public class InvoiceByFirstNameRepository extends IndexRepository<String> { public InvoiceByFirstNameRepository() { super("invoice_by_firstname", "firstname", Invoice::getFirstName, Criteria::getFirstName); } }
  30. 30. Ippon Technologies © 2015 Index — classe parente public class IndexRepository<T> { @Inject private Session session; private final String tableName; private final String valueName; private final Function<Invoice, T> valueGetter; private final Function<Criteria, T> criteriumGetter; private PreparedStatement insertStmt; private PreparedStatement findStmt; private PreparedStatement findWithOffsetStmt; @PostConstruct public void init() { /* initialise les PreparedStatements */ }
  31. 31. Ippon Technologies © 2015 Index — Écriture @Override public void insert(Invoice invoice) { T value = valueGetter.apply(invoice); if (value != null) { session.execute( insertStmt.bind( invoice.getUserId(), Dates.toDate(invoice.getInvoiceDay()), value, invoice.getId())); } }
  32. 32. Ippon Technologies © 2015 Index — Écriture insertStmt = session.prepare( QueryBuilder.insertInto(tableName) .value("user_id", bindMarker()) .value("invoice_day", bindMarker()) .value(valueName, bindMarker()) .value("invoice_id", bindMarker()) ); public static Date toDate(LocalDate date) { return date == null ? null : Date.from(date.atStartOfDay().atZone(ZoneOffset.systemDefault()).toInstant()); }
  33. 33. Ippon Technologies © 2015 Index — Recherche @Override public CompletableFuture<Iterator<UUID>> find(Criteria criteria, LocalDate day, UUID offset) { T criterium = criteriumGetter.apply(criteria); if (criterium == null) { return CompletableFuture.completedFuture(null); } BoundStatement stmt; if (invoiceIdOffset == null) { stmt = findStmt.bind(criteria.getUserId(), Dates.toDate(day), criterium); } else { stmt = findWithOffsetStmt.bind(criteria.getUserId(), Dates.toDate(day), criterium, offset); } return Jdk8.completableFuture(session.executeAsync(stmt)) .thenApply(rs -> Iterators.transform(rs.iterator(), row -> row.getUUID(0))); }
  34. 34. Ippon Technologies © 2015 Index — Recherche findWithOffsetStmt = session.prepare( QueryBuilder.select() .column("invoice_id") .from(tableName) .where(eq("user_id", bindMarker())) .and(eq("invoice_day", bindMarker())) .and(eq(valueName, bindMarker())) .and(lte("invoice_id", bindMarker())) );
  35. 35. Ippon Technologies © 2015 Index — Recherche (Guava to Java 8) public static <T> CompletableFuture<T> completableFuture(ListenableFuture<T> guavaFuture) { CompletableFuture<T> future = new CompletableFuture<>(); Futures.addCallback(guavaFuture, new FutureCallback<T>() { @Override public void onSuccess(V result) { future.complete(result); } @Override public void onFailure(Throwable t) { future.completeExceptionally(t); } }); return future; }
  36. 36. Ippon Technologies © 2014 JAVA Service de recherche
  37. 37. Ippon Technologies © 2015 Service — Class @Service public class InvoiceSearchService { @Inject private InvoiceRepository invoiceRepository; @Inject private InvoiceByDayRepository byDayRepository; @Inject private InvoiceByLastNameRepository byLastNameRepository; @Inject private InvoiceByFirstNameRepository byLastNameRepository; @Inject private InvoiceByCityRepository byCityRepository; @Inject private InvoiceByZipCodeRepository byZipCodeRepository;
  38. 38. Ippon Technologies © 2015 Service — recherche public ResultPage findByCriteria(Criteria criteria) { return byDateInteval(criteria, (crit, day, offset) -> { CompletableFuture<Iterator<UUID>> futureUuidIt; if (crit.hasIndexedCriteria()) { /* * ... Recherche multi-critère à voir dans la prochaine diapo ... */ } else { futureUuidIt = byDayRepository.find(crit.getUserId(), day, offset); } return futureUuidIt; }); }
  39. 39. Ippon Technologies © 2015 Service — recherche CompletableFuture<Iterator<UUID>>[] futures = Stream.<IndexRepository> of( byLastNameRepository, byFirstNameRepository, byCityRepository, byZipCodeRepository) .map(repo -> repo.find(crit, day, offset)) .toArray(CompletableFuture[]::new); futureUuidIt = CompletableFuture.allOf(futures).thenApply(v -> Iterators.intersection(TimeUUIDComparator.desc, Stream.of(futures) .map(CompletableFuture::join) .filter(Objects::nonNull) .collect(Collectors.toList())));
  40. 40. Ippon Technologies © 2015 Service — Comparaison des UUIDs /** * Comparateur de TimeUUID équivalent à celui de Cassandra: * @see org.apache.cassandra.db.marshal.TimeUUIDType#compare() */ public enum TimeUUIDComparator implements Comparator<UUID> { desc { @Override public int compare(UUID o1, UUID o2) { long delta = o2.timestamp() - o1.timestamp(); if (delta != 0) return Ints.saturatedCast(delta); return o2.compareTo(o1); } }; }
  41. 41. Ippon Technologies © 2015 Service — Boucle sur les jours @FunctionalInterface private static interface DayQuery { CompletableFuture<Iterator<UUID>> find(Criteria criteria, LocalDate day, UUID invoiceIdOffset); } private ResultPage byDateInteval(Criteria criteria, DayQuery dayQuery) { int limit = criteria.getLimit(); List<Invoice> resultList = new ArrayList<>(limit); LocalDate dayOffset = criteria.getDayOffset(); UUID invoiceIdOffset = criteria.getInvoiceIdOffset(); /* ... Boucle sur les jours ; à voir dans la prochaine diapo ... */ return new ResultPage(resultList); }
  42. 42. Ippon Technologies © 2015 Service — Boucle sur les jours LocalDate day = criteria.getLastDay(); do { Iterator<UUID> uuidIt = dayQuery.find(criteria, day, invoiceIdOffset).join(); limit -= loadInvoices(resultList, uuidIt, criteria, limit); if (uuidIt.hasNext()) { return new ResultPage(resultList, day, uuidIt.next()); } day = day.minusDays(1); invoiceIdOffset = null; } while (!day.isBefore(criteria.getFirstDay()));
  43. 43. Ippon Technologies © 2015 Service — chargement des factures private int loadInvoices(List<Invoice> resultList, Iterator<UUID> uuidIt, Criteria criteria, int limit) { List<CompletableFuture<Invoice>> futureList = new ArrayList<>(limit); for (int i = 0; i < limit && uuidIt.hasNext(); ++i) { futureList.add(invoiceRepository.findOne(uuidIt.next())); } futureList.stream() .map(CompletableFuture::join) .forEach(resultList::add); return futureList.size(); }
  44. 44. Ippon Technologies © 2014 Limitations
  45. 45. Ippon Technologies © 2015 Limitations La recherche ne fonctionne que sur un texte précis ○ Pas de recherche “plein texte” ○ Comme dans une base de données classique La pagination ne donne pas le nombre total de pages de résultats Ce mécanisme ne peut fonctionner que s’il existe des critères obligatoires fortement discriminants (ici: user_id et invoice_day)
  46. 46. Ippon Technologies © 2014 Résultats
  47. 47. Ippon Technologies © 2015 Résultats métier ● Gestion d’un an de données, sans limite ○ Nous comptons monter à 3 ans ○ Ancien système: limité à 3 mois ● Obtention des résultats en “temps réel” ○ Les données sont immédiatement disponibles ○ Ancien système: 24h de retard ● Coûts nettement plus bas
  48. 48. Ippon Technologies © 2015 Résultats techniques ● Les tests Gatling ont montré que nous pouvions tenir 5000 utilisateurs concurrents ○ Sur des requêtes complexes, avec multiples critères et pagination ● Nous avons aussi démontré que le cluster était scalable linéairement ○ En termes de volumétrie: ajout de nouveaux disques (passage en JBOD) ○ En termes de performance: ajout de nouveaux noeuds à chaud
  49. 49. Ippon Technologies © 2014 Merci à tous!

×