четверг, 30 декабря 2010 г.

Вызов mysql_use_result в Python-MySQL

Точно так же, как и Perl DBI, Python-MySQL по умолчанию использует API функцию mysql_store_result, что на больших объемах выборки (тестировалось с выборкой возвращающей 17 млн. записей) приводит к использованию невероятного количества памяти - и результатов работы скрипта можно не ждать - ибо система будет увлеченно занята копированием страниц из свопа - и в своп. Метод борьбы с этой бедой - тот же, что и в DBI - нужно использовать функцию mysql_use_result (плата за экономию ресурсов проста и жестока - пока result не закрыт, никакой другой запрос на этом подключении выполнить нельзя).
На практике делается это так. При использовании модуля MySQLdb вместо обычного вызова метода cursor() без аргументов пишем:
cu = db.cursor(MySQLdb.cursors.SSCursor)
Скажем несколько слов о поведении данного типа курсоров. Как легко догадаться, привычный вызов fetchall() делает курсор на серверной стороне абсолютно безсмысленным - так что нужно использовать fetchone(). Вот тут-то нас и поджидает небольшая засада - очевидный цикл:
for i in range(0, cu.rowcount):
как раз и не работает, ибо для SSCursor rowcount не определен. Для любителей чисто питонского цикла
for rw in cu:
сразу скажу, что данная конструкция, как ни странно, работает правильно. Так же можно организовать цикл способом аналогичным тому, который предлагается ниже для модуля _mysql.
Так вот, переходим к этому модулю. Выигрыш в производительности он дает выдающийся - на моей выборке быстрее раз в 5 (даже быстрее, чем аналогичный скрипт на Perl), так что ради таких выгод вполне можно потерпеть несколько зубодробительный синтаксис (тот, кто знаком с MySQL C API ничего страшного в этом синтаксисе не увидит).
Работаем оно так:
db.query("select * from sometable")
rs = db.use_result()
Все это еще не беда. Беда начинается дальше. rs.num_rows() по причинам, изложенным выше, не работает, а потому с циклом придется слегка повозиться. Python - это вам не C и не Perl - оператор присваивания внутри условия цикла не поддерживается, и потому нужно организовывать достаточно уродливый цикл (последний раз что-то подобное приходилось делать в языке 1С Бухгалтерии):

rw = rs.fetch_row()
while rw:
    # do something
    rw = rs.fetch_row()
На этом сюрпризы не заканчиваются. rw в этом примере - вовсе не tuple, состоящий из полей запроса, а tuple, состоящий tuples, состоящих из полей (к счастью, по умолчанию, там этот tuple строго один - для возврата нескольких строк за раз fetch_row нужно вызывать с аргументом maxrows - значение 0 означает "все"). Так что присваивание переменным значений полей будет выглядеть так:
fld1, fld2, fld3 = rw[0]
Еще нужно помнить, что если MySQLdb приводит типы данных MySQL к аналогичным типам Python, то в _mysql все данные - тупые строки (если я правильно помню, аналогичные функции C API ведут себя аналогично).

вторник, 23 ноября 2010 г.

Установка debian-testing c USB stick

Оговорюсь с порога: если вы желаете поставить с USB stick debian-stable, то описанная последовательность действий вам вовсе не интересна, ибо готовые LiveUSB образы лежат вот здесь: http://cdimage.debian.org/debian-cd/current-live/i386/usb-hdd/
А вот с testing придется слегка повозиться. Предположим, что имеется установленная система Linux (вероятно, пойдет практически любая работоспособная) и 1 Гб флешка /dev/sdb - дальнейшее является творчески переработанными инструкциями из хорошей статьи  http://d-i.pascal.at/ (язык - англ.)
Из пакетов, которые не всегда входят в дистрибутив, понадобится только syslinux.
Поехали:
# mkfs.msdos -I /dev/sdb
# syslinux /dev/sdb
# mount /dev/sdb /mnt
Берем vmlinuz и initrd.gz напр. отсюда: http://ftp.fi.debian.org/debian/dists/testing/main/installer-i386/current/images/hd-media/ (зеркало debian не обязательно должно быть финским) и копируем их:
# cp vmlinuz initrd.gz /mnt
Берем понравившийся нам iso-образ отсюда: http://cdimage.debian.org/cdimage/weekly-builds/i386/iso-cd/ (мой выбор debian-testing-i386-xfce+lxde-CD-1.iso) и его тоже - туда же (не забудем, что syslinux не понимает длинных имен):
# cp debian-testing-i386-xfce+lxde-CD-1.iso /mnt/netinst.iso
И создаем следующий файл конфигурации загрузчика /mnt/syslinux.cfg:

default vmlinuz
append initrd=initrd.gz ramdisk_size=10240 \
 root=/dev/rd/0 devfs=mount,dall \
 rw DEBCONF_PRIORITY=medium
(последние три строки - в реальности - одна, слешей не надо,  перенесено просто для убористости).
Отмонтируем флешку - грузимся с нее - и наблюдаем привычный установщик debian.

P.S. система почему-то установилась в какой-то достаточно скромной конфигурации (без менеджера рабочего стола), но решение этой проблемы едва ли затруднит средней руки дебианщика. На всякий случай для памяти:
# mount /dev/sdb /mnt
# mount -o loop /mnt/netinst.iso /media/cdrom
# apt-get update
# apt-get install lxde 

среда, 21 июля 2010 г.

Вставка нулевых значений в auto_increment поля в MySQL



По умолчанию, если в поле с опцией auto_increment вставляется значение 0 или null, MySQL заменяет его следующим значением счетчика. Если критично важно вставить именно 0, то нам поможет директива:
sql-mode='NO_AUTO_VALUE_ON_ZERO'
в /etc/my.cnf
или строка:
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';
в начале файла дампа.

среда, 14 апреля 2010 г.

Странное поведение mktime относительно летнего времени


На опыте столкнулся с тем, что результат, возвращаемый функцией mktime(3) не учитывает перехода на зимнее/летнее время (т.е. результаты для "лета" оказываются на час больше, чем нужно). Проверено на linux 2.6.18 и FreeBSD 8.0 с корректно установленной зоной MSK/MSD - так что, вероятно, именно такое странное поведение соответствует спецификации POSIX. Самое смешное в том, что (в полном соответствии с документацией) функция mktime не только возвращает типа значение time_t, но и "нормализует" содержимое переданной в нее по указателю struct timespec, заполняя совершенно корректными (учитывающеми перевод времени) значениями поля tm_isdst, tm_gmtoff и tm_zone. Очевидно, где-то в этом направлении и лежит путь борьбы с этой функцией. Т.е вместо очевидного:

struct timespec ts;

time_t tm = mktime(&ts);

Пишем теперь так:

struct timespec ts;

time_t tm = mktime(&ts) - ts.tm_isdst * 3600;

Эстетам же и забывчивым можно предложить определить вот такой макрос:

#define mktime(t) (mktime(t) - (t)->tm_isdst * 3600)

и использовать mktime, как обычно.

Еще идеи?

пятница, 12 марта 2010 г.

Проброс сессии ssh из внешнего мира в немаршрутизируемую сеть (ssh tunnel)



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

Исходные данные

В задаче участвуют три хоста: машина, за клавиатурой которой мы находимся (имя: my-cons, адрес: 11.0.0.11), машина, к которой хотим получить доступ (имя: in-serv, адрес: 10.0.0.10) и машина-шлюз, с которой доступны обе эти машины (имя: gat-serv, адрес: 12.0.0.12). В дальнейшем хост, на котором будет вводиться команда, обозначается приглашением командной строки.
Пусть на всех трех хостах имеется учетная запись bigadm, под которой мы и будем работать. Также предполагается, что на in-serv и gat-serv запущен sshd

О ключах

Т.к. наиболее рациональным способом авторизации в ssh является использование публичных ключей, что напишем, какие ключи, где надо прописать.
  1. ключ my-cons:~bigadm/.ssh/id_rsa.pub должен быть добавлен в in-serv:~bigadm/.ssh/authorized_keys
  2. ключ gat-serv:~bigadm/.ssh/id_rsa.pub должен быть добавлен в gat-serv:~bigadm/.ssh/authorized_keys
  3. т.к. скорее всего процесс настраивается с помощью того же ssh, то ключ my-cons:~bigadm/.ssh/id_rsa.pub не помешает в gat-serv:~bigadm/.ssh/authorized_keys (строго говоря, для решения самой задачи это НЕ необходимо - если у нас напр. при настройке был доступ к консоли этого сервера - или мы производили настройку с какого-то другого хоста).
Сам процесс

Все достаточно просто:
bigadm@gat-serv$ ssh -f -N -g -L 2222:10.0.0.10:22 bigadm@12.0.0.12
(следует обратить внимание на ключ -g - без него 2222 порт открывается на адресе 127.0.0.1 - т.е. туннелем можно воспользоваться только локально - а это вовсе не то, что нам сейчас требуется (хотя применимо напр. для проброса по шифрованному каналу нешифрованного трафика - POP3, etc).
bigadm@my-cons$ ssh -p2222 12.0.0.12
и мы должны оказаться в консоли in-serv.

вторник, 2 февраля 2010 г.

nsswitch.conf для FreeBSD, пересобранной без NIS



Дальнейшие опыты проверены на FreeBSD 8.0

NIS в наше время используется достаточно редко, так что при пересоборке мира существует соблазн добавить в /etc/src.conf строку WITHOUT_NIS=YES (для более ранних версий править надо файл /etc/make.conf). После пересборки с этой опцией все вроде работает, но порой замечаются легкие странности - например, демон cron не отправляет стандартный вывод выполенных комманд по почте, а в /var/log/cron пишет:
NSSWITCH(_nsdispatch): nis, passwd_compat, endpwent, not found, and no fallback provided
Очевидно, проблему надо искать в /etc/nsswitch.conf
Там по умолчанию стоит:
passwd: compat
man-страница nsswitch.conf(5) сообщает, что compat означает подержку в файлах /etc/passwd и /etc/group строк + и - , что на самом деле имеет какой-то смысл только при использовании NIS (кто с ней знаком, помнит, зачем это надо - прочим нормальным людям едва ли необходимо об этом знать). Очевидно, система, собранная без поддержки NIS, просто перестает понимать какой-такой это compat - и пасует. Решение представляется очевидным. Меняем строку на банальное:
passwd: files

...и ожидаем новых сюрпризов. Мораль - хочешь спать спокойно, нечего править /etc/src.conf ;)