postgres google from engine compute app python mysql google-app-engine webapp2 google-cloud-sql

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?