Make WordPress Core

Opened 3 months ago

Last modified 3 months ago

#61025 new enhancement

Do not write in DB when updating a user if no user data has changed

Reported by: asumaran's profile asumaran Owned by:
Milestone: Awaiting Review Priority: normal
Severity: normal Version: 6.5
Component: Users Keywords:
Focuses: performance Cc:

Description

I've been working on improving the response times when checking out using WooCommerce.

We use Hyper DB with a separate host for writes. Subsequent reads are affected after every write. Avoiding any write query helps.

I noticed there's a duplicate query when checking out.

UPDATE `wp_users` SET `user_pass` = '$P$BlixIWEMbCbx76WhYlGoBnta7X8Bgf.', `user_nicename` = 'foo1713310736', `user_email` = 'foo1713310736@mail.test', `user_url` = '', `user_registered` = '2024-04-16 23:38:56', `user_activation_key` = '', `display_name` = 'foo1713310736' WHERE `ID` = 180

Turns out, WC creates a new cutomer using wc_create_new_customer which under the good uses wp_insert_user and then lines below updates the customer using WC_Customer->save() method which under the hood uses wp_update_user which also uses wp_insert_user.

At first I've tried to fix it in WooCommerce's scope but the issue is in the wp_insert_user function.

Since there's no hook to control the write, I'm proposing the same as update_metadata is already doing, if the passed value is the same as the previous one then skip the write query.

Here's how to reproduce the improvement:

Run the following using WP Shell

$username = 'foo' . time();
$user_email = $username . '@mail.test';
$user_id = wp_insert_user([ 'user_login' => $username, 'user_pass' => 'password', 'user_email' => $user_email ]);
wp_update_user( [ 'ID' => $user_id, 'user_email' => $user_email ] );
wp_update_user( [ 'ID' => $user_id, 'user_email' => $user_email ] );
wp_update_user( [ 'ID' => $user_id, 'user_email' => $user_email ] );
exit;

Small plugin to inspect queries

<?php
/*
 * Plugin Name: Show Queries (@asumaran)
 */

if ( ! defined( 'SAVEQUERIES' ) ) {
        define( 'SAVEQUERIES', true );
}

add_action( 'shutdown', 'output_queries' );

function output_queries() {
        global $wpdb;
        $queries = [];
        foreach ( $wpdb->queries as $query ) {
                if ( str_contains( $query[0], 'UPDATE `wp_users`' ) ) {
                        $queries[] = $query[0];
                }
        }
        error_log( "\r\n" . print_r( join( "\r\n", $queries ), true ) );
}

Without the patch:

It should show 3 UPDATE to the wp_users table in the log

UPDATE `wp_users` SET `user_pass` = '$P$BlDtStXHL/XErbE2J8VeILcjNOcVub.', `user_nicename` = 'foo1713365151', `user_email` = 'foo1713365151@mail.test', `user_url` = '', `user_registered` = '2024-04-17 14:45:51', `user_activation_key` = '', `display_name` = 'foo1713365151' WHERE `ID` = 183
UPDATE `wp_users` SET `user_pass` = '$P$BlDtStXHL/XErbE2J8VeILcjNOcVub.', `user_nicename` = 'foo1713365151', `user_email` = 'foo1713365151@mail.test', `user_url` = '', `user_registered` = '2024-04-17 14:45:51', `user_activation_key` = '', `display_name` = 'foo1713365151' WHERE `ID` = 183
UPDATE `wp_users` SET `user_pass` = '$P$BlDtStXHL/XErbE2J8VeILcjNOcVub.', `user_nicename` = 'foo1713365151', `user_email` = 'foo1713365151@mail.test', `user_url` = '', `user_registered` = '2024-04-17 14:45:51', `user_activation_key` = '', `display_name` = 'foo1713365151' WHERE `ID` = 183

With the patch:

It shouldn't log any update queries to the wp_users table.

Attachments (3)

user.php (805 bytes) - added by asumaran 3 months ago.
Do not update the users table if the user data didn't change
user.patch (805 bytes) - added by asumaran 3 months ago.
61025.patch (1.4 KB) - added by asumaran 3 months ago.

Download all attachments as: .zip

Change History (4)

@asumaran
3 months ago

Do not update the users table if the user data didn't change

@asumaran
3 months ago

@asumaran
3 months ago

#1 @asumaran
3 months ago

Updated the patch to skip calling the profile_update action if the UPDATE query did not run.

By not calling this action, WooCommerce will avoid running an unnecessary UPDATE query to set the last_update user meta if the user data hasn't changed.

Although, in the profile_update action the $old_user_data and $userdata is passed, we could use it to avoid running the query in WooCommerce's scope. But didn't make sense to me to avoid running the UPDATE query and then calling the profile_update action.

I think it's consistent to avoid calling the action if the UPDATE query didn't run.

So, either my changes make sense or a new filter is introduced to avoid the UPDATE query we are trying to avoid with my changes.

Happy to hear anything about my proposal.

Last edited 3 months ago by asumaran (previous) (diff)
Note: See TracTickets for help on using tickets.