Показаны сообщения с ярлыком perl. Показать все сообщения
Показаны сообщения с ярлыком perl. Показать все сообщения

четверг, 25 декабря 2008 г.

Тюнинг DBD::mysql


Ниже описаны два параметра соединения с сервером БД MySQL с помощью модуля DBD::mysql, которые могут использоваться для повышения производительности.
  1. mysql_server_prepare - операторы сохраняются на стороне сервера.
  2. Активируется с помощью добавления выражения mysql_server_prepare=1 к DSN, напр.:


    $dbh = DBI->connect('DBI:mysql:db1:localhost;mysql_server_prepare=1', 'user', 'pass');


    По идее такой подход должен повышать производительность массовых запросов. Проверяем на заведомо слабой машине (сервер и клиент на одном и том же компьютере). Миллион insert выполняется быстрее на 11%, миллион update - на 19%, миллион delete - на 11%. Стоило оно того? Не знаю. Но вроде противопоказаний не обнаружено.


  3. mysql_use_result - результат запроса не сохраняется на клиенте перед использованием (умолчательное поведение называется mysql_store_result). Может быть применено:
    1. на уровне database handler: добавлением mysql_use_result=1 к DSN или оператором

      $dbh->{mysql_use_result}=1;

    2. или на уровне statement handler - в операторе:

      $dbh->prepare('select * from bigtable', {mysql_use_result => 1});

      или (после prepare и до execute) оператором:

      $sth->{mysql_use_result} = 1;

    Пользу от такого поведения можно почуствовать, если оператор возвращает очень много записей - порядка нескольких миллионов. При умолчательном mysql_store_result perl (по крайней мере, v 5.8.8 на FreeBSD 7.0) пожирает всю доступную память - и умирает, а при mysql_use_result - ничего, живет - и даже обходится не очень большим количеством памяти. К недостаткам этого метода работы с результатом man-страница DBD::mysql (3) относит

    tends to block other processes,

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

четверг, 18 декабря 2008 г.

Обработка сигнала ALRM в Perl


Если мы запускаем из скрипта на Perl какую-то внешнюю программу, может случиться так, что программа эта насмерть зависнет - и следующая после system строчка скрипта никогда не выполнится. Бороться тут можно двумя способами - запуском задания в фоне и обработкой сигнала ALRM. Первый способ не рассматриваем по причине его очевидности (& писать все умеют - fork/exec тоже всем известен), второй немножко интереснее. Простой пример кода:
alarm 60;
system "/very/slow/command";
alarm 0; # это мы сбрасываем будильник, если он не сработал
Если команда, вызванная system, провозится больше минуты, то скрипт аварийно завершится с сообщением "Alarm clock". Если такое поведение нас не устраивает (т.е. зависание дочерней программы не является фатальным), то все несколько сложнее. Последовательность alarm/system/alarm 0 нужно поместить внутрь оператора
eval {}; - и поместить в начало этого блока собственную обработку сигнала ALRM - в самом простом случае такую:
local $SIG{ALRM} = sub {die "Signal ALRM fired\n"};
Этот die не приведет к выходу из программы - только завершит оператор eval {}; - что нам и требовалось. Еще необходимо учесть один момент - описанная схема - прекрасный генератор zombie-процессов. Для борьбы с ними где-то в программе нужно поместить такой цикл:
use POSIX ':sys_wait_h';
while (waitpid(-1, WNOHANG) > 0) {};
Подробнее про работу с сигналами можно почитать в perlipc (1).

среда, 1 октября 2008 г.

Калькулятор для пересчета сетевой маски в длину префикса и обратно

Конечно, настоящий network administrator должен уметь пересчитывать netmask в prefixlen в уме, разбуженный среди ночи. Но ум с возрастом имеет свойство притупляться - и почему-то именно в сфере арифметики в уме. Потому пишем костыль для стареющего админа - скрипт на любимом админском языке, который примет на входе длину префикса или сетевую маску - и подсчитает то, чего нам не хватало


#!/usr/bin/perl -w

# netmask <-> prefixlen convert
# usage: $0 mask|len

use strict;

if ($#ARGV != 0 || $ARGV[0] =~ /[^\d.]/) {
print STDERR "Usage: $0 mask|len\n";
exit 1;
}
if ($ARGV[0] =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
# на входе - сетевая маска

# максимально возможная длина префикса
my $r = 32;
# переводим адрес в число
my $n = ($1 << 24) + ($2 << 16) + ($3 << 8) + $4;
# пока не появится 1 в младшем бите,
# сдвигаем адрес вправо - и уменьшаем длину на 1

until ($n & 1) {
$n >>= 1;
$r--;
}
print $r, "\n";
} else {
# на входе - длина префикса

# переводим ее в числовой адрес маски
my $r = (0xffffffff << (32 - $ARGV[0]))
& 0xffffffff;
# и форматируем в dot-нотацию
for (my $i = 24; $i >= 0; $i -= 8) {
print (($r & (0xff << $i)) >> $i);
if ($i) {
print '.';
}
}
print "\n";
}

четверг, 18 сентября 2008 г.

Транзакции в DBD::mysql

Движок INNODB в MySQL поддерживает транзакции, но по умолчанию DBI работает в режиме AutoCommit. Ниже описывается, как удалось заставить DBD::mysql работать с транзакциями.
Обнаружено два работоспособных пути:
  • в DBI->connect включать атрибут {AutoCommit => 0} (в man DBI написано, что его значение будет сброшено на умолчательную единицу первым же COMMIT, но на практике такое поведение в данной конфигурации не наблюдалось). Транзакция начинается автоматически после DBI->connect - явная посылка BEGIN не требуется - так же успешно сами по себе начинаются все последующие транзакции - очевидно, первой командой транзакции считается следующая после $dbh->commit или $dbh->rollback; Вызов команды $dbh->begin_work при выключенным AutoCommit приводит к ошибке (если мы установили атрибут RaiseError => 1, рекомендуемый документацией, то эта ошибка станет фатальной).
  • если необходимо явно указывать начало транзакции, то в DBI->connect оставляем AutoCommit по умолчанию равным 1, транзакцию же будем начинать коммандой $dbh->begin_work. После $dbh->commit значение $dbh->{AutoCommit} сбрасывается в 1 - в полном соответствии с документацией - т.е. мы продолжаем работать в режиме без транзакций.
Наличие таких двух вариантов поведения, вероятно, не является особенностью собственно DBI - в главе START TRANSACTION, COMMIT, and ROLLBACK Syntax документации по MySQL описаны такие же возможности - либо можно установить параметр сессии SET AUTOCOMMIT=0, и тогда транзакции будут неявно начинаться и записываться/сбрасыться по COMMIT/ROLBACK - либо можно при умолчательном AUTOCOMMIT=1 явно начинать транзакцию коммандой START TRANSACTION или BEGIN.

среда, 17 сентября 2008 г.

Игры со ссылками в Perl: многомерный массив


Массивы Perl (в смысле хэши и списки) не могут быть многомерными, но с помощью механизма ссылок их легко можно эмулировать - т.е. создать, например, хэш, содержащий ссылки на другие хэши. Ниже приводится вполне извращенная демонстрация этой возможности. Если кому-то хочется больших извращений, читайте man-страницу perlref(1).


#!/usr/bin/perl -w

# программа создает хэш, содержащий ссылки на список,
# который в свою очередь содержит ссылки на хэш,
# элементы хэша - ссылки на дескрипторы ввода-вывода


# для начала откроем парочку дескрипторов
open FD, '>/tmp/myout';
open PD, '|nroff -man';

# записываем значения в наш хэш:
# полный синтаксис такой

$a{kiwi}->[7]->{deer} = *FD;
# но работает и в сокращенном виде
$a{'.TH'}[1]{elk} = *PD;
# синтаксис *foo - это ссылка на все,
# что угодно (global reference),
# работает и для дескрипторов ввода-вывода

$a{banana}[5]{wapiti} = *STDOUT;

# а теперь посмотрим, что в нашем хэше
foreach $k (sort keys %a) {
# элемент хеша $a{$k} - ссылка на список,
# значит, $#{$a{$k}} - число элементов в нем

for ($m = 0; $m <= $#{$a{$k}}; $m++) {
# проверка на defined понадобилась
# т.к. добавление второго элемента в список
# автоматически добавляет нулевой, первый и т.д.

if (defined $a{$k}[$m]) {
foreach $n (sort keys %{$a{$k}[$m]}) {
# печатаем в наш дескриптор
# почему-то использование $a{$k}[$m]{$n}
# в print дает syntax error,
# поэтому забиваем костыль

$fh = $a{$k}[$m]{$n};
print $fh join(' ', $k, $n, $m), "\n";
# и не забудем его закрыть
if (fileno($fh) != 1) {
# не stdout
close $fh;
}
}
}
}

Итак, в результате работы нашей програмы на стандарном выводе должна появиться строка:
banana wapiti 5
в файле /tmp/myout - строка:
kiwi deer 7
а на вводе команды nrof -man (а потом - в отформатированном виде - тоже на стандартном выводе) директива:
.TH elk 1

P.S. если хочется узнать, ссылкой на какой тип данных является переменная $x, поможет функция ref($x) - возвращает ARRAY, HASH и т. д.