I know that performance optimization isn't a top priority for most Drupal devs due to the small number of affected users, nevertheless I would like to point out the issues we've discovered using Nodereferences on a site with 100K+ nodes:
1. NR autocomplete takes a long time (> 10 sec), locking up the browser UI, causing the request to sometimes time out.
2. Saving nodes with NRs takes a long time and consumes huge amounts of memory.
Both issues are caused by _nodereference_potential_references() being too inflexible limiting query results:
(1) is caused by allowing exact string or unanchored substring matches, but not a substring limited to the start of a string. Unanchored title substring matching is simply not possible with 100K+ nodes, because the existing database indexes can't be used. A solution would be to add substring matching to the start of a string and allow admins to select the search method in the field settings.
(2) is caused by un-scalable programming in nodereference_field('validate'), where simply all possible nodes are queried (in our case the resulting array contained more than 100K entries). The solution would be to modify _nodereference_potential_references() to accept a keyed array, which would allow us to specify the database field to search for: array('nid' => $item['nid']) (sort of a really simple query builder). This would allow us to lookup exact values.
I know it's pretty late in the 2.x dev cycle, but since this would require an API change to _nodereference_potential_references() I would first like to get some feedback from the devs before actually starting to create a patch.
| Comment | File | Size | Author |
|---|---|---|---|
| #29 | 298651-nodereference-validateperformance.patch | 1.85 KB | andrewlevine |
| #24 | nodereference.module_5.8.patch | 1.14 KB | KingMoore |
| #22 | noderef_performance-298651-22.patch | 25.85 KB | smk-ka |
| #21 | noderef_performance-298651-21.patch | 26.03 KB | smk-ka |
| #19 | noderef_performance-298651-18.patch | 26.98 KB | smk-ka |
Comments
Comment #1
moshe weitzman commentedI think these have been mentioned before. I think #1 is a great idea, and we are not too late for a patch there. #2 looks good too though I'm not sure I fully grok it. In any case, we are not too late for performance fixes.
Comment #2
yched commented1) Proposed solution sounds OK, but this indeed needs to be a setting for noderef fields. Unanchored title match is precious in many cases.
Patch welcome :-)
2) I don't think I get your proposition either.
For noderef fields in 'standard mode' (refenrenceable nodes defined by node type checkboxes), it's true that validation could be smarter, we only need to get the node type of a given nid - simple query indeed.
For noderef fields in 'views' mode, you can't avoid building the query and retrieving the nodes.
I'll try to come up with smthing.
Comment #3
yched commentedAbout 2) :
What do you think of this ?
Comment #4
nonsiesubscribe - I'm having the same issues as well
Comment #5
smk-ka commentedA little addendum first: The autocomplete browser lock-up described in (1) is actually caused by a missing LIMIT clause in the references query. While the matching operator is certainly also an issue, a few hundred kilobytes large JSON response that needs to be transmitted and processed on the client side is the real culprit. Trivial solution: allow setting a limit.
So, this patch is what I've come up so far. It changes _nodereference_potential_references() to accept an additional column and operator (but not as an array, as initially suggested), and a numerical limit.
Unlike yched said, the column to filter on actually may also be used in 'views' mode, it was just tied to the 'title' column until now.
The operator is influenced by Views' operators, however only 'equals', 'contains', and 'starts' are supported.
The UI to allow admins to select the operator has not been implemeted (yet).
Comment #6
smk-ka commentedJust discovered that Userreference uses a slightly different approach (should have looked at it earlier), by letting you directly specify the uid (nid in out case) as optional function argument. This seems a bit cleaner than passing in the column name, as you hardly ever will use anything else than title or nid.
Comment #7
moshe weitzman commentedCNW based on last comment.
Comment #8
yched commentedMight be worth checking out #308215: Creates malformed SQL and does not scale (maybe it's just a plain dupe - I'm not where I can easily check this)
Comment #9
smk-ka commentedUpdated patch:
- Changed $column argument to $nid, just like in userreference.module.
- Switched the order of arguments and added constants for the $op argument instead of strings. This gives us backwards compatibility for free (formerly, $exact_match was boolean, now FALSE maps to NODEREFERENCE_CONTAINS, TRUE maps to NODEREFERENCE_EQUALS).
- Added widget settings form which allows you to choose the matching operator (when using the autocomplete widget).
Comment #10
moshe weitzman commentedThis saved performance for my large site. You are right that the LIMIT is even more important that the 'starts with' operator ... I did a code review as well and it looks good. You might uppercase 'Contains' and 'Starts with' in the #options but thats real nitpicky stuff. Well done.
Comment #11
markus_petrux commentedsubscribing
Comment #12
robertgarrigos commentedsubscribing
Comment #13
yched commentedNice work !
Updated patch, with a few ediits / fixes :
- better namespacing for the 'match mode' constants - also start from 1, beacause 0 is painful with isset()s
- got rid of the _nodereference_translate_op() function, this does not really deserve to be abstracted out IMO
- renamed $op to $match, $op is used for 'à la hook_nodeapi' operators (you know, the ones that just got dumped in D7 :-) )
- I don't think we really want the 'exact title match' option available as a widget setting. Who would want that ?
- Preserve the setting when switching widget type back and forth.
- $limit was not working in Views mode
- validation failed if submitting without the [nid:x] part (JS disabled) in Views mode
Also (detail), I think the user-facing text in for the widget setting could be a little simplified IMO. 'Operator' is a little complex.
The only remaining gripe I have is that when validating a multiple noderef fields, the patch performs one query per nid to check. So while the patch makes it better for sites with many nodes, it also degrades it for all sites.
It would be cool to have _nodereference_potential_references() accept an array of $nids instead of a $nid.
Problem is that Views doesn't come with a filter for node.nid that lets us do that. We could add our own (we already do something like that for userref autocomplete), based on views_handler_filter_in_operator, but then it would also be available in Views UI, which we wouldn't want here.
Earl suggested we add that clause in the display plugin's query() method. Could be worth investigating
We'll also need to port this to userref, probably. I'd rather keep the code for those two modules as 'duplicate' as possible.
Comment #14
yched commentedThe code still needs some polishing, but the attached patch does as described above :
- move additional filtering clauses into direct add_where() calls in the plugin's query()
- handles arrays of nids
Still need to apply this to userref.
Comment #15
smk-ka commented- Removed "Exact match" as a selectable option for autocomplete. Trying to guess exact node titles indeed feels like playing lottery :P
- Added a sanity check to content_plugin_display_references::query() to catch old (1.x) $match parameters and route them to the default value (CONTAINS).
- Replaced placeholder generation code with db_placeholder() calls (2 times).
Still to-do: better wording on settings page.
Comment #16
yched commented- Thks for the update.
"sanity check to catch old (1.x) $match parameters"
I'm not sure what you mean ?
- Same patch as #15, mirrored to userref as well. I changed $nids to $ids in the functions signatures to minimize diffs between the modules.
This raises the question of the NODEREFERENCE_MATCH_* constants. Both modules will use the same constants and values. Currently, userref uses the constants defined in noderef, but we don't want to have userref.module depend on noderef.module. Defining them in both modules sounds awkward. Maybe content.module should define them instead, just like it holds the views display plugin. Still thinking.
Comment #17
yched commentedPrevious patch was missing a ';'
Comment #18
yched commentedPolished patch :
- About the constants issue mentioned in #16 : I actually dropped the constants in favor of string literals
- further unify bits of code in noderef and userref modules : non-views query builder in [node|user]reference_potential_references_standard()
- polish the Views plugin code as per Earl's advice on IRC
- proposed wording for the widget settings :
Suggestions / enhancements welcome.
This should be ready now. If no-one objects, I'll commit this shortly.
Comment #19
smk-ka commented** CROSS POST **
"I'm not sure what you mean ?"
I was referring to the added
isset($match_clauses[$match])part, which checks that the option actually exists and prevents a notice otherwise. It was once in your patch, on a second occasion it was missing.Updated patch:
- Moved constants to content.module as proposed, changing names to CONTENT_REFERENCE_* to follow content.module's schema. I've left out _MATCH to shorten the names a bit, change back if you're unhappy with it.
- Fixed $item['nid'] should actually be $item['uid'] for userreferences.
- Reset error message in hook_field() for userreferences.
Comment #20
moshe weitzman commentedI had a quick read and the only parts that raised an eyebrow are
$view->display_handler->set_option('use_pager', TRUE);and the plugin definition which also enables the pager. Is this needed? In typical views execution, pagers are very expensive on big sites since they trigger a count query. Perhaps thats not applicable here.Comment #21
smk-ka commentedRerolled patch from #18 with the two userreference-related fixes from #19 incorporated.
@moshe
It's very well possible that Views has changed since this patch was first written. I'll investigate whether limiting the result set still requires use_pager.
Comment #22
smk-ka commentedDid some testing with use_pager removed and everything seems to be working ok.
Comment #23
yched commentedMy bad, I added the pager stuff when trying to make $limit work in Views mode, and didn't realize it was not actually needed.
Committed. Thanks guys !
Comment #24
KingMoore commentedhere is a quick fix patch for D5, in case anyone needs it.
Comment #25
yched commentedPatch in #24 is not good.
It just makes sure the node exists, not that it is valid for the nodereference field (list of 'referenceable' nodes, defined on the field settings form)
If users enter a 'real' node name and skips autocompletion, the input will be accepted even if the node is not valid.
This is just a warning, not an invitation for someone to tackle a D5 backport. I'm personally not planning to spend time on checking / reviewing a D5 patch.
Comment #26
KingMoore commentedD5 Related:
yched, it fixes the memory "leak" which was completely taking down our server w/128mb of ram on node save. so, the patch may help someone even if it's not perfect. however, thanks for pointing that out. I didn't even realize a nodereference field would work if you just typed a name w/out the autocomplete [nid:###] bit.
I tested that patch under several scenarios and it seemed to work the same as w/out the patch. I did not test just typing in a node name w/out [nid:####]. Could you explain to me how a nodereference field is supposed to perform in that situation?
Comment #27
Anonymous (not verified) commentedAutomatically closed -- issue fixed for two weeks with no activity.
Comment #28
sunThis patch fixes a major performance issue we experienced in 5.x originally and I would really like to see it in 5.x, too (partially, if necessary).
Comment #29
andrewlevine commentedHere is an improved version of KingMoore's patch (#24) that does validates node type and uses a simple select statement instead of node_load
Comment #30
alfaguru commentedIt ought to be possible to use the view to validate a selection, by retrieving the query from the view then adding a where clause for the node id(s), something like "node.nid IN(nnn, nnn)". It should work though I haven't tried it.
Comment #31
finex commentedWhat about Drupal 6? Is this patch useful for D6 too?
Comment #32
yched commentedCCK D6 has the changes already.
Comment #33
Mesdag commentedThe suggested patch by andrewlevine contains a bug on r129. This condition is also FALSE for new nodereference fields, this inhibits you from creating new node reference fields.
Comment #34
karens commentedToo old now, way past the time when we will add features to D5 version.