来自 App Engine 的 Google Cloud SQL 的连接限制是啥,以及如何最好地重用数据库连接?
Posted
技术标签:
【中文标题】来自 App Engine 的 Google Cloud SQL 的连接限制是啥,以及如何最好地重用数据库连接?【英文标题】:What are the connection limits for Google Cloud SQL from App Engine, and how to best reuse DB connections?来自 App Engine 的 Google Cloud SQL 的连接限制是什么,以及如何最好地重用数据库连接? 【发布时间】:2012-05-12 13:24:22 【问题描述】:我有一个使用 Google Cloud SQL 实例存储数据的 Google App Engine 应用。我需要我的实例能够通过 restful 调用一次为数百个客户端提供服务,每个调用都会导致一个或几个数据库查询。我已经包装了需要数据库访问的方法,并将数据库连接的句柄存储在 os.environ 中。请参阅this SO question/answer,了解我的基本做法。
但是,一旦有几百个客户端连接到我的应用并触发数据库调用,我就会开始在 Google App Engine 错误日志中收到这些错误(当然,我的应用返回 500):
could not connect: ApplicationError: 1033 Instance has too many concurrent requests: 100 Traceback (most recent call last): File "/base/python27_run
Google App Engine 和 Google Cloud SQL 的经验丰富的用户有什么建议吗?提前致谢。
这是我在需要数据库连接的方法周围使用的装饰器的代码:
def with_db_cursor(do_commit = False):
""" Decorator for managing DB connection by wrapping around web calls.
Stores connections and open connection count in the os.environ dictionary
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.
Based mostly on post by : Shay Erlichmen
At: https://***.com/a/10162674/379037
"""
def method_wrap(method):
def wrap(*args, **kwargs):
if kwargs.get('existing_cursor', False):
#Bypass everything if method called with existing open cursor
vdbg('Shortcircuiting db wrapper due to exisiting_cursor')
return method(None, *args, **kwargs)
conn = os.environ.get("__data_conn")
# Recycling connection for the current request
# For some reason threading.local() didn't work
# and yes os.environ is supposed to be thread safe
if not conn:
conn = _db_connect()
os.environ["__data_conn"] = conn
os.environ["__data_conn_ref"] = 1
dbg('Opening first DB connection via wrapper.')
else:
os.environ["__data_conn_ref"] = (os.environ["__data_conn_ref"] + 1)
vdbg('Reusing existing DB connection. Count using is now: 0',
os.environ["__data_conn_ref"])
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
dbg('Wrapper executing DB commit.')
conn.commit()
return result
finally:
cursor.close()
finally:
os.environ["__data_conn_ref"] = (os.environ["__data_conn_ref"] -
1)
vdbg('One less user of DB connection. Count using is now: 0',
os.environ["__data_conn_ref"])
if os.environ["__data_conn_ref"] == 0:
dbg("No more users of this DB connection. Closing.")
os.environ["__data_conn"] = None
db_close(conn)
return wrap
return method_wrap
def db_close(db_conn):
if db_conn:
try:
db_conn.close()
except:
err('Unable to close the DB connection.', )
raise
else:
err('Tried to close a non-connected DB handle.')
【问题讨论】:
app.yaml 中有 threadsafe: true 吗? 使用 'threadsafe: true' 不能很好地与使用 os.environ 一起工作,因为连接不能跨线程共享。有关线程安全的解决方案,请参阅我的回答 ***.com/a/10438622/1373093。 @JJC 清除了很多东西。如果您知道的话,cloud.google.com/sql/docs/diagnose-issues#limits 和 cloud.google.com/sql/pricing#v1-pricing 提到的每层 100 个待处理连接和 250 个并发连接之间有什么区别 @Kartik 抱歉,我不知道。 (但你应该是一个很好的用户,并为你在 Stack Overflow 上阅读的好问题/答案投票。:-)) 【参考方案1】:简短回答: 您的查询可能太慢了,并且 mysql 服务器没有足够的线程来处理您尝试发送的所有请求。
长答案:
作为背景,Cloud SQL 有两个相关的限制:
连接:这些对应于代码中的“conn”对象。服务器上有相应的数据结构。一旦这些对象过多(当前配置为 1000 个),最近最少使用的将自动关闭。当您下方的连接关闭时,您将在下次尝试使用该连接时收到未知连接错误 (ApplicationError: 1007)。 并发请求:这些是在服务器上执行的查询。每个执行的查询都会占用服务器中的一个线程,因此限制为 100。当并发请求过多时,后续请求将被拒绝并返回您收到的错误 (ApplicationError: 1033)听起来连接限制对您没有影响,但我想提一下以防万一。
对于并发请求,增加限制可能会有所帮助,但通常会使问题变得更糟。我们过去见过两种情况:
死锁:长时间运行的查询正在锁定数据库的关键行。所有后续查询都阻塞在该锁上。应用程序在这些查询上超时,但它们继续在服务器上运行,占用这些线程直到deadlock timeout 触发。 慢查询:每个查询都非常非常慢。这通常发生在查询需要临时文件排序时。应用程序超时并重试查询,而第一次尝试查询仍在运行并计入并发请求限制。如果你能找到你的平均查询时间,你可以估计你的 mysql 实例可以支持多少 QPS(例如,每个查询 5 ms 意味着每个线程 200 QPS。由于有 100 个线程,你可以做 20,000 QPS。50 ms每个查询意味着 2000 QPS。)您应该使用EXPLAIN 和SHOW ENGINE INNODB STATUS 来查看这两个问题中的哪一个。
当然,您也有可能只是在您的实例上驱动了大量流量而没有足够的线程。在这种情况下,无论如何,您可能会最大化实例的 cpu,因此添加更多线程将无济于事。
【讨论】:
谢谢肯。我没有看到任何错误 1007,并且我相信我正确地关闭了连接。我也不认为慢查询是罪魁祸首,因为所有查询都相当简单(没有连接,很少分组)表很小(水平和垂直),所有的都有索引(或等效的唯一约束)正在查找的列等。从仪表板状态来看,最大化实例上的 CPU 也不太可能成为问题。所以,我认为这将其缩小到僵局。我正在进一步研究它。如果可以的话,可以稍后联系您寻求建议。 在您上面的 sn-p 中,我看不到连接在哪里关闭。也许您没有包括所有内容? 另外,GROUP BY 仍然可以导致文件排序,所以即使是一个也可能会伤害到你。 是的,抱歉,复制/粘贴失败。我添加了关闭连接并结束功能的缺失行。谢谢! 从谷歌支持来到这里 - 清除了很多。谢谢【参考方案2】:我从文档中阅读并注意到有 12 个连接/实例限制:
查找“每个 App Engine 实例与 Google Cloud SQL 实例的并发连接不能超过 12 个”。在https://developers.google.com/appengine/docs/python/cloud-sql/
【讨论】:
谢谢。当我两年前发布这个问题时,我没有在文档中看到这个,但也许它就在那里,我只是错过了它。 :-D 无论如何,它有助于解释为什么我的项目无法扩展并最终不得不放弃。 :-( 那么你解决问题了吗?除了清除旧连接之外,您还能找出其他任何东西吗?最近有点遇到同样的问题以上是关于来自 App Engine 的 Google Cloud SQL 的连接限制是啥,以及如何最好地重用数据库连接?的主要内容,如果未能解决你的问题,请参考以下文章
来自 App Engine 的 Google Cloud SQL 的连接限制是啥,以及如何最好地重用数据库连接?
Google App Engine 上的传出 HTTP 请求位置
建立数据库连接时出错:Google App Engine Deploy
.jsp 文件不适用于 Google App Engine 留言簿教程