Improve performance and API consistency by introducing node_delete_multiple(). The use case in core is the admin/content/node page. Additional use cases are Views Bulk Operations, migration scripts, and devel_generate.

If you are deleting 50 nodes, this patch replaces 200 DELETE queries with 4 DELETE queries. It also saves 50 calls to node_access() which can lead to 100 SELECT queries if you are using a node access module. And there is more to be gained if do multiple for search_wipe() and term delete, and so on.

At the same time, I've modernized node_delete a bit. I removed its internal access control as thats already done by the menu system. API functions shouldn't have access control. Similarly I moved the watchdog() and drupal_set_message() calls to the submit handlers which keeps the node_delete cleaner.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Status: Needs review » Needs work

The last submitted patch failed testing.

catch’s picture

Very nice.

Looks like the node_load() needs to be moved before the deletes are run (and could presumably be node_load_multiple if so?). Also some debug left in the patch.

moshe weitzman’s picture

Status: Needs work » Needs review
FileSize
3.68 KB

Doh - thats why the node_load() was failing. I changed to node_load_multiple() and moved out of the loop. That saved another 49 queries. And I forgot that we saved 98 queries due to performing one cache_clear_all() instead of fifty. By my count, we are at about 445 saved queries.

moshe weitzman’s picture

FileSize
3.75 KB

Preserve same drupal_set_message() so tests are happy.

catch’s picture

Status: Needs review » Needs work
+/**
+ * Delete multiple nodes.
+ */

needs @param for $nids. Looks like node_delete() is missing it as well.

Apart from that and waiting on the test bot, this looks great.

moshe weitzman’s picture

Status: Needs work » Needs review
FileSize
3.84 KB

With those params documented.

mikeryan’s picture

I'm concerned about node relationships (whether through CCK related node fields, or module-specific relationships). What if a delete hook tries to reference a related node which was wiped out in the db_delete('node')? The answer, I believe, is that upward compatibility is not a goal and module maintainers will need to be sure their delete hooks gracefully deal with the situation, but at least we need to remember to document the change in behavior.

Another thing to document is that node_delete() will no longer do a drupal_set_message() - any module calling node_delete() and expecting a message to the user will need to roll its own.

node_load() here (unless the behavior is changed in D7) caches the nodes that are about to be deleted, which consumes gobs of memory when doing mass deletions. See #287063: node_delete "leaks memory" due to deleted nodes cached. Also note the suggestion there about reordering the deletion and the hook calls.

Thanks.

moshe weitzman’s picture

FileSize
4.82 KB

Mike - I think the referenced thing is a non-issue. nodeapi('delete') is passing in a full $node object that was made before the delete happenned on the node tables. Modules can take whatever action they deem necessary - they have all the info they need. This also addresses your other concern about order of operations for the delete queries.

Yes, I will document the dsm() and watchdog change on the module update page.

I added a $write_cache param to node_load_multiple which defaults to true but is set to false by node_delete_multiple. This should address most of the memory concerns.

catch’s picture

Why not just use the $reset parameter? That'll disable the static as well, saves the extra param, and it's not that likely the nodes will already have been loaded on the same request that they get deleted.

moshe weitzman’s picture

FileSize
5.84 KB

If I read it correctly, $reset just wipes the cache at the beginning of the function - that doesn't really help avoid the saving at the end. We want to avoid populating the global $node_cache since thats what causes memory bloat.

Seems like we should convert $node_cache to a drupal_static() and then the caller can reset cache before or after the call to node_delete_multiple(). Thats quite a bit cleaner. Patch attached.

catch’s picture

Hmm, good point. That's much nicer anyway though. Looks ready to me, but didn't run all tests on it and the bot is still catching up.

Status: Needs review » Needs work

The last submitted patch failed testing.

moshe weitzman’s picture

Status: Needs work » Needs review
FileSize
5.48 KB

I put back the $reset param since I think it is nice for the function to perform the reset instead of the caller. Better encapsulation. The change to drupal_static stays in place, and it does allow the callers to clear the cache which is important. This patch is back to its simple self.

Berdir’s picture

Status: Needs review » Needs work

Playing DBTNG Coding Style watchdog again, the following lines aren't correctly intented..

+  db_delete('node')
+  ->condition('nid', $nids, 'IN')
+  ->execute();
+  db_delete('node_revision')
+  ->condition('nid', $nids, 'IN')
+  ->execute();
+  db_delete('history')
+  ->condition('nid', $nids, 'IN')
+  ->execute();

condition and execute should be intented two more spaces.

moshe weitzman’s picture

Status: Needs work » Needs review
FileSize
5.04 KB

fixed indentation.

moshe weitzman’s picture

does the bot really complete the test suite in 3 minutes! I need some of that. se http://testing.drupal.org/pifr/file/1/nmd_0.patch

catch’s picture

Status: Needs review » Reviewed & tested by the community

Tests pass, code looks good, existing API still works the same, we should add a short note to update docs after commit. RTBC.

Status: Reviewed & tested by the community » Needs work

The last submitted patch failed testing.

moshe weitzman’s picture

Status: Needs work » Reviewed & tested by the community
FileSize
5.21 KB

reroll. back to rtbc.

sun’s picture

Status: Reviewed & tested by the community » Needs work

Looks good.

 function node_load_multiple($nids = array(), $conditions = array(), $reset = FALSE) {
-  static $node_cache = array();
+  $node_cache = &drupal_static(__FUNCTION__, array());
+  

Blank lines should be blank (white-space here).

  * @param $reset
- *   Whether to reset the internal node_load cache.
+ *   Whether to reset the node_load_multiple cache.
  *
+
  * @return

Empty line here.

  * Delete a node.
+ * 
+ * @param $nid
+ *   A node ID.

Blank PHPDoc lines should be blank (trailing white-space here and also node_delete_multiple()).

 function node_delete_confirm_submit($form, &$form_state) {
   if ($form_state['values']['confirm']) {
+    $node = node_load($form_state['values']['nid']);
     node_delete($form_state['values']['nid']);
+    watchdog('content', '@type: deleted %title.', array('@type' => $node->type, '%title' => $node->title));
+    drupal_set_message(t('@type %title has been deleted.', array('@type' => node_get_types('name', $node), '%title' => $node->title)));
   }

What bugs me a bit is that there is no error handling in here. However, that's out of the scope for this issue.

moshe weitzman’s picture

Status: Needs work » Needs review
FileSize
5.2 KB

Fixed whitespace.

sun’s picture

You're sure that this was the right patch? I'm not sure what changed in there, but the issues of #20 are still contained.

moshe weitzman’s picture

FileSize
5.65 KB

Retrying.

sun’s picture

Status: Needs review » Reviewed & tested by the community

Nice!

webchick’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs documentation

Very nice!

I also like that those watchdog messages are moved to the confirm form rather than within the API function. Icky.

Committed to HEAD. Please note the addition of this function on the 6.x -> 7.x upgrades page.

moshe weitzman’s picture

Status: Needs work » Fixed
Issue tags: -Needs documentation

Added upgrade docs.

Status: Fixed » Closed (fixed)
Issue tags: -Performance

Automatically closed -- issue fixed for 2 weeks with no activity.