понедельник, 31 мая 2010 г.

Блеск и нищета PostgreSQL

Давно сталкиваюсь в продакшене с проблемой, что выборки с подзапросами выполняются неприлично долго. Причем в тестах воспроизвести такое поведение никак не удавалось. И вот наконец получилось!
Оказывается, проблема существует всегда, но становится заметной только на таблицах с миллионами записей - простейшая выборка "тормозит", если увеличиваются _значения_ используемых в выборке идентификаторов, потому в таблицах с десятками тысяч и менее записей баг просто незаметен.

Сначала приведу один из рабочих запросов, над которыми ломал голову:
SELECT d.id AS document_id,
 d.phone_number AS phone,
 r.name AS region,
 p.code AS point,
 p.trademark AS point_trademark,
 (case when p.is_priority then '1' else '' end) AS color_it,
 c.code AS center_code,
 a.value AS status,
 u2.name AS user_2,
 tp.name AS template,
 c.name AS center,
 (a.save_date at time zone interval '04:00')::date AS work_date,
 date_trunc('second',(a.save_date at time zone interval '04:00')::time) AS work_time 
 FROM offline.documents AS d
 JOIN auth.users AS u ON d.user_id = u.id
 JOIN auth.regions AS r ON u.region_id = r.id
 JOIN auth.points AS p ON u.point_id = p.id
 JOIN auth.centers AS c ON u.center_id = c.id
 JOIN offline.attributes AS a ON d.id = a.document_id
 AND a.is_last
 AND a.save_date BETWEEN (now()::timestamp - interval '2 week') AND (now()::timestamp + interval '1 day')
 AND a.document_id IN (
  SELECT document_id
  FROM offline.attributes
  WHERE user_id='1111'::int
 )
 JOIN auth.users u2 ON a.user_id=u2.id
 JOIN offline.document_templates tp ON d.document_template_id = tp.id
 JOIN offline.attributes a_s ON d.id = a_s.document_id
 AND a_s.is_last_status
 AND a_s.value='status1'
 WHERE 
 d.id in (
  SELECT document_id
  FROM offline.attributes
  WHERE user_id='1111'::int
 )
 ORDER BY p.is_priority DESC, a_s.save_date ASC
 OFFSET '0'::int
 LIMIT '50'::int;
Последовательное упрощение запроса показало, что проблема скрывается в совершенно элементарном запросе:
SELECT id FROM offline.documents where id in (select document_id from tmp);
Притом подзапрос возвращает менее сотни записей, а выборка из основной таблицы идет по PRIMARY KEY:
explain analyze select document_id from offline.attributes where user_id=3650;
"Index Scan using attributes_user_id_idx on attributes  (cost=0.00..1417.18 rows=546 width=4) (actual time=0.015..0.154 rows=79 loops=1)"
"  Index Cond: (user_id = 3650)"
"Total runtime: 0.195 ms"

create temp table tmp as select document_id from offline.attributes where user_id=3650;
select count(*) from tmp;
79

explain analyze SELECT id FROM offline.documents where id in (select document_id from tmp);
"Merge Join  (cost=100000046.39..100030925.47 rows=200 width=4) (actual time=797.686..888.461 rows=40 loops=1)"
"  Merge Cond: ("outer".id = "inner".document_id)"
"  ->  Index Scan using documents_pkey on documents  (cost=0.00..29407.21 rows=587549 width=4) (actual time=0.010..637.431 rows=587025 loops=1)"
"  ->  Sort  (cost=100000046.39..100000046.89 rows=200 width=4) (actual time=0.150..0.169 rows=40 loops=1)"
"        Sort Key: tmp.document_id"
"        ->  HashAggregate  (cost=100000036.75..100000038.75 rows=200 width=4) (actual time=0.092..0.097 rows=40 loops=1)"
"              ->  Seq Scan on tmp  (cost=100000000.00..100000031.40 rows=2140 width=4) (actual time=0.003..0.046 rows=79 loops=1)"
"Total runtime: 888.522 ms"


explain analyze SELECT d.id FROM offline.documents as d, tmp where d.id=tmp.document_id;
"Merge Join  (cost=100000149.78..100031057.96 rows=2140 width=4) (actual time=795.564..885.250 rows=79 loops=1)"
"  Merge Cond: ("outer".id = "inner".document_id)"
"  ->  Index Scan using documents_pkey on documents d  (cost=0.00..29407.21 rows=587549 width=4) (actual time=0.011..636.265 rows=587025 loops=1)"
"  ->  Sort  (cost=100000149.78..100000155.13 rows=2140 width=4) (actual time=0.098..0.127 rows=79 loops=1)"
"        Sort Key: tmp.document_id"
"        ->  Seq Scan on tmp  (cost=100000000.00..100000031.40 rows=2140 width=4) (actual time=0.003..0.043 rows=79 loops=1)"
"Total runtime: 885.329 ms"

explain analyze SELECT id FROM offline.documents where id in (826798,826798,832792,832792,836566,842270,842270,842144,
842144,842034,842037,842037,844288,844288,844658,844658,844640,853201,853201,853313,853549,853313,853549,855102,855102,
855102,855097,855097,855102,856702,856702,869625,869625,869861,869861,869860,869860,870458,870458,870463,870463,871536,
871536,872055,872055,872279,872279,872368,872368,873792,873789,873789,878005,878005,878022,878022,878022,878022,888492,
888492,890071,890071,890061,890061,891715,891677,891483,891715,891483,895619,895619,899233,899205,899160,899160,899205,
899233,899697,899697);
"Bitmap Heap Scan on documents  (cost=158.28..466.00 rows=79 width=4) (actual time=0.341..0.394 rows=40 loops=1)"
"  Recheck Cond: ((id = 826798) OR (id = 826798) ... OR (id = 899697) OR (id = 899697))"
"  ->  BitmapOr  (cost=158.28..158.28 rows=79 width=0) (actual time=0.330..0.330 rows=0 loops=1)"
"        ->  Bitmap Index Scan on documents_pkey  (cost=0.00..2.00 rows=1 width=0) (actual time=0.013..0.013 rows=1 loops=1)"
"              Index Cond: (id = 826798)"
"        ->  Bitmap Index Scan on documents_pkey  (cost=0.00..2.00 rows=1 width=0) (actual time=0.003..0.003 rows=1 loops=1)"
"              Index Cond: (id = 826798)"
...
"        ->  Bitmap Index Scan on documents_pkey  (cost=0.00..2.00 rows=1 width=0) (actual time=0.005..0.005 rows=1 loops=1)"
"              Index Cond: (id = 899697)"
"        ->  Bitmap Index Scan on documents_pkey  (cost=0.00..2.00 rows=1 width=0) (actual time=0.003..0.003 rows=1 loops=1)"
"              Index Cond: (id = 899697)"
"Total runtime: 0.617 ms"
Еще несколько экспериментов - и готов тестовый сценарий для воспроизведения проблемы:
drop table tmp_facts6;
create table tmp_facts6(id serial NOT NULL, CONSTRAINT tmp_facts6_pkey PRIMARY KEY (id)) WITHOUT OIDS;
insert into tmp_facts6 select id from generate_series(1,1000000) as id;

drop table tmp;
create table tmp as select (1000*random())::int as document_id from generate_series(1,100) as document_id;
explain analyze select id from tmp_facts6 where id in (select document_id from tmp);
"Merge Join  (cost=100000046.39..100021005.39 rows=200 width=4) (actual time=0.292..1.568 rows=96 loops=1)"
"  Merge Cond: ("outer".id = "inner".document_id)"
"  ->  Index Scan using tmp_facts6_pkey on tmp_facts6  (cost=0.00..18456.00 rows=1000000 width=4) (actual time=0.009..0.726 rows=994 loops=1)"
"  ->  Sort  (cost=100000046.39..100000046.89 rows=200 width=4) (actual time=0.252..0.290 rows=96 loops=1)"
"        Sort Key: tmp.document_id"
"        ->  HashAggregate  (cost=100000036.75..100000038.75 rows=200 width=4) (actual time=0.118..0.183 rows=96 loops=1)"
"              ->  Seq Scan on tmp  (cost=100000000.00..100000031.40 rows=2140 width=4) (actual time=0.002..0.057 rows=100 loops=1)"
"Total runtime: 1.645 ms"

drop table tmp;
create table tmp as select (100000*random())::int as document_id from generate_series(1,100) as document_id;
explain analyze select id from tmp_facts6 where id in (select document_id from tmp);
"Merge Join  (cost=100000046.39..100021005.39 rows=200 width=4) (actual time=1.759..115.736 rows=100 loops=1)"
"  Merge Cond: ("outer".id = "inner".document_id)"
"  ->  Index Scan using tmp_facts6_pkey on tmp_facts6  (cost=0.00..18456.00 rows=1000000 width=4) (actual time=0.010..71.777 rows=99397 loops=1)"
"  ->  Sort  (cost=100000046.39..100000046.89 rows=200 width=4) (actual time=0.268..0.313 rows=100 loops=1)"
"        Sort Key: tmp.document_id"
"        ->  HashAggregate  (cost=100000036.75..100000038.75 rows=200 width=4) (actual time=0.121..0.220 rows=100 loops=1)"
"              ->  Seq Scan on tmp  (cost=100000000.00..100000031.40 rows=2140 width=4) (actual time=0.003..0.057 rows=100 loops=1)"
"Total runtime: 115.824 ms"

drop table tmp;
create table tmp as select (1000000*random())::int as document_id from generate_series(1,100) as document_id;
explain analyze select id from tmp_facts6 where id in (select document_id from tmp);
"Merge Join  (cost=100000046.39..100021005.39 rows=200 width=4) (actual time=31.807..1148.310 rows=100 loops=1)"
"  Merge Cond: ("outer".id = "inner".document_id)"
"  ->  Index Scan using tmp_facts6_pkey on tmp_facts6  (cost=0.00..18456.00 rows=1000000 width=4) (actual time=0.011..713.344 rows=995715 loops=1)"
"  ->  Sort  (cost=100000046.39..100000046.89 rows=200 width=4) (actual time=0.263..0.305 rows=100 loops=1)"
"        Sort Key: tmp.document_id"
"        ->  HashAggregate  (cost=100000036.75..100000038.75 rows=200 width=4) (actual time=0.121..0.178 rows=100 loops=1)"
"              ->  Seq Scan on tmp  (cost=100000000.00..100000031.40 rows=2140 width=4) (actual time=0.004..0.062 rows=100 loops=1)"
"Total runtime: 1148.408 ms"
Как видим, скорость выборки падает при пропорционально _увеличению значений идентификаторов_, возвращаемых подзапросом! Грабли просто фееричные, пожалуй, разработчикам PostgreSQL пора давать Шнобелевскую премию.

Upd.Надо же, в форуме указал версию постгреса, а здесь забыл:
select version();
                                                    version                                                     
----------------------------------------------------------------------------------------------------------------
 PostgreSQL 8.1.15 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.1.2 20061115 (prerelease) (Debian 4.1.1-21)
(1 row)
В 8.4 этого бага нет, но версия 8.1 поддерживаемая, а в ней не поправили. Вечная память...

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

Веб-редакторы - метод contentEditable и jQuery

Свершилось - метод contentEditable теперь поддерживают все браузеры, начиная от IE 6 и Firefox 3 (см. Firefox 3 -has attribute ContentEditable on all HTML elements). Соответственно, создание редактируемой области на странице более не требует iframe.
Достойная внимания реализация в виде JQuery-плугина представлена здесь: In-place editing with contentEditable property and jQuery
Демонстрацию смотреть тут

Выглядит просто, а возможности очень большие. Например, можно форматировать текст с помощью клавиш: Ctrl+b, Ctrl+i, Ctrl+u. Манипуляции с блоками текста - Ctrl+c, Ctrl+x, Ctrl+v - доступны также из контекстного меню. Для гиков, скажите вы? А вот и нет - можно просто скопировать форматированный текст из MS Word или с веб-страницы и вставить в редактируемый элемент с сохранением форматирования! Конечно, следует учитывать, что таким способом пользователь может в однострочное поле вставить многострочный документ солидных размеров, но проверку вводимых пользователем данных никто не отменял.

Пример элемента приложения с подобным функционалом предлагаю посмотреть по следующему адресу:
contentEditable Plug-in Проверки вводимых данных здесь нет - первым делом я скопировал всю страницу и вставил это содержимое в элемент списка, и это сработало; но мы уж слишком много хотим от aspnet программеров - им думать некогда, "чукча писатель".

Upd.
Добавил плугин в пакет mbgserver-jquery, опробовал на сервере. Все работает. Для очистки, как обычно, прогоняем через tidy
| tidy -q --doctype omit 2>/dev/null
и забираем HTML из тэга body. Стоит отметить, что полученный результат будет именно содержимым исходного тэга, на который назначен редактор.
Если нас интересует лишь текст, можно, например, использовать вот такой фильтр:
| poisk-file-textfilter html
При этом форматирование по возможности сохранится с помощью ASCII символов и переводов строки. Утилита poisk-file-textfilter входит в комплект разработанной мной системы poisk (см. также одноименный пакет в репозитории). Если же достаточно получить однострочный текст, самым адекватным вариантом будет удаление HTML тэгов и символов разрыва строки простым регекспом.

UUID для PostgreSQL с помощью Tcl

Понадобилось мне быстро добавить уникальный идентификатор (uuid) к таблице. На тикле нужная функция пишется очень легко - с помощью пакета uuid или вызова внешней утилиты. Генерация 10 000 идентификаторов во втором случае занимает около 10 с на десктопе, что вполне себе шустро, так что на нем и остановимся.
Установим пакет с нужной нам утилитой:
sudo aptitude install uuid
А вот и реализация функции:
--DROP FUNCTION uuid();

CREATE OR REPLACE FUNCTION uuid()
  RETURNS text AS
$BODY$

  return [string map {- ""} [exec uuid]]
  
$BODY$
  LANGUAGE 'pltclu' VOLATILE SECURITY DEFINER;
ALTER FUNCTION uuid() OWNER TO offline;

--select uuid();

alter table offline.documents add column checksum text not null default uuid();

CREATE UNIQUE INDEX documents_checksum_idx
  ON offline.documents
  USING btree
  (checksum);

Очень даже наглядная иллюстрация "unix way". Разумеется, так делать можно только под юниксами, где порождение множества процессов есть совершенно нормальное дело - потому и "unix way".

Жизненный цикл программиста

Ссылка на статью: Жизненный цикл программиста

Статья известного российского системного программиста, зав. лабораторией Института системного анализа РАН, члена Российской академии интернета, автора шахматной программы «КАИССА» (первого чемпиона мира среди шахматных программ), президента компьютерной фирмы ДИСКо, лауреата всех профессиональных опросов «Top-100 Российского компьютерного бизнеса», Михаила Донского.

Прекрасно написано и по существу, для меня было истинным удовольствием ознакомиться со статьей. Как физик по образованию, особенно оценил изложение основ системного подхода в программировании. А как человек, много читающий, получил удовольствие от стиля изложения. Рекомендуется к прочтению всем.

четверг, 27 мая 2010 г.

Масштабируемые СУБД

Статья здесь: PAPER: HIGH PERFORMANCE SCALABLE DATA STORES

Хотелось бы перевести как "хранилища данных", но такой термин существует и означает нечто иное; скорее, термин СУБД подходит, хотя я знаю, что под таковыми в русскоязычной литературе обычно (ошибочно) подразумевают реляционные СУБД, а то и вовсе объектно-реляционные (что, в общем-то, самой грязной воды маркетинг). Насчет высокой производительности вопрос спорный, все зависит от задач. Вот так название статьи и превратилось в "Масштабируемые СУБД".

Авторы выделяют четыре класса систем:
  • Key-value stores: Redis, Scalaris, Voldmort, and Riak.
  • Document stores: Couch DB, MongoDB, and SimpleDB.
  • Record stores: BigTable, HBase, HyperTable, and Cassandra.
  • Scalable RDBMSs: MySQL Cluster, ScaleDB, Drizzle, and VoltDB.

К сожалению, некоторые из этих продуктов закрытые, а некоторые - сырые и глючные, но что есть, то есть. Уж не знаю, почему из обзора выпала MemcachedDB. Думаю, статья полезна в качестве шпаргалки по особенностям вышеназванных систем.

Редактор JSON

Пакет edit-json, имя редактора edit_json. Покамест не опробовал в деле, посмотрю чуть позже.

вторник, 25 мая 2010 г.

Tcl и XMPP

Есть и сервер и клиент:
tclxmppd - Cross-platform XMPP and Jabber server written in Tcl
tclxmpp - XMPP client and component library in Tcl

А также в клиенте Cocinella можно посмотреть соответствующую библиотеку:
JabberLib

Как оно сделано в Tkabber я не знаю, но нужный код там тоже есть.

Плюс мы всегда можем вызвать внешнюю утилиту, например, ejabberdctl (необходимо запускать от рута или пользователя ejabberd) или sendxmpp (от любого пользователя, но требуется указать логин и пароль для доступа к jabber-серверу).

Memcached и Tcl

MEMCACHED FOR TCL

Требует библиотеку libmemcached, для которой в дебиане squeeze есть пакет libmemcached2 и в sid пакет libmemcached4.

Актуальные исходники брать здесь:
http://github.com/bovine/memcached-for-Tcl

Расширения для Tcl

Tcl Extensions - by Category

Модем Huawei E1550 в debian - продолжение

Приобрел соответствующую железку, подключил. Ниже рассказываю, как именно.

Вот подробное описание для работы с модемами в линуксе:
ZTE MF626, он же ONDA MT503HS
Как "приручить" МТС-модем Huawei E1550

Для билайновских модемов есть вот такое java-поделие, тормозное, но работающее:
Юзер Интерфейс под Linux

А вот такой конфиг позволит обычным способом подключиться:
/etc/wvdial.conf
[Dialer Defaults]
Phone =.
Username =.
Password =.
New PPPD = yes

[Dialer beeline]
Auto Reconnect = on
Init1 = ATZ
Init2 = AT+CGDCONT=1,"IP","home.beeline.ru"
Baud = 460800
Modem = /dev/ttyUSB0
Modem Type = USB Modem
Phone = *99#
Password = beeline
Username = beeline
Abort on Busy = on
Stupid Mode = on
У меня включен тариф "Легкий безлимит", на других тарифах точка доступа может отличаться. Все для удобства абонента ;-) Указанная в конфиге скорость подключения игнорируется, так что приведенное выше значение ни на что не влияет.

Вот что мы видим при подключении:
$ dmesg
[  221.048062] usb 1-2: new high speed USB device using ehci_hcd and address 5
[  221.192393] usb 1-2: New USB device found, idVendor=12d1, idProduct=1446
[  221.192406] usb 1-2: New USB device strings: Mfr=2, Product=1, SerialNumber=0
[  221.192416] usb 1-2: Product: HUAWEI Mobile
[  221.192423] usb 1-2: Manufacturer: HUAWEI Technology
[  221.192717] usb 1-2: configuration #1 chosen from 1 choice
[  221.289585] Initializing USB Mass Storage driver...
[  221.290352] scsi4 : SCSI emulation for USB Mass Storage devices
[  221.291282] usb-storage: device found at 5
[  221.291293] usb-storage: waiting for device to settle before scanning
[  221.291410] scsi5 : SCSI emulation for USB Mass Storage devices
[  221.291754] usbcore: registered new interface driver usb-storage
[  221.291771] USB Mass Storage support registered.
[  221.295183] usb-storage: device found at 5
[  221.295195] usb-storage: waiting for device to settle before scanning
[  226.289435] usb-storage: device scan complete
[  226.291519] scsi 4:0:0:0: CD-ROM            HUAWEI   Mass Storage     2.31 PQ: 0 ANSI: 2
[  226.294667] usb-storage: device scan complete
[  226.296953] scsi 5:0:0:0: Direct-Access     HUAWEI   MMC Storage      2.31 PQ: 0 ANSI: 2
[  226.311774] sd 5:0:0:0: [sdb] Attached SCSI removable disk
[  226.463645] sr0: scsi-1 drive
[  226.463660] Uniform CD-ROM driver Revision: 3.20
[  226.466129] sr 4:0:0:0: Attached scsi CD-ROM sr0
[  226.535211] sd 0:0:0:0: Attached scsi generic sg0 type 0
[  226.539139] sr 4:0:0:0: Attached scsi generic sg1 type 5
[  226.547436] sd 5:0:0:0: Attached scsi generic sg2 type 0
[  238.327943] usb 1-2: USB disconnect, address 5
[  244.776079] usb 1-2: new high speed USB device using ehci_hcd and address 6
[  244.919301] usb 1-2: New USB device found, idVendor=12d1, idProduct=1001
[  244.919315] usb 1-2: New USB device strings: Mfr=2, Product=1, SerialNumber=0
[  244.919325] usb 1-2: Product: HUAWEI Mobile
[  244.919332] usb 1-2: Manufacturer: HUAWEI Technology
[  244.919613] usb 1-2: configuration #1 chosen from 1 choice
[  244.926491] scsi9 : SCSI emulation for USB Mass Storage devices
[  244.926884] usb-storage: device found at 6
[  244.926894] usb-storage: waiting for device to settle before scanning
[  244.928269] scsi10 : SCSI emulation for USB Mass Storage devices
[  244.928980] usb-storage: device found at 6
[  244.928990] usb-storage: waiting for device to settle before scanning
[  245.048333] usbcore: registered new interface driver usbserial
[  245.048401] USB Serial support registered for generic
[  245.048617] usbcore: registered new interface driver usbserial_generic
[  245.048629] usbserial: USB Serial Driver core
[  245.075331] USB Serial support registered for GSM modem (1-port)
[  245.075856] option 1-2:1.0: GSM modem (1-port) converter detected
[  245.076600] usb 1-2: GSM modem (1-port) converter now attached to ttyUSB0
[  245.076664] option 1-2:1.1: GSM modem (1-port) converter detected
[  245.078926] usb 1-2: GSM modem (1-port) converter now attached to ttyUSB1
[  245.078991] option 1-2:1.2: GSM modem (1-port) converter detected
[  245.082711] usb 1-2: GSM modem (1-port) converter now attached to ttyUSB2
[  245.082840] usbcore: registered new interface driver option
[  245.082852] option: v0.7.2:USB Driver for GSM modems


$ sudo wvdial beeline
--> WvDial: Internet dialer version 1.60
--> Cannot get information for serial port.
--> Initializing modem.
--> Sending: ATZ
OK
--> Sending: AT+CGDCONT=1,"IP","home.beeline.ru"
AT+CGDCONT=1,"IP","home.beeline.ru"
OK
--> Modem initialized.
--> Sending: ATDT*99#
--> Waiting for carrier.
ATDT*99#
CONNECT
--> Carrier detected.  Starting PPP immediately.
--> Starting pppd at Tue May 25 01:10:15 2010
--> Pid of pppd: 22255
--> Using interface ppp0
--> pppd: [10]
--> pppd: [10]
--> pppd: [10]
--> pppd: [10]
--> pppd: [10]
--> pppd: [10]
--> local  IP address 172.19.119.211
--> pppd: [10]
--> remote IP address 10.64.64.64
--> pppd: [10]
--> primary   DNS address 217.118.66.243
--> pppd: [10]
--> secondary DNS address 217.118.66.244
--> pppd: [10]

И пару слов об ужастиках с форумов. Миф первый - об ужасах переключения режимов - у меня все ок, воткнул и работает. При этом установлен пакет usb-modeswitch, который и отвечает за работу устройства в режиме модема, причем одновременно доступен встроенный псевдо-CD-ROM и картридер. Миф второй - о дисконнектах каждые три минуты - таковых не наблюдаю, связь устойчивая 3G.

И "на закуску" мои впечатления. Попробовал ради интереса скачать большой файл - средняя скорость около 200 килобайт в секунду (меняется от 100 до 250 килобайт), файл на 45 мегабайт скачивается за 4 минуты. Время полвторого ночи, возможно, днем ситуация изменится, посмотрим. Модем светит ровным синим светом, что по инструкции означает устойчивую 3G связь - охотно в это верю, глядя на скорость соединения.

traceroute ничего полезного не показывает, а вот пинг до московского сервера весьма стабилен:
$ ping mobigroup.ru
PING mobigroup.ru (213.148.6.78) 56(84) bytes of data.
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=1 ttl=52 time=99.5 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=2 ttl=52 time=99.9 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=3 ttl=52 time=99.9 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=4 ttl=52 time=100 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=5 ttl=52 time=100 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=6 ttl=52 time=100 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=7 ttl=52 time=99.9 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=8 ttl=52 time=99.9 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=9 ttl=52 time=99.8 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=10 ttl=52 time=99.9 ms

$ ping mobigroup.ru
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=1 ttl=52 time=99.4 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=2 ttl=52 time=97.8 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=3 ttl=52 time=104 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=4 ttl=52 time=108 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=5 ttl=52 time=95.1 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=6 ttl=52 time=93.9 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=7 ttl=52 time=102 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=8 ttl=52 time=92.0 ms
Опять же, интересно будет сравнить с результатами в дневное время.

Upd.
А теперь смотрим днем, в 12:40:
$ ping mobigroup.ru
PING mobigroup.ru (213.148.6.78) 56(84) bytes of data.
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=1 ttl=52 time=111 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=2 ttl=52 time=111 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=3 ttl=52 time=118 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=4 ttl=52 time=115 m
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=5 ttl=52 time=135 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=6 ttl=52 time=126 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=7 ttl=52 time=125 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=8 ttl=52 time=124 ms

$ ping mobigroup.ru
PING mobigroup.ru (213.148.6.78) 56(84) bytes of data.
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=1 ttl=52 time=179 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=2 ttl=52 time=157 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=3 ttl=52 time=157 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=4 ttl=52 time=156 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=5 ttl=52 time=155 ms
64 bytes from mobigroup.ru (213.148.6.78): icmp_req=6 ttl=52 time=162 ms
В консоли теперь заметна задержка, но работать можно.

Скорость закачки того же большого файла в интервале от 40-ка до 70-ти килобайт в секунду. Что ж, вполне прилично.

понедельник, 24 мая 2010 г.

Об архитектуре Reddit

В предыдущей заметке Супервизоры для систем высокой доступности я уже давал ссылку на статью
7 LESSONS LEARNED WHILE BUILDING REDDIT TO 270 MILLION PAGE VIEWS A MONTH

А сейчас хочу провести небольшой анализ обсуждаемой архитектуры. В статье приводится 7 правил - рассмотрим их все.

LESSON ONE: CRASH OFTEN
The essence of this lesson is: automatically restart failed and cancerous services.

Согласен полностью.

LESSON 2: SEPARATION OF SERVICES
The essence of this lesson is: group like processes and data on different boxes.

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

LESSON 3: OPEN SCHEMA
The essence of this lesson is: don't worry about the schema.

Рекомендация верная, но перечисленные ограничения слишком надуманные. Для таблиц вида "thing id, key, value" прекрасно работают join-ы в форме "join mytable on mytable.thing_id=thing_id and mytable.key=key". Соответственно, одну такую таблицу подключаем столько раз, сколько разных key нас интересуют. Вероятно, для автора просто не нужны join-ы в силу решаемой им задачи, но вовсе незачем было придумывать лишнее ограничение.

LESSON 4: KEEP IT STATELESS
С первого взгляда не очевидно, но правило избыточно. Добавлю, что, когда приложение это позволяет, нам повезло и многих проблем просто не возникнет, но что делать в противном случае?.. Для ясности стоит ввести понятия макро- и микросостояний. Макросостояние - когда ход обработки запроса определяется состоянием всей системы, а микро - только текущего узла. Первое требует глобальной синхронизации, зато второе совершенно автономно. Потеря микросостояния приведет, максимум, к разрыву сессии пользователя. Частным случаем сервисов с микросостоянием являются сервисы без собственного состояния. Если мы придерживаемся совета LESSON 2: SEPARATION OF SERVICES, то автоматически выполняется и текущая рекомендация. А если мы реализуем сохранение состояния при перезапуске сервиса согласно идее LESSON ONE: CRASH OFTEN, то никто ничего и не заметит. Потому уточним рекомендацию так: замените все макросостояния на микро.

LESSON 5: MEMCACHE
The essence of this lesson is: memcache everything.

Совершенно неверно, автор ставит телегу впереди лошади. Не нужно серверу приложений работать с кэшем, его задача - возвращать вычисляемые актуальные данные, устанавливая заголовки, контролирующие кэширование, такие, как expires, cache-control, etc. Нужен кэш - поставим кэширующий прокси перед фронтэндом или отдельным бэкэндом. В предлагаемом мной варианте значительную часть работы возьмут на себя прокси-серверы провайдеров, т.к. они тоже смогут эффективно кэшировать контент, таким образом мы совершенно бесплатно получаем CDN - распределенную сеть раздачи нашего контента.

LESSON 6: STORE REDUNDANT DATA
The essence of this lesson is: the key to speed is to precompute everything and cache it.

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

LESSON 7: WORK OFFLINE
The essence of this lesson is: do the minimal amount of work on the backend and tell the user you are done.

Технически противоречит пункту LESSON 4: KEEP IT STATELESS. Действительно, если операция производится независимо от запроса пользователя, необходим планировщик для выполнения задач плюс, опционально, поддержка сессий пользователя для оповещения о результатах. В случае, когда требуется оповестить конкретного пользователя, появляется необходимость хранения состояния задачи на сервере. И только в том случае, когда безопасностью пренебрегают, можно сразу же предоставить пользователю адрес для получения сведений о состоянии задачи по ее идентификатору - но тогда любой пользователь сможет увидеть это состояние. Несомненно, в каких-то системах такой подход допустим, но это лишь частный случай и притом с весьма серьезными противопоказаниями к применению.

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

Супервизоры для систем высокой доступности

7 LESSONS LEARNED WHILE BUILDING REDDIT TO 270 MILLION PAGE VIEWS A MONTH

LESSON ONE: CRASH OFTEN
The essence of this lesson is: automatically restart failed and cancerous services.

The downside of running your own system in a colo is that you are on the hook for maintenance. When your service dies you have to fix it now, even at 2AM. This is a constant tension in your life. You have to take a computer with you everywhere and you know that anytime anyone calls it could be another disaster you have to fix. It ruins your life.

One way to mitigate this problem is restart process that have died or become cancerous. Reddit uses Supervise to automatically restart applications. Special monitoring programs kill processes that use too much memory, use too much CPU, or aren’t responsive. Instead of worrying just restart and the system is up. Of course you have to read the logs and find a root cause, but until then it keeps you sane.

Так оно и есть, причем сейчас есть более продвинутая реализация супервизора - runit. Кстати, в аппаратных реализациях mission-critical систем использование микросхемы-супервизора является обязательным. Но электротехнику, электронику и прочие дисциплины современные "программисты" не изучают...

суббота, 22 мая 2010 г.

Репликация пользователей из основной БД в ejabberd

Для обеспечения высокой доступности при минимальной нагрузке оптимальным решением является синхронизация учетных записей пользователей из основной БД в базу ejabberd (mnesia). Ранее я приводил пример авторизации внешним скриптом, но для более-менее серьезного применения это создает лишнюю нагрузку на основную БД, плюс снижает доступность.

См. ejabberdctl should be able to dump to stdout.

Дамп пользователей
sudo ejabberdctl dump_table /tmp/dump passwd

Парсер дампа
#!/usr/bin/tclsh8.5

proc K {arg args} {set arg}
proc << name {K [read [set f [open $name]]] [close $f]}

array set jusers {}
set lines [split [<< /tmp/dump] \n]
foreach line [lrange $lines 1 end] {
    set user [string map {, " " \{ "" \} ""} [string range $line 0 end-1]]
    set name [lindex $user 1]
    set host [lindex $user 2]
    set pass [lindex $user 3]
    lappend jusers($host) $name $pass
}

puts $jusers(kuban)
В результате для каждого виртуального хоста получаем список формата {nickname password}, далее сравниваем с аналогичной записью в основной БД и для отличающихся обновляем через ejabberdctl или любым удобным нам способом. Скрипт разумно запускать от имени пользователя ejabberd, подкладывая ему в заранее определенное место дамп пользователей из основной базы или настроив доступ на чтение к основной базе. Дамп 3000 пользователей из ejabberd занимает около 0,25 с, обновление учетной записи одного пользователя около 0,1 с. Так что репликацию можно выполнять хоть раз в минуту, ничуть не нагружая при этом сервер. На мой взгляд, разумно ограничиться интервалом 5 минут.

воскресенье, 16 мая 2010 г.

Очереди и деревья в SQLite

Приведу пару ссылок на сообщения рассылки:
Managing trees in the database
Persistent queue implementation based on SQLite

Книга "Dive Into HTML5"

Книга живет по адресу diveintohtml5.org а называется она
Dive Into HTML5
BY
MARK PILGRIM
WITH ILLUSTRATIONS FROM THE PUBLIC DOMAIN

суббота, 15 мая 2010 г.

Переход на squeeze

Перехожу на новый дебиан, это позволит уменьшить число необходимых бэкпортов и получить новые версии некоторых пакетов, которые я не бэкпортировал, но хотел бы обновить. Итак, нижеперечисленные пакеты моего репозитория могут быть упразднены (перечислены только критичные для работы системы пакеты, на самом деле стали неактуальными и версии многих других):
haproxy stunnel fossil tcl8.4 tcl8.5 tcl8.6 unzip zip
Внес следующие изменения. В некоторых конфигах присутствовали директивы, неподдерживаемые новыми версиями пакетов, их закомментировал.
haproxy
#capture cookie session= len 64
Заметим, что это изменения апстрим-версии. Также при запуске выдаются некоторые предупреждения, их посмотрим позже.

stunnel4
#xforwardedfor = yes
Эта директива добавлена использованным мною патчем (см. на оффсайте haproxy) и в апстриме не поддерживается.

В пакетах tcl8.5/tcl8.6 основное изменение в моей сборке связано с директивой KILL_OCTAL. Пока под вопросом, влияет ли эта директива на текущий код (ранее были обнаружены проблемы в биллинге, если эта директива не активирована).

Пакеты unzip и zip были просто бэкпортами, их необходимость вызвана тем, что старые версии некорректно работали с файлами формата OpenDocument (в которых использованы некоторые достаточно новые опции zip-сжатия). Так что замена этих двух пакетов произошла совершенно прозрачно.

Пакет fossil теперь есть в апстриме, свою сборку держать более нет нужды.

Обновление еще трех сотен стандартных пакетов функционал нашей системы не затрагивает.

Upd. Установка grub2 прошла успешно. Что интересно, при установке ядра из тестинга не было предложено заменить имена устройств на их идентификаторы! Отмечу, что после обновления немного снизилась загруженность системы, полагаю, за счет новых stunnel и haproxy.

Прицел на eJabberd

Поговорим о выборе системы сообщений для пользователей некоего проекта или компании. Притом исходим из следующих требований:

  • пользовательский интерфейс системы сообщений должен встраиваться в интернет/интранет портал, работающий на строго фиксированном ip с доступом исключительно по https. Почему такое ограничение, обсуждать особенно ни к чему, поскольку это бизнес-требование, добавлю лишь, что для некоторых пользователей есть и техническое ограничение - например, скайлинк на тарифных планах для корпоративщиков (по крайней мере, на некоторых) предоставляет лишь доступ по https.
  • собственная авторизация (любым способом, без ограничений), так что зарегистрированные в основной БД пользователи имеют доступ к системе сообщений (притом из соображений безопасности в основной БД хранится лишь хэш пароля)
  • всеобъемлющий API для интеграции с системой сообщений - в частности, для подключения AJAX-интерфейса пользователя (никаких web sockets, см. первое требование) и логирования всех сообщений (логирование может быть реализовано от варианта с ведением простого лог-файла до записи в БД с онлайн-индексированием, рассылкой уведомлений по email и созданием отчетов и проч.)

В настоящий момент, все перечисленные условия (и не только) могут быть реализованы с помощью XMPP-протокола. HTTP доступ предоставляют все современные jabber-сервера с помощью технологии HTTP binding (примечание: нужно пробросить директорию /http-bind/ портала на порт jabber-сервера). Среди некоторого многообразия jabber-серверов есть и один весьма неплохой - ejabberd. Почему именно он, история долгая, но на этом варианте я остановился два года назад и с тех пор не было причин изменить свое мнение (тестировал его, мелкие баги были, но сейчас они уже поправлены). В силу того, что в дебиане сейчас присутствует работоспособная версия ejabberd, пришло время запускать его в продакшен (мне хватает забот со своим деб-репозиторием и мантейнить лишний пакет без необходимости не стану).

Как всегда, быстрее всего ознакомиться с возможностями выбранного продукта можно, посмотрев существующие расширения к нему: Contributions
И, конечно, смотрим Official ejabberd documentation А также страницу вики Делаем Jabber-сервер

Что касается логирования, меня больше интересует отдельная система, нежели встроенный модуль. Например, это Bandersnatch:
Guide to Install Bandersnatch
Install Bandersnatch to log Messages
или фронт-энд Jorge (мне плевать, что оно на пхп, поскольку рассматриваю перечисленные варианты как образцы для написания своего решения).
Впрочем, есть и встраиваемые модули для логирования:
mod_logdb - Log User Messages to DB
Сохранение логов muc-конференций в виде html

Теперь поговорим об AJAX-интерфейсе. как один из вариантов интерфейса (есть в дебиане): A web based Jabber/XMPP client Умеет многое, смотрим здесь в подробностях: Features
Немного об установке интерфейса с джаббер-сервером:
EJabberd + JWChat + Apache2
А тут рассказано, как можно потыкать пальцем протокол для разной мелкой автоматизации: Джаббер чат на веб-странице

И немного о публичных сервисах, для ознакомления:
http://www.olark.com/portal/?rid=hab.la
http://www.meebo.com/
Create a Google Talk chatback badge

Upd.
За пример работающего конфига и некоторые подсказки спасибо Anton Kovalenko.

Пользователь admin существует сразу после установки, надо лишь назначить ему нужный пароль. Например, вот так:
sudo ejabberdctl change_password admin хост пароль
Теперь от имени пользователя admin с указанным паролем заходим в веб-интерфейс http://хост:5280/admin/ Хм, почему же здесь доступ по открытому HTTP? Непорядок, правим в конфиге
-web_admin
+web_admin, tls, {certfile, "/etc/ejabberd/ejabberd.pem"}
Далее создаем свой сертификат - установленный по умолчанию мне не интересен, лучше свой выписать, или получить здесь ICA XMPP Foundation (бесплатно). После перезапуска еджаббера входим уже по защищенному соединению https://хост:5280/admin/

В веб-интерфейсе выбираем наш виртуальный хост, и для него можно создавать shared rosters ("группы общих контактов"), управлять пользователями и прочее. Впрочем, вот именно "управление" вызывает разумные сомнения, так что оставим только возможность просмотра настроек - пусть значения из конфиг-файла перезаписывают все изменения, вносимые через веб-интерфейс:
override_global.
override_local.
override_acls.

Что касается авторизации, то все достаточно просто, см. 4 Authentication
Примеры скриптов вполне наглядны, вот один из них: Postgresql external auth script (Perl)

Вот такой тиклевый скрипт умеет авторизовывать пользователей:
ejabberd_auth
#!/usr/bin/tclsh8.5
# https://git.process-one.net/ejabberd/mainline/blobs/raw/2.1.x/doc/dev.html#htoc8

fconfigure stdin -translation binary -buffering none
fconfigure stdout -translation binary -buffering none

# the result code (coded as a short), should be 1 for success/valid, or 0 for failure/invalid
proc auth_ok {} {
    puts -nonewline [binary format Su2 {2 1}]
}
proc auth_no {} {
    puts -nonewline [binary format Su2 {2 0}]
}

# check user or user/password
proc auth {server user {password {}}} {
    auth_ok
}

while {1} {
    if {[binary scan [read stdin 2] Su len] == 1} {
        lassign [split [read stdin $len] :] type user server password
        exec logger "ejabberd external auth script: $type $user $server $password"
        switch $type {
            auth {
                # check if a username/password pair is correct
                auth $server $user $password
            }
            isuser {
                # check if it’s a valid user
                auth $server $user
            }
            default {
                auth_no
            }
        }
    }
    if {[eof stdin]} {
 break
    }
}
На самом деле скрипт у меня обращается к БД для проверки авторизации, здесь же я упростил для наглядности.
Для основного домена "домен" я желаю встроенную авторизацию, а для всех прочих - внешним скриптом. В конфиге это будет записано так:
%%%   ==============
%%%   AUTHENTICATION

%%
%% Authentication using external script
%% Make sure the script is executable by ejabberd.
%%
{auth_method, external}.
{host_config, "домен", [{auth_method, internal}]}.

{extauth_program, "/usr/local/bin/ejabberd_auth"}.
Проверил, указанная конфигурация успешно работает. С той оговоркой, что скрипты в цикле поедают ресурсы и при перезапуске ejabberd продолжают работать (при каждом старте ejabberd запускается еще 4 скрипта). Проблемы решаемые, но ничего хорошего в этом нет.

Раз у нас есть авторизация, самое время поиграться с ростерами. Пробуем:
sudo ejabberdctl srg_create groupadm хост1 "Администраторы" "Группа администраторов" groupadm\\ngroupall
sudo ejabberdctl srg_create groupall хост1 "МР ЮГ" "Все пользователи МР ЮГ" "groupadm"

sudo ejabberdctl srg_user_add admin хост groupadm хост1
sudo ejabberdctl srg_user_add veter хост1 groupadm хост1
sudo ejabberdctl srg_user_add @all@ хост groupall хост1

sudo ejabberdctl srg_user_del test хост groupall хост1

sudo ejabberdctl srg_delete groupadm хост1
Здесь в команде srg_create последний аргумент содержит список всех групп, которые будут видеть участники создаваемой группы; обычно туда включают и саму создаваемую группу, чтобы ее участники видели друг друга. В документации этот момент разъясняется как-то странно, сразу и не понять, что саму группу туда тоже нужно включить:
Displayed groups
A list of groups that will be in the rosters of this group’s members.

Конструкция @all@ для внешней авторизации работать не будет, т.к. ejabberd не может предоставить список всех зарегистрированных пользователей. И тем не менее, использование @all@ не приводит к каким-либо ошибкам, @all@ просто игнорируется.

Рассматриваемый подход имеет как свои плюсы, так и минусы. Хорошо то, что информация о пользователях может забираться из внешнего источника, а плохо то, что нужен постоянный доступ к этому источнику, фиксированный его формат, высокая скорость отклика и минимальные накладные расходы даже при множестве обращений. Еще вспомним про невозможность поиска по стандартным атрибутам - конечно, свой коннектор позволит искать в ldap и других внешних хранилищах, но создаст заведомо узкое место в системе. Так что теперь посмотрим, как может быть выполнена репликация нашей БД с пользователями и пользовательской информации в ejabberd. Что ж, я исхожу из принципа "мухи отдельно, котлеты отдельно", а вариант с доступом к внешнему хранилищу уже подробно рассмотрен выше. Вот что касается вариантов онлайн-синхронизации пользовательской информации:
ejabberdctl that may be useful for you, like push_roster_all or srg_user_add

This way you can implement the logic of adding/deleting contacts in your
prefered language, and then call the script ejabberdctl to perform the changes.

If you later notice that calling a shell script for each change is too
slow for you,
you can install also ejabberd_xmlrpc, which provides an XML-RPC server where
you can send your command queries (instead of the shell script ejabberdctl).

Есть и сторонние сервисы/утилиты для переноса контактов, в том числе между разными серверами. Например, Jabber roster utility или ее клон Утилита для Jabber Ростера на PHP

Продолжаем установкой пакета jwchat (копируем или симлинкаем к себе файлы *.ru, создаем конфиг, правим симлинк на XMPP javascript-библиотеку). Выясняется, что при включенном SSL для веб-интерфейса не работает http-bind, так что пока отключаем SSL (возвращаем к дефолтовым настройкам секцию конфига). Плюс в разделе модулей вписываем в конфиг модуль {mod_http_bind, []} без которого http binding не работает (понятно, почему не работает, но непонятно, с чего бы это в дефолтовом конфиге его нет, раз уж предлагается http binding). Теперь
links http://localhost:5280/http-bind/
работает и показывает страничку с информацией. Идем в jwchart -все верно, получаем желанный AJAX-интерфейс. Для корпоративного портала стоит предусмотреть подстановку имени и пароля пользователя, дабы не напрягать последнего лишними деталями.

Довольно интересной для пользователей возможностью является icq-гейт. Что ж, попробуем:
sudo aptitude install -R pyicq
sudo mcedit /etc/pyicqt.conf.xml
sudo mkdir /var/lib/pyicqt/JabberID/
sudo chown pyicqt:pyicqt /var/lib/pyicqt/JabberID/
sudo /etc/init.d/pyicqt restart
Здесь JabberID это icq.что-то-там согласно определенному в конфиге.
Гейт прикручивается к указанному виртуальному домену, хотя при желании его можно использовать для любого домена, если знать адрес гейта или адрес его домена. В общем, работает. Настроить клиента Миранда можно вот так: Подключение ICQ в Miranda - только учтем, что в новых версиях это меню располагается слева внизу главного окна Миранды (впрочем, там вариантов немного, так что я быстро нашел).

Теперь на AJAX-программирование подробнее посмотрим. jwchat работает "из коробки" (см. выше, как его ставить). Отображаемые поля vCard меня не устроили, например, нет номера телефона, но это решается добавлением нужной строчки в соответствующий html-файл (яваскрипт видит все значения в карточке, а заполняет, что логично, только существующие на странице поля, так что настройка отображения vCard заключается в добавлении строчек в html-таблицу).

Расширение Strophe: Programming with XMPP and JavaScript
jQuery and Strophe: Made for Each Other
jquery-bosh
XMPP LIBRARY
Professional XMPP Programming with JavaScript and jQuery Jan 2010 eBook-BBL

Upd.
А вот и способ автоматизировать отправку сообщений из консоли:
sudo aptitude install sendxmpp
echo "admin@myhost password" > ~/.sendxmpprc
chmod a-rw,u+rw ~/.sendxmpprc
# сообщение пользователю
echo "автоматическая рассылка"|sendxmpp veter@nnov
# рассылка всем онлайн-пользователям
echo "автоматическая рассылка"|sendxmpp nnov/announce/online
# сообщение в чат от несуществующего пользователя robot
echo -e "сообщение\n"|sendxmpp  -r robot --chatroom talks@conference.nnov
# просто строка с форматированием
echo "строка"|sendxmpp conference.nnov
Отправка сообщения занимает порядка 1 с времени, этого во многих случаях вполне достаточно; заметим, что отправка через ejabberdctl ничуть не быстрее.

вторник, 11 мая 2010 г.

Вечный ИЕ6

В целях проверки пользователей на вшивость, то есть на использование ИЕ6/7, я хотел сказать, погрепал логи. Кстати вспомнилась замечательная статья о том, откуда такая путаница в идентификации браузеров: История строки User-Agent в браузерах

Итак, результаты. В МР Поволжье ИЕ6/7 практически не используют - меньше 10% запросов. Зато в МР Юг почти 50% запросов именно от указанных браузеров! Результат жуткий, что-то в духе восстания зомби - учитывая, что сама Микрософт уже старатся эти артефакты закопать побыстрее, а они упорно сопротивляются. Отмечу, что это результаты по работе нашей веб-системы поддержки дилеров сотовых операторов.

понедельник, 10 мая 2010 г.

Обновление js-библиотек пакета mbgserver-jquery

Деб-пакет mbgserver-jquery содержит базовые javascript библиотеки, используемые в наших проектах. Многие из них поправлены мною для исправления багов или получения желаемой функциональности. В целях унификации и упрощения поддержки периодически проверяю состояние апстрима, и по возможности заменяю правленную версию на оригинальную. И вот наконец-то удалось собрать набор библиотек, вовсе не требующих правки! jgrowl и ajaxupload доведены до ума в апстриме, причем все мои патчи независимо реализованы разработчиками :-) Конечно, не мешало бы им патчи эти отправить своевременно, но я разработку на javascript не люблю и занимаюсь этим лишь по необходимости. Расширение autocomplete заменяем на аналог из JQuery UI, появившийся в последнем релизе. Замечу, что указанный аналог имеет глюк с позиционированием списка вариантов при изменении масштаба страницы (а именно, замечено при нажатии Ctrl + в браузере google chrome). Расширения thickbox и cluetip заменит стандартный dialog из JQuery UI, сделать для него обертку с поддержкой AJAX тривиально. А вот treeview просто выкидываем, поскольку такой интерфейс неудобен, использовался у нас вынужденно в одном месте, и уже удалось придумать, чем его заменить: Прототип интерфейса рассылки сообщений для ПО оффлайн-сервис

Новые версии библиотек помещены в поддиректорию js пакета, а соответствующие стили - в поддиректорию css. В настоящий момент также включено 8 стилей оформления для JQuery UI, выбранные мною по своему вкусу; картинки внедрены непосредственно в файлы css.

Ниже привожу список патчей, которые были использованы в предыдущей версии библиотек. Эта информация предназначена исключительно в роли подсказки при обновлении библиотек в существующих проектах.
jquery.autocomplete.js - добавлен обработчик для onItemSelect

jquery.jgrowl.js - подправлено для отображения _и_ первого сообщения 
в виде плавающего меню
    if($('div.jGrowl-notification:parent',this.element).size()>2
    заменено на
    if($('div.jGrowl-notification:parent',this.element).size()>1

thickbox.js - добавлен аргумент onLoadFunction к функции tb_show 
(необходим для отображения datapicker во всплывающем окне)
    заменил
        <a href='#' id='TB_closeWindowButton'>close</a> or Esc Key</div>
    на
        <a href='#' id='TB_closeWindowButton'>[×]</a></div>
    указал путь к картинке
        var tb_pathToImage="/img/jquery/thickbox/loadingAnimation.gif";

jquery.autocomplete.min.js - переименован служебный параметр limit 
в ajax_limit_autocomplete: limit:options.max на ajax_limit_autocomplete:options.max

jquery.autocomplete.min.js - добавлен второй параметр onItemSelectParam в функцию onItemSelect

jquery.autocomplete.min.js - для указания кол-ва элементов в выпадающем списке 
поправить константу в коде проверки значения параметра options.scroll
    options.scroll?10 заменено на options.scroll?20

#jquery.form.min.js - заменено el.disabled на el.disabled1 - чтобы значения 
# задизэбленных элементов тоже передавались на сервер
#jquery.form.min.js - для невыбранных чекбоксов теперь возвращает пустые значения
#   (t == 'checkbox' || t == 'radio') && !el.checked => (t == 'radio') && !el.checked
#
# // fixed - hidden inputs must be cleaned too!
#    if (t == 'text' || t == 'password' || tag == 'textarea' || t == 'hidden')

jquery.treeview - добавлена поддержка json-атрибута onclick

3.ajaxupload.3.1.js - поправлена обработка JSON-ответа сервера. Проверено в Firefox и Opera.

Upd. Баги все же обнаружились - в JQuery UI аж в 4-х местах написана потрясающая конструкция вида "+ ++" вместо "+ (++ )". Обновил пакет, тестируем дальше.

Upd. В ajaxupload не поддерживаются Firefox 1.x/2.x. Мой патч эту проблему решает, но таковых пользователей единицы, рекомендуем им обновить браузер, нежели поддерживать совместимость с указанными ископаемыми браузерами.

воскресенье, 9 мая 2010 г.

Используем data:uri

Берем последний JQuery + JQuery UI с официального сайта, причем отказываемся от загрузки эффектов UI и соглашаемся с выбором темы оформления по умолчанию ui-lightness. Далее пакуем все это в deb-пакет mbgserver-jquery, сразу же заменяя ссылки на картинки в css-файле на их base64 представление согласно формату data:uri. Написанный на скорую руку скрипт делает то, что нам нужно, хотя его не стоит рекомендовать как образец программирования на тикле:
#!/usr/bin/tclsh8.5

proc K {arg args} {set arg}
proc >> {name data} {set f [open $name w]; puts -nonewline $f $data; close $f}
proc << name {K [read [set f [open $name]]] [close $f]}

set css [exec sh -c "cat *css"]
foreach file [exec ls images/] {
    set file images/$file
#    puts $file
    set image [string map {\r {} \n {}} [exec base64 $file]]
    set mime [exec file --brief --mime-type $file]
    set css [regsub -all -- $file $css "'data:$mime;base64,$image'"]
}
#puts $css
set dir [exec pwd]
set dir [exec basename $dir]
>> ../${dir}.css $css
Замечу, что в css недопустимо многострочное представление base64-строк. Указанный скрипт также наличествует в собранном пакете mbgserver-jquery.

Теперь делаем симлинки скриптов и стилей JQuery в рабочие директории, добавляем свои, пакуем их все вместе, получая файлы autoload.js и autoload.css и можно пользоваться.

Проверим на виджете Dialog. О нем много понаписано, например, jQuery UI – виджет Dialog Отлично, открывается окно диалога с фоновой картинкой заголовка и кнопкой закрытия в правом верхнем углу. Подробности смотрим в файрбаге или гугл девелопер тулс, в зависимости от нашего браузера.

Вот экзерсис в тему: Кроссбраузерный data URI в CSS-файлах. Имхо это зло, делать подобные вещи в продакшене сейчас, когда ИЕ6 сама микрософт жаждет похоронить, но ежели сильно приспичит, то как один из вариантов может пригодиться.

Навигация в AJAX-приложениях

Всем хороши AJAX-приложения, особенно когда в них работает навигация с помощью функций перехода по истории браузера и можно обновить текущую страницу. Во всех уважающих себя браузерах это реализуется, а печально известные ИЕ6 и ИЕ7, как всегда, отличились. Ну, что с ними делать - жить они еще будут на просторах рунета лет 10, не меньше. Как пел великий бард: "Остается одно - только лечь помереть". Точно такая же ситуация с data:uri и многими другими возможностями современного веба. Для корпоративных приложений, коими я и занимаюсь, поддержка ИЕ6 критична, что не радует. С другой стороны, если отказаться от попыток предоставить все возможности веб-приложений в ИЕ6, можно многое улучшить для пользователей современных браузеров. Чем же мы можем пренебречь в поддержке ИЕ6? Это выпадающие меню, которые можно заменить на страницы со списком пунктов (уже давно так делаем), это картинки интерфейса, которые просто не отобразятся (например, картинки пунктов меню - начинаем так делать), и еще можно отказаться от предоставления истории навигации (видимо, так в ближайшее время начнем делать). Взамен для "полноценных" браузеров мы получаем в разы меньшее количество запросов к серверу и, соответственно, ускорение загрузки страниц, а более интенсивное использование AJAX экономит трафик и ускоряет работу, притом повышая удобство использования веб-приложений. Если учесть, что корпоративные системы зачастую обязаны работать на единственном хосте и не имеют возможности вынести картинки/стили/скрипты на отдельные домены для ускорения загрузки и во избежание пересылки кукисов, то перечисленные выше факторы становятся еще весомее. Что же касается специальных хаков для ИЕ, то практика показала, что с ними и так огромные проблемы производительности ИЕ становятся всепожирающими - для пользователей, кто в веб-приложении работает весь день, утечки памяти ИЕ и так заставляют регулярно перезапускать браузер, а то и перезагружаться, и дополнительный код для обхода багов добавляет еще больше проблем. ТАк что ИЕ6/7 прибить бы надо поскорее, но раз не можем, остается поддерживать для них лишь минимально необходимый функционал.

Что же касается ИЕ6 и систем, которые только с ним и работают... видимо, в особо тяжелых случаях придется писать интерфейсы-врепперы для доступа к ним, скрывающие от пользователей всю убогость означенных систем за современным интерфейсом.

Что касается технической реализации AJAX-навигации, то об этом вполне исчерпывающе расскажут следующие ссылки:
Location
Шаблоны AJAX в ASP.NET. Страница 5. Шаблон уникального URL-адреса
jQuery hashchange event
элементы навигации AJAX в ИЕ8
Якорная навигация на jQuery (graceful degradation)
Динамически подгружаемый контент в Joomla

Существует и JQuery-плугин Address: jQuery Address - Deep linking for the masses, но на мой вкус это слишком утяжеленное решение и необходимости в нем нет.

Upd.
Также небесполезными для меня оказались следующие материалы:
jQuery FAQ – часто задаваемые вопросы по jQuery.
Выносим CSS в пост-загрузку
Выделение строк в многостраничных списках на веб

Здесь есть интересные ссылки: Эта неделя в jQuery, том 6
Вполне изящной выглядит идея создания навигации с помощью селекторов JQuery - решение столь же легкое, сколь и функциональное: jQuery ListNav Plugin
Очередной обзор "лучших", как всегда, самое интересное в комментариях: 10 Best jQuery Plugins for working with Tables Достаточно интересным выглядит вот этот проект DataTables

И чуть не забыл самую важную ссылку: JQuery documentation
Или вот: РУКОВОДСТВО JQUERY

Upd.
jQuery hashchange event

суббота, 8 мая 2010 г.

SQLite БД и утилиты КЛАДР

Как ни странно, недавно на одном из форумов снова увидел вопросы по использованию КЛАДР. Вместо того, чтобы разобраться с тем, что из себя представляет КЛАДР, его сначала пытаются как-нибудь преобразовать во что-нибудь, а потом с этим чем-нибудь работать. Делать этого не следует, а чтобы понять, почему - загляните в описание формата КЛАДР.

О том, как работать с исходной структурой КЛАДР, сохранив ее для удобства в БД SQLite, я уже писал:
Использование адресного справочника КЛАДР в Tcl
Преобразование справочника КЛАДР в формат SQLite
Утилиты КЛАДР

Сейчас я просто перекладываю деб-пакеты (исходного кода и бинарные) с БД и утилитами, описанные в перечисленных статьях, в открытый репозиторий. Скомпилировать утилиты под любую ОС не представляет сложностей, а под убунту можно и просто поставить бинарные деб-пакеты.

SQLite наносит ответный удар: Write-Ahead Logging в SQLite 3.7.0

Недавно Oracle вознамерился откусить часть чужого пирога в нише встраиваемых СУБД, предлагая пользователям SQLite прозрачный переход на новую версию СУБД BerkeleyDB с встроенной поддержкой программного интерфейса SQLite. Еще бы им не зариться - SQLite используется от смартфонов Android, iPhone и других до ОС Solaris, Linux и прочих. Притом для обеспечения совместимости Oracle использовал код самого SQLite, который распространяется под лицензией Public Domain и доступен для коммерческого использования. Но скорость реакции апстрима SQLite заслуживает всяческих похвал и в ответ на анонс компании Oracle команда разработчиков SQLite предоставляет новый режим работы СУБД SQLite без блокировки при чтении и записи! Таким образом, существующие пользователи SQLite не получат обещанного выигрыша от предлагаемого им Oracle перехода на BerkeleyDB просто потому, что в новой версии SQLite будет почти аналогичная конкурентность и множество других возможностей, которые Oracle еще не успела скопировать.

Пожалуй, это самая долгожданная возможность, необходимость которой очевидна и даже варианты ее архитектуры уже давно обсуждались в рассылке SQLite. Подробности же текущей реализации см. здесь: Write-Ahead Logging Обратите внимание, что эта функция еще в разработке и документация также будет дополняться и даже в чем-то может измениться. Что касается текущего состояния WAL, то под линуксом уже работает, сейчас идет разработка версии для виндоус.

Что же означает новость для разработчиков? Как наиболее очевидное следствие - упростится разработка и поддержка проектов на SQLite. Кроме того, окажется возможной онлайн-репликация БД - например, с помощью моей утилиты sqlite3-rdiff. Также следует ожидать значительного увеличения скорости работы БД как на обычных HDD, так и на SSD-дисках - последнее особенно важно в связи с уверенным продвижением SSD во всех сегментах от портативной электроники до дата-центров. Плюс к тому, окончательно будет развеян миф о том, что для построения больших информационных систем нужны большие СУБД! :-)

Upd. Стала известна предполагаемая дата релиза SQLite 3.7.0 - 1 июля 2010.

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

Отказоустойчивость и балансировка нагрузки в кластере

Вот уже с неделю размышляю над дальнейшей стратегией построения веб-систем. А также, как водится, перелопачиваю документацию. Наконец-то нашлось время сформулировать многое из того, о чем раньше некогда было всерьез подумать. В том числе, нашел проект и одноименный деб-пакет keepalived. Почему именно этот проект, хотя есть намного более известные аналоги (heartbeat)? Все просто - именно keepalived рекомендует автор HAProxy, предлагая в документации варианты совместного использования и примеры конфигурации. Сам я давно и успешно использую HAProxy и с его автором общался, неизменно получая точные и ясные ответы на вопросы продакшен конфигурации и защиты от сбоев и атак различных видов. Это была первая причина. А вторая - на мой взгляд, конфигурационные параметры heartbeat придуманы точно не гуманоидами, вот пусть эти головоногие (в лучшем случае) ими и пользуются. Третья причина - костыли в heartbeat для предотвращения одновременного использования разделяемых дисков несколькими узлами - при пропадании связи между узлами данные на таких дисках все равно будут повреждены (каждый узел будет считать себя мастером и работать с дисками), так стоило ли городить огород, когда вместо реализации балансировки между узлами система вынуждает узлы простаивать, да еще есть ощутимая вероятность потери данных на разделяемых дисках! Такой подход разработчиков дискредитирует всю систему - лучше бы сразу сказали, что их решение не может гарантировать монопольный доступ к разделяемым дискам.

Вкратце, keepalived реализует протокол VRRP (Virtual Router Redundancy Protocol) для создания виртуального linux-маршрутизатора на основе LVS. Описание LVS - Linux Virtual Server можно посмотреть на одноименном сайте. Вот краткое введение в системы высокой доступности на этом же сайте: High Availability И там же смотрим примеры конфигурация для Keepalived и heartbeat и пары других систем.

Погуглим, что еще пишут про практическое использование:

Статья целиком или почти целиком цитирует документацию HAProxy, но все же: Setting Up A High-Availability Load Balancer (With Failover and Session Support) With HAProxy/Keepalived On Debian Etch

А вот и пошаговая инструкция с небольшим теоретическим экскурсом в предмет: Improving Network Reliability with Keepalived

И, как было упомянуто выше, примеры реализации отказоустойчивого кластера на основе HAProxy + keepalived можно найти в документации HAProxy.

ANN: O'Reilly book "Using SQLite" available for pre-order

Из рассылки анонсов SQLite:

sqlite-announce Digest, Vol 25, Issue 1
In conjunction with O'Reilly Media, I am happy to announce that the
book "Using SQLite" is now available for pre-order. When released
later this summer, the title should also be available in several
popular ebook formats.

O'Reilly Media: http://oreilly.com/catalog/9780596521189/

Amazon: http://www.amazon.com/Using-SQLite-Jay-Kreibich/dp/0596521189/


You can help!

"Using SQLite" is taking part in O'Reilly Lab's "Open Feedback
Publishing System" (OFPS). While we continue to prepare the final
draft, you can read an online version of the book and leave feedback.
Changes and edits to the current draft are pushed to the website daily,
allowing you to track and watch as the book takes its final form.

I invite all SQLite list members to register and participate:

http://using-sqlite.labs.oreilly.com/

Thank you for your help and support!

-j

--
Jay A. Kreibich < J A Y @ K R E I B I.C H >


Автор книги - активный участник рассылки sqlite-users. Просмотрев бегло онлайн версию книги, могу сказать, что широта охвата материала впечатляет.

четверг, 6 мая 2010 г.

О безопасности пластиковых карт в интернет

Только недавно рассказывал товарищу, как все это работает, а он упрямился и твердил, что в его банке говорят другое - в поддержке Газпромбанк утверждают, что у них все круто работает, а что платежи от интернет-магазинов не проходят, так это все интернет-магазины неправильные. Разумеется, назвать хоть один "правильный" интернет магазин не могут. Поскольку втолковать человеку, что его проблема решается выбором правильного банка, было сложно, появление этой статьи меня очень порадовало, тем более, что в комментариях к ней все предельно доступно расписано: Как защитить свою карту в Интернет

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

Skype для дома и бизнеса

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

Анонс я увидел вот здесь Новые тарифные планы Skype и видеоконференции в следующей бете. Процитирую самое интересное, на мой взгляд:

Вчера вечером Skype объявил о введении новой системы тарифных планов (subscriptions) для звонков на фиксированные и мобильные номера. Подробностей пока не так много, но речь идет о гибкой системе формирования своего, персонализированного тарифного плана.

Цены на новые тарифные планы начинаются с €0,89 (€1.02 с НДС) в месяц, что дает цену в €0,01 за минуту для звонков на номера практически по всему миру. Для сравнения: сейчас тарифный план «Мир» охватывает 40 стран, новая система будет покрывать 170 стран.

Давно я ждал, когда же такое предложение появится по России, поскольку сотовые операторы безобразно жадничают на звонках в другие регионы.

Выбор браузера: google-chrome

Постепенно и довольно незаметно браузер от гугл стал удобен и быстр. С девелоперской версии 5 я начал его использовать под линуксом (пакет google-chrome-unstable) вместо конкуерора и файрфокса. Должен признаться, что именно google chrome позволяет мне использовать в качестве основного компьютера нетбук - браузер так быстр, что мощный компьютер просто не требуется, так что хром великолепно дополняет среду lxde. Полсотни вкладок, открытые в течении нескольких дней, не приводят к каким-либо утечкам памяти или снижению отзывчивости хрома, что по сравнению с файрфоксом доставляет просто огромное удовольствие. Флэш я отключаю, поскольку эта очень неэффективная технология и вдвое снижает время жизни от батареи, а прокрутка страниц с флэш временами сильно "тормозит" (прав Apple в своем желании "закопать" флэш - это действительно делается на благо пользователей). На днях появилась сборка новой версии под виндоус, так что есть повод сделать краткий обзор для пользователей всех платформ.

В обсуждаемой версии нам доступны утилиты для веб-разработчика - жмем Ctrl+Shift+i (или Ctrl+Shift+j, если сразу же хотим получить командную строку панели) и профит! Утилиты в данный момент стали очень удобны и для меня вполне заменяют developer toolbar в файрфоксе. Подробнее смотреть здесь: Google Chrome Developer Tools Tutorial
Вот эта статья будет неплохим примером того, как можно использовать вышеназванные утилиты для разработки или тестирования: Администрирование WEB SQL DB в Chrome
Аналогично с помощью утилит разработчика можно поэкспериментировать и с материалом вот этой статьи Разработка с использованием WEB SQL DB и Local Storage

Одно из интересных усовершенствований заключается во встроенной поддержки технологии флэш - особенно важно, что теперь флэш-плугин можно отключать на странице управления плугинами. По указанной ссылке вы найдете, как это сделать и описание некоторых других нововведений: 10 новых возможностей, которые стоит попробовать в Google Chrome 5

Для наших заказчиков я также рекомендую этот браузер, особенно его версию, не требующую установки: Google Chrome Portable.

воскресенье, 2 мая 2010 г.

Модем Huawei E1550 в debian

Было дело, когда-то я активно пользовался gprs, который был хиленьким, глючным и дорогим каналом доступа в интернет, но - был. Прошли годы, появился сначала анлим доступ в интернет через проводной модем, потом анлим по эзернет... Про gprs вспоминал редко - когда хотелось поработать вдали от цивилизации, но недоступность анлим-тарифов убивала эту идею на корню. Сегодня ситуация меняется - появились недорогие анлим-тарифы, да и edge зачастую доступен вне городов (3g-доступ пока возможен лишь в крупных городах). Итак, пришло время, а тарифы далее будут лишь снижаться.

Начинаем, разумеется, с выбора оборудования, то есть модема. Погуглив, я остановился на модели USB-модем Huawei E1550. Теперь посмотрим, как нам его подключить в debian. Замечу, что в сети полным-полно сильно замудренных историй о настройке этого устройства в убунту и дебиане - но содержат они или полный бред, или обрывочные куски некоего тайного знания. Вот как пример: Настройка интернета USB-модем Huawei E1550 в Ubuntu 9.10 (Debian 5.0.3) и Windows 7. Посмотрит на описанные в статье пассы пользователь виндоус или макос и решит, что линукс - система сложная и неудобная. А на самом деле, это танцы с бубном для дурдома на выезде, и авторы статьи о дебиане знают примерно столько же, сколько мартышки о балете. В самом деле, достали уже подобные базграмотные писульки.

Так как же это делается в дебиане? Я опишу так называемый debian-way, который хорош для установки любого оборудования, поддерживаемого системой (а для неподдерживаемого зачастую достаточно подключить репозитории testing/sid и повторить описанные операции). Итак, приступим.

Узнаем идентификатор устройства
Этот шаг нужен не всегда, так как зачастую можно обойтись именем модели, но будем методичны. Поскольку модема у меня еще нет и сам посмотреть командой lsusb я не могу, попросим гугл найти необходимые сведения:
$ lsusb
...
Bus 004 Device 003: ID 12d1:1001 Huawei Technologies Co., Ltd. E620 USB Modem
...

Какой пакет поддерживает нужное нам оборудование?
Это просто, очень просто - установим apt-file, который умеет искать по списку всех файлов для всех подключенных в системе репозиториев пакетов. Сначала поищем по имени модели:
$ apt-file search e1550
Ничего не нашли. Не беда, это был лишь пробный выстрел. Теперь пробуем по идентификатору устройства:
$ apt-file search 12d1 | grep 1001
usb-modeswitch-data: /etc/usb_modeswitch.d/12d1:1001
Как видим, поддержка оборудования есть и предоставляется пакетом usb-modeswitch-data. Но это низкоуровневый пакет, а нам удобно было бы поставить что-то более дружелюбное к пользователю. Что же, продолжаем.

Какой пакет с пользовательским интерфейсом умеет работать с найденным ранее пакетом поддержки оборудования?
И это легко выполняется средствами пакетного менеджмента debian - достаточно запросить систему, какие пакеты зависят от указанного. Зависят - это означает, что используют предоставляемые указанным пакетом файлы. Действуем по цепочке "бабка за репку, дедка за бабку,...", причем мы начинаем с репки - usb-modeswitch-data, а найти хотим мышку - ведь она лучше всех репку дергает (впрочем, цепочка может оказаться короче, скажем, до внучки, а может и длиннее и на мышке не заканчиваться - это мы скоро узнаем ):
Мышка за кошку,
Кошка за Жучку,
Жучка за внучку,
Внучка за бабку,
Бабка за дедку,
Дедка за репку —
Тянут-потянут — и вытянули репку.
$ apt-cache rdepends usb-modeswitch-data
usb-modeswitch-data
Reverse Depends:
  usb-modeswitch

$ apt-cache rdepends usb-modeswitch
usb-modeswitch
Reverse Depends:
  usb-modeswitch-data
  usb-modeswitch-data
  usb-modeswitch-data
  modemmanager

$ apt-cache rdepends modemmanager
modemmanager
Reverse Depends:
  network-manager

Пакет network-manager достаточно известен, кроме того, название вполне говорящее - вполне очевидно, что это именно пользовательский интерфейс. Если все же не совсем очевидно, можно точно также посмотреть список пакетов, зависящих от network-manager - их много, почти полсотни и станет понятно, что пора остановиться. Далее ставим или network-manager или, если у вас, как у меня, личное его неприятие, ставим предыдущий пакет из полученной цепочки - modemmanager. Значит, наша с вами - не сказка, а быль, - звучит так:

network-manager за modemmanager,
modemmanager за usb-modeswitch,
usb-modeswitch за usb-modeswitch-data —
и бежит интернет через модем.

Вот, вкратце, и все таинство подключения искомого модема в debian. Заодно мы увидели, как наш любимый дистрибутив использует зарекомендовавшие себя тысячелетиями принципы :-) И кто скажет, что это сложно?

Upd.
Модем Huawei E1550 в debian - продолжение

суббота, 1 мая 2010 г.

Книга по оптимизации веб-сайтов на стороне клиента

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

Книга «Реактивные веб-сайты»

Прототип интерфейса рассылки сообщений для ПО оффлайн-сервис

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



Разумеется, более современный подход заключается в предоставлении полнотекстового поиска по именам, так что, зная отправителя (если отправляем конкретному адресату), легко его найти. В случае массовой рассылки удобно предоставить набор некоторых групп (комнат, чатов и т.п.), которым и отправляется сообщение.

Прототип сделан с помощью расширения Pencil к браузеру Mozilla Firefox.

Отправить выбранным пользователям

Отправить всем пользователям указанных регионов

Upd.
Отправить выбранным пользователям


Отправить всем пользователям указанных регионов

(C) Alexey Pechnikov aka MBG, mobigroup.ru