Question:

Where is the proper WordPress place to add a filter for pre_get_table_charset, prior to it getting executed?

The pre_get_table_charset filter is triggered in wp-db.php::get_table_charset().


Update: Solution

Mark Kaplun pointed me in the right direction, which is to put the filter into a drop-in. Because we’re using an MU setup (as evidenced by my table-name), the easiest and simplest drop-in was sunrise.php, which as I understand it, is designed to be loaded super-early anyway.

Update: Caveat

Once you successfully have the filter loaded, you will start getting these errors:

PHP Notice:  Undefined index: wp_2_options in /srv/www/wordpress/web/wp/wp-includes/wp-db.php on line 25xx

This is because wp-db.php doesn’t cache the result the same way it would if it had to look it up from the DB, and this cache is used by both wp-db.php::get_table_charset() and and wp-db.php::get_col_charset().

Solution: define a second filter, pre_get_col_charset, with 3 parameters, which returns the same value as your pre_get_table_charset filter.

add_filter('pre_get_col_charset', function($charset, $table, $column) {
    return 'utf8mb4'; 
}, 10, 2);

Background:

WordPress generates a consistently slow query on our server:

SHOW FULL COLUMNS FROM `wp_2_options`

This is generated in wp-db.php::get_table_charset(), and is used to determine the charset for a table. It is, of course, entirely unnecessary, because we know what the charset is. The most recent WordPress update ensured all our tables were utf8mb4, so surely we can hard-code this, and reduce the page-load speed?

Yes, we can. WordPress even provides a filter for it.

    /**
     * Filters the table charset value before the DB is checked.
     *
     * Passing a non-null value to the filter will effectively short-circuit
     * checking the DB for the charset, returning that value instead.
     *....
     */
    $charset = apply_filters( 'pre_get_table_charset', null, $table );

Writing the code for this is trivial:

add_filter('pre_get_table_charset', function($charset, $table) {return 'utf8mb4'; }, 10, 2);

However, this filter is called VERY early in the WordPress execution stack. It is called before plugins are loaded and before mu-plugins are loaded.

It is called AFTER config/application.php, but at that point, the add_filter() function is not yet defined.

I know there are a billion and one places where I can hack the WordPress core and insert that line, but I’d prefer not to if I can.

Also, we’re using a WordPress Bedrock setup, if that helps at all.

2 Answers
2

Seems like your assessment is correct and that specific function will be called before you will have a chance to add the filter in a traditional way. The related code flow is a call relatively early at boot time to wp_not_installed to check if wordpress is installed which calls in turn is_blog_active which tries to read the siteurl option, and therefor a DB query is made and the validation of table structure is done.

There seems to be two ways around it

  1. Have an object cache. You should in any case, but in this specific case the option will most likely be retrieved from the cache and no DB access will have to be done, at least not that early.

  2. Write a drop-in. Drop-ins are loaded much earlier in boot, and you probably have two options here, to override the DB class with the db.php drop-in or add an advanced-cache drop-in and set your filter there.

While it sound horrible to change the DB driver, in practice it is relatively safe as the drive do not change much between versions. Obviously the easier path is the advanced cache if you don’t have object cache.

Obviously you should probably first investigate why is the query slow. from the description it doesn’t seem like something that can get slow.

Tags:

Leave a Reply

Your email address will not be published. Required fields are marked *