Parallel pool import

This commit allow spa_load() to drop the spa_namespace_lock so
that imports can happen concurrently. Prior to dropping the
spa_namespace_lock, the import logic will set the spa_load_thread
value to track the thread which is doing the import.

Consumers of spa_lookup() retain the same behavior by blocking
when either a thread is holding the spa_namespace_lock or the
spa_load_thread value is set. This will ensure that critical
concurrent operations cannot take place while a pool is being
imported.

The zpool command is also enhanced to provide multi-threaded support
when invoking zpool import -a.

Lastly, zinject provides a mechanism to insert artificial delays
when importing a pool and new zfs tests are added to verify parallel
import functionality.

Contributions-by: Don Brady <don.brady@klarasystems.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: George Wilson <gwilson@delphix.com>
Closes #16093
This commit is contained in:
George Wilson
2024-04-22 12:42:38 -04:00
committed by GitHub
parent f4f156157d
commit c183d164aa
19 changed files with 818 additions and 72 deletions
+2 -1
View File
@@ -466,7 +466,8 @@ tests = ['zpool_import_001_pos', 'zpool_import_002_pos',
'import_paths_changed',
'import_rewind_config_changed',
'import_rewind_device_replaced',
'zpool_import_status']
'zpool_import_status', 'zpool_import_parallel_pos',
'zpool_import_parallel_neg', 'zpool_import_parallel_admin']
tags = ['functional', 'cli_root', 'zpool_import']
timeout = 1200
+3
View File
@@ -1144,6 +1144,9 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
functional/cli_root/zpool_import/zpool_import_missing_003_pos.ksh \
functional/cli_root/zpool_import/zpool_import_rename_001_pos.ksh \
functional/cli_root/zpool_import/zpool_import_status.ksh \
functional/cli_root/zpool_import/zpool_import_parallel_admin.ksh \
functional/cli_root/zpool_import/zpool_import_parallel_neg.ksh \
functional/cli_root/zpool_import/zpool_import_parallel_pos.ksh \
functional/cli_root/zpool_initialize/cleanup.ksh \
functional/cli_root/zpool_initialize/zpool_initialize_attach_detach_add_remove.ksh \
functional/cli_root/zpool_initialize/zpool_initialize_fault_export_import_online.ksh \
@@ -0,0 +1,165 @@
#!/bin/ksh -p
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or https://opensource.org/licenses/CDDL-1.0.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright 2007 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
#
# Copyright (c) 2023 Klara, Inc.
#
. $STF_SUITE/include/libtest.shlib
. $STF_SUITE/tests/functional/cli_root/zpool_import/zpool_import.cfg
. $STF_SUITE/tests/functional/cli_root/zpool_import/zpool_import.kshlib
#
# DESCRIPTION:
# Verify that admin commands to different pool are not blocked by import
#
# STRATEGY:
# 1. Create 2 pools
# 2. Export one of the pools
# 4. Import the pool with an injected delay
# 5. Execute some admin commands against both pools
# 6. Verify that the admin commands to the non-imported pool don't stall
#
verify_runnable "global"
function cleanup
{
zinject -c all
destroy_pool $TESTPOOL1
destroy_pool $TESTPOOL2
}
function pool_import
{
typeset dir=$1
typeset pool=$2
SECONDS=0
errmsg=$(zpool import -d $dir -f $pool 2>&1 > /dev/null)
if [[ $? -eq 0 ]]; then
echo ${pool}: imported in $SECONDS secs
echo $SECONDS > ${DEVICE_DIR}/${pool}-import
else
echo ${pool}: import failed $errmsg in $SECONDS secs
fi
}
function pool_add_device
{
typeset pool=$1
typeset device=$2
typeset devtype=$3
SECONDS=0
errmsg=$(zpool add $pool $devtype $device 2>&1 > /dev/null)
if [[ $? -eq 0 ]]; then
echo ${pool}: added $devtype vdev in $SECONDS secs
echo $SECONDS > ${DEVICE_DIR}/${pool}-add
else
echo ${pool}: add $devtype vdev failed ${errmsg}, in $SECONDS secs
fi
}
function pool_stats
{
typeset stats=$1
typeset pool=$2
SECONDS=0
errmsg=$(zpool $stats $pool 2>&1 > /dev/null)
if [[ $? -eq 0 ]]; then
echo ${pool}: $stats in $SECONDS secs
echo $SECONDS > ${DEVICE_DIR}/${pool}-${stats}
else
echo ${pool}: $stats failed ${errmsg}, in $SECONDS secs
fi
}
function pool_create
{
typeset pool=$1
typeset device=$2
SECONDS=0
errmsg=$(zpool create $pool $device 2>&1 > /dev/null)
if [[ $? -eq 0 ]]; then
echo ${pool}: created in $SECONDS secs
echo $SECONDS > ${DEVICE_DIR}/${pool}-create
else
echo ${pool}: create failed ${errmsg}, in $SECONDS secs
fi
}
log_assert "Simple admin commands to different pool not blocked by import"
log_onexit cleanup
#
# create two pools and export one
#
log_must zpool create $TESTPOOL1 $VDEV0
log_must zpool export $TESTPOOL1
log_must zpool create $TESTPOOL2 $VDEV1
#
# import pool asyncronously with an injected 10 second delay
#
log_must zinject -P import -s 10 $TESTPOOL1
pool_import $DEVICE_DIR $TESTPOOL1 &
sleep 2
#
# run some admin commands on the pools while the import is in progress
#
pool_add_device $TESTPOOL1 $VDEV2 "log" &
pool_add_device $TESTPOOL2 $VDEV3 "cache" &
pool_stats "status" $TESTPOOL1 &
pool_stats "status" $TESTPOOL2 &
pool_stats "list" $TESTPOOL1 &
pool_stats "list" $TESTPOOL2 &
pool_create $TESTPOOL1 $VDEV4 &
wait
log_must zpool sync $TESTPOOL1 $TESTPOOL2
zpool history $TESTPOOL1
zpool history $TESTPOOL2
log_must test "5" -lt $(<${DEVICE_DIR}/${TESTPOOL1}-import)
#
# verify that commands to second pool did not wait for import to finish
#
log_must test "2" -gt $(<${DEVICE_DIR}/${TESTPOOL2}-status)
log_must test "2" -gt $(<${DEVICE_DIR}/${TESTPOOL2}-list)
log_must test "2" -gt $(<${DEVICE_DIR}/${TESTPOOL2}-add)
[[ -e ${DEVICE_DIR}/${TESTPOOL1}-create ]] && log_fail "unexpected pool create"
log_pass "Simple admin commands to different pool not blocked by import"
@@ -0,0 +1,130 @@
#!/bin/ksh -p
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or https://opensource.org/licenses/CDDL-1.0.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright 2007 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
#
# Copyright (c) 2023 Klara, Inc.
#
. $STF_SUITE/include/libtest.shlib
. $STF_SUITE/tests/functional/cli_root/zpool_import/zpool_import.cfg
. $STF_SUITE/tests/functional/cli_root/zpool_import/zpool_import.kshlib
#
# DESCRIPTION:
# Verify that pool imports by same name only have one winner
#
# STRATEGY:
# 1. Create 4 single disk pools with the same name
# 2. Generate some ZIL records (for a longer import)
# 3. Export the pools
# 4. Import the pools in parallel
# 5. Repeat with using matching guids
#
verify_runnable "global"
POOLNAME="import_pool"
DEV_DIR_PREFIX="$DEVICE_DIR/$POOLNAME"
VDEVSIZE=$((512 * 1024 * 1024))
log_assert "parallel pool imports by same name only have one winner"
# each pool has its own device directory
for i in {0..3}; do
log_must mkdir -p ${DEV_DIR_PREFIX}$i
log_must truncate -s $VDEVSIZE ${DEV_DIR_PREFIX}$i/${DEVICE_FILE}$i
done
function cleanup
{
zinject -c all
log_must set_tunable64 KEEP_LOG_SPACEMAPS_AT_EXPORT 0
log_must set_tunable64 METASLAB_DEBUG_LOAD 0
destroy_pool $POOLNAME
log_must rm -rf $DEV_DIR_PREFIX*
}
log_onexit cleanup
log_must set_tunable64 KEEP_LOG_SPACEMAPS_AT_EXPORT 1
log_must set_tunable64 METASLAB_DEBUG_LOAD 1
function import_pool
{
typeset dir=$1
typeset pool=$2
typeset newname=$3
SECONDS=0
errmsg=$(zpool import -N -d $dir -f $pool $newname 2>&1 > /dev/null)
if [[ $? -eq 0 ]]; then
touch $dir/imported
echo "imported $pool in $SECONDS secs"
elif [[ $errmsg == *"cannot import"* ]]; then
echo "pool import failed: $errmsg, waited $SECONDS secs"
touch $dir/failed
fi
}
#
# create four exported pools with the same name
#
for i in {0..3}; do
log_must zpool create $POOLNAME ${DEV_DIR_PREFIX}$i/${DEVICE_FILE}$i
log_must zpool export $POOLNAME
done
log_must zinject -P import -s 10 $POOLNAME
#
# import the pools in parallel, expecting only one winner
#
for i in {0..3}; do
import_pool ${DEV_DIR_PREFIX}$i $POOLNAME &
done
wait
# check the result of background imports
typeset num_imports=0
typeset num_cannot=0
for i in {0..3}; do
if [[ -f ${DEV_DIR_PREFIX}$i/imported ]]; then
((num_imports += 1))
fi
if [[ -f ${DEV_DIR_PREFIX}$i/failed ]]; then
((num_cannot += 1))
loser=$i
fi
done
[[ $num_imports -eq "1" ]] || log_fail "expecting an import"
[[ $num_cannot -eq "3" ]] || \
log_fail "expecting 3 pool exists errors, found $num_cannot"
log_note "$num_imports imported and $num_cannot failed (expected)"
log_pass "parallel pool imports by same name only have one winner"
@@ -0,0 +1,137 @@
#!/bin/ksh -p
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or https://opensource.org/licenses/CDDL-1.0.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright 2007 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
#
# Copyright (c) 2023 Klara, Inc.
#
. $STF_SUITE/include/libtest.shlib
. $STF_SUITE/tests/functional/cli_root/zpool_import/zpool_import.cfg
. $STF_SUITE/tests/functional/cli_root/zpool_import/zpool_import.kshlib
# test uses 8 vdevs
export MAX_NUM=8
#
# DESCRIPTION:
# Verify that pool imports can occur in parallel
#
# STRATEGY:
# 1. Create 8 pools
# 2. Generate some ZIL records
# 3. Export the pools
# 4. Import half of the pools synchronously to baseline sequential cost
# 5. Import the other half asynchronously to demonstrate parallel savings
# 6. Export 4 pools
# 7. Test zpool import -a
#
verify_runnable "global"
#
# override the minimum sized vdevs
#
VDEVSIZE=$((512 * 1024 * 1024))
increase_device_sizes $VDEVSIZE
POOLNAME="import_pool"
function cleanup
{
zinject -c all
log_must set_tunable64 KEEP_LOG_SPACEMAPS_AT_EXPORT 0
log_must set_tunable64 METASLAB_DEBUG_LOAD 0
for i in {0..$(($MAX_NUM - 1))}; do
destroy_pool $POOLNAME-$i
done
# reset the devices
increase_device_sizes 0
increase_device_sizes $FILE_SIZE
}
log_assert "Pool imports can occur in parallel"
log_onexit cleanup
log_must set_tunable64 KEEP_LOG_SPACEMAPS_AT_EXPORT 1
log_must set_tunable64 METASLAB_DEBUG_LOAD 1
#
# create some exported pools with import delay injectors
#
for i in {0..$(($MAX_NUM - 1))}; do
log_must zpool create $POOLNAME-$i $DEVICE_DIR/${DEVICE_FILE}$i
log_must zpool export $POOLNAME-$i
log_must zinject -P import -s 12 $POOLNAME-$i
done
wait
#
# import half of the pools synchronously
#
SECONDS=0
for i in {0..3}; do
log_must zpool import -d $DEVICE_DIR -f $POOLNAME-$i
done
sequential_time=$SECONDS
log_note "sequentially imported 4 pools in $sequential_time seconds"
#
# import half of the pools in parallel
#
SECONDS=0
for i in {4..7}; do
log_must zpool import -d $DEVICE_DIR -f $POOLNAME-$i &
done
wait
parallel_time=$SECONDS
log_note "asyncronously imported 4 pools in $parallel_time seconds"
log_must test $parallel_time -lt $(($sequential_time / 3))
#
# export pools with import delay injectors
#
for i in {4..7}; do
log_must zpool export $POOLNAME-$i
log_must zinject -P import -s 12 $POOLNAME-$i
done
wait
#
# now test zpool import -a
#
SECONDS=0
log_must zpool import -a -d $DEVICE_DIR -f
parallel_time=$SECONDS
log_note "asyncronously imported 4 pools in $parallel_time seconds"
log_must test $parallel_time -lt $(($sequential_time / 3))
log_pass "Pool imports occur in parallel"