<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2russianfull.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:media="http://search.yahoo.com/mrss/" xmlns:creativeCommons="http://backend.userland.com/creativeCommonsRssModule" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0">
<channel>
	<title>GrAndSE's blog</title>
	<link>http://grandse.org.ua/feed/</link>
	
	<description>Останні замітки з блогу GrAndSE.org.ua</description>
	<copyright>© 2008, GrAndSE. All rights reserved.</copyright>
	<ttl>60</ttl> 
	<language>uk</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<creativeCommons:license>http://creativecommons.org/licenses/by/2.0/</creativeCommons:license><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" href="http://feeds.feedburner.com/GrandsesBlog" type="application/rss+xml" /><feedburner:feedFlare href="http://add.my.yahoo.com/rss?url=http%3A%2F%2Ffeeds.feedburner.com%2FGrandsesBlog" src="http://us.i1.yimg.com/us.yimg.com/i/us/my/addtomyyahoo4.gif">Subscribe with My Yahoo!</feedburner:feedFlare><feedburner:feedFlare href="http://www.newsgator.com/ngs/subscriber/subext.aspx?url=http%3A%2F%2Ffeeds.feedburner.com%2FGrandsesBlog" src="http://www.newsgator.com/images/ngsub1.gif">Subscribe with NewsGator</feedburner:feedFlare><feedburner:feedFlare href="http://feeds.my.aol.com/add.jsp?url=http%3A%2F%2Ffeeds.feedburner.com%2FGrandsesBlog" src="http://o.aolcdn.com/favorites.my.aol.com/webmaster/ffclient/webroot/locale/en-US/images/myAOLButtonSmall.gif">Subscribe with My AOL</feedburner:feedFlare><feedburner:feedFlare href="http://www.bloglines.com/sub/http://feeds.feedburner.com/GrandsesBlog" src="http://www.bloglines.com/images/sub_modern11.gif">Subscribe with Bloglines</feedburner:feedFlare><feedburner:feedFlare href="http://www.netvibes.com/subscribe.php?url=http%3A%2F%2Ffeeds.feedburner.com%2FGrandsesBlog" src="http://www.netvibes.com/img/add2netvibes.gif">Subscribe with Netvibes</feedburner:feedFlare><feedburner:feedFlare href="http://fusion.google.com/add?feedurl=http%3A%2F%2Ffeeds.feedburner.com%2FGrandsesBlog" src="http://buttons.googlesyndication.com/fusion/add.gif">Subscribe with Google</feedburner:feedFlare><feedburner:feedFlare href="http://www.pageflakes.com/subscribe.aspx?url=http%3A%2F%2Ffeeds.feedburner.com%2FGrandsesBlog" src="http://www.pageflakes.com/ImageFile.ashx?instanceId=Static_4&amp;fileName=ATP_blu_91x17.gif">Subscribe with Pageflakes</feedburner:feedFlare><feedburner:feedFlare href="http://lenta.yandex.ru/settings.xml?name=feed&amp;url=http%3A%2F%2Ffeeds.feedburner.com%2FGrandsesBlog" src="http://lenta.yandex.ru/i/addfeed.gif">?????? ? ??????.?????</feedburner:feedFlare><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com" /><item>
		<title>Збираємо php 5.3 та nginx докупи</title>
		<description>php 5.3 вийшов вже досить давно. Хоча лише невелика кількість хостерів оновлюється до нової версії, але рано чи пізно ця версія стане основною для більшості хостингів, особливо з огляду на те, що в ній з'явилась велика кількість приємних нововведень. Коли я працював з Mac OS X, я вже встиг познайомитись з php 5.3 (та ще й описати &lt;a href="http://grandse.org.ua/messages/show/83" title="Слід звернути увагу"&gt;зміни з якими я зіткнувся&lt;/a&gt;), але не мав змоги поекспериментувати з новим функціоналом, перевірити всі самописні (і не тільки) бібліотечки, що я використовую, на сумісність з новою версією. А хочеться ж :)
Я вже встиг оновитись до бети Ubuntu, однак там все ще відсутній пакет для php-5.3. Оскільки й від версії 5.2 відмовлятись не хочеться, то я вирішив зібрати для експериметів зв'язку php-fastcgi+nginx.

&lt;h3&gt;Збираємо php&lt;/h3&gt;
Оскільки готові пакети відсутні, то будемо збирати з вихідного коду. Завантажити його можна &lt;a href="http://www.php.net/downloads.php" title="Сторінка завантаження php"&gt;без жодних проблем&lt;/a&gt;. Розпаковуємо та переходимо для конфігурування. Кожен сам добре знає, які модулі йому потрібні. Особисто я зробив це так:&lt;!-----more-----!&gt;
&lt;code class="code-block"&gt;
./configure  --prefix=/usr/local --with-mysql=/usr --with-mysqli=/usr/bin/mysql_config --with-pgsql=/usr --with-tidy=/usr --with-openssl-dir=/usr --with-zlib-dir=/usr --enable-mbstring --with-xpm-dir=/usr --with-pdo-pgsql=/usr --with-pdo-mysql=/usr --with-xsl=/usr --with-ldap --with-xmlrpc --with-iconv-dir=/usr --enable-exif --enable-calendar --with-bz2=/usr --with-mcrypt=/usr --with-gd --with-jpeg-dir=/usr --with-png-dir=/usr --with-zlib-dir=/usr --with-freetype-dir=/usr/lib --enable-mbstring --enable-zip --with-pear
&lt;/code&gt;
Такий спосіб встановлення вимагає мати в системі досить багато додатково встановлений *-dev пакетів. Залишається зібрати та встановити:
&lt;code class="code-block"&gt;
make
sudo make install
&lt;/code&gt;
Якщо все пройшло успішно, то ми маємо php встановлений з префіксом /usr/local.

&lt;h3&gt;Налаштування php-fastcgi&lt;/h3&gt;
За основу для подальших маніпуляцій я взяв &lt;a href="http://blog.codefront.net/2007/06/11/nginx-php-and-a-php-fastcgi-daemon-init-script/" title="Матеріал про php-fastcgi та nginx"&gt;маетеріал з Codefront.net&lt;/a&gt;. Наступний крок - підготовка скрипта для завантаження php в якості демону fastcgi. Для тих хто не знає поясню, що fastcgi може працювати як окремий сервер, що через сокет отримує запит та параметри середовища, і як відповідь повертає результати свого виконання. Отже створюємо файл &lt;var&gt;/etc/init.d/php-fastcgi&lt;/var&gt; з таким вмістом: 
&lt;code class="code-block"&gt;
#! /bin/sh
### BEGIN INIT INFO
# Provides:          php-fastcgi
# Required-Start:    $all
# Required-Stop:     $all
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Start and stop php-cgi in external FASTCGI mode
# Description:       Start and stop php-cgi in external FASTCGI mode
### END INIT INFO

# Author: Kurt Zankl &amp;lt;[EMAIL PROTECTED]&amp;gt;

# Do NOT "set -e"

PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="php-cgi in external FASTCGI mode"
NAME=php-fastcgi
DAEMON=/usr/bin/php-cgi
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
PHP_CONFIG_FILE=/etc/php5/cgi/php.ini

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] &amp;&amp; . /etc/default/$NAME

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (&amp;gt;= 3.0-6) to ensure that this file is present.
. /lib/lsb/init-functions

# If the daemon is not enabled, give the user a warning and then exit,
# unless we are stopping the daemon
if [ "$START" != "yes" -a "$1" != "stop" ]; then
        log_warning_msg "To enable $NAME, edit /etc/default/$NAME and set START=yes"
        exit 0
fi

# Process configuration
export PHP_FCGI_CHILDREN PHP_FCGI_MAX_REQUESTS
DAEMON_ARGS="-q -b $FCGI_HOST:$FCGI_PORT -c $PHP_CONFIG_FILE"

do_start()
{
        # Return
        #   0 if daemon has been started
        #   1 if daemon was already running
        #   2 if daemon could not be started
        start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test &amp;gt; /dev/null \
                || return 1
        start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON \
                --background --make-pidfile --chuid $EXEC_AS_USER --startas $DAEMON -- \
                $DAEMON_ARGS \
                || return 2
}

do_stop()
{
        # Return
        #   0 if daemon has been stopped
        #   1 if daemon was already stopped
        #   2 if daemon could not be stopped
        #   other if a failure occurred
        start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE &amp;gt; /dev/null # --name $DAEMON
        RETVAL="$?"
        [ "$RETVAL" = 2 ] &amp;&amp; return 2
        # Wait for children to finish too if this is a daemon that forks
        # and if the daemon is only ever run from this initscript.
        # If the above conditions are not satisfied then add some other code
        # that waits for the process to drop all resources that could be
        # needed by services started subsequently.  A last resort is to
        # sleep for some time.
        start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
        [ "$?" = 2 ] &amp;&amp; return 2
        # Many daemons don't delete their pidfiles when they exit.
        rm -f $PIDFILE
        return "$RETVAL"
}
case "$1" in
  start)
        [ "$VERBOSE" != no ] &amp;&amp; log_daemon_msg "Starting $DESC" "$NAME"
        do_start
        case "$?" in
                0|1) [ "$VERBOSE" != no ] &amp;&amp; log_end_msg 0 ;;
                2) [ "$VERBOSE" != no ] &amp;&amp; log_end_msg 1 ;;
        esac
        ;;
  stop)
        [ "$VERBOSE" != no ] &amp;&amp; log_daemon_msg "Stopping $DESC" "$NAME"
        do_stop
        case "$?" in
                0|1) [ "$VERBOSE" != no ] &amp;&amp; log_end_msg 0 ;;
                2) [ "$VERBOSE" != no ] &amp;&amp; log_end_msg 1 ;;
        esac
        ;;
  restart|force-reload)
        log_daemon_msg "Restarting $DESC" "$NAME"
        do_stop
        case "$?" in
          0|1)
                do_start
                case "$?" in
                        0) log_end_msg 0 ;;
                        1) log_end_msg 1 ;; # Old process is still running
                        *) log_end_msg 1 ;; # Failed to start
                esac
                ;;
          *)
                # Failed to stop
                log_end_msg 1
                ;;
        esac
        ;;
  *)
        echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" &amp;gt;&amp;2
        exit 3
        ;;
esac
&lt;/code&gt;
Тепер треба створити конфігурацію за замовчуванням для fastcgi-демону, яка буде знаходитись в файлі &lt;var&gt;/etc/default/php-fastcgi&lt;/var&gt;. Задамо користувача www-data за аналогом до стандартної кунфігурації apache, порт 9000 та ще деякі параметри:
&lt;code class="code-block"&gt;
START=yes
# Which user runs PHP? (default: www-data)
EXEC_AS_USER=www-data
# Host and TCP port for FASTCGI-Listener (default: localhost:9000)
FCGI_HOST=localhost
FCGI_PORT=9000
# Environment variables, which are processed by PHP
PHP_FCGI_CHILDREN=4
PHP_FCGI_MAX_REQUESTS=1000
&lt;/code&gt;
Все готово для запуску демона. Тепер можна запустити демона:
&lt;code class="code-block"&gt;
sudo /etc/init.d/php-fastcgi start
&lt;/code&gt;

&lt;h3&gt;Налаштування nginx&lt;/h3&gt;
Встановимо сайтом за замовчуванням для nginx якийсь site-name, що буде виконуватись через php-fastcgi сервер, конфігурацію якого ми щойно провели. Для цого необхідно відредагувати файл &lt;var&gt;/etc/nginx/sites-avaliable/default&lt;/var&gt;:
&lt;code class="code-block"&gt;
server {
        listen   80;
        server_name  localhost;

        access_log  /var/log/nginx/localhost.access.log;

        location / {
                root   /var/www/site-name;
                index  index.html index.htm;
        }

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        location ~ \.php$ {
                fastcgi_pass   127.0.0.1:9000;
                fastcgi_index  index.php;
                fastcgi_param  SCRIPT_FILENAME  /var/www/site-name$fastcgi_script_name;
                include fastcgi_params;
        }
}
&lt;/code&gt;
Спочатку задаємо параметри серверу (ім'я та порт), логування, директорію сайту.  Далі вказуємо додатковий location для обробки php. А тут в свою чергу &lt;var&gt;fastcgi_pass&lt;/var&gt; вказує на адресу fastcgi-сервера, fastcgi_index на файл, о буде завантажуватись за замовчуванням, а далі слідує набор директив, що описують параметри, які мають передатись серверу fastcgi. Частина опису винесена в файл &lt;var&gt;/etc/nginx/fastcgi_params&lt;/var&gt;:
&lt;code class="code-block"&gt;
fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param  REDIRECT_STATUS    200;
&lt;/code&gt;
Тепер можна хапустити nginx:
&lt;code class="code-block"&gt;
sudo /etc/init.d/nginx start
&lt;/code&gt;

&lt;h3&gt;І все&lt;/h3&gt;
Тепер можна відкрити браузер та на localhost побачити ваш чудовий сайт :).Цього має бути цілком достатньо для початку роботи, а далі вже можно попрацювати з параметрами nginx та додати наприклад функціонал аналогічний до mod_rewrite і т.д. Головне, що я собі зробив таку от нотатку, та не забуду як проводити базове налаштування fastcgi та nginx :)&lt;img src="http://feeds.feedburner.com/~r/GrandsesBlog/~4/eipEwn9PDtI" height="1" width="1"/&gt;</description>
		<link>http://feedproxy.google.com/~r/GrandsesBlog/~3/eipEwn9PDtI/88</link>
		<pubDate>Sun, 04 Oct 2009 08:56:50 +0300</pubDate>
		<dc:creator>grandse</dc:creator>
	<feedburner:origLink>http://grandse.org.ua/messages/show/88</feedburner:origLink></item>
	<item>
		<title>І я теж Шерлок Холмс.</title>
		<description>Останні два дні мав змогу повноцінно насолодитись розробкою та відлагодженням коду на C++. І тут зрозумів, який же &lt;strong&gt;драйв&lt;/strong&gt; я отримую від цього процесу. Ніяк інакше це назвати не можна. Причому це задоволення не покидало мене протягом всього часу роботи з кодом: від того моменту, коли я написав ті два десятки рядків, що читали вхідні дані з файлу та виводили їх на екран, до того часу, коли я запустив останнього разу цю невеличку програму та побачив результати її виконання, саме так як це й мало бути. Навіть після цього разом з задоволенням від закінченої справи та очікуванням на виплату за зроблену роботу відчув ще щось.. Немов дочитав цікаву книжку. І так само, як на останніх сторінках захопливого твору, якось не хотілось щоб це невеличке диво закінчилось.
І це C++, відомий досить сладною роботою з пам'яттю, вимогливістю до написання коду, готовий в будь-який момент сказати Segmentation fault, без яких би то не було пояснень. Ніякого трейсу стеку, чи хоча б невеликого повідомлення з вказівкою на рядок, де цей самий fault вискочив. І тут починається виведення додаткової інформації по всьому коду, з метою виявлення місця де виникла помилка, покрокове виконання програми за допомогою &lt;strong&gt;gdb&lt;/strong&gt; (для тих хто не знаю, консольний відлагоджувач, досить потужний та гнучкий, хоча й незвичний).&lt;!-----more-----!&gt;
Раніше я не любив процес відлагодження коду. Як і ще десятки програмістів. Особливо така нелюбов стосується початківців. Так, написання коду, створення ідеї, ні з чим порівняти неможливо. Коли з рядків коду збирається щось... що працює. Звісно класне відчуття, коли зі шматочків збирається щось величеньке, в ньому з'являється те, що можна назвати архітектурою. А яке задоволення, коли з часом в написання коду приходять шаблони, рефакторінг та "ефемерна архітектура" початківця, перетворуються на щось більш логічне та елегантне.
Але й у відлагодженні коду з часом я почав бачити свій кайф. Чому так багато людей читають детективи? Чому стільки людей фанатіють від серіалу про доктора-наркомана? Чому в часи цілковитого 3d все ще виходять простенькі іграшки-головоломки і мають вони вельми велику популярність? Та все ж тому, що це дозволяє випробувати свій розум, кинути виклик невідомій проблемі та подолати її.
Однак такий виклик потребує наполегливості, досвіду, постійної практики та розвиненої інтуіції. Тому так початківці й не люблять відлагоджувати код - всі вищеперелічені здібності вимагають тренувань. Однак, і кожна спортивна чи інтелектуальна гра також вимагає докласти певних зусиль, щоб досягнути якогось рівня та отримувати задоволення значно більше ніж на початку, коли й по м'ячу влучити складно, не то щоб забити гол. Різниця в тому, що по телевізору не показують чемпіонат Європи з програмування. :)
Кожному своє. І то справа кожного програміста, чи отримувати задоволення від написання самого коду, тестів для коду (теж дуже класна гра), підготовки даних, рефакторингом, оптимізацією і як кінцевого результату повноцінної робочої програми. Особисто я отримую і тому дуже щасливий :)&lt;img src="http://feeds.feedburner.com/~r/GrandsesBlog/~4/Uutajbe8c8A" height="1" width="1"/&gt;</description>
		<link>http://feedproxy.google.com/~r/GrandsesBlog/~3/Uutajbe8c8A/87</link>
		<pubDate>Mon, 28 Sep 2009 08:42:19 +0300</pubDate>
		<dc:creator>grandse</dc:creator>
	<feedburner:origLink>http://grandse.org.ua/messages/show/87</feedburner:origLink></item>
	<item>
		<title>Чому Open Source не помре?</title>
		<description>Саме сьогодні мені захотілось поділитись своїми думками стосовно світу &lt;strong&gt;Open Source&lt;/strong&gt;. Виною тому є моя спроба зробити свій внесок в проект &lt;a href="http://forge.mysql.com/wiki/MySQL_Proxy" title="Сторінка проекту MySQL Proxy на MySQL forge"&gt;MySQL Proxy&lt;/a&gt;. Про неї я розповім окремо, коли доля мого шматочка коду стане відомою, а поки що хочу поділитись своїми думками з приводу Open Source як явища та його подальшої долі.
Досить багато людей вважають, що вільне (чи тим паче з відкритим кодом) програмне забезпечення не може бути якісним, що в нього немає майбутнього. В чому причина таких думок? По-перше, звичка до того, що програмне забезпечення - великий бізнес, що за прибутками переріс розробку та продаж апаратного забезпечення. По-друге, прихильники такої думки прагнуть стати частиною цього бізнесу на якому б то не було рівні. По-третє, нерозуміння причин стрімкого розвитку світу Open Source та вільного ПЗ.
Почну з причин, які лежать в основі розвитку відкритого програмного забезпечення.
&lt;!-----more-----!&gt;
&lt;h3&gt;Чому ж вільне ПЗ буде розвиватись?&lt;/h3&gt;
По-перше, &lt;strong&gt;попит на вільне ПЗ&lt;/strong&gt;. Як би там не було, але кількість бажаючих отримати щось задарма завжди буде великою. І чим жорсткіше буде законодавство що до піратства, тим більше людей буде схилятись до використання ПЗ, яке не вимагає плати за використання. Навіть коли це ПЗ поступається своєму комерційному аналогу. 
Не обов'язково люди не бажають платити за ПЗ тому що в них не вистачає грошей чи "високі принципи" заважають. Зазвичай причина в тому, що користувачу не потрібен весь той функціонал, що пропонує платна програма, а цілком вистачить якого безкоштовного аналога, який до цього має щей перевагу у мініатюрності. 
Достатньо згадати Adobe Photoshop. Скажу чесно: я не знаю мабуть і сотої частини можливостей Photoshop. Не сумніваюсь, що там є все чи практично все, що тільки може знадобитись. Однак я навіть не знаю всього що там є. І мені повністю вистачає &lt;a href="http://gimp.org/" title="Сайт проекту Gimp"&gt;Gimp&lt;/a&gt; та &lt;a href="http://inkscape.org/" title="Сайт проекту Inkscape"&gt;Inkscape&lt;/a&gt;. Хоча я й знайшов для себе ряд недоліків в цих програмах, операції які б могли виконуватись в них легше чи якісніше. Підозрюю, що все це вміє робити Photoshop. Однак я не готовий платити близко &lt;a href="http://www.adobe.com/products/photoshop/compare/" title="Ціни на Photoshop"&gt;700 доларів&lt;/a&gt; за цей продукт, а потім ще по 200 доларів за оновлення до нової версії. Тому що я насправді не знаю навіщо мені потрібно витрачати такі кошти. І при цьому, може виявитись, що цей продукт не зможе повністю задовольнити мої вимоги (деякі речі все ж таки простіше зробити в Gimp чи Inkscape).
Як показує практика, досить часто відкрите програмне забезпечення ні чим не поступається закритому. Наприклад, у світі браузерів одну з лідуючих позицій займає широко відомий Firefox, який є повністю відкритим продуктом. Вже котрий рік я користуюсь ним, як основним браузером. І не тільки я. А ще згадаймо список популярних мов програмування - практично всі їх компілятори відкриті, або мають відкритий аналог. А найпопулярніша платформа для ведення блогів? Перелік можна продовжувати.
З усього вище сказано випливає друга причина: &lt;strong&gt;спільнота&lt;/strong&gt; - люди, що витрачають свій час на розробку, тестування, написання документації ПЗ, відповіді на питання, що виникають у інших користувачів цих інструментів. Коли якість дорого та закритого продукту, його функціонал чи ціна не задовольняють людину, що має можливість писати своє програмне забезпечення, досить часто така людина бере та реалізує те, шо їй потрібно. Ця ж людина (або група людей) стають першими тестерами продукту. І якщо продукт виходить назовні, то навколо нього нарожується спільнота (якщо цей інструмент комусь потрібний).
&lt;img src="/files/images/got-team.jpg" class="centered" title="Team" alt="You've got you'r team" width="600px" /&gt;
Часто кінцеві користувачі, що виступають в ролі тестерів та служби підтримки, а іноді і приєднуються до групи розробників, є більш потужньою і важливою силою, ніж ті люди, що розпочали проект. Особливо для його популяризації. Причина в когнітивній протидії будь-якого ПЗ. Тобто в необхідності розбиратись як з ним працювати. Автору програми це звісно ж зрозуміло і дизайн інтерфейсу користувача здається доволі логічним, а частенько й простим. А для кінцевого користувача може виглядати зовсім інакше. І частіше кращою допомогою за будь-яку документацію (а спробуйте ж написати все просто та зрозуміло, для людини що є "абсолютним нулем", коли ви розумієте не лише як з ним працювати, а також як воно влаштоване всередені) буде порада іншого звичайного користувача. 
А тут десь близко криється ще один монстр: &lt;strong&gt;підтримка корпорацій&lt;/strong&gt;. Справа в тому, що корпорації часто зацікавлені у використанні вільного ПЗ. З одного боку - економія. Уявіть собі скільки коштує використання сотен, тисяч, десятків тисяч ліцензій того чи іншого продукту. Навіть за умов пільгових програм, що продавці частенько пропонують для оптових замовників. Багатенько. Але корпорація можуть і заплатити, тому це далеко не головна причина.
З іншого боку, для корпорацій вільне ПЗ - ринок збуту. Всім відомо, що раз *nix займає великий відсоток серверних систем (які доречі порівняно з користувацькими коштують значно дорожче), то є величезний за обсягом сегмент ринку дорогого апаратного забезпечення. І все що потрібно, так це щоб воно працювало з відкритими ОС. Тому великі компанії, що займаються розробкою заліза частенько не обходять стороною написання драйверів для Linux, чи внесення змін в ядро, для підтримки власної продукції. І так роблять не тільки розробники апаратного забезпечення.
Ще одна можливість для корпорацій скористатись відкритим ПЗ - тестова платформа, безкоштовні додатки та область для пошуку кадрів. Відкриваючи частину внутрішнього істнтрументарія у вигляді відкритого коду, корпорація діє змогу кожному зацікавленому в цьому продукті отримати змогу з ним попрацювати. Натомість отримає відгуки про цей інструмент, деякі додатки, що додають новий функціонал, пошук помилок та часто шляхи подолання проблем. Якщо у відкритий доступ потряпляє полегшена версія продукту (наприклад, OpenSolaris, OpenSuse), то це дає змогу частину роботи з розробки основного функціоналу перекласти на зовнішніх розробників, а сконцентруватись на тих можливостях, що реально можуть принести прибуток. Впевнений, що це вигідніше ніж шлях який обрала Microsoft для бета-тестування Windows Vista (плата за участь в тестуванні!!!), яка як можна побачити не набула великої популярності, не дивлячись на можливості та вплив цієї компанії.
Будь-хто, зто приймав участь у тестуванні, розробці та взагалі роботі з продукцією компанії чи то відкритим інструментарієм, що використовується компанією, отримує деякі навички як її співробітник. Так Google, наскільки мені відомо, з радістю набирає в штат людей, що задіяні в розробці продуктів, якими користується сама компанія.
Не слід забувати про піар. Як тільки компанія відкриває частину свого існтрументарію, приймає участь у розробці вілкритого ПЗ, одразу ж вона звертає на себе увагу, створює позитивне враження серед прихильників відкритого ПЗ. Яким би це не здавалось незначним, однак силу реклами та прихильність покупців ніхто не спростовував.
З огляду на вище перелічене безглуздими є думки про низьку якість програмного забезпечення з відкритим вихідним кодом. Воно знаходиться на тому ж рівні, що й платне ПЗ, оскільки за ним стоять люди, що зацікавлені в його розвитку як інструменту для зодоволення власних потреб чи способу заробітку.
&lt;h3&gt;Як заробити в світі відкритого ПЗ?&lt;/h3&gt;
Так чи інакше, однак вільне ПЗ все більше виходить на ринок. І для всіх, хто хоче заробляти на цьому ринку не враховувати це не має права. Навіть компанія Microsoft, що може вважатись лідером руху за платне програмне забезепечення, все більшу увагу звертає на світ Open Source. 
Те що програмісти можуть заробляти на розробці ПЗ при наявності інструментів добре підтверджує всесвітня мережа з величезною кількістю великих та меленкіх компаній, що займається розробкою різноманітних сайтів. Здавалося б, наявна величезна кількість безкоштовних CMS, що дозволяють за декілька хвилин створити самойстійно сайт. Однак самі програмісти широко використовують такі системи для заробітку. Більша частина інтернет-бізнесу обертається навколо open source: *SBD/Linux, Apache/lighttpd/nginx, php/perl/python/ruby/java, mysql/postgresql. 
Якщо хочеться чогось іншого, корім штампування візиток, онлайн-магазинів та блогів, завжди є можливість спробувати створити власний сервіс-стартап, на основі все тих же відкритих існтрументів. 
&lt;img src="/files/images/be-yourself.jpg" class="centered" title="Team" alt="You've got you'r team" width="600px" /&gt;
У світі desktop все складніше: створити нове фінансово-вигідне для розробника ПЗ складніше ніж знайти нову нішу в мережі. Однак виною тому не велика кількість безкоштовного ПЗ, а як раз крупне комерційне. Навряд чи себе виправдає розробка ще одного графічного пакету, чи пакету для моделювання: обігнати в цьому сегменті лідерів ринку буде дуже складно за функцоіналом, а менша ціна не буде грати вирішальної ролі - ті кому дійсно потрібен Photoshop придбають саме його за будь-які гроші, ніж втричі дешевший аналог без імені та можливостей. Хоча я впевнений, що є сегменти спеціалізованого ПЗ не зайніті ні відкритими ні комерційними проектами, де ви можете створити щось дійсно нове та якісне.
Що до розробки корпоративних систем, тут знову ж таки правила дистують не Open Source гравці, а крупні компанії, що давно працюють в цьому сегменті. Для різноманітних бугалтерських систем, систем управління підприємствами, систем обліку майна та людських ресурсів існує або дуже дороге комерційне ПЗ, або компанії що займаються розробкою такого ПЗ на замовлення. І якщо Ви прагнете працювати на цьому ринку, то шукайте ворога не в світі відкритого коду. Быльше того, тут можна знайти велику кількість інструментів та напрацювань, що можуть допомогти.
Ну і звісно, якщо Ваша компанія вже працює, то відкритий проект в якому вона б взяла участь, чи власний код, що буде викладений в якості Open Source завжди можна використовувати в якості піару. Я думаю, що не слід нехтувати прикладом багатьох великих компаній, які майже щомісячно відкривають код деяких своїх продуктів чи то існтрументів роботи і при цьому приносять власникам величезні прибутки.
&lt;h3&gt;Об'єктивність&lt;/h3&gt;
Вирок собі я написав в другому реченні цього чудового допису :) Так, так, я можу вважатися одним з фанатів Open Source, що з задоволенням ним користуються та готові витрачати свій час на його розвиток. Причому цілком безкоштовно. Однак така упередженість не зменшує вагу наведених мною фактів. І стосовно причин стрімкого розвитку відкритого ПЗ, і стосовно заробітку, і стосовно якості цього самого ПЗ. І ніяких холіварів та суперечок ;)
Дякую за увагу всім хто зміг дочитати до цього місця! ;)&lt;img src="http://feeds.feedburner.com/~r/GrandsesBlog/~4/oUBcTTHxSVg" height="1" width="1"/&gt;</description>
		<link>http://feedproxy.google.com/~r/GrandsesBlog/~3/oUBcTTHxSVg/86</link>
		<pubDate>Sun, 20 Sep 2009 08:58:13 +0300</pubDate>
		<dc:creator>grandse</dc:creator>
	<feedburner:origLink>http://grandse.org.ua/messages/show/86</feedburner:origLink></item>
	<item>
		<title>День програміста</title>
		<description>Радий всіх товаришів за родом занять та інтересами привітати з &lt;strong&gt;днем програміста&lt;/strong&gt; (256 днем року). Бажаю щоб завжди на вашу долю випадали цікаві для Вас задачі, щоб писались вони легко, щоб помилок було мало, а пошук тих що є проходив безболісно та швидко та щоб за ці цікаві для Вас задачі гонорари були достойні вашого високого професійного рівня. Ну і ще гарного Вам спілкування з колегами та керівниками. І чудово провести цей день :)
P.S.: Якось так вже сталось, що ну ніяк на блозі не пишу про не-ІТ свята - завжди щось заважає.&lt;img src="http://feeds.feedburner.com/~r/GrandsesBlog/~4/EbqNec_HR9o" height="1" width="1"/&gt;</description>
		<link>http://feedproxy.google.com/~r/GrandsesBlog/~3/EbqNec_HR9o/84</link>
		<pubDate>Sun, 13 Sep 2009 08:44:24 +0300</pubDate>
		<dc:creator>grandse</dc:creator>
	<feedburner:origLink>http://grandse.org.ua/messages/show/84</feedburner:origLink></item>
	<item>
		<title>Слід звернути увагу </title>
		<description>Так вже сталось, що я іноді пишу щось в неприємному ключі про мову php. :) Хоча й сам дуже часто нею користуюсь. І як я з часом розумію, причина тут не тільки в тому, що досить багато роботи є для php-програміста, а ще й в тому, що деякі речі дуже просто зробити саме на php. Можливо на python чи ruby ще простіше, однак оскільки я вже дуже звик до php, то сам можу попрацювати в якості такого собі справочного матеріалу, що може багато розповісти і про синтаксиси виклику функцій і про бібліотеки :) Тому для того, щоб написати маленький скриптик на декілька десятків рядків мені в більшості випадків не треба думати, як з python чи ruby.
Вже давно вийшов php 5.3 і хоча більшість хостерів ще не планують перехід на нього, однак як було з взагалі з 5ою гілкою php він раніше чи пізнціше стане основним на більшості хостингів. Тому хочу звернути вашу увагу на декілька аспектів, що можуть стати неприємними сюрпризами при переході.&lt;!-----more-----!&gt;
&lt;dl&gt;
&lt;dt&gt;Підключення до БД.&lt;/dt&gt;
&lt;dd&gt;Не знаю чи всюди, однак в збірці під MacOS X php 5.3 вимагає вказувати порт. Наприклад так:
&lt;code class="code-block"&gt;
$conn = mysql_connect('localhost:3306', 'username', 'password');
&lt;/code&gt;
Інакше отримаємо помилку, що так і так, а підключитись не вийде. Підозрюю, що причиною тому є новий драйвер для MySQL, що працює тепер напряму, без використання libmysl.&lt;/dd&gt;
&lt;dt&gt;Об'єми пам'яті&lt;/dt&gt;
&lt;dd&gt;Знову ж таки при роботі з БД MySQL виникає проблема з виходом за ліміт доступної для роботи пам'яті. На тому коді, що цілком нормально працював з версією 5.2&lt;/dd&gt;
&lt;dt&gt;Магічні методи&lt;/dt&gt;
&lt;dd&gt;Те, що по мені серйозно вдарило, так це необхідність того, щоб &lt;code&gt;__get&lt;/code&gt;, &lt;code&gt;__set&lt;/code&gt;, &lt;code&gt;__unset&lt;/code&gt;, &lt;code&gt;__isset&lt;/code&gt; були нестатичними. Те що вони тепер мають бути завжди public є досить логічним кроком, а от з неможливістю бути статичнми щось не так. Тепер доречі поряд з методом &lt;code&gt;__call&lt;/code&gt; з'явився &lt;code&gt;__callStatic&lt;/code&gt; (ось тут я цылком погоджуюсь).&lt;/dd&gt;
&lt;dt&gt;Прощавай eregi та split&lt;/dt&gt;
&lt;dd&gt;Якщо Ви користувались запість perl-сумісний регулярних виразів posix-сумісні, то тут Вам буде очікувати неприємний сюрприз з виносом цього функціоналу за рамки основної поставки php. Досить непогано, що функціонал що дублюється починає видалятись з основної поставки php - навіщо зайві функції, що додають плутанини?&lt;/dd&gt;
&lt;dt&gt;Массиви та об'єкти&lt;/dt&gt;
&lt;dd&gt;Тепер масиви та об'єкти відрізняються більше, тому деякі методи для роботи з масивами, що раніше працювали з об'єктами, відтепер будуть працювати лише з масивами.&lt;/dd&gt;
&lt;dt&gt;magic quotes&lt;/dt&gt;
&lt;dd&gt;Magic quotes відмічені для остаточного видалення, тому використання всіх функцій та інструкцій в php.ini буде викликати E_DEPRECATED&lt;/dd&gt;
&lt;dt&gt;Робота з сесіями&lt;/dt&gt;
&lt;dd&gt;&lt;code&gt;session_register()&lt;/code&gt;, &lt;code&gt;session_unregister()&lt;/code&gt;, &lt;code&gt;session_is_registered()&lt;/code&gt; теж відмічені для видалення&lt;/dd&gt;
&lt;/dl&gt;
Тепер в php можна &lt;a href="http://ua2.php.net/gc_enable" title="Документація"&gt;увімкнути систему циклічної збірки сміття (garbage collector)&lt;/a&gt;. Про те, наскільки добре чи погано вона працює стане відомо пізніше, однак в цілому враження від php 5.3 окрім перших трьох пунктів з переліку непогані. Здається, що php рухається у вірному напрямку і має всі шанси перетворитись раніше чи пізніше а досиь красиву мову, на яку навіть я перестану нарікати :)
Ну і ще хочу додати вже не про php 5.3. Доволі давно я присав про &lt;a href="http://grandse.org.ua/messages/show/62" title="Bug or feature? Робота з числами в php"&gt;роботу з числами в php&lt;/a&gt;. Одним з пунктів мого невдоволення була наявність переводу числа в рядок в залежності від локалі. Тоді я якось не забув про функцію sprintf, що спеціально призначеня для форматного виведення данних. І тим паче забув (та що й казати, навіть і ніколи не звертав уваги) про те, що для виведення дійсних чисел є два формати %f та %F. Різниця в тому, що перший залежить від встановленої в скрипті локалі, а другий ні.
Так що, вдалого написання коду. Дякую за увагу!&lt;img src="http://feeds.feedburner.com/~r/GrandsesBlog/~4/C7ARAAsNf4M" height="1" width="1"/&gt;</description>
		<link>http://feedproxy.google.com/~r/GrandsesBlog/~3/C7ARAAsNf4M/83</link>
		<pubDate>Thu, 10 Sep 2009 08:56:56 +0300</pubDate>
		<dc:creator>grandse</dc:creator>
	<feedburner:origLink>http://grandse.org.ua/messages/show/83</feedburner:origLink></item>
	<item>
		<title>Соціальна ІТ спільнота розробників</title>
		<description>Останнім часом звернув своб увагу на соціальну спільноту &lt;a href="rozrobka.com" title="Головна сторінка скоціальнохї спільноти розробників"&gt;Rozrobka.com&lt;/a&gt;. Зараєстрований я там доволі давно, після коментаря на моєму блозі пана &lt;a href="http://www.zenyk.com" title="Його персональний блог"&gt;Zenuk&lt;/a&gt;. Але тоді чомусь тільки трошки пробіг очима по першій сторінці і далі не став читати. Відштовхнула схожість проекту на всім відомий Хабрахабр. 
Однак останнім часом я змінив свою думку, перечитав всі цікаві особисто мені матераіли на Розробці, привів до ладу свій профіль та потрошку викладаю посилання з описами на свої матеріали. Можливо треба було б викладати матеріали повністю, але мені відверто кажучи лінь це робити.
Нажаль ктивність доволі низька, хоча наскільки я розумію відвідувачів постійних там немало і раніше писали активніше. Невистачає авторів, які б постійно писали матеріали, заохочуючи туди нових відвідувачів. Думаю, що з цим то боротись можна, а проект достойний того, щоб люди туди приходили та залишались там. Так що, якщо Вам є що сказати, якщо Вас цікавить така тематика, то пропоную Вам приєднуватись до &lt;a href="http://rozrobka.com" title="Головна сторінка"&gt;Соціальної ІТ спільноти&lt;/a&gt;.&lt;img src="http://feeds.feedburner.com/~r/GrandsesBlog/~4/YE_0p6GGvuM" height="1" width="1"/&gt;</description>
		<link>http://feedproxy.google.com/~r/GrandsesBlog/~3/YE_0p6GGvuM/82</link>
		<pubDate>Sun, 06 Sep 2009 08:16:40 +0300</pubDate>
		<dc:creator>grandse</dc:creator>
	<feedburner:origLink>http://grandse.org.ua/messages/show/82</feedburner:origLink></item>
	<item>
		<title>Порівняння швидкодії БД (PostgreSQL, MySQL, SQLite)</title>
		<description>Думаю, найбільш популярною на сьогодняшній день серед розробників є БД &lt;a href="http://mysql.com" title="Головна сторінка проекту MySQL"&gt;MySQL&lt;/a&gt;. Досить простий у використанні, розповсюджений серед хостерів, більшість CMS орієнтовані саме на нього, а починаючи з 5ої версії в цій системі з'явилось багато можливостей, притаманних корпоративним БД. І список можливостей продовжує рости. Розробники та прихильники MySQL досить часто стверджують, що він дуже швидкий, гнучкий та здатний до масштабування. 
Однак, не дивлячись на те з MySQL все так добре та чудово, саме&lt;a href="http://postgresql.org" title="Головна сторінка проекту PostgreSQL"&gt;PostgreSQL&lt;/a&gt; використовується для збереження данних в більшості великих соціальних мереж, саме нею скористалось Yahoo! для побудови &lt;a href="http://habrahabr.ru/blogs/postgresql/26289/" title="Замітка про найбільшу в світі БД"&gt;найбільшої в світі БД&lt;/a&gt;. Цікаво, що прихильники PostgreSQL з гордістю кажуть, що їх улюбленець значно швидший за MySQL, ну а його прихильники в свою чергу доволі часто кажуть протилежне.
А тут ще &lt;a href="http://habrahabr.ru/blogs/postgresql/26289/" title="Головна сторінка проекту SQLite"&gt;SQLite&lt;/a&gt;, що займає доволі потужні позиції в desktop-орієнтованому софті, а ще дехто каже про чудові можливості Firebird. І це тільки найпопулярніші з opensource баз данних. 
Втомившись читати купу різних порівнянь, вирішив провести для себе самого ряд простих тестів та нашвидкоруч оцінити як поводять себе деякі БД.&lt;!-----more-----!&gt;
&lt;h3&gt;"Методика" тестування&lt;/h3&gt;
Мій спосіб перевірки швидкодії не претендує ні на звання "серйозної методики". Мене просто цікавило, скільки запитів того чи іншого виляду витримає та чи інша БД. Причому для тесту було обрано найпримітивніші запити на запис та читання. Це синтетичний тест, погляд лише з одного боку. Однак чим є БД за своєю суттю? Інструментом для збереження данних. А які операції є основними? Читання та запис. Тому не дивлячись на свою синтетичність, леяку інформацію про БД він надає.
Оскільки в мене завжди на робочому місті встановлено MySQL та PostgreSQL, а для роботи з SQLite практично ніякого додаткого інструментарію непотрібно, то для тесту було обрано саме ці БД. Причому ніякої додаткової конфігурації ні для MySQL ні для PostgreSQL не проводилось. Хоча б тому що я не вважаю себе знавцем тонкого налаштування серверів БД і мої маніпуляції могли б зробити тільки гірше.
Для уніфікації тестів для кожної з БД я написав ось такий абстрактний клас, що є прототипом для тесту БД:
&lt;code class="code-block"&gt;
/**
 * Abstract class for test running
 */
abstract class AbstractDatabaseTest {
	protected $connection;
	/**
	 * Creates test suite, connect to database and create table
	 */
	public function __construct($host, $login, $password, $database) {
		$this-&amp;gt;connect($host, $login, $password, $database);
		$this-&amp;gt;createTable();
	}
	/**
	 * Close drop table
	 */
	public function __destruct() {
		$this-&amp;gt;query('DROP TABLE test');
		$this-&amp;gt;disconnect();
	}
	/**
	 * Abstract function for query executing
	 */
	public abstract function query($query);
	/**
	 * Abstract function fot table creation
	 */
	public abstract function createTable();
	/**
	 * Abstract method for creating database connection
	 */
	public abstract function connect($host, $login, $password, $database);
	/**
	 * Abstract method for closing connection
	 */
	public abstract function disconnect();
	/**
	 * Method for running test
	 */
	public function runTest($query, $times) {
		for ($i = 0; $i &amp;lt; $times; $i++)
			$this-&amp;gt;query(preg_replace("/[\?]/", $i, $query));
	}
	/**
	 * Create single query for all requests
	 */
	public function runSingleTest($query, $times, $exec = true) {
		$q = '';
		for ($i = 0; $i &amp;lt; $times; $i++)
			$q .= preg_replace("/[\?]/", $i, $query).";\n";
		if ($exec)
			$this-&amp;gt;query($q, $i);
		else
			return $q;
	}
	/**
	 * Run test as transaction
	 */
	public function runTransactionTest($query, $times) {
		$q = 'BEGIN;'."\n";
		$q .= $this-&amp;gt;runSingleTest($query, $times, false);
		$q .= 'COMMIT;';
	}
}
&lt;/code&gt;
Цей класс декларує конструктор та деструктор, що в свою чергу звертаються до абстрактний функцій &lt;code&gt;connect()&lt;/code&gt; та &lt;code&gt;disconnect()&lt;/code&gt;, які мають бути перевантажені в нащадках з метою отримання підключення до серверу БД та його безпечного закриття. Також предокларовані методи для виконання тестів (&lt;code&gt;runTest()&lt;/code&gt;, &lt;code&gt;runSingleTest()&lt;/code&gt;, &lt;code&gt;tunTransactionTest()&lt;/code&gt;), які будуть звертатись до низькорівневих функцій роботи з підключенням через абстрактний метод &lt;code&gt;query()&lt;/code&gt;. Ну і залишається ще одна функція, що створює тестову таблицю.
Тоді для тесту наприклад &lt;strong&gt;PostgreSQL&lt;/strong&gt; достатньо описати такий клас:
&lt;code class="code-block"&gt;
/**
 * Class for working with PostgreSQL database
 */
class PostgreSQLTest extends AbstractDatabaseTest {
	/**
	 * Creates database object
	 */
	public function connect($host, $login, $password, $database) {
		$this-&amp;gt;connection = pg_connect('host='.$host.' dbname='.$database.' user='.$login.' password='.$password)
			or die('Could not connect to database: '.pg_last_error($this-&amp;gt;connection));
	}
	/**
	 * Close connection
	 */
	public function disconnect() {
		pg_close($this-&amp;gt;connection);
	}
	/**
	 * Create database table
	 */
	public function createTable() {
		pg_query($this-&amp;gt;connection, 'CREATE TABLE test
		(
		  id serial,
		  "name" character varying(255),
		  o integer,
		  CONSTRAINT id_k PRIMARY KEY (id)
		)');
	}
	/**
	 * Execute query
	 * @param	query	query for execution
	 */
	public function query($query) {
		pg_query($this-&amp;gt;connection, $query) 
			or die('Could not execute query: '.pg_last_error($this-&amp;gt;connection)."\n".$query."\n");
	}
}
&lt;/code&gt;
Як можна побачити, для тестування створюється таблиця, що містить три поля: &lt;var&gt;id&lt;/var&gt; (ключ), &lt;var&gt;name&lt;/var&gt; (текстове поле), &lt;var&gt;o&lt;/var&gt; (цілочислене поле). Таким чином можна протестувати вибірки за різним типом порівнянь. Аналогічні класи я створив для &lt;strong&gt;SQLite&lt;/strong&gt; та два для &lt;strong&gt;MySQL&lt;/strong&gt; (&lt;strong&gt;InnoDB&lt;/strong&gt; та &lt;strong&gt;MyISAM&lt;/strong&gt;). 
Тепер сам тест:
&lt;code class="code-block"&gt;
&amp;lt;/?php
**
&amp;gt;runSingleTest($query, $times);
			} elseif ($testType == 'trans') {
				$msg = ' in transaction';
				$test-&amp;gt;runTransactionTest($query, $times);
			} else {
				$test-&amp;gt;runTest($query, $times);
			}
			$end = microtime(true);
			$summary += ($end-$begin);
		}
		// Put time
		$summary = $summary/$iters;	// Try to get average time
		echo $type, ' (', $times, ' times'.$msg.') takes ', "\t\t", $summary, " sec.\n";
		return array($type =&amp;gt; $summary);
	}
	/**
	 * Static method for test running
	 */
	public static function run($className, $host, $user, $pass, $database, $count = 1000) {
		$results = array();
		// Usual test
		$test = new $className($host, $user, $pass, $database);
		$results[] = self::singleTest($test, 'INSERT INTO test (name, o) VALUES (\'record?\', ?)', $count, 'INSERT');
		$results[] = self::singleTest($test, 'SELECT * FROM test WHERE id = ?', $count, 'SELECT ID');
		$results[] = self::singleTest($test, 'SELECT * FROM test WHERE o = ?', $count, 'SELECT INT');
		$results[] = self::singleTest($test, 'SELECT * FROM test WHERE name = \'record?\'', $count, 'SELECT TEXT');
		$results[] = self::singleTest($test, 'SELECT * FROM test WHERE name like \'%?\'', $count, 'SELECT LIKE');
		$results[] = self::singleTest($test, 'DELETE FROM test WHERE id = ?', $count, 'DELETE');
		unset($test);
		// Prepare for output
		$prepared = array();
		foreach ($results as $res) 
			foreach ($res as $key =&amp;gt; $value)
				$prepared[$key] = $value;
		return $prepared;
	}
}
&amp;lt; * Class for running tests
 */
class Test {
	/**
	 * Test for running multiply-query, signle-query and transtactional test on demand
	 * @param	test	test connection
	 * @param	query	query fo execution
	 * @param	times	how many requests will be transmitted
	 * @param	type	additional message about request type
	 */
	public static function singleTest(AbstractDatabaseTest $test, $query, $times, $type, $testType = 'usual') {
		// Select iterations count
		/* TODO: uncomment it later
		if ($times = 1000) 
			$iters = 50;
		elseif ($items = 5000)
			$iters = 10;
		else */
			$iters = 1;
		// Evaluate time
		$summary = 0;
		for ($i = 0; $i  $iters; $i++) {
			$begin = microtime(true);
			$msg = '';
			if ($testType == 'single') {
				$msg = ' in single query';
				$test-/code&amp;gt;
Як видно з коду спочатку виконуються вставки в БД, потім вибірки за ключем, цілочисельним полем, повним порівнянням текстового поля та шаблонним порівнянням через &amp;lt;strong&amp;gt;like&amp;lt;/strong&amp;gt;.
Для збірки всього до купи, читання конфігураційного файлу та збереження результатів у вигляді html було написано ось такий код:
&amp;lt;code class="code-block"&amp;gt;
// Get configuration file
$config = (isset($args[1]))? $args[1]: 'config.conf';
if (!file_exists($config))
	die('Error:'."\t".'file '.$config.' does not exists'."\n");
// Read configuration
$configuration = array();
if (!($file = fopen($config, 'r')))
	die('Error:'."\t".'can open config file for reading: '.$config."\n");
while ($parsed = fgetcsv($file, 1000, '=')) {
	// Check format
	if (count($parsed) != 2) {
		var_dump($parsed);
		die('Error:'."\t".'wrong configuration file format '."\n");
	}
	// Parse line
	if (preg_match("/,/", $parsed[1])) 
		$vals = explode(",", $parsed[1]);
	else
		$vals = array($parsed[1]);
	// Add parameters
	if (!isset($configuration[$parsed[0]]))
		$configuration[$parsed[0]] = array();
	foreach ($vals as $val)
		$configuration[$parsed[0]][] = $val;
}
fclose($file);
// Load classes
include 'lib/abstract.php';
include 'lib/test.php';
foreach ($configuration['libs'] as $lib) {
	include 'lib/'.$lib.'.php';
}
// Output file
$result = (isset($args[2]))? $args[2]: 'results.html';
$output = fopen($result, "w");
// Counts
fwrite($output, '
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
	&amp;lt;title&amp;gt;Database test&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
	&amp;lt;h1&amp;gt;Database test&amp;lt;/h1&amp;gt;
	'
);
$types = array('INSERT', 'SELECT ID', 'SELECT INT', 'SELECT TEXT', 'SELECT LIKE', 'DELETE');
foreach ($configuration['count'] as $count) {
	fwrite($output, '&amp;lt;h2&amp;gt;'.$count.' items&amp;lt;/h2&amp;gt;
	&amp;lt;table&amp;gt;
		&amp;lt;tr&amp;gt;
			&amp;lt;th&amp;gt; &amp;lt;/th&amp;gt;
');
	$results = array();
	foreach ($configuration['database'] as $db) {
		fwrite($output, "\t\t\t&amp;lt;th&amp;gt;$db&amp;lt;/th&amp;gt;\n");
		$results[$db] = Test::run($db, $configuration['host'][0], $configuration['user'][0], 
						$configuration['pass'][0], $configuration['db'][0], $count);
	}
	fwrite($output, "\t\t&amp;lt;/tr&amp;gt;\n");
	foreach ($types as $type) {
		fwrite($output, "\t\t&amp;lt;tr&amp;gt;\n\t\t\t&amp;lt;td&amp;gt;$type&amp;lt;/td&amp;gt;\n");
		foreach ($configuration['database'] as $db) 
			fwrite($output, "\t\t\t&amp;lt;td&amp;gt;".$results[$db][$type]."&amp;lt;/td&amp;gt;\n");
		fwrite($output, "\t\t&amp;lt;/tr&amp;gt;\n");
	}
	fwrite($output, "\t&amp;lt;/table&amp;gt;\n");
}
fwrite($output, '&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;');
&lt;/code&gt;
Настав час погратись.
&lt;h3&gt;Результати тестів&lt;/h3&gt;
Спочатку я провів тестування на старенькій машинці з Amd Sempron 2500+ 1 Gb DDR з файловою системою raiserfs під управлінням Ubuntu 9.04 для 1000, 5000 та 10000 елементів. Отримав такі результати:
&lt;h4&gt;1000 items&lt;/h4&gt;
&lt;table&gt;
	&lt;tr&gt;
		&lt;th&gt; &lt;/th&gt;
		&lt;th&gt;SQLiteTest&lt;/th&gt;
		&lt;th&gt;MySQLTestIsam&lt;/th&gt;
		&lt;th&gt;MySQLTestInno&lt;/th&gt;
		&lt;th&gt;PostgreSQLTest&lt;/th&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;INSERT&lt;/td&gt;
		&lt;td&gt;8.46631503105&lt;/td&gt;
		&lt;td&gt;0.182038068771&lt;/td&gt;
		&lt;td&gt;0.922454833984&lt;/td&gt;
		&lt;td&gt;1.3453848362&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;SELECT ID&lt;/td&gt;
		&lt;td&gt;0.140038013458&lt;/td&gt;
		&lt;td&gt;0.401347875595&lt;/td&gt;
		&lt;td&gt;0.375024080276&lt;/td&gt;
		&lt;td&gt;1.090269804&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;SELECT INT&lt;/td&gt;
		&lt;td&gt;0.825850963593&lt;/td&gt;
		&lt;td&gt;1.01993608475&lt;/td&gt;
		&lt;td&gt;1.38959908485&lt;/td&gt;
		&lt;td&gt;1.50171399117&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;SELECT TEXT&lt;/td&gt;
		&lt;td&gt;0.847486019135&lt;/td&gt;
		&lt;td&gt;1.13318300247&lt;/td&gt;
		&lt;td&gt;1.46317386627&lt;/td&gt;
		&lt;td&gt;1.63713502884&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;SELECT LIKE&lt;/td&gt;
		&lt;td&gt;1.00699400902&lt;/td&gt;
		&lt;td&gt;1.10880994797&lt;/td&gt;
		&lt;td&gt;1.43363499641&lt;/td&gt;
		&lt;td&gt;1.72174596786&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;DELETE&lt;/td&gt;
		&lt;td&gt;8.6017370224&lt;/td&gt;
		&lt;td&gt;0.277512073517&lt;/td&gt;
		&lt;td&gt;1.48435211182&lt;/td&gt;
		&lt;td&gt;1.46303582191&lt;/td&gt;
	&lt;/tr&gt;
&lt;/table&gt;
&lt;h4&gt;5000 items&lt;/h4&gt;
&lt;table&gt;
	&lt;tr&gt;
		&lt;th&gt; &lt;/th&gt;
		&lt;th&gt;SQLiteTest&lt;/th&gt;
		&lt;th&gt;MySQLTestIsam&lt;/th&gt;
		&lt;th&gt;MySQLTestInno&lt;/th&gt;
		&lt;th&gt;PostgreSQLTest&lt;/th&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;INSERT&lt;/td&gt;
		&lt;td&gt;48.9740638733&lt;/td&gt;
		&lt;td&gt;0.897409915924&lt;/td&gt;
		&lt;td&gt;4.29658889771&lt;/td&gt;
		&lt;td&gt;6.77726101875&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;SELECT ID&lt;/td&gt;
		&lt;td&gt;0.521908998489&lt;/td&gt;
		&lt;td&gt;1.68041110039&lt;/td&gt;
		&lt;td&gt;1.66038298607&lt;/td&gt;
		&lt;td&gt;5.13580799103&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
	&lt;td&gt;SELECT INT&lt;/td&gt;
		&lt;td&gt;18.8576068878&lt;/td&gt;
		&lt;td&gt;17.7570590973&lt;/td&gt;
		&lt;td&gt;26.9309771061&lt;/td&gt;
		&lt;td&gt;16.0441360474&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;SELECT TEXT&lt;/td&gt;
		&lt;td&gt;18.7875778675&lt;/td&gt;
		&lt;td&gt;19.5907330513&lt;/td&gt;
		&lt;td&gt;28.7952878475&lt;/td&gt;
		&lt;td&gt;19.5119550228&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;SELECT LIKE&lt;/td&gt;
		&lt;td&gt;23.2714881897&lt;/td&gt;
		&lt;td&gt;19.961373806&lt;/td&gt;
		&lt;td&gt;28.3384401798&lt;/td&gt;
		&lt;td&gt;20.7058980465&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;DELETE&lt;/td&gt;
		&lt;td&gt;44.6153440475&lt;/td&gt;
		&lt;td&gt;1.21550679207&lt;/td&gt;
		&lt;td&gt;7.36333107948&lt;/td&gt;
		&lt;td&gt;7.01828098297&lt;/td&gt;
	&lt;/tr&gt;
&lt;/table&gt;
&lt;h4&gt;10000 items&lt;/h4&gt;
&lt;table&gt;
	&lt;tr&gt;
		&lt;th&gt; &lt;/th&gt;
		&lt;th&gt;SQLiteTest&lt;/th&gt;
		&lt;th&gt;MySQLTestIsam&lt;/th&gt;
		&lt;th&gt;MySQLTestInno&lt;/th&gt;
		&lt;th&gt;PostgreSQLTest&lt;/th&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;INSERT&lt;/td&gt;
		&lt;td&gt;96.4258060455&lt;/td&gt;
		&lt;td&gt;1.78123998642&lt;/td&gt;
		&lt;td&gt;8.63258886337&lt;/td&gt;
		&lt;td&gt;13.5855839252&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;SELECT ID&lt;/td&gt;
		&lt;td&gt;0.983808994293&lt;/td&gt;
		&lt;td&gt;3.49806404114&lt;/td&gt;
		&lt;td&gt;3.36594605446&lt;/td&gt;
		&lt;td&gt;10.2117769718&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;SELECT INT&lt;/td&gt;
		&lt;td&gt;78.3077938557&lt;/td&gt;
		&lt;td&gt;65.505810976&lt;/td&gt;
		&lt;td&gt;95.4381818771&lt;/td&gt;
		&lt;td&gt;53.9780220985&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;SELECT TEXT&lt;/td&gt;
		&lt;td&gt;72.8628439903&lt;/td&gt;
		&lt;td&gt;103.212995052&lt;/td&gt;
		&lt;td&gt;71.3834159374&lt;/td&gt;
		&lt;td&gt;69.0468411446&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;SELECT LIKE&lt;/td&gt;
		&lt;td&gt;89.72281003&lt;/td&gt;
		&lt;td&gt;107.325515985&lt;/td&gt;
		&lt;td&gt;72.6874220371&lt;/td&gt;
		&lt;td&gt;71.0193040371&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;DELETE&lt;/td&gt;
		&lt;td&gt;88.7086949348&lt;/td&gt;
		&lt;td&gt;2.29328393936&lt;/td&gt;
		&lt;td&gt;15.0134780407&lt;/td&gt;
		&lt;td&gt;13.882789135&lt;/td&gt;
	&lt;/tr&gt;
&lt;/table&gt;
Цього мені виявилось замало, і я вирішив спробувати провести для більшої кількості елементів. Хоча б ще для 50 та 100 тис. Оскільки тести на такій кількості елементів займають доволі багато часу, то їх я провів на іншій машині: Intel Atom, 1 Gb DDR2, ext3 і тією самою Ubuntu 9.04 з ядром 2.6.30. І отримав ось такі результати:
	&lt;h4&gt;1000 items&lt;/h4&gt;
	&lt;table&gt;
		&lt;tr&gt;
			&lt;th&gt; &lt;/th&gt;
			&lt;th&gt;SQLiteTest&lt;/th&gt;
			&lt;th&gt;MySQLTestIsam&lt;/th&gt;
			&lt;th&gt;MySQLTestInno&lt;/th&gt;
			&lt;th&gt;PostgreSQLTest&lt;/th&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;INSERT&lt;/td&gt;
			&lt;td&gt;3.42623877525&lt;/td&gt;
			&lt;td&gt;0.485679149628&lt;/td&gt;
			&lt;td&gt;1.01362395287&lt;/td&gt;
			&lt;td&gt;1.63590288162&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;SELECT ID&lt;/td&gt;
			&lt;td&gt;0.519760131836&lt;/td&gt;
			&lt;td&gt;1.09079289436&lt;/td&gt;
			&lt;td&gt;0.573807001114&lt;/td&gt;
			&lt;td&gt;1.17772603035&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;SELECT INT&lt;/td&gt;
			&lt;td&gt;2.56960511208&lt;/td&gt;
			&lt;td&gt;2.94936800003&lt;/td&gt;
			&lt;td&gt;2.23329114914&lt;/td&gt;
			&lt;td&gt;1.9480099678&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;SELECT TEXT&lt;/td&gt;
			&lt;td&gt;2.33323192596&lt;/td&gt;
			&lt;td&gt;3.13713407516&lt;/td&gt;
			&lt;td&gt;2.38640213013&lt;/td&gt;
			&lt;td&gt;2.154296875&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;SELECT LIKE&lt;/td&gt;
			&lt;td&gt;2.18005108833&lt;/td&gt;
			&lt;td&gt;2.80790996552&lt;/td&gt;
			&lt;td&gt;2.43208599091&lt;/td&gt;
			&lt;td&gt;2.29287910461&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;DELETE&lt;/td&gt;
			&lt;td&gt;3.46106314659&lt;/td&gt;
			&lt;td&gt;0.81547498703&lt;/td&gt;
			&lt;td&gt;0.937149047852&lt;/td&gt;
			&lt;td&gt;1.56592297554&lt;/td&gt;
		&lt;/tr&gt;
	&lt;/table&gt;
&lt;h4&gt;5000 items&lt;/h4&gt;
	&lt;table&gt;
		&lt;tr&gt;
			&lt;th&gt; &lt;/th&gt;
			&lt;th&gt;SQLiteTest&lt;/th&gt;
			&lt;th&gt;MySQLTestIsam&lt;/th&gt;
			&lt;th&gt;MySQLTestInno&lt;/th&gt;
			&lt;th&gt;PostgreSQLTest&lt;/th&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;INSERT&lt;/td&gt;
			&lt;td&gt;16.860270977&lt;/td&gt;
			&lt;td&gt;3.05509400368&lt;/td&gt;
			&lt;td&gt;4.44511294365&lt;/td&gt;
			&lt;td&gt;8.22729110718&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;SELECT ID&lt;/td&gt;
			&lt;td&gt;1.90315794945&lt;/td&gt;
			&lt;td&gt;3.12027382851&lt;/td&gt;
			&lt;td&gt;2.6133749485&lt;/td&gt;
			&lt;td&gt;6.00253605843&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;SELECT INT&lt;/td&gt;
			&lt;td&gt;35.5371229649&lt;/td&gt;
			&lt;td&gt;30.0901350975&lt;/td&gt;
			&lt;td&gt;44.3228580952&lt;/td&gt;
			&lt;td&gt;25.8845059872&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;SELECT TEXT&lt;/td&gt;
			&lt;td&gt;32.2936480045&lt;/td&gt;
			&lt;td&gt;33.9316368103&lt;/td&gt;
			&lt;td&gt;48.9920039177&lt;/td&gt;
			&lt;td&gt;29.3765230179&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;SELECT LIKE&lt;/td&gt;
			&lt;td&gt;41.3553678989&lt;/td&gt;
			&lt;td&gt;35.6514999866&lt;/td&gt;
			&lt;td&gt;49.3500881195&lt;/td&gt;
			&lt;td&gt;33.2532720566&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;DELETE&lt;/td&gt;
			&lt;td&gt;17.458812952&lt;/td&gt;
			&lt;td&gt;2.04428100586&lt;/td&gt;
			&lt;td&gt;5.16926288605&lt;/td&gt;
			&lt;td&gt;8.14243984222&lt;/td&gt;
		&lt;/tr&gt;
	&lt;/table&gt;
&lt;h4&gt;10000 items&lt;/h4&gt;
	&lt;table&gt;
		&lt;tr&gt;
			&lt;th&gt; &lt;/th&gt;
			&lt;th&gt;SQLiteTest&lt;/th&gt;
			&lt;th&gt;MySQLTestIsam&lt;/th&gt;
			&lt;th&gt;MySQLTestInno&lt;/th&gt;
			&lt;th&gt;PostgreSQLTest&lt;/th&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;INSERT&lt;/td&gt;
			&lt;td&gt;35.5141940117&lt;/td&gt;
			&lt;td&gt;5.62814283371&lt;/td&gt;
			&lt;td&gt;8.56925797462&lt;/td&gt;
			&lt;td&gt;16.4258570671&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;SELECT ID&lt;/td&gt;
			&lt;td&gt;3.68663096428&lt;/td&gt;
			&lt;td&gt;7.88118100166&lt;/td&gt;
			&lt;td&gt;5.21180105209&lt;/td&gt;
			&lt;td&gt;11.7483720779&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;SELECT INT&lt;/td&gt;
			&lt;td&gt;130.809201956&lt;/td&gt;
			&lt;td&gt;116.798990965&lt;/td&gt;
			&lt;td&gt;169.440524817&lt;/td&gt;
			&lt;td&gt;90.0897068977&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;SELECT TEXT&lt;/td&gt;
			&lt;td&gt;125.267574072&lt;/td&gt;
			&lt;td&gt;128.374459982&lt;/td&gt;
			&lt;td&gt;188.359474897&lt;/td&gt;
			&lt;td&gt;105.278270006&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;SELECT LIKE&lt;/td&gt;
			&lt;td&gt;160.688622952&lt;/td&gt;
			&lt;td&gt;135.743841887&lt;/td&gt;
			&lt;td&gt;188.13747716&lt;/td&gt;
			&lt;td&gt;117.56459713&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;DELETE&lt;/td&gt;
			&lt;td&gt;34.9218218327&lt;/td&gt;
			&lt;td&gt;3.94423508644&lt;/td&gt;
			&lt;td&gt;10.6728608608&lt;/td&gt;
			&lt;td&gt;16.3871839046&lt;/td&gt;
		&lt;/tr&gt;
	&lt;/table&gt;
&lt;h4&gt;50000 items&lt;/h4&gt;
	&lt;table&gt;
		&lt;tr&gt;
			&lt;th&gt; &lt;/th&gt;
			&lt;th&gt;SQLiteTest&lt;/th&gt;
			&lt;th&gt;MySQLTestIsam&lt;/th&gt;
			&lt;th&gt;MySQLTestInno&lt;/th&gt;
			&lt;th&gt;PostgreSQLTest&lt;/th&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;INSERT&lt;/td&gt;
			&lt;td&gt;186.580451012&lt;/td&gt;
			&lt;td&gt;27.5768818855&lt;/td&gt;
			&lt;td&gt;42.6071000099&lt;/td&gt;
			&lt;td&gt;78.3421108723&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;SELECT ID&lt;/td&gt;
			&lt;td&gt;17.9766161442&lt;/td&gt;
			&lt;td&gt;38.9252171516&lt;/td&gt;
			&lt;td&gt;25.6919820309&lt;/td&gt;
			&lt;td&gt;55.8121678829&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;SELECT INT&lt;/td&gt;
			&lt;td&gt;3373.11729312&lt;/td&gt;
			&lt;td&gt;2714.17641687&lt;/td&gt;
			&lt;td&gt;4097.4976809&lt;/td&gt;
			&lt;td&gt;1971.63789201&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;SELECT TEXT&lt;/td&gt;
			&lt;td&gt;3319.83222294&lt;/td&gt;
			&lt;td&gt;3077.92565894&lt;/td&gt;
			&lt;td&gt;4539.87472916&lt;/td&gt;
			&lt;td&gt;2321.9553771&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;SELECT LIKE&lt;/td&gt;
			&lt;td&gt;4336.35408092&lt;/td&gt;
			&lt;td&gt;3266.41011596&lt;/td&gt;
			&lt;td&gt;4621.69911408&lt;/td&gt;
			&lt;td&gt;2826.56725407&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;DELETE&lt;/td&gt;
			&lt;td&gt;167.727796078&lt;/td&gt;
			&lt;td&gt;19.2260329723&lt;/td&gt;
			&lt;td&gt;47.8846509457&lt;/td&gt;
			&lt;td&gt;79.9886329174&lt;/td&gt;
		&lt;/tr&gt;
	&lt;/table&gt;
&lt;h4&gt;100000 items&lt;/h4&gt;
	&lt;table&gt;
		&lt;tr&gt;
			&lt;th&gt; &lt;/th&gt;
			&lt;th&gt;SQLiteTest&lt;/th&gt;
			&lt;th&gt;MySQLTestIsam&lt;/th&gt;
			&lt;th&gt;MySQLTestInno&lt;/th&gt;
			&lt;th&gt;PostgreSQLTest&lt;/th&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;INSERT&lt;/td&gt;
			&lt;td&gt;380.183732986&lt;/td&gt;
			&lt;td&gt;57.4253020287&lt;/td&gt;
			&lt;td&gt;87.9900531769&lt;/td&gt;
			&lt;td&gt;154.548943996&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;SELECT ID&lt;/td&gt;
			&lt;td&gt;37.0985400677&lt;/td&gt;
			&lt;td&gt;65.9570541382&lt;/td&gt;
			&lt;td&gt;54.8159809113&lt;/td&gt;
			&lt;td&gt;114.572535038&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;SELECT INT&lt;/td&gt;
			&lt;td&gt;13562.0152919&lt;/td&gt;
			&lt;td&gt;11314.5157359&lt;/td&gt;
			&lt;td&gt;19920.4457819&lt;/td&gt;
			&lt;td&gt;8018.85069585&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;SELECT TEXT&lt;/td&gt;
			&lt;td&gt;15053.0083721&lt;/td&gt;
			&lt;td&gt;12835.214098&lt;/td&gt;
			&lt;td&gt;21606.1742871&lt;/td&gt;
			&lt;td&gt;9508.44170213&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;SELECT LIKE&lt;/td&gt;
			&lt;td&gt;19123.6498909&lt;/td&gt;
			&lt;td&gt;13622.2726769&lt;/td&gt;
			&lt;td&gt;18889.7097459&lt;/td&gt;
			&lt;td&gt;11153.777102&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;DELETE&lt;/td&gt;
			&lt;td&gt;353.978772879&lt;/td&gt;
			&lt;td&gt;40.8035359383&lt;/td&gt;
			&lt;td&gt;96.7261219025&lt;/td&gt;
			&lt;td&gt;192.977801085&lt;/td&gt;
		&lt;/tr&gt;
	&lt;/table&gt;
&lt;h3&gt;Ну і хто ж кращий?&lt;/h3&gt;
Перше, на чому зверну увагу: SQLite дуже швидко вибирає елементи за первинним ключем. Причому навіть при великій кількості елементів в БД. Також при обсязі записів до 5 тис. елементів його швидкодія для простих вибірок йде на рівні або навіть випереджає інши БД. Однак швидкість запису та видалення елементів є для нього не простою задачею, внаслідок того, що вся БД знаходиться в одному єдиному файлі. Однак враження від цієї БД в мене залишилось дуже гарне і я при необхідності зберігати дані в програмах, що мають працювати локально, з великим задоволенням скористаюсь цією БД. Дарма я не звернува уваги на цю БД раніше, колись би зберіг обі дуже багато часу.
Тепер про MySQL та PostgreSQL. Другий виявився повільнішим на вставках та видаленні і що не дуже приємно при виборі за первинним ключем. Повільні вставки мене не лякають, а от швидкість виборок за ключем мене дійсно не радує.
Що означає на практиці повільніша швидкість вставок? Приведу приклад в рамках абстрактного сайту: один перегляд сторінки вимагає вибрати список коментарів, список статей, список тегів, якісь налаштування, посилання, коментарі. Наприклад, CMS Joomla 1.5 виконує десь 13 запитів при перегляді головнох сторінки (жахливо, чи не так). Нехай, відвідуваність у проекту буде така як і у мого блога, тобто хочаб 10 уніків щодня, які роблять хоча б 20 переглядів. Тобто близько 250-260 запитів на вибірку до БД. А скільки коментарів залишать ці читачі? Добре, якщо 10% читачів будуть окментувати. Тобто за день середньостатистично буде 1 запит на вставку коментаря, ну і якби я писав кожного дня то зі збереженнями тексту в процесі написання вийшло б десь 10 запитів на запис. Тобто в такому ідеальному варіанті дуже активного автора та користувачів добре якщо кількість записів у бд досягне 5% від загального числа запитів. Коли запис йде швидко, то це добре, однак значно критичніше для реальних задач швидко вибирати дані.
І тут PostgreSQL тримається досить добре: на 1000 елементів він йде поряд з MySQL InndoDB, на 5000 наздоганяє MySQL MyIsam, ну а на 10 тис. починає показувати свою високу здатність витримувати навантаження. Швикдість виконання запитів вища ща відповідну у двигунця MyISAM відсотків на 20, не кажучи про InnoDB, який повільно плентається інколи відстаючи в два рази. Хочу хвернути увагу на також на те, що робота з різними типами данних відбувається в PostgreSQL по різному. За цілими числами вибірка відбувається швидше (за різницею швидкодії між MySQL та PostgreSQL).
В чому різниця між MyISAM та InnoDB. У другого можливості ширші. Тільки він дозволяє вводити в БД додаткову логіку, таку як вторинні ключі, що є на мою думку важливим для побудови серйозних проектів. Фактично InnoDB і мав би бути конкурентом для PostgreSQL. Тобто за результатами тесту MySQL в якості корпоративної БД програє за всіма параметрами PostgreSQL: функціонал обмежений, швидкість роботи нижча.
&lt;h3&gt;Що ж обрати?&lt;/h3&gt;
Думали буду кричати: "Обирайте PostgreSQL"? Не буду. Оскільки вибір цієї БД є виправданим коли кількість записів в БД сягає хоча б 10 тис на таблицю. Для якогось персонального сайту, сайту-візитки, простого не навантаженого проекту цілком вистачить MySQL, оскільки там кількість записів у БД на таблицю навряд сягне хоча б однієї тисячі записів. Та й для багатьох навантажених проетів важливими є ще багато факторів, окрім швидкодії БД (наприклад, як саме було спроектовано БД, як написано код, чи кешуються якось дані). Значно важливішим при виборі PostgreSQL є необхідність включати якусь логіку чи складні типи збереження данних на рівень БД. Тут і за швидкодією і за можливостями кращим вибором буде саме PostgreSQL.
Головне при виборі БД керуватись здоровим глуздом. Ще хочу додати два посилання на інші тести, що проводили порівняння PostgreSQL та MySQL. Першим буде &lt;a href="http://www.samag.ru/art/07.2007/07.2007_02.html" title="Блогохостинг: PostgreSQL vs. MySQL"&gt;порівняння навантаження що витримують MySQL та PostgreSQL в рамках блогохостингу&lt;/a&gt;, в якому досить детально описується і методика тестування і пояснюються результати. Ну а &lt;a href="http://tweakers.net/reviews/649/7" title="Порівняння потужних систем на роботі з різними БД"&gt;тест за кількістю конкуруючих запитів від Tweacers.net&lt;/a&gt; є досить відомим, і хоча й досить застарілим, однак добре показує як справляється з високими навантаженнями різни БД.
Дякую за увагу.&lt;img src="http://feeds.feedburner.com/~r/GrandsesBlog/~4/ebXnfhMh0IU" height="1" width="1"/&gt;</description>
		<link>http://feedproxy.google.com/~r/GrandsesBlog/~3/ebXnfhMh0IU/80</link>
		<pubDate>Thu, 03 Sep 2009 08:00:55 +0300</pubDate>
		<dc:creator>grandse</dc:creator>
	<feedburner:origLink>http://grandse.org.ua/messages/show/80</feedburner:origLink></item>
	<item>
		<title>Modev View Controller framework своїми руками</title>
		<description>Декілька днів тому з великим задоволенням прочитав статтю про &lt;a href="http://perevodik.net/ua/posts/16/" title="Паттерн Спостерігач (Observer) в PHP"&gt;паттерн Спостерігач (Observer)&lt;/a&gt;. І вирішив, що треба таки написати статтю про дуже розповсюджений зараз паттерн проектування Model View Controller (Модель Відображення Контроллер, скорочено MVC). З тих самих пір як я познайомився з ним більше двох років тому на прикладі фреймворку &lt;a href="http://rubyonrails.org/" title="Головна сторінка проекту"&gt;Ruby on Rails&lt;/a&gt;, я використовую самей цей паттерн в проектуванні бульш-менш великих проектів. І впевнений, що його використання дозволяє значно покращити зрозумілість програмного коду, спростити його рефакторинг та подальшу підтримку. Думаю, що після невеличкого приклада, всі хто ще не знайомий з цим паттерном будуть вимушені погодитись з цим.
Зараз цей паттерн використовується в CMS Joomla! та Livestreet, в .NET framework теж було додано інструментарій для роботи саме в рамках цієї ідеології, Swing для Java вимагає (наскільки це можливо) писати саме на основі такого паттерну, та ще й існує багато фреймворків, основаних на ньому: вищезгаданий Ruby on Rails, Django, Code Igniter, Zend Framework, Spring. Тому розумыння MVC є одною з необхідних речей для будь-якого програміста, що вважає себе професіоналом, оскільки дозволить значно легше розібратись з будь-яким з вищеперерахованих та ще десятків інших готових рішень.. 
В якості прикладу я напишу простенький php-фреймворк, на основі якого розроблю елементарний двигунець для блога (так-так, я "містер-оригінальність" :)) з можливістю додавати, видаляти та переглядати статті. Хоча й слово "розроблю" виглядає тут занадто пафосно ;)&lt;!-----more-----!&gt;
&lt;h3&gt;Модель (Model)&lt;/h3&gt;
Першим елементом паттерну MVC є модель. &lt;strong&gt;Модель&lt;/strong&gt; - спосіб збереження та органіації данних всередені проекту написаного в рамках паттерну MVC. Просто кажучи, це і є см набір данних та методи для роботи з цим набором, такі як зебреження та отримання інформації з БД (файлу, мережі), доступ до окремого елемента і т. п. 
Поясню детальніше на прикладі класу, що дозволяє вибирати дані з таблиці та поводить себе як проста нетипізована колекція (файл model.php):
&lt;code class="code-block"&gt;
/**
 * Class for working with data from database
 */
class Model implements Iterator {
	/** Database connection */
	protected static $con = null;
	/** Database table */
	protected $table = null;
	/** Items */
	protected $var = null;

	/* Iterator imterface methods */
	/** Go to the begining of the array */
	public function rewind() {
		return reset($this-&amp;gt;var);
	}
	/** Returns value of the current arrays element */
	public function current() {
		return current($this-&amp;gt;var);
	}
	/** Returns value of the end arrays element */
	public function end() {
		return end($this-&amp;gt;var);
	}
	/** Returns key of the current arrays element */
	public function key() {
		return key($this-&amp;gt;var);
	}
	/** Go to the next element and return it's value */
	public function next() {
		return next($this-&amp;gt;var);
	}
	/** Returns valid */
	public function valid() {
		$var = $this-&amp;gt;current() !== false;
		return $var;
	}
	/** Return count of selected elements */
	public function count() {
		return sizeof($this-&amp;gt;var);
	}
	/** Get element with specified index */
	public function get($index) {
		return (isset($this-&amp;gt;var[$index]))? $this-&amp;gt;var[$index]: null;
	}
	/** clear data */
	public function clear() {
		$this-&amp;gt;var = array();
	}

	/** 
	 * Constructor
	 */
	public function __construct($table) {
		// Create conneciton if needed
		if (self::$con == null) {
			self::$con = mysql_connect('localhost:3306', 'username', 'password')
				or die('Could not connect: '.mysql_error());
			mysql_select_db('blog');
		}
		$this-&amp;gt;table = $table;
	}
	/**
	 * Destructor
	 */
	public function __destruct() {
		if (self::$con == null) 
			mysql_close(self::$con);
	}

	/**
	 * Item's selector
	 */
	public function select($query = '') {
		// Select items
		$query = ($query and !empty($query))? $query: 'SELECT * FROM '.$this-&amp;gt;table;
		$res = mysql_query($query)
			or die('Could not select items: '.mysql_error().'&amp;lt;/br&amp;gt; on query '.$query);
		// Clear previously selected data
		$this-&amp;gt;clear();
		// Fetch selected data
		while ($this-&amp;gt;var[] = mysql_fetch_object($res)) ;
	}

}
&lt;/code&gt;
Наведений вище класс має поле &lt;var&gt;$var&lt;/var&gt;, призначене для збереження данних, та набір методів що дозволяють переміщуватись в рамках цього набору елементів. Сам масив данних заповнюється через виклик методу &lt;code&gt;select()&lt;/code&gt;, що виконує запит до бази данних і за замовчуванням обираж всі елементи з таблиці для данної моделі. Як можна побачити метод написано таким чином, що запит можна легко змінити. Саме підключення до БД виконує конструктор, який також встановлює ім'я таблиці з якох за замовчуванням обираються данні. Для початку такого набору методів цілком вистачить і монжна перейти до наступного елементу цього паттерну.
&lt;h3&gt;Представлення (View)&lt;/h3&gt;
&lt;strong&gt;Представлення&lt;/strong&gt; - спосіб яким будуть представлені дані користувачу. У випадку простого блога, це буде набір php файлів, що відповідають різним функціям системи. Наприклад для списку повідомлень в блозі (файл messages.index.php):
&lt;code class="code-block"&gt;
&amp;lt;h1&amp;gt;Messages list&amp;lt;/h1&amp;gt;

&amp;lt;?php
foreach ($GLOBALS['messages'] as $message){
?&amp;gt;
&amp;lt;h2&amp;gt;&amp;lt;?php echo $message-&amp;gt;title; ?&amp;gt;&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;?php echo $message-&amp;gt;body; ?&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;?php
}
?&amp;gt;

&amp;lt;a href="dispatcher.php?con=messages&amp;act=create"title="createnewitem"&amp;gt;Create new message&amp;lt;/a&amp;gt;
&lt;/code&gt;
Однак використання паттерну MVC не зводиться тільки но написанням web-орієнтованих проектів, тому в якості представлення можуть виступати інші класи чи функції, що наприклад будують GUI. Чи наприклад в рамках того ж web-програмування, може викорситовуватись якийсь шаблонізатор. Тому для уніфікації нашого фреймворку напишемо класс, що буде відповідати за роботу з представленнями (файл view.php):
&lt;code class="code-block"&gt;
/**
 * View class
 */
class View {
	/**
	 * Show element
	 */
	public static function show($controller, $method) {
		include $controller.'.'.$method.'.php';
	}
}
&lt;/code&gt;
Тепер замінивши метод &lt;code&gt;show()&lt;/code&gt; можна переорієнтувати фреймфорк на іншу модель відображення данних.
&lt;h3&gt;Котролер (Controller)&lt;/h3&gt;
&lt;strong&gt;Контролер&lt;/strong&gt; - частина проекту, що выдповідає за формування логіки роботи проекту. Тобто саме тут виконується операції з моделлю, та підготовка данних для представлення користувачу. Для поставленої задачі для початку вистачить ось такого простого класу (файл MessagesController.php):
&lt;code class="code-block"&gt;
class MessagesController {
	/** Model for this controller */
	private $model;
	/**
	 * Constructor
	 */
	public function __construct() {
		$this-&amp;gt;model = new Model('messages');
	}
	/**
	 * Select method
	 */
	public function index() {
		$this-&amp;gt;model-&amp;gt;select();
		$GLOBALS['messages'] = $this-&amp;gt;model;
		View::show('messages', 'index');
	}
}
&lt;/code&gt;
Наведений вище класс під час ініціалізації створює необхідну йому для роботи модель. Також він має метод для вибірки елементів з моделі, що зберігає цю вибірку в глобальну змінну та викликає метод для завантаження представлення.
&lt;h3&gt;Зберемо все до купи&lt;/h3&gt;
Залишається об'єднати всі прошарки в єдиний елемент проект. Для цього створимо файл:dispatcher.php, що буде точкою входу:
&lt;code class="code-block"&gt;
include 'model.php';
include 'view.php';
$controller = $_REQUEST['con']? $_REQUEST['con']: 'messages';
$action = $_REQUEST['act']? $_REQUEST['act']: 'index';
$class = ucfirst($controller.'Controller');
include $class.'.php';
$controller = new $class();
$controller-&amp;gt;$action();
&lt;/code&gt;
Також необхідно створити на локальному MySQL сервері користувача uasername з паролем password (чи замінити відповідні елементи в model.php), буза данних blog та таблицю messages з трьома полями: 
&lt;code class="code-block"&gt;
CREATE TABLE  `blog`.`messages` (
  `id` int(11) NOT NULL auto_increment COMMENT 'Messsage index',
  `title` varchar(255) NOT NULL COMMENT 'Message title',
  `body` text NOT NULL COMMENT 'Message text',
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM COMMENT='Table for messages'
&lt;/code&gt;
Набравши в адресному рядку браузеру щось на зразок http://localhost/dispatcher.php ми маємо побачити сторінку з одним посиланням "Create new message". Якщо перейти за цим посиланням, то отримаємо повідомлення:
&lt;code class="code-block"&gt; 
Warning: View::include(messages.create.php) [view.include]: failed to open stream: No such file or directory in /var/www/grandse/view.php on line 10

Warning: View::include() [function.include]: Failed opening 'messages.create.php' for inclusion (include_path='.:/usr/share/php:/usr/share/pear') in /var/www/grandse/view.php on line 10
&lt;/code&gt;
Так.. До цього повернемось трошки пізінше, а поки що змусимо наш проект хоча б щось нам показувати. Для цього достатньо лише додати запис в таблицю БД. І цього вже буде достатньо для перегляду списку повідомлень. А от з додаванням нового допису доведеться ще попрацювати.
&lt;h3&gt;Розширюємо функціонал&lt;/h3&gt;
Створимо файл messages.crate.php такого змісту:
&lt;code class="code-block"&gt;
&amp;lt;h1&amp;gt;Create new message&amp;lt;/h1&amp;gt;

&amp;lt;form method="post"&amp;gt;
	&amp;lt;input type="hidden"name="con"value="messages"/&amp;gt;
	&amp;lt;input type="hidden"name="act"value="save"/&amp;gt;
	&amp;lt;label for="title"&amp;gt;Title:&amp;lt;/label&amp;gt;
	&amp;lt;input type="text"name="title"value=""/&amp;gt; &amp;lt;/br&amp;gt;
	&amp;lt;label for="body"&amp;gt;Message body:&amp;lt;/label&amp;gt;
	&amp;lt;textarea name="body"&amp;gt;&amp;lt;/textarea&amp;gt; &amp;lt;/br&amp;gt;
	&amp;lt;input type="submit"value="Addmessage"/&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;
Та додамо функцію в контролер:
&lt;code class="code-block"&gt;
	public function create() {
		View::show('messages', 'create');
	}
&lt;/code&gt;
Тепер якщо перейти за посиланням після списку повідомлень, то побачимо форму додавання нового запису. Однак, форма хоч і є, однак додавання повідомлення не спрацює. Тому треба знову ж таки додати необхідний метод в контролер, цього разу для внесення змін:
&lt;code class="code-block"&gt;
	public function save() {
		$this-&amp;gt;model-&amp;gt;insert(array('title' =&amp;gt; $_REQUEST['title'], 'body' =&amp;gt; $_REQUEST['body']));
		$this-&amp;gt;index();
	}
&lt;/code&gt;
Якщо поглянути на клас Model, то можна побачити, що методу insert модель не має. Тому його необхідно створити. Одразу додамо в модель функціонал і для видалення елементів:
&lt;code class="code-block"&gt;
	public function insert(Array $item) {
		$query = 'INSERT INTO '.$this-&amp;gt;table.' (';
		$values = ') vALUES (';
		$is_first = true;
		foreach ($item as $name =&amp;gt; $value) {
			if (!$is_first) {
				$query .= ', ';
				$values .= ', ';
			}
			else
				$is_first = false;
			$query .= $name;
			$values .= '\''.mysql_real_escape_string($value).'\'';
		}
		mysql_query($query.$values.')')
			or die('Could not insert item: '.mysql_error());
	}
 
	public function remove($field, $value) {
		mysql_query('DELETE FROM '.$this-&amp;gt;table.' WHERE '.$field.' = \''.mysql_real_escape_string($value).'\'')
			or die('Could not remove item: '.mysql_error());
	}
&lt;/code&gt;
Ну що ж, залишається додати відповідні методи в контроллер:
&lt;code class="code-block"&gt;
	public function save() {
		$this-&amp;gt;model-&amp;gt;insert(array('title' =&amp;gt; $_REQUEST['title'], 'body' =&amp;gt; $_REQUEST['body']));
		$this-&amp;gt;index();
	} 

	public function remove() {
		if ((int)$_REQUEST['id']&amp;gt;0)
			$this-&amp;gt;model-&amp;gt;remove('id', $_REQUEST['id']);
		$this-&amp;gt;index();
	}
&lt;/code&gt;
Тепер додавання нового елементу має працювати, а от з видаленням складніше: ми не маємо посилання яке б видаляло елемент. Це легко виправити відредагувавши файл messages.index.php:
&lt;code class="code-block"&gt;
&amp;lt;h1&amp;gt;Messages list&amp;lt;/h1&amp;gt;

&amp;lt;?php
foreach ($GLOBALS['messages'] as $message){
?&amp;gt;
&amp;lt;h2&amp;gt;&amp;lt;?php echo $message-&amp;gt;title; ?&amp;gt;&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;?php echo $message-&amp;gt;body; ?&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;a href="dispatcher.php?con=messages&amp;act=remove&amp;id=?php echo $message-&amp;gt;id; ?&amp;gt;" title="Remove message"&amp;gt;Remove&amp;lt;/a&amp;gt;
&amp;lt;?php
}
?&amp;gt;

&amp;lt;a href="dispatcher.php?con=messages&amp;act=create" title="create new item"&amp;gt;Create new message&amp;lt;/a&amp;gt;
&lt;/code&gt;
&lt;h3&gt;Так просто&lt;/h3&gt;
Так. Насправді все так просто. Витративши всього годину-півтори можна написати такий от простий php framework, що буде реалізовувати паттерн Model View Controller, та дозволить швиденько писати програмний код в рамках цього паттерну. Тай паттерн сам простий і як можно побачити при розумному икористання зменшує як кількість коду необхідного для реалізації рутинних операцій так і дозволяє розділити програму на три частини: дані, логіка та взаємодія з користувачем. Це саме по собі дозволяє зробити код більш зрозумілим та прозорим, причому не тільки тому хто його писав, а ще будь-якій людині, яка витратила годину-дві (а може всього 15 хвилин :)) на те, щоб зрозуміти, що таке той звір MVC. 
Доречі, можете &lt;a href="http://grandse.org.ua/files/archives/php-framework.tar.bz2" title="Архыв з готовим прикладом"&gt;завантажити приклад&lt;/a&gt; з кінцевим варіантом описаного вище "блогового двигунці". Дякую за увагу!&lt;img src="http://feeds.feedburner.com/~r/GrandsesBlog/~4/J9Ae58b26g4" height="1" width="1"/&gt;</description>
		<link>http://feedproxy.google.com/~r/GrandsesBlog/~3/J9Ae58b26g4/81</link>
		<pubDate>Fri, 28 Aug 2009 21:08:30 +0300</pubDate>
		<dc:creator>grandse</dc:creator>
	<feedburner:origLink>http://grandse.org.ua/messages/show/81</feedburner:origLink></item>
	<item>
		<title>GROUP_CONCAT та інші дрібнички в PostgreSQL</title>
		<description>&lt;a  href="http://grandse.org.ua/messages/tag/PostgreSQL" title="Дописи помічені міткою PostgreSQL"&gt;Не вперше&lt;/a&gt; я хочу написати про &lt;a href="http://postgresql.org" title="Офіційний сайт проекту PostgreSQL"&gt;БД PostgreSQL&lt;/a&gt;. Цього разу хочу розповісти про декілька дрібничок, що можуть стати неприємною несподіванкою для людей, що довгий час працювали з &lt;a href="http://mysql.com/" title="Офіційний сайт проекту MySQL"&gt;MySQL&lt;/a&gt; та зіпсувати враження від цієї чудової БД. І ось такі дрібнички часто є причиною того, що PostgreSQL вважається деякими програмістами системою значно складнішою в роботі за MySQL. Особисто я з цим не можу погодитись - просто треба знати, як і що зробити саме тут. 
Ну що ж. Розпочнемо з простого, а там буде видно.&lt;!-----more-----!&gt;
&lt;h3&gt;Auto increment&lt;/h3&gt;
Всі хто працював з MySQL звикли до того, що всі цілочислені поля можуть заповнуватись автоматично по порядку, якщо до поля додати атрибут &lt;strong&gt;auto_increment&lt;/strong&gt;. Наприклад так:
&lt;code class="code-block"&gt;
CREATE TABLE items (
id INTEGER AUTO_INCREMENT;
);
&lt;/code&gt;
В PostgreSQL для створення таких полів присутній спеціальний тип &lt;strong&gt;serial&lt;/strong&gt;. 
&lt;code class="code-block"&gt;
CREATE TABLE items (
  id SERIAL
);
&lt;/code&gt;
Насправді така дія для PostgreSQL означає: 
&lt;ol&gt;
&lt;li&gt;Створити послідовність (&lt;strong&gt;sequence&lt;/strong&gt;) &lt;var&gt;items_id_seq&lt;/var&gt;.&lt;/li&gt;
&lt;li&gt;Створити поля &lt;var&gt;id&lt;/var&gt; з типом &lt;strong&gt;integer&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Встановити значення поля &lt;var&gt;id&lt;/var&gt; за замовчуванням рівним &lt;code&gt;nextval('items_id_seq'::regclass)&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
Що можна записати у вигляді sql-коду:
&lt;code class="code-block"&gt;
CREATE SEQUENCE items_id_seq
  INCREMENT 1
  MINVALUE 1
  MAXVALUE 9223372036854775807
  START 1
  CACHE 1;

CREATE TABLE items (
   id integer DEFAULT nextval('items_id_seq'::regclass)
);
&lt;/code&gt;
Доречі, PostgreSQL створює sequences не лише для типу serial і користуватись можна не лише для того, щоб створити auto increment поле.
&lt;h3&gt;Id останнього доданого елемента&lt;/h3&gt;
Програмісти, що довгий час працюють з MySQL звикли до того, що отримати значення поля з атрибутом auto_increment можна одним запитом: &lt;code&gt;SELECT LAST_INSERT_ID();&lt;/code&gt;. А через через php за допомогою функції &lt;strong&gt;mysql_insert_id()&lt;/strong&gt;. Аналогічна функція в PostgreSQL відсутня (так само як і функція для того ж в php). 
Однак тут на допомогу прийдуть знову ж таки послідовності:
&lt;code class="code-block"&gt;
SELECT currval('items_id_seq'::regclass);
&lt;/code&gt;
чи вибравси поле last_value з таблиці, що выдповідає за послідовність:
&lt;code class="code-block"&gt;
SELECT last_value FROM items_id_seq
&lt;/code&gt;
&lt;h3&gt;Дата та час&lt;/h3&gt;
Робота з датою та часом в PostgreSQL - досить серйозна тема, якій можна присвятити окрему статтю. Тому я зверну увагу лише на тому, чим відрізняється робота з датою та часом від роботи в MySQL. Почну з типів. Тут все практично однаково: є три типи основні типи &lt;strong&gt;DATE&lt;/strong&gt;, &lt;strong&gt;TIME&lt;/strong&gt; та збірний тип &lt;strong&gt;TIMESTAMP&lt;/strong&gt; (аналогом йому в MySQL слугує &lt;strong&gt;DATETIME&lt;/strong&gt;). Однак будь-який програміст, що досить довго працював з MySQL спитає, а як зробити поле, яке при будь-якій зміні данних автоматично прийматиме значення дати та часу останньої модифікації?
Для таких цілей слугують трігери, що з'явився тільки в MySQL 5, чим і можна пояснити наявність в ньому типу &lt;strong&gt;TIMESTAMP&lt;/strong&gt;. В PostgreSQL аналогічного ефекту можна досягнути створивши трігер-функцію:
&lt;code class="code-block"&gt;
CREATE OR REPLACE FUNCTION update_modified_column()
  RETURNS trigger AS
$BODY$
BEGIN
   NEW.modified = now(); 
   RETURN NEW;
END;
$BODY$
  LANGUAGE 'plpgsql' VOLATILE
  COST 100;
&lt;/code&gt;
Тепер можна створити таблицю з полем modified та трігером на update (що буде виконуватись після внесення змін до таблиці), який буде викликати описану вище функцію:
&lt;code class="code-block"&gt;
CREATE TABLE items (
  id SERIAL,
  created TIMESTAMP WITHOUT TIME ZONE DEFAULT now(),
  modified TIMESTAMP WITHOUT TIME ZONE DEFAULT now()
)

CREATE TRIGGER update_items
  AFTER UPDATE
  ON items
  FOR EACH ROW
  EXECUTE PROCEDURE update_modified_column();
&lt;/code&gt;
І знову ж дозволю собі нагадати, що обсласт ьвикористання трігерів значно ширша за таку просту задачу, як оновлення полів для роботи з датою.
&lt;h3&gt;GROUP_CONCAT&lt;/h3&gt;
Дуже популярна тема. Всі програмісти, що приходять з MySQL раніше чи пізніше зтикаються з необхідністю згрупувати декілька елементів за якимось полем. Наприклад є така таблиця:
&lt;code class="code-block"&gt;
CREATE TABLE items (
id SERIAL,
title VARCHAR(255),
user_id INTEGER
)
&lt;/code&gt;
В MySQL можна було б записати запит для агрегації всих полів за user_id так:
&lt;code class="code-block"&gt;
SELECT user_id, GROUP_CONCAT(id), GROUP_CONCAT(title) FROM items GROUP BY user_id;
&lt;/code&gt;
Однак, одразу в PostgreSQL такий результат отримати не можна. В документації можна знайти &lt;a href="http://www.postgresql.org/docs/8.1/interactive/xaggr.html" title="Документація з PostgreSQL. Корситувацькы агрегатори"&gt;таке рішення&lt;/a&gt;, що реалізуэться за допомогою агрегаторів:
&lt;code class="code-block"&gt;
create aggregate array_accum (
sfunc = array_append,
basetype = anyelement,
stype = anyarray,
initcond = '{}'
);
&lt;/code&gt;
Однак такий агрегатор повертає результат у вигляді масиву, що лякає тиххто раніше не працював з таким різноманіттям типів в рамках БД. Тому можна створити іншу функцію-агрегатор, що буде збирати значення в один рядок та матиме можливість видозмінювати розділювач між елементами:
&lt;code class="code-block"&gt;
CREATE FUNCTION _group_concat(text, text, text)
    RETURNS text AS $$
      SELECT CASE
        WHEN $2 IS NULL THEN $1
        WHEN $1 IS NULL THEN $2
        ELSE $1 operator(pg_catalog.||) $3 operator(pg_catalog.||) $2
      END
    $$ IMMUTABLE LANGUAGE SQL;

CREATE AGGREGATE group_concat(text, text) (
	SFUNC = _group_concat,
	STYPE = text
);
&lt;/code&gt;
Вище описано функцію &lt;code&gt;_group_concat()&lt;/code&gt;, що вионує конкатенацю двох елементів, що були передані у вигляді параметрів. Причому символом-розділювачем є значеня вказане у якості третього параметру. Цю функцію використовує агрегатор &lt;code&gt;group_concat&lt;/code&gt;. Єдиним, що може викликати дискомфорт при роботі є необхідність дотримуватись типів полів, а отже для нормальної агрегації потрібно використовувати зведення типів:
&lt;code class="code-block"&gt;
SELECT user_id, GROUP_CONCAT(CAST(id AS text), ','), GROUP_CONCAT(title, ';') FROM items GROUP BY user_id;
&lt;/code&gt;
Синтаксис зовсім трошки відрізняється від аналогічного в MySQL. Отже не такий страшний PostgreSQL про нього думають. :) Хоча для любителів використовувати SQL запити без агрегаторів та додаткових функцій є &lt;a href="http://explainextended.com/2009/05/02/group_concat-in-postgresql-without-aggregate-functions/" title="Одна з спроб реалізувати функціонал group_concat, однак чистим sql"&gt;інший шлях&lt;/a&gt;.
&lt;h3&gt;Строгість та широкі можливості&lt;/h3&gt;
PostgreSQL як можна помітити вимагає трошки більше уваги та дій для роботи. Однак це не є обузою, оскільки можливості він має ширші, структура є логічною, прозорою та уніфікованою. Мені здається, що строгість дозволяє зменшити кількість помилок ціною незначного збільшення часу на написання запитів. Сподіваюсь, що цим невеличким матеріалом я зможу зберегти комусь час та нерви.
Хочу ще неодноразово повернутись до теми обговорення PostgreSQL та його порівняння з іншими БД. Дякую за увагу!&lt;img src="http://feeds.feedburner.com/~r/GrandsesBlog/~4/6ZrfCcnTNpY" height="1" width="1"/&gt;</description>
		<link>http://feedproxy.google.com/~r/GrandsesBlog/~3/6ZrfCcnTNpY/79</link>
		<pubDate>Fri, 21 Aug 2009 10:19:47 +0300</pubDate>
		<dc:creator>grandse</dc:creator>
	<feedburner:origLink>http://grandse.org.ua/messages/show/79</feedburner:origLink></item>
	<item>
		<title>MySQL proxy та логування запитів</title>
		<description>Вже більше тижня я ніяк не можу одужати. Переважно лежу вдома, відпочиваю та набираюсь сил. Тому досить довгий час я не міг зібратись з думками та дописати (хоча й почав доволі давно) продовження &lt;a href="http://grandse.org.ua/messages/show/71" title="Погляд на MySQL proxy"&gt;своєї розповіді&lt;/a&gt; про молодий та цікавий проект &lt;strong&gt;MySQL proxy&lt;/strong&gt;, що дозволяє досить легко проксювати запити до одного чи декількох серверів MySQL. Нагадаю, що таким чином можна створювати системи, що можуть витримувати підвищені навантаження, внаслідок розподілення запитів між декількома серверами, систему автоматичного коректування запитів, чи систему ведення логів запитів та підключень для декількох різних серверів. Думаю, що невеличке занурення в написання lua-скриптів для конфігурування MySQL proxy слід розпочати саме з такої досить простої задачі (це я про ведення логів).
Для початку рекомендую продивитись &lt;a href="http://grandse.org.ua/messages/show/71" title="Погляд на MySQL proxy"&gt;попередній матеріал&lt;/a&gt; та &lt;a href="http://grandse.org.ua/messages/show/68" title="Крихітка Lua"&gt;мою ж розповідь про Lua&lt;/a&gt;, хоча це й необов'язково.&lt;!-----more-----!&gt;
&lt;h3&gt;Загальна структура скрипта, параметри виклику функцій&lt;/h3&gt;
Скрипти конфігурації MySQL proxy мажуть включати декілька функцій:
&lt;ol&gt;
&lt;li&gt;connect_server() - виконується під час підключення клієнта до серверу&lt;/li&gt;
&lt;li&gt;read_handshake(auth) - виконуэться після спроби підключення клієнта до серверу&lt;/li&gt;
&lt;li&gt;read_auth(auth) - виконується тоді, коли клієнт відправляє запит на авторизацію&lt;/li&gt;
&lt;li&gt;read_auth_result(packet) - відповідь на резльтат запиту на авторизацію&lt;/li&gt;
&lt;li&gt;read_query(packet) - виконуэться після відправки запиту від клієнту, перед його виконанням&lt;/li&gt;
&lt;li&gt;read_query_result(inj) - виконується перед відправкою результатів клієнту&lt;/li&gt;
&lt;li&gt;disconnect_client() - виконується під час підключення клієнта до серверу&lt;/li&gt;
&lt;/ol&gt;
Ці функції можуть виконуватись лише в тому порядку, в якому я вони були перераховані, і цей &lt;strong&gt;порядок змінити неможливо&lt;/strong&gt;. З одного боку, це є обмеженням (неможливо, якщо спроба авторизації була невдалою, змінити логін та пароль користувача на якийсь інший та спробувати авторизуватись знову, чи спробувати інший бекенд для авторизації), однак на практиці є доволі гнучкою (звісно, якщо розібратись з основною ідеєю, доступними параметрами та мовою lua).
Тепер перейдемо до параметрів, що передаються в кожну з функцій. &lt;strong&gt;connect_server()&lt;/strong&gt; не має параметрів. На цьому етапі робота може проводитись з глобальною таблицею proxy.connection, яка містить інформацію про підключення. Думаю, що поле &lt;strong&gt;proxy.connection.thread_id&lt;/strong&gt;, що вказую id потоку-підключення, не є настільки важливим, як &lt;strong&gt;proxy.connection.backend_ndx&lt;/strong&gt;. Останнє поле таблиці вказує на номер бекенду, який використовується при спробі виконати підключення. Тобто, якщо проксі-сервер використовується для підключення для однієї з декількох серверів БД, то цей індекс буде вказувати на номер серверу, до якого ми будемо виконувати підключення. Маніпучюючи цим параметром маємо змогу змінювати сервер. (неприклад, для балансування навантаженням).
&lt;strong&gt;read_handshake()&lt;/strong&gt; - в якості параметру отримує данні про підюклчення. Ця таблиця містить поля. 
&lt;ul&gt;
&lt;li&gt;mysqld_version - версія MySQL серверу&lt;/li&gt;
&lt;li&gt;thread_id - id потоку&lt;/li&gt;
&lt;li&gt;scramble - буфер паролю&lt;/li&gt;
&lt;li&gt;server_addr - IP адреса серверу&lt;/li&gt;
&lt;li&gt;client_addr - IP адреса клієнту&lt;/li&gt;
&lt;/ul&gt;
Функція &lt;strong&gt;read_auth()&lt;/strong&gt; як параметр отримує таблицю, що містить дані про авторизацію користувача, а саме &lt;strong&gt;username&lt;/strong&gt; - логін користувача для підключення, &lt;strong&gt;password&lt;/strong&gt; - пароль користувача, та default_db - БД за замовчуванням.
У функцію &lt;strong&gt;read_auth_result()&lt;/strong&gt; у якості параметру передається пакет, якої поверне сервер у результаті авторизації. Щоб оцінити результат, який повертає сервер, можна конвертувати рельутат приблизно так:
&lt;code class="code-block"&gt;
local state = auth.packet:byte() 
&lt;/code&gt;
Порівнюючи значення змінної state з константами &lt;strong&gt;proxy.MYSQLD_PACKET_OK&lt;/strong&gt; та &lt;strong&gt;proxy.MYSQLD_PACKET_ERR&lt;/strong&gt; можемо перевірити результати авторизації.
Функція &lt;strong&gt;read_query()&lt;/strong&gt; в акості параметру отримує пакет, що був переданий клієнтом. Взагалі, для обробки є додаткові скрипти, що розбирають пакет для отримання самого запиту, його розбору і т.д. Взагалі шляхом конвертування пакету в byte можемо отримати код того, що міститься в пакеті (якщо це нормальний запит, то значення має бути рівним константі &lt;strong&gt;proxy.COM_QUERY&lt;/strong&gt;), а запит можна отримати як підрядок пакету починаючи з другого символу: 
&lt;code class="code-block"&gt;
string.sub(packet, 2)
&lt;/code&gt;
Функція &lt;strong&gt;read_query_result()&lt;/strong&gt; - в якості параметру отримує таблицю, що містить:
&lt;ul&gt;
&lt;li&gt;id - ID результату, що відповідає ID який було присвоєно запиту&lt;/li&gt;
&lt;li&gt;query - оригінальний рядок запит&lt;/li&gt;
&lt;li&gt;query_time - час у мілісекундах, що пройшов до моменту отримання першого рядку результату запиту&lt;/li&gt;
&lt;li&gt;response_time - час у мілісекундах, що пройшов до моменту отримання останнього рядку результату запиту&lt;/li&gt;
&lt;li&gt;resultset — результуючі дані&lt;/li&gt;
&lt;/ul&gt;
Тепер вже можна перейти до написання першого повноцінного скрипта.
&lt;h3&gt;Додамо трошки Lua&lt;/h3&gt;
Дозволю собі нагадати, що пишемо ми скрипт для логування запитів і підключень до серверу. Для цього нам треба додати функцію, що буде виконувати запис у файл:
&lt;code class="code-block"&gt;
function write(message)
	print("["..session.."] "..message)
	fh:write(string.format("[%s\t%6d] -- %s \n", os.date('%Y-%m-%d %H:%M:%S'), session, message))
	fh:flush()
end
function debug_write(message)
	if is_debug then
		write(message)
	end
end
&lt;/code&gt;
Вище наведено дві функції, що будуть використовуватись для ведення логів. Перша використовується для прямого запису в лог, а друга для запису у випадку, якщо скрипт знаходиться в режимі відлагодження (змінна &lt;var&gt;is_debug&lt;/var&gt;). Як можна помітити, наведені функції використовуюить деякі глобальні змінні. Для того, щоб корситовуватись глобальними змінніми можна в середені скрипта описати змінні поза функціями. Наприклад так:
&lt;code class="code-block"&gt;
local is_debug	= false
&lt;/code&gt;
Однак вони будуть дійсні лише в рамках однієї сессії роботи MySQL proxy. Для створення змінних, які будуть використовуватись для конфігурації скриптів та містити дані необхідні для роботи всіх сессій (наприклад кількість сессій) можна використовувати таблицю proxy.global:
&lt;code class="code-block"&gt;
if not proxy.global.config.log then
	proxy.global.config.log = {
		sessions = 0,
		is_debug = true
	}
end
proxy.global.config.log.sessions = proxy.global.config.log.sessions+1
local session 	= proxy.global.config.log.sessions	-- session id
local is_debug 	= proxy.global.config.log.is_debug	-- is need to write debug info
&lt;/code&gt;
Тут ми ініціалізуємо глобальну таблицю для збереження конфігурації, ініціалізуємо початковими занченням та виводимо в наш скрипт в якості глобальних для цієї сесії даних.
Залишається відкрити файл для запису:
&lt;code class="code-block"&gt;
local log_file 		= os.getenv("PROXY_LOG_FILE")
if (log_file == nil) then
	log_file = "/tmp/mysql-proxy.log"
end
local fh = io.open(log_file, "a+")
&lt;/code&gt;
В наведеному вище коді спочатку намагаємось отримати значення змінної оточення PROXY_LOG_FILE. Якщо воно не встановлене, то використовуэмо значення за замовчуванням (/tmp/mysql-proxy.log). І отриманий файл відкривається для дозапису.
Настав час перейти до написання самого скрипту для логування подій:
&lt;code class="code-block"&gt;
local commands		= require("proxy.commands")

function connect_server() 
	-- new connection created
	debug_write("")
	write("")
	debug_write("[connect_server] "..proxy.connection.client.address)
end

-- 
function read_auth_result(auth)
	debug_write("  [read_auth_result] "..proxy.connection.client.address)
	local packet = auth.packet:byte()
	if packet == proxy.MYSQL_PACKET_OK then
		proxy.connection.backend_ndx = 0
	elseif packet == proxy.MYSQL_PACKET_ERR then
		write("  (read_auth_result) auth failed")
	else
		write("  (read_auth_result) ... don't know: "..string.format("%q", auth.packet))
	end
end

-- on query execution
function read_query(packet)
	-- prepare
	debug_write("  [read_query] "..proxy.connection.client.address)
	local cmd	= commands.parse(packet)
	local c		= proxy.connection.client
	-- look at forward connected to backend
	debug_write("    current backend	= "..proxy.connection.backend_ndx)
	debug_write("    client username	= "..c.username)
	if cmd.type == proxy.COM_QUERY then
		debug_write("    query		= "..cmd.query)
	end
end

-- get query result
function read_query_result(inj) 
	debug_write("  [read_query_result]")
end

-- on client disconnect
function disconnect_client() 
	debug_write("[disconnect_client]"..proxy.connection.client.address)
end
&lt;/code&gt;
Наведений вище код підключає додаткову бібліотеку proxy.commands, що використовується для обробки запитів до БД. Також використовується логування підключень до серверів, вдалих та невдалих авторизацій, запитів (з вказанням імені користувача, що виконував цей запит та номером бекенд-серверу до якого було направлено запит) та відключення клієнтів. Скрипт доволі простий, і якщо Ви уважно читали початок попередньо написане мною, то без проблем розберетесь з ним.
&lt;h3&gt;Далі буде&lt;/h3&gt;
Цього в принципі вже достатньо для того, щоб писати свою власні "бойові" скрипти. Однак я планую написати ще одну статтю з на цю тему, в якій опишу реальну задачу, та наведу програмний код, який мені довелось написати для її вирішення. Сподіваюсь це буде цікаво та корисно.
Дякую за увагу!&lt;img src="http://feeds.feedburner.com/~r/GrandsesBlog/~4/dHtw8eKrkOw" height="1" width="1"/&gt;</description>
		<link>http://feedproxy.google.com/~r/GrandsesBlog/~3/dHtw8eKrkOw/77</link>
		<pubDate>Mon, 03 Aug 2009 20:48:32 +0300</pubDate>
		<dc:creator>grandse</dc:creator>
	<feedburner:origLink>http://grandse.org.ua/messages/show/77</feedburner:origLink></item>
</channel>
</rss>
