Содержание


Советы по разработке Web-сайтов

Использование antipool.py для потокового доступа к базе данных Python

Экономия и совместное использование подключений к базе данных

Comments

Серия контента:

Этот контент является частью # из серии # статей: Советы по разработке Web-сайтов

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Советы по разработке Web-сайтов

Следите за выходом новых статей этой серии.

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

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

В этой статье я использую отличный адаптер psycopg2, хотя, в принципе, любая база данных Python, использующая DBAPI, будет работать аналогично. Единственное препятствие здесь заключается в том, что, по-моему, объектно-реляционные преобразования (object-relational mapping - ORM) скрывают слишком многое из того, что на самом деле происходит с обращениями к базе данных, накладывают ряд искусственных ограничений и, как правило, больше мешают программированию, а не облегчают его.

Без пулов

В идеальном случае (без объединения соединений в пул) любой поток или процесс, в котором серверное Web-приложение подключается к базе данных, будет быстро получать соединение, создавать курсор, затем выполнять некоторые довольно быстрые операции, подтверждать или откатывать операции назад, после чего закрывать соединение. Достаточно просто, если всё идёт хорошо. Например:

Листинг 1. Web-сервер, записывающий информацию в СУБД
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()

В данном примере показан не самый лучший исходный код, поскольку я мог бы поставить try/'except' с двух сторон от вызова connect(), а может быть даже try/finally, чтобы удостовериться, что происходит вызов conn.close(). Также я не использую сокрытие значения в DBAPI, хотя мог бы. И всё же, AddData() имеет все внешние признаки типичного обращения к базе данных.

Что идет не так (и как это исправить)

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

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

С одной стороны, объединение в пул предопределённых соединений из локального пула - это производимая в памяти операция, которая редко даёт сбой (кроме случая исчерпания лимита соединений), так как проблем со временем ожидания в сети или базе данных не возникает. С другой стороны, использование пула позволяет точно и локально управлять лимитом соединений. Если за ограничение количества соединений отвечает база данных, на уровне приложения невозможно определить количество используемых и свободных соединений; грубым приближением могло бы служить количество открытых потоков, но такой подход ненадёжен. Модуль antipool.py в фоновом режиме увеличивает и сокращает количество собранных в пул (реальных) соединений, чтобы не занимать реальные соединения дольше, чем необходимо. Для пользователя, однако, объединённое в пул соединение выглядит точно так же, как и реальное соединение с базой данных, за исключением того, что информация о наличии соединений в пуле является локальной, а максимальное количество соединений является параметром пула соединений, а не базы данных.

Даже при использовании antipool.py мне кажется более удобным заключить дальнейшую организацию пула в тонкую оболочку, а не использовать каждый раз все опции ConnectionPool. Более того, в самом общем случае при достижении предельного количества реальных соединений достаточно с помощью простой функции sleep() запросить ещё одно. Если множество вычислительных потоков действительно ведёт себя некорректно (никогда не закрывается), проблема сохранится, но, по крайней мере, будет легче выполнять отладку. Вот оболочка, которую я использую:

Листинг 2. webapp_pool.py
"""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

Заключение

В этой статье я использовал не требующий знания о базе данных модуль antipool.py. На мой взгляд, antipool.py весьма гибок; в нём есть дополнительные хорошо продуманные возможности, которые не обсуждались в этой короткой статье. Однако если вы планируете использовать встроенные в psycopg2 возможности или другие инструменты для организации пула, основные принципы использования соединений, объединённых в пулы, остаются прежними.


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Web-архитектура, SOA и web-сервисы
ArticleID=357830
ArticleTitle=Советы по разработке Web-сайтов: Использование antipool.py для потокового доступа к базе данных Python
publish-date=12102008