Implement better and more solar specific pool for PG
Supported parameters - pool_size how big is the pool - pool_overflow how many overflow connections should be allowed Change-Id: Iba92eb94754ef7314bc3d4bf0e413e7d61e027f8
This commit is contained in:
parent
e1795c1f63
commit
8add0260f2
@ -18,6 +18,6 @@
|
|||||||
- shell: docker exec vagrant_pg_1 psql -h localhost -U solar postgres -c "CREATE DATABASE solar WITH owner=postgres LC_COLLATE='C' TEMPLATE template0;"
|
- shell: docker exec vagrant_pg_1 psql -h localhost -U solar postgres -c "CREATE DATABASE solar WITH owner=postgres LC_COLLATE='C' TEMPLATE template0;"
|
||||||
- lineinfile:
|
- lineinfile:
|
||||||
dest: /.solar_config_override
|
dest: /.solar_config_override
|
||||||
line: "solar_db: postgresql://solar:solar@127.0.0.1:5432/solar"
|
line: "solar_db: postgresql://solar:solar@127.0.0.1:5432/solar?solar_pool=True"
|
||||||
state: present
|
state: present
|
||||||
create: yes
|
create: yes
|
||||||
|
@ -81,6 +81,7 @@ elif _connection.mode == 'postgresql':
|
|||||||
opts["password"] = _connection.password
|
opts["password"] = _connection.password
|
||||||
# TODO: allow set Postgresql classes from playhouse
|
# TODO: allow set Postgresql classes from playhouse
|
||||||
opts.setdefault('db_class', 'PostgresqlDatabase')
|
opts.setdefault('db_class', 'PostgresqlDatabase')
|
||||||
|
opts.setdefault('solar_pool', True)
|
||||||
client = SqlClient(_connection.database,
|
client = SqlClient(_connection.database,
|
||||||
**opts)
|
**opts)
|
||||||
else:
|
else:
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
import uuid
|
import uuid
|
||||||
|
import yaml
|
||||||
|
|
||||||
from peewee import BlobField
|
from peewee import BlobField
|
||||||
from peewee import CharField
|
from peewee import CharField
|
||||||
@ -416,6 +417,16 @@ class SqlClient(object):
|
|||||||
fromlist = db_class_str
|
fromlist = db_class_str
|
||||||
__import__(mod, fromlist=[fromlist])
|
__import__(mod, fromlist=[fromlist])
|
||||||
db_class = getattr(sys.modules[mod], fromlist)
|
db_class = getattr(sys.modules[mod], fromlist)
|
||||||
|
args = map(yaml.safe_load, args)
|
||||||
|
kwargs = {k: yaml.safe_load(v) if isinstance(v, basestring) else v
|
||||||
|
for k, v in kwargs.items()}
|
||||||
|
# use_pool is defaulted to True with PG backend
|
||||||
|
use_pool = kwargs.pop('solar_pool', False)
|
||||||
|
if use_pool:
|
||||||
|
from solar.dblayer.sql_pool import get_pooled_db
|
||||||
|
db_class = get_pooled_db(db_class,
|
||||||
|
kwargs.pop('solar_pool_size', 7),
|
||||||
|
kwargs.pop('solar_pool_overflow', 20))
|
||||||
session = db_class(*args, **kwargs)
|
session = db_class(*args, **kwargs)
|
||||||
self._sql_session = session
|
self._sql_session = session
|
||||||
self.buckets = {}
|
self.buckets = {}
|
||||||
|
120
solar/dblayer/sql_pool.py
Normal file
120
solar/dblayer/sql_pool.py
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2015 Mirantis, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import gevent
|
||||||
|
|
||||||
|
from collections import deque
|
||||||
|
from gevent.lock import RLock
|
||||||
|
from gevent.lock import Semaphore
|
||||||
|
|
||||||
|
|
||||||
|
class FakeLock(object):
|
||||||
|
"""A lock that does not lock, used to replace peewee connection lock"""
|
||||||
|
|
||||||
|
def acquire(self, *args, **kwargs):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def release(self, *args, **kwargs):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __exit__(self, *args, **kwargs):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<%s at 0x%x>" % (self.__class__.__name__, id(self))
|
||||||
|
|
||||||
|
|
||||||
|
def get_pooled_db(orig_db, pool_size, pool_overflow):
|
||||||
|
"""Wrapper that makes enables pool in DB
|
||||||
|
|
||||||
|
This is wrapper for peewee database that has separate own logic of
|
||||||
|
connection pooling, adjusted for our use case
|
||||||
|
"""
|
||||||
|
db_lock = RLock()
|
||||||
|
pool = deque()
|
||||||
|
pool_size_overflow = pool_size + pool_overflow
|
||||||
|
|
||||||
|
_tmp_lock = RLock()
|
||||||
|
_conn_count = [0, 0, 0]
|
||||||
|
s = Semaphore(pool_size_overflow)
|
||||||
|
|
||||||
|
def close_conn(g):
|
||||||
|
g._db_obj._close(g._db_conn)
|
||||||
|
|
||||||
|
def pooled__connect(obj, *args, **kwargs):
|
||||||
|
ret = None
|
||||||
|
n = 10
|
||||||
|
max_tries = n - 1
|
||||||
|
for i in xrange(n):
|
||||||
|
if not s.acquire(blocking=True, timeout=0.1):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
ret = pool.pop()
|
||||||
|
c = ret.cursor()
|
||||||
|
try:
|
||||||
|
c.execute('SELECT 1;')
|
||||||
|
finally:
|
||||||
|
c.close()
|
||||||
|
except IndexError:
|
||||||
|
with _tmp_lock:
|
||||||
|
_conn_count[0] += 1
|
||||||
|
_conn_count[1] += 1
|
||||||
|
ret = obj._orig__connect(*args, **kwargs)
|
||||||
|
except Exception:
|
||||||
|
s.release()
|
||||||
|
if i == max_tries:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
if ret is not None:
|
||||||
|
break
|
||||||
|
if ret is None:
|
||||||
|
raise Exception('To many connections')
|
||||||
|
|
||||||
|
cg = gevent.getcurrent()
|
||||||
|
if hasattr(cg, 'link'):
|
||||||
|
cg._db_conn = ret
|
||||||
|
cg._db_obj = obj
|
||||||
|
cg.link(close_conn)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def pooled__close(obj, conn):
|
||||||
|
with _tmp_lock:
|
||||||
|
_conn_count[2] += 1
|
||||||
|
|
||||||
|
with db_lock:
|
||||||
|
if len(pool) < pool_size:
|
||||||
|
conn.rollback() # if anything left, rollback
|
||||||
|
pool.append(conn)
|
||||||
|
s.release()
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
s.release()
|
||||||
|
with _tmp_lock:
|
||||||
|
_conn_count[0] -= 1
|
||||||
|
|
||||||
|
return obj._orig__close(conn)
|
||||||
|
|
||||||
|
l = locals()
|
||||||
|
for x in ('_connect', '_close'):
|
||||||
|
orig = getattr(orig_db, x)
|
||||||
|
setattr(orig_db, "_orig_%s" % x, orig)
|
||||||
|
setattr(orig_db, x, l['pooled_%s' % x])
|
||||||
|
|
||||||
|
orig_db._conn_lock = FakeLock()
|
||||||
|
return orig_db
|
Loading…
Reference in New Issue
Block a user