L'amour est dans le graphe
Un loft ou un pré pour trouver l'amour en 2013 ? Has-been, embrassez plutôt le graphe !
C'est en s'appuyant sur ce concept que MEETIC et OCTO ont choisi de prototyper le développement d'un moteur de recommandation construit autour d'une base de données graphe. Basé sur les principes du filtrage collaboratif, l'objectif est donc d'optimiser les propositions de rencontre en fonction des interactions entre les membres et d'autres axes possibles. Nous vous présenterons donc lors de cette session les différentes étapes de constructions de ce PoC en détaillant les choix technologiques réalisés, le code produit et les limites atteintes.
Alors si vous êtes curieux de découvrir Neo4j, le langage Cypher et la manipulation de tous ces concepts en PHP, venez assister à la première saison !
2. 2
C’est quoi ?
Un retour
d’expérience
Sur le
développement
d’un moteur de
recommandation
Basé sur les
interactions entre
membres
Construit autour
d’une base
graphe (Neo4j)
Eric Dupré
R&D Deputy Director
@Ricouninho
Matthieu Robin
Responsable Core Analyse
Djamel Zouaoui
Manager
@DjamelOnLine
Thomas Vial
Architecte senior
10. 10
Périmètre de la session
Batch de
requetage
Serveur Neo4J
Batch d’import
Membres
Interactions
Prospects
La requête de filtrage collaboratif a été écrite en langage Cypher
Gremlin a été évalué pour l’écriture de l’algorithme de filtrage collaboratif mais abandonné à
cause de ces limitations
Des limites sur les primitives présentes de base dans le langage gremlin
Des limites sur les features présentes (ex : évaluation native de variable dans la requête)
Gremlin est bati sur le langage groovy et n’est donc pas « naturel » à l'utilisation avec du php (ou
n’importe quel autre langage que java)
Des retours d’expérience de l’éditeur (non atteint dans notre poc) concernant des problèmes de
performance (à priori lié à l'utilisation de groovy et l’overhead que cela apporte)
11. 11
START me = node:node_auto_index(abo_id='170021316')
MATCH me-[:wink|favorite]->she<-[:wink|favorite]-slm-[:wink|favorite]->prospects
WHERE not(me = slm) and not(me--prospects)
RETURN count(*) AS prospectsWeight, prospects.attraction AS attraction, prospects.abo_id AS abo_id
ORDER BY count DESC, attraction DESC, abo_id DESC;
Requête CYPHER : cas d’école
Djamel
Nelly
Celine
Marina
Thomas
Rudy
Mathieu
David
Pauline
Anne
Caroline
Siti
Sabrina
Pondération : 3
Pondération : 1
Pondération : 2
Pondération : 1
Pondération : 2
12. 12
Requête CYPHER : cas d’école
Djamel
Nelly
Celine
Marina
Thomas
Rudy
Mathieu
David
Pauline
Anne
Caroline
Siti
Sabrina
START me = node:node_auto_index(abo_id='170021316')
MATCH me-[:wink|favorite]->she<-[:wink|favorite]-slm-[:wink|favorite]->prospects
WHERE not(me = slm) and not(me--prospects)
RETURN count(*) AS prospectsWeight, prospects.attraction AS attraction, prospects.abo_id AS abo_id
ORDER BY count DESC, attraction DESC, abo_id DESC;
Pondération : 3
Pondération : 1
Pondération : 2
Pondération : 1
Pondération : 2
13. 13
Requête CYPHER : cas d’école
Djamel
Nelly
Celine
Marina
Thomas
Rudy
Mathieu
David
Pauline
Anne
Caroline
Siti
Sabrina
START me = node:node_auto_index(abo_id='170021316')
MATCH me-[:wink|favorite]->she<-[:wink|favorite]-slm-[:wink|favorite]->prospects
WHERE not(me = slm) and not(me--prospects)
RETURN count(*) AS prospectsWeight, prospects.attraction AS attraction, prospects.abo_id AS abo_id
ORDER BY count DESC, attraction DESC, abo_id DESC;
Pondération : 3
Pondération : 1
Pondération : 2
Pondération : 1
Pondération : 2
14. 14
Requête CYPHER : cas d’école
Djamel
Nelly
Celine
Marina
Thomas
Rudy
Mathieu
David
Pauline
Anne
Caroline
Siti
Sabrina
START me = node:node_auto_index(abo_id='170021316')
MATCH me-[:wink|favorite]->she<-[:wink|favorite]-slm-[:wink|favorite]->prospects
WHERE not(me = slm) and not(me--prospects)
RETURN count(*) AS prospectsWeight, prospects.attraction AS attraction, prospects.abo_id AS abo_id
ORDER BY count DESC, attraction DESC, abo_id DESC;
Pondération : 3
Pondération : 1
Pondération : 2
Pondération : 1
Pondération : 2
15. 15
Requête CYPHER : cas d’école
Djamel
Nelly
Celine
Marina
Thomas
Rudy
Mathieu
David
Pauline
Anne
Caroline
Siti
Sabrina
START me = node:node_auto_index(abo_id='170021316')
MATCH me-[:wink|favorite]->she<-[:wink|favorite]-slm-[:wink|favorite]->prospects
WHERE not(me = slm) and not(me--prospects)
RETURN count(*) AS prospectsWeight, prospects.attraction AS attraction, prospects.abo_id AS abo_id
ORDER BY count DESC, attraction DESC, abo_id DESC;
Pondération : 3
Pondération : 1
Pondération : 2
Pondération : 1
Pondération : 2
16. 16
START me = node:node_auto_index(abo_id='170021316')
MATCH me-[:wink|favorite]->she<-[:wink|favorite]-slm-[:wink|favorite]->prospects
WHERE not(me = slm) and not(me--prospects)
RETURN count(*) AS prospectsWeight, prospects.attraction AS attraction, prospects.abo_id AS abo_id
ORDER BY count DESC, attraction DESC, abo_id DESC;
Requête CYPHER : cas d’école
Djamel
Nelly
Celine
Marina
Thomas
Rudy
Mathieu
David
Pauline
Anne
Caroline
Siti
Sabrina
Pondération : 3
Pondération : 1
Pondération : 2
Pondération : 1
Pondération : 2
18. 18
Le problème : « les serials »
Les serial dragueurs
* Attention, cet homme se cache dans la salle * La fameuse Pauline, mais elle n’est pas dans la
salle…
Les serial bombes
98% des personnes dans notre
dataset ont réalisé moins de 107
interactions
Le max est à 2640 avec une
moyenne à 304 sur les 2 derniers %
98% des personnes dans notre
dataset ont reçu moins de 78
sollicitations
Le max est à 955 avec une
moyenne à 176 sur les 2 derniers %
19. 19
Monsieur X est un membre :
Il a eu 43 interactions avec d’autres membres
Cela le connecte à 3 265 « sender like me » distincts
mais en comptant la pondération cela représente 8 764 chemins différents (donc
autant de nœuds)
Au final il dispose de 37 467 « prospects »
Mais en prenant la pondération cela représente 2 050 395
Quelques chiffres
De part la nature connectée des graphes, les nœuds les plus connectés
influent de façon significative sur les nœuds les moins connectés dans notre
contexte
21. 21
START me = node:node_auto_index(abo_id='170021316')
MATCH me-[:wink|favorite]->she<-[:wink|favorite]-slm
WHERE not(me = slm) AND slm.age > me.age -10 and slm.age < me.age + 5
AND slm.nbFavoriteOut + slm.nbWinkOut < 100
WITH me, slm, count(*) AS slmWeight
ORDER BY slmWeight DESC
LIMIT 20
MATCH slm-[:wink|favorite]->prospects
WITH me, prospects, sum(slmWeight) AS prospectsWeight
WHERE not(me--prospects)
RETURN prospectsWeight AS count, prospects.attraction AS attraction, prospects.abo_id AS abo_id
ORDER BY count DESC, attraction DESC, abo_id DESC
LIMIT 100;
Optimisations fonctionnelles [1/3]
Les filtres fonctionnels
Pour aller plus loin : Machine learning et/ou statistiques pour découvrir les features les plus représentatives
22. 22
Optimisations fonctionnelles [2/3]
Filtrer les serial dragueurs
Pour aller plus loin : Modélisation optimisée avec une notion de nœud éligible/non éligible et des requêtes basées
sur la connectivité
START me = node:node_auto_index(abo_id='170021316')
MATCH me-[:wink|favorite]->she<-[:wink|favorite]-slm
WHERE not(me = slm) AND slm.age > me.age -10 and slm.age < me.age + 5
AND slm.nbFavoriteOut + slm.nbWinkOut < 100
WITH me, slm, count(*) AS slmWeight
ORDER BY slmWeight DESC
LIMIT 20
MATCH slm-[:wink|favorite]->prospects
WITH me, prospects, sum(slmWeight) AS prospectsWeight
WHERE not(me--prospects)
RETURN prospectsWeight AS count, prospects.attraction AS attraction, prospects.abo_id AS abo_id
ORDER BY count DESC, attraction DESC, abo_id DESC
LIMIT 100;
23. 23
Optimisations fonctionnelles [3/3]
N’utiliser que les « senders like me » les plus
représentatifs
Pour aller plus loin : Valeur à affiner en fonction des données et à mettre en musique avec l’ensemble des
optimisations fonctionnelles
START me = node:node_auto_index(abo_id='170021316')
MATCH me-[:wink|favorite]->she<-[:wink|favorite]-slm
WHERE not(me = slm) AND slm.age > me.age -10 and slm.age < me.age + 5
AND slm.nbFavoriteOut + slm.nbWinkOut < 100
WITH me, slm, count(*) AS slmWeight
ORDER BY slmWeight DESC
LIMIT 20
MATCH slm-[:wink|favorite]->prospects
WITH me, prospects, sum(slmWeight) AS prospectsWeight
WHERE not(me--prospects)
RETURN prospectsWeight AS count, prospects.attraction AS attraction, prospects.abo_id AS abo_id
ORDER BY count DESC, attraction DESC, abo_id DESC
LIMIT 100;
24. 24
Optimisations techniques [1/4]
Utilisation des sous requêtes (WITH)
Pour aller plus loin : Evidemment comme chaque optimisation technique, elle est liée à une situation donnée et
peut donc dans certains cas être inopérante voire pénalisante
START me = node:node_auto_index(abo_id='170021316')
MATCH me-[:wink|favorite]->she<-[:wink|favorite]-slm
WHERE not(me = slm) AND slm.age > me.age -10 and slm.age < me.age + 5
AND slm.nbFavoriteOut + slm.nbWinkOut < 100
WITH me, slm, count(*) AS slmWeight
ORDER BY slmWeight DESC
LIMIT 20
MATCH slm-[:wink|favorite]->prospects
WITH me, prospects, sum(slmWeight) AS prospectsWeight
WHERE not(me--prospects)
RETURN prospectsWeight AS count, prospects.attraction AS attraction, prospects.abo_id AS abo_id
ORDER BY count DESC, attraction DESC, abo_id DESC
LIMIT 100;
25. 25
Et ça donne quoi ?
0.0001
0.001
0.01
0.1
1
0 100 200 300 400 500 600 700 800 900
ExecutionTime(s)
Number of prospects
26. 26
Optimisations techniques [2/4]
Utilisation des « unmanaged extension »
Pour aller plus loin : Analyse profonde du plan d’éxecution pour comprendre qu’elle est la partie à optimiser / à
débrancher
29. 29
Optimisations techniques [4/4]
Optimisation des caches
Pour aller plus loin : Nécessite d’avoir une vision assez fine de la modélisation mais aussi des requêtes d’accès à
la donnée (en collaboration avec les développeurs et pas en amont des développements)
Cache de nœuds
Cache de relations
Cache de clefs d’index
Cache de valeurs d’index
Cache des propriétés
...
30. 30
Meetic est une boîte innovante (et si vous êtes célibataire elle peut vous aider )
PHP ce n’est pas si mal (contrairement à ce dont je me souvenais…)
Les graphes et Neo4j apportent une vraie réponse dans le traitement des
données fortement connectées
Les datastores NoSql sont des produits puissants mais pas magiques
Il existe des données encore vierges (ou presque) d’analyse et elles
représentent des potentiels gisements de valeur donc open « your mind and
kiss the graph»
Ce qu’il faut retenir