Limited ckcksums to check data cksums
So... Long store short, checking metadata cksums is just intractably
slow.
But data cksums?
Yes checking data cksums is still O(b^2), but unlike metadata lookups,
which involve many small backwards reads, data reads are very easy to
cache. So instead of O(b^2), it's more like O(b^2/c), where c is your
rcache size.
Still O(b^2) when c << b, but I'm not sure that's avoidable without
adding more cksums.
At the very least, if you have enough RAM, c == b reduces this to O(b),
which is nice for "large" systems that want hardened reads without a
performance loss.
---
But why bother checking data cksums if we still have a read-hole with
metadata cksums?
Well, while considering the problem in the context of future features, I
noticed something _really interesting_:
- ckredund + metadata - reasonable ✓
- ckredund + data - impractical ✗, parity fanout + O(f+r) is bad
- ckcksums + metadata - impractical ✗, small reads + O(b^2) is bad
- ckcksums + data - reasonable ✓, assuming enough rcache
The current planned design for data redundancy makes it also intractably
slow to check every read, since it would require xoring all blocks that
contribute to the relevant parity block, but this isn't a problem for
metadata redundancy.
So while neither ckredund nor ckcksums can tractably close the read-hole
on their own, it looks like together they will be able to cover
everything without completely sacrificing performance. Neat!
Of course this isn't possible if ckcksums/ckredund imply checking both
metadata and data, so they need to be split apart.
And I don't really see a point in keeping the intractable variants
around in the codebase.
---
Dropping metadata ckcksums also means we can get rid of the ugly
lfsr_bd_ckrbydprefix and lfsr_bd_ckrbydsuffix functions, which were
basically duplicating all of lfsr_rbyd_fetch. That was quite a wart!
This saves a nice chunk of code when ckcksums is enabled:
code stack ctx
default before: 38128 2624 752
default after: 38128 (+0.0%) 2624 (+0.0%) 752 (+0.0%)
ckparity before: 39724 3048 764
ckparity after: 39700 (-0.1%) 3048 (+0.0%) 760 (-0.5%)
ckcksums before: 40612 3184 772
ckcksums after: 39396 (-3.0%) 3096 (-2.8%) 760 (-1.6%)
This commit is contained in:
@@ -1348,117 +1348,6 @@ code = '''
|
||||
|
||||
# Some simple ckcksums tests
|
||||
|
||||
# test every single-bit error in block 0/1
|
||||
[cases.test_ck_ckcksums_mroot]
|
||||
defines.BADBLOCK = [0, 1]
|
||||
defines.BADBIT = -1
|
||||
defines.BADBLOCK_BEHAVIOR = [
|
||||
'LFS_EMUBD_BADBLOCK_PROGFLIP',
|
||||
'LFS_EMUBD_BADBLOCK_READFLIP',
|
||||
]
|
||||
# this should stay inlined
|
||||
defines.SIZE = 'BLOCK_SIZE/16'
|
||||
ifdef = 'LFS_CKCKSUMS'
|
||||
code = '''
|
||||
// test all bad bits in the mroot
|
||||
for (lfs_size_t i = 0;
|
||||
i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1);
|
||||
i++) {
|
||||
lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT;
|
||||
|
||||
// reset the bd prng every run for reproducibility
|
||||
lfs_emubd_seed(CFG, 42);
|
||||
printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n",
|
||||
(lfs_size_t)BADBLOCK, badbit/8, badbit, badbit/8, badbit%8);
|
||||
|
||||
// mark our badbit as bad
|
||||
lfs_emubd_markbadbit(CFG, BADBLOCK, badbit) => 0;
|
||||
|
||||
// With metastability, basically any filesystem operation can
|
||||
// return LFS_ERR_CORRUPT. This is ok, what we're really testing
|
||||
// for is no internal/external asserts failing.
|
||||
|
||||
// format
|
||||
lfs_t lfs;
|
||||
int err = lfsr_format(&lfs, LFS_M_RDWR | LFS_M_CKCKSUMS, CFG);
|
||||
assert(!err || err == LFS_ERR_CORRUPT);
|
||||
if (err == LFS_ERR_CORRUPT) {
|
||||
goto corrupt;
|
||||
}
|
||||
err = lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKCKSUMS, CFG);
|
||||
assert(!err || err == LFS_ERR_CORRUPT);
|
||||
if (err == LFS_ERR_CORRUPT) {
|
||||
goto corrupt;
|
||||
}
|
||||
|
||||
{
|
||||
// create a file
|
||||
lfsr_file_t file;
|
||||
err = lfsr_file_open(&lfs, &file, "bathykorus",
|
||||
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL);
|
||||
assert(!err || err == LFS_ERR_CORRUPT);
|
||||
if (err == LFS_ERR_CORRUPT) {
|
||||
goto corrupt_mounted;
|
||||
}
|
||||
uint32_t prng = 42;
|
||||
uint8_t wbuf[SIZE];
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
lfs_ssize_t res = lfsr_file_write(&lfs, &file, wbuf, SIZE);
|
||||
assert(res == SIZE || res == LFS_ERR_CORRUPT);
|
||||
if (res == LFS_ERR_CORRUPT) {
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
goto corrupt_mounted;
|
||||
}
|
||||
err = lfsr_file_close(&lfs, &file);
|
||||
if (err == LFS_ERR_CORRUPT) {
|
||||
goto corrupt_mounted;
|
||||
}
|
||||
|
||||
// try to read our file
|
||||
for (int remount = 0; remount < 2; remount++) {
|
||||
// remount?
|
||||
if (remount) {
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
err = lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKCKSUMS, CFG);
|
||||
if (err == LFS_ERR_CORRUPT) {
|
||||
goto corrupt;
|
||||
}
|
||||
}
|
||||
|
||||
// yes reads can fail here
|
||||
err = lfsr_file_open(&lfs, &file, "bathykorus", LFS_O_RDONLY);
|
||||
assert(!err
|
||||
|| err == LFS_ERR_CORRUPT
|
||||
// bit errors can also cause our fs state to "rollback",
|
||||
// which is not great but we can't solve this with ckreads
|
||||
// alone
|
||||
|| err == LFS_ERR_NOENT);
|
||||
if (err == LFS_ERR_CORRUPT || err == LFS_ERR_NOENT) {
|
||||
goto corrupt_mounted;
|
||||
}
|
||||
uint8_t rbuf[SIZE];
|
||||
lfs_ssize_t res = lfsr_file_read(&lfs, &file, rbuf, SIZE);
|
||||
assert(res == SIZE || res == LFS_ERR_CORRUPT);
|
||||
if (res == LFS_ERR_CORRUPT) {
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
goto corrupt_mounted;
|
||||
}
|
||||
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
}
|
||||
}
|
||||
|
||||
corrupt_mounted:;
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
|
||||
corrupt:;
|
||||
// reset badbit
|
||||
lfs_emubd_markgood(CFG, BADBLOCK) => 0;
|
||||
}
|
||||
'''
|
||||
|
||||
# test every single-bit error in a file's data block
|
||||
[cases.test_ck_ckcksums_data]
|
||||
defines.BADBIT = -1
|
||||
@@ -1590,140 +1479,6 @@ code = '''
|
||||
}
|
||||
'''
|
||||
|
||||
# test every single-bit error in a file's btree node
|
||||
[cases.test_ck_ckcksums_btree]
|
||||
defines.BADBIT = -1
|
||||
defines.BADBLOCK_BEHAVIOR = [
|
||||
'LFS_EMUBD_BADBLOCK_PROGFLIP',
|
||||
'LFS_EMUBD_BADBLOCK_READFLIP',
|
||||
]
|
||||
# force the file to create a btree
|
||||
defines.INLINE_SIZE = 0
|
||||
defines.CRYSTAL_THRESH = -1
|
||||
defines.FRAGMENT_SIZE = 'BLOCK_SIZE/8'
|
||||
defines.SIZE = '2*FRAGMENT_SIZE'
|
||||
ifdef = 'LFS_CKCKSUMS'
|
||||
code = '''
|
||||
// first we need to figure out where the btree block will actually
|
||||
// end up, fortunately our block randomization is intentionally
|
||||
// consistent
|
||||
|
||||
// format
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, LFS_F_RDWR | LFS_F_CKCKSUMS, CFG) => 0;
|
||||
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKCKSUMS, CFG) => 0;
|
||||
|
||||
// create a file
|
||||
lfsr_file_t file;
|
||||
lfsr_file_open(&lfs, &file, "bathykorus",
|
||||
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
||||
uint32_t prng = 42;
|
||||
uint8_t wbuf[SIZE];
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
|
||||
// find the btree block
|
||||
lfsr_traversal_t t;
|
||||
lfsr_traversal_open(&lfs, &t, 0) => 0;
|
||||
lfs_block_t badblock;
|
||||
while (true) {
|
||||
struct lfs_tinfo tinfo;
|
||||
lfsr_traversal_read(&lfs, &t, &tinfo) => 0;
|
||||
if (tinfo.btype == LFS_BTYPE_BTREE) {
|
||||
badblock = tinfo.block;
|
||||
break;
|
||||
}
|
||||
}
|
||||
lfsr_traversal_close(&lfs, &t) => 0;
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
|
||||
// now test all bad bits in the btree block
|
||||
for (lfs_size_t i = 0;
|
||||
i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1);
|
||||
i++) {
|
||||
lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT;
|
||||
|
||||
// reset the bd prng every run for reproducibility
|
||||
lfs_emubd_seed(CFG, 42);
|
||||
printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n",
|
||||
badblock, badbit/8, badbit, badbit/8, badbit%8);
|
||||
|
||||
// mark our badbit as bad
|
||||
lfs_emubd_markbadbit(CFG, badblock, badbit) => 0;
|
||||
|
||||
// With metastability, basically any filesystem operation can
|
||||
// return LFS_ERR_CORRUPT. This is ok, what we're really testing
|
||||
// for is no internal/external asserts failing.
|
||||
|
||||
// format
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, LFS_F_RDWR | LFS_F_CKCKSUMS, CFG) => 0;
|
||||
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKCKSUMS, CFG) => 0;
|
||||
|
||||
{
|
||||
// create a file
|
||||
lfsr_file_t file;
|
||||
lfsr_file_open(&lfs, &file, "bathykorus",
|
||||
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
||||
uint32_t prng = 42;
|
||||
uint8_t wbuf[SIZE];
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
lfs_ssize_t res = lfsr_file_write(&lfs, &file, wbuf, SIZE);
|
||||
assert(res == SIZE || res == LFS_ERR_CORRUPT);
|
||||
if (res == LFS_ERR_CORRUPT) {
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
goto corrupt_mounted;
|
||||
}
|
||||
int err = lfsr_file_close(&lfs, &file);
|
||||
if (err == LFS_ERR_CORRUPT) {
|
||||
goto corrupt_mounted;
|
||||
}
|
||||
|
||||
// try to read our file
|
||||
for (int remount = 0; remount < 2; remount++) {
|
||||
// remount?
|
||||
if (remount) {
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKCKSUMS, CFG) => 0;
|
||||
}
|
||||
|
||||
// yes reads can fail here
|
||||
err = lfsr_file_open(&lfs, &file, "bathykorus", LFS_O_RDONLY);
|
||||
assert(!err
|
||||
|| err == LFS_ERR_CORRUPT
|
||||
// bit errors can also cause our fs state to "rollback",
|
||||
// which is not great but we can't solve this with ckreads
|
||||
// alone
|
||||
|| err == LFS_ERR_NOENT);
|
||||
if (err == LFS_ERR_CORRUPT || err == LFS_ERR_NOENT) {
|
||||
goto corrupt_mounted;
|
||||
}
|
||||
uint8_t rbuf[SIZE];
|
||||
lfs_ssize_t res = lfsr_file_read(&lfs, &file, rbuf, SIZE);
|
||||
assert(res == SIZE || res == LFS_ERR_CORRUPT);
|
||||
if (res == LFS_ERR_CORRUPT) {
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
goto corrupt_mounted;
|
||||
}
|
||||
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
}
|
||||
}
|
||||
|
||||
corrupt_mounted:;
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
|
||||
// reset badbit
|
||||
lfs_emubd_markgood(CFG, badblock) => 0;
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user