Neo4j.rb - index paginated with Kaminari too slow - kaminari

Here is my controller (index is sorted):
def index
if params[:limit]
#bisacs = Bisac.order(:bisac_code).page(params[:page]).per(params[:limit])
else
#bisacs = Bisac.order(:bisac_code).page(params[:page])
end
end
Here are the queries executed (too many in my opinion and too slow, 6-7 seconds for a first page or to navigate to he next / last one:
Started GET "/bisacs" for 127.0.0.1 at 2015-08-11 13:16:33 -0400
Processing by BisacsController#index as HTML
Rendered home/_main_links.html.erb (0.2ms)
CYPHER 358ms MATCH (result_bisac:`Bisac`) RETURN count(result_bisac) AS result_bisac
CYPHER 389ms MATCH (result_bisac:`Bisac`) RETURN result_bisac ORDER BY result_bisac.bisac_code SKIP {skip_0} LIMIT {limit_25} | {:skip_0=>0, :limit_25=>25}
CYPHER 119ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24319299}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24319299}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24320808}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24320808}
CYPHER 120ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24319262}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24319262}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24320349}
CYPHER 120ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24320349}
CYPHER 120ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24318456}
CYPHER 119ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24318456}
CYPHER 119ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24320054}
CYPHER 116ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24320054}
CYPHER 119ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24321703}
CYPHER 119ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24321703}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24319503}
CYPHER 117ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24319503}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24321755}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24321755}
CYPHER 116ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24319313}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24319313}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24321376}
CYPHER 119ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24321376}
CYPHER 119ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24321021}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24321021}
CYPHER 123ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24319280}
CYPHER 119ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24319280}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24318845}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24318845}
CYPHER 122ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24320822}
CYPHER 119ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24320822}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24318841}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24318841}
CYPHER 119ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24321956}
CYPHER 117ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24321956}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24319031}
CYPHER 119ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24319031}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24320070}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24320070}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24322195}
CYPHER 116ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24322195}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24319124}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24319124}
CYPHER 119ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24322258}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24322258}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24318767}
CYPHER 121ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24318767}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24320583}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24320583}
CYPHER 118ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24319086}
CYPHER 117ms MATCH n WHERE (ID(n) = {ID_n}) WITH n RETURN ID(n) | {:ID_n=>24319086}
Rendered bisacs/index.html.erb within layouts/application (6722.1ms)
Completed 200 OK in 7096ms (Views: 7095.5ms)
How this can be improved? Why these 25 nodes are fetched one by one?
To retrieve all 3983 codes in the Neo4j console takes only 1 second or so:
MATCH (result_bisac:`Bisac`) RETURN result_bisac ORDER BY result_bisac.bisac_code;
Returned 3983 rows in 1064 ms, displaying first 1000 rows.

Hrmm, ok, so this is another somewhat embarrassing answer ;)
If you put the following in your config (either in config/environments/development.rb or config/application.rb) it should fix it:
config.neo4j._active_record_destroyed_behavior = true
Explination: The gem implements the ActiveNode#exists? method a bit differently than the ActiveRecord#exists? method. In ActiveRecord it just does a check to see if the object was destroyed at some point in memory. In ActiveNode we were doing a check to the actual database (and maybe we need query caching at some point).
So, long story short, we didn't want to introduce this change right away because it's a breaking change, so I put in a configuration variable which fixes it for now. The configuration variable shouldn't be neccessary in version 6.0

I commented the following first 3 lines in the tag in the view and speed become acceptable, around one second:
<% #bisacs.each do |bisac| %>
<tr>
<td><%#= link_to 'Show', bisac %></td>
<td><%#= link_to 'Edit', edit_bisac_path(bisac) %></td>
<td><%#= link_to 'Destroy', bisac, method: :delete, data: { confirm: 'Are you sure?' } %></td>
<td><%= bisac.bisac_code%></td>
<td><%= bisac.bisac_value%></td>
</tr>
<% end %>
However adding a link back to "show" makes the page rendering very slow again:
<% #bisacs.each do |bisac| %>
<tr>
<td><%= link_to bisac.bisac_code, bisac%></td>
<td><%= bisac.bisac_value%></td>
</tr>
<% end %>

Related

Neo4j: Find nodes with property name that contains string (in the property name, not the property value)

Is there a way to find all nodes with properties that have a certain string?
Eg here with "ID":
match (n) where exists( n[".*"+"ID"]) return n
(this does not work).
Thanks!
This will give you just the keys.
MATCH (n) WHERE ANY(x IN KEYS(n) WHERE x =~".*ID") RETURN n, KEYS(n) AS myKeys
This will give you just the values.
MATCH (n) WHERE ANY(x IN KEYS(n) WHERE x =~".*ID")
RETURN n, [x IN KEYS(n) WHERE x =~".*ID" | n[x]] AS myValues
If you have apoc, this will give you the keys and the values.
MATCH (n) WHERE ANY(x IN KEYS(n) WHERE x =~".*ID")
WITH n, [x IN KEYS(n) WHERE x =~".*ID" | x] AS myKeys
RETURN id(n) AS nodeId, apoc.map.submap(n, myKeys) as submap

Shortest paths excluding one node

I am working on a query on the movie database to check the shortest paths between n nodes. In this simplified example, we want all the shortest paths between 2 movies:
match p=allShortestPaths((n)-[*]-(m)) where id(n) = 87 and id(m) = 121
return p;
Now I want to have all the shortest paths that don't include Keanu Reeves in it.
I tried this:
match p=allShortestPaths((n)-[*]-(m)) where id(n) = 87 and id(m) = 121 and NONE(n in nodes(p) where n.name = "Keanu Reeves")
return p;
This however takes an eternity to load, even after I have indexed the name field of Person...
Then In tried the following:
match p=allShortestPaths((n)-[*]-(m)) where id(n) = 87 and id(m) = 121
with p WHERE NONE(n in nodes(p) where n.name = "Keanu Reeves")
return p;
This however gives me no results. I misinterpreted this by thinking it would just return those paths which don't have Keanu Reeves in between:
(no changes, no records)
I tested if I could get only those with Keanu Reeves in between with the any() function. This works perfectly:
match p=allShortestPaths((n)-[*]-(m)) where id(n) = 87 and id(m) = 121
with p WHERE ANY(n in nodes(p) where n.name = "Keanu Reeves")
return p;
What is the best approach to tackle this? I must say that my production query is way more complex than this, but it all boils down to this problem. It has to be a performant solution.
The problem is that if one of the nodes from the path does not have the property name, then the entire check will not be passed.
So or do we check for the existence of a property:
match p=allShortestPaths((n)-[*]-(m))
where
n.title = 'The Replacements' and
m.title = 'Speed Racer' and
NONE(n in nodes(p) where EXISTS(n.name) and n.name = "Keanu Reeves")
return p
Or use the COALESCE function:
match p=allShortestPaths((n)-[*]-(m))
where
n.title = 'The Replacements' and
m.title = 'Speed Racer' and
NONE(n in nodes(p) where COALESCE(n.name, '') = "Keanu Reeves")
return p

Sum up counts in Neo4j

I have directors who have directed films. These films have genres and some actors starring. I want to find the films by a directed sorted by the sum of (no of genres of the film, no of actors starring in the film).
MATCH(n) -- (f:Film)
WHERE n.name = "Steven Spielberg"
MATCH (f) - [r] -> (g:Genre)
OPTIONAL MATCH (f) - [r2] -> (s:Starring)
WITH n, f, count(r) as gc, count(r2) as sc
RETURN n, f, gc, sc
ORDER BY gc DESC
This works but now I want to sum gc and sc and order films by the result. How does one do that?
I think you can just add the sum you want in your RETURN statement and then order results by it:
MATCH(n) -- (f:Film)
WHERE n.name = "Steven Spielberg"
MATCH (f) - [r] -> (g:Genre)
OPTIONAL MATCH (f) - [r2] -> (s:Starring)
WITH n, f, count(r) as gc, count(r2) as sc
RETURN n, f, gc, sc, gc+sc AS S
ORDER BY S DESC

Cypher Query fail while defining the relationship count

Neo4j Version: 3.0.4
Objective of the below query is to eliminate duplicate bus service and bustop in a path, it work fine if i didn't provide the relationship count -[r:CONNECTSWITH]-> but if the relationship count defined -[r:CONNECTSWITH*..3]-> ,then its throwing
Key not found: r
Working:
OPTIONAL MATCH p=(o:PORT{name:"busstop1"})-[r:CONNECTSWITH]->(d:PORT{name:"busstop2"})
WHERE ALL(r1 IN rels(p)
WHERE 1 = size(filter(r2 IN rels(p) WHERE (r1.service = r2.service))))
AND ALL(n IN nodes(p) WHERE 1 = size(filter(m IN nodes(p) WHERE id(m) = id(n))))
RETURN p
LIMIT 10
Not Working:
OPTIONAL MATCH p=(o:PORT{name:"busstop1"})-[r:CONNECTSWITH*..3]->(d:PORT{name:"busstop2"})
WHERE ALL(r1 IN rels(p)
WHERE 1 = size(filter(r2 IN rels(p) WHERE (r1.service = r2.service))))
AND ALL(n IN nodes(p) WHERE 1 = size(filter(m IN nodes(p) WHERE id(m) = id(n))))
RETURN p
LIMIT 10
Work around Solution:
OPTIONAL MATCH p=(o:PORT{name:"busstop1"})-[r:CONNECTSWITH*..3]->(d:PORT{name:"busstop2"})
WHERE ALL(r1 in rels(p)
WHERE 1 = size(filter(r2 IN rels(p) WHERE (r1.service = r2.service)))) =
ALL(n IN nodes(p) WHERE 1 = size(filter(m IN nodes(p) WHERE id(m) = id(n))))
AND ALL(r1 in rels(p)
WHERE 1 = size(filter(r2 IN rels(p) WHERE (r1.service = r2.service))))
RETURN p
LIMIT 10
Aside: This feels like a neo4j bug. If you are encountering this with the latest neo4j version, you may want to submit a neo4j issue.
As a possible workaround, since the query does not actually use the r identifier, try removing it from the query.

Neo4j rels.to_other method fails with error

I have an user speaking several languages.
I am writing a query to find out if the user speaks english.
I want to find out if the user speaks english by searching if a relationship exists between the user node and and the language node.
I am doing this using rels.to_other or rels.to_others. Both are failing as described below. And BTW what is the difference between the "to_other" and "to_others" methods?
irb(main):105:0> u
=> #<User uuid: "b1461edd-a574-44a7-9555-68659a361be2", created_at: Tue, 06 Oct 2015 16:21:26 +0000, name: "Levi Damian", type: "Regular", updated_at: Tue, 06 Oct 2015 16:21:26 +0000>
irb(main):106:0> l
=> #<Language uuid: "e388eb70-66b2-4b20-8f15-6ca11b34d03d", name: "English">
irb(main):107:0> u.rels.to_other(l)
CYPHER 802ms MATCH n, (n)-[r]-(p) WHERE (ID(n) = {ID_n}) RETURN r as result | {:ID_n=>38}
NoMethodError: CYPHER 125ms MATCH n WHERE (ID(n) = {ID_n}) RETURN n | {:ID_n=>38}
CYPHER 125ms MATCH n WHERE (ID(n) = {ID_n}) RETURN n | {:ID_n=>43}
CYPHER 125ms MATCH n WHERE (ID(n) = {ID_n}) RETURN n | {:ID_n=>38}
CYPHER 124ms MATCH n WHERE (ID(n) = {ID_n}) RETURN n | {:ID_n=>42}
CYPHER 128ms MATCH n WHERE (ID(n) = {ID_n}) RETURN n | {:ID_n=>38}
CYPHER 130ms MATCH n WHERE (ID(n) = {ID_n}) RETURN n | {:ID_n=>41}
CYPHER 124ms MATCH n WHERE (ID(n) = {ID_n}) RETURN n | {:ID_n=>38}
CYPHER 125ms MATCH n WHERE (ID(n) = {ID_n}) RETURN n | {:ID_n=>86}
CYPHER 124ms MATCH n WHERE (ID(n) = {ID_n}) RETURN n | {:ID_n=>38}
CYPHER 129ms MATCH n WHERE (ID(n) = {ID_n}) RETURN n | {:ID_n=>87}
undefined method `to_other' for #<Array:0x007fedc2d6b980>
from (irb):107
from /Users/levi/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/railties-4.2.4/lib/rails/commands/console.rb:110:in `start'
Why this error and how I can make it work?
rels immediately returns all relationships from that node to... everything. It returns an array, hence the error. It can be an extremely slow, memory intensive operation if you have a node with many relationships.
Assuming you have a languages association in your model, you can do u.languages.match_to(l).exists? instead. It will return a boolean and does not return any graph objects. You can also use blank? or empty?.

Resources