### avoid read-on-write

傷城~ 2022-04-24 14:34 323阅读 0赞

avoid read-on-write

什么是 “read-on-write” problem?

在我们使用最常见的buffer write 中 “read-on-write” 问题指的是当我需要进行小于4k
大小buffer write 的时候, 需要先将数据所在的page 从disk 中读取出放入到page cache,
在page cache 中修改好, 然后再将这4k 数据写入回去. 这样在buffer write
的时候就多了一次磁盘IO

InnoDB redo log的做法是, 判断这个4k page 是不是第一次写入, 如果是第一次写入的话, 那么我会将这次写入填0, 变成4k 大小的size, 然后进行写入, 这样就可以避免了一次 “read-on-write” 操作, 后续继续写入的时候, 就不需要进行4k 大小的io, 因为这个时候数据已经在page cache 中, 直接写入就可以的.

我原先的想法是在内存中保留这个4k 大小的buffer, 每次修改都修改4k 大小的buffer, 每次写入也都进行4k 大小的写入, 这样做法反而不过InnoDB 的做法来得好, 因为每次写入都变成4k, 从用户空间拷贝到page cache 也需要拷贝4k 大小的数据, 而InnoDB 的做法只需要拷贝数据大小的size 就可以, 做的更细

在这里的测试case 和InnoDB redo log 场景类似, 是循环使用文件的场景, 在LevelDB/RocksDB 的wal 中, 由于每次都是创建新文件并进行append写入, 就不需要读取原先page 的内容, 因此是并不存在这样的问题. 但是LevelDB/RocksDB 存在的另一个问题是Append write 性能比overwriting 会差很多. 下次再对比测试.

下面两个例子可以看出同样写1G 大小的文件. InnoDB 的做法比标准追加写入做法提高40%

t.cc

write time microsecond(us) 51668825

t2.cc

write time microsecond(us) 29358162

  1. // t.cc
  2. // 标准的通过pwrite 的写入方法
  3. // 如果将这里的pwrite 改成write, 我们获得的还是几乎一样的performance
  4. // num = pwrite(fd, aligned_buf, dsize, off);
  5. // 估计在pwrite 内部实现, 如果只有一个线程写入, 并且这个offset 并没有移动, 就不需要先进行seek, 再write 了
  6. #include <stdio.h>
  7. #include <fcntl.h>
  8. #include <unistd.h>
  9. #include <sys/time.h>
  10. #include <stdint.h>
  11. #include <random>
  12. #include <linux/falloc.h>
  13. #include <sys/syscall.h>
  14. uint64_t NowMicros() {
  15. struct timeval tv;
  16. gettimeofday(&tv, NULL);
  17. return static_cast<uint64_t>(tv.tv_sec) * 1000000 + tv.tv_usec;
  18. }
  19. int main()
  20. {
  21. uint64_t st, ed;
  22. uint64_t file_size = 1LL * 1024LL * 1024LL * 1024LL;
  23. int fd = open("/disk11/tf", O_CREAT | O_RDWR, 0666);
  24. int ret;
  25. unsigned char *aligned_buf;
  26. int dsize = 512;
  27. ret = posix_memalign((void **)&aligned_buf, 4096, 4096 * 10);
  28. for (int i = 0; i < dsize; i++) {
  29. aligned_buf[i] = (int)random() % 128;
  30. }
  31. lseek(fd, 0, SEEK_SET);
  32. st = NowMicros();
  33. int num;
  34. off_t off = 0;
  35. for (uint64_t i = 0; i < file_size / dsize; i++) {
  36. num = pwrite(fd, aligned_buf, dsize, off);
  37. off += 512;
  38. fsync(fd);
  39. if (num != dsize) {
  40. printf("write error num %d\n", num);
  41. return -1;
  42. }
  43. }
  44. ed = NowMicros();
  45. printf("write time microsecond(us) %lld\n", ed - st);
  46. return 0;
  47. }

iostat 上, 可以看到nvme8n1有明显的磁盘read IO 存在

Imgur

  1. // t2.cc
  2. // InnoDB 优化做法, 在遇到第一个4k 对齐地址的时候, 将当前这一次IO 对齐成4k, 空闲的部分filling zero. 从而避免了这一次需要将该page 从文件中读取到Page cache
  3. #include <stdio.h>
  4. #include <fcntl.h>
  5. #include <unistd.h>
  6. #include <sys/time.h>
  7. #include <stdint.h>
  8. #include <random>
  9. #include <stdlib.h>
  10. #include <string.h>
  11. #include <linux/falloc.h>
  12. #include <sys/syscall.h>
  13. uint64_t NowMicros() {
  14. struct timeval tv;
  15. gettimeofday(&tv, NULL);
  16. return static_cast<uint64_t>(tv.tv_sec) * 1000000 + tv.tv_usec;
  17. }
  18. int main()
  19. {
  20. uint64_t st, ed;
  21. uint64_t file_size = 1LL * 1024LL * 1024LL * 1024LL;
  22. int fd = open("/disk11/tf", O_CREAT | O_RDWR, 0666);
  23. int ret;
  24. unsigned char *aligned_buf;
  25. int dsize = 512;
  26. ret = posix_memalign((void **)&aligned_buf, 4096, 4096 * 10);
  27. for (int i = 0; i < dsize; i++) {
  28. aligned_buf[i] = (int)random() % 128;
  29. }
  30. lseek(fd, 0, SEEK_SET);
  31. st = NowMicros();
  32. int num;
  33. off_t off = 0;
  34. for (uint64_t i = 0; i < file_size / dsize; i++) {
  35. if (i % 8 == 0) {
  36. memset(aligned_buf + 512, 0, 4096);
  37. num = pwrite(fd, aligned_buf, 4096, off);
  38. } else {
  39. num = pwrite(fd, aligned_buf, dsize, off);
  40. }
  41. off += 512;
  42. fsync(fd);
  43. if (num != dsize && num != 4096) {
  44. printf("write error num %d\n", num);
  45. return -1;
  46. }
  47. }
  48. ed = NowMicros();
  49. printf("write time microsecond(us) %lld\n", ed - st);
  50. return 0;
  51. }

iostat 可以看到这个时刻nvme8n1是没有磁盘的read IO 存在的

Imgur

通过 blktrace 可以看到每一个IO的过程, 可以看详细的看到在t2 中是有read IO 的存在, 并且可以看到
在t 中read IO : write IO = 1:8

这是通过blktrace 看到的 t2 的IO

  1. 259,9 2 357 0.001166883 0 C WS 75242264 + 8 [0]
  2. ## 一个IO 的开始
  3. 259,6 2 358 0.001173249 113640 A WS 75242264 + 8 <- (259,9) 75240216
  4. 259,9 2 359 0.001173558 113640 Q WS 75242264 + 8 [a.out]
  5. 259,9 2 360 0.001173664 113640 G WS 75242264 + 8 [a.out]
  6. 259,9 2 361 0.001173939 113640 U N [a.out] 1
  7. 259,9 2 362 0.001174017 113640 I WS 75242264 + 8 [a.out]
  8. 259,9 2 363 0.001174249 113640 D WS 75242264 + 8 [a.out]
  9. 259,9 2 364 0.001180838 0 C WS 75242264 + 8 [0]
  10. ## 一个IO 的结束
  11. 259,6 2 365 0.001187163 113640 A WS 75242264 + 8 <- (259,9) 75240216
  12. 259,9 2 366 0.001187367 113640 Q WS 75242264 + 8 [a.out]
  13. 259,9 2 367 0.001187477 113640 G WS 75242264 + 8 [a.out]
  14. 259,9 2 368 0.001187755 113640 U N [a.out] 1
  15. 259,9 2 369 0.001187835 113640 I WS 75242264 + 8 [a.out]
  16. 259,9 2 370 0.001188072 113640 D WS 75242264 + 8 [a.out]
  17. 259,9 2 371 0.001194495 0 C WS 75242264 + 8 [0]
  18. 259,6 2 372 0.001200968 113640 A WS 75242264 + 8 <- (259,9) 75240216
  19. 259,9 2 373 0.001201164 113640 Q WS 75242264 + 8 [a.out]
  20. 259,9 2 374 0.001201268 113640 G WS 75242264 + 8 [a.out]
  21. 259,9 2 375 0.001201540 113640 U N [a.out] 1

从上面可以看出, 这个IO 从开始到结束都不会有Read 相关的IO

对比于t 产生的IO 是这样的

  1. # 一个IO 的开始, 从这里可以看到这里有读IO 的出现, 并且其实这里是每产生1个读IO, 后续跟着8个 write IO
  2. 259,6 0 184 0.001777196 58995 A R 55314456 + 8 <- (259,9) 55312408
  3. 259,9 0 185 0.001777463 58995 Q R 55314456 + 8 [a.out]
  4. 259,9 0 186 0.001777594 58995 G R 55314456 + 8 [a.out]
  5. 259,9 0 187 0.001777863 58995 D RS 55314456 + 8 [a.out]
  6. 259,9 0 188 0.002418822 0 C RS 55314456 + 8 [0]
  7. # 一个读IO 结束
  8. 259,6 0 189 0.002423915 58995 A WS 55314456 + 8 <- (259,9) 55312408
  9. 259,9 0 190 0.002424192 58995 Q WS 55314456 + 8 [a.out]
  10. 259,9 0 191 0.002424434 58995 G WS 55314456 + 8 [a.out]
  11. 259,9 0 192 0.002424816 58995 U N [a.out] 1
  12. 259,9 0 193 0.002424992 58995 I WS 55314456 + 8 [a.out]
  13. 259,9 0 194 0.002425247 58995 D WS 55314456 + 8 [a.out]
  14. 259,9 0 195 0.002432434 0 C WS 55314456 + 8 [0]

write time microsecond(us) 42413626

write time microsecond(us) 114360346

:!./a.out
write time microsecond(us) 39630577

发表评论

表情:
评论列表 (有 0 条评论,323人围观)

还没有评论,来说两句吧...

相关阅读