Originally posted here: Resource - ZVOL Blocksize Modifier | TrueNAS Community
ZVOL Block Size Modifier: Fine-tuning for Optimal Performance
!!THIS IS BETA SOFTWARE!!
ZFS’s ZVOLs, with their robustness and versatility, are a staple in many storage solutions. However, every system has room for enhancement. One such tweakable parameter in ZVOLs is the volblocksize. After its initial setup, traditional methods don’t allow for this value to be altered. Yet, changing circumstances, performance metrics, or evolved best practices might shine a light on the need for such modification.
The ZVOL Block Size Modifier is here to facilitate this transformation, but with a twist: it creates a second copy of your chosen ZVOL with the new block size. This dual-copy approach ensures the integrity of your original data while providing an avenue for adjustments. However, an important implication of this method is the requirement for sufficient storage space. Before initiating the script, you must ensure there’s ample free space to accommodate this duplicated data.
Understanding Block Sizes:
- In ZFS, the volblocksize for ZVOLs can range from 512 bytes to 128K.
- Warning: The default minimum block size is 8192. Setting volblocksize less than that size may have unintended consequences.
- To reduce wasted space a minimum volblocksize of 8192 is recommended.
- It defines the size of the data blocks used for the underlying storage.
1. Type of Workload:
- Sequential Workloads: If the I/O pattern is mostly sequential, such as in large file transfers, backups, or streaming, a larger block size (like 128K) can be efficient. Larger blocks mean fewer I/O operations for the same amount of data, making them more suited for sequential tasks.
- Random Workloads: If the I/O operations are mostly random, as seen in databases or virtualization environments, a smaller block size (like 8K or 16K) might be more appropriate. Smaller blocks reduce the risk of I/O amplification where reading or writing a small amount of data requires the system to handle a much larger block.
2. Storage Medium:
- HDDs: Traditional hard drives often benefit from larger block sizes due to their sequential read/write nature.
- SSDs: Solid-state drives can handle random access better than HDDs. Depending on the SSD’s specifications and your workload, you might lean towards smaller block sizes.
. Potential Fragmentation:
Using a block size that is too large for your typical data can lead to inefficiencies and fragmentation. For instance, if you have a ZVOL storing a database that typically writes data in 8K chunks, but you’ve set a 128K block size, you’ll end up wasting space and potentially slowing down I/O operations.
Recommendation:
It’s often advisable to test various block sizes in a controlled environment before deciding. Set up benchmarks that mirror your typical workload and monitor key metrics like I/O throughput, latency, and CPU usage across different block sizes. This empirical approach often provides the clearest indication of which size is optimal.
Lastly, always ensure you have regular backups of your data. Adjusting block sizes, while safe when done correctly, can introduce risks. Making changes to a production environment without backups is a risk not worth taking.
It’s also worth noting, values that are not exposed in the TrueNAS UI, such as those greater than 128K or less than 4K may not work as expected.
Beginning the Journey: Selecting Your ZVOL
Upon starting the ZVOL Block Size Modifier, the script will helpfully present all your ZVOLs and their existing block sizes. From this lineup, simply identify the one you believe could benefit from adjustment.
The Heart of Customization: Determining the Size
Once you’ve made your pick, it’s time for size determination. Whether you’re leaning towards 4K, 128K, or any size in between, the choice of the new volblocksize is in your hands.
Tailoring the Environment: Cloning with Precision
With the specifications in place, the tool then embarks on the cloning process. It’s about creating a bespoke environment for your data, so the script will forge a clone of your chosen ZVOL but with your decided block size.
Transition and Integrity: Migrating the Data
Following the cloning, the data migration phase swings into action. Content from your original ZVOL gets meticulously transferred to the newly crafted one. This step can be a test of patience, especially with substantial volumes of data, but the wait ensures the integrity and completeness of your information.
System Harmony: The Service Sync
To meld all changes seamlessly into the system, there’s a service sync phase. The middlewared service undergoes a restart, ensuring the integration of the old with the new.
Reflecting on Origins: Deciding on the Original ZVOL
As the process nears completion, the script brings you to a decision point regarding the original ZVOL. You can opt to retain it, perhaps for backup or archival reasons, or you can choose to erase it, freeing up space and optimizing your storage.
A Word of Caution: While the script has been designed to manage your ZVOLs safely, operations at this level carry inherent risks. There’s always the slim chance of data loss or anomalies. It’s crucial to ensure you have up-to-date backups and have tested the process in a controlled environment before making adjustments to critical production data. Your data’s safety is paramount, and proactive measures are the best defense.
Version v0.02
#!/bin/bash
# Add this near the beginning of your script:
declare -i clean_exit=0
# Define color functions
color_output() {
local color=$1
local text=$2
case $color in
red) echo -e "\033[31m$text\033[0m" ;;
green) echo -e "\033[32m$text\033[0m" ;;
yellow) echo -e "\033[33m$text\033[0m" ;;
blue) echo -e "\033[34m$text\033[0m" ;;
purple) echo -e "\033[35m$text\033[0m" ;;
cyan) echo -e "\033[36m$text\033[0m" ;;
*) echo "$text" ;;
esac
}
color_error() {
echo "$(color_output red "$1")"
}
color_prompt() {
echo "$(color_output blue "$1")"
}
bytes_to_human_readable() {
local -i bytes=$1
if ((bytes > 1099511627776)); then echo $((bytes / 1099511627776))"T"
elif ((bytes > 1073741824)); then echo $((bytes / 1073741824))"G"
elif ((bytes > 1048576)); then echo $((bytes / 1048576))"M"
elif ((bytes > 1024)); then echo $((bytes / 1024))"K"
else echo $bytes"B"
fi
}
cleanup() {
if (( clean_exit == 0 )); then
color_error "\nExiting early. Performing cleanup..."
if [[ -n "$NEW_ZVOL_NAME" ]]; then
zfs destroy "$NEW_ZVOL_NAME" 2>/dev/null
fi
fi
}
trap cleanup SIGINT SIGTERM SIGHUP
tmp_error_log=$(mktemp)
# Intro message
echo "$(color_output yellow 'ZVOL Block Size Modifier v.02 - NickF')"
echo "$(color_output cyan '- Larger volblocksize can help with mostly sequential workloads and will gain a compression efficiency')"
echo "$(color_output green '- Smaller volblocksize can help with random workloads and minimize IO amplification, but will use more metadata and may have worse space efficiency')"
echo "$(color_output red 'This program is free software under the terms of the GNU General Public License. It is provided WITHOUT WARRANTY. See <https://www.gnu.org/licenses/> for more details.')"
read -p "Accept terms and proceed? [Y/N]: " choice
if [[ "$choice" != [Yy] ]]; then
exit 1
fi
# Generate a list of existing ZVOLs
zvols=($(zfs list -t volume -o name))
# Display the ZVOLs with aligned properties
echo -e "\nAvailable ZVOLs:"
for i in "${!zvols[@]}"; do
if zfs list "${zvols[$i]}" &> /dev/null; then
block_size=$(zfs get -H -o value volblocksize "${zvols[$i]}")
provisioned_size=$(zfs get -H -o value volsize "${zvols[$i]}")
used_space=$(zfs get -H -o value used "${zvols[$i]}")
printf "[%d] %-50s Block Size: %-10s Used: %-10s Provisioned: %s\n" "$i" "${zvols[$i]}" "$block_size" "$used_space" "$provisioned_size"
fi
done
# Get user's choice and validate
read -p "Choose the zvol number you'd like to clone: " index
if [[ -z ${zvols[$index]} ]]; then
color_error "Invalid zvol number."
rm "$tmp_error_log"
exit 1
fi
OLD_ZVOL_NAME=${zvols[$index]}
# Get the desired block size and validate
read -p "Enter the new desired volblocksize (e.g. 8K, 16K, 32K): " NEW_BLOCK_SIZE
if ! [[ $NEW_BLOCK_SIZE =~ ^[1-9][0-9]*[KkMmGgTt]$ ]]; then
color_error "Invalid block size format."
rm "$tmp_error_log"
exit 1
fi
NEW_ZVOL_NAME="${OLD_ZVOL_NAME}-${NEW_BLOCK_SIZE}"
OLD_ZVOL_SIZE=$(zfs get -H -o value volsize "$OLD_ZVOL_NAME")
# Convert OLD_ZVOL_SIZE to bytes for the calculation
old_zvol_size_in_bytes=$(echo "$OLD_ZVOL_SIZE" | awk '/K/ {print $1*1024}
/M/ {print $1*1024^2}
/G/ {print $1*1024^3}
/T/ {print $1*1024^4}')
new_zvol_size_in_bytes=$(( old_zvol_size_in_bytes * 12 / 10 ))
# Convert back to a human-readable size for zfs create (assuming GiB for simplicity)
new_zvol_size_in_gib=$(( new_zvol_size_in_bytes / (1024**3) ))
echo "INFO: Old zvol size was: $OLD_ZVOL_SIZE."
echo "INFO: New zvol will be created with a size 20% larger than the old one."
echo "INFO: Calculated new zvol size: ${new_zvol_size_in_gib}G."
echo "Creating the new zvol named: $NEW_ZVOL_NAME with block size: $NEW_BLOCK_SIZE and size: ${new_zvol_size_in_gib}G."
zfs create -s -V "${new_zvol_size_in_gib}G" -b "$NEW_BLOCK_SIZE" "$NEW_ZVOL_NAME" 2>"$tmp_error_log"
if [ $? -ne 0 ]; then
color_error "Failed to create the new zvol. Here's the error:"
cat "$tmp_error_log"
rm "$tmp_error_log"
exit 1
fi
echo "INFO: Successfully created the new zvol: $NEW_ZVOL_NAME with the size: ${new_zvol_size_in_gib}G and block size: $NEW_BLOCK_SIZE."
# After new ZVOL creation
zpool_name=$(echo "$NEW_ZVOL_NAME" | cut -d'/' -f1)
pool_status=$(zpool status -x "$zpool_name" | head -1)
if [[ "$pool_status" != *"is healthy" ]]; then
color_error "The zpool $zpool_name is in an unstable state: $pool_status. Exiting."
rm "$tmp_error_log"
exit 1
fi
# Function to get the available space on the pool
get_pool_avail_space_in_bytes() {
local pool_name="$1"
local avail_space
avail_space=$(zfs list -H -o avail -r "$pool_name" | head -n 1)
echo "$avail_space" | awk '/K/ {print $1*1024}
/M/ {print $1*1024^2}
/G/ {print $1*1024^3}
/T/ {print $1*1024^4}'
}
# Function to get the size of the ZVOL in bytes
get_zvol_size_in_bytes() {
local zvol_name="$1"
local zvol_size
zvol_size=$(zfs get -H -o value used "$zvol_name")
echo "$zvol_size" | awk '/K/ {print $1*1024}
/M/ {print $1*1024^2}
/G/ {print $1*1024^3}
/T/ {print $1*1024^4}'
}
# Identify the pool name from ZVOL path. Assuming ZVOL is in format: pool_name/...
POOL_NAME=$(echo "$OLD_ZVOL_NAME" | cut -d'/' -f1)
# Check if there's enough space in the pool for the data transfer
available_space_in_bytes=$(get_pool_avail_space_in_bytes "$POOL_NAME")
old_zvol_size_in_bytes=$(get_zvol_size_in_bytes "$OLD_ZVOL_NAME")
if (( available_space_in_bytes < old_zvol_size_in_bytes )); then
color_error "Not enough space in the zpool to proceed with the data transfer. Exiting."
zfs destroy "$NEW_ZVOL_NAME"
exit 1
fi
echo "INFO: old_zvol_size_in_bytes = $old_zvol_size_in_bytes"
echo "INFO: available_space_in_bytes = $available_space_in_bytes"
# If there's enough space, proceed with the data transfer
echo "$(color_output yellow "Starting data transfer from the old zvol to the new one. Depending on the ZVOL size, this process can take a while. Please be patient and don't interrupt the operation.")"
dd if=/dev/zvol/"$OLD_ZVOL_NAME" of=/dev/zvol/"$NEW_ZVOL_NAME" bs="$NEW_BLOCK_SIZE" status=progress 2> errorlog.txt
if [ $? -ne 0 ]; then
color_error "Data transfer failed. Here's the error:"
cat errorlog.txt
rm errorlog.txt
zfs destroy "$NEW_ZVOL_NAME"
exit 1
fi
# Check the used space of the new ZVOL
NEW_ZVOL_USED_SPACE=$(zfs get -H -o value used "$NEW_ZVOL_NAME")
# Convert the used space to bytes for comparison
NEW_ZVOL_USED_BYTES=$(echo "$NEW_ZVOL_USED_SPACE" | awk '/K/ {print $1*1024}
/M/ {print $1*1024^2}
/G/ {print $1*1024^3}
/T/ {print $1*1024^4}')
MINIMUM_THRESHOLD=57344 # 56K in bytes
if [ "$NEW_ZVOL_USED_BYTES" -lt "$MINIMUM_THRESHOLD" ]; then
color_error "Data transfer might not have been successful. Used space on new ZVOL is suspiciously low."
exit 1
fi
# Confirmation for deletion
echo "$(color_output yellow 'You are about to delete the original ZVOL which means the data will only exist on the new ZVOL with the modified block size. Make sure you have backups or you're certain about this action.')"
read -p "Delete the original zvol ($OLD_ZVOL_NAME)? [y/N]: " delete_choice
if [[ "$delete_choice" == [Yy] ]]; then
zfs destroy "$OLD_ZVOL_NAME"
echo "Original zvol $OLD_ZVOL_NAME has been deleted."
else
echo "$(color_output yellow 'Original ZVOL retained.')"
fi
# Restart the service if needed
echo "$(color_output cyan 'Restarting the middlwared service can momentarily disrupt services relying on it. However, it might be necessary for changes to take effect in certain systems.')"
read -p "Do you wish to restart the middlwared service now? [y/N]: " restart_choice
if [[ "$restart_choice" == [Yy] ]]; then
service middlwared restart
echo "$(color_output green 'Middlwared service restarted successfully.')"
fi
# Summarize the actions
echo "$(color_output green 'SUMMARY:')"
echo "Original ZVOL: $OLD_ZVOL_NAME"
echo "New ZVOL: $NEW_ZVOL_NAME"
echo "Block Size Modified From: $(zfs get -H -o value volblocksize "$OLD_ZVOL_NAME") to: $NEW_BLOCK_SIZE"
echo "Data Successfully Transferred: Yes"
if [[ "$delete_choice" == [Yy] ]]; then
echo "Original ZVOL Deleted: Yes"
else
echo "Original ZVOL Deleted: No"
fi
# Set clean_exit to 1 right before the script's successful termination message:
echo "$(color_output green 'Operation completed successfully. Have a great day!')"
clean_exit=1
Version v0.01
#!/bin/bash
# Define color functions
color_output() {
local color=$1
local text=$2
case $color in
red) echo -e "\033[31m$text\033[0m" ;;
green) echo -e "\033[32m$text\033[0m" ;;
yellow) echo -e "\033[33m$text\033[0m" ;;
blue) echo -e "\033[34m$text\033[0m" ;;
purple) echo -e "\033[35m$text\033[0m" ;;
cyan) echo -e "\033[36m$text\033[0m" ;;
*) echo "$text" ;;
esac
}
color_error() {
echo "$(color_output red "$1")"
}
cleanup() {
color_error "\nCTRL+C detected. Exiting gracefully..."
if [[ -n "$NEW_ZVOL_NAME" ]]; then
zfs destroy "$NEW_ZVOL_NAME" 2>/dev/null
fi
exit 2
}
trap cleanup SIGINT
# Print the intro message
echo "$(color_output yellow 'ZVOL Block Size Modifier v.01 - NickF')"
echo "$(color_output cyan '- Larger volblocksize can help with mostly sequential workloads and will gain a compression efficiency')"
echo "$(color_output green '- Smaller volblocksize can help with random workloads and minimize IO amplification, but will use more metadata and may have worse space efficiency')"
echo "$(color_output red 'This program is free software under the terms of the GNU General Public License. It is provided WITHOUT WARRANTY. See <https://www.gnu.org/licenses/> for more details.')"
read -p "Accept terms and proceed? [Y/N]: " choice
if [[ "$choice" != [Yy] ]]; then
exit 1
fi
# Generate a list of existing ZVOLs and their block sizes
zvols=($(zfs list -t volume -o name))
block_sizes=($(zfs get -H -o value volblocksize -t volume))
# Find the length of the longest zvol name
max_length=0
for zvol in "${zvols[@]}"; do
if [[ ${#zvol} -gt $max_length ]]; then
max_length=${#zvol}
fi
done
# Display the ZVOLs with aligned block sizes
echo -e "\nAvailable ZVOLs:"
for i in "${!zvols[@]}"; do
printf "[%d] %-${max_length}s \t %s\n" "$i" "${zvols[$i]}" "${block_sizes[$i]}"
done
# Get the user's choice
read -p "Choose the zvol number you'd like to clone: " index
OLD_ZVOL_NAME=${zvols[$index]}
OLD_ZVOL_SIZE=$(zfs get -H -o value volsize "$OLD_ZVOL_NAME")
# Get the desired block size
read -p "Enter the new desired volblocksize (e.g. 8K, 16K, 32K): " NEW_BLOCK_SIZE
NEW_ZVOL_NAME="${OLD_ZVOL_NAME}-${NEW_BLOCK_SIZE}"
echo "Creating the new zvol named: $NEW_ZVOL_NAME with block size: $NEW_BLOCK_SIZE."
zfs create -s -V "$OLD_ZVOL_SIZE" -b "$NEW_BLOCK_SIZE" "$NEW_ZVOL_NAME"
if [ $? -ne 0 ]; then
color_error "Failed to create the new zvol. Exiting."
exit 1
fi
echo "$(color_output yellow 'Starting data transfer. This might take a while...')"
dd if=/dev/zvol/"$OLD_ZVOL_NAME" of=/dev/zvol/"$NEW_ZVOL_NAME" bs="$NEW_BLOCK_SIZE" status=progress
echo "Data transfer completed. Restarting the middlwared service..."
service middlewared restart
echo "Do you want to delete the original zvol?"
color_error "WARNING: This will permanently delete the original zvol!"
read -p "Delete $OLD_ZVOL_NAME? [Y/N]: " choice
if [[ "$choice" == [Yy] ]]; then
zfs destroy "$OLD_ZVOL_NAME"
echo "$(color_output green 'Original zvol deleted successfully!')"
else
echo "$(color_output yellow 'Original zvol retained.')"
fi
echo "$(color_output green 'Script completed successfully!')"