среда, 30 сентября 2009 г.

"У меня все ходы записаны!"

Функциональность ZFS history трудно переоценить.

Все административные действия, касающиеся ZFS-пулов, записываются в неотключаемый и неочищаемый журнал, располагающийся в метаданных.

История ведется начиная с создания пула и сохраняется в метаданных этого пула. Давайте посмотрим, как это выглядит:

root @ pegasus / # zpool history data
History for 'data':
2009-09-26.17:06:50 zpool create data mirror c2t0d0 c2t1d0
2009-09-26.17:07:13 zpool scrub data
2009-09-27.00:13:21 zfs create data/OraHome1
2009-09-27.00:13:23 zfs create data/OraHome2
2009-09-27.00:13:28 zfs create data/oradata
2009-09-27.00:13:42 zfs set recordsize=32k data/OraHome1
2009-09-27.00:13:43 zfs set recordsize=32k data/OraHome2
2009-09-27.00:16:23 zfs set atime=off data
2009-09-27.00:16:28 zfs set atime=off data/OraHome1
2009-09-27.00:16:29 zfs set atime=off data/OraHome2
2009-09-27.00:16:33 zfs set atime=off data/oradata
2009-09-27.00:18:09 zfs set compression=gzip-9 data/oradata
2009-09-27.14:58:15 zfs create data/oradata/flash_recovery_area
2009-09-27.14:58:20 zfs create data/oradata/plsql_libs
2009-09-27.14:58:44 zfs set recordsize=16k data/oradata/plsql_libs
2009-09-27.22:58:20 zpool import -f data
2009-09-27.22:59:01 zpool export data
2009-09-27.17:01:27 zpool import data
2009-09-27.17:01:35 zpool scrub data
2009-09-27.17:02:24 zpool clear data
2009-09-27.17:03:23 zfs destroy -r data/oradata
2009-09-27.17:06:09 zfs destroy -r data/oradata
2009-09-27.17:06:56 zfs create data/db
2009-09-27.17:07:13 zfs create data/db/oradata
2009-09-27.17:07:21 zfs create data/db/flash_recovery_area
2009-09-27.17:07:26 zfs create data/db/plsql_libs
2009-09-27.17:07:47 zfs set recordsize=16k data/db/plsql_libs
2009-09-27.17:08:48 zfs set mountpoint=/data/oradata data/db/oradata
2009-09-27.17:09:18 zfs set mountpoint=/data/oradata/flash_recovery_area data/db/flash_recovery_area
2009-09-27.17:09:32 zfs set mountpoint=/data/oradata/plsql_libs data/db/plsql_libs
2009-09-27.17:09:52 zfs set mountpoint=none data/db
2009-09-27.17:10:25 zpool clear data
2009-09-27.17:11:41 zpool scrub data
2009-09-27.17:23:00 zpool scrub -s data
2009-09-27.17:24:22 zpool detach data c2t0d0
2009-09-27.17:35:10 zpool attach data c2t1d0 c2t0d0
2009-09-27.17:52:15 zpool detach data c2t0d0
2009-09-27.18:49:20 zpool attach data c2t1d0 c2t0d0
2009-09-27.19:33:11 zfs create data/apex
2009-09-27.19:33:21 zfs set recordsize=16k data/apex
2009-09-27.19:37:52 zpool detach data c2t0d0
2009-09-27.21:30:43 zpool attach data c2t1d0 c2t0d0
2009-09-28.22:53:36 zpool scrub data
2009-09-28.23:25:05 zfs snapshot -r data/db/plsql_libs@snapshot
2009-09-28.23:26:21 zfs destroy -r data/db/plsql_libs@snapshot
2009-09-28.23:45:06 zfs snapshot -r data/db/plsql_libs@snapshot
2009-09-28.23:46:05 zfs destroy -r data/db/plsql_libs@snapshot
2009-09-28.23:47:45 zfs receive -dFv data/db/plsql_libs
2009-09-28.23:47:50 zfs destroy -r data/db/plsql_libs@snapshot
2009-09-28.23:48:27 zfs destroy -r data/db/plsql_libs
2009-09-28.23:49:50 zfs receive -dFv data
2009-09-28.23:49:50 zfs destroy -r data@snapshot
2009-09-30.00:35:48 zfs snapshot -r data/db/plsql_libs@snapshot
2009-09-30.00:36:58 zfs destroy -r data/db/plsql_libs@snapshot
2009-09-30.01:01:50 zfs snapshot -r data/db/plsql_libs@snapshot
2009-09-30.01:02:44 zfs destroy -r data/db/plsql_libs@snapshot
2009-09-30.01:03:53 zfs snapshot -r data/db/plsql_libs@snapshot
2009-09-30.01:04:47 zfs destroy -r data/db/plsql_libs@snapshot
2009-09-30.01:07:56 zfs snapshot -r data/db/plsql_libs@snapshot
2009-09-30.01:08:50 zfs destroy -r data/db/plsql_libs@snapshot
2009-09-30.01:35:00 zfs snapshot -r data/db/plsql_libs@snapshot
2009-09-30.01:35:51 zfs destroy -r data/db/plsql_libs@snapshot
2009-09-30.01:35:51 zfs snapshot -r data/db/plsql_libs@snapshot
2009-09-30.01:36:44 zfs destroy -r data/db/plsql_libs@snapshot
2009-09-30.01:43:01 zfs snapshot -r data/db/plsql_libs@snapshot
2009-09-30.01:43:55 zfs destroy -r data/db/plsql_libs@snapshot
2009-09-30.01:44:11 zfs destroy data/db/plsql_libs
2009-09-30.01:45:49 zfs receive -dF data
2009-09-30.01:45:49 zfs destroy -r data@snapshot

Причем функциональность history позволяет практически выполнять аудит действий в пределах пула:

root @ pegasus / # zpool history -l data |more
History for 'data':
2009-09-26.17:06:50 zpool create data mirror c2t0d0 c2t1d0 [user root on pegasus
:global]
2009-09-26.17:07:13 zpool scrub data [user root on pegasus:global]
2009-09-27.00:13:21 zfs create data/OraHome1 [user root on pegasus:global]
2009-09-27.00:13:23 zfs create data/OraHome2 [user root on pegasus:global]
2009-09-27.00:13:28 zfs create data/oradata [user root on pegasus:global]
2009-09-27.00:13:42 zfs set recordsize=32k data/OraHome1 [user root on pegasus:g
lobal]
2009-09-27.00:13:43 zfs set recordsize=32k data/OraHome2 [user root on pegasus:g
lobal]
2009-09-27.00:16:23 zfs set atime=off data [user root on pegasus:global]
.....

В истории мы видим пользователей и зону, что, например, при делегированном администрировании в зонах либо использовании RBAC достаточно актуально.

Так что, у нас действительно все ходы записаны. :)

вторник, 29 сентября 2009 г.

VirtualBox, ZFS и зеркалирование

Намедни я дважды напоролся на одну проблему, связанную с гостевым Solaris 10 на VirtualBox, установленной на ZFS.

Оказывается, в такой среде ZFS не любит - и еще как не любит! - дефрагментации дисков хозяйской ОС.

После выполнения дефрагментации файлов *.VDI пулы бьются, с неустранимыми ошибками. Если не позаботиться сделать снапшотов и не сохранить из за пределами виртуальной машины или при отсутствии зеркалирования - все пропало, шеф, все пропало! ;)

Не надо спешить кидать помидоры и орать "Sun must die!". Горячки не порите.

Оставим в покое особенности поведения VirtualBox на различных хозяйских ОС.

Давайте не спеша разберемся, что именно пропало и почему.

Во-первых: да, у ZFS встроенная защита контрольными суммами:

root @ pegasus / # zfs get checksum data
NAME PROPERTY VALUE SOURCE
data checksum on default

включенная по умолчанию. Все верно.

Однако контрольные суммы обеспечивают лишь обнаружение повреждений, но никак не обладают достаточно избыточностью для исправления ошибок на лету, физических и логических.

Для того, чтобы исправить ошибки файловой системы требуется избыточность. Которая обеспечивается двумя способами:

root @ pegasus / # zfs get copies data
NAME PROPERTY VALUE SOURCE
data copies 1 default

Как показано выше - возможностью записи данных избыточными копиями. Не самый идеальный способ - уменьшение реальных объемов хранения в N-раз, к тому же, копии лежат в пределах одной и той же файловой системы и при ряде повреждений будут повреждены полностью.

Второй способ, более православный - но и более дорогостоящий - зеркалирование пулов:

root @ pegasus / # zpool status data
pool: data
state: ONLINE
scrub: none requested
config:

NAME STATE READ WRITE CKSUM
data ONLINE 0 0 0
mirror ONLINE 0 0 0
c2t1d0 ONLINE 0 0 0
c2t0d0 ONLINE 0 0 0

errors: No known data errors

В такой конфигурации ZFS приобретает свойство self-healing, основанное при единичных логических сбоях на автоматической замене поврежденных блоков на целые, берущиеся с зеркала (зеркал). При этом, если повреждения физические (как было в моем случае - после пересборки файловых фрагментов родительской ОС ZFS посчитал блоки сбойными), то это вылечивается процедурой zfs scrub.

Как известно, scrub и ресильверинг (resilvering) - кровные родственники, причем если при отсутствии зеркал scrub всего лишь проверяет контрольные суммы (неповрежденных блоков ему взять просто неоткуда), то при наличии зеркала поврежденные блоки немедленно извлекаются с зеркала и кладутся на место. Если нижележащее железо физически в порядке, то процедура ресильверинга (что реально выполняла ZFS - скруббинг или ресильверинг - показывает команда zpool status) завершается очень быстро и пул снова приходит в целостное состояние.

Иначе говоря, если попытаться проделать фокус с выносом ZFS-пула мусором из /dev/urandom на незеркалироваанном пуле, то вы убьете его быстро и необратимо. Однако в случае зеркал восстановление логических повреждений происходит в реальном времени и закончится как раз в момент окончания выполнения команды dd.

Понятное дело, что зеркалирование возможно далеко не всегда. Прежде всего, нужно иметь эти самые раздельные устройства (обеспечение избыточности модификацией параметра copies кажется чрезмерным расточительством). В противном случае только физический бэкап спасет гигантов мысли.

Следует учитывать еще аспект производительности. Зеркалирование ZFS с дисками (устройствами) физически подключенными к одному контроллеру означает, как правило, деградацию производительности. Размещение частей зеркал на одних и тех же дисках означает то же самое.

В описываемом случае с VirtualBox диск родительской машины физически один, разбитый на два раздела по административным соображениям:

C:\...инистратор\.VirtualBox\HardDisks>dir
Том в устройстве C имеет метку SYSTEM
Серийный номер тома: ECF8-A9D1

Содержимое папки C:\Documents and Settings\Администратор\.VirtualBox\HardDisks

27.09.2009 02:49 [dir] .
27.09.2009 02:49 [dir] ..
29.09.2009 11:08 8 941 289 984 pegasus_data_disk1.vdi
29.09.2009 11:08 6 742 409 728 pegasus_disk1.vdi
2 файлов 15 683 699 712 байт
2 папок 4 529 758 208 байт свободно

D:\.VirtualBox\HardDisks>dir
Том в устройстве D имеет метку DATA
Серийный номер тома: 14E1-00B2

Содержимое папки D:\.VirtualBox\HardDisks

27.09.2009 02:54 [dir] .
27.09.2009 02:54 [dir] ..
29.09.2009 11:08 9 010 496 000 pegasus_data_disk2.vdi
29.09.2009 11:08 6 382 748 160 pegasus_disk2.vdi
2 файлов 15 393 244 160 байт
2 папок 33 612 677 120 байт свободно

Понятно, что разнос виртуальных зеркал по логическим дискам преследует исключительно уменьшение вероятности одновременного и идентичного логического повреждения зеркал. Производительность в данном случае не является и не может являться целью.

Данная конфигурация позволяет лишь наделить пулы гостевой ОС способностью к восстановлению (а зеркала корневого пула - еще и способностью загрузиться с менее поврежденного зеркала для выполнения ресильверинга).

Прошу обратить внимание, что все вышенаписанное отнюдь не является критикой ZFS. Сама абсурдность конфигурации (ZFS под гостевой ОС поверх совершенно и принципиально другой файловой системы) почти исключает чудесное беспроблемное функционирование.

Тем не менее, сделаем выводы. Как и во всех других случаях, правильное использование различных физических и логических топологий файловых систем, позволяет минимизировать возможные проблемы и потенциальный (а иногда и весьма кинетический) ущерб.

среда, 23 сентября 2009 г.

Смерть Кощея в игле, игла в яйце, яйцо в утке, утка в зайце, заяц в сундуке...

В далеком 2007м году я писал о том, что маниакальная виртуализация или виртуальная маниакальность это зло. Собственно, я и посейчас так считаю. Однако же, сподобился недавно сам соорудить подобное зло. Сразу скажу - не от хорошей жизни и не в расчете на хорошие результаты, однако же...

Задача, собственно, стояла следующим образом. Потребовалось соорудить в одном флаконе сановский сервер и ноутбук под виндой. Ну, нужно одновременно клиент, который существует в природе только для винды и еще парочки платформ (коими ноутбук, в общем, не слишком является, помимо того, что пингвин в данном случае не выход - для маньяков говорю) и сервер приличный. Пингвин поставить отпало (по многим соображениям, одно из которых - да пошел бы он...), спаркбуки почили в бозе. Осталось одно. РеанимационнаяВиртуальная машина.

Ну, с богом. Берем Sun Virtual Box. Громоздим его на машину. Монтируем образы Solaris 10 5/09. Вперед.

Первая засада. На ноуте 4 Гб памяти. Однако отрезать виртуальной машине больше 1,5 Гб не получается. Ладно, черт с ним. В конце-концов, лихого сервера нам не трэба, перетопчется.

Извратимся. Поставим-ка его на ZFS, да на корневой пул. Во как. На NTFS (хозяйская ось - 2003 сервер), да Солярис на ZFS, да загоним на него Оракл... ;)

До кучи бросим в него Sun Studio 12, врубим натуральную компиляцию всего и вся PL/SQL в базе - так, из жлобства, нафигачим файловые системы в иерархию с подходящими размерами recordsize...

Ну-с, что тут у нас. Долго ли, коротко ли, топтаться пришлось пару дней (с перерывами), однако затоптался. Что в сухом остатке?

Да, мсье знает толк в извращениях...













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

Разумеется, гуй соляриса пошел в жертву свиноголовым богам. ;) Чорная консоль.

Оракл, как ни странно, вкатился, ZFS отнюдь не стал выпендриваться "Дай сюда еще гигабайт":

root @ pegasus / # top
last pid: 503; load avg: 0.05, 0.22, 1.00; up 0+00:21:52 12:13:01
45 processes: 44 sleeping, 1 on cpu
CPU states: 96.5% idle, 0.0% user, 3.5% kernel, 0.0% iowait, 0.0% swap
Memory: 1500M phys mem, 466M free mem, 1024M total swap, 1024M free swap

PID USERNAME LWP PRI NICE SIZE RES STATE TIME CPU COMMAND
503 root 1 54 0 2320K 1288K cpu/0 0:00 0.14% top
353 oracle 11 59 0 512M 443M sleep 0:00 0.12% oracle
407 oracle 3 59 0 21M 1500K sleep 0:00 0.05% httpd
349 oracle 12 59 0 515M 442M sleep 0:00 0.04% oracle
7 root 14 59 0 12M 9828K sleep 0:03 0.03% svc.startd
9 root 15 59 0 10M 9516K sleep 0:05 0.03% svc.configd
363 oracle 1 59 0 511M 442M sleep 0:01 0.01% oracle
394 oracle 16 59 0 19M 7776K sleep 0:00 0.00% opmn
402 oracle 1 59 0 21M 7400K sleep 0:00 0.00% httpd
361 oracle 1 59 0 515M 454M sleep 0:01 0.00% oracle
359 oracle 1 59 0 512M 446M sleep 0:01 0.00% oracle
212 root 18 59 0 13M 9132K sleep 0:01 0.00% fmd
355 oracle 1 59 0 511M 445M sleep 0:00 0.00% oracle
473 oracle 1 59 0 513M 443M sleep 0:00 0.00% oracle
357 oracle 1 59 0 511M 443M sleep 0:00 0.00% oracle

Все живое, не скажу, разумеется, что феерически быстро - та же система на голой машине бегала бы, разумеется, значительно веселей - но для игрищ с блудницами и где шляпу повесил-там и офис - очень даже удовлетворительно.

Оно конечно, сюда б гипервизор лучше смотрелся - но Сан-жлобы, гипервизор не дают. А пытаться ставить гипервизор не от Сан - как-то стремно показалось да и под руку вовремя не попался. А теперь уже поздняк метадзе - переставлять винду не мой национальный вид спорта. :)

Что ж, получилась настоящая смерть Кощея. Как и сказано в заголовке статьи... ;)

вторник, 22 сентября 2009 г.

Shared server - это очень просто

В подавляющем большинстве случаев мы используем при работе с базой режим DEDICATED servers. То есть, для каждого соединения сервер запускает выделенный серверный процесс.

Эта схема хороша для чистого клиент-сервера, при условии, что достаточно памяти для запуска всех без исключения процессов и при этом соединения держатся достаточно долго, для того, чтобы не приходилось сессии разрывать и устанавливать заново.

Существует, однако, достаточно распространенная сейчас архитектура приложений, когда такой режим очень нехорош с точки зрения накладных расходов, и, соответственно, ведет к ухудшению отклика системы.

Это Web-приложения, работающие с базой. mod_plsql работает как Oracle*Net клиент, и, при каждом HTTP-вызове устанавливает и разрывает соединение с базой. Это ведет к большому количеству запусков выделенных процессов, высокому переключению контекстов и, в конечном итоге, ухудшает отклик системы.

Все бы ничего, но при резких скачках нагрузки и фиксированных настройках Web-листенера и базы статистика фиксирует достаточно часто появляющиеся ошибки "503: Server busy". Что в принципе говорит о том, что база не успевает реагировать на запросы ввиду того, что приходится быстро запускать серверные процессы в достаточно большом количестве.

Технически Оракл иногда рекомендует в подобных случаях (для Web-приложений) использовать Shared server. (Раньше в Oracle*Net была возможность в DEDICATED-режиме установить некоторое количество prespawned-серверных процессов, сейчас ее нет).

Считается, однако, что Shared-сервер непонятно, как сконфигурировать и как использовать.

На самом деле все очень просто.

Возьмем реальный пример базы, которая находится на одном хосте с Web-листенером. Допустим, что мы хотим ее перевести для Web-соединений (и только для них) в режим Shared server. Оставив сервис DEDICATED для административных целей.

Как мы это сделаем?

Во-первых, выключим существующие диспетчеры по умолчанию:

alter system set dispatchers='';

Делаем мы это для того, чтобы не плодить толпу слушающих диспетчеров, потому что нижеприведенная команда добавляет диспетчеры к существующим. По умолчанию в десятке есть диспетчер XDB, он нам не больно-то и нужен. Посему сбросим его и будем задавать собственные диспетчеры.

Затем приступим к конфигурированию собственно Shared-сервиса базы:

alter system set large_pool_size=8m scope=both;

alter system set dispatchers="(ADDRESS=(PROTOCOL=tcp)(PORT=16384))(DISPATCHERS=2)" scope=both;

alter system set shared_server_sessions=100 scope=both;

Параметр shared_server_sessions требуется задать для скорости, причем его значение меньше, чем значение processes (в Oracle Reference объясняется, почему это нужно сделать). В нашем случае processes = 150. Порт диспетчеров мы зафиксировали специально, потому что в противном случае он будет при каждом запуске инстанса меняться.

Запустим два серверных процесса начально:

alter system set shared_servers=2 scope=both;

и ограничим максимальное количество процессов-диспетчеров и серверных процессов разумной величиной:

alter system set max_dispatchers=5 scope=both;
alter system set max_shared_servers=5 scope=both;

Кстати, перезапускать инстанс не нужно, процессы стартуют автоматически. После чего листенер автоматически регистрирует новый сервис (кстати, делает он это совсем не сразу, а спустя иногда несколько минут после появления диспетчеров - динамическая регистрация сервисов):

oracle @ hostname1 ~ $ lsnrctl status

LSNRCTL for Solaris: Version 10.2.0.4.0 - Production on 22-СЕН-2009 12:58:03

Copyright (c) 1991, 2007, Oracle. All rights reserved.

Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=hostname1)(PORT=1521)(QUEUESIZE=10)(SEND_BUF_SIZE=65536)(RECV_BUF_SIZE=65536)))
STATUS of the LISTENER
------------------------
Alias LISTENER
Version TNSLSNR for Solaris: Version 10.2.0.4.0 - Production
Start Date 21-СЕН-2009 11:49:01
Uptime 1 days 1 hr. 9 min. 2 sec
Trace Level off
Security ON: Password or Local OS Authentication
SNMP OFF
Listener Parameter File /export/home/OraHome1/app/oracle/product/10.2.0/network/admin/listener.ora
Listener Log File /export/home/OraHome1/app/oracle/product/10.2.0/network/log/listener.log
Listening Endpoints Summary...
(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=www.yvoinov.com)(PORT=1521)))
Services Summary...
Service "PLSExtProc" has 1 instance(s).
Instance "PLSExtProc", status UNKNOWN, has 1 handler(s) for this service...
Service "SUN10.domain.com" has 2 instance(s).
Instance "SUN10", status UNKNOWN, has 1 handler(s) for this service...
Instance "SUN10", status READY, has 2 handler(s) for this service...
Service "SUN10_XPT.domain.com" has 2 instance(s).
Instance "SUN10", status UNKNOWN, has 1 handler(s) for this service...
Instance "SUN10", status READY, has 2 handler(s) for this service...
The command completed successfully

DEDICATED-сервис назывался SUN10, Shared-сервис называется SUN10_XPT, имя присвоено автоматически.

Однако теперь нужно сделать две вещи. Во-первых, настроить листенер. В нашем случае настройка должна быть не вполне обычная:

SID_LIST_LISTENER =
(SID_LIST =
(SID_DESC =
(GLOBAL_DBNAME = SUN10.domain.com)
(SID_NAME = SUN10)
(ORACLE_HOME = /export/home/OraHome1/app/oracle/product/10.2.0)
)
(SID_DESC =
(GLOBAL_DBNAME = SUN10_XPT.domain.com)
(SID_NAME = SUN10)
(ORACLE_HOME = /export/home/OraHome1/app/oracle/product/10.2.0)
)
(SID_DESC =
(SID_NAME = PLSExtProc)
(ORACLE_HOME = /export/home/OraHome1/app/oracle/product/10.2.0)
(PROGRAM = extproc)
)
)

ADMIN_RESTRICTIONS_LISTENER = ON

LOGGING_LISTENER = ON

LISTENER =
(DESCRIPTION_LIST =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = www.domain.com)(PORT = 1521)(QUEUESIZE = 10)(SEND_BUF_SIZE = 65536)(RECV_BUF_SIZE = 65536))
)
)
)

Заметьте две вещи - мы не добавили строку прослушивания для порта 16386 shared-сервера и добавили статическую регистрацию для него же. Первое - не нужно (листенер не запустится), второе нужно дабы исключить таймауты при автоматической регистрации сервисов.

Во-вторых, нужно настроить mod_plsql для соединения не с DEDICATED, а с SHARED-сервером, ну и для остальных клиентов базы разделить сервисы, чтобы, скажем, администраторы всегда ходили на DEDICATED-сервис.

Изменим одну запись в dads.conf и перезапустим Web-сервер:

#PlsqlDatabaseConnectString localhost:1521:SUN10.domain.com ServiceNameFormat
PlsqlDatabaseConnectString localhost:16384:SUN10_XPT.domain.com ServiceNameFormat

Для решения последней задачи добавим в tnsnames.ora запись о shared-сервисе и изменим запись dedicated-сервиса:

SUN10 =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521))
)
(CONNECT_DATA =
(SERVICE_NAME = SUN10.domain.com)
(SERVER=dedicated)
)
)

SUN10_XPT =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 16384))
)
(CONNECT_DATA =
(SERVICE_NAME = SUN10_XPT.domain.com)
(SERVER=shared)
)
)

В общем-то, это все. Осталось проверить, работает ли сервис:

oracle @ hostname ~ $ lsnrctl services

LSNRCTL for Solaris: Version 10.2.0.4.0 - Production on 22-СЕН-2009 13:06:51

Copyright (c) 1991, 2007, Oracle. All rights reserved.

Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=www.domain.com)(PORT=1521)(QUEUESIZE=10)(SEND_BUF_SIZE=65536)(RECV_BUF_SIZE=65536)))
Services Summary...
Service "PLSExtProc" has 1 instance(s).
Instance "PLSExtProc", status UNKNOWN, has 1 handler(s) for this service...
Handler(s):
"DEDICATED" established:0 refused:0
LOCAL SERVER
Service "SUN10.domain.com" has 2 instance(s).
Instance "SUN10", status UNKNOWN, has 1 handler(s) for this service...
Handler(s):
"DEDICATED" established:0 refused:0
LOCAL SERVER
Instance "SUN10", status READY, has 2 handler(s) for this service...
Handler(s):
"D000" established:0 refused:0 current:8 max:992 state:ready
DISPATCHER
(ADDRESS=(PROTOCOL=tcp)(HOST=www.domain.com)(PORT=16384))
"DEDICATED" established:0 refused:0 state:ready
LOCAL SERVER
Service "SUN10_XPT.domain.com" has 2 instance(s).
Instance "SUN10", status UNKNOWN, has 1 handler(s) for this service...
Handler(s):
"DEDICATED" established:0 refused:0
LOCAL SERVER
Instance "SUN10", status READY, has 2 handler(s) for this service...
Handler(s):
"D000" established:0 refused:0 current:8 max:992 state:ready
DISPATCHER
(ADDRESS=(PROTOCOL=tcp)(HOST=www.domain.com)(PORT=16384))
"DEDICATED" established:0 refused:0 state:ready
LOCAL SERVER
The command completed successfully

Работает. Посмотрим представления базы данных:

SQL> select dispatcher, status, queue from v$circuit;

DISPATCHER STATUS QUEUE
---------------- ---------------- ----------------
00000003BFE619B0 NORMAL NONE
00000003BFE619B0 NORMAL NONE
00000003BFE619B0 NORMAL NONE
00000003BFE619B0 NORMAL NONE
00000003BFE619B0 NORMAL NONE
00000003BFE619B0 NORMAL NONE

6 rows selected.

SQL> select name, circuit, idle, busy, requests from v$shared_server;

NAME CIRCUIT IDLE BUSY REQUESTS
---- ---------------- ---------- ---------- ----------
S000 00 9300193 6556 7667
S001 00 9299953 6842 7872

Как видно, все, что мы хотели - мы и получили. Со временем можно подкорректировать количество процессов.

Что до результатов - того, ради чего это все затевалось - то субъективные замеры показывают минимум двукратное улучшение отклика, что, в общем, легко объяснимо - постоянно запущенные диспетчеры и серверные процессы всегда готовы принять сессию из Web, и почти прекратились ошибки 503 Server Busy.

PS. В Oracle 10R2 на платформах x86/x64 не советую включать на диспетчерах connection pooling - из-за ошибки в ядре база начинает дампить с ошибками 7445 и 600. Конечно, pooling сильно увеличивает нагрузочную способность диспетчеров - однако стабильность будет никакая.

суббота, 12 сентября 2009 г.

Линейное наращивание емкости и организация ZFS-пулов

Однажды на курсах мне задали интересный вопрос. Звучал он так:

При аттаче устройств в пул они добавляются в зеркало. А какую команду использовать, чтобы просто присоединить к пулу еще одно устройство? Как при конкатенированных RAID-томах?

Очень просто (значительно проще, чем сделать то же самое с Solaris Volume manager):

server5# zpool status
pool: rpool
state: ONLINE
scrub: none requested
config:

NAME STATE READ WRITE CKSUM
rpool ONLINE 0 0 0
mirror ONLINE 0 0 0
c0t0d0s0 ONLINE 0 0 0
c0t1d0s0 ONLINE 0 0 0

errors: No known data errors

server5# zpool create data c0t0d0s6

server5# zfs list data
NAME USED AVAIL REFER MOUNTPOINT
data 89.5K 39.6G 1K /data

server5# zpool add data c0t1d0s6

server5# zfs list data
NAME USED AVAIL REFER MOUNTPOINT
data 110K 79.2G 18K /data

server5# zpool status
pool: data
state: ONLINE
scrub: none requested
config:

NAME STATE READ WRITE CKSUM
data ONLINE 0 0 0
c0t0d0s6 ONLINE 0 0 0
c0t1d0s6 ONLINE 0 0 0

errors: No known data errors

pool: rpool
state: ONLINE
scrub: none requested
config:

NAME STATE READ WRITE CKSUM
rpool ONLINE 0 0 0
mirror ONLINE 0 0 0
c0t0d0s0 ONLINE 0 0 0
c0t1d0s0 ONLINE 0 0 0

errors: No known data errors
server5#

Недостатком подобных томов, как и в случае конкатенированных RAID-0, разумеется, будет невысокая производительность и полное отсутствие отказоустойчивости. Зато - полная емкость дисковых устройств. ;)

Не поможет ли гигантам мысли RAID-Z?

Попробуем:

server5# zpool create data raidz c0t0d0s6 c0t1d0s6

server5# zpool status data
pool: data
state: ONLINE
scrub: none requested
config:

NAME STATE READ WRITE CKSUM
data ONLINE 0 0 0
raidz1 ONLINE 0 0 0
c0t0d0s6 ONLINE 0 0 0
c0t1d0s6 ONLINE 0 0 0

errors: No known data errors
server5# zfs list data
NAME USED AVAIL REFER MOUNTPOINT
data 89.5K 39.6G 1K /data

Нет. Не помог. Емкость не удвоилась.

Что до скорости, то давайте, пожалуй, оценим - что быстрее.

Вот результаты RAID-Z:

server5# date; mkfile 1g /data/test.file; date
Saturday, September 12, 2009 4:54:08 PM ALMT
Saturday, September 12, 2009 4:54:32 PM ALMT

26 секунд создавался файл 1 Гб.

Создадим снова конкатенированный ZFS-пул и посмотрим на его скорость:

server5# zpool create data c0t0d0s6 c0t1d0s6

server5# zfs list data
NAME USED AVAIL REFER MOUNTPOINT
data 1.00G 78.2G 1.00G /data

server5# zpool status data
pool: data
state: ONLINE
scrub: none requested
config:

NAME STATE READ WRITE CKSUM
data ONLINE 0 0 0
c0t0d0s6 ONLINE 0 0 0
c0t1d0s6 ONLINE 0 0 0

errors: No known data errors

server5# date; mkfile 1g /data/test.file; date
Saturday, September 12, 2009 4:56:10 PM ALMT
Saturday, September 12, 2009 4:56:20 PM ALMT

10 секунд создавался файл 1 Гб.

Создадим для сравнения зеркало ZFS и померяем скорость:

server5# zpool create data mirror c0t0d0s6 c0t1d0s6
server5# zpool status data
pool: data
state: ONLINE
scrub: none requested
config:

NAME STATE READ WRITE CKSUM
data ONLINE 0 0 0
mirror ONLINE 0 0 0
c0t0d0s6 ONLINE 0 0 0
c0t1d0s6 ONLINE 0 0 0

errors: No known data errors

server5# date; mkfile 1g /data/test.file; date
Saturday, September 12, 2009 4:59:10 PM ALMT
Saturday, September 12, 2009 4:59:30 PM ALMT

20 секунд создавался файл 1 Гб.

А теперь вопрос - почему зеркало оказалось медленней, и неожиданно вырвался вперед конкатенированный пул?

Ответ очевиден. Взгляните на слайсы, образующие пул. Они сидят на одном контроллере.

Вот и весь ответ.

При подключении дисков на разные контроллеры скоростной перевес был бы у зеркала, а конкатенированный пул, напротив, проиграл бы при однопоточной записи (и чтении).

В данном случае против нас играет однопоточная запись и два диска на одном контроллере - и кэширование ZFS не играет никакой роли:

server5# date; mkfile 1g /data/test.file; date
Saturday, September 12, 2009 4:59:10 PM ALMT
Saturday, September 12, 2009 4:59:30 PM ALMT

server5# date; mkfile 1g /data/test.file; date
Saturday, September 12, 2009 5:06:32 PM ALMT
Saturday, September 12, 2009 5:06:52 PM ALMT

server5# date; mkfile 1g /data/test.file; date
Saturday, September 12, 2009 5:07:02 PM ALMT
Saturday, September 12, 2009 5:07:21 PM ALMT

Учтите одну вещь. Производительность дисковой системы - вещь сильно зависящая от физической топологии сториджа (контроллеры-шлейфы-диски), от количества шпинделей и, самое главное, от характера ввода-вывода.

В принципе, наш тест некорректен. Однопоточная запись одного файла - не тест.

Как практик, однако, могу заметить, что данная тенденция сохраняется почти всегда в реальных системах.

Говоря другими словами, и RAID-Z, и его родственник RAID-5 проигрывают в скорости зеркалу, причем тем сильнее, чем большее количество шпинделей при большем количестве контроллеров используется в массиве. И уж, разумеется, всяко больше, чем показали наши тесты.

Резюмируя вышесказанное и проделанное. ZFS-не чудотворная икона. Да, она масштабируемая и распараллеливаемая. Но - по-прежнему важна топология сториджа, по-прежнему важен адекватный выбор организации массивов соответственно задаче, по-прежнему родня RAID-5 в пролете по скорости.

пятница, 11 сентября 2009 г.

Резервирование и восстановление ZFS IV

Как-то сложно мы восстанавливали датасет. "Хлопотно это". Зачем-то клоны делать со снапшотов, переименований куча, игры с точками монтирования. Все же пул нам удалось восстановить легко и непринужденно. Нельзя ли что-нибудь втереть, или вправить, или еще что-нибудь?

Пришлось немного поиграть с процедурой восстановления. Опуская подробности и неудачи, приведем конечный результат:

server5# zfs list
NAME USED AVAIL REFER MOUNTPOINT
data 150M 19.4G 50.0M /data
data/work1 100M 19.4G 20K /data/work1
data/work1/user1 100M 19.4G 100M /data/work1/user1
rpool 14.6G 12.7G 94K /rpool
rpool/ROOT 6.60G 12.7G 18K legacy
rpool/ROOT/zfsroot 6.60G 12.7G 6.51G /
rpool/ROOT/zfsroot/var 101M 12.7G 101M /var
rpool/dump 4.00G 12.7G 4.00G -
rpool/export 117K 12.7G 20K /export
rpool/export/home 97K 12.7G 97K /export/home
rpool/swap 4G 16.6G 130M -

server5# gzcat server5.data%work2.0.zfs.gz | zfs receive -vdF data
receiving full stream of data/work2@snapshot into data/work2@snapshot
received 15.0KB stream in 1 seconds (15.0KB/sec)
receiving full stream of data/work2/user2@snapshot into data/work2/user2@snapshot
received 100MB stream in 4 seconds (25.1MB/sec)

server5# zfs list
NAME USED AVAIL REFER MOUNTPOINT
data 250M 19.3G 50.0M /data
data/work1 100M 19.3G 20K /data/work1
data/work1/user1 100M 19.3G 100M /data/work1/user1
data/work2 100M 19.3G 20K /data/work2
data/work2@snapshot 0 - 20K -
data/work2/user2 100M 19.3G 100M /data/work2/user2
data/work2/user2@snapshot 0 - 100M -
rpool 14.6G 12.7G 94K /rpool
rpool/ROOT 6.60G 12.7G 18K legacy
rpool/ROOT/zfsroot 6.60G 12.7G 6.51G /
rpool/ROOT/zfsroot/var 101M 12.7G 101M /var
rpool/dump 4.00G 12.7G 4.00G -
rpool/export 117K 12.7G 20K /export
rpool/export/home 97K 12.7G 97K /export/home
rpool/swap 4G 16.6G 130M -

server5# zfs destroy -r data/work2@snapshot

server5# zfs list
NAME USED AVAIL REFER MOUNTPOINT
data 250M 19.3G 50.0M /data
data/work1 100M 19.3G 20K /data/work1
data/work1/user1 100M 19.3G 100M /data/work1/user1
data/work2 100M 19.3G 20K /data/work2
data/work2/user2 100M 19.3G 100M /data/work2/user2
rpool 14.6G 12.7G 94K /rpool
rpool/ROOT 6.60G 12.7G 18K legacy
rpool/ROOT/zfsroot 6.60G 12.7G 6.51G /
rpool/ROOT/zfsroot/var 101M 12.7G 101M /var
rpool/dump 4.00G 12.7G 4.00G -
rpool/export 117K 12.7G 20K /export
rpool/export/home 97K 12.7G 97K /export/home
rpool/swap 4G 16.6G 130M -

Ха, а ведь датасеты восстаналивать можно и коротким путем! Кратко - датасет нужно тупо восстаналивать точно так же, как и весь пул. Содержимое будет перезаписано поверх существующих данных, если таковые имеются (в существующих файловых системах).

Теперь мы практически созрели написать скрипт zfs_restore.sh:

#!/sbin/sh

#
# ZFS filesystem(s) restore from archive.
#
# ZFS archives uses for bare-metal restore
# and systems cloning.
#
# Archive names can be incremental, like that:
# [hostname].[pool|dataset].n.zfs<.gz>, n=0,1,2...
# Note: Do not rename archive files! Filenames will
# use to recovery purposes.
#
# Version 1.0 (C) 2009 Y.Voinov
#
# If you specify archive name (can be with destination directory)
# in command line, script will run in non-interactive mode.
# If you not specify pool/dataset name neither command line nor
# interactive mode, it will be read from archive name.
#
# ident "@(#)zfs_restore.sh 1.0 09/11/09 YV"
#

#############
# Variables #
#############

# Snapshots extension by default
# Correct it if these snapshots already exists in the system
SNAP_EXT="snapshot"
# Archiver name
ARC_NAME="gzip"
# Archive file suffix
ARC_SUFFIX=".gz"
# Default archive extension
ext="zfs"

# OS utilities
CUT=`which cut`
DATE=`which date`
ECHO=`which echo`
FILE=`which file`
GREP=`which grep`
GZIP=`which gzip`
ID=`which id`
PRINTF=`which printf`
SED=`which sed`
UNAME=`which uname`
WHOAMI=`which whoami`
ZFS=`which zfs`

OS_VER=`$UNAME -r|$CUT -f2 -d"."`
OS_NAME=`$UNAME -s|$CUT -f1 -d" "`
OS_FULL=`$UNAME -sr`

###############
# Subroutines #
###############

check_os ()
{
# Check OS
$PRINTF "Checking OS... "
if [ "$OS_NAME" = "SunOS" -a "$OS_VER" -lt "10" ]; then
$ECHO "ERROR: Unsupported OS: $OS_FULL"
$ECHO "Exiting..."
exit 1
else
$ECHO "$OS_FULL"
fi
}

check_root ()
{
# Check if user root
$PRINTF "Checking super-user... "
if [ -f /usr/xpg4/bin/id ]; then
WHO=`/usr/xpg4/bin/id -n -u`
elif [ "`$ID | $CUT -f1 -d" "`" = "uid=0(root)" ]; then
WHO="root"
else
WHO=$WHOAMI
fi

if [ ! "$WHO" = "root" ]; then
$ECHO "ERROR: You must be super-user to run this script."
exit 1
fi
$ECHO "$WHO"
}

check_fs_exists ()
{
# Check filesystem exists
arg_fs=$1

ret=`$ZFS list -H -o name $arg_fs > /dev/null 2>&1; $ECHO $?`

if [ "$ret" != "0" ]; then
$ECHO "ERROR: ZFS pool/dataset $arg_fs does not exist."
$ECHO " Please specify another ZFS."
$ECHO "Exiting..."
exit 1
fi
}

archive_type ()
{
# Check archive type using extension and header check
arg_file=$1

if [ ! -z "`$ECHO $arg_file | $GREP $ARC_SUFFIX$`" -a \
! -z "`$FILE $arg_file | $GREP $ARC_NAME`" ]; then
$ECHO "$ARC_NAME"
elif [ ! -z "`$ECHO $arg_file | $GREP $ext$`" -a \
! -z "`$FILE $arg_file | $GREP $ext`" ]; then
$ECHO "$ext"
else
$ECHO "unknown"
fi
}

archive_exists ()
{
# Check archive exist and it readable
arg_arc=$1

# First check archive exists and readable
if [ ! -f "$arg_arc" -a ! -r "$arg_arc" ]; then
$ECHO "ERROR: Archive $arg_arc does not exist"
$ECHO " or you haven't permissions to read."
$ECHO "Exiting..."
exit 1
fi

# Second we'll check archive type
if [ "`archive_type $arg_arc`" = "$ARC_NAME" -a \
"`archive_type $arg_arc`" = " $ext" ]; then
$ECHO "ERROR: Archive $arg_arc it has wrong type."
$ECHO "Exiting..."
exit 1
fi
}

check_non_interactive ()
{
# Check if script runs in non-interactive mode
arg1=$1
arg2=$2

# If script command-line argument not specify,
# then run in interactive mode
if [ "x$arg1" = "x" -a "x$arg2" = "x" ]; then

# Set interactive mode flag
interactive="1"

$ECHO "---------------------------------------"
$ECHO "ZFS Restore archive"
$ECHO "---------------------------------------"
$ECHO
$ECHO ">>> Press [Enter] to continue or"
$ECHO ">>> Press [Ctrl+C] to cancel operation."
$ECHO
read p

# Read archive to receive
$ECHO "Input archive name (can be with path)"
$PRINTF "and press enter: "
read archive_file

# Read pool to restore
$ECHO "Input existing pool to restore"
$PRINTF "and press enter: "
read filesystem

elif [ "x$arg1" = "x/?" -o "x$arg1" = "x/h" -o "x$arg1" = "x/help" -o "x$arg1" = "xhelp" ]; then

# Set interactive mode flag
interactive="0"

$ECHO "Usage: $0 calls script in interactive mode."
$ECHO " or"
$ECHO " $0 [/source path/archive] [pool]"
$ECHO " calls script in non-interactive mode."
$ECHO
$ECHO "Note: Archives will be decompressed if GZip installed."
exit 0

else
archive_file=$arg1
filesystem=$arg2
fi

# If filesystem not specified, let's get it from file name
if [ "x$filesystem" = "x" ]; then
filesystem=`$ECHO "$archive_file" | $CUT -f2 -d"." | $SED -e 's/%/\//g' | $CUT -f1 -d"/"`
fi

# Check filesystem exists
check_fs_exists $filesystem
# Check archive file exists and correct type
archive_exists $archive_file
}

destroy_fs ()
{
# Destroy filesystem(s) recursively
arg_fs=$1

$ZFS destroy -r "$arg_fs" > /dev/null 2>&1

# Check exit code
if [ "`$ECHO $?`" != "0" ]; then
$ECHO "WARNING: Filesystem $arg_fs does not exists."
fi
}

zfs_receive ()
{
# Receive filesystem(s) from archive
arg_file=$1 # Archive to receive
arg_filesys=$2 # Target filesystem

# Verbose output flag set in interactive mode
if [ "$interactive" = "1" ]; then
verb="v"
fi

if [ "$compress" = "1" -a "`archive_type $arg_file`" = "$ARC_NAME" ]; then
$GZIP -d -c $arg_file | $ZFS receive -dF"$verb" "$arg_filesys"
elif [ "`archive_type $arg_file`" = "$ext" ]; then
$ZFS receive -dF"$verb" "$arg_filesys" < "$arg_file"
fi }

############## # Main block # ##############

# Checking OS
check_os

# Checking root
check_root

# Check non-interactive mode
check_non_interactive $1 $2

$ECHO "*** BEGIN: ZFS restore for $filesystem at `$DATE`."

# Check archiver
if [ ! -f "$GZIP" -a ! -x "$GZIP" ]; then
$ECHO "WARNING: Decompression can NOT be used. GZip not found." compress="0"
else $ECHO "Archive can be decompressed with gzip." compress="1"
fi

# First destroy all snapshots recursively
destroy_fs "$filesystem@$SNAP_EXT"

# Restore ZFS pool/dataset
zfs_receive $archive_file $filesystem

# Finally destroy all snapshots recursively
destroy_fs "$filesystem@$SNAP_EXT"
$ECHO "*** DONE: ZFS restore for $filesystem at `$DATE`."


Вот он - тот самый пресловутый эквивалент zfsdump/zfsrestore, который, как священный Грааль, искали UFS-ники.

Решение по резервированию и восстановлению на основе вышеприведенной логики руководство по использованию (на двух языках) можно скачать здесь (реализовано в виде стандартного пакета Solaris, устанавливаемого в /usr/local/bin).

PS. Приведенные в статьях скрипты zfs_backup.sh/zfs_restore.sh работают с точками монтирования как локальными, так и удаленными (через NFS). Пакет поддерживает работу как с удаленного резервного сервера, так и на локальной машине, работает через SSH, позволяет выполнять удаленное сохранение/восстановление в файловый архив и файловую систему ZFS.

четверг, 10 сентября 2009 г.

Резервирование и восстановление ZFS III

Во вчерашней статье мы восстанавливали датасет ZFS из архивированного стрима, неожиданно натанцевались с кучей команд - снапшоты, клоны, promote, переименования... А как быть, когда мы хотим восстановить целый ZFS-пул, зарезервированный нашим скриптом zfs_backup.sh?

Итак, вообразим себе гипотетический случай. У нас есть пул data сложной структуры, живущий на отдельном диске/слайсе:

server5# zfs list
NAME USED AVAIL REFER MOUNTPOINT
data 270M 19.3G 70.0M /data
data/work1 100M 19.3G 19K /data/work1
data/work1/user1 100M 19.3G 100M /data/work1/user1
data/work2 100M 19.3G 19K /data/work2
data/work2/user2 100M 19.3G 100M /data/work2/user2
rpool 14.6G 12.7G 93K /rpool
rpool/ROOT 6.60G 12.7G 18K legacy
rpool/ROOT/zfsroot 6.60G 12.7G 6.50G /
rpool/ROOT/zfsroot/var 100M 12.7G 100M /var
rpool/dump 4.00G 12.7G 4.00G -
rpool/export 117K 12.7G 20K /export
rpool/export/home 97K 12.7G 97K /export/home
rpool/swap 4G 16.6G 130M -

server5# ls -al /test
total 781
drwxr-xr-x 2 root root 3 Sep 10 11:40 .
drwxr-xr-x 26 root root 34 Sep 10 11:41 ..
-rw-r--r-- 1 root root 280588 Sep 10 11:40 server5.data.0.zfs.gz
server5# ls -al /data
total 143424
drwxr-xr-x 4 root root 6 Sep 10 11:42 .
drwxr-xr-x 26 root root 34 Sep 10 11:41 ..
-rw------T 1 root root 20971520 Sep 10 11:42 file1_1.data.test
-rw------T 1 root root 52428800 Sep 10 11:39 file1.data.test
drwxr-xr-x 3 root root 3 Sep 10 11:38 work1
drwxr-xr-x 3 root root 3 Sep 10 11:38 work2
server5# ls -al /data/work1/user1
total 204863
drwxr-xr-x 2 root root 3 Sep 10 11:39 .
drwxr-xr-x 3 root root 3 Sep 10 11:38 ..
-rw------T 1 root root 104857600 Sep 10 11:39 file2.data.work1.user1.test
server5# ls -al /data/work2/user2
total 204863
drwxr-xr-x 2 root root 3 Sep 10 11:39 .
drwxr-xr-x 3 root root 3 Sep 10 11:38 ..
-rw------T 1 root root 104857600 Sep 10 11:39 file3.data.work2.user2.test

Допустим, мы его потеряли, причем вместе с диском. У нас есть архив server5.data.0.zfs.gz всего пула. И мы хотели бы восстановить весь пул, со всем содержимым, причем, по-возможности, сделать это попроще.

Давайте сначала приговорим наш существующий пул:

server5# zpool destroy data

server5# zfs list
NAME USED AVAIL REFER MOUNTPOINT
rpool 14.6G 12.7G 93K /rpool
rpool/ROOT 6.60G 12.7G 18K legacy
rpool/ROOT/zfsroot 6.60G 12.7G 6.50G /
rpool/ROOT/zfsroot/var 100M 12.7G 100M /var
rpool/dump 4.00G 12.7G 4.00G -
rpool/export 117K 12.7G 20K /export
rpool/export/home 97K 12.7G 97K /export/home
rpool/swap 4G 16.6G 130M -

server5# zpool create data c0t0d0s6

server5# zpool status data
pool: data
state: ONLINE
scrub: none requested
config:

NAME STATE READ WRITE CKSUM
data ONLINE 0 0 0
c0t0d0s6 ONLINE 0 0 0

errors: No known data errors

server5# zfs list
NAME USED AVAIL REFER MOUNTPOINT
data 89.5K 19.6G 1K /data
rpool 14.6G 12.7G 93K /rpool
rpool/ROOT 6.60G 12.7G 18K legacy
rpool/ROOT/zfsroot 6.60G 12.7G 6.50G /
rpool/ROOT/zfsroot/var 100M 12.7G 100M /var
rpool/dump 4.00G 12.7G 4.00G -
rpool/export 117K 12.7G 20K /export
rpool/export/home 97K 12.7G 97K /export/home
rpool/swap 4G 16.6G 130M -

Восстанавливаем содержимое пула:

server5# gzcat server5.data.0.zfs.gz | zfs receive -vdF data
receiving full stream of data@snapshot into data@snapshot
received 50.1MB stream in 2 seconds (25.1MB/sec)
receiving full stream of data/work2@snapshot into data/work2@snapshot
received 15.0KB stream in 1 seconds (15.0KB/sec)
receiving full stream of data/work2/user2@snapshot into data/work2/user2@snapshot
received 100MB stream in 3 seconds (33.4MB/sec)
receiving full stream of data/work1@snapshot into data/work1@snapshot
received 15.0KB stream in 1 seconds (15.0KB/sec)
receiving full stream of data/work1/user1@snapshot into data/work1/user1@snapshot
received 100MB stream in 3 seconds (33.4MB/sec)

server5# zfs destroy -r data@snapshot

Все? Кажется, действительно все. Проверяем:

server5# zfs list
NAME USED AVAIL REFER MOUNTPOINT
data 250M 19.3G 50.0M /data
data/work1 100M 19.3G 19K /data/work1
data/work1/user1 100M 19.3G 100M /data/work1/user1
data/work2 100M 19.3G 19K /data/work2
data/work2/user2 100M 19.3G 100M /data/work2/user2
rpool 14.6G 12.7G 93K /rpool
rpool/ROOT 6.60G 12.7G 18K legacy
rpool/ROOT/zfsroot 6.60G 12.7G 6.50G /
rpool/ROOT/zfsroot/var 100M 12.7G 100M /var
rpool/dump 4.00G 12.7G 4.00G -
rpool/export 117K 12.7G 20K /export
rpool/export/home 97K 12.7G 97K /export/home
rpool/swap 4G 16.6G 130M -
server5#

server5# ls -al /data
total 102447
drwxr-xr-x 4 root root 5 Sep 10 11:39 .
drwxr-xr-x 26 root root 34 Sep 10 11:47 ..
-rw------T 1 root root 52428800 Sep 10 11:39 file1.data.test
drwxr-xr-x 3 root root 3 Sep 10 11:38 work1
drwxr-xr-x 3 root root 3 Sep 10 11:38 work2

Вуаля. "Реконструкция завершена."

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

PS. Суть такого приема в том, что при восстановлении снапшотов на самый верхний уровень иерархии - в ZFS-пул - никаких фокусов с клонированием и переименованием по-сути не нужно. Операция receive на уровне пула выполняется как рекурсивное клонирование с одновременным переименованием всех снапшотов стрима. Причем восстановление (прием снапшотов) может выполняться как в пустой пул, так и в пул с данными (не уничтоженный), при этом данные на диске будут перезаписаны.

среда, 9 сентября 2009 г.

Резервирование и восстановление ZFS II

В предыдущей статье я описал процедуру создания резервной копии из снапшотов и скрипт, позволяющий создавать архивированные бэкапы ZFS. Во второй статье мы рассмотрим в деталях процедуру ручного восстановления из сделанного нами архивированного бэкапа файловой системы (датасета).

Сделать бэкап - это лишь половина дела, нужно, как минимум, убедиться, что мы сможем восстановиться с него.

Итак, воспроизведем все с самого начала на нашем многострадальном server5.

Опустим шаги по подготовке тестовой среды, покажем лишь их финал:

server5# mkdir /test

server5# zfs list
NAME USED AVAIL REFER MOUNTPOINT
data 131K 19.6G 20K /data
rpool 14.6G 12.7G 93K /rpool
rpool/ROOT 6.59G 12.7G 18K legacy
rpool/ROOT/zfsroot 6.59G 12.7G 6.49G /
rpool/ROOT/zfsroot/var 100M 12.7G 100M /var
rpool/dump 4.00G 12.7G 4.00G -
rpool/export 117K 12.7G 20K /export
rpool/export/home 97K 12.7G 97K /export/home
rpool/swap 4G 16.6G 130M -

server5# zfs create data/work
server5# cd /data/work
server5# ls
server5# mkfile 100m file.test

server5# zfs list
NAME USED AVAIL REFER MOUNTPOINT
data 100M 19.5G 20K /data
data/work 100M 19.5G 100M /data/work
rpool 14.6G 12.7G 93K /rpool
rpool/ROOT 6.59G 12.7G 18K legacy
rpool/ROOT/zfsroot 6.59G 12.7G 6.49G /
rpool/ROOT/zfsroot/var 100M 12.7G 100M /var
rpool/dump 4.00G 12.7G 4.00G -
rpool/export 117K 12.7G 20K /export
rpool/export/home 97K 12.7G 97K /export/home
rpool/swap 4G 16.6G 130M -

server5# ls -al /data/work
total 7
drwxr-xr-x 2 root root 3 Sep 9 17:26 .
drwxr-xr-x 3 root root 4 Sep 9 17:38 ..
-rw------T 1 root root 104857600 Sep 9 17:26 file2.test

Выполним резервное копирование датасета data/work при помощи скрипта, описанного в предыдущей статье:

server5# zfs_backup.sh data/work /test
Checking OS... SunOS 5.10
Checking super-user... root
*** BEGIN: ZFS backup for data/work at Wednesday, September 9, 2009 5:27:19 PM ALMT.
Archive will be compressed with gzip -9.
WARNING: Snapshot data/work@snapshot does not exists.
*** DONE: ZFS backup for data/work at Wednesday, September 9, 2009 5:27:23 PM ALMT.

Готово. Уничтожим файловую систему, которую мы зарезервировали:

server5# zfs destroy data/work
server5# zfs list
NAME USED AVAIL REFER MOUNTPOINT
data 132K 19.6G 20K /data
rpool 14.6G 12.7G 93K /rpool
rpool/ROOT 6.59G 12.7G 18K legacy
rpool/ROOT/zfsroot 6.59G 12.7G 6.49G /
rpool/ROOT/zfsroot/var 100M 12.7G 100M /var
rpool/dump 4.00G 12.7G 4.00G -
rpool/export 117K 12.7G 20K /export
rpool/export/home 97K 12.7G 97K /export/home
rpool/swap 4G 16.6G 130M -


Вперед. Начнем восстановление.

Создаем временную файловую систему и принимаем в нее снапшот:

server5# zfs create data/rcvd
server5# zfs set canmount=noauto data/rcvd
server5# zfs set compression=on data/rcvd
server5# gzcat server5.data%work.0.zfs.gz | zfs receive -vdF data/rcvd
receiving full stream of data/work@snapshot into data/rcvd/work@snapshot
received 100MB stream in 2 seconds (50.1MB/sec)

server5# zfs list
NAME USED AVAIL REFER MOUNTPOINT
data 100M 19.5G 21K /data
data/rcvd 36K 19.5G 18K /data/rcvd
data/rcvd/work 18K 19.5G 18K /data/rcvd/work
data/rcvd/work@snapshot 0 - 18K -
data/work 100M 19.5G 100M /data/work
rpool 14.6G 12.7G 93K /rpool
rpool/ROOT 6.59G 12.7G 18K legacy
rpool/ROOT/zfsroot 6.59G 12.7G 6.49G /
rpool/ROOT/zfsroot/var 100M 12.7G 100M /var
rpool/dump 4.00G 12.7G 4.00G -
rpool/export 117K 12.7G 20K /export
rpool/export/home 97K 12.7G 97K /export/home
rpool/swap 4G 16.6G 130M -

Теперь приступаем к волшебству:

server5# zfs clone data/rcvd/work@snapshot data/rcvd_clone
server5# zfs promote data/rcvd_clone
server5# zfs set canmount=noauto data/rcvd_clone
server5# zfs set canmount=noauto data/rcvd_clone
server5# zfs set mountpoint=/data/work data/rcvd_clone
property may be set but unable to remount filesystem
server5# zfs rename data/work data/work.old
server5# zfs rename data/rcvd_clone data/work
server5# zfs mount data/work
server5# zfs destroy data/work.old
server5#

Осталось кое-что зачистить после восстановления:

server5# zfs destroy -r data/rcvd
server5# zfs destroy -r data/work@snapshot

server5# zfs list
NAME USED AVAIL REFER MOUNTPOINT
data 100M 19.5G 21K /data
data/work 100M 19.5G 100M /data/work
rpool 14.6G 12.7G 93K /rpool
rpool/ROOT 6.59G 12.7G 18K legacy
rpool/ROOT/zfsroot 6.59G 12.7G 6.49G /
rpool/ROOT/zfsroot/var 100M 12.7G 100M /var
rpool/dump 4.00G 12.7G 4.00G -
rpool/export 117K 12.7G 20K /export
rpool/export/home 97K 12.7G 97K /export/home
rpool/swap 4G 16.6G 130M -

И - "Реконструкция завершена" (С) "Пятый Элемент":

server5# ls -al /data/work
total 7
drwxr-xr-x 2 root root 3 Sep 9 17:56 .
drwxr-xr-x 3 root root 3 Sep 9 17:59 ..
-rw------T 1 root root 104857600 Sep 9 17:56 file1.test

Как очевидно, процедура, в общем, несложная - но достаточно объемистая, рутинная и желательно ее упростить еще больше. В одной из следующих статей мы напишем скрипт zfs_restore.sh для выполнения вышерассмотренных шагов по восстановлению файловых систем из нашиэ бэкапов в одно действие.

вторник, 8 сентября 2009 г.

Резервирование и восстановление ZFS I

Резервное копирование файловых систем - первая задача, с которой сталкивается каждый системный администратор. Shit happens, носители информации имеют тенденцию выходить из строя по самым различным причинам, начиная от частых отключений питания и перегревов в серверных помещениях и заканчивая акцидентальным удалением пьяным администратором собственных файловых систем, с выносом всего и вся командами наподобие патча Бармина.

И первый сюрприз, подстерегающий системных администраторов с ZFS это - а где, собственно, zfsdump и zfsrestore?

Это, однако, не самый суровый сюрприз. Второй по значимости, но не менее впечатляющий - где FLAR-архивы для некорневых пулов? Нет, идея засунуть /export/home на root pool - она не лишена привлекательности, но как, скажем, писать имиджи для bare-metal restore в случае, если ее туда не засунули? Ибо, лично меня, например, как-то не впечатляет идея терабайтного root pool (хотя некоторые самоделкины ухитряются проделать и такое).

Второй сюрприз требует некоторых пояснений. Если на UFS flar собирает в архив все смонтированные на / файловые системы, то, в случае с ZFS, если вы, к примеру, высадили /export/home на отдельный пул, то обнаружите, что внутри flar-архива теперь находится только и исключительно root pool и больше ничего.

Намедни нарвался я на одну проблему, как раз описанную выше. Если за пределы root pool вынесены какие-либо другие данные отдельными пулами, то, даже с учетом того, что они смонтированы на /, они не включаются в состав flar-архива.

Соответственно, как выполнять bare-metal restore, в случае чего (чего не приведи бог, конечно),

Для начала некоторый поиск привел к данной статье. Статья зело интересная и поучительная, особенно с учетом того, что flar все еще с ZFS не слишком дружит. Правда, для адаптации описанного решения к повседневным задачам большинства из нас нужно сильно помахать напильником и поскрипеть мозгом. Головным. :)

Но, в действительности, ввиду особенностей ZFS, все же следует обратить свой взгляд в сторону функциональности zfs send и zfs receive.

Дабы гарантировать целостность любых данных, будем иметь дело со снапшотами.

Первое приближение приводит нас к однострочникам для cron:

# Automated data backup job with zfs
# Running weekly at 01:00 Saturday
0 1 * * 6 /sbin/zfs destroy -r data@snap > /dev/null 2>&1;/bin/rm -f /export/home/archives/data.zfs.gz && /sbin/zfs snapshot -r data@snap && /sbin/zfs send -R data@snap | /bin/gzip -9 > /export/home/archives/data.zfs.gz && /sbin/zfs destroy -r data@snap > /dev/null 2>&1

# Automated system backup job with zfs
# Running weekly at 02:00 Saturday
0 2 * * 6 /sbin/zfs destroy -r rpool@snap > /dev/null 2>&1;/bin/rm -f /export/home/archives/rpool.zfs.gz && /sbin/zfs snapshot -r rpool@snap && /sbin/zfs send -R rpool@snap | /bin/gzip -9 > /export/home/archives/rpool.zfs.gz && /sbin/zfs destroy -r rpool@snap > /dev/null 2>&1

Обратите внимание на следующий факт. Пулы резервируются посредством рекурсивных снапшотов, это гарантирует целостность и полноту данных. Датастримы сжимаются посредством gzip с максимальной компрессией (во-первых, можно легко разобрать архивы руками, во-вторых - сильно экономится пространство, правда, с поправкой на природу данных пулов). Собственно, датастримы совершенно спокойно сохраняются в файлы, и, при необходимости, и отдельные файлы, и датасеты и целиком пулы сравнительно легко извлечь и положить на место функциональностью клонирования, promote и rename.

Однако хотелось бы написать нечто, подобное данной утилите для создания flar-архивов нулевого уровня.

Собственно, взяв за основу вышеозначенную утилиту, удалось менее, чем за сутки родить следующий скрипт:

#!/sbin/sh

#
# ZFS filesystem(s) compressed backup.
#
# ZFS archives uses for bare-metal restore
# and systems cloning.
#
# Archive names will be incremental, like that:
# [hostname].[pool|dataset].n.zfs<.gz>, n=0,1,2...
# Note: Do not rename archive files! Filenames will
# use to recovery purposes.
#
# Version 1.0 (C) 2009 Y.Voinov
#
# If you specify pool/dataset
# and destination directory in command line,
# script will run in non-interactive mode.
#
# ident "@(#)zfs_backup.sh 1.0 09/07/09 YV"
#

#############
# Variables #
#############

# Snapshots extension by default
# Correct it if these snapshots already exists in the system
SNAP_EXT="snapshot"
# Archive file suffix
ARC_SUFFIX=".gz"
# GZip default compression level
COMP_LEVEL="9"
# Default archive extension
ext="zfs"
# Initial archive sequence
initial_arc_seq="0"

# OS utilities
CUT=`which cut`
DATE=`which date`
ECHO=`which echo`
GZIP=`which gzip`
HOSTNAME=`which hostname`
ID=`which id`
PRINTF=`which printf`
RM=`which rm`
SED=`which sed`
UNAME=`which uname`
WHOAMI=`which whoami`
ZFS=`which zfs`

OS_VER=`$UNAME -r|$CUT -f2 -d"."`
OS_NAME=`$UNAME -s|$CUT -f1 -d" "`
OS_FULL=`$UNAME -sr`

# System name
system=`$HOSTNAME`

###############
# Subroutines #
###############

check_os ()
{
# Check OS
$PRINTF "Checking OS... "
if [ "$OS_NAME" = "SunOS" -a "$OS_VER" -lt "10" ]; then
$ECHO "ERROR: Unsupported OS: $OS_FULL"
$ECHO "Exiting..."
exit 1
else
$ECHO "$OS_FULL"
fi
}

check_root ()
{
# Check if user root
$PRINTF "Checking super-user... "
if [ -f /usr/xpg4/bin/id ]; then
WHO=`/usr/xpg4/bin/id -n -u`
elif [ "`$ID | $CUT -f1 -d" "`" = "uid=0(root)" ]; then
WHO="root"
else
WHO=$WHOAMI
fi

if [ ! "$WHO" = "root" ]; then
$ECHO "ERROR: You must be super-user to run this script."
exit 1
fi
$ECHO "$WHO"
}

archive_exists ()
{
# Check archive file exist
if [ "$compress" = "1" -a -f "$file.gz" ]; then
$ECHO "1"
elif [ "$compress" = "0" -a -f "$file" ]; then
$ECHO "1"
else
$ECHO "0"
fi
}

set_file ()
{
# Check archive name exists
# and create new name if needful
attempt=0

while [ "`archive_exists`" = "1" ]; do
file=`$ECHO $file|$CUT -f2 -d"."`
file="$dest/$system.$file.$attempt.$ext"
if [ "`archive_exists`" != "1" ]; then
break
fi
attempt=`expr $attempt + 1`
done
}

check_fs_exists ()
{
# Check filesystem exists
arg_fs=$1

ret=`$ZFS list -H -o name $arg_fs > /dev/null 2>&1; $ECHO $?`

if [ "$ret" != "0" ]; then
$ECHO "ERROR: ZFS pool/dataset $arg_fs does not exist."
$ECHO " Please specify another ZFS."
$ECHO "Exiting..."
exit 1
fi
}

check_dest_dir ()
{
# Check directory exist and it writable
arg_dest=$1

if [ ! -d "$arg_dest" or ! -w "$arg_dest" ]; then
$ECHO "ERROR: Directory $arg_dest does not exist"
$ECHO " or you haven't permissions to write."
$ECHO "Exiting..."
exit 1
fi
}

check_non_interactive ()
{
# Check if script runs in non-interactive mode
arg1=$1
arg2=$2

# If script command-line argument not specify,
# then run in interactive mode
if [ "x$arg1" = "x" -a "x$arg2" = "x" ]; then

# Set interactive mode flag
interactive="1"

$ECHO "---------------------------------------"
$ECHO "$system ZFS backup archive creation"
$ECHO "---------------------------------------"
$ECHO
$ECHO ">>> Press to continue or"
$ECHO ">>> Press to cancel operation."
$ECHO
read p

# Read pool/dataset to archive
$ECHO "Input pool/dataset name to backup"
$PRINTF "and press enter: "
read filesystem

# Read directory/mount point to send
$ECHO "Input archive destination mount point"
$PRINTF "and press enter: "
read dest

elif [ "x$arg1" = "x/?" -o "x$arg1" = "x/h" -o "x$arg1" = "x/help" -o "x$arg1" = "xhelp" ]; then

# Set interactive mode flag
interactive="0"

$ECHO "Usage: $0 calls script in interactive mode."
$ECHO " or"
$ECHO " $0 [pool/dataset] [destination]"
$ECHO " calls script in non-interactive mode."
$ECHO
$ECHO "Note: Archives will be compressed if GZip installed."
exit 0

else
# If script command-line argument specified,
# run in non-interactive mode
filesystem=$arg1
dest=$arg2
fi

# Check filesystem exists
check_fs_exists $filesystem
# Check destination directory
check_dest_dir $dest
}

destroy_fs ()
{
# Destroy filesystem(s) recursively
filesys=$1

$ZFS destroy -r $filesys > /dev/null 2>&1

# Check exit code
if [ "`$ECHO $?`" != "0" ]; then
$ECHO "WARNING: Filesystem $filesys does not exists."
fi
}

create_snap ()
{
# Create snapshot recursively
filesys=$1

$ZFS snapshot -r "$filesys@$SNAP_EXT"
}

zfs_send ()
{
# Send filesystem to the destination
filesys=$1

# Verbose output flag set in interactive mode
if [ "$interactive" = "1" ]; then
verb="v"
fi

if [ "$compress" = "1" ]; then
$ZFS send -R"$verb" "$filesys@$SNAP_EXT" | $GZIP "-$COMP_LEVEL" > "$file$ARC_SUFFIX"
else
$ZFS send -R"$verb" "$filesys@$SNAP_EXT" > $file
fi
}

##############
# Main block #
##############

# Checking OS
check_os

# Checking root
check_root

# Check non-interactive mode
check_non_interactive $1 $2

$ECHO "*** BEGIN: ZFS backup for $filesystem at `$DATE`."

# Check archiver
if [ ! -f "$GZIP" -a ! -x "$GZIP" ]; then
$ECHO "WARNING: Compression will NOT be used. GZip not found."
compress="0"
else
$ECHO "Archive will be compressed with gzip -$COMP_LEVEL."
compress="1"
fi

# Set initial archive file name
# Replase slashes with % if dataset specified
file="$dest/$system.`$ECHO $filesystem | $SED -e 's/\//%/g'`.$initial_arc_seq.$ext"

# Set file name if incremental naming using
# (if archive with the same name already exists)
set_file

# First destroy snapshots recursively if it exists
destroy_fs "$filesystem@$SNAP_EXT"

# Second create recursive snapshots
create_snap $filesystem

# Third send all snapshots recursively to archive
zfs_send $filesystem

# Finally destroy all snapshots recursively
destroy_fs "$filesystem@$SNAP_EXT"

$ECHO "*** DONE: ZFS backup for $filesystem at `$DATE`."

В общем-то, все достаточно просто. Скрипт создает архивированные с максимальной компрессией (если находит в системе GZip) файловые образы рекурсивных снапшотов для заданных ZFS-пулов и датасетов, и является основой для (скоро будет написана) утилиты восстановления из данных архивов. Не хватило времени написать и ее тоже. ;)

Данные архивы легко могут быть использованы для рукопашного восстановления (если слегка подумать и покурить маны).

В качестве точки назначения можно указывать ленту, точку монтирования локальной системы или NFS (собственно, целью данной работы и было написать нечто, приближенное к ufsdump и/или flar).

Маленькое замечание. При указании датасетов для архивирования при сохранении архива, имя которого формируется на основе имени датасета, выполняется замена слэшей (/) на символ "%", в противном случае создается не файл, а поддерево директорий с коротким файлом в самой нижней директории. Для восстановления руками это не имеет решающего значения, и сделано с умыслом для удобства использования в обратной утилите zfs_restore.sh (которая в данный момент находится в процессе написания).

Если сравнивать GZip -9 c compress, посредством которого сжимаются flar-архивы, то очевидно, что GZip сжимает значительно лучше:

root @ host /export/home/archives # ls -al
total 6835661
drwxr-xr-x 2 root root 5 Sep 8 16:56 .
drwxr-xr-x 17 root root 17 Sep 7 11:17 ..
-rw-r--r-- 1 root root 3410629509 Sep 8 16:12 host.data.0.zfs.gz
-rw-r--r-- 1 root root 1446917194 Sep 8 16:40 host.rpool.0.zfs.gz
-rw-r--r-- 1 root root 2136622740 Sep 8 17:04 host0.flar

Flar-архив в данном примере и host.rpool.0.zfs.gz записаны с одной и той же файловой системы со следующим заполнением:

rpool 9.98G 21.5G 41.5K /rpool
rpool/ROOT 3.97G 21.5G 18K /rpool/ROOT
rpool/ROOT/zfsBE_s10_509 3.97G 21.5G 3.97G /
rpool/dump 2.00G 21.5G 2.00G -
rpool/swap 4G 25.5G 98K -

Чистый объем root pool в данной системе составляет чуть менее 4 Гб, и архивируется, соответственно, в 2 Гб flar и 1,3 Гб компрессированного стрима ZFS.

Так что, как видно из приведенного примера, компрессирование стримов ZFS имеет смысл в любом случае. Навскидку приведу пару публикаций на данную тему:

Пруфлинк 1
Пруфлинк 2

Умные мысли посещают умные головы одновременно. ;)

PS. При передаче снапшотов ZFS по сети компрессирование также имеет смысл. Меньше передаваемой информации, меньше загрузка сети (правда, большая загрузка CPU, однако нам хорошо известно, что законы сохранения для IT действуют ;)).

среда, 2 сентября 2009 г.

Разборка flar-архива

Потребовалось мне намедни вынуть несколько файлов из flar-архива. Неожиданно оказалось, что в инете нет вменяемого описания, как это сделать. Удалось нагуглить одну- единственную внятную ссылку. Нет, в теории известно, что собирается архив посредством cpio и compress.

Но вот как в точности это сделать - вопрос. Что ж, попробуем способом, описанным в вышеприведенной ссылке (с поправкой на местные условия).

(Ух как много времени потребовалось на разборку архива размером около 7 Гб! Больше часа заняли нижеописанные процедурки!)

# flar split scilla0.flar
# mv archive archive.Z; uncompress archive.Z
# cpio -idmv -I archive

Последняя команда вытаскивает содержимое архива в текущую директорию. Стоит позаботиться проделывать все это не в корневом каталоге.

К сожалению, выбрать по шаблону одну необходимую директорию не удалось. Пришлось вытаскивать все и извлекать после то, что нужно.

Оперативная память и ZFS II

В предыдущей статье я рассказывал, почему ZFS так зверски задействует оперативную память под ARC. Сейчас мы попробуем выяснить несколько моментов - в частности, нельзя ли как-то улучшить положение в плане латентности Соляриса при запросах на память, занятую кэшами ZFS, более эффективно использовать дисковое пространство пулов, немного поиграть с предвыборкой да и вообще - возможен ли хоть какой-то тюнинг ZFS или, как говорит Sun - "Тюнинг есть зло"?

Дело в следующем. Как и было сказано, параметр recordsize у ZFS по умолчанию 128К. Это, в общем, сделано с умыслом - "Мы объединяем диски, а не делим их" - и речь идет о действительно больших сториджах. Однако такой большой размер записи предполагает, что и предвыборка будет выполняться как минимум такими порциями. Если, скажем, у нас на руках директория с большим количеством мелких файлов на пуле с таким размером записи - кэш потребуется примерно вдвое (грубо) больше, чем реально требуется для всех этих файлов вместе взятых.

Кроме того, есть еще одно соображение. В предыдущей статье я сказал, что память кэша освобождается по запросу. Это действительно так. Однако - как быть с приложениями, которые часто и интенсивно запрашивают память? Будет ли здесь играть роль тот факт, что ZFS еще нужно освободить память, а перед этим выполнить хоть небольшой - но чекпойнт?

Простой здравый смысл говорит - да, будет. И факты это подтверждают. Система на ZFS, с высокой нагрузкой на память, имеет существенно большее количество переключений контекста, что прямо указывает на повышенные накладные расходы и конкуренцию ZFS за место в памяти с приложениями. Что не может не сказаться на производительности.

Опять же, наша цель не только достижение экстремальной производительности, но и все-таки достаточно бережливое отношение к ресурсам (попытаемся объять необъятное - и рыбку съесть и шкурку сдать, тем более, что в нашем случае есть прямая зависимость между скоростью отклика системы и свободной оперативной памятью).

Проблема, как ни странно, заключается в гуляющих по интернету примерах, где ZFS-пулы создаются, прямо скажем, с минимальным числом параметров. Кои включают в себя все умолчания, среди которых и recordsize=128K.

Причем, если подумать, то это значение недурно для массивов терабайтных и более порядков, но вот для слайсов (когда диски все-таки делятся), и, в частности, для рутовых пулов - определенно великовато. С учетом специфики предвыборки.

Кстати говоря, даже для массивов - далеко не для всех видов данных полезно размер записи такой величины иметь-то, собственно. Тьма мелких и часто запрашиваемых файлов - например, файловые веб-сервера, порталы и портальчики - все это при таком значении записи ZFS будет крайне тяжко сказываться на оперативной памяти.

Все бы ничего, но ресурсы не безграничны, а, если их даже много, все же нужно их употреблять с пользой (например, улучшая отклик систем), а не швырять налево и направо.

Собственно, из всего вышеперечисленного следует, что стоит попробовать использовать на благо уникальные свойства ZFS - сиречь, возможность дробить пулы на датасеты и устанавливать параметры хранения для файловых систем выборочно.

Вчера, по следам прошлой статьи, я провел эксперимент с описанным в статье сервером (с той же самой машиной) , и раздробил пул, на котором стоят ораклиные приложения, на датасеты, с созданием некоей иерархии файловых систем с раздельной установкой параметра recordsize.

Опуская детали процесса реструктурирования пула (и учитывая тот факт, что root pool я уже изменить не могу - что не смертельно, поскольку он один не создает феерической нагрузки на память), покажу лишь конечный результат.

Вот статистика работающей системы с прогретым кэшем:

load averages: 0.01, 0.04, 0.08; up 0+00:16:14 12:29:55
76 processes: 75 sleeping, 1 on cpu
CPU states: 99.4% idle, 0.1% user, 0.5% kernel, 0.0% iowait, 0.0% swap
Kernel: 297 ctxsw, 2 trap, 499 intr, 240 syscall, 2 flt
Memory: 4095M phys mem, 1937M free mem, 4096M total swap, 4096M free swap

PID USERNAME NLWP PRI NICE SIZE RES STATE TIME CPU COMMAND
613 root 1 59 0 6228K 5072K cpu/0 0:00 0.06% top
430 oracle 1 59 0 0K 0K sleep 0:00 0.03% oracle
532 oracle 35 59 0 235M 120M sleep 0:12 0.02% java
426 oracle 1 59 0 0K 0K sleep 0:00 0.01% oracle
400 ias1 6 59 0 25M 11M sleep 0:00 0.01% webcached
402 ias1 6 59 0 56M 33M sleep 0:00 0.01% webcached
410 oracle 1 59 0 0K 0K sleep 0:00 0.01% oracle
420 oracle 11 59 0 0K 0K sleep 0:00 0.01% oracle
463 ias0 16 59 0 19M 7824K sleep 0:00 0.00% opmn
428 oracle 1 59 0 0K 0K sleep 0:00 0.00% oracle
477 ias0 3 59 0 25M 1728K sleep 0:00 0.00% httpd
446 oracle 3 59 0 0K 0K sleep 0:00 0.00% tnslsnr
536 oracle 1 59 0 8224K 2616K sleep 0:00 0.00% sshd
418 oracle 17 59 0 0K 0K sleep 0:00 0.00% oracle
303 root 1 100 -20 2552K 1224K sleep 0:00 0.00% xntpd

В сравнении со вчерашними цифрами картина заметно другая. Более того, отклик стал лучше почти в полтора-два раза (субъективно).

Что реально было сделано? Всего лишь создано 18 датасетов:

oracle @ host ~ $ zfs list
NAME USED AVAIL REFER MOUNTPOINT
data 12.0G 90.4G 96K /export/home
data/OraHome1 4.70G 90.4G 4.70G /export/home/OraHome1
data/OraHome2 788M 90.4G 788M /export/home/OraHome2
data/OraHome3 501M 90.4G 501M /export/home/OraHome3
data/OraHome4 163M 90.4G 163M /export/home/OraHome4
data/apex 641M 90.4G 641M /export/home/apex
data/awstats 37.0M 90.4G 37.0M /export/home/awstats
data/db 5.19G 90.4G 18K none
data/db/flash_recovery_area 157M 90.4G 157M /export/home/flash_recovery_area
data/db/oradata 4.82G 90.4G 4.82G /export/home/oradata
data/db/plsql_libs 217M 90.4G 217M /export/home/oradata/plsql_libs
data/users 281K 90.4G 18K none
data/users/ias0 29K 90.4G 29K /export/home/ias0
data/users/ias1 43.5K 90.4G 43.5K /export/home/ias1
data/users/oracle 190K 90.4G 190K /export/home/oracle
rpool 9.96G 21.5G 41.5K /rpool
rpool/ROOT 3.96G 21.5G 18K /rpool/ROOT
rpool/ROOT/zfsBE_s10_509 3.96G 21.5G 3.96G /
rpool/dump 2.00G 21.5G 2.00G -
rpool/swap 4G 25.5G 16K -

Параметр recordsize установлен для датасетов следующим образом:

root @ host / # zfs get recordsize
NAME PROPERTY VALUE
data recordsize 128K
data/OraHome1 recordsize 64K
data/OraHome2 recordsize 64K
data/OraHome3 recordsize 64K
data/OraHome4 recordsize 64K
data/apex recordsize 16K
data/awstats recordsize 16K
data/db recordsize 128K
data/db/flash_recovery_area recordsize 128K
data/db/oradata recordsize 128K
data/db/plsql_libs recordsize 16K
data/users recordsize 16K
data/users/ias0 recordsize 16K
data/users/ias1 recordsize 16K
data/users/oracle recordsize 16K
rpool recordsize 128K
rpool/ROOT recordsize 128K
rpool/ROOT/zfsBE_s10_509 recordsize 128K
rpool/dump recordsize -
rpool/swap recordsize -

Что в сухом остатке?

А в сухом остатке следующие выводы:

  1. Создавать датасеты на ZFS-пулах можно и нужно.
  2. Умная Маша, еще на этапе создания пула, почешет репу и подумает, под что он - и ДО заполнения установит нужные параметры хранения пула, прежде всего, recordsize. Это, кстати говоря, касается и root pool. Размер записи в 128К может оказаться реально великоват для конкретной файловой системы - в плане расхода памяти под ARC прежде всего. Соответственно, мы также увидели, что это тюнинговый параметр. Собственно, такой же, как и размер кластера или блока для RAID, кто не въехал.
  3. Кроме оптимизации, дробление на датасеты (в том числе иерархические - для хоть сколько-нибудь сложных либо продвинутых систем хранения) - отличная административная практика. Снапшоты, клонирование, горячее реструктурирование, выборочные бэкапы итп. Собственно, все то же самое, для чего и предназначены отдельные файловые системы с точками монтирования. Кстати говоря, точки монтирования в ZFS допускают абсолютно гибкое жонглирование, хоть вообще не задавай для родительских ФС маунтпойнты.
Что ж, коллеги. Тщательней планируйте сторидж - и будет вам счастье. И быстро, и удобно, и ресурсы экономите.

PS. Кстати говоря, объем операций IO снизился заметно:

root @ host / # zpool iostat 5
capacity operations bandwidth
pool used avail read write read write
---------- ----- ----- ----- ----- ----- -----
data 12.0G 92.0G 0 8 0 189K
rpool 5.96G 26.0G 0 0 0 0
---------- ----- ----- ----- ----- ----- -----
data 12.0G 92.0G 0 1 0 24.0K
rpool 5.96G 26.0G 0 0 0 0
---------- ----- ----- ----- ----- ----- -----
data 12.0G 92.0G 0 0 0 7.98K
rpool 5.96G 26.0G 0 0 0 0
---------- ----- ----- ----- ----- ----- -----
data 12.0G 92.0G 0 15 0 610K
rpool 5.96G 26.0G 0 0 0 0
---------- ----- ----- ----- ----- ----- -----
data 12.0G 92.0G 0 2 0 55.9K
rpool 5.96G 26.0G 0 0 0 0
---------- ----- ----- ----- ----- ----- -----
data 12.0G 92.0G 0 0 0 7.98K
rpool 5.96G 26.0G 0 0 0 0
---------- ----- ----- ----- ----- ----- -----
data 12.0G 92.0G 0 0 0 16.0K
rpool 5.96G 26.0G 0 0 0 0
---------- ----- ----- ----- ----- ----- -----
data 12.0G 92.0G 0 2 0 35.1K
rpool 5.96G 26.0G 0 0 0 0
---------- ----- ----- ----- ----- ----- -----
data 12.0G 92.0G 0 1 0 25.5K
rpool 5.96G 26.0G 0 0 0 0
---------- ----- ----- ----- ----- ----- -----
data 12.0G 92.0G 0 13 0 473K
rpool 5.96G 26.0G 0 0 0 0
---------- ----- ----- ----- ----- ----- -----
^C

Что, в общем, говорит о том, что мне не померещилось.

вторник, 1 сентября 2009 г.

Оперативная память и ZFS

Снова здорово. Народ, перешедший на ZFS, неожиданно для себя обнаружил малое количество свободной памяти в работающей системе и запаниковал.

Собственно, я уже писал о том, как ZFS обращается с памятью и почему не надо волноваться.

Хорошо, повторенье - мать ученья, это особенно актуально в день Школия. :)

Показываю еще одну систему.

Это не прохладно работающая тыловая система, а фронтальный сервер, с Oracle EE, Apache, WebCache, сопутствующими сервисами и весьма активным ДНС впридачу.

root @ host / # top
load averages: 0.01, 0.01, 0.00; up 0+22:37:37 10:55:35
78 processes: 77 sleeping, 1 on cpu
CPU states: 99.1% idle, 0.4% user, 0.5% kernel, 0.0% iowait, 0.0% swap
Kernel: 301 ctxsw, 18 trap, 518 intr, 636 syscall, 17 flt
Memory: 4095M phys mem, 97M free mem, 4096M total swap, 4096M free swap

PID USERNAME NLWP PRI NICE SIZE RES STATE TIME CPU COMMAND
6472 oracle 11 59 0 0K 0K sleep 0:33 0.13% oracle
6467 oracle 11 59 0 0K 0K sleep 0:44 0.11% oracle
7382 root 1 59 0 20M 19M cpu/0 0:02 0.08% top
6412 oracle 1 59 0 0K 0K sleep 0:18 0.03% oracle
6400 oracle 17 59 0 0K 0K sleep 0:08 0.02% oracle
6392 oracle 1 59 0 0K 0K sleep 0:17 0.02% oracle
6454 ias1 6 59 0 61M 38M sleep 0:21 0.01% webcached
6408 oracle 1 59 0 0K 0K sleep 0:19 0.01% oracle
6402 oracle 20 59 0 0K 0K sleep 0:14 0.01% oracle
6603 oracle 35 59 0 251M 161M sleep 0:28 0.01% java
6452 ias1 6 59 0 25M 11M sleep 0:12 0.01% webcached
7262 oracle 1 59 0 8224K 2660K sleep 0:00 0.00% sshd
6474 oracle 3 59 0 0K 0K sleep 0:01 0.00% tnslsnr
6396 oracle 1 59 0 0K 0K sleep 0:01 0.00% oracle
315 root 1 100 -20 2552K 1240K sleep 0:02 0.00% xntpd

Как видно, свободной памяти почти нет. Однако:

root @ host / # swap -l
swapfile dev swaplo blocks free
/dev/zvol/dsk/rpool/swap 181,2 8 8388600 8388600

Неубедительно, правда? Взглянем детальней:

root @ host / # swap -s
total: 922904k bytes allocated + 840908k reserved = 1763812k used, 2641584k available

root @ host / # vmstat 5 5
kthr memory page disk faults cpu
r b w swap free re mf pi po fr de sr s0 -- -- -- in sy cs us sy id
0 0 0 2780460 303496 12 83 0 0 0 0 4 9 0 0 0 547 779 332 1 1 98
0 0 0 2584824 128200 0 18 0 0 0 0 0 6 0 0 0 520 630 290 0 0 99
0 0 0 2584548 127992 0 9 0 0 0 0 0 6 0 0 0 520 400 308 0 0 100
0 0 0 2584472 127884 0 0 0 0 0 0 0 2 0 0 0 504 324 277 0 0 99
0 0 0 2584464 127876 0 0 0 0 0 0 0 30 0 0 0 566 323 338 0 0 100

Подкачка, в общем, копеечная, как и показывает top.

Причем это в разгар рабочего дня, почти на пике нагрузок. О, вот потребовалась память:

load averages: 0.00, 0.01, 0.00; up 0+22:48:39 11:06:37
79 processes: 78 sleeping, 1 on cpu
CPU states: 99.2% idle, 0.3% user, 0.5% kernel, 0.0% iowait, 0.0% swap
Kernel: 287 ctxsw, 2 trap, 509 intr, 625 syscall, 2 flt
Memory: 4095M phys mem, 124M free mem, 4096M total swap, 4096M free swap

PID USERNAME NLWP PRI NICE SIZE RES STATE TIME CPU COMMAND
6472 oracle 11 59 0 0K 0K sleep 0:33 0.12% oracle
6467 oracle 11 59 0 0K 0K sleep 0:44 0.10% oracle
7427 root 1 59 0 3460K 2296K cpu/1 0:00 0.09% top
6412 oracle 1 59 0 0K 0K sleep 0:18 0.03% oracle
6392 oracle 1 59 0 0K 0K sleep 0:17 0.01% oracle
6408 oracle 1 59 0 0K 0K sleep 0:20 0.01% oracle
6402 oracle 20 59 0 0K 0K sleep 0:14 0.01% oracle
6603 oracle 35 59 0 251M 161M sleep 0:28 0.01% java
6454 ias1 6 59 0 61M 38M sleep 0:22 0.01% webcached
6452 ias1 6 59 0 25M 11M sleep 0:12 0.01% webcached
6400 oracle 17 59 0 0K 0K sleep 0:09 0.01% oracle
6541 ias0 16 59 0 19M 7840K sleep 0:04 0.00% opmn
6474 oracle 3 59 0 0K 0K sleep 0:01 0.00% tnslsnr
6552 ias0 3 59 0 25M 1832K sleep 0:12 0.00% httpd
7262 oracle 1 59 0 8224K 2660K sleep 0:00 0.00% sshd

"... и немедленно выпил."

Как видно, при запросах на память, ZFS сразу отступает, освобождая ARC в пользу приложения/процесса. Механизм preemption для ZFS закон.

Прошу обратить внимание на тот факт, что, хотя в подкачке и зарезервировано некоторое количество страниц, они туда фактически не попадают, так как ZFS возвращает страницы сканеру страниц раньше, чем происходит выгрузка в подкачку:

root @ host / # iostat 5 5
tty sd0 cpu
tin tout kps tps serv us sy wt id
0 38 508 9 12 1 1 0 98
0 22 8 1 0 0 0 0 100
0 8 20 2 0 0 0 0 100
0 8 48 5 0 0 0 0 100
0 8 8 1 0 0 0 0 100

Что при этом делает ZFS? Достаточно активно работает:

root @ host / # zpool iostat 5
capacity operations bandwidth
pool used avail read write read write
---------- ----- ----- ----- ----- ----- -----
data 12.1G 91.9G 1 5 183K 310K
rpool 5.95G 26.0G 0 0 13.5K 844
---------- ----- ----- ----- ----- ----- -----
data 12.1G 91.9G 0 1 0 51.1K
rpool 5.95G 26.0G 0 0 0 0
---------- ----- ----- ----- ----- ----- -----
data 12.1G 91.9G 0 2 0 105K
rpool 5.95G 26.0G 0 0 0 0
---------- ----- ----- ----- ----- ----- -----
data 12.1G 91.9G 0 17 0 502K
rpool 5.95G 26.0G 0 0 0 0
---------- ----- ----- ----- ----- ----- -----
data 12.1G 91.9G 0 0 0 17.6K
rpool 5.95G 26.0G 0 0 0 0
---------- ----- ----- ----- ----- ----- -----
data 12.1G 91.9G 0 0 0 16.0K
rpool 5.95G 26.0G 0 0 0 0
---------- ----- ----- ----- ----- ----- -----
data 12.1G 91.9G 0 0 0 12.0K
rpool 5.95G 26.0G 0 0 0 0
---------- ----- ----- ----- ----- ----- -----
data 12.1G 91.9G 0 0 0 16.0K
rpool 5.95G 26.0G 0 0 0 0
---------- ----- ----- ----- ----- ----- -----
data 12.1G 91.9G 0 12 0 338K
rpool 5.95G 26.0G 0 0 0 0
---------- ----- ----- ----- ----- ----- -----
data 12.1G 91.9G 0 25 0 1.02M
rpool 5.95G 26.0G 0 0 0 0
---------- ----- ----- ----- ----- ----- -----
^C

Так что, дорогие коллеги, успокаиваемся и не волнуемся. Солярис определенно писали не за еду. :)

Но самый сакраментальный вопрос мы-таки упустили из виду. Итак,

Почему ZFS так мечет оперативную память?

Чтобы ответить на этот вопрос, вспомним две вещи.

Первая при создании пула/датасета обычно не задают параметр recordsize, коей по умолчанию равен в подавляющем большинстве случаев 128К:

root @ host / # zfs get all|grep recordsize
rpool recordsize 128K default
rpool/ROOT recordsize 128K default
rpool/ROOT/zfsBE_sol10_509 recordsize 128K default

Обычно никто не догадывается изменить его ДО заполнения пула/датасета, а после менять его поздно - изменение коснется лишь вновь записываемых данных, те данные, которые уже были, останутся со старым размером записи.

Соответственно, поскольку у ZFS умная и глубокая предвыборка, то она производится вот этими самыми большими порциями. Что, с одной стороны, в некоторых приложениях есть то, что требуется.

Если в системе только один root pool, то, как показывает практика, существенной нагрузки на память нет, что вполне объяснимо. Прочитали данные в память один раз, ZFS из кэша данные выкинула и освободила его по таймауту.

Если на нем стоит что-нибудь простенькое, типа прокси-сервера - большой нагрузки тоже не будет, у приложения собственный кэш, который сам утаптывает ARC.

А вот если есть второй пул, на котором стоит Oracle - другое дело. Oracle читает большими предвыборками, мультиблоком (db_file_multiblock_read_count), если процент scattered reads большой, то, соответственно, ARC буферизует предвыборку, особенно когда обнаруживает предсказуемую и устойчивую склонность приложения к повторяющимся большим предвыборкам данных с диска.

Вот и вся причина заполнения памяти в случае, если на ZFS стоит Oracle.

Вопрос. Насколько это критично?

Опыт показывает, что работа СУБД как минимум не замедляется, а, при необходимости освободить означенную память, занятую кэшем ARC, это делается немедленно, как и было сказано выше.

Можно ли уменьшить расход памяти под кэш ZFS?

Если вас это беспокоит - можно.

Во-первых, можно создавать пулы под БД с отдельными логами и кэшами на дисках. Рекомендую перед этим все же внимательно прочесть вот это. Вдумчиво курим маны zpool:

log A separate intent log device. If more than one log
device is specified, then writes are load-balanced
between devices. Log devices can be mirrored. How-
ever, raidz and raidz2 are not supported for the
intent log. For more information, see the "Intent
Log" section.

cache A device used to cache storage pool data. A cache
device cannot be cannot be configured as a mirror
or raidz group. For more information, see the
"Cache Devices" section.

Во-вторых, можно до заполнения пулов поиграть с их свойством recordsize. Уменьшение данного свойства, скажем, до 16К, уменьшает нагрузку на память, даже в случае использования БД. Данная рекомендация, однако, вступает в некоторое противоречие вот с этими рекомендациями от инженеров Сан, смотреть Oracle Considerations - там есть пара взаимоисключающих параграфов, которые могут вызвать когнитивный диссонанс:

  • For better OLTP performance, match the ZFS recordsize to the Oracle db_block_size.
  • Use a separate file system with the default 128K record size for Oracle logs.
Заметьте, что, с точки зрения, скажем, БД, предпочитающей select или работающей с LOB (реально приличного размера) размер записи в 128К в целом предпочтительней пулов с меньшими значениями размеров записи.

В-третьих, можно просто прикупить еще памяти и не беспокоиться. :)

ZFS ведет себя вовсе не как Windows - "... сколько находит - столько и занимает". Сколько нужно - столько и займет. И отдаст немедленно по первому требованию.