from - ¿Cuál es un buen enfoque para administrar la conexión db en una aplicación Python de Google Cloud SQL(GAE)?
mysql connection google cloud (4)
Estoy aprendiendo Google App Engine y estoy tratando de encontrar un buen enfoque para administrar mi conexión de base de datos a una instancia de Google Cloud SQL (si no has usado GC-SQL, básicamente, es MySQL en la nube, con algunos limitaciones).
Estoy usando el entorno GAE de python (2.7) con el marco webapp2 para manejar las solicitudes. Sé que las preguntas frecuentes dicen que se recomienda que se haga una nueva conexión con el DB con cada solicitud, pero no sé cuál es la forma recomendada de cerrar la conexión. Cada vez que intento eliminar tablas durante el desarrollo, el GC-SQL se cuelga y "show processlist" muestra que hay un montón de procesos (probablemente porque no estoy cerrando el DB) y que uno de ellos está esperando un bloqueo ( es probable que el proceso intente soltar las tablas). Esto es molesto y me obliga a reiniciar la instancia GC-SQL (como reiniciar el servicio mysql-server, me imagino). También hay contratiempos de DB ocasionales que creo que están relacionados con el hecho de que realmente no estoy cerrando mi conexión de base de datos.
Entonces, por ejemplo, ¿debería tener un destructor en mi instancia de subclase webapp2.Requesthandler para desconectarme del DB? Los objetos GAE parecen almacenarse en caché a veces, por lo que también es algo a tener en cuenta. Supongo que podría simplemente conectar / consultar / desconectar para cada consulta, pero esto parece no ser óptimo.
Sé que esta es una pregunta vaga, pero espero que alguien que ha jugado en esta área pueda dar algunos consejos a mi manera.
¡Gracias por adelantado!
Actualización: Intenté implementar una envoltura alrededor de los métodos que necesitan un cursot, usando la respuesta de Shay como punto de partida. Estoy recibiendo errores GAE. Aquí hay una nueva pregunta específica para eso: ¿Cuáles son los límites de conexión para Google Cloud SQL de App Engine y cómo reutilizar mejor las conexiones de base de datos?
Aquí hay un ejemplo completo de la aplicación de ejemplo helloworld de la Guía de introducción . Se basa en fragmentos de Shay Erlichmen y JJC , pero esta versión es insegura.
Puedes usarlo así:
@with_db_cursor(do_commit = True)
def get(self, cursor):
cursor.execute(''SELECT guestName, content, entryID FROM entries'')
app.yaml
application: helloworld
version: 1
runtime: python27
api_version: 1
threadsafe: true
handlers:
- url: /.*
script: helloworld.app
helloworld.py
import cgi
import logging
import os
import threading
import webapp2
from google.appengine.api import rdbms
_INSTANCE_NAME = <name goes here>
def _db_connect():
return rdbms.connect(instance=_INSTANCE_NAME, database=''guestbook'')
_mydata = threading.local()
def with_db_cursor(do_commit = False):
""" Decorator for managing DB connection by wrapping around web calls.
Stores connections and open cursor count in a threadlocal
between calls. Sets a cursor variable in the wrapped function. Optionally
does a commit. Closes the cursor when wrapped method returns, and closes
the DB connection if there are no outstanding cursors.
If the wrapped method has a keyword argument ''existing_cursor'', whose value
is non-False, this wrapper is bypassed, as it is assumed another cursor is
already in force because of an alternate call stack.
"""
def method_wrap(method):
def wrap(self, *args, **kwargs):
if kwargs.get(''existing_cursor'', False):
# Bypass everything if method called with existing open cursor.
return method(self, None, *args, **kwargs)
if not hasattr(_mydata, ''conn'') or not _mydata.conn:
_mydata.conn = _db_connect()
_mydata.ref = 0
_mydata.commit = False
conn = _mydata.conn
_mydata.ref = _mydata.ref + 1
try:
cursor = conn.cursor()
try:
result = method(self, cursor, *args, **kwargs)
if do_commit or _mydata.commit:
_mydata.commit = False
conn.commit()
return result
finally:
cursor.close()
finally:
_mydata.ref = _mydata.ref - 1
if _mydata.ref == 0:
_mydata.conn = None
logging.info(''Closing conn'')
conn.close()
return wrap
return method_wrap
class MainPage(webapp2.RequestHandler):
@with_db_cursor(do_commit = True)
def get(self, cursor):
cursor.execute(''SELECT guestName, content, entryID FROM entries'')
self.response.out.write("""
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<title>My Guestbook!</title>
</head>
<body>""")
self.response.out.write("""
<table style="border: 1px solid black">
<tbody>
<tr>
<th width="35%" style="background-color: #CCFFCC; margin: 5px">Name</th>
<th style="background-color: #CCFFCC; margin: 5px">Message</th>
<th style="background-color: #CCFFCC; margin: 5px">ID</th>
</tr>""")
for row in cursor.fetchall():
self.response.out.write(''<tr><td>'')
self.response.out.write(cgi.escape(row[0]))
self.response.out.write(''</td><td>'')
self.response.out.write(cgi.escape(row[1]))
self.response.out.write(''</td><td>'')
self.response.out.write(row[2])
self.response.out.write(''</td></tr>'')
self.response.out.write("""
</tbody>
</table>
<br /> No more messages!
<br /><strong>Sign the guestbook!</strong>
<form action="/sign" method="post">
<div>First Name: <input type="text" name="fname" style="border: 1px solid black"></div>
<div>Message: <br /><textarea name="content" rows="3" cols="60"></textarea></div>
<div><input type="submit" value="Sign Guestbook"></div>
</form>
</body>
</html>""")
class Guestbook(webapp2.RequestHandler):
@with_db_cursor(do_commit = True)
def post(self, cursor):
fname = self.request.get(''fname'')
content = self.request.get(''content'')
# Note that the only format string supported is %s
cursor.execute(''INSERT INTO entries (guestName, content) VALUES (%s, %s)'', (fname, content))
self.redirect("/")
app = webapp2.WSGIApplication(
[(''/'', MainPage),
(''/sign'', Guestbook)],
debug=True)
Escribí un decorador para manejar la conexión SQL, no dudes en llamarte :)
# Here is how you use the decorator from below
# the open, commit, and close is done by the decorator
@need_cursor(do_commit = True)
def get(self, cursor, request): # cursor param is added by the decorator
execute_sql(cursor, sql)
def need_cursor(do_commit = False):
def method_wrap(method):
def wrap(*args, **kwargs):
conn = os.environ.get("__data_conn")
# Recycling connection for the current request
# For some reason threading.local() didn''t worked
# and yes os.environ suppose to be thread safe
if not conn:
conn = create_connection() # You need to implement this
os.environ["__data_conn"] = conn
os.environ["__data_conn_ref"] = 1
else:
os.environ["__data_conn_ref"] =
os.environ["__data_conn_ref"] + 1
try:
cursor = conn.cursor()
try:
result = method(cursor, *args, **kwargs)
if do_commit or os.environ.get("__data_conn_commit"):
os.environ["__data_conn_commit"] = False
conn.commit()
return result
finally:
cursor.close()
finally:
os.environ["__data_conn_ref"] =
os.environ["__data_conn_ref"] - 1
if os.environ["__data_conn_ref"] == 0:
os.environ["__data_conn"] = None
conn.close()
return wrap
return method_wrap
Este es mi enfoque, que considera posibles excepciones. Utilizo este enfoque en un entorno de producción y funciona bien:
def _create_connection(schema):
if (os.getenv(''SERVER_SOFTWARE'') and
os.getenv(''SERVER_SOFTWARE'').startswith(''Google App Engine/'')):
socket = ''/cloudsql/%s'' % env.DB_INSTANCE_NAME
return MySQLdb.connect(unix_socket=socket, user=env.DB_APP_USER,
passwd=env.DB_APP_PASS, db=schema)
else:
return MySQLdb.connect(host=''127.0.0.1'', port=3306,
user=env.DB_APP_USER, passwd=env.DB_APP_PASS,
db=schema)
def with_db(commit=False, schema=env.DB_SCHEMA_NAME):
def method_wrap(method):
@functools.wraps(method)
def wrap(self, *args, **kwds):
# If needed,a connection pool can be added here.
connection = _create_connection(schema)
try:
cur = connection.cursor()
self.cur = cur
self.conn = connection
result = method(self, *args, **kwds)
if commit:
connection.commit()
except OperationalError as e:
logging.error(''Operational error./r/nSQL exception: {},/r/n''
''Last Query: {}''.format(e, cur._last_executed))
if commit and connection.open:
connection.rollback()
raise
except MySQLError as e:
try:
warns = self.conn.show_warnings()
error = self.conn.error()
except:
warns = ""
error = ""
logging.error(''Try to rolling back transaction./r/nSQL exception: {},/r/n''
''Last Query: {},/r/nConn warn: {},/r/nError: {}''
.format(e, cur._last_executed, warns, error))
if commit and connection.open:
connection.rollback()
raise
except Exception as e:
logging.error(''Try to rolling back transaction. Non SQL exception: {0}''.format(e))
if commit and connection.open:
connection.rollback()
raise
finally:
connection.close()
return result
return wrap
return method_wrap
Puedes usarlo así:
@with_db(commit=True)
def update_user_phone(self, user, phone):
self.cur.execute(_SQL_UPDATE_USER_PHONE, (phone, user.id))
# add or replace existing user to cache
user.phone = phone
self._update_user_cache(user)
No estoy familiarizado con Google Cloud SQL, pero ¿no podrías usar un middleware WSGI para abrir y cerrar la conexión?