Статьи

Советы по проверке и повышению производительности ввода-вывода хранилища с MongoDB

Я выступаю в MongoDB World в июне этого года. Приходите услышать от меня и других экспертов MongoDB! Используйте код 25AntoineGirbal для 25% скидки на билеты на http://world.mongodb.com !

В большинстве приложений дисковый ввод-вывод обычно становится вашим главным узким местом, а все остальные глупые узкие места (например, процессор, количество соединений и т. Д.) Решаются. И независимо от того, нравится это нашим конкурентам или нет, блокировка записи редко становится узким местом в хорошо разработанном приложении 🙂

Недавно я провел некоторое время в большом приложении, работающем в Microsoft Azure с дисками Azure, и все, что было сделано из ОЗУ, забирало НАВСЕГДА. Это вина MongoDB? Не совсем, я выполнял запросы и агрегаты для рабочего набора, который был больше оперативной памяти, а диски Azure допускали только около 100 операций ввода-вывода в секунду (и это не имеет значения, будь то последовательный или случайный ввод-вывод на виртуальном диске). Таким образом, ожидалось, что на агрегирование более 100 миллионов документов потребуется 24 часа, а при объединении шардинга (что требует повторной агрегации на дорогой стадии) это выглядело как 2 дня. И если это терпит неудачу в любой точке, это должно быть перезапущено с самого начала. Излишне говорить, что это было непригодно.

MongoDB не может выйти за физические пределы диска, волшебства нет, и это верно для любой базы данных. Для такой тяжелой рабочей нагрузки вы хотите убедиться, что ваш ящик не слизняк! Существует множество инструментов для проверки скорости диска (например, bonnie ++), но на самом деле есть один, который подходит для дистрибутива mongo : mongoperf.

Использование Mongoperf

Чтобы использовать его, поместите простую конфигурацию JSON в файл и запустите его следующим образом из папки на диске, который вы хотите протестировать :

$ cat ./mongoperf.conf
{ nThreads:1024, fileSizeMB:1000, mmf:false, r:true, w:true, syncDelay:60 }
$ mongoperf < ./mongoperf.conf
...
87 ops/sec 0 MB/sec
106 ops/sec 0 MB/sec
85 ops/sec 0 MB/sec
92 ops/sec 0 MB/sec

Варианты довольно просты: вы можете выбрать размер файла на диске, количество потоков для выполнения операций и операции чтения или записи. Один интересный вариант — это mmf, который говорит использовать файлы с отображением в памяти так же, как MongoDB. Если они включены, операции чтения и записи проходят через ОЗУ (таким образом, могут кэшироваться и буферизироваться), и в конечном итоге грязные страницы записываются на диск каждые секунды задержки синхронизации . Опция mmf дает лучшие результаты, которые близко имитируют MongoDB, но которые трудно интерпретировать — для целей простого тестирования самого диска лучше его оставить .

Теперь результат, который вы видите выше, относится к диску Azure со скоростью около 100 iops. При такой производительности вы не сможете создать приложение, которое читает не только ОЗУ, либо выполняет запись со скоростью более 100 секунд в секунду. Тем не менее, давайте параллельно проверим запуск mongoperf, что диск действительно размечен с помощью iostat :

$ iostat -x 2
avg-cpu: %user %nice %system %iowait %steal %idle
0.13 0.00 0.13 15.89 0.00 83.86

Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s avgrq-sz avgqu-sz await svctm %util
sdb 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
sdc 0.00 0.00 42.50 89.00 340.00 372.00 5.41 1.60 12.49 7.58 99.70
sda 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00

Как видно выше, использование диска sdc диска действительно составляет 99,70%. Для удовольствия давайте протестируем mongoperf на моем собственном ноутбуке, который является MacBook старого поколения с SSD:

...
new thread, total running : 128
12527 ops/sec 48 MB/sec
12220 ops/sec 47 MB/sec
12558 ops/sec 49 MB/sec
...

Это уже намного лучше. Один более старый SSD может довести его до 12 000 iops, что довольно впечатляет. Для справки: один жесткий диск должен дать вам около 250 iops, а твердотельный RAID5 на 10 жестких дисков — до 3000 iops. Эти драгоценные iops — это то, что нам нужно для создания сложных приложений большого объема, которые хотят использовать индексацию и агрегирование.

Тестирование в EC2

Let’s use an EC2 i2 instance (new release at this time of writing) which looks promising. The i2.2xlarge gives us 2x 800GB SSDs which we can try to combine. Each disk is rated for 35k iops according to Amazon. We’re using the new Amazon Linux AMI (HVM) which packs a 3.10 Linux kernel. Let’s format a drive with EXT4 and mount it (after properly setting the line in fstab):

$ sudo mkfs.ext4 /dev/sdb
$ sudo vim /etc/fstab
$ grep sdb /etc/fstab
/dev/sdb /media/ephemeral0 auto defaults,noatime,nofail,comment=cloudconfig 0 2
$ sudo mount /media/ephemeral0
$ df
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/xvda1 8123812 1439044 6584520 18% /
devtmpfs 31448496 64 31448432 1% /dev
tmpfs 31329260 0 31329260 0% /dev/shm
/dev/xvdb 769018760 70368 729861452 1% /media/ephemeral0

A few things to note: the mount should use noatime which prevents extra unwanted IO to metadata. Also the readahead should be set low in general (using blockdev) but it is not important here since we’ll be testing with direct IO. Now let’s go ahead with testing the drive:

$ cat mongodb-linux-x86_64-2.6.0-rc2/mongoperf.conf
{
nThreads:1024,
fileSizeMB:1000,
mmf:false,
r:true,
w:true,
syncDelay:60
}
$ cd /media/ephemeral0/
$ sudo ~/mongodb-linux-x86_64-2.6.0-rc2/bin/mongoperf < ~/mongodb-linux-x86_64-2.6.0-rc2/mongoperf.conf
...
new thread, total running : 1
5474 ops/sec 21 MB/sec
new thread, total running : 2
7717 ops/sec 30 MB/sec
new thread, total running : 4
7740 ops/sec 30 MB/sec
new thread, total running : 8
7776 ops/sec 30 MB/sec
new thread, total running : 16
7548 ops/sec 29 MB/sec
new thread, total running : 32
7661 ops/sec 29 MB/sec
...
$ sudo iostat -x 2
avg-cpu: %user %nice %system %iowait %steal %idle
0.13 0.00 2.08 10.39 0.13 87.28
Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s avgrq-sz avgqu-sz await svctm %util
xvda 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
xvdb 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
xvdc 0.00 0.00 3777.50 7555.00 30220.00 30220.00 5.33 1.64 0.14 0.09 98.20

What’s interesting here is that we get about 5000 iops right off the bat with 1 thread, but then it hits a ceiling below 8000 iops even with more threads. Weird. Let’s try with XFS on the second drive.

$ sudo yum install xfsprogs
$ sudo mkfs.xfs /dev/sdc
$ sudo mkdir /media/ephemeral1
$ sudo mount /dev/sdc
$ cd /media/ephemeral1
$ sudo ~/mongodb-linux-x86_64-2.6.0-rc2/bin/mongoperf < ~/mongodb-linux-x86_64-2.6.0-rc2/mongoperf.conf
...
new thread, total running : 1
4801 ops/sec 18 MB/sec
new thread, total running : 2
7088 ops/sec 27 MB/sec
new thread, total running : 4
10104 ops/sec 39 MB/sec
...
new thread, total running : 256
33972 ops/sec 132 MB/sec
new thread, total running : 512
39590 ops/sec 154 MB/sec
new thread, total running : 1024
47873 ops/sec 187 MB/sec

It is much faster with XFS! It is maxing out around 47k iops with 1024 threads! XFS was supposed to be about 15% faster than EXT4 with SSD, but here it looks like it’s a different ballgame where it really scales with the number of threads used. Impressive.

$ sudo iostat -x 2
avg-cpu: %user %nice %system %iowait %steal %idle
1.17 0.00 22.37 75.75 0.71 0.00
Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s avgrq-sz avgqu-sz await svctm %util
xvda 0.00 3.50 0.00 3.00 0.00 52.00 17.33 0.01 2.00 2.00 0.60
xvdb 10.00 2.50 24143.00 25074.50 193240.00 193336.50 7.85 136.90 2.78 0.02 100.20
xvdc 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00

Checking the result with iostat, it does show 100% utilization with 25k read ops and 25k write ops per second.

Setting up a RAID

Our server has 2 drives, so ideally we would get 2x the throughput by using RAID0 on them. The usual way of doing this is through mdadm.

$ sudo umount /dev/sd[b,c]
$ sudo mdadm --create /dev/md0 -c 256 --level 0 --raid-devices 2 /dev/sdb /dev/sdc
mdadm: Defaulting to version 1.2 metadata
mdadm: array /dev/md0 started.
$ sudo mkfs.xfs /dev/md0
...
$ sudo vim /etc/fstab
$ grep md0 /etc/fstab
/dev/md0 /media/raid auto defaults,noatime,nofail,comment=cloudconfig 0 2
$ sudo mount /dev/md0
mount: mount point /media/raid does not exist
$ sudo mkdir /media/raid
$ sudo mount /dev/md0

Note here that I am forcing the stripe size to 256KB. If you don’t do that, the new 1.2 RAID format uses 512KB stripes which is too high for XFS or kernel buffers to match and performance will suffer (that seems like a bug on the RAID side..). Let’s see the performance:

$ sudo ~/mongodb-linux-x86_64-2.6.0-rc2/bin/mongoperf < ~/mongodb-linux-x86_64-2.6.0-rc2/mongoperf.conf
new thread, total running : 1
3587 ops/sec 14 MB/sec
new thread, total running : 2
5436 ops/sec 21 MB/sec
new thread, total running : 4
6432 ops/sec 25 MB/sec
new thread, total running : 8
7151 ops/sec 27 MB/sec
new thread, total running : 16
9397 ops/sec 36 MB/sec
...
new thread, total running : 256
60636 ops/sec 236 MB/sec
new thread, total running : 512
77010 ops/sec 300 MB/sec
new thread, total running : 1024
89326 ops/sec 348 MB/sec

Those results are very good. We went from 47k with 1 drive to 89k with 2 drives, so the performance loss appears to be minimal. An alternative is to use LVM instead of mdadm, which let’s you do RAID0 and also includes very useful features like snapshotting (backups will be easier!). Let see what performance LVM can give:

$ sudo umount /dev/md0
$ sudo mdadm --stop /dev/md0
mdadm: stopped /dev/md0
$ sudo pvcreate /dev/sd[b,c]
Physical volume "/dev/sdb" successfully created
Physical volume "/dev/sdc" successfully created
$ sudo vgcreate vg0 /dev/sd[b,c]
Volume group "vg0" successfully created
$ sudo lvcreate -i 8 -I 256 -n mongo -l 100%FREE vg0
Rounding size (381546 extents) down to stripe boundary size (381544 extents)
Number of stripes (8) must not exceed number of physical volumes (2)
$ sudo lvcreate -i 2 -I 256 -n mongo -l 100%FREE vg0
Logical volume "mongo" created
$ sudo mkfs.xfs /dev/vg
$ sudo vim /etc/fstab
$ sudo grep vg0 /etc/fstab
/dev/vg0/mongo /media/raid auto defaults,noatime,nofail,comment=cloudconfig 0 2
$ sudo mount /dev/vg0/mongo
$ cd /media/raid/
$ sudo ~/mongodb-linux-x86_64-2.6.0-rc2/bin/mongoperf < ~/mongodb-linux-x86_64-2.6.0-rc2/mongoperf.conf
new thread, total running : 1
3491 ops/sec 13 MB/sec
new thread, total running : 2
5863 ops/sec 22 MB/sec
new thread, total running : 4
9051 ops/sec 35 MB/sec
new thread, total running : 8
14206 ops/sec 55 MB/sec
new thread, total running : 16
21367 ops/sec 83 MB/sec
...
new thread, total running : 256
59237 ops/sec 231 MB/sec
new thread, total running : 512
70155 ops/sec 274 MB/sec
new thread, total running : 1024
84789 ops/sec 331 MB/sec
...
$ sudo iostat -x 2
avg-cpu: %user %nice %system %iowait %steal %idle
1.97 0.00 41.47 55.50 0.99 0.07 Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s avgrq-sz avgqu-sz await svctm %util xvda 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 xvdb 10.50 8.00 21199.50 21806.50 169684.00 200100.00 8.60 125.75 2.91 0.02 97.60 xvdc 16.00 7.00 21376.00 21672.00 171156.00 199024.00 8.60 115.85 2.69 0.02 94.80 dm-0 0.00 0.00 42663.00 42943.00 341304.00 399212.00 8.65 496.08 5.73 0.01 100.00

The performance of LVM is very good here too at about 85k iops. It seems to be in line with software RAID (madm) and actually has been reported to give better / more consistent performance in some cases.

Conclusion

In conclusion:

  • disk IO is the most important hardware metric since it is the typical bottleneck. Without good disk IO, you can forget all those sexy high volume applications with advanced indexing and aggregation
  • go for SSD if you can. An SSD will give you 100x the IO performance of an HDD.
  • test the disk performance before you commit to a box setup. There are good tools like bonnie++ and mongoperf. You might as well avoid surprises after you have committed a given setup to production.
  • use XFS, mount with noatime, set low read-ahead.
  • to compound the performance of disk, set up a RAID0 with MDADM or LVM (but note that any disk dying will kill the array)
  • make use of enough threads in the application. Now this works well for reading threads (you can have as many as you want) but for writing it is really a matter of the kernel’s background pdflush process which uses between 2 and 8 threads only. Sadly the writing part may not make full use of the SSD capacity depending on how well the OS implements it.