[wp-trac] [WordPress Trac] #18631: wp_set_object_terms() should only wp_update_term_count() for affected terms

WordPress Trac wp-trac at lists.automattic.com
Fri Sep 9 21:18:19 UTC 2011


#18631: wp_set_object_terms() should only wp_update_term_count() for affected terms
--------------------------+------------------------------------
 Reporter:  jeremyclarke  |      Owner:
     Type:  defect (bug)  |     Status:  new
 Priority:  normal        |  Milestone:  Awaiting Review
Component:  Taxonomy      |    Version:
 Severity:  normal        |   Keywords:  has-patch dev-feedback
--------------------------+------------------------------------
 {{{
 wp_set_object_terms($object_id, $terms, $taxonomy, $append = false)
 }}}

 == The problem ==

 Currently, after creating/inserting any new terms passed in via the
 {{{$terms}}} argument, {{{wp_set_object_terms()}}} will run
 {{{wp_update_term_count()}}} on all of the {{{$terms}}} passed in,
 regardless of whether they were actually affected in any way. This seems
 really wasteful to me, and as far as I can tell causes huge problems when
 trying to delete large terms or remove terms from many posts at once.

 If you look at the delete section of the function you can see that it
 notes the terms that are being deleted, and after deletion runs
 {{{wp_update_term_count()}}} '''only''' on those terms. This is the way it
 should be.

 The main section that adds terms, on the other hand, ignores whether a
 term was affected and updates the counts of all terms passed in. Inside
 the {{{foreach}}} loop it carefully checks if each term is already
 attached to the object, and if it is not, it adds it. Unfortunately, the
 call to {{{wp_update_term_count()}}} that comes next fails to use this
 information, updating all categories both new and old.

 IMHO only posts that weren't already on the object should have their
 counts re-generated and re-saved, as there is no reason to believe that
 the other terms have changed.

 Am I missing something? I can't see a reason to reset counts on the other
 terms that weren't added to the object.

 == Importance ==

 This may seem trivial, but when adding/removing a single category to/from
 hundreds of posts the re-counting time adds up quickly (deleting one term
 took 18 minutes on my site!) In such a scenario only the one term should
 have it's count redone, but every single term on every single post ends up
 being recounted over and over because they are all being passed in. Sure,
 {{{wp_defer_term_counting()}}} is a partial solution to this issue, but
 even there it should only be deferring that one term that is being
 added/deleted, not all the terms that are merely being confirmed as
 already on the post.

 I noticed this because the {{{$append}}} version of
 {{{wp_set_object_terms()}}} is HUGELY faster on the site I'm working on,
 because when you use it you don't pass in already-assigned {{{$terms}}},
 and thus you don't encounter the bug I'm describing.

 Unlike for adding a single category, where you can just pass in one
 {{{$term}}} and {{{$append=true}}}, there is currently no matching system
 for removing a single category, you are locked into passing in all terms
 that were previously on the object but with your deleted term removed (see
 {{{wp_delete_term()}}} for an example). IMHO this is why
 {{{wp_delete_term()}}} has such atrocious performance on large sites with
 lots of terms (see #4365).

 FWIW the issue of simple term-removal will be solved by #15475 , which
 adds a {{{wp_remove_object_terms()}}} function that works as the opposite
 of {{{wp_set_object_terms()}}} with {{{$append=true}}}. If you look at the
 patch on that ticket you'll see that it has the same property as the
 deletion section of wp_set_object_terms: it only resets the term count on
 terms that were actually deleted.

 == Solution ==

 My solution (patch attached) is straightforward: Create a new array in
 {{{wp_set_object_terms()}}} called {{{$new_tt_ids}}} and add tt_ids of
 terms into it as they are added to the object, but not if they were
 already attached to it. Then when running {{{wp_update_term_count()}}} use
 that array instead of the full array of all $tt_ids set on the object.

 After some testing I've noticed huge improvements in performance of
 deleting categories :

 Without patch (# of posts in category/seconds for AJAX to finish deleting
 it)
 * 12 / 21s
 * 86 / 156s

 With patch
 * 12 / .76s
 * 112 / 2.72s
 * 408 / 10.2s

 I also tested my own term migration system which uses
 {{{wp_defer_term_counting()}}} and the difference was significant, though
 not as pronounced for predictable reasons (~7 seconds for 50 posts before
 patch, ~1 second for 50 posts after patch).

 P.S. Is there a reason {{{wp_delete_term()}}} doesn't use
 {{{wp_defer_term_counting()}}} while deleting posts? Seems like it would
 save a lot of trouble!

-- 
Ticket URL: <http://core.trac.wordpress.org/ticket/18631>
WordPress Trac <http://core.trac.wordpress.org/>
WordPress blogging software


More information about the wp-trac mailing list