You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 08:17:05