You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by ra...@apache.org on 2026/08/03 13:11:42 UTC
(airflow) branch v3-3-test updated: [v3-3-test] Fix Dag run duration stats crash on PostgreSQL 14+ (#70833) (#70964)
This is an automated email from the ASF dual-hosted git repository.
vatsrahul1001 pushed a commit to branch v3-3-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-3-test by this push:
new d172ab22a61 [v3-3-test] Fix Dag run duration stats crash on PostgreSQL 14+ (#70833) (#70964)
d172ab22a61 is described below
commit d172ab22a6163f558d606418471768b3e43a92d9
Author: github-actions[bot] <41...@users.noreply.github.com>
AuthorDate: Mon Aug 3 18:41:32 2026 +0530
[v3-3-test] Fix Dag run duration stats crash on PostgreSQL 14+ (#70833) (#70964)
PostgreSQL 14 changed EXTRACT(epoch FROM ...) to return numeric rather than double precision, so DagRun.duration is hydrated as Decimal on Postgres 14+. The duration-stats percentile interpolation then multiplies a Decimal by a float, which Python disallows, so the Dag run stats endpoint raises a TypeError for any Dag that has completed runs.
(cherry picked from commit 30006eb9f8c76b42beb349b5c1d859678a641253)
Co-authored-by: Pierre Jeambrun <pi...@gmail.com>
---
.../api_fastapi/core_api/services/ui/dag_run.py | 4 ++-
.../core_api/services/ui/test_dag_run.py | 41 ++++++++++++++++++++++
2 files changed, 44 insertions(+), 1 deletion(-)
diff --git a/airflow-core/src/airflow/api_fastapi/core_api/services/ui/dag_run.py b/airflow-core/src/airflow/api_fastapi/core_api/services/ui/dag_run.py
index a9ba75417c8..2a3ab183252 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/services/ui/dag_run.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/services/ui/dag_run.py
@@ -35,7 +35,9 @@ def compute_duration_stats(durations: list[float]) -> DurationStats | None:
if not durations:
return None
- sorted_d = sorted(durations)
+ # On Postgres 14+, DagRun.duration comes back as Decimal (EXTRACT(epoch ...) returns
+ # numeric); coerce to float so the percentile interpolation below (Decimal * float) works.
+ sorted_d = sorted(float(d) for d in durations)
counts = Counter(round(d) for d in sorted_d)
max_count = max(counts.values())
diff --git a/airflow-core/tests/unit/api_fastapi/core_api/services/ui/test_dag_run.py b/airflow-core/tests/unit/api_fastapi/core_api/services/ui/test_dag_run.py
new file mode 100644
index 00000000000..afa9c5277e2
--- /dev/null
+++ b/airflow-core/tests/unit/api_fastapi/core_api/services/ui/test_dag_run.py
@@ -0,0 +1,41 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from __future__ import annotations
+
+from decimal import Decimal
+
+import pytest
+
+from airflow.api_fastapi.core_api.services.ui.dag_run import compute_duration_stats
+
+
+@pytest.mark.parametrize("cast", [float, Decimal], ids=["float", "decimal"])
+def test_compute_duration_stats_handles_float_and_decimal(cast):
+ """Durations arrive as Decimal on Postgres 14+ (EXTRACT(epoch ...) returns numeric) and as
+ float/int on other backends; the stats must compute for either without raising TypeError."""
+ durations = [cast(v) for v in (10, 20, 20, 30, 40)]
+
+ stats = compute_duration_stats(durations)
+
+ assert stats is not None
+ assert stats.mean == 24.0
+ assert stats.mode == 20.0
+ assert stats.p50 == 20.0
+ assert stats.p90 == 36.0
+ assert stats.p95 == 38.0
+ assert stats.p99 == 39.6