【发布时间】:2021-02-06 18:57:27
【问题描述】:
应用启动内存
Partition of a set of 249162 objects. Total size = 28889880 bytes.
Index Count % Size % Cumulative % Referrers by Kind (class / dict of class)
0 77463 31 5917583 20 5917583 20 types.CodeType
1 30042 12 3774404 13 9691987 34 function
2 51799 21 3070789 11 12762776 44 tuple
3 15106 6 2061017 7 14823793 51 dict of type
4 5040 2 1928939 7 16752732 58 function, tuple
5 6627 3 1459448 5 18212180 63 type
6 5227 2 1346136 5 19558316 68 dict of module
7 16466 7 1026538 4 20584854 71 dict (no owner)
8 734 0 685897 2 21270751 74 dict of module, tuple
9 420 0 626760 2 21897511 76 function, module
100 次后续调用后的应用内存(与 SQLAlchemy 交互)
Partition of a set of 628910 objects. Total size = 107982928 bytes.
Index Count % Size % Cumulative % Referrers by Kind (class / dict of class)
0 23373 4 27673632 26 27673632 26 sqlalchemy.sql.schema.Column
1 141175 22 20904408 19 48578040 45 dict of sqlalchemy.sql.schema.Column
2 78401 12 5984371 6 54562411 51 types.CodeType
3 34133 5 4239726 4 58802137 54 function
4 64371 10 3661978 3 62464115 58 tuple
5 20034 3 2971710 3 65435825 61 dict of sqlalchemy.sql.schema.Table
6 13356 2 2297232 2 67733057 63 sqlalchemy.sql.base.ColumnCollection
7 15924 3 2133374 2 69866431 65 dict of type
8 5095 1 1946855 2 71813286 67 function, tuple
9 8714 1 1793696 2 73606982 68 type
通过 rcs 检测内存使用情况的辅助函数
def heap_results():
from guppy import hpy
hp = hpy()
h = hp.heap()
return Response(response=str(h.bytype),
status=200,
mimetype='application/json')
SQLAlchemy 的实现相当简单。使用 db.Model,我们正在为 ORM 创建一个类,并将表分解为 ORM 类的子函数。
在将最终响应返回给用户之前,我们是 gc.collect()。我们也在使用db.session.flush()、db.session.expunge_all()和db.session.close()。
我们已尝试删除db.session.* 命令以及gc.collect()。没有任何变化。
这是我们应用程序内存使用情况的时间序列图,正在重新启动的应用程序是您看到内存上限重置为稳定状态的地方:
模拟 HAProxy 的代码。
def reconnect():
hostnames = [Settings.SECRETS.get('PATRONI_HOST_C', ''), Settings.SECRETS.get('PATRONI_HOST_E', '')]
try:
master_node = HAProxy(hostnames=hostnames)
except (ValueError, TypeError, BaseException) as e:
# send an alert here though, use the informant!
raise e
else:
if master_node in ['None', None]:
raise ValueError("Failed to determined which server is acting as the master node")
my_app.config['SQLALCHEMY_DATABASE_URI'] = "postgresql://{}:{}@{}/{}".format(Settings.SECRETS['PATRONI_USER'],
Settings.SECRETS['PATRONI_PASSWORD'],
master_node,
Settings.SECRETS['PATRONI_DB'])
my_app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
'pool_recycle': 1800
}
new_db = SQLAlchemy(my_app)
new_db_orm = DBORM(new_db)
return new_db_orm
DBORM(经过修改以隐藏全部功能)的样子:
class DBORM(object):
def __init__(self, database):
self.database = database
self.owner_model = self.owner_model()
def create_owner_model(self):
db = self.database
class OwnerModel(db.Model):
__tablename__ = "owners"
owner_id = db.Column(UUID(as_uuid=True), unique=True,
nullable=False, primary_key=True)
client_owner = db.Column(db.String(255), unique=False, nullable=False)
admin_owner = db.Column(db.String(255), unique=False, nullable=False)
@staticmethod
def owner_validation(owner_model, owner=None):
if owner is not None:
owner_model = OwnerModel.get_owner_by_id(owner_id=owner_id,
return_as_model=True)
if owner_model is not None:
client_owner = owner_model.client_owner
admin_owner = owner_model.admin_owner
if client_owner is None and admin_owner is None:
return False
elif client_owner.lower() == owner.lower():
return True
elif admin_owner.lower() == owner.lower():
return True
else:
return False
else:
return None
else:
return None
通过 API 使用 OwnerModel 的示例
@api.route('/owners/{owner_id}')
def my_function(owner_id):
try:
dborm = reconnect()
except (AttributeError, KeyError, ValueError, BaseException) as e:
logger.error(f'Unable to get an owner model.')
logger.info(f'Garbage collector, collected: {gc.collect()}')
return Response(response=Exception.database_reconnect_failure(),
status=503,
mimetype='application/json')
else:
response = dborm.get_owner_by_id(owner_id=owner_id)
logger.info(f'Garbage collector, collected: {gc.collect()}')
return Response(response=json.dumps(response),
status=200,
mimetype='application/json')
【问题讨论】:
-
内存使用仅在应用程序完全重启时重置。由于我们很可能将 SQLAlchemy 确定为罪魁祸首,因此我们只想知道是否有办法关闭 SQLAlchemy 的引用,或者我们是否缺少某些东西。作为一个高度可扩展的应用程序,如果我们不能处理高 IO,它对我们的服务来说并不是一个好兆头。
-
您是否有机会反复创建新的
Table和Column对象?你真的应该为此提供一个minimal reproducible example。 -
好的,我更新了原始消息以包含我们如何实例化数据库 ORM 的一些代码示例。不幸的是,连接是 HA,所以我们每次与它交互时都需要重新连接,以确保我们获得主节点(写入权限)。
-
@IljaEverilä 见上文^
-
create_owner_model看起来确实在不断创建同一模型的新版本。这种情况经常发生吗?根据您的设置,这些不会是 GCd,因为 SQLAlchemyMetaData和声明性基础都保存已创建表和模型的注册表。这是因为您的应用程序应该只创建一次,如果您需要从一个数据库实例更改为另一个,只需交换Engine。通常您也不需要这样做,并且每个应用程序生命周期只需创建一次引擎。
标签: python flask memory-leaks sqlalchemy