barbitoff programmer`s blog

Здесь я публикую заметки из программерской жизни: грабли, на которые мне случилось наступить, проблемы, для которых было найдено элегантное (или не очень) решение, а также все, с чем мне пришлось столкнуться и чем хотелось бы поделиться =)
PS Если хотите меня поблагодарить - на странице есть 3 места, чтобы это сделать =)

среда, 29 августа 2012 г.

PostgreSQL: бэкапы больших БД

Самая простая идея бэкапов PostgreSQL - это pg_dump, вызываемый cron`ом. Где такой подход крайней неэффективен и порой даже вообще неприменим - так это при больших объемах БД, особенно, если БД изменяется редко. В таких случаях мы будем получать большое количество увесистых, но практически идентичных бэкапов.
Благо разработчики постгреса подумали над этим вопросом, и предусмотрели возможность "разностных" бэкапов БД (точнее, бэкапится не одна БД, а кластер целиком). В данном случае имеется "базовый" бэкап кластера, и множество мелких бэкапов, содержищих лишь измененные данные. В качестве этих минибэкапов выступают "write ahead" логи (WAL).
Исчерпывающее описание процесса настройки приведено на сайте postgresql (http://www.postgresql.org/docs/8.4/static/continuous-archiving.html), я приведу лишь небольшой пример (используется PostgreSQL 8.4 на Debian Squeeze):
1) Во-первых, необходимо включить архивацию WAL и задать команду, которая будет вызываться каждый раз, когда postgres закончил очередной WAL-файл и его можно архивировать. В моем случае каждый WAL-файл архивируется 7z-ом и кладется в /backup/WAL-archive/ (перед этим проверяется, что файл ещё не существует, как рекомендовано в доках постгреса). Ниже приведен фрагмент конфигурации кластера:
# - Archiving -
archive_mode = on
archive_command = 'test ! -f /backup/WAL-archive/%f.7z && 7zr a /backup/WAL-archive/%f %p'
#archive_timeout = 0 
2) Перезапускаем postgres
3) Если все ок, через некоторое время в /backup/WAL-archive/ начнут появляться файлы архивов
4) Теперь нужно выполнить базовый бэкап кластера. Для начала, сообщаем серверу, что начался бэкап, выполняя следующий SQL-запрос:
SELECT pg_start_backup('backup')
Параметр SQL-функции pg_start_backup является лишь меткой, его значение не принципиально.
Затем, выполняем собственно базовый бэкап директории данных кластера:
tar c --exclude='pg_xlog/*' -C /var/lib/postgresql/8.4/ myclaster | 7zr a -si /backup/base-archive/base.tar.7z
В данном случае директория данных моего кластера размещена в /var/lib/postgresql/8.4/myclaster, а tar.7z архив будет сложен в /backup/base-archive/. Содержимое директории WAL-логов исключается их копии, т.к. WAL`ы архивируются отдельно.
Теперь можно сообщить серверу, что бэкап закончен, запросом:
SELECT pg_stop_backup();
Сервер заархивирует все созданные за время бэкапа WAL`ы, а также ещё один файл с расширением "backup". Его имя будет указывать на тот WAL-файл, начиная с которого данные не попали в бэкап. Все WAL-файлы до него можно удалять, т.к. они  относятся к изменениям, уже присутствующим в только что созданном базовом бэкапе.

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


вторник, 28 августа 2012 г.

Debian: скачивание папок по ftp

Возникла необходимость бэкапить несколько папок с одного сервера на другой, получая их по ftp (на обоих серверах - Debian). Написать соответствующий bash-скрипт не составило труда, возник лишь вопрос с выбором утилиты для собственно скачки папок по ftp. Первой идеей был wget, умеющий и работать с ftp, и рекурсивно выкачивать директории:
wget -r "ftp://user@password:host"
Но тут камнем преткновения стал русский язык в именах скачиваемых файлов и папок: независимо от наличия / отсутствия поддержки iri папки и файлы с русскими именами успешно скачивались, вот только сохранялись с побитыми именами (приведен фрагмент лога wget`а):
--2012-08-27 11:24:14--  ftp://login:passorw@host/dir/%D0%BD%D0%B5%D1%82%20%D0%B2%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B5/file.doc
           => « host/dir/не\321%82 в \321%81пи\321%81ке/ file.doc»
(в оригинале должно было быть "нет в списке"), т.е. часть русских символов оставалась URL-кодированной. При этом на сервере используется UTF-8, и другие клиенты скачивают файлы без проблем.
В результате от wget я отказался в пользу lftp. В нем скачка директории выполняется так:
lftp -e 'mirror -e dir /path/to/download_dir/ bye;' -u user,password host
Здесь "dir" - папка на ftp-сервере,  /path/to/download_dir/ - путь для сохранения скачанной папки.

Debian Squeeze: wget и поддержка iri


По каким-то причинам wget из репов Debian Squeeze скомпилирован без поддержки Internationalized Resource Identifier и Internationalized Domain Names (IRI / IDN):
# wget -V
GNU Wget 1.12 built on linux-gnu.
+digest +ipv6 +nls -ntlm +opie +md5/builtin -https -gnutls -openssl
-iri 
При попытке использовать опцию --iri при вызове wget (или раскомментировать её в /etc/wgetrc) выводится соответствующее сообщение:
# wget --iri "http://some.site/"
This version does not have support for IRIs
Решается это перекомпиляцией wget (можно воспользоваться сорцами из репов Debian, хоть там и не самая актуальная версия). Однако здесь не все так просто (здесь я собираю wget с суффиксом "-iri", чтобы он установился отдельно от родного wget`а):
# apt-get source wget
# cd wget-1.12
# ./configure --program-suffix=-iri
...
configure: WARNING: Libidn not found
...
Если последнее сообщение случайно пропустить в выводе configure-скрипта, то wget соберется все равно без iri. Устраняем причину warning`а:
# aptitude install libidn11-dev
Теперь конфигурация проходит как надо:
# ./configure --program-suffix=-iri
...
configure: Enabling support for IRI.
...
 Всё, компилируем и устанавливаем:
# make
# make install
Результат достигнут:
# wget-iri -V
GNU Wget 1.12 built on linux-gnu.
+digest +ipv6 +nls -ntlm +opie +md5/builtin -https -gnutls -openssl
+iri 

понедельник, 27 августа 2012 г.

"dpkg-source: not found" при попытке выполнить "apt-get source"

Проблема:
При попытке скачать исходные файлы командой "apt-get source <package_name>" возникает ошибка:
sh: dpkg-source: not found
Причина:
Не установлен пакет dpkg-dev.  

четверг, 23 августа 2012 г.

Hibernate: Hi/Lo алгоритм генерации идентификаторов

Hi/Lo алгоритм генерации идентификаторов предполагает следующее: чтобы за каждым идентификатором нового persistent-объекта не бегать в БД (как в случае с, например, sequence-генераторами), можно "резервировать" сразу некоторое количество идентификаторов, и назначать их новым объектам, пока они не закончатся и не придется снова идти в БД за новой партией. 
Резервирование заключается в следующем: идентификатор разбивается на 2 части (например, поразрядно). Первая часть (n старших разрядов) - "hi", берется из БД (например, из последовательности). Вторая же - "lo" (m младших разрядов), последовательно назначается приложением. Размер m настраивается в конфигурации, n же определяется типом данных (short / int / long) и установленным размером m.
Пусть, например, m равен двум десятичным разрядам. Тогда, в начале, приложение запрашивает очередное hi-значение из БД (например, из последовательности), и затем использует его для генерации 100 идентификаторов для новых объектов. Скажем, если полученное значение последовательности равно 17, то диапазон идентификаторов, который сможет использовать приложение для новых объектов, будет равен [1700,1799].
Особенно данная оптимизация эффективна при выполнении пакетных вставок большого числа записей в БД.
PS Правда, генератор seqhilo в Hibernate 4.1 ведет себя немного отличным образом: для приведенного выше пример он генерирует последовательности идентификаторов: [1717, 1816], [1818, 1917] и т.п., т.е. он сначала прибавляет hi-значение к младшим разрядам, и лишь потом начинает инкрементирование. Это приводит к образованию не сплошной последовательности идентификаторов, а hi- и lo-части делятся уже не поразрядно. Возможно, такому поведению есть какое-то объяснение, нужно поискать.

start-stop-daemon в CentOS

start-stop-daemon из Debian в CentOS нет, но можно собрать его из сорцов (в составе Debian`овского dpkg):
yum install gcc gcc-c++ m4 make automake libtool gettext openssl-devel
wget http://ftp.de.debian.org/debian/pool/main/d/dpkg/dpkg_1.16.8.tar.xz
yum install xz
tar xpJf *.tar.xz
cd dpkg-1.16.8/
yum install ncurses-static
make
cd utils
make install
Правда бинарник кладется в /usr/local/sbin/, которого у меня в CentOS нет в PATH. Так что нужно либо добавить в PATH, либо сделать на  /usr/local/sbin/start-stop-daemon симлинк, например, из /usr/sbin:
ln -s /usr/local/sbin/start-stop-daemon /usr/sbin/start-stop-daemon
Спасибо http://chast.in/start-stop-daemon-on-centos.html

Распаковка tar.xz в CentOS

В Debian есть xz-utils. В CentOS чуть по-другому:
yum install xz
tar xpJf my.tar.xz
Без установки xs при попытке распаковки будет возникать ошибка:
tar (child): xz: Cannot exec: No such file or directory

Аналог insserv / update-rc.d в CentOS

Аналогом команд insserv / update-rc.d, предназначенных в Debian для управления скриптами автозагрузки, в CentOS является chkconfig. Например, для добавления скрипта tomcat6 вместо:
insserv tomcat6 
нужно вызывать:
chkconfig --add tomcat6
Спасибо http://serverfault.com/questions/245418/rh-centos-service-chkconfig-equivalents-in-ubuntu-debian

вторник, 21 августа 2012 г.

Debian / Mint: apt-get за прокси с авторизацией

Чтобы apt заработал за проксей с basic http авторизацией в Debian, нужно прописать в /etc/apt/apt.conf:
Acquire{
http
{
Proxy "http://user:pass@proxy_host:proxy_port";
};
};
В Linux Mint аналогично, но из-за отсутствия apt.conf нужно создать файл в /etc/apt/apt.conf.d/, назвав его, например, "02proxy", и записать приведенный выше конфиг туда.

четверг, 16 августа 2012 г.

Куда сохраняет файлы официальный Google Drive клиент под Android при нажатии "Добавить в офлайн режим"

В родном Google Drive клиенте (называющемся "Диск Google" в русскоязычном маркете) есть функция сохранения любых файлов с "диска" локально для оффлайн доступа. Для этого предназначена опция "Добавить в офлайн-режим" при нажатии на любой файл (или галочка "Офлайн режим" там же). Сохраняются файлы правда не на самое видное место, что несколько затрудняет доступ к ним извне клиента Google Drive. А именно, размещаются они в папках вида:
/sdcard/Android/data/com.google.android.apps.docs/files/pinned_docs_files_do_not_edit/<xxx>/<filename>
, где  <xxx>  - длинная последовательность шестнадцатеричных цифр.

четверг, 2 августа 2012 г.

Версионирование проектов NetBeans

Задался тут вопросом, какие именно файлы в проекте NetBeans нужно версионировать, а какие являются моими локальными настройками и в систему контроля версий попадать не должны (из-за возможных конфликтов с таковыми у других разработчиков). Хорошо, что эта тема прокомментирована в документации NB: http://netbeans.org/kb/docs/java/import-eclipse.html#versioning, где сказано, что подпапку nbproject/private версионировать не нужно (также как и build/dist директории, что, в общем-то, логично).