Function App deadlock when merge >10k entities to azure data table

Phillip 30 Reputation points
2026-06-08T12:01:59.0566667+00:00

Hey, We have a Function App in Flex Consumption Plan with nodejs that reads a CSV file (50k rows, 2 mb) add merges that row by row to a azure data table. The rows a added one after another. After ~10k rows the function stops logging to application insight, no error and nothing inside the invocations overview. I tried to figure out, if it happens because of any quotas, but memory is low and the duration is ~3 min.

When introducing batch, e.g. merge 10 rows in parallel, it works.

It might be a good solution to use batching, but I am curious what causes the issue. Any idea?

Info: I cannot use transaction based batching as I having unique partition keys

Appreciate your help!

Best,
Phillip

Azure Functions
Azure Functions

An Azure service that provides an event-driven serverless compute platform.


Answer accepted by question author
Rakesh Mishra 11,340 Reputation points Microsoft External Staff Moderator
2026-06-08T12:20:12.8066667+00:00

Hi @Phillip ,

I'm glad to hear that issue is resolved. However, we received an update from Product team on the root cause of the issue. Please find the details below. 

Revised Root Cause -- ATOL Premature Pod Drain (Known Bug)

After further analysis with the FlexConsumption team, the root cause has been revised. Stamp waws-prod-fra-039 is confirmed ATOL-migrated, and this CRI is attributed to a __known ATOL platform bug__where pods without active HTTP requests are drained prematurely (~6-7 min) instead of waiting the intended 10-12 min grace period.

Evidence From Telemetry

FunctionsLogs drain sequence for this app on Jun 25 (pod 0--816d66a0):

Time (UTC) Event
12:43:38 Pod starts up
12:44:20 Pod health: Healthy
12:51:02 MarkedForDrain -- only ~7.4 min after startup
12:51:03 Drain InProgress, outstandingInvocations=1 (timer function still running)
12:51:49 RpcFunctionInvocationDispatcher shutdown initiated
12:54:19 Drain retry (poll interval 1m, max retries 1)
12:54:51 InvocationResponse received (invocation completed in time)
12:55:03 Drain completed, outstandingInvocations=0

The host log also shows: 5 HandleInstanceStateTimeoutSeconds timeouts occurred, returning unchanged state -- confirming pod state transition was forced after repeated timeouts.

How This Explains the Symptoms
  1. "Function stops after ~10k rows (~3-10 min)": The ATOL drain kicks in at ~6-7 min, terminating the pod while the timer-triggered function is still processing rows sequentially. The exact row count where it stops depends on how fast each row is processed and when the drain fires.
  2. "No error logged": The drain is infrastructure-level. The pod is terminated gracefully from the platform's perspective, but the function never gets a chance to log an error -- the process is simply killed.
  3. "Batching works": Parallel batch processing completes in ~2-3 minutes, finishing well before the ~6-7 min drain window. Sequential processing exceeds the drain window and gets killed.
  4. "Same behavior with Queue output": The issue is not the output target -- it is the pod being drained while the invocation is still running.
  5. "Different chunk sizes show different thresholds": Different levels of parallelism change the total execution time, which determines whether the function finishes before or after the premature drain kicks in.
Known Bug Details
  • Bug: ATOL does not properly wait for pods without active HTTP requests (e.g., timer-triggered functions) before draining. Pods are drained at ~6 min instead of the intended 10-12 min grace period.
  • Scope: Affects ATOL-migrated stamps with timer-triggered (non-HTTP) functions that run longer than ~6 minutes.
  • Status: An internal work item for the known bug is being worked upon and will be deployed soon.
Updated Recommendations
  1. Short-term workaround: Use batch/parallel processing (Promise.all()) to complete execution within the ~6 min drain window. Customer has already confirmed this works.
  2. Platform fix needed: The ATOL drain logic needs to respect the full 10-12 min grace period for pods with active non-HTTP invocations (timer, queue, etc.).  They are working on the fix. 

Please let me know if there are any further question on this. If all good, please let me know if we can close the case. 

Was this answer helpful?

2 people found this answer helpful.

2 additional answers

Sort by: Newest
  1. Phillip 30 Reputation points
    2026-06-25T12:58:11.8166667+00:00

    Ok so I think, I saw what happend. It only happens when triggering functions manually. Seem like in that case it is always a http trigger instead of the configured one and the http trigger is just running into a deadlock, if it does not receive a response fast enough. Still it would be good to get some sort of error log and see the invocation inside the invocation with an error.

    Was this answer helpful?

    0 comments No comments

  2. Alex Burlachenko 25,285 Reputation points MVP Volunteer Moderator
    2026-06-22T11:13:33.05+00:00

    Phillip hi , thx for sharing urs issue here at Q&A portal,

    the function is getting stuck on the async/storage side, not hitting a hard memory or duration limit.

    Doing 50k table merges one-by-one is a lot of round trips. After ~10k ops, u may be running into slow retries, socket exhaustion, throttling, or the Node event loop getting backed up. App Insights can also go quiet if the worker is still alive but stuck waiting on pending I/O. The fact that 10 parallel merges works is a good clue. It means the storage account/table can handle the load, but the fully sequential pattern is prob too slow or getting trapped in retries somewhere.

    Since u have unique partition keys, normal transactional batch won’t help, yeah. https://learn.microsoft.com/en-us/rest/api/storageservices/performing-entity-group-transactions

    Best fix is what u already tested process in small chunks with controlled concurrency, like 10-50 at a time, plus retry/backoff for 429, 503, and timeouts. Don’t fire all 50k at once tho, that just moves the fire from the kitchen to the garage.

    Worth logging every 500 or 1000 rows with the current row number and catching/logging each failed merge. I’d also log SDK retry attempts if possible, because my guess is the function isn’t deadlocked, it’s sitting inside storage SDK retries or waiting on network I/O.

    For longer imports, Durable Functions or a queue-based fan-out is cleaner. Put rows/chunks onto a queue and let multiple function executions process them. https://learn.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-overview So yeah, batching is not just a workaround here. For 50k remote table writes, controlled concurrency is basically the right design.

    rgds,

    Alex

    &

    If my answer was helpful pls mark it and additional thx if u follow me at Q&A portal

    Was this answer helpful?


Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.