<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	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:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Юникс и другие &#187; Python</title>
	<atom:link href="http://boombick.org/blog/posts/category/programming/python/feed" rel="self" type="application/rss+xml" />
	<link>http://boombick.org/blog</link>
	<description>Just another WordPress weblog</description>
	<lastBuildDate>Tue, 29 Nov 2011 22:14:38 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0</generator>
		<item>
		<title>Установка Django на devio.us</title>
		<link>http://boombick.org/blog/posts/95</link>
		<comments>http://boombick.org/blog/posts/95#comments</comments>
		<pubDate>Tue, 04 May 2010 08:02:31 +0000</pubDate>
		<dc:creator>boombick</dc:creator>
				<category><![CDATA[*NIX]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://boombick.org/blog/posts/95</guid>
		<description><![CDATA[Как, наверняка, всем известно, недавно появился проект Devio.us, который предлагает всем желающим бесплатный shell-хостинг на базе OpenBSD. В нагрузку к шеллу также предлагается домен третьего уровня, вида %username%.devio.us или devio.us/~%username%, одна база данных MySQL и 100 мегов места. За пару баксов в месяц эти показатели можно улучшить :) На сервере установлен PHP (версии 5.2.12) как [...]]]></description>
			<content:encoded><![CDATA[<p>Как, наверняка, всем известно, недавно появился проект <a href="http://devio.us">Devio.us</a>, который предлагает всем желающим бесплатный shell-хостинг на базе OpenBSD. В нагрузку к шеллу также предлагается домен третьего уровня, вида %username%.devio.us или devio.us/~%username%, одна база данных MySQL и 100 мегов места. За пару баксов в месяц эти показатели можно улучшить :)<br />
На сервере установлен PHP (версии 5.2.12) как модуль апача (весьма, кстати, древнего &#8211; 1.3.29) и предлагается CGI-интерфейс. Ну и также доступны perl 5.10.1 и python 2.5.4. Целью проекта является привлечение новых членов в коммьюнити OpenBSD. При регистрации необходимо указать причину, по которой вам кровь из носу надо получить там аккаунт :) Процент отказов, к слову, довольно мал.<br />
Итак, поигравшись с PHP, захотелось чего-то большего. И так как на сервере присутствует python, захотелось прикрутить туда Django.<br />
<span id="more-95"></span><br />
<strong>Установка Django</strong><br />
Проведя ревизию модулей apache я выяснил, что ни mod_python, ни mod_wsgi не установлены. Грустно, но ладно. Для начала я просто залил туда дистрибутив django (версии 1.1.1)</p>
<blockquote>
<pre>
$ scp Django-1.1.1.tar.gz devio.us:</pre>
</blockquote>
<p>и распаковал его</p>
<blockquote>
<pre>
$ gunzip Django-1.1.1.tar.gz
$ tar xvf Django-1.1.1.tar
$ ln -s Django-1.1.1 django #  для дальнейшего удобства</pre>
</blockquote>
<p>Добавим переменную окружения PYTHONPATH для корректной работы Django</p>
<blockquote>
<pre>
$ export PYTHONPATH='/home/boombick/django'
$ echo "export PYTHONPATH='/home/boombick/django'" &gt;&gt; ~/.profile</pre>
</blockquote>
<p>И создадим симлинк для django-admin</p>
<blockquote>
<pre>
$ mkdir ~/bin
$ ln -s /home/boombick/django/django/bin/django-admin.py /home/boombik/bin/django-admin.py</pre>
</blockquote>
<p>Дальше все просто: создаем новый проект Django</p>
<blockquote>
<pre>
$ django-admin.py startproject djtest
$ cd djtest</pre>
</blockquote>
<p>И на этом этапе я просто запустил dev-сервер из поставки Django на высоком порту (занять порт с номером ниже 1024 без привелегий root не получится)</p>
<blockquote>
<pre>
$ python manage.py runserver 0.0.0.0:32000</pre>
</blockquote>
<p>Зайдя на http://boombick.devio.us:32000 я увидел приветственную страницу пустого проекта Django. Все получилось :)</p>
<p><strong>Запуск Django через CGI</strong><br />
В принципе, можно было бы оставить все и так. Но.<br />
1. Хотелось все-таки ходить к себе на страничку по 80 порту<br />
2. На free-аккаунте нельзя детачить приложения. Т.е. нельзя оставить запущенным сервер и разлогиниться. Что, впрочем, логично, иначе сервер превратился бы в рассадник IRC-ботов :)<br />
Можно, конечно, извратиться и держать логин постоянно, восстанавливая оборванное подключение и перезапуская сервер, но это не наш путь.</p>
<p>Итак.<br />
Сначала я просто пробрасывал запросы к Django через PHP (в виде index.php) :) Эдакий PHP-Proxy, но мало того, что это запредельно криво, так еще и не очень понятно, что делать с POST-запросами. Выход был один &#8211; заставить Django работать через CGI.<br />
Помучив гугл я нашел несколько разноненных мануалов и приступил к выполнению.<br />
Для начала создадим в директории <code>$HOME/public_html</code> файл django.cgi вот с таким содержанием:</p>
<blockquote>
<pre>
#!/usr/local/bin/python
# encoding: utf-8
"""
django.cgi

A simple cgi script which uses the django WSGI to serve requests.
Rewrited for use on devio.us shell hosting

Code copy/pasted from PEP-0333 and then tweaked to serve django.

http://www.python.org/dev/peps/pep-0333/#the-server-gateway-side

This script assumes django is on your sys.path, and that your site code is at
/home/mycode/mysite. Copy this script into your cgi-bin directory (or do
whatever you need to to make a cgi script executable on your system), and then
update the paths at the bottom of this file to suit your site.

This is probably the slowest way to serve django pages, as the python
interpreter, the django code-base and your site code has to be loaded every
time a request is served. FCGI and mod_python solve this problem, use them if
you can.

In order to speed things up it may be worth experimenting with running
uncompressed zips on the sys.path for django and the site code, as this can be
(theorectically) faster. See PEP-0273 (specifically Benchmarks).

http://www.python.org/dev/peps/pep-0273/

Make sure all python files are compiled in your code base. See

http://docs.python.org/lib/module-compileall.html

"""

import os, sys
sys.path.append("/home/boombick/django") # Поменяйте на путь к Django
sys.path.append("/home/boombick/djtest")  # Поменяйте на путь к вашему проекту

import django.core.handlers.wsgi

def run_with_cgi(application):

    environ                      = dict(os.environ.items())
    environ['wsgi.input']        = sys.stdin
    environ['wsgi.errors']       = sys.stderr
    environ['wsgi.version']      = (1,0)
    environ['wsgi.multithread']  = False
    environ['wsgi.multiprocess'] = True
    environ['wsgi.run_once']     = True

    if environ.get('HTTPS','off') in ('on','1'):
        environ['wsgi.url_scheme'] = 'https'
    else:
        environ['wsgi.url_scheme'] = 'http'

    headers_set  = []
    headers_sent = []

    def write(data):
        if not headers_set:
             raise AssertionError("write() before start_response()")

        elif not headers_sent:
             # Before the first output, send the stored headers
             status, response_headers = headers_sent[:] = headers_set
             sys.stdout.write('Status: %s\r\n' % status)
             for header in response_headers:
                 sys.stdout.write('%s: %s\r\n' % header)
             sys.stdout.write('\r\n')

        sys.stdout.write(data)
        sys.stdout.flush()

    def start_response(status,response_headers,exc_info=None):
        if exc_info:
            try:
                if headers_sent:
                    # Re-raise original exception if headers sent
                    raise exc_info[0], exc_info[1], exc_info[2]
            finally:
                exc_info = None     # avoid dangling circular ref
        elif headers_set:
            raise AssertionError("Headers already set!")

        headers_set[:] = [status,response_headers]
        return write

    result = application(environ, start_response)
    try:
        for data in result:
            if data:    # don't send headers until body appears
                write(data)
        if not headers_sent:
            write('')   # send headers now if body was empty
    finally:
        if hasattr(result,'close'):
            result.close()

os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
run_with_cgi(django.core.handlers.wsgi.WSGIHandler())</pre>
</blockquote>
<p>Затем меняем в <code>/path/to/dajngo-project/settings.py</code> параметр <code>ROOT_URLCONF</code> на <code>urls</code> вместо <code>projectname.urls</code><br />
Делаем файл исполняемым</p>
<blockquote>
<pre>
$ chmod a+x public_html/django.cgi</pre>
</blockquote>
<p>И проверяем работоспособность :)</p>
<p>http://boombick.devio.us/django.cgi</p>
<p><a href="http://boombick.org/blog/wp-content/uploads/2010/05/djangocgi.png" title="djangocgi.png"><img src="http://boombick.org/blog/wp-content/uploads/2010/05/djangocgi.thumbnail.png" alt="djangocgi.png" /></a><br />
Если все ок, то можно переходить к следующему шагу. А если нет и вы получаете 500 ошибку, то смотрите лог. Который, кстати, лежит в <code>/var/www/logs/error_log</code> В процессе установки очень удобно было его мониторить в отдельной консольке при помощи <code>tail -f</code><br />
Учтите только, что в этот лог падают сообщения о всех ошибках и вам надо отделять свои запросы от остальных :)</p>
<p><strong>Настройка Apache</strong><br />
Теперь надо настроить Apache для переадресации всех запросов к нашему django.cgi. Я очень давно не ковырялся с Apache (в последнее время использую исключительно nginx) и с трудом и доками вспоминал синтаксис mod_rewrite<br />
В результате родился вот такой вот файлик <code>.htaccess</code>, который надо положить в директорию <code>public_html</code></p>
<blockquote>
<pre>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /django.cgi%{REQUEST_URI}?%{QUERY_STRING}</pre>
</blockquote>
<p><strong>Проверяем</strong><br />
В <code>~/djtest/urls.py</code> добавим правило для обработки всех адресов</p>
<blockquote>
<pre>
from django.conf.urls.defaults import *
from views import test_page

urlpatterns = patterns('',
    (r'^(.*)$', test_page)
)</pre>
</blockquote>
<p>И создадим файл <code>~/djtest/views.py</code> с, собственно, обработчиком</p>
<blockquote>
<pre>
from django.http import HttpResponse       

def test_page(request, url):
    html = "WORKING! Url is %s" % url
    return HttpResponse(html)</pre>
</blockquote>
<p>Заходим на http://boombick.devio.us/foo/bar<br />
<a href="http://boombick.org/blog/wp-content/uploads/2010/05/django.png" title="django.png"><img src="http://boombick.org/blog/wp-content/uploads/2010/05/django.thumbnail.png" alt="django.png" /></a><br />
Все работает :)</p>
<p><strong>Благодарности</strong><br />
Хочу сказать большое спасибо ребятам из конференции django@conference.jabber.ru, а конкретно <strong>ne_formal</strong> за неоценимую помощь :) Без него этот процесс затянулся бы куда больше!</p>
<p><strong>Важное замечание</strong><br />
Если вы читали комментарии в начале файла django.cgi, то не могли не заметить такой абзац:</p>
<blockquote><p>This is probably the slowest way to serve django pages, as the python<br />
interpreter, the django code-base and your site code has to be loaded every<br />
time a request is served. FCGI and mod_python solve this problem, use them if<br />
you can.
</p></blockquote>
<p>Что в вольном переводе выглядит как:<br />
<em>Это самый медленный путь для работы с django. На каждый запрос создается экземпляр интерпретатора Python. Если у вас есть возможность использовать FCGI или mod_python &#8211; используйте их!</em><br />
Так что статью прощу рассматривать исключительно как proof of concept или выход из совсем уж безвыходной ситуации! :)<br />
Enjoy!</p>
]]></content:encoded>
			<wfw:commentRss>http://boombick.org/blog/posts/95/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Виртуальные хосты на lighttpd с хранением конфигурации в MySQL</title>
		<link>http://boombick.org/blog/posts/49</link>
		<comments>http://boombick.org/blog/posts/49#comments</comments>
		<pubDate>Wed, 28 Jan 2009 10:29:07 +0000</pubDate>
		<dc:creator>boombick</dc:creator>
				<category><![CDATA[*NIX]]></category>
		<category><![CDATA[Debian]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[web-servers]]></category>

		<guid isPermaLink="false">http://boombick.org/blog/posts/49</guid>
		<description><![CDATA[Статья посвящена настройке виртуальных хостов для lighttpd, работающего под управлением Debian, с хранением всех конфигурационных данных в MySQL. Главная особенность в том, что мы не будем использовать модуль для Лайти mod_mysql_vhost, который позволяет хранить в базе лишь имя хоста и его корневой раздел. Наша конфигурация более гибкая и позволяет хранить в БД все директивы для [...]]]></description>
			<content:encoded><![CDATA[<p>Статья посвящена настройке виртуальных хостов для lighttpd, работающего под управлением Debian, с хранением всех конфигурационных данных в MySQL. Главная особенность в том, что мы не будем использовать модуль для Лайти <code>mod_mysql_vhost</code>, который позволяет хранить в базе лишь имя хоста и его корневой раздел. Наша конфигурация более гибкая и позволяет хранить в БД все директивы для каждого виртуального хоста<br />
<span id="more-49"></span><br />
<b>Установка MySQL 5.0</b><br />
Устанавливаем MySQL:</p>
<blockquote><p>apt-get install mysql-server mysql-client</p></blockquote>
<p>Задаем пароль для пользователя <code>root</code> (замените <code>yourrootsqlpassword</code> на ваш пароль)</p>
<blockquote><p>mysqladmin -u root password yourrootsqlpassword</p></blockquote>
<p>Проверим, какие адреса слушает MySQL, при помощи команды</p>
<blockquote><p>netstat -tap | grep mysql</p></blockquote>
<p>Вывод должен быть примерно таким:</p>
<blockquote><pre>
tcp        0      0 localhost.localdo:mysql *:*                     LISTEN     2713/mysqld
</pre>
</blockquote>
<p>Это значит, что MySQL слушает только <code>localhost.localdomain</code> и вы установили пароль. Если  же вывод похож на:</p>
<blockquote><pre>
tcp        0      0 *:mysql *:*                     LISTEN     2713/mysqld
</pre>
</blockquote>
<p>вам необходимо также установить пароль для вашего хоста (в нашем примере это <code>server1.example.com</code>), иначе кто-угодно сможет подключиться к БД и манипулировать данными</p>
<blockquote><pre>
mysqladmin -h server1.example.com -u root password yourrootsqlpassword
</pre>
</blockquote>
<p><b>Устанавливаем Lighttpd, Python и python-mysqldb</b><br />
Мы будем использовать скрипт, написанный на Python, для чтения конфигурации из БД. Устанавливаем все необходимое:</p>
<blockquote><p>
apt-get install lighttpd python python-mysqldb
</p></blockquote>
<p><b>Подготовка базы данных</b><br />
Подключаемся к MySQL:</p>
<blockquote><p>mysql -u root -p</p></blockquote>
<p>создаем базу данных и пользователя. А также даем пользователю привилегии SELECT для запросов к нашей базе</p>
<blockquote><pre>
CREATE DATABASE lighttpd;
GRANT SELECT ON lighttpd.* TO lighttpd@localhost IDENTIFIED BY 'secret';
GRANT SELECT ON lighttpd.* TO lighttpd@localhost.localdomain IDENTIFIED BY 'secret';
FLUSH PRIVILEGES;
</pre>
</blockquote>
<p>Не забудьте заменить &#8220;<code>secret</code>&#8221; на реальный пароль<br />
Осталось создать таблицу для наших виртуальных хостов и подготовка БД на этом закончена</p>
<blockquote><pre>
USE lighttpd;
CREATE TABLE IF NOT EXISTS domains (
domain varchar(64) NOT NULL PRIMARY KEY,
docroot varchar(128) NOT NULL,
config text
);
quit;
</pre>
</blockquote>
<p><b>Сценарий для получения данных из БД</b><br />
Создаем скрипт на python, который будет читать из БД конфигурацию виртуальных хостов</p>
<blockquote><pre>
#!/usr/bin/env python
import sys
import MySQLdb

# load configuration data from the database
db=MySQLdb.connect(host='localhost', db=sys.argv[1], user=sys.argv[2], passwd=sys.argv[3])
cur = db.cursor()
cur.execute("SELECT * FROM domains")
rs=cur.fetchall()
db.close()

for domain in rs:

    print "$HTTP[\"host\"] == \"%s\" {\nserver.document-root = \"%s\"\n%s\n}" % (domain[0], domain[1], domain[2])
</pre>
</blockquote>
<p>и сохраняем его в <code>/usr/share/lighttpd/mysql_vhost.py</code><br />
Делаем скрипт исполняемым</p>
<blockquote><p>chmod 755 /usr/share/lighttpd/mysql_vhost.py</p></blockquote>
<p>Теперь надо настроить Лайти для использования нашего срипта. Для этого открываем конфиг <code>/etc/lighttpd/lighttpd.conf</code> и добавляем в конец следующую строчку</p>
<blockquote><pre>
[...]
include_shell "/usr/share/lighttpd/mysql_vhost.py lighttpd lighttpd secret"
</pre>
</blockquote>
<p>(первый параметр &#8211; это имя БД, второй &#8211; имя пользователя БД, третий &#8211; пароль для доступа к БД)<br />
После всех изменений перезапустим lighttpd</p>
<blockquote><p>/etc/init.d/lighttpd restart</p></blockquote>
<p><b>Настройка виртуальных хостов</b><br />
В примере мы настроим два виртуальных хоста: <code>www.example.com</code> (с корневым разделом в <code>/var/www/www.example.com/web</code>) и <code>www.example.org</code> (с корневым разделом в <code>/var/www/www.example.org/web</code>). Мы будем использовать различные параметры для каждого хоста для демонстрации возможностей :)<br />
Для <code>www.example.com</code> мы разрешим листинг директорий и создадим алиас <code>test</code>, который будет указывать на DocumentRoot. Для <code><br />
www.example.org</code> листинг директорий будет отключен.</p>
<p>Если корневые разделы еще не существуют, то создадим их:</p>
<blockquote><p>
mkdir -p /var/www/www.example.com/web<br />
mkdir -p /var/www/www.example.org/web</p></blockquote>
<p>Подключаемся к MySQL и добавим описание наших хостов</p>
<blockquote><pre>
INSERT INTO domains VALUES ('www.example.com','/var/www/www.example.com/web/','dir-listing.activate = "enable"\nalias.url = ( "/test" => "/var/www/www.example.com/web" )');
INSERT INTO domains VALUES ('www.example.org','/var/www/www.example.org/web/','dir-listing.activate = "disable"');
</pre>
</blockquote>
<p>Обратите внимание на первый запрос <code>INSERT</code>. Если вы хотите использовать более одной директивы, то поместите между ними управляющий символ &#8220;\n&#8221;<br />
Все, виртуальные хосты настроены. Для проверки корректности работы нашего скрипта, запустим его из командной строки:</p>
<blockquote><p>/usr/share/lighttpd/mysql_vhost.py lighttpd lighttpd secret</p></blockquote>
<p>В выводе должна содержаться корректная информация о наших хостах:</p>
<blockquote><pre>
server1:~# /usr/share/lighttpd/mysql_vhost.py lighttpd lighttpd secret
$HTTP["host"] == "www.example.com" {
server.document-root = "/var/www/www.example.com/web/"
dir-listing.activate = "enable"
alias.url = ( "/test" => "/var/www/www.example.com/web" )
}
$HTTP["host"] == "www.example.org" {
server.document-root = "/var/www/www.example.org/web/"
dir-listing.activate = "disable"
}
server1:~#</pre>
</blockquote>
<p>В отличие от <code>mod_mysql_vhost</code>, наш способ требует перезапуска сервера</p>
<blockquote><p>/etc/init.d/lighttpd restart</p></blockquote>
<p><b>Тестируем</b><br />
Пришло время проверить, верно ли работают наши виртуальные хосты. В корневых директориях хостов нет индексных файлов<br />
<code>http://www.example.com</code> (листинг директорий включен)<br />
<a href='http://boombick.org/blog/wp-content/uploads/2009/01/11.png' title='11.png'><img src='http://boombick.org/blog/wp-content/uploads/2009/01/11.thumbnail.png' alt='11.png' /></a></p>
<p><code>http://www.example.com/test</code> Директории <code>test</code> не существует, но мы прописали алиас для этого пути. Поэтому мы видим содержимое DocumentRoot<br />
<a href='http://boombick.org/blog/wp-content/uploads/2009/01/21.png' title='21.png'><img src='http://boombick.org/blog/wp-content/uploads/2009/01/21.thumbnail.png' alt='21.png' /></a></p>
<p><code>http://www.example.org</code> Мы получаем ошибку 404, потому что индексного файла нет, а листинг директорий отключен<br />
<a href='http://boombick.org/blog/wp-content/uploads/2009/01/31.png' title='31.png'><img src='http://boombick.org/blog/wp-content/uploads/2009/01/31.thumbnail.png' alt='31.png' /></a></p>
<p><code>http://www.example.org/test</code> Листинг директорий отключен, и мы не делали алиасов для этого хоста. Поэтому все та же ошибка 404<br />
<a href='http://boombick.org/blog/wp-content/uploads/2009/01/4.png' title='4.png'><img src='http://boombick.org/blog/wp-content/uploads/2009/01/4.thumbnail.png' alt='4.png' /></a></p>
<p><noindex><b>Оригинал:</b> <a href="http://howtoforge.org/creating-advanced-mysql-based-vhosts-on-lighttpd-debian-etch">http://howtoforge.org/creating-advanced-mysql-based-vhosts-on-lighttpd-debian-etch</a></noindex></p>
]]></content:encoded>
			<wfw:commentRss>http://boombick.org/blog/posts/49/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Использование antipool.py для потокового доступа к базе данных Python</title>
		<link>http://boombick.org/blog/posts/42</link>
		<comments>http://boombick.org/blog/posts/42#comments</comments>
		<pubDate>Thu, 25 Dec 2008 09:47:01 +0000</pubDate>
		<dc:creator>boombick</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://boombick.org/blog/posts/42</guid>
		<description><![CDATA[Долго работающие на Web-сервере приложения зачастую используют серверные СУБД для широкого спектра задач, особенно в транзакционных приложениях. В действительности, я считаю, что базы данных могли бы использоваться ещё большим количеством Web-сервисов, если бы разработчики не испытывали некоторых проблем, связанных с конфигурированием СУБД (но это тема для другой статьи). При всех своих многочисленных преимуществах обращение к [...]]]></description>
			<content:encoded><![CDATA[<p>Долго работающие на Web-сервере приложения зачастую используют серверные СУБД для широкого спектра задач, особенно в транзакционных приложениях. В действительности, я считаю, что базы данных могли бы использоваться ещё большим количеством Web-сервисов, если бы разработчики не испытывали некоторых проблем, связанных с конфигурированием СУБД (но это тема для другой статьи). При всех своих многочисленных преимуществах обращение к базам данных имеет, по крайней мере, одно узкое место &#8211; соединения.<br />
<span id="more-42"></span><br />
Соединения могут вызвать проблему двумя различными способами. Непосредственно им самим требуется небольшая ширина полосы пропускания для обмена информацией по сети и совсем немного процессорных ресурсов и памяти на машине с СУБД. Эти ресурсы минимальны, но пренебрегать ими нельзя. И что ещё важнее, СУБД предоставляет ограниченное количество соединений. В ситуации, когда основным клиентом СУБД является Web-приложение, не столь уж важно, будет ли это ограничение распространяться на каждый клиент или на общее количество соединений, поскольку основной объём операций создаёт единственный клиент базы данных (Web-сервер).</p>
<p>В этой статье я использую отличный адаптер psycopg2, хотя, в принципе, любая база данных Python, использующая DBAPI, будет работать аналогично. Единственное препятствие здесь заключается в том, что, по-моему, объектно-реляционные преобразования (object-relational mapping &#8211; ORM) скрывают слишком многое из того, что на самом деле происходит с обращениями к базе данных, накладывают ряд искусственных ограничений и, как правило, больше мешают программированию, а не облегчают его.</p>
<p><strong>Без пулов</strong><br />
В идеальном случае (без объединения соединений в пул) любой поток или процесс, в котором серверное Web-приложение подключается к базе данных, будет быстро получать соединение, создавать курсор, затем выполнять некоторые довольно быстрые операции, подтверждать или откатывать операции назад, после чего закрывать соединение. Достаточно просто, если всё идёт хорошо. Например:</p>
<p>Web-сервер, записывающий информацию в СУБД</p>
<blockquote>
<pre>
def AddData(foo, bar):
    "Эта функция обычно вызывается в собственном потоке"
    from psycopg import connect
    conn = connect("dbname=transact user=web host=server.mine")
    cur = conn.cursor()
    cur.execute("INSERT INTO userdata VALUES (%r, %r)" % (foo, bar))
    conn.commit()
    conn.close()</pre>
</blockquote>
<p>В данном примере показан не самый лучший исходный код, поскольку я мог бы поставить try/&#8217;except&#8217; с двух сторон от вызова connect(), а может быть даже try/finally, чтобы удостовериться, что происходит вызов conn.close(). Также я не использую сокрытие значения в DBAPI, хотя мог бы. И всё же, AddData() имеет все внешние признаки типичного обращения к базе данных.</p>
<p><strong>Что идет не так (и как это исправить)</strong><br />
В вышеприведённом исходном коде имеется несколько моментов, которые часто вызывают сложности. Одна из проблем состоит в том, что когда одновременно запущено множество потоков, на самом деле некоторые из них не смогут получить соединение, даже если ещё не достигнуто ограничение базы данных. Теоретически такая проблема не должна возникать, но в реальности это происходит. Другая проблема состоит в том, что когда достигнут предел для пула соединений базы данных — обычно из-за того, что потоки завершаются за большее время, чем ожидалось, либо вызывают исключения перед закрытием соединения — неудача в получении соединения становится гораздо более вероятной, чем хотелось бы.</p>
<p>Обе проблемы можно решить за счёт создания локального пула потоков. Пул соединений &#8211; это просто набор предварительно определённых соединений к базе данных, которые могут использоваться повторно и освобождаться каждым потоком с &#8220;виртуальным соединением&#8221;. При этом реальные соединения с базой данных закрываются относительно нечасто, тогда как их собранные в пул представители запрашиваются и освобождаются при каждой транзакции.</p>
<p>С одной стороны, объединение в пул предопределённых соединений из локального пула &#8211; это производимая в памяти операция, которая редко даёт сбой (кроме случая исчерпания лимита соединений), так как проблем со временем ожидания в сети или базе данных не возникает. С другой стороны, использование пула позволяет точно и локально управлять лимитом соединений. Если за ограничение количества соединений отвечает база данных, на уровне приложения невозможно определить количество используемых и свободных соединений; грубым приближением могло бы служить количество открытых потоков, но такой подход ненадёжен. Модуль antipool.py в фоновом режиме увеличивает и сокращает количество собранных в пул (реальных) соединений, чтобы не занимать реальные соединения дольше, чем необходимо. Для пользователя, однако, объединённое в пул соединение выглядит точно так же, как и реальное соединение с базой данных, за исключением того, что информация о наличии соединений в пуле является локальной, а максимальное количество соединений является параметром пула соединений, а не базы данных.</p>
<p>Даже при использовании antipool.py мне кажется более удобным заключить дальнейшую организацию пула в тонкую оболочку, а не использовать каждый раз все опции ConnectionPool. Более того, в самом общем случае при достижении предельного количества реальных соединений достаточно с помощью простой функции sleep() запросить ещё одно. Если множество вычислительных потоков действительно ведёт себя некорректно (никогда не закрывается), проблема сохранится, но, по крайней мере, будет легче выполнять отладку. Вот оболочка, которую я использую:</p>
<p>webapp_pool.py</p>
<blockquote>
<pre>
"""USAGE:
from webapp_pool import get_connection
conn, cur = get_connection()   # Might hang, but never raises
cur.execute(SQL)
conn.commit()
conn.release()    # 'conn.release()' not 'conn.close()'
"""
import psycopg2
from time import sleep
from antipool import ConnectionPool
from database import host, database, user, MAXCONNECTIONS
conn_pool = ConnectionPool(psycopg2,
                           host=host,
                           database=database,
                           user=user,
                           options={'maxconn':MAXCONNECTIONS})
def get_connection():
    got_connection = False
    while not got_connection:
        try:
            conn = conn_pool.connection()
            cur = conn.cursor()
            got_connection = True
        except psycopg2.OperationalError, mess:
            # Might log exception here
            sleep(1)
        except AttributeError, mess:
            # Might log exception here
            sleep(1)
    return conn, cur</pre>
</blockquote>
<p><strong>Заключение</strong><br />
В этой статье я использовал не требующий знания о базе данных модуль antipool.py. На мой взгляд, antipool.py весьма гибок; в нём есть дополнительные хорошо продуманные возможности, которые не обсуждались в этой короткой статье. Однако если вы планируете использовать встроенные в psycopg2 возможности или другие инструменты для организации пула, основные принципы использования соединений, объединённых в пулы, остаются прежними.</p>
<p><strong>Оригинал:</strong> <noindex><a href="http://www.ibm.com/developerworks/ru/library/wa-antipool/index.html?ca=dre-ru-1210">http://www.ibm.com/developerworks/ru/library/wa-antipool/index.html?ca=dre-ru-1210</a></noindex></p>
]]></content:encoded>
			<wfw:commentRss>http://boombick.org/blog/posts/42/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	<img style='margin:0;padding:0;border:0;' width='1px' height='1px' src="http://boombick.org/blog/wp-content/plugins/mystat/mystat.php?act=time_load&id=0&rnd=161066641" /></channel>
</rss>

