Azure Function App频繁挂起与死锁问题排查求助
Azure Function定时触发器频繁挂起/死锁问题排查
我使用Python SDK开发了一个带有多个Timer Trigger的Azure Function App,每个触发器通过Microsoft Graph API从Microsoft Entra ID拉取全量用户或组数据,写入部署在Azure上的PostgreSQL数据库。目前该应用每2-3天就会在“资源与健康”标签页出现挂起和死锁错误,我们使用的是EP1应用服务计划。
该任务需拉取约5万条用户数据,属于高I/O操作。本地运行及手动触发部署后的应用均正常,但自动触发时频繁挂起。请问我的代码是否存在根本性问题导致挂起与死锁?是否因为Postgres写入未采用异步方式?恳请提供排查思路。
相关代码示例
1. PostgreSQLConnector类(数据库连接与插入操作)
class PostgreSQLConnector: def __init__(self): self.host = os.getenv("PG_HOST") self.dbname = os.getenv("PG_DB") self.user = os.getenv("PG_USER") self.password = os.getenv("PG_PASS") self.sslmode = "require" def __enter__(self): self.connect() self.cursor = self.connection.cursor() return self def __exit__(self, exc_type, exc_value, traceback): self.cursor.close() self.close_connection() def insert_many_into_table(self, table_name, columns, entries): sql_query = f""" INSERT INTO {table_name} ({', '.join([''.join(name) for name, _ in columns])}) VALUES ({', '.join(['%s'] * len(columns))}) """ try: batch_size = 10000 batches = [entries[i:i + batch_size] for i in range(0, len(entries), batch_size)] commit_interval = 10 execute_many_counts = 0 batch_counter = 0 for i, batch in enumerate(batches): self.cursor.executemany(sql_query, batch) batch_counter += len(batch) execute_many_counts += 1 if execute_many_counts % commit_interval == 0: self.connection.commit() self.connection.commit() except Exception as e: self.connection.rollback() raise Exception(f"Error committing changes: {e}")
2. GraphHelper类(认证并从Entra拉取用户)
class GraphHelper: client_credential: ClientSecretCredential app_client: GraphServiceClient def __init__(self, tenant_id, client_id, client_secret): client_id = client_id tenant_id = tenant_id client_secret = client_secret self.client_credential = ClientSecretCredential( tenant_id, client_id, client_secret ) self.app_client = GraphServiceClient(self.client_credential) async def get_all_entra_users(self): request_configuration = UsersRequestBuilder.UsersRequestBuilderGetRequestConfiguration( query_parameters=UsersRequestBuilder.UsersRequestBuilderGetQueryParameters( select=self.default_user_select_properties, top=999 ) ) all_users = [] top_users = await self.app_client.users.get(request_configuration=request_configuration) for user in top_users.value: all_users.append(user) next_link = top_users.odata_next_link try: while next_link: top_users = await self.app_client.users.with_url(next_link).get(request_configuration=request_configuration) for user in top_users.value: all_users.append(user) next_link = top_users.odata_next_link except Exception as e: logging.info(e) return all_users
3. function_app.py中的触发器定义
tenant_id = os.getenv("AZURE_TENANT_ID") client_id = os.getenv("AZURE_CLIENT_ID") client_secret = os.getenv("AZURE_CLIENT_SECRET") graph_helper_instance = GraphHelper(tenant_id, client_id, client_secret) @app.function_name(name="allEntraUsersTimerTrigger") @app.timer_trigger(schedule="0 0 1 * * *", arg_name="allEntraUsersTimerTrigger", run_on_startup=False, use_monitor=False) async def all_entra_users_timer_trigger(allEntraUsersTimerTrigger: func.TimerRequest) -> None: try: users = await graph_helper_instance.get_all_entra_users() user_entries = [tuple([getattr(user, key) for key, _ in SchemaDefinitions.columns_users]) for user in users] with PostgreSQLConnector() as postgres_helper_instance: postgres_helper_instance.drop_table(SchemaDefinitions.table_name_users) postgres_helper_instance.create_table(SchemaDefinitions.table_name_users, SchemaDefinitions.columns_users) postgres_helper_instance.insert_many_into_table(SchemaDefinitions.table_name_users, SchemaDefinitions.columns_users, user_entries) except Exception as e: # Error handling
代码层面的潜在问题
1. 同步数据库操作阻塞异步事件循环
你的PostgreSQLConnector使用的是同步数据库驱动(如psycopg2),但Function的主逻辑是异步函数(async def)。在异步上下文中调用同步阻塞的IO操作,会占用事件循环的唯一线程,导致整个Function进程无法响应其他请求或触发器,最终引发挂起。这是代码中最核心的问题之一。
2. 全量表重建引发锁冲突
每次触发都执行drop_table + create_table + 全量插入:
- 该操作会持有表级排他锁,若多个触发器(如用户、组的定时任务)同时执行,会触发锁等待,进而引发死锁。
- 删除、创建、插入操作未放在同一事务中,不仅可能导致数据不一致,还会延长锁持有时间,加剧冲突概率。
3. 内存过载风险
get_all_entra_users会将5万条用户数据全部加载到内存的all_users列表中,对于EP1这类资源有限的应用服务计划(CPU/内存配额较低),极易引发内存耗尽,导致进程被Azure平台强制回收或挂起。
4. 异常处理缺失关键信息
- Graph API分页拉取时的异常仅做
logging.info(e),未中断流程或记录详细栈信息,可能导致数据缺失却无法定位根源。 - Function主逻辑的异常处理仅留注释,无具体日志记录或告警,无法追踪挂起前的错误细节。
排查与优化思路
1. 切换为异步PostgreSQL驱动
使用asyncpg替代同步驱动,重构PostgreSQLConnector为异步实现:
- 用
async with管理异步连接与事务 - 调用异步版本的
executemany和提交/回滚方法,确保数据库操作不阻塞事件循环
2. 优化数据库操作逻辑
- 放弃全量删除重建,改用增量同步:记录上次同步时间戳,仅拉取并更新变更数据;若必须全量同步,用
TRUNCATE TABLE替代DROP + CREATE,TRUNCATE锁开销更小、速度更快。 - 将
truncate/drop+create+insert放入同一事务,缩短锁持有时间,减少冲突。 - 调整批量参数:EP1资源有限,将
batch_size从10000降至1000-2000,且每批次提交一次(而非每10批次),降低事务资源占用。
3. 优化Graph API数据拉取
- 采用流式处理:分页拉取时,每获取一页数据就插入数据库,无需全部加载到内存,降低内存压力。
- 调整
top参数:将999改为500,减少单次拉取的数据量,进一步缓解内存占用。
4. 资源与监控优化
- 查看EP1计划的资源指标:在Azure门户检查Function的CPU、内存、数据库连接数,确认是否因资源耗尽挂起;若EP1不足,可临时升级到EP2/EP3验证问题是否解决。
- 开启Application Insights:追踪函数执行的日志、异常、性能数据,定位挂起发生在Graph拉取还是数据库插入阶段。
- 检查PostgreSQL锁状态:查询
pg_locks视图,查看死锁发生时的锁持有情况,确认冲突来源。
5. 触发器调度优化
- 若多个Timer Trigger执行时间重叠,调整调度规则,避免同时触发,减少数据库并发压力。
- 开启
use_monitor=True:让Azure定时器使用更可靠的监控机制,避免重复触发或调度异常。
内容的提问来源于stack exchange,提问作者Aleksandar Vuckovic
相关产品推荐
相关产品推荐

