开源 Firefox Ubuntu apache 微软 Windows shell 程序员 云计算 java mysql Android centos Python php nginx linux google 编程 wordpress

論數據庫redo/data存儲規劃與SSD寫傾斜

背景

SSD以其良好的IOPS和讀寫帶寬,正在逐漸取代原來的主流存儲,成為企業存儲市場的新寵。

在一些對存儲IOPS和讀寫帶寬需要較大的重要應用中,例如數據庫,SSD的使用也越來越普遍。

但是SSD的壽命和寫入量有關,如果沒有規劃好,可能會拉高故障率和成本。

另一方面,SSD還存在寫放大的可能,例如寫1字節,對應到SSD上也需要一個原子寫(可能是4KB或者其他大小(廠家決定)),廠家可能還有寫緩存來減緩寫放大的問題。就不再深入討論了。

讀寫傾斜的分析

以PostgreSQL數據庫為例,如果沒有規劃好存儲,出現傾斜是必然的。

出現寫傾斜後,SSD的剩余壽命也會出現傾斜,你一定不願意看到一邊是滿血,一邊是奄奄一息的硬盤吧。 小時候就教育我們要德智體全面發展的人才,數據庫設計也一樣。

通常一個PostgreSQL數據庫會有這樣一些目錄

drwx------ 6 digoal digoal  4096 Jul 28 10:11 base
drwx------ 2 digoal digoal  4096 Jul 28 10:18 global
drwx------ 2 digoal digoal  4096 Jul 28 11:17 pg_clog
drwx------ 2 digoal digoal  4096 Jul 28 10:03 pg_commit_ts
drwx------ 2 digoal digoal  4096 Jul 28 10:03 pg_dynshmem
-rw------- 1 digoal digoal  4468 Jul 28 10:03 pg_hba.conf
-rw------- 1 digoal digoal  1636 Jul 28 10:03 pg_ident.conf
drwx------ 2 digoal digoal  4096 Jul 28 10:18 pg_log
drwx------ 4 digoal digoal  4096 Jul 28 10:03 pg_logical
drwx------ 4 digoal digoal  4096 Jul 28 10:03 pg_multixact
drwx------ 2 digoal digoal  4096 Jul 28 10:18 pg_notify
drwx------ 2 digoal digoal  4096 Jul 28 10:03 pg_replslot
drwx------ 2 digoal digoal  4096 Jul 28 10:03 pg_serial
drwx------ 2 digoal digoal  4096 Jul 28 10:03 pg_snapshots
drwx------ 2 digoal digoal  4096 Jul 28 10:18 pg_stat
drwx------ 2 digoal digoal  4096 Jul 28 11:32 pg_stat_tmp
drwx------ 2 digoal digoal 12288 Jul 28 11:18 pg_subtrans
drwx------ 2 digoal digoal  4096 Jul 28 10:03 pg_tblspc
drwx------ 2 digoal digoal  4096 Jul 28 10:03 pg_twophase
-rw------- 1 digoal digoal     4 Jul 28 10:03 PG_VERSION
lrwxrwxrwx 1 digoal digoal    22 Jul 28 10:03 pg_xlog -> /data05/digoal/pg_xlog
-rw------- 1 digoal digoal    88 Jul 28 10:03 postgresql.auto.conf
-rw------- 1 digoal digoal 21641 Jul 28 10:09 postgresql.conf
-rw------- 1 digoal digoal    35 Jul 28 10:18 postmaster.opts
-rw------- 1 digoal digoal    75 Jul 28 10:18 postmaster.pid

寫入量較大的目錄或內容 :

臨時表空間
數據表空間
REDO日誌
寫請求較多的:

REDO日誌, 
幾乎所有的數據變更都要寫REDO,包括垃圾回收的操作在內(除臨時表,UNLOGGED,HASH索引不需要),

screenshot

每次寫入量的最小單位為wal-blocksize,默認8KB。

   --with-wal-blocksize=BLOCKSIZE
                          set WAL block size in kB [8]
數據庫有兩塊buffer, wal buffer和shared buffer。 
當數據發生變更時,首先會在SHARED BUFFER中變更,每一次變更都會記錄redo,而且檢查點後的第一次變更需要記錄完整的數據塊(if full page write=on),所有的REDO都會經過wal buffer,最終都要落盤。

screenshot

而shared buffer,則由bgwriter調度,每隔一段時間將根據老化算法,將臟頁write到OS DIRTY PAGE,再由OS調度最終落盤,OS調度還會合並IO,所以IO量相比xlog會小很多。

另外checkpoint也會將shared buffer寫入磁盤,調用的是sync接口,但是由於checkpoint不頻繁,所以shared buffer的sync操作是極少的。

screenshot

所以當一個數據塊發生多次變更時,被寫入到數據盤的次數可能很少,而被寫入到XLOG的次數則是每次變更都要寫(當然每次小的變更,可能只寫BLOCK的一部分數據到XLOG(但還是會包含很多額外的附加信息)),從這個現象上來看,XLOG的寫入量會比數據盤的寫入量大很多。

screenshot

从上面的分析来看,XLOG盘的写入量会比数据盘大很多,如果你将XLOG盘分离出去,就要小心写倾斜了。

screenshot

佐證讀寫傾斜的推理

用cgroup統計xlog盤與數據盤的讀寫請求與字節數來佐證前面的論據。

用於測試的塊設備對應的加載點

[root@iZ28tqoemgtZ cgroups]# ll /dev/vde
brw-rw---- 1 root disk 253, 64 Jul  7 22:35 /dev/vde
[root@iZ28tqoemgtZ cgroups]# ll /dev/vdd
brw-rw---- 1 root disk 253, 48 Jul  7 22:35 /dev/vdd

/dev/vde        689G   13G  642G   2% /data05
/dev/vdd        689G   13G  642G   2% /data04

cgroup配置

        mount -t tmpfs cgroup_root /sys/fs/cgroup
        mkdir /sys/fs/cgroup/blkio
        mount -t cgroup -o blkio none /sys/fs/cgroup/blkio

        mkdir -p /sys/fs/cgroup/blkio/test1/

echo "253:48 10000000000" >./blkio.throttle.read_iops_device 
echo "253:64 10000000000" >./blkio.throttle.read_iops_device
echo "253:64 10000000000" >./blkio.throttle.write_iops_device
echo "253:48 10000000000" >./blkio.throttle.write_iops_device 

数据库

# su - digoal
$ vi env.sh
export PS1="$USER@`/bin/hostname -s`-> "
export PGPORT=1921
export PGDATA=/data04/digoal/pg_root
export LANG=en_US.utf8
export PGHOME=/home/digoal/pgsql9.5
export LD_LIBRARY_PATH=$PGHOME/lib:/lib64:/usr/lib64:/usr/local/lib64:/lib:/usr/lib:/usr/local/lib:$LD_LIBRARY_PATH
export date=`date +"%Y%m%d%H%M"`
export PATH=$PGHOME/bin:$PATH:.
export MANPATH=$PGHOME/share/man:$MANPATH
export PGHOST=$PGDATA
export PGUSER=postgres
export PGDATABASE=postgres
alias rm='rm -i'
alias ll='ls -lh'
#unalias vi

xlog和数据盘分布在data05和data04目录

initdb -D /data04/digoal/pg_root -X /data05/digoal/pg_xlog -E UTF8 --locale=C -U postgres

数据库参数

listen_addresses = '0.0.0.0'            # what ip address(es) to listen on;
port = 1921                             # (change requires restart)
max_connections = 100                   # (change requires restart)
superuser_reserved_connections = 3      # (change requires restart)
unix_socket_directories = '.'   # comma-separated list of directories
shared_buffers = 16GB                   # min 128kB
maintenance_work_mem = 512MB            # min 1MB
dynamic_shared_memory_type = posix      # the default is the first option
vacuum_cost_delay = 0                   # 0-100 milliseconds
bgwriter_delay = 10ms                   # 10-10000ms between rounds
wal_level = minimal                     # minimal, archive, hot_standby, or logical
fsync = on                              # turns forced synchronization on or off
synchronous_commit = on         # synchronization level;
full_page_writes = on                   # recover from partial page writes
wal_buffers = 16MB                      # min 32kB, -1 sets based on shared_buffers
commit_delay = 10                       # range 0-100000, in microseconds
commit_siblings = 5                     # range 1-1000
checkpoint_timeout = 55min              # range 30s-1h
max_wal_size = 32GB
checkpoint_completion_target = 0.9      # checkpoint target duration, 0.0 - 1.0
log_destination = 'csvlog'              # Valid values are combinations of
logging_collector = on          # enable capturing of stderr and csvlog
log_truncate_on_rotation = on           # If on, an existing log file with the
log_timezone = 'PRC'
autovacuum = on                 # Enable autovacuum subprocess?  'on'
autovacuum_max_workers = 8              # max number of autovacuum subprocesses
autovacuum_naptime = 1s         # time between autovacuum runs
autovacuum_vacuum_cost_delay = 0        # default vacuum cost delay for
datestyle = 'iso, mdy'
timezone = 'PRC'
lc_messages = 'C'                       # locale for system error message
lc_monetary = 'C'                       # locale for monetary formatting
lc_numeric = 'C'                        # locale for number formatting
lc_time = 'C'                           # locale for time formatting
default_text_search_config = 'pg_catalog.english'

启动数据库放入分组

# cgexec -g blkio:test1 su - digoal -c ". ~/env.sh ; pg_ctl start"

# cd /sys/fs/cgroup/blkio/test1
[test1]# cat tasks 
20749
20750
20752
20753
20754
20755
20756

生成tpc-B测试数据1000万条

$ pgbench -i -s 100

开始压测

$ pgbench -M prepared -n -r -P 5 -c 48 -j 48 -T 10000

观察数据盘和日志盘的数据写入量和sync量 
日志盘的写入量远远大于数据盘,日志盘没有write,全部是sync操作 
数据盘由于有write操作,所以write大于sync,这也佐证了我前面的论据

# cat blkio.throttle.io_service_bytes 
253:64 read 69632
253:64 Write 39504248832
253:64 Sync 39504248832
253:64 Async 69632
253:64 Total 39504318464
253:48 Read 0
253:48 Write 522616832
253:48 Sync 248053760
253:48 Async 274563072
253:48 Total 522616832
253:0 Read 126976
253:0 Write 0
253:0 Sync 0
253:0 Async 126976
253:0 Total 126976
Total 40027062272

# cat blkio.throttle.io_serviced
253:64 Read 17
253:64 Write 2949886
253:64 Sync 2949886
253:64 Async 17
253:64 Total 2949903
253:48 Read 0
253:48 Write 50796
253:48 Sync 8722
253:48 Async 42074
253:48 Total 50796
253:0 Read 25
253:0 Write 0
253:0 Sync 0
253:0 Async 25
253:0 Total 25
Total 3000724

結論就是日誌盤的寫入量比數據盤大很多,這個CASE是75倍。

如何解這個問題

如果你的生產中是這麽部署的,日誌盤用了4塊SSD,數據盤用了8塊SSD,那麽問題來了。

每塊SSD的寫入量假設為10PB,那麽SSD4塊盤的SSD總的寫入量為40PB,8快盤的總寫入量為80PB。

那麽也許1年之後XLOG就寫入40PB了,而數據盤才寫入1PB不到。

SSD的剩余壽命就會相差懸殊,如果因為日誌盤的問題,直接下線整臺主機就不對了,還有8快盤沒磨損呢。 

screenshot

怎麽解呢?

XLOG的寫入是無法避免的,但是我們有方法防止故障。

1. 在XLOG盤還有命的時候,提取更換,如果有RAID的話,一塊塊換,提前rebuild。

需要註意的是,熱插拔可能會有短暫的IO堵塞,rebuild過程中也會造成性能影響。  
減少XLOG的寫入方法 
2.1. 關閉full page write會少寫一點XLOG,但是造成無法處理操作系統或硬件的數據庫崩潰恢復。

2.2. 拉長檢查點會少些一點XLOG。

均衡磨損的方法 
3. 數據盤和日誌盤不要分開,大家放在一起,一起磨損。這樣就不會傾斜了,但是同樣要解決一個問題,rebuild。

其他

1. 機械盤也有機械盤的問題,例如某個場景導致的磁盤問題(例如Oracle數據庫,一直讀寫的輪詢使用的REDO區間),或者一直更新某一個數據塊的記錄。 這部分磁盤可能很容易損壞。 
而SSD不存在這個問題,因為有磨損算法,即使你不停更新一個塊,也不會出現這個CEIL壞掉導致不可用,肯定SSD內部已經提前將這個ceil的內容轉移並更新映射關系了。

2. cgroup

Proportional Weight division of bandwidth
-----------------------------------------
You can do a very simple testing of running two dd threads in two different
cgroups. Here is what you can do.

- Enable Block IO controller
        CONFIG_BLK_CGROUP=y

- Enable group scheduling in CFQ
        CONFIG_CFQ_GROUP_IOSCHED=y

- Compile and boot into kernel and mount IO controller (blkio); see
  cgroups.txt, Why are cgroups needed?.

        mount -t tmpfs cgroup_root /sys/fs/cgroup
        mkdir /sys/fs/cgroup/blkio
        mount -t cgroup -o blkio none /sys/fs/cgroup/blkio

        mkdir -p /sys/fs/cgroup/blkio/test1/

- blkio.sectors
        - number of sectors transferred to/from disk by the group. First
          two fields specify the major and minor number of the device and
          third field specifies the number of sectors transferred by the
          group to/from the device.

- blkio.io_service_bytes
        - Number of bytes transferred to/from the disk by the group. These
          are further divided by the type of operation - read or write, sync
          or async. First two fields specify the major and minor number of the
          device, third field specifies the operation type and the fourth field
          specifies the number of bytes.

- blkio.io_serviced
        - Number of IOs completed to/from the disk by the group. These
          are further divided by the type of operation - read or write, sync
          or async. First two fields specify the major and minor number of the
          device, third field specifies the operation type and the fourth field
          specifies the number of IOs.

延伸阅读

    评论