bbPress Can’t Count

In a highly-concurrent high-load environment bbPress will not count the topics and replies correctly. This happens due to several race conditions in the code. While not a critical vulnerability, it’s annoying. I wonder how the dotorg forums keep the numbers accurate? Maybe they don’t and nobody cares 🙂 but it’s something I’ve been very passionate about – data accuracy and race conditions.

bbPress Can't Count

I was load-testing a client website with bbPress. I sent 2,000 topic creation requests and 8,000 replies. 10,000 POST requests in total at a rate of 40 requests per second. Got 200 OK responses from all of them and looked through the site to make sure nothing broke. I noticed that the forum’s Posts count was 7,197 instead of the expected 10,000. The Topics count was also off. The database showed that there were exactly 2,000 topics and 8,000 replies in the forum, yet the count was short by almost 3,000.

I assumed that the numbers should be cached and not recounted every time. So I looked for grep -rni 'update_post_meta(.*count'. This brought up quite a bit of met keys. I removed the _bbp_total_reply_count key, refreshed the Forums page and the counts were recalculated to 10,000 for Posts and 2,000 for Topics.

Looking through the code for the bbp_increase_forum_reply_count function it became evident that bbPress count updates suffer from the most common race condition issue that is found in WordPress. The code can be simplied to something like:

$count = get_post_meta( $forum_id, '_bbp_total_reply_count', true );
update_post_meta( $forum_id, '_bbp_total_reply_count', $count + 1 );

The problem is that two concurrent requests can both get the same value and then update the database with the same increment. This results in the same number being written to the database. Instead of 2 replies only 1 will be counted. Classic.

The solution

I don’t personally think that this requires a fix of any sort inside bbPress itself. If you’re worried about missed numbers you can clear the following count meta keys:

  • _bbp_topic_count and _bbp_topic_count_hidden
  • _bbp_total_topic_count and _bbp_total_topic_count_hidden
  • _bbp_total_reply_count and _bbp_reply_count_hidden
  • _bbp_total_reply_count and _bbp_total_reply_count_hidden
  • _bbp_anonymous_reply_count if you have anonymous posting enabled
  • _bbp_forum_subforum_count, _bbp_voice_count can probably be left alone as they’re not updated often

This can be done once a day with a cronjob and a SQL query: DELETE FROM wp_postmeta WHERE meta_key LIKE '_bbp_%count%'.

Counts are also automatically reset (recounted by SQL queries) when things like deleting a thread or a forum happen. This may not be frequently enough.

Another overkill solution is using the WP Lock library that provides a locking API for WordPress. This would be overkill for something like bbPress post count numbers, but can be patched into bbPress from the outside by hooking into the meta counts. This has its own overhead: reply POST requests will be slightly slower due to the added synchronization.

Threadsafety in WordPress

If you’re worried about data integrity in your WordPress core, plugin, theme or custom PHP code, I highly recommend visiting threadsafe.org to find out how race conditions occur, how they impact your data and how to get professional help. This is a commercial project I co-founded and help maintain.

Also check out my 2016 WordCamp talk about race conditions (Russian audio, English subtitles).