2017-05-26 1 views
2

Dans mon application Neo4j/SDN 4, toutes mes requêtes Cypher sont basées sur des ID Neo4j internes.Neo4j SDN 4 Performance GraphId vs Index

Ceci est un problème car je ne peux pas compter sur ces ID dans mes URL d'application Web. Neo4j peut réutiliser ces identifiants, donc il y a de fortes chances pour qu'à l'avenir, sous le même ID, nous trouvions un autre nœud.

J'ai essayé de ré-implémenter cette logique basée sur la solution suivante: Using the graph to control unique id generation mais j'ai noté une dégradation des performances de requête.

D'un point de vue théorique, si une requête Cypher basée sur la propriété avec @Index(unique = true, primary = true)

par exemple:

@Index(unique = true, primary = true) 
private Long uid; 

entity.uid = {someId} 

travail avec les mêmes performances que une requête Cypher qui est basée sur interne Neo4j ID:

id(entity) = {someId} 

MISE À JOUR

C'est :schema sortie:

Indexes 
    ON :BaseEntity(uid) ONLINE 
    ON :Characteristic(lowerName) ONLINE 
    ON :CharacteristicGroup(lowerName) ONLINE 
    ON :Criterion(lowerName) ONLINE 
    ON :CriterionGroup(lowerName) ONLINE 
    ON :Decision(lowerName) ONLINE 
    ON :FlagType(name) ONLINE (for uniqueness constraint) 
    ON :HAS_VALUE_ON(value) ONLINE 
    ON :HistoryValue(originalValue) ONLINE 
    ON :Permission(code) ONLINE (for uniqueness constraint) 
    ON :Role(name) ONLINE (for uniqueness constraint) 
    ON :User(email) ONLINE (for uniqueness constraint) 
    ON :User(username) ONLINE (for uniqueness constraint) 
    ON :Value(value) ONLINE 

Constraints 
    ON (flagtype:FlagType) ASSERT flagtype.name IS UNIQUE 
    ON (permission:Permission) ASSERT permission.code IS UNIQUE 
    ON (role:Role) ASSERT role.name IS UNIQUE 
    ON (user:User) ASSERT user.email IS UNIQUE 
    ON (user:User) ASSERT user.username IS UNIQUE 

Comme vous pouvez le voir, j'ai un index sur :BaseEntity(uid)

BaseEntity est une classe de base dans ma hiérarchie de l'entité, par exemple:

@NodeEntity 
public abstract class BaseEntity { 

    @GraphId 
    private Long id; 

    @Index(unique = false) 
    private Long uid; 

    private Date createDate; 

    private Date updateDate; 

... 

} 

@NodeEntity 
public class Commentable extends BaseEntity { 
... 
} 

@NodeEntity 
public class Decision extends Commentable { 

    private String name; 

} 

Will cet index uid être utilisé quand je cherche par exemple pour (d:Decision) WHERE d.uid = {uid}?

PROFIL resuls - ID interne vs propriété indexée

requête basée sur ID interne

PROFILE MATCH (parentD)-[:CONTAINS]->(childD:Decision) 
WHERE id(parentD) = 1474333 
MATCH (childD)-[relationshipValueRel1475199:HAS_VALUE_ON]-(filterCharacteristic1475199) 
WHERE id(filterCharacteristic1475199) = 1475199 
WITH relationshipValueRel1475199, childD 
WHERE ([1, 19][0] <= relationshipValueRel1475199.value <= [1, 19][1]) 
WITH childD 
MATCH (childD)-[relationshipValueRel1474358:HAS_VALUE_ON]-(filterCharacteristic1474358) 
WHERE id(filterCharacteristic1474358) = 1474358 
WITH relationshipValueRel1474358, childD 
WHERE (ANY (id IN ['Compact'] WHERE id IN relationshipValueRel1474358.value)) 
WITH childD 
MATCH (childD)-[relationshipValueRel1475193:HAS_VALUE_ON]-(filterCharacteristic1475193) 
WHERE id(filterCharacteristic1475193) = 1475193 
WITH relationshipValueRel1475193, childD 
WHERE (ANY (id IN ['16:9', '3:2', '4:3', '1:1'] 
WHERE id IN relationshipValueRel1475193.value)) 
WITH childD 
OPTIONAL MATCH (childD)-[vg:HAS_VOTE_ON]->(c) 
WHERE id(c) IN [1474342, 1474343, 1474340, 1474339, 1474336, 1474352, 1474353, 1474350, 1474351, 1474348, 1474346, 1474344] 
WITH childD, vg.avgVotesWeight as weight, vg.totalVotes as totalVotes 
WITH * MATCH (childD)-[ru:CREATED_BY]->(u:User) 
WITH ru, u, childD , toFloat(sum(weight)) as weight, toInt(sum(totalVotes)) as totalVotes 
ORDER BY weight DESC 
SKIP 0 LIMIT 10 
RETURN ru, u, childD AS decision, weight, totalVotes, 
[ (parentD)<-[:DEFINED_BY]-(entity)<-[:COMMENTED_ON]-(comg:CommentGroup)-[:COMMENTED_FOR]->(childD) | {entityId: id(entity), types: labels(entity), totalComments: toInt(comg.totalComments)} ] AS commentGroups, 
[ (parentD)<-[:DEFINED_BY]-(c1)<-[vg1:HAS_VOTE_ON]-(childD) | {criterionId: id(c1), weight: vg1.avgVotesWeight, totalVotes: toInt(vg1.totalVotes)} ] AS weightedCriteria, 
[ (parentD)<-[:DEFINED_BY]-(ch1:Characteristic)<-[v1:HAS_VALUE_ON]-(childD) WHERE NOT ((ch1)<-[:DEPENDS_ON]-()) | {characteristicId: id(ch1), value: v1.value, totalHistoryValues: toInt(v1.totalHistoryValues), description: v1.description, valueType: ch1.valueType, visualMode: ch1.visualMode} ] AS valuedCharacteristics 

sortie PROFIL:

Version Cypher: CYPHER 3.1, planificateur: COST, exécution: INTERPRETE . 350554 coups totaux db en 238 ms.

enter image description here

requête basée sur la propriété indexée uid

PROFILE MATCH (parentD)-[:CONTAINS]->(childD:Decision) 
WHERE parentD.uid = 61 
MATCH (childD)-[relationshipValueRel1475199:HAS_VALUE_ON]-(filterCharacteristic1475199) 
WHERE filterCharacteristic1475199.uid = 15 
WITH relationshipValueRel1475199, childD 
WHERE ([1, 19][0] <= relationshipValueRel1475199.value <= [1, 19][1]) 
WITH childD 
MATCH (childD)-[relationshipValueRel1474358:HAS_VALUE_ON]-(filterCharacteristic1474358) 
WHERE filterCharacteristic1474358.uid = 10 
WITH relationshipValueRel1474358, childD 
WHERE (ANY (id IN ['Compact'] WHERE id IN relationshipValueRel1474358.value)) 
WITH childD 
MATCH (childD)-[relationshipValueRel1475193:HAS_VALUE_ON]-(filterCharacteristic1475193) 
WHERE filterCharacteristic1475193.uid = 14 
WITH relationshipValueRel1475193, childD 
WHERE (ANY (id IN ['16:9', '3:2', '4:3', '1:1'] 
WHERE id IN relationshipValueRel1475193.value)) 
WITH childD 
OPTIONAL MATCH (childD)-[vg:HAS_VOTE_ON]->(c) 
WHERE c.uid IN [26, 27, 24, 23, 20, 36, 37, 34, 35, 32, 30, 28] 
WITH childD, vg.avgVotesWeight as weight, vg.totalVotes as totalVotes 
WITH * MATCH (childD)-[ru:CREATED_BY]->(u:User) 
WITH ru, u, childD , toFloat(sum(weight)) as weight, toInt(sum(totalVotes)) as totalVotes 
ORDER BY weight DESC 
SKIP 0 LIMIT 10 
RETURN ru, u, childD AS decision, weight, totalVotes, 
[ (parentD)<-[:DEFINED_BY]-(entity)<-[:COMMENTED_ON]-(comg:CommentGroup)-[:COMMENTED_FOR]->(childD) | {entityId: id(entity), types: labels(entity), totalComments: toInt(comg.totalComments)} ] AS commentGroups, 
[ (parentD)<-[:DEFINED_BY]-(c1)<-[vg1:HAS_VOTE_ON]-(childD) | {criterionId: id(c1), weight: vg1.avgVotesWeight, totalVotes: toInt(vg1.totalVotes)} ] AS weightedCriteria, 
[ (parentD)<-[:DEFINED_BY]-(ch1:Characteristic)<-[v1:HAS_VALUE_ON]-(childD) WHERE NOT ((ch1)<-[:DEPENDS_ON]-()) | {characteristicId: id(ch1), value: v1.value, totalHistoryValues: toInt(v1.totalHistoryValues), description: v1.description, valueType: ch1.valueType, visualMode: ch1.visualMode} ] AS valuedCharacteristics 

version Cypher: CYPHER 3.1, planificateur: COST, exécution: INTERPRETE. 671326 total des hits db en 426 ms.

enter image description here

Est-il possible d'améliorer la performance basée sur uid?

Répondre

5

Vous avez raison de ne pas utiliser les identifiants internes Neo4j dans les URL web, car ils peuvent être réutilisés après la suppression du noeud, etc.Du point de vue des performances, l'ID interne est aussi rapide que possible - il s'agit en fait d'un décalage dans le fichier avec les enregistrements de noeud/relation (vous auriez pu remarquer qu'il s'agit de 2 séquences d'ID distinctes, vous pouvez avoir un noeud avec ID = z et relation avec le même id = x).

Toute utilisation d'un index doit être plus lente, car la base de données recherche d'abord l'index, obtient l'ID interne et lit ensuite l'enregistrement de noeud.

Cependant, pour la grande majorité des applications , la différence de performance est négligeable. - sera probablement beaucoup plus petit que la latence réseau ou le surdébit général OGM.

Si vous voyez une différence notable

  • vérifier les index existe dans la base de données (par exemple :schema dans le navigateur Neo4j)
  • tour sur l'exploitation forestière et de vérifier vos requêtes ont l'étiquette correcte (régler le niveau info pour org.neo4j.ogm)
  • si l'index existe et requête contient l'étiquette droite puis utilisez PROFILE pour vérifier le plan de requête

MISE À JOUR

Oui, l'index sera utilisé pour les requêtes comme:

MATCH (d:Decision) WHERE d.uid = {uid} ... 

qui devrait se générés par

session.load(Decision.class, uid) 

si votre index est primaire ou findByUid sur DecisionRepository.

Prenez garde que l'indice pourrait ne pas être utilisé lorsque la clause where apparaît au milieu de la requête:

... 
WITH x 
MATCH (x)-[...]-(d) WHERE d.uid = {uid} ... 

Cela dépend du plan de requête et vous devez utiliser PROFILE pour enquêter sur ce sujet.

+0

Merci pour votre réponse. En ce moment j'essaye de venir avec une approche comment refactoriser mon système afin d'éviter le problème avec la réutilisation d'identification et je vois le schéma suivant - dans mes URL de Web j'emploierai l'uid de substitution. Si id n'est pas nécessaire pour être placé dans l'url web, j'utiliserai l'identifiant interne Neo4j. Ainsi, l'uuid de substitution ne sera utilisé que dans les urls web sinon dans tous les autres endroits sur le client je vais utiliser l'identifiant interne Neo4j. Est-ce que ça fait du sens ? – alexanoid

+0

Avoir 2 façons d'accéder aux entités par ID peut compliquer inutilement les choses. J'irais avec uuid personnalisé seulement. Comme je l'ai dit, l'index est * rapide *, la différence entre la recherche d'index interne et d'index sera inférieure d'un ordre de grandeur à la latence du réseau ou à l'overhead général OGM. –

+0

J'ai une utilisation très intensive de différents identifiants dans une seule requête (https://stackoverflow.com/questions/43824894/neo4j-cypher-query-structure-and-performance-optimization) donc la dégradation des performances avec une approche basée sur un UID pur a été perceptible par l'oeil humain. – alexanoid