Skip to content

Commit 77f2265

Browse files
committed
Add last_scrubbed_txg property and option to scrub from last saved txg
The `last_scrubbed_txg` property indicates the transaction group (TXG) up to which the most recent scrub operation has checked and repaired the dataset. This provides administrators with insight into the data integrity status of their pool at a specific point in time. Sponsored-By: Wasabi Technology, Inc. Sponsored-By: Klara Inc. Signed-off-by: Mariusz Zaborski <[email protected]>
1 parent f2aca4b commit 77f2265

File tree

16 files changed

+187
-12
lines changed

16 files changed

+187
-12
lines changed

cmd/zpool/zpool_main.c

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,8 @@ get_usage(zpool_help_t idx)
512512
return (gettext("\tinitialize [-c | -s | -u] [-w] <pool> "
513513
"[<device> ...]\n"));
514514
case HELP_SCRUB:
515-
return (gettext("\tscrub [-s | -p] [-w] [-e] <pool> ...\n"));
515+
return (gettext("\tscrub [-e | -s | -p | -C] [-w] "
516+
"<pool> ...\n"));
516517
case HELP_RESILVER:
517518
return (gettext("\tresilver <pool> ...\n"));
518519
case HELP_TRIM:
@@ -8429,12 +8430,13 @@ wait_callback(zpool_handle_t *zhp, void *data)
84298430
}
84308431

84318432
/*
8432-
* zpool scrub [-s | -p] [-w] [-e] <pool> ...
8433+
* zpool scrub [-e | -s | -p | -C] [-w] <pool> ...
84338434
*
84348435
* -e Only scrub blocks in the error log.
84358436
* -s Stop. Stops any in-progress scrub.
84368437
* -p Pause. Pause in-progress scrub.
84378438
* -w Wait. Blocks until scrub has completed.
8439+
* -C Scrub from last saved txg.
84388440
*/
84398441
int
84408442
zpool_do_scrub(int argc, char **argv)
@@ -8450,9 +8452,10 @@ zpool_do_scrub(int argc, char **argv)
84508452
boolean_t is_error_scrub = B_FALSE;
84518453
boolean_t is_pause = B_FALSE;
84528454
boolean_t is_stop = B_FALSE;
8455+
boolean_t is_txg_continue = B_FALSE;
84538456

84548457
/* check options */
8455-
while ((c = getopt(argc, argv, "spwe")) != -1) {
8458+
while ((c = getopt(argc, argv, "spweC")) != -1) {
84568459
switch (c) {
84578460
case 'e':
84588461
is_error_scrub = B_TRUE;
@@ -8466,6 +8469,9 @@ zpool_do_scrub(int argc, char **argv)
84668469
case 'w':
84678470
wait = B_TRUE;
84688471
break;
8472+
case 'C':
8473+
is_txg_continue = B_TRUE;
8474+
break;
84698475
case '?':
84708476
(void) fprintf(stderr, gettext("invalid option '%c'\n"),
84718477
optopt);
@@ -8477,6 +8483,18 @@ zpool_do_scrub(int argc, char **argv)
84778483
(void) fprintf(stderr, gettext("invalid option "
84788484
"combination :-s and -p are mutually exclusive\n"));
84798485
usage(B_FALSE);
8486+
} else if (is_pause && is_txg_continue) {
8487+
(void) fprintf(stderr, gettext("invalid option "
8488+
"combination :-p and -C are mutually exclusive\n"));
8489+
usage(B_FALSE);
8490+
} else if (is_stop && is_txg_continue) {
8491+
(void) fprintf(stderr, gettext("invalid option "
8492+
"combination :-s and -C are mutually exclusive\n"));
8493+
usage(B_FALSE);
8494+
} else if (is_error_scrub && is_txg_continue) {
8495+
(void) fprintf(stderr, gettext("invalid option "
8496+
"combination :-e and -C are mutually exclusive\n"));
8497+
usage(B_FALSE);
84808498
} else {
84818499
if (is_error_scrub)
84828500
cb.cb_type = POOL_SCAN_ERRORSCRUB;
@@ -8485,6 +8503,8 @@ zpool_do_scrub(int argc, char **argv)
84858503
cb.cb_scrub_cmd = POOL_SCRUB_PAUSE;
84868504
} else if (is_stop) {
84878505
cb.cb_type = POOL_SCAN_NONE;
8506+
} else if (is_txg_continue) {
8507+
cb.cb_scrub_cmd = POOL_SCRUB_FROM_LAST_TXG;
84888508
} else {
84898509
cb.cb_scrub_cmd = POOL_SCRUB_NORMAL;
84908510
}

include/sys/dmu.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ typedef struct dmu_buf {
381381
#define DMU_POOL_CREATION_VERSION "creation_version"
382382
#define DMU_POOL_SCAN "scan"
383383
#define DMU_POOL_ERRORSCRUB "error_scrub"
384+
#define DMU_POOL_LAST_SCRUBBED_TXG "last_scrubbed_txg"
384385
#define DMU_POOL_FREE_BPOBJ "free_bpobj"
385386
#define DMU_POOL_BPTREE_OBJ "bptree_obj"
386387
#define DMU_POOL_EMPTY_BPOBJ "empty_bpobj"

include/sys/fs/zfs.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ typedef enum {
265265
ZPOOL_PROP_DEDUP_TABLE_SIZE,
266266
ZPOOL_PROP_DEDUP_TABLE_QUOTA,
267267
ZPOOL_PROP_DEDUPCACHED,
268+
ZPOOL_PROP_LAST_SCRUBBED_TXG,
268269
ZPOOL_NUM_PROPS
269270
} zpool_prop_t;
270271

@@ -1088,6 +1089,7 @@ typedef enum pool_scan_func {
10881089
typedef enum pool_scrub_cmd {
10891090
POOL_SCRUB_NORMAL = 0,
10901091
POOL_SCRUB_PAUSE,
1092+
POOL_SCRUB_FROM_LAST_TXG,
10911093
POOL_SCRUB_FLAGS_END
10921094
} pool_scrub_cmd_t;
10931095

include/sys/spa.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,6 +1081,7 @@ extern uint64_t spa_get_deadman_failmode(spa_t *spa);
10811081
extern void spa_set_deadman_failmode(spa_t *spa, const char *failmode);
10821082
extern boolean_t spa_suspended(spa_t *spa);
10831083
extern uint64_t spa_bootfs(spa_t *spa);
1084+
extern uint64_t spa_get_last_scrubbed_txg(spa_t *spa);
10841085
extern uint64_t spa_delegation(spa_t *spa);
10851086
extern objset_t *spa_meta_objset(spa_t *spa);
10861087
extern space_map_t *spa_syncing_log_sm(spa_t *spa);

include/sys/spa_impl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ struct spa {
318318
uint64_t spa_scan_pass_scrub_spent_paused; /* total paused */
319319
uint64_t spa_scan_pass_exam; /* examined bytes per pass */
320320
uint64_t spa_scan_pass_issued; /* issued bytes per pass */
321+
uint64_t spa_scrubbed_last_txg; /* last txg scrubbed */
321322

322323
/* error scrub pause time in milliseconds */
323324
uint64_t spa_scan_pass_errorscrub_pause;

lib/libzfs/libzfs.abi

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3132,7 +3132,8 @@
31323132
<enumerator name='ZPOOL_PROP_DEDUP_TABLE_SIZE' value='36'/>
31333133
<enumerator name='ZPOOL_PROP_DEDUP_TABLE_QUOTA' value='37'/>
31343134
<enumerator name='ZPOOL_PROP_DEDUPCACHED' value='38'/>
3135-
<enumerator name='ZPOOL_NUM_PROPS' value='39'/>
3135+
<enumerator name='ZPOOL_PROP_LAST_SCRUBBED_TXG' value='39'/>
3136+
<enumerator name='ZPOOL_NUM_PROPS' value='40'/>
31363137
</enum-decl>
31373138
<typedef-decl name='zpool_prop_t' type-id='af1ba157' id='5d0c23fb'/>
31383139
<typedef-decl name='regoff_t' type-id='95e97e5e' id='54a2a2a8'/>
@@ -5984,7 +5985,8 @@
59845985
<underlying-type type-id='9cac1fee'/>
59855986
<enumerator name='POOL_SCRUB_NORMAL' value='0'/>
59865987
<enumerator name='POOL_SCRUB_PAUSE' value='1'/>
5987-
<enumerator name='POOL_SCRUB_FLAGS_END' value='2'/>
5988+
<enumerator name='POOL_SCRUB_FROM_LAST_TXG' value='2'/>
5989+
<enumerator name='POOL_SCRUB_FLAGS_END' value='3'/>
59885990
</enum-decl>
59895991
<typedef-decl name='pool_scrub_cmd_t' type-id='a1474cbd' id='b51cf3c2'/>
59905992
<enum-decl name='zpool_errata' id='d9abbf54'>

man/man7/zpoolprops.7

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
.\" Copyright (c) 2021, Colm Buckley <[email protected]>
2929
.\" Copyright (c) 2023, Klara Inc.
3030
.\"
31-
.Dd July 29, 2024
31+
.Dd June 20, 2024
3232
.Dt ZPOOLPROPS 7
3333
.Os
3434
.
@@ -135,6 +135,14 @@ A unique identifier for the pool.
135135
The current health of the pool.
136136
Health can be one of
137137
.Sy ONLINE , DEGRADED , FAULTED , OFFLINE, REMOVED , UNAVAIL .
138+
.It Sy last_scrubbed_txg
139+
Indicates the transaction group (TXG) up to which the most recent scrub
140+
operation has checked and repaired the dataset.
141+
This provides insight into the data integrity status of their pool at
142+
a specific point in time.
143+
The
144+
.Xr zpool-scrub 8
145+
might be used to utilize this property.
138146
.It Sy leaked
139147
Space not released while
140148
.Sy freeing

man/man8/zpool-scrub.8

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,8 @@
3636
.Sh SYNOPSIS
3737
.Nm zpool
3838
.Cm scrub
39-
.Op Fl s Ns | Ns Fl p
39+
.Op Ns Fl e | Ns Fl p | Fl s Ns | Fl C Ns
4040
.Op Fl w
41-
.Op Fl e
4241
.Ar pool Ns
4342
.
4443
.Sh DESCRIPTION
@@ -114,6 +113,10 @@ The pool must have been scrubbed at least once with the
114113
feature enabled to use this option.
115114
Error scrubbing cannot be run simultaneously with regular scrubbing or
116115
resilvering, nor can it be run when a regular scrub is paused.
116+
.It Fl C
117+
Continue scrub from last saved txg (see zpool
118+
.Sy last_scrubbed_txg
119+
property).
117120
.El
118121
.Sh EXAMPLES
119122
.Ss Example 1

module/zcommon/zpool_prop.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ zpool_prop_init(void)
128128
zprop_register_number(ZPOOL_PROP_DEDUP_TABLE_SIZE, "dedup_table_size",
129129
0, PROP_READONLY, ZFS_TYPE_POOL, "<size>", "DDTSIZE", B_FALSE,
130130
sfeatures);
131+
zprop_register_number(ZPOOL_PROP_LAST_SCRUBBED_TXG,
132+
"last_scrubbed_txg", 0, PROP_READONLY, ZFS_TYPE_POOL, "<txg>",
133+
"LAST_SCRUBBED_TXG", B_FALSE, sfeatures);
131134

132135
/* default number properties */
133136
zprop_register_number(ZPOOL_PROP_VERSION, "version", SPA_VERSION,

module/zfs/dsl_scan.c

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,9 @@ static uint_t zfs_resilver_defer_percent = 10;
231231
((scn)->scn_phys.scn_func == POOL_SCAN_SCRUB || \
232232
(scn)->scn_phys.scn_func == POOL_SCAN_RESILVER)
233233

234+
#define DSL_SCAN_IS_SCRUB(scn) \
235+
((scn)->scn_phys.scn_func == POOL_SCAN_SCRUB)
236+
234237
/*
235238
* Enable/disable the processing of the free_bpobj object.
236239
*/
@@ -1137,15 +1140,24 @@ dsl_scan_done(dsl_scan_t *scn, boolean_t complete, dmu_tx_t *tx)
11371140

11381141
spa_notify_waiters(spa);
11391142

1140-
if (dsl_scan_restarting(scn, tx))
1143+
if (dsl_scan_restarting(scn, tx)) {
11411144
spa_history_log_internal(spa, "scan aborted, restarting", tx,
11421145
"errors=%llu", (u_longlong_t)spa_approx_errlog_size(spa));
1143-
else if (!complete)
1146+
} else if (!complete) {
11441147
spa_history_log_internal(spa, "scan cancelled", tx,
11451148
"errors=%llu", (u_longlong_t)spa_approx_errlog_size(spa));
1146-
else
1149+
} else {
11471150
spa_history_log_internal(spa, "scan done", tx,
11481151
"errors=%llu", (u_longlong_t)spa_approx_errlog_size(spa));
1152+
if (DSL_SCAN_IS_SCRUB(scn)) {
1153+
VERIFY0(zap_update(dp->dp_meta_objset,
1154+
DMU_POOL_DIRECTORY_OBJECT,
1155+
DMU_POOL_LAST_SCRUBBED_TXG,
1156+
sizeof (uint64_t), 1,
1157+
&scn->scn_phys.scn_max_txg, tx));
1158+
spa->spa_scrubbed_last_txg = scn->scn_phys.scn_max_txg;
1159+
}
1160+
}
11491161

11501162
if (DSL_SCAN_IS_SCRUB_RESILVER(scn)) {
11511163
spa->spa_scrub_active = B_FALSE;

module/zfs/spa.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,9 +451,10 @@ spa_prop_get_config(spa_t *spa, nvlist_t *nv)
451451

452452
spa_prop_add_list(nv, ZPOOL_PROP_DEDUP_TABLE_SIZE, NULL,
453453
ddt_get_ddt_dsize(spa), src);
454-
455454
spa_prop_add_list(nv, ZPOOL_PROP_HEALTH, NULL,
456455
rvd->vdev_state, src);
456+
spa_prop_add_list(nv, ZPOOL_PROP_LAST_SCRUBBED_TXG, NULL,
457+
spa_get_last_scrubbed_txg(spa), src);
457458

458459
version = spa_version(spa);
459460
if (version == zpool_prop_default_numeric(ZPOOL_PROP_VERSION)) {
@@ -4726,6 +4727,12 @@ spa_ld_get_props(spa_t *spa)
47264727
if (error != 0 && error != ENOENT)
47274728
return (spa_vdev_err(rvd, VDEV_AUX_CORRUPT_DATA, EIO));
47284729

4730+
/* Load the last scrubbed txg. */
4731+
error = spa_dir_prop(spa, DMU_POOL_LAST_SCRUBBED_TXG,
4732+
&spa->spa_scrubbed_last_txg, B_FALSE);
4733+
if (error != 0 && error != ENOENT)
4734+
return (spa_vdev_err(rvd, VDEV_AUX_CORRUPT_DATA, EIO));
4735+
47294736
/*
47304737
* Load the livelist deletion field. If a livelist is queued for
47314738
* deletion, indicate that in the spa

module/zfs/spa_misc.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2681,6 +2681,12 @@ spa_mode(spa_t *spa)
26812681
return (spa->spa_mode);
26822682
}
26832683

2684+
uint64_t
2685+
spa_get_last_scrubbed_txg(spa_t *spa)
2686+
{
2687+
return (spa->spa_scrubbed_last_txg);
2688+
}
2689+
26842690
uint64_t
26852691
spa_bootfs(spa_t *spa)
26862692
{

module/zfs/zfs_ioctl.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1718,6 +1718,9 @@ zfs_ioc_pool_scrub(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl)
17181718
error = spa_scrub_pause_resume(spa, POOL_SCRUB_PAUSE);
17191719
} else if (scan_type == POOL_SCAN_NONE) {
17201720
error = spa_scan_stop(spa);
1721+
} else if (scan_cmd == POOL_SCRUB_FROM_LAST_TXG) {
1722+
error = spa_scan_range(spa, scan_type,
1723+
spa_get_last_scrubbed_txg(spa), 0);
17211724
} else {
17221725
error = spa_scan(spa, scan_type);
17231726
}

tests/zfs-tests/tests/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,6 +1225,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
12251225
functional/cli_root/zpool_scrub/zpool_scrub_multiple_copies.ksh \
12261226
functional/cli_root/zpool_scrub/zpool_scrub_offline_device.ksh \
12271227
functional/cli_root/zpool_scrub/zpool_scrub_print_repairing.ksh \
1228+
functional/cli_root/zpool_scrub/zpool_scrub_txg_continue_from_last.ksh \
12281229
functional/cli_root/zpool_scrub/zpool_error_scrub_001_pos.ksh \
12291230
functional/cli_root/zpool_scrub/zpool_error_scrub_002_pos.ksh \
12301231
functional/cli_root/zpool_scrub/zpool_error_scrub_003_pos.ksh \

tests/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ typeset -a properties=(
6363
"bcloneused"
6464
"bclonesaved"
6565
"bcloneratio"
66+
"last_scrubbed_txg"
6667
"feature@async_destroy"
6768
"feature@empty_bpobj"
6869
"feature@lz4_compress"
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#!/bin/ksh -p
2+
#
3+
# CDDL HEADER START
4+
#
5+
# The contents of this file are subject to the terms of the
6+
# Common Development and Distribution License (the "License").
7+
# You may not use this file except in compliance with the License.
8+
#
9+
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10+
# or https://opensource.org/licenses/CDDL-1.0.
11+
# See the License for the specific language governing permissions
12+
# and limitations under the License.
13+
#
14+
# When distributing Covered Code, include this CDDL HEADER in each
15+
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16+
# If applicable, add the following below this CDDL HEADER, with the
17+
# fields enclosed by brackets "[]" replaced with your own identifying
18+
# information: Portions Copyright [yyyy] [name of copyright owner]
19+
#
20+
# CDDL HEADER END
21+
#
22+
23+
# Copyright (c) 2023, Klara Inc.
24+
#
25+
# This software was developed by
26+
# Mariusz Zaborski <[email protected]>
27+
# under sponsorship from Wasabi Technology, Inc. and Klara Inc.
28+
29+
. $STF_SUITE/include/libtest.shlib
30+
. $STF_SUITE/tests/functional/cli_root/zpool_scrub/zpool_scrub.cfg
31+
. $STF_SUITE/tests/functional/cli_root/zpool_import/zpool_import.kshlib
32+
33+
#
34+
# DESCRIPTION:
35+
# Verify scrub -C
36+
#
37+
# STRATEGY:
38+
# 1. Create a pool and create one file.
39+
# 2. Verify that the last_txg_scrub is 0.
40+
# 3. Run scrub.
41+
# 4. Verify that the last_txg_scrub is set.
42+
# 5. Create second file.
43+
# 6. Invalidate both files.
44+
# 7. Run scrub only from last point.
45+
# 8. Verify that only one file, that was created with newer txg,
46+
# was detected.
47+
#
48+
49+
verify_runnable "global"
50+
51+
function cleanup
52+
{
53+
log_must zinject -c all
54+
log_must rm -f $mntpnt/f1
55+
log_must rm -f $mntpnt/f2
56+
}
57+
58+
log_onexit cleanup
59+
60+
log_assert "Verify scrub -C."
61+
62+
# Create one file.
63+
mntpnt=$(get_prop mountpoint $TESTPOOL/$TESTFS)
64+
65+
log_must file_write -b 1048576 -c 10 -o create -d 0 -f $mntpnt/f1
66+
log_must sync_pool $TESTPOOL true
67+
f1txg=$(get_last_txg_synced $TESTPOOL)
68+
69+
# Verify that last_scrubbed_txg isn't set.
70+
zpoollasttxg=$(zpool get -H -o value last_scrubbed_txg $TESTPOOL)
71+
log_must [ $zpoollasttxg -eq 0 ]
72+
73+
# Run scrub.
74+
log_must zpool scrub -w $TESTPOOL
75+
76+
# Verify that last_scrubbed_txg is set.
77+
zpoollasttxg=$(zpool get -H -o value last_scrubbed_txg $TESTPOOL)
78+
log_must [ $zpoollasttxg -ne 0 ]
79+
80+
# Create second file.
81+
log_must file_write -b 1048576 -c 10 -o create -d 0 -f $mntpnt/f2
82+
log_must sync_pool $TESTPOOL true
83+
f2txg=$(get_last_txg_synced $TESTPOOL)
84+
85+
# Make sure that the sync txg are different.
86+
log_must [ $f1txg -ne $f2txg ]
87+
88+
# Insert faults.
89+
log_must zinject -a -t data -e io -T read $mntpnt/f1
90+
log_must zinject -a -t data -e io -T read $mntpnt/f2
91+
92+
# Run scrub from last saved point.
93+
log_must zpool scrub -w -C $TESTPOOL
94+
95+
# Verify that only newer file was detected.
96+
log_mustnot eval "zpool status -v $TESTPOOL | grep '$mntpnt/f1'"
97+
log_must eval "zpool status -v $TESTPOOL | grep '$mntpnt/f2'"
98+
99+
# Verify that both files are corrupted.
100+
log_must zpool scrub -w $TESTPOOL
101+
log_must eval "zpool status -v $TESTPOOL | grep '$mntpnt/f1'"
102+
log_must eval "zpool status -v $TESTPOOL | grep '$mntpnt/f2'"
103+
104+
log_pass "Verified scrub -C show expected status."

0 commit comments

Comments
 (0)