понедельник, 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;
}