Upgrade to Pro — share decks privately, control downloads, hide ads and more …

MongoDB Paris 2012: MongoDB chez illicotravel

mongodb
June 14, 2012
73

MongoDB Paris 2012: MongoDB chez illicotravel

Illicotravel est un comparateur de voyages faisant 450 000 VU/mois générant un volume d'affaire de plus de 30 millions d'euros pour nos partenaires voyagistes et compagnies aériennes. Quelles ont été nos motivations pour abandonner MySQL ? Comment avons-nous intégré MongoDB dans notre architecture Java ? Avons-nous toujours besoin d'une base en SQL ? (un indice: non) Quelles ont été nos découvertes, nos surprises, et surtout les patterns récurrents lors de l'utilisation des index, du map/reduce ou de morphia (ODM)

mongodb

June 14, 2012
Tweet

Transcript

  1.  Illicotravel, c’est:  Un des plus gros comparateurs de

    voyages en France  450 000 VU/mois  30 millions de CA généré pour les partenaires  Bcp de développements internes ▪ Langage de script outillé fait maison ▪ Propre framework de composants JavaScript sur Mootools  Approx. 10 Go de données
  2.  Nous vivons dans un monde hautement abstrait et confortable

     POO  Les Objets se baladent sur le réseau en XML ou en JSON  Une grosse partie de notre travail consiste à rester dans ce monde parfait  Nous utilisons des librairies JavaScript qui nous affranchissent des soucis des navigateurs  Nous pouvons changer de serveur d’appli, nos servlets tournerons toujours  Nous nettoyons les données externes (les fichiers XML des voyagistes, les captures HTML des offres, …)
  3. public PreparedStatement createPreparedStatement(Connection con) throws SQLException { String sql =

    "INSERT INTO vacation" + " (sourceId, creationDate, advertiserName, idFromAdvertiser, vacationType, title, extraInfo, shortDescription, longDescription, continentCode, countryCode, city, dayCount, nightCount, hotelName, smallImageUrl, bigImageUrl, detailUrl)" + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; PreparedStatement ps = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); ps.setString(1, vacation.getSourceId()); ps.setTimestamp(2, toTimestamp(vacation.getCreationDate())); ps.setString(3, vacation.getAdvertiserName()); ps.setString(4, vacation.getIdFromAdvertiser()); ps.setString(5, vacation.getVacationType()); ps.setString(6, vacation.getTitle()); ps.setString(7, vacation.getExtraInfo()); ps.setString(8, vacation.getShortDescription()); ps.setString(9, vacation.getLongDescription()); ps.setString(10, vacation.getContinentCode()); ps.setString(11, vacation.getCountry().getCode()); ps.setString(12, vacation.getCity()); ps.setInt(13, vacation.getDayCount()); ps.setInt(14, vacation.getNightCount()); ps.setString(15, vacation.getHotelName()); ps.setString(16, vacation.getSmallImageUrl()); ps.setString(17, vacation.getBigImageUrl()); ps.setString(18, vacation.getDetailUrl()); return ps; }
  4.  Le monde SQL fourni des outils pour résoudre ces

    problèmes  ORM: Hibernate  Clustering ▪ MySQL Cluster ▪ Driver JDBC virtuel (Continuent)…  Loin du produit « évident » qu’on s'imagine pouvoir coder soi-même (avec du temps)
  5.  Ancienne architecture (simplifiée) Front (Java) Front (Java) MySQL DAO

    (PHP) Load balancer (Apache) Cherchez l’erreur JSON/HTTP
  6.  En gros, le choix était entre Cassandra et MongoDB

     Cassandra: trop bas niveau  Une HashMap distribuée  Les index sont à gérer soi-même (en tout cas à l’époque). Et surtout…
  7. <vol> prix, voyagiste, … <allers></allers> <retours></retours> </vol> <hotel> voyagiste, description,

    … <photos></photos> <prix par type de chambre> </prix par type de chambre> </hotel> <sejour> voyagiste, description, … <prix par date et ville de départ> </ prix par date et ville de départ > </sejour> Et surtout…
  8.  Expérience personnelle  tout va bien (on parlera des

    gros Map/Reduce plus loin)  nous sommes en 1.8  2 révolutions en parallèle  NOSQL  RAM (OVH: 109,99 €/mois pour Xeon i7 – 24 Go)
  9.  Verbosité en Java (petite structure)  Verbosité en Java

    (grosse structure)  Morphia et concurrence  Index  Grosses requêtes  Map/Reduce
  10. JavaScript Object literal: var index = { city: 1, date:

    1, price: 1} Custom class héritant de BasicDBObject DBObject index = new DBO("city", 1, "date", 1, "price", 1); Java Driver: DBObject index= new BasicDBObject(); index.put("city", 1); index.put("date", 1); index.put("price", 1) Java Driver + Google Guava DBObject index = new BasicDBObject(ImmutableMap.of( "city", 1, "date", 1, "price", 1));
  11. Objet Car (location de voiture) Avec DBO (custom DBObject): DBObject

    index = new DBO( "brand", car.getBrand(), "model", car.getModel(), "gear", car.getGear(), "image", car.getImageUrl(), "price", car.getPrice(), …);
  12. Solution évidente: Morphia (ODM) Quelques annotations + getMorphia().toDBObject(car) Puis…la version

    stockée commence à changer . les dates deviennent des string . des champs en plus, des champs en moins Que faire ?
  13.  On passe Morphia et on modifie le résultat ?

     On utilise notre custom DBO ? Et la lecture ? Les structures stockées sont...autre chose => On crée des classes représentant les données stockées. Eg: CarInDb
  14. Problème:  Vision simpliste: "j'ai un Objet, je le lis

    en 1 ligne, je le modifie et je le sauve en 1 ligne"  Et si...2 users le font en même temps, le 2e peut écraser les modifs du 1er Solution:  Morphia uniquement en lecture  écriture fine utilisant $inc, $set, $push, ...
  15. Problème: 1. les champs requêtés peuvent être très variables 2.

    L’ordre d’un index est important (MongoVUE anyone?), mais surtout: on ne peut pas sauter un champs
  16. Solution: date city depCity | depCountry | airline price ...

    L'idéal On fait attention MongoUtils.index(col, "date", "city", "depCity" , "price"); MongoUtils.index(col, "date", "city" , "depCountry" , "price"); MongoUtils.index(col, "date", "city", "depCity" , "airline", "price"); MongoUtils.index(col, "date", "city" , "depCountry", "airline", "price");
  17.  problème: gros scan de la collection => requête longue

     eg: prix min des séjours par pays (30 sec)  Solutions: 1. background cache (code maison pour ehcache) 2. pattern: profiter du nombre élevé de requêtes en découpant une grosse requête en pls petites => bien gérer ses Randoms
  18.  abstraction séduisante  puissance réelle  mais...  pas

    si instinctif que ça: ▪ SELECT min(price) FROM flights GROUP BY departure, arrival devient ▪ db.coll.group( { key: { departure:true, arrival:true }, reduce: function(obj,prev) { if(out.price > doc.price) { out.price = doc.price; }, });  pour un min, on n'a pas accès à l'objet associé  on se retrouve à générer du texte  perf. JS, non multi-core
  19. l'idéal serait:  comme en JavaScript forEach / map /

    filter  comme en Java 8 avec les lambdas => framework d'aggrégation  language d'expression très limité, mais pas de JavaScript