自去年以来,我们一直致力于构建和推出 HPOS(高性能订单存储)作为一项可选功能。这改变了我们在数据库中存储订单数据的方式,从自定义帖子类型 wp_post
和wp_postmeta
表到专门用于存储订单数据的自定义表。我们还发布了这些表的详细结构:高性能订单存储:数据库模式
您可以在WooCommerce 自定义订单表的计划中了解此更改的动机。
现在,我们很高兴通过分享我们一直在做的高性能订单存储HPOS性能基准测试的结果,与大家分享HPOS将带来的性能提升。我们将比较基于帖子的存储与 HPOS 在常见功能和查询(例如订单创建、过滤等)方面的性能。
设置和方法
我们将在我们的测试站点上运行这些测试,该测试站点已经有大约 40 万个订单和 3 万个产品。该网站运行在一个商业计划中,每月收费 25 美元。该站点memcached
启用并安装了缓存(主机标准产品的一部分),但该站点上没有其他特定的性能插件处于活动状态。
我们将在 WP shell 中运行查询,这意味着我们不会利用主机提供的并行性。换句话说,我们将只使用一个 worker,这相当于在一个请求中完成所有工作,而主机实际上并行提供了更多的 worker,即主机可以并行处理许多 Web 请求。这就是说,网站的整体性能比我们的基准测试在这里建议的要高得多。
我们还禁用了 HPOS <> 后同步,因为启用它会破坏基准测试的目的,尤其是对于订单创建流程。您可以在高性能订单存储:向后兼容性和同步中阅读有关同步如何工作的更多信息。我们预计最终商店将在其网站上永久禁用同步。
此外, WP 6.1 中引入的WP query cache 性能提升也被禁用,因此我们的查询实际上会访问数据库。
该站点上的表大小wp_postmeta
约为 2GB(140 万行),表大小wp_posts
约为 240MB(60 万行)。
请注意,使用的 WooCommerce 版本是开发版本,构建在cot-main
分支上。这有稳定性修复,目前在主干中不可用,但应该很快可用(这些补丁正在审查周期中)。
测试
我们将使用一些直接查询进行测试,因为它们可以最直接地触发被测系统,而不会受到请求其他部分的干扰。
订单创建
通过减少我们为创建订单而必须执行的插入查询的数量,创建订单被确定为该项目的主要目标之一。在这里,我们看到 顺序插入性能提高了大约 5 倍,这主要是因为我们现在需要运行更少的插入查询,因为多个元字段在自定义表中被展平了。
基准代码
function benchmark_hpos_create_usage() {
global $wpdb;
$product_id = 502197;
$count = 1000;
$product = wc_get_product( $product_id );
$label = \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled() ? 'HPOS' : 'Posts table';
$time = microtime( true );
for ( $i = 0; $i < $count; $i++ ) {
$order = new WC_Order();
$order->set_billing_first_name( 'John' );
$order->set_billing_address_2( '123' );
$order->add_product( $product );
$order->save();
assert( $order->get_id() > 0 );
}
$time_taken = microtime( true ) - $time;
echo "[$label] " . 'Time to create ' . $count . ' orders: ' . $time_taken . 's';
}
结果
[HPOS] 创建1000个订单的时间:15.181570053101s[帖子表]创建1000个订单的时间:78.124469995499s
订单结账
与上述类似,我们也期望结账性能有所提高。请注意,插入性能不会直接转化为结帐性能,因为后者涉及各种数据验证和触发器(例如库存管理、各种 do_action 和 apply_filter 挂钩、用户验证等)。
即便如此,在使用 HPOS 时,我们看到结账 (对于一种产品的简单结账)提高了约 1.5 倍:
基准代码
function hpos_benchmark_process_checkout() {
$label = \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled() ? 'HPOS' : 'Posts table';
wc_load_cart();
$product = wc_get_product( 502197 );
add_filter( 'woocommerce_payment_successful_result', function ( $result, $order_id ) {
assert( $order_id > 0 );
throw new Exception( 'Order created: ' . $order_id );
}, 10, 3 );
$_POST['terms'] = 1;
$_POST['terms-field'] = 1;
$_POST['createaccount'] = 1;
$_POST['payment_method'] = 'cod';
$_POST['billing_first_name'] = 'John';
$_POST['billing_last_name'] = 'Doe';
$_POST['billing_company'] = 'Company';
$_POST['billing_address_1'] = '123 Main St';
$_POST['billing_address_2'] = '';
$_POST['billing_city'] = 'San Francisco';
$_POST['billing_state'] = 'CA';
$_POST['billing_postcode'] = '94107';
$_POST['billing_country'] = 'US';
$_POST['billing_email'] = 'test_' . time() . '@example.com';
$_POST['billing_phone'] = '555-555-5555';
$_POST['ship_to_different_address'] = 0;
add_action( 'woocommerce_after_checkout_validation', function ( $data, $errors ) {
if ( $errors->get_error_messages() ) {
print_r( implode( $errors->get_error_messages() ) );
throw new Exception( 'Validation failed: ' . implode( ', ', $errors->get_error_messages() ) );
}
}, 10, 2 );
$time_taken = 0;
for ( $i = 0; $i < 10; $i++ ) {
wc_empty_cart( true );
$checkout_nonce = wp_create_nonce( 'woocommerce-process_checkout' );
$_REQUEST['woocommerce-process-checkout-nonce'] = $checkout_nonce;
wc_clear_notices();
WC()->cart->add_to_cart( $product->get_id(), 1 );
benchmark_checkout( $time_taken );
}
wc_maybe_define_constant( 'DOING_AJAX', false );
echo "[$label] " . 'Time to process 10 checkouts: ' . $time_taken . 's';
}
function benchmark_checkout( &$time_taken ) {
$time = microtime( true );
try {
WC()->checkout()->process_checkout();
} catch ( Exception $e ) {
echo $e->getMessage();
}
$time_taken = $time_taken + ( microtime( true ) - $time );
}
结果
[HPOS] 处理 10 次结帐的时间:0.99165391921997s[帖子表] 处理 10 次结帐的时间:1.5086543560028s
使用元数据搜索订单
在 HPOS 项目中,我们还可以自由地在order_meta表上实现索引,这是我们在 post 表上没有的。如您所料, 添加索引可加快元查询查找速度,在以下基准测试代码中约为 10 倍。另一方面,额外的索引会降低插入速度,但这应该被扁平化带来的插入性能提升所抵消,如之前的测试所述。
基准代码
function benchmark_hpos_metadata() {
$label = \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled() ? 'HPOS' : 'Posts table';
$time = microtime( true );
$query = new WC_Order_Query(
array(
'limit' => 1000,
'return' => 'ids',
'orderby' => 'id',
'order' => 'DESC',
'billing_address_index' => 'John 123 ',
'meta_query' => array(
array(
'key' => '_billing_address_index',
'value' => 'John 123 ',
'compare' => '=',
),
),
)
);
$orders = $query->get_orders();
$time_taken = microtime( true ) - $time;
assert( count( $orders ) === 1000 );
echo "[$label] " . 'Time to search ' . count( $orders ) . ' orders by metadata: ' . $time_taken . 's';
}
结果
[HPOS] 按元数据搜索 1000 个订单的时间:0.052868127822876s[帖子表]按元数据搜索1000个订单的时间:0.63891506195068s
按索引列过滤
现在我们已经将几个 post_meta 键扁平化到它们自己的列中,我们还在相关的地方利用各个 meta_keys 上的索引。其中一个字段 customer_id
有自己的索引列,而在 post 表中,它存储在 _customer_user
元键中。 在 HPOS 中,这个获取客户所有订单的查询现在快 40 倍, 因为它可以使用列的索引 customer_id
。
基准代码
function benchmark_hpos_filter_customer() {
$label = \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled() ? 'HPOS' : 'Posts table';
$time = microtime( true );
$query = new WC_Order_Query(
array(
'limit' => 1000,
'return' => 'ids',
'customer_id' => 56902779,
)
);
$orders = $query->get_orders();
$time_taken = microtime( true ) - $time;
echo "[$label] " . 'Time to filter ' . count( $orders ) . ' orders for customer: ' . $time_taken . 's' . PHP_EOL;
}
结果
[HPOS] 为客户筛选 1000 个订单的时间:0.015961170196533s[帖子表]为客户过滤1000个订单的时间:0.59882402420044s
使用非索引列搜索订单
与上面类似,一些扁平化的列没有被索引。但是,我们仍然期望性能有所提高,因为搜索区域仍然从wp_postmeta
之前的整个表格减少到每个订单只有一个条目。在下面的查询中, 这 为我们的数据库带来了高达 3 倍的改进。
基准代码
function benchmark_hpos_search_non_index_usage() {
$label = \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled() ? 'HPOS' : 'Posts table';
$time = microtime( true );
if ( 'HPOS' === $label ) {
$query = new WC_Order_Query(
array(
'limit' => 1000,
'return' => 'ids',
'orderby' => 'id',
'order' => 'DESC',
'field_query' => array(
array(
'field' => 'created_via',
'value' => 'checkout',
)
)
)
);
} else {
$query = new WC_Order_Query(
array(
'limit' => 1000,
'return' => 'ids',
'orderby' => 'id',
'order' => 'DESC',
'created_via' => 'checkout'
)
);
}
$orders = $query->get_orders();
assert( count( $orders ) === 1000 );
$time_taken = microtime( true ) - $time;
echo "[$label] " . 'Time to search orders: ' . $time_taken . 's' . PHP_EOL;
}
结果
[HPOS] 查询订单时间:0.29565596580505s[帖子表]搜索订单的时间:1.0048348903656s
数据库大小
借助 HPOS,我们通过移出与订单相关的所有条目在技术上对post-meta表进行分片。我们希望站点管理员最终能够删除属于订单的现有post-meta行,从而提高订单工作流之外的站点性能(因为较小的表大小意味着 MySQL 在查询期间扫描的行数较少)。
例如,在大型部署的 woocommerce.com 中,订单约占所有帖子记录的 81%。在 wp_postmeta
表中,这种偏差更大,属于订单的元记录约占所有行的 97%。如果我们要删除属于订单的帖子元记录(我们计划在 HPOS 运行一段时间后执行此操作), woocommerce.com 将看到 post-meta表数据减少 97%。我们希望这种尺寸减小能够提高网站的整体性能,而不仅限于订单。
结论
如您所见,HPOS 提高了各种与订单相关的流程的性能,并有可能大大提高一般站点的性能。我们鼓励所有插件开发人员升级他们的插件以支持 HPOS,以便生态系统中的商家可以利用这个项目。
下一步
与查询基准类似,我们也在运行网络请求性能测试,并计划很快发布结果。