четверг, 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).

среда, 26 ноября 2008 г.

Bounce report на русском языке в Postfix


Сама идея посылки сообщений от SMTP-сервера пользователю на любом языке, кроме английского, лично мне кажется в корне неверной, но иногда очень просят ;( Тогда приступаем.
Шаблоны сообщений сервера (всего их 4: failure_template, delay_template, success_template и verify_template) лежат в файле, описывамом в main.cf как bounce_template_file
т.е. добавляем к нашему конфигу примерно такую строку:
bounce_template_file = /etc/postfix/bounce.cf

Теперь дело за малым - создать сам файл. Шаблоны по умолчанию (чтобы было, что править) можно получить либо командой:
$ postconf -b
либо лично у меня уже был файлик bounce.cf.default в директории с конфигами postfix
Пишем в наш файл что-то типа:

success_template = <
EOF
Charset: windows-1251

From: MAILER-DAEMON (Mail Delivery System)
Subject: Successful Mail Delivery Report

Ваше письмо успешно доставлено получателю.
Подробности приведены ниже.
С уважением, Программа Postfix на сервере $myhostname
--------------------------------------------------------------------
EOF

(...и три других шаблона в том же духе).

Прошу обратить внимание, что указанный charset действует только в теле письма, а не в заголовках - они должны быть в гольном ascii. За справкой о прочих возможностях/невозможностях оращайтесь к man-странице bounce(5) - там все довольно неплохо описано.

вторник, 11 ноября 2008 г.

Работа с привилегиями пользователей в MySQL


Памятка по операциям выдачи-отъема привилегий пользователей в MySQL (тестировалось на версии 5.1.24). Предполагается, что все последующие действия совершаются от имени учетной записи root сервера MySQL.
Пусть имеется база данных myDb0 с таблицами myTbl1 и myTbl2. Создадим пользователя newusr с правами на чтение из первой таблицы и чтение-вставку во вторую.
mysql> grant select on mydb0.mytbl1 to 'newusr'@'localhost' identified by 'qwerty123';
mysql> grant select, insert on mydb0.mytbl2 to 'newusr'@'localhost';
Заметим в скобках: (
  1. предложение identified by 'qwerty123' нужно только при первом grant для данного пользователя;
  2. для выполнения второго grant (т.е. выдача привилегий уже существующему пользователю) права root необязательны - достаточно иметь привилегию grant на те объекты, к которым мы выделяем права).
Просмотреть существующие привилегии можно командой:
mysql> show grants for 'newusr'@'localhost';
Удалить права можно командой вида:
mysql> revoke insert on mydb0.mytbl2 from 'newusr'@'localhost';
Но если прав выдавалось много, то проще воспользоваться таблицами в системной базе данных mysql. Та есть, например, таблица - tables_priv (сруктура такая: Host char(60), Db char(64), User char(16), Table_name char(64), Grantor char(77), Timestamp timestamp, Table_priv set('Select', 'Insert', 'Update', 'Delete', 'Create', 'Drop', 'Grant', 'References', 'Index', 'Alter', 'Create View', 'Show view', 'Trigger'), Column_priv set('Select', 'Insert', 'Update', 'References'), PRIMARY KEY (Host, Db, User, Table_name)), а, значит можно применить, такую, например, массовую операцию:
mysql> delete from mysql.tables_priv where user='newusr' and db='mydb0';
Если пользователю выдавалась привилегия на все таблицы базы (т.е. mydb0.*), то удалять ее надо из таблицы db(структура такая: Host char(60), Db char(64), User char(16), Select_priv enum('N','Y'), Insert_priv enum('N','Y'), Update_priv enum('N','Y'), Delete_priv enum('N','Y'), Create_priv enum('N','Y'), Drop_priv enum('N','Y'), Grant_priv enum('N','Y'), References_priv enum('N','Y'), Index_priv enum('N','Y'), Alter_priv enum('N','Y'), Create_tmp_table_priv` enum('N','Y'), Lock_tables_priv` enum('N','Y'), Create_view_priv enum('N','Y'), Show_view_priv enum('N','Y'), Create_routine_priv enum('N','Y'), Alter_routine_priv enum('N','Y'), Execute_priv enum('N','Y'), Event_priv enum('N','Y'), Trigger_priv enum('N','Y'), PRIMARY KEY (Host, Db, User)):
mysql> delete from mysql.db where user='newusr' and db='mydb0';
Все операции над этими таблицами не приводит к непосредсвенному изменению привилегий (т.е. вывод show privs не изменится), чтобы записать изменения не забудем выполнить:
mysql> flush privileges;
И еще два замечания в скобках: (
  1. при удалении таблиц и баз данных строчки в таблицах db и tables_priv никуда не исчезают - т.е. при создании нового объекта со старым именем к нему применятся прежде существовавшие права. Хорошо это или плохо, сразу не скажешь, но забывать об этом не рекомендуется;
  2. привилегия grant usage on *.*, отображаемая командой show grants, является привилегией по умолчанию при создании пользователя и может быть удалена только удалением самого пользователя командой drop user 'newusr'@'localhost' или delete from mysql.user where user='sel').

среда, 5 ноября 2008 г.

Создание интерфейсов VLAN во FreeBSD 7.0 и Mandriva 2008.1



Задача - нужно создать два VLAN интерфейса (для определенности - номера VLAN: 62 и 99) на одной сетевой карте (пусть в Linux она будет eth0, во freebsd - em0). Кособокость обеих способов примерно одинакова, так что дальше нет ни слова пропаганды - просто констатация.

FreeBSD
Пересобираем ядро, добавив в конфиг строку
device vlan
или пишем в /boot/loader.conf
if_vlan_load="YES"
потом пишем в /etc/rc.conf такое:
cloned_interfaces="vlan62 vlan99"
ifconfig_vlan99="inet x.x.x.x netmask y.y.y.y vlan 99 vlandev em0"
ifconfig_vlan62="inet z.z.z.z netmask w.w.w.w vlan 62 vlandev em0"
после перезагрузки должны появиться интерефейсы vlan62 и vlan99

Mandriva
(аналогично все завелось на CentOS 5.2, в инете сообщалось, что та же схема работает и в других redhat-подобных дисрибутивах)
Должен быть установлен пакет, содержащий команду vconfig. В мандриве этот пакет звался vlan-utils и по умолчанию не был установлен - в CentOS 5.2 пакет vconfig стоял по умолчанию - ну там вообще много всего лишнего ;)
Короче говоря, делаем
$ which vconfig
и при отрицательном ответе хватаемся за urpmf (для мандривы) или yum search для CentOS.
Потом создаем файл /etc/sysconfig/network-scripts/ifcfg-eth0.62
в нем пишем такое:
DEVICE=eth0.62
VLAN=yes
BOOTPROTO=static
IPADDR=x.x.x.x
NETMASK=y.y.y.y
ONBOOT=yes
аналогично создаем и файл ifcfg-eth0.99
После команды
# /etc/init.d/network restart
у нас должны появиться интерфейсы eth0.62 и eth0.99
Для других семейств Linux привожу "сырые" команды оболочки (в самом крайнем случае, их можно и вписать в /etc/rc.local). Создание VLAN интерфейса:
# vconfig add eth0 62
удаление: (мне пригодилось и в мандриве ;)
# vconfig rem eth0.62

P.S. интерфейся Linux вида eth0.62 для комады sysctl будут выглядеть как eth0/62 - лишняя точка сбила бы команду с толку.
P.P.S. ежели используется VirtualBox, то следует заметить что на драйверах Intel PRO (em(4)) vlan не заведется - это не мной первым замечено - нужно использовать PCnet (le(4)).

четверг, 30 октября 2008 г.

Явное указание архитектуры процессора при сборке RPM

Если мы компилим rpm пакеты из SRPM, то почему бы не убить двух зайцев сразу - и не оптимизировать пакеты под свой процессор (если, конечно, машина не крутится на стареньком i586). Описываемый ниже спсоб не страдает элегантностью, но он, по крайней мере, работал на Mandriva 2008.1
Наша задача разбивается на два этапа (в приведенных далее командах предполагается что пакет собирается для архитектуры pentium4 - доступные значения архитектуры можно смотреть в man gcc (1) (значения опций -march и -mtune) или в файле /usr/lib/rpm/rpmrc - там нам интересны значения arch_canon):
  1. для компиляции пакета для нужной целевой платформы в строку вызова rpmbuild добавляем опцию --target=pentium4
  2. пакет-то мы на прошлом шаге собрали, только rpm -i не сможет его установить. Побеждется это записью строки pentium4-intel-linux (или как там правильно для Вашей архитектуры) в файл /etc/rpm/platform

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

Заставляем работать software модем на ноутбуке с Linux


Есть ноутбук (если точнее, Fujitsu-Siemens Esprimo U9200) и Linux (если точнее, Mandriva 2009.0), у ноутбука есть встроенный модем (по сообщениям винды Motorola SM56), который автоматически при установке мандривы не обнаруживается. Прикручиваем руками.
Т.к. модем у нас софтверный, то понятно, что для его работы нужна некая софтина. Называтся эта софтина slmodemd. В случае мандривы устанавливатеся так:
# urpmi slmodem
Если в вашем дистрибутиве такого пакета нет, то можно поискать тут.
В простом случае для ее работы достаточно поправить код страны в файле /etc/sysconfig/slmodemd:
SLMODEMD_COUNTRY=RUSSIA
на случай, если вы заблудились вдали от родины, то список допустимых стран покажет команда:
$ slmodemd --countrylist
и запустить демон командой:
# /etc/init.d/slmodemd start
Критерий успеха - появление в каталоге /dev файла ttySL0 (это и будет порт модема для программы дозвона).
Но у меня так просто не заработало. У меня понадобилось включить у модема режим ALSA. Пишем в /etc/sysconfig/slmodemd:
SLMODEMD_MODULE=snd-intel8x0m
а потом (как это не удивительно) надо править скрипт /etc/init.d/slmodemd Причина в том, что в режиме ALSA устройством модема является не умолчательное /dev/slamr0, а устройство, полученное от системы asound. Там другое пространство имен. Нужное имя (из двух цифр) ищем командой:
$ cat /proc/asound/pcm | grep -i modem
Видим что-то типа:
00-06: Si3054 Modem : Si3054 Modem : capture 1
заменяем черточки в номере (первое поле) запятыми - и получаем устройство hw:0,6
Так и пишем в /etc/init.d/slmodem:
SLMODEMD_FULLDEVICE=hw:0,6
(это строчка в ветке if [ "$SLMODEMD_MODULE" = snd_intel8x0m ] - как раз наш случай).
На всякий случай, если slmodemd предполагается запускать не через скрипт /etc/init.d , а из какого-то своего скрипта, то все много короче. Строка вызова такая:
# slmodemd -c RUSSIA -a hw:0,6
(кажется, работоспособен и более простой синтаксис -a modem:0)
Модем часто теряет несущую сразу после соединения. Если для дозвона используется wvdial, то одолеть проблему можно, добавив в /etc/wvdial.conf строку:
Carrier Check = no
(при использовании гольного pppd проблема не наблюдается).
P.S. после этого модем завелся, но связаться с провайдером пока удается только на протоколах V.22bis и ниже. Если кому-то интересны такие скорости, то команда выбора протокола в этом случае такая: at+ms=122
Полный список доступных протоколов можно получить командой at+ms=? (протоколы выводятся в виде цифровых кодов, для их перевода в более принятую V.XX-нотацию, можно заглянуть в файл modem/modem_defs.h в исходниках slmodem - в нем искать описание enum DP_ID. Неплохую справку по АТ-командам, поддерживаемым slmodem, можно найти, почитав файл modem/modem_at.c - надо сказать, что перечень их довольно сильно отличается от официальной документации производителя модема - ежели вам все-таки не хватает списка от Motorola, читайте его тут).

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

Настройка vpn-туннеля по протоколу L2TP в Linux



Дано: Mandriva 2008.1, маршрутизатор Cisco 7200 серии, терминирующий туннели и поддерживающий протокол L2TP, нужно поднять туннель по этому протоколу без поддержки IPSec.
Пусть: адрес Cisco 172.16.0.1, мой шлюз по умолчанию (до поднятия туннеля) 10.0.0.1, имя пользователя vpn_user, пароль vpn_secret (в дальнейшем эти значения в командах и конфигах заменять на реальные).
Перед тем как начнем - два важных пункта подготовки.
Первое.
Маршрут до терминатора vpn должен существовать как до, так и после поднятия туннеля (когда у нас изменится маршрут по умолчанию), потому необходимо прописать его явно:
# route add -host 172.16.0.1 gw 10.0.0.1
Так как в будущем это должно работать при загрузке, то надо либо добавить эту строку в файл /etc/rc.local (грубый вариант, который будет работать в любом Linux), либо (для Mandriva и RedHat-like) добавить строку
172.16.0.1/32 via 10.0.0.1
в файл /etc/sysconfig/network-scripts/route-eth0 (если у вас несколько сетевух, то возможно будет не eth0)
Еще один вариант (точно работает в Mandriva и рекомендуется Mandriva Wiki) - файл называется /etc/sysconfig/network-scripts/eth0.route (UPD: имя route-eth0 тоже работает) и синтакис у него такой:
ADDRESS0=172.16.0.1
NETMASK0=255.255.255.255
GATEWAY0=10.0.0.1
(если маршрут - не первый в фале, то вместо 0 будет 1, 2 и т. д.)
Для других популярных Linux действуем примерно так (прочитано в сети, не тестировалось):
В debian-like (*buntu, etc):
в файле /etc/network/interfaces
в раздел
iface eth0 inet static
добавить строку
<символ табуляции>post-up route add -host 172.16.0.1 gw 10.0.0.1
В OpenSUSE файл называется /etc/sysconfig/network/ifroute-eth0
и синтаксис строк в нем такой:
172.16.0.1 10.0.0.1 32
И второе. Для того, чтобы туннель мог быть успешно установлен, необходимо разрешить прохождение пакетов протокола UDP между портами 1701 терминатора vpn и нашего компьютера в обоих направлениях. Т.е. если конфигурацией файрвола на прохождение пакетов UDP наложены какие-то ограничения (а они у разумного администратора должны быть наложены), то должны быть добавлены примерно такие два правила:
# iptables -A INPUT -p udp -s 172.16.0.1 --sport 1701 --dport 1701 -j ACCEPT
# iptables -A OUTPUT -p udp -d 172.16.0.1 --sport 1701 --dport 1701 -j ACCEPT
Теперь начинаем собственно строить VPN. Входящий в репозиторий Mandriva пакет l2tpd у меня не завелся, потому ставил его форк xl2tpd (брать тут - есть tar.gz и srpm - кому что больше нравится). Версия у меня была 1.2.0-1. Собираем, ставим, пишем конфиг демона (файл /etc/xl2pd/xl2tpd.conf):
[lac c7200]
name = vpn_user
require chap = yes

require pap = no
lns = 172.16.0.1
redial = yes
require authentication = no
ppp debug = no
pppoptfile = /etc/ppp/options.xl2tpd
autodial = yes
hidden bit = no
flow bit = yes
length bit = yes

Опции pppd как видно из прошлого файла, пишем в /etc/ppp/options.xl2tpd:
unit 0
name vpn_user
ipparam l2tp_vpn
connect /bin/true
mru 1460
mtu 1460
noauth
persist
maxfail 0
nopcomp
noaccomp

имя и пароль добавляем в /etc/ppp/chap-secrets:
vpn_user * vpn_secret

И - для поднятия маршрутизации (т.к. pppd не умеет при запуске перзаписать существующий маршрут по умолчанию) пишем такой скрипт /etc/ppp/ip-up.local:
#!/bin/sh

# переменная $6 - это ipparam,
# заданный нами в /etc/ppp/options.xl2tpd
if [ "$6" = "l2tp_vpn" ]
then
route delete default
route add default ppp0
fi

а для обратного отката - скрипт /etc/ppp/ip-down.local:
#!/bin/sh
route delete default
route add default 10.0.0.1

(не забыть, что эти два файла должны быть исполняемыми)
Если при выключенном vpn шлюз по умолчанию Вам не нужен (все равно никуда не пущают ;), то можно вместо таких жестоких путей просто добавить строку:
defaultroute
в файл /etc/ppp/options.xl2tpd
Запускаться наш vpn будет командой:
# /etc/init.d/xl2tpd start
Осталось заметить в скобках, что в ту же мандриву входит модуль ядра pppol2tp. Так вот его в данной конфигурации загружать не надо. xl2tpd вроде должен уметь с ним работать, но у меня не заработал. Возможно, что-то не то с версиями ядра и демона.

среда, 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";
}

Запуск ntpd под FreeBSD


Как это ни странно, но в стандартной поставке FreeBSD болванки для файла конфигурации ntpd (8) нет ни в /etc, ни в /usr/share/examples. Поэтому восполняю это досадное упущение. Взято из FreeBSD Handbook и http://support.ntp.org/bin/view/Servers/StratumOneTimeServers (список публичных ntp-серверов 1 страты). Называется файл конфигурации /etc/ntp.conf, минимально необходимое содержимое его такое:
# это сервера, откуда берем время
# если используется restrict, лучше брать те сервера, у которых
# 1 адрес, а не пул - а то придетс яписать доступ всем
server clock.nc.fukuoka-u.ac.jp prefer
server ntp1.mmo.netnod.se
server ntp.nic.cz
# а это ACL
restrict default ignore
restrict 127.0.0.1
# это те, от кого мы берем дату
restrict ntp.nic.cz noquery notrap
restrict ntp1.mmo.netnod.se noquery notrap
restrict clock.nc.fukuoka-u.ac.jp noquery notrap
# а это - наши клиенты
restrict x.x.x.x mask y.y.y.y nomodify notrap
restrict z.z.z.z mask w.w.w.w nomodify notrap

не забыть конечно добавить в /etc/rc.conf такое:
ntpd_enable="YES"
работает ntpd на порте 123 по протоколу UDP - так что эти порт должен быть открыт в firewall
Тестирование с UNIX-like клиента такой командой:
$ ntpdate -d your_server_addr
По ее выводу легко понять, какая дата получена с сервера (и получена ли вообще). Опция -d означает не устанавливать дату фактически, зато показать отладочную информацию (что нам и нужно).
Много полезного можно узнать, запуская на сервере команду ntpq (8) - напр. про соседей нам расскажут субкоманды peer и ass (sic!)

P.S. Во FreeBSD 7.1-PRERELEASE столкнулся со странным поведением стартового скрипта /etc/rc.d/ntpd - он зачем-то запускат два процесса ntpd (с клиента все выглядит нормально, но на сервере второй процесс переодически грязно ругается в логи). На всякий случай, если у кого-нибудь вдруг произойдет тоже самое (в чем лично я сильно сомневаюсь), сообщаю, что методом тыка удалось найти на эту беду управу. Помогает строчка
ntpdate_enable="YES"
в файле /etc/rc.conf Почему помогает - не знаю сам - придумалось оно путем сложных ассоциаций на тему строки REQUIRE в скрипте /etc/rc.d/ntpd

понедельник, 22 сентября 2008 г.

INNODB, INSERT ... SELECT и большие таблицы

В MySQL 5 (у меня версия 5.1.24-rc-log) при использовании движка INNODB существует проблема: если делаем INSERT INTO mytable SELECT ... FROM outhertable WHERE ... , и таблица, из которой SELECT - достаточно большая (миллионы записей), то после нескольких десятков секунд страдания появляется сообщение об ошибке: The total number of locks exceeds the lock table size
Есть два способа, как с этим бороться:
  1. по большому счету это не способ, а костыль, но если на машине сильная нехватка ОЗУ, то можно воспользоваться. Разделяем INSERT ... SELECT на два оператора - сперва делаем SELECT ... INTO OUTFILE 'tmpfilename', а потом LOAD DATA INFILE 'tmpfilename' INTO TABLE mytable Потом этот файл неплохо удалить - только следует учесть, что владелец у него - mysql, зато права доступа - еще интереснее - 0666 ;)
  2. вариант более нормальный - установить в /etc/my.cnf параметр innodb_buffer_pool_size=???M Документация утверждает, что значение может быть до 80% ОЗУ - то есть - кому сколько не жалко. У меня проблема не наблюдалась уже при 64 Мб. Значение по умолчанию (на платформе FreeBSD 7.0) - всего лишь 8 Мб. Просмотреть текущее значение этой переменной можно командой show variables like 'innodb_buf%';
Обсуждение этой ошибки на официальном сайте MySQL можно посмотреть тут (много английских букв)

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

Создание самоподписанного SSL сертификата для apache


Крохотная, но очень часто нужная страничка из записной книжки.
Команды взяты отсюда
Опыты проводились с apache-ssl-1.3.41, но по идее долно работать и для других версий.
Создать самоподписанный сертификат без пароля (passphrase) с временем жизни 1000 дней (по умолчанию - 30):
$ openssl req -new -x509 -nodes -out server.crt -keyout server.key -days 1000
Глянуть содержимое сертификата:
$ openssl x509 -noout -text -in server.crt
Подробнее об опциях читать man req и man x509 man openssl дан только общий обзор команды)

P.S. после длительных попыток заставить корректно работать name-based virtual hosting с SSL (не то что оно совсем не работало, оно работало, но как-то не так - напр. упорно выдавало клиенту для разных виртуальных хостов один и тот же сертификат), нашел в официальной документации строку, что сие невозможно. См. здесь и здесь Вот так просто и сурово: "не работает по причине самой природы протокола SSL". Все нередко встречающиеся в сети конфиги с директивами SSLCertificateFile внутри контейнера <VirtualHost> считать неудачной шуткой. Единственный рекомендованный документацией путь - использование разных портов для разных виртуальных хостов.

Транзакции в 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 и т. д.

вторник, 16 сентября 2008 г.

Обновление приложений во FreeBSD и в CentOS



Старая как мир задача - что-то где-то обновилось (напр. clamav), нужно броситься вдогонку за новой версией. Что делаем? (только pls не ищите ниже очередного гундения на тему, что круче - линух или фря - это просто записки на память) Версии систем у меня такие: FreeBSD 7.0 и CentOS 5.2.
FreeBSD
  • Если нет никакого еще дерева портов (это кажется не наш случай, но на всякий пожарный), то # portsnap fetch extract update;
  • Если уже запускался ранее portsnap(8) (искать его следы в /var/db/portsnap - если командой portsnap не пользовались, то он будет пустой), то # portsnap fetch update;
  • Если есть старое дерево портов и supfile для портов (болванку брать в /usr/share/examples/cvsup/ports-supfile), то # csup ports-supfile;
Потом:
# cd /usr/ports/dir/ourport
# make deinstall reinstall clean
Если какие-то пакеты зависят от нашего, make reinstall не обращает на это никакого внимания, толтко сообщает о несчастных в консоль (чтобы потом не забыли похоронить трупы и эвакуировать выживших ;)
Если обновляли демона - не забыть:
# /usr/local/etc/rc.d/ourdaemon restart
и смотреть в логи на результаты
CentOS
Ищем где-нибудь на просторах инета нужный src.rpm (допустимые платформы rhel или centos - желательно, fc - фопустимо, с mdv и suse лучше не связываться). Для начала можно попробовать поискать на офинциальных зеркалах - напр. тут, но там мало чего есть. Порой помогает rpmfind или сайты приложений). Допустим, нашли:
# rpm -i myprog.src.rpm
# cd /usr/src/redhat/SPECS
ищем specfile нашего приложения - и нещадно правим. Потом:
# rpmbuild -bb myprog.spec
установочный пакадж образуется в папке ../RPMS/arch (arch может быть noarch, i386, i586 и т. д.), его и ставим:
# rpm -U ../RPMS/myarch/myprog.rpm
Если это был демон, перзапускаем:
# /etc/init.d/mydaemon restart
и тоже идем читать логи ;)
Удалить srpm, если исходники уже не нужны, можно командой:
# rpmbuild --rmsource myprog.spec

пятница, 12 сентября 2008 г.

Изменение объема памяти, максимально доступной процессу, во FreeBSD


Как это не прискорбно, программы, которым нужно очень много памяти, существуют не только в Window$ ;(
По умолчанию во FreeBSD 7.0 на платформе i386 программе доступно максимально:
  • на стек - 64 Мб,
  • на сегмент текста - 128 Мб,
  • на сегмент данных - 512 Мб
Если этого мало, есть два пути:
1. в файле конфигурации ядра можно задать опции MAXDSIZ, MAXSSIZ и MAXTSIZ. Синтаксис такой:
options MAXDSIZ=(2048UL*1024*1024)
(примеры смотреть в /usr/src/sys/conf/NOTES)
2. в /boot/loader.conf можно установить переменные kern.maxdsiz, kern.maxssiz и kern.maxtsiz
синтаксис:
kern.maxdsiz="1073741824"
Документация утверждает, что установки в /boot/loader.conf имеют приоритет над установками в конфиге ядра.

Немного о логах в MySQL


Несколько практически важных команд из файла /etc/my.cnf, относящиеся к ведению журнала (расположены в секции [mysqld]):
log=filename
пишет в файл все запросы, поступающие на сервер. Может быть крайне полезна при отладке. На рабочем сервере этот прекрасный способ создания гиганских файлов лучше отключить.
Эти две команды включают ведение бинарного журнала (используется при реплицицировании):
bin-log=filename
server-id=1

(в последней команде важен именно параметр 1 - он задает master сервер, значения от 2 и выше - slave).
Если ничего реплицировать не планируется. бинарный лог лучше выключить - больно уж он большой. Если реплика все-таки необходима, неплохо ограничить размер бинарного лога - и устроить ротацию - например, вот так:
max_binlog_size=8192000
expire_logs_days=1

несмотря на свое название, последняя команда действут только на бинарный лог. Если нужно ротировать остальные логи, на то есть команда:
$ mysqladmin flush-logs
она закрывает/открывает текстовые логи - т.е. действует так, как на нормальные программы kill -HUP (хотя в документации на MySQL мне этого читать не приходилось, но на моей системе - Ver 5.1.24-rc-log for portbld-freebsd7.0 on i386 - привычная kill -HUP прекрасно справляется с этой операцией) - а бинарный лог начинает писать в новый файл.
Еще есть журнал медленных запросов. Кроме самого SQL выражения и времени выполнения, в него пишется, сколько строк возвращено и сколько было просмотрено. Возможно, это пригодиться при оптимизации самых тяжелых запросов. Первый параметр - куда писать, второй - после скольки секунд выполнения запрос считается медленным:
log-slow-queries=filename
long_query_time=5

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

Перенос FreeBSD на другой диск


...и такое в жизни бывает :( Диск начал грязно ругаться в dmesg на ошибки чтения-записи. Значит, надо переезжать. Система у меня версии 7.0, но, кажется, процедура эта уже давно не изменялась.
Выключаемся, вставляем новый диск (у меня он ad2). Он у меня немного поменьше, потому внимательно смотрим
$ df -m
- и думаем, какую файловую систему насколько можно ужать.
Подумали - можно приступать.
# sysinstall
идем в меню Configure->Fdisk, в нем удаляем существующие партиции и создаем одну новую на весь диск. Сложностей никаких. Есть тонкость - выходить нужно по Q, не нажимая W. (Если в задумчивости нажали, катастрофы нет - но перед следующей операцией придется перегружаться).
На вопрос про Boot Manager отвечаем Standard.
Переходим в пункт меню Label.
Здесь нас поджидает еще одна тонкость. Будущий корневой раздел должен быть создан с меткой ad2s1a, но sysinstall не позволяет задавать метку вручную, а при указании точки монтирования отличной от / автоматически присваеват имя ad2s1d. У меня сработала такая последовательность: сперва создал раздел с точкой монтирования / - и он получил правильную метку, а потом нажал M - и изменил точку монтирования на /tmp/root.
Далее без каких-либо сложностей создаются раздел подкачки ad2s1b и файловые системы с точками монтирования /tmp/usr и /tmp/var (соотношение меток и точек монтирования сверяйте с вашим /etc/fstab - если, конечно, у вас нет охоты переделать все по-новому).
Выход из Label нажатием W (появляется большое ругательное окно, с вопросом, понимаете ли вы, что творите. Нужно ответить Yes) и потом Q.
Выходим из sysinstall и смотрим на новые файловые системы:
$ mount
Теперь переносим на них содержимое старого диска:
# cd /
# find . -xdev -print | cpio -p -m /tmp/root
# cd /usr
# find . -xdev -print | cpio -p -m /tmp/usr
# cd /var
# find . -xdev -print | cpio -p -m /tmp/var
Чтобы веселее было ждать конца копирования, у cpio можно добавить опцию --verbose
(команда копирования может быть и такой: pax -rw -X -pe . /tmp/root - так короче, но вариант find+cpio просто привычнее - и переносимее - кто-нибудь видел pax напр. под Linux? - про другие unix-like системы уже молчу).
Вот и весь процесс. Выключаемся, ставим новый диск на место старого - и грузим систему.

вторник, 9 сентября 2008 г.

Пересборка мира в FreeBSD 7.0


0. Задача
Обновление свежеустановленной FreeBSD 7.0 до STABLE, а дерева портов - до CURRENT. В качестве методического руководства использовался FreeBSD Handbook. Написание этой заметки вызвано тем, что в Handbook нужная информация "размазана" по большому количеству страниц (на каждой из которых страшно много букв - да еще и нерусских :), а тут я попробовал собрать все в кучу - и описать процедуру как неветвящуюся последовательность.

1. Установка FreeBSD
Ставим FreeBSD как обычно, только не забываем исходные тексты (src) и коллекцию портов (FreeBSD ports).

2. Получение свежей версии исходных текстов
Для синхронизации использовался csup (1) (рекомендуемый в handbook cvsup нужно ставить из /usr/ports/net/cvsup, а он хочет еще компилятор языка Modula 3 (кто когда последний раз на таком программировал? :) - а нам зачем все это надо?) Болванки для supfile лежат в /usr/share/examples/cvsup.
Чтобы их использовать, нужно изменить строку
*default host=CHANGE_THIS.FreeBSD.org
на какой-то реальный адрес. Я поставил cvsup4.ru.FreeBSD.org, но на всякий случай - вот здесь есть полный список зеркал. Нам интересны два файла stable-supfile и ports-supfile. Правим их - и поехали:
# csup stable-supfile
# csup ports-supfile


3. Пересборка пользовательского окружения
Если не хочется собирать много лишнего, можно создать файл /etc/make.conf (по умолчанию не существут) и добавить в него что-нибудь типа:
CPUTYPE=pentium4
WITHOUT_INET6=YES
WITHOUT_IPX=YES
WITHOUT_ZFS=YES
(представление о возможных опциях можно получить, изучая содержимое файлов /usr/share/examples/etc/make.conf и /usr/share/mk/bsd.own.mk)
Крайне не рекомендую писать WITHOUT_FORTH - если, конечно, вы не знаете точно, чего хотите от этой опции добиться - дело в том, что интерпретатор экзотического языка Forth входит в загрузчик FreeBSD. У меня собранная с этой опцией система насмерть зависала при начале загрузки ядра.

А теперь собственно пересборка:
# cd /usr/src
# make buildworld


4. Пересборка ядра
Обычно нужно что-то подправить в конфиге GENERIC (напр. закоментить options INET6 и device trm), так что создадим свой конфиг:
# cd /usr/src/sys/i386/conf
# cp GENERIC MYCFG

... правим его...
# cd ../../..
# make buildkernel KERNCONF=MYCFG

# make installkernel KERNCONF=MYCFG

на всякий случай перед обновлением системы забекапим файлы конфигурации. Например, так:
# mkdir -p ~/tmp/etc
# cd /etc
# find . -print | cpio -p -m ~/tmp/etc

5. Перезагрузка в однопользовательском режиме
Теперь перегружаемся - и в меню загрузки FreeBSD выбираем Single user mode (Клавиша 4). Когда видим приглашение для /bin/sh нажать RETURN, жмем этот самый RETURN - и начинается самое увлекательное:
# fsck -p
# mount -u /
# mount -a -t ufs

# swapon -a

(эти операции не относятся к пересборке - просто Single user mode ничего лишнего не запускал - вот мы ручками и поднимаем более-менее работоспособное окружение - примонтируем на чтение-запись файловые системы и подключаем раздел подкачки).
А теперь - пора за дело:
# cd /usr/src
# mergemaster -p
# make installworld

# mergemaster -U
опции последней команды задают автоматическое обновление всех файлов в /etc , которые не изменялись пользователем.
И - на перезагрузку. Теперь уже - обычную.

6. Пейзаж после битвы
Перезагрузились? Любуемся на новый вывод команды
$ uname -a
И - если все остались живы - самое время убрать после себя мусор:
# cd /usr/src
# make delete-old
(перед тем, как дать эту команду можно посмотреть
$ make check-old
увидим список того, что предполагается удалить. А то вдруг там что-то ценное? :)
# rm -rf /usr/obj

Пример хэш-таблиц в C

В стандарной библиотеке языка C, как известно, нет поддержки хэш-таблиц (она есть в STL C++, но этот язык - не в моем вкусе), но зато GNU libc сожержит функции работы с db-файлами (используются, например, sendmail). Причем, как указано в man-страницк на функцию dbopen, аргумент <имя файла> может принимать значение NULL - и при этом таблица на будет сохраняться на диске. Что нам на самом деле и надо. Далее следует маленький (абсолютно практически бесполезный) пример, демонстрирующий, как такое использование функции позволяет вполне гладко работать с хэш-таблицей на гольном C.

Заголовочные файлы соответствуют FreeBSD 7.0 - в других сиситемах стОит посмотреть man dbopen(3).

/* программа создает хэш-таблицу, ключами которой являются аргументы коммандной строки, а значениеями - пара (порядковый номер аргумента, длина в символах */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <db.h>
#include <sys/types.h>
#include <fcntl.h>
#include <limits.h>


/* такую структуру имеют данные в нашей таблице */
struct mydata {
int num;
size_t len;
};


int main(int argc, char** argv) {

DB* db;

/* и ключ, и значение в db-файле имеют тип DBT - это структура из size_t size и void* data - как видно, реальные данные могут иметь любой размер и любой тип */
DBT ky, va;
int i;
struct mydata d;

/* первый NULL в аргументах - это как раз знак того, что таблицу не надо сохранять на диск */
if ((db = dbopen(NULL, O_CREAT | O_RDWR, 0600, DB_HASH, NULL)) == NULL)
perror("dbopen");
exit(1);
}
for (i = 0; i < argc; i++) {

/* заполняем ключ */

ky.size = strlen(argv[i]);
ky.data = (void*)argv[i];

/* значение */
va.size = sizeof(d);
d.num = i;
d.len = ky.size;
va.data = (void*)&d;

/* и пишем в таблицу */
db->put(db, &ky, &va, 0);
}

/* а теперь, чтобы убедиться, что у нас все получилось, выведем ключи и значения в обратном порядке */
for (i = argc - 1; i >= 0; i--) {

/* опять надо заполнить ключ */
ky.size = strlen(argv[i]);
ky.data = (void*)argv[i];

/* зато значение заполнится само */
if (!db->get(db, &ky, &va, 0)) {
struct mydata* md = (struct mydata*)(va.data);
printf("key: %s value: (num: %d, len: %ld)\n", (char*)(ky.data), d->num, d->len);
}
}

/* таблица нам надоела - закроем ее */
db->close(db);
return 0;
}


вторник, 26 августа 2008 г.

Будни сисадмина

Чё, старье, летать когда будем?