A few weeks ago, I wrote an article titled Post Meta Abuse, but I think some have misunderstood the problem at hand.
Meta Queries are Bad?
Searching for posts via post meta is bad, but grabbing post meta is not.
For example, this is a hideously expensive/slow query:
$args = array( 'meta_key' => 'color', 'meta_value' => 'blue', 'meta_compare' => '!=' ); $query = new WP_Query( $args );
But this is super fast, almost free to run:
$color = get_post_meta( get_the_id(), 'color', true );
What’s Going On?
Post meta is optimised for fetching meta keys and values for a given post ID. If you know the ID of the post, then fetching post meta is fast! That’s what this table is built for.
A Helping Hand From WP Core
Core helps you out so that repeated calls to get_post_meta doesn’t get expensive. When you request data, it’s stored in WP_Cache so that no additional DB queries happen
More Helping Hands From WP Core?
When you fetch a post from the database, WordPress takes some initiative and fetches all of its post meta at the same time. So your code doesn’t trigger queries to fetch post meta from the database on the current post. Retrieving post meta values for the current post is essentially free. Combined with object caches such as Redis or Memcached, this makes post meta incredibly fast.
So Where’s The Problem?
Queries that search for posts. Post meta is optimised for direct accesses when you know the post ID in advance, it isn’t built for searches. That’s why taxonomy tables were added.
One of the reasons I’ve tweeted several times that ACF doesn’t scale is for this reason. ( Avoid ACF 4 for other reasons ). ACF allows you to enter huge amounts of data as post meta, which is fine for displaying posts. But if you ever try to list just the posts that have a certain field, or the posts where field X is bigger than 5, your site grinds to a halt and performance goes out the window. Install Query Monitor by John Blackbourn and watch your admin bar turn blood red with expensive database queries.
Luckily, ACF 5 and other more modern custom field systems such as Field Manager have taxonomy options. If you can force a field to be backed by taxonomy/term data, not post meta, then you can query for and search/filter for posts with that field to your hearts content.
To Summarise
- Calling
get_post_meta
on a known post is good! Super fast. - Using the custom field parameters to search for things will give you crippling performance
Post meta is optimised for direct accesses with a known post ID. If you’re trying to search for posts with or without certain meta values, you should use a taxonomy instead.
Pingback: How to vet a plugin for add on development - Austin WordPress Meetup
Hi Tom,
Let my ask you how would you go about a fast way to query a lists of posts in this situation:
– We have one hierarchical taxonomy with two parent terms. So they build two branches in the taxonomy tree.
– Say the viewer is in a term page, in: site.com/parent_term_a/children_term_aa/grandchildren_term_aaa
– Say grandchildren_term_aaa has a term meta value that relates it with a grandchild from the other big branch: site.com/parent_term_b/children_term_ba/grandchildren_term_bcc.
– As said before, viewer is in /../grandchildren_term_aaa, and we want to show them links for the term pages of terms that are related, like /../grandchildren_term_bcc.
So that query should use wp_term_query() to get terms with that term meta value? Or is there a lighter and quicker way?
So my question is about querying for terms that are in the same taxonomy but in different, distant branches, but they must be related somehow, for example with term meta.
Thanks
In that case you just call
get_term_meta
since you already know the ID of the current term. The term meta can then hold the value of the other terms IDBut this all seems overcomplicated. If you want to group things together, use a taxonomy, or better yet, you’re grouping the wrong things.
For example, instead of grouping the terms ( which are already grouped by parentage ), group the posts, and show a list of terms the posts have. You gain the benefit that your UI is more direct and easier to understand, whereas before it wasn’t clear where the suggestions came from. Is it suggesting A B and C because that person is A B and C? Or is it the term? Why would the parent child relationship have this? It’s confusing.
You also gain the benefit that the entire thing now requires no manual intervention to set up relatedness.
For example, we might have a product category, and the user is inside an electric car category. In the above case, I would imagine you have a term meta saying ‘cars’ and want to show other terms that have this term meta. The correct answer is to use a ‘cars’ term as the parent.
Alternatively, look at the posts inside the electric cars page, and list their categories as suggestions.
The fundamental anti-pattern here is that you should not be grouping things via meta, be it post meta or term meta, especially if you need to query for them.
However, it may be that what you’re talking about doesn’t fit into this at all because you’ve removed all context to make it a generic case, and that the actual problem is that the data structure is broken, and something that should be a CPT is actually a taxonomy, and vice versa.
It’s really difficult to answer when there’s no context as any answer has to fit every use case, which is simply impossible without speaking in the most generic vaguest sense. If you can provide specifics I can be more helpful. The generic case of how to store a group of groups is usually solved by hierarchy/term parents, or refactoring
Hi Tom,
All that you said is perfectly valid, of course. But let me tell you why I think it doesn’t apply to my case.
I’ll be specific: ‘hotels’ cpt, ‘locations’ taxonomy.
The main problem is that there are two types of terms: the first is the admin-terms, like continent, country, region, city. These is the main classification and what provides the url, like site.com/north-america/usa/california/san-francisco.
The other types are things like ‘tourist-regions’, ‘valleys’ or ‘mountain-ranges’. As you can imagine, there is a million combinations between these terms with the admin-* ones.
A real case is a resort that belongs to three different countries -yes, this actually exists-, one valley -which covers two of those countries-, two regional parks (of two different countries), one national park (we are lucky, just one country), three tourist regions (which cover five admin-regions) and, of course, one mountain range and two different sub-mountain ranges.
So, to simplify, let’s just imagine only a second type of terms: natural-locations, like ‘parks’ and ‘valleys’
Now, the problem is: if viewer is in some country/region page, we need to show them the valleys and parks related to that region. They can click on a valley that covers that region (remember, the valley can cover another region) and, when going to that valley-term page, they can see links to those two regions the valley is in, the one they saw before and a new one.
I would be happy to simply make more taxonomies for those other types of terms, but then, how one term of a taxonomy could be related to a term of another taxonomy? Parenthood between terms doesn’t work either because a valley is not child of any region or viceversa. Different types of locations can make many combinations with others. Mostly many-to-many.
It’s easier -I think- to build just one taxonomy with parent terms for every type of location, build the tree and then tell WordPress: hey, this region has this valley. Hey, that region has the same valley and a park. Hey, this park covers two different countries. That’s why I think term meta applies here: relating pages of terms that are in distant branches of the taxonomy tree.
Then, for every term page, I can query which terms are related for this location-item and go to their pages.
The only alternative I can think of is something you’ve mentioned: if the viewer is in a specific region term page, they’re viewing a list of hotels (posts). So let’s show them the list of other terms (be one taxonomy, be others) that those hotels belong to.
But that would be a complex and expensive query, right?
Thanks and sorry for the convoluted exposition.
Keep in mind that at the end of the day, the taxonomy table was deliberately optimised for finding posts XYZ where we know something about them ABC, where ABC is a term, and that the post meta table was optimised for finding key/value pairs of a known post.
As for:
continent, country, region, city etc
A location taxonomy sounds great, and maybe you can attach a term meta named `park`. Then you can assign another tag to the post `in-park` when the term is attached. Sure the two aren’t directly attached but is that really necessary with the right filters? And do you really require it? As long as the user and the UI are correct I don’t see the need for absolute precision here.
On top of that, it’s a trade off, where the choice of only storing 1 copy of data, and making sure it is incredibly accurate and semantically correct, ensures problems. This is a rabbit hole that you could fall down forever, and apply to everything. E.g. we can store a price in post meta, but how do we store the currency in such a way that it’s attached? We can’t have post meta meta?
Keep in mind that sometimes the problem is what you want to store, and sometimes the problem is exactly that, a problem to be solved.
And finally, it’s what you’re going to do with the data that has more of an impact on how to store it than what is ‘supposed’ to be done. E.g. it doesn’t make sense to store exact prices as terms, but if you’re Amazon, it might.
Hi Tom,
“Post meta is optimised for direct accesses with a known post ID. If you’re trying to search for posts with or without certain meta values, you should use a taxonomy instead.”
There are some situations we can’t use taxonomy like votes for posts, prices, dates (not the post published date), etc.
But it is requirements that you able to sort posts by votes, prices, dates, etc.
What can we do in this situation.
Thanks.
> There are some situations we can’t use taxonomy like votes for posts, prices, dates (not the post published date), etc.
That’s not true, you can store intermediate values. The data in your DB isn’t just a record of truth, it’s a data store. You’re perfectly able to store a term saying that the price is “between $5 and $10”, and if that speeds things up great! You’re perfectly capable of storing calculated values to save time, so why store just the precision version of the data?
> But it is requirements that you able to sort posts by votes, prices, dates, etc.
For dates, those posts have fields in the post that can and should be used. A lot of people use meta instead or UI reasons, or because future dates don’t show, but those are different problems with their own solutions, e.g. a `pre_get_posts` filter to fix the future dates issue. That way dates relies on an indexed value, which is faster than post meta, and even faster than taxonomies.
If you can store votes or prices in the menu order field then you can gain the same boost. Else, store approximate values in terms for filtering, and sort on post meta. Sometimes sorting on post meta is unavoidable, in which case if you want to avoid the slowdown, elastic search would be the best bet.
Remember, the trade off is yours to make, I’m simply making you aware that there’s one being made, and that there is a price being paid that most people are unaware of