Skip to content

Add deferrable support to InfluxDB3Operator - #71976

Open
subhramit wants to merge 26 commits into
apache:mainfrom
subhramit:deferrable-influxdb3-operator
Open

Add deferrable support to InfluxDB3Operator#71976
subhramit wants to merge 26 commits into
apache:mainfrom
subhramit:deferrable-influxdb3-operator

Conversation

@subhramit

@subhramitsubhramit commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Closes#67107
Follow-up to #58929

Add deferrable support to InfluxDB3Operator

When deferrable=True, the operator now defers to a new InfluxDB3QueryTrigger so the worker slot is released while the query runs. The trigger awaits InfluxDBClient3.query_async() and resumes the task with the same JSON-serializable record shape returned by the synchronous path.
Also update provider metadata and documentation to surface the trigger, add a deferrable example, and bump the influxdb3-python minimum version to >=0.12.0 (the release that introduced query_async()).

Note that this deviates from the issue's proposed API as I did not add a "poll_interval"/polling loop. InfluxDB 3's query_async streams the full result over one Arrow Flight call, which means there's no server-side job to poll a status on, unlike Snowflake/BigQuery/Redshift. The trigger awaits the query once and emits a single event.
This is more similar in shape to SQLExecuteQueryTrigger (used by GenericTransfer's deferrable path) - which also has a single await yielding one TriggerEvent with no poll_interval for the same reason:

asyncdefrun(self) ->AsyncIterator[TriggerEvent]:
try:
self.log.info("Extracting data from %s", self.conn_id)
self.log.info("Executing: \n %s", self.sql)
self.log.info("Reading records from %s", self.conn_id)
results=awaitself._get_records()
self.log.info("Reading records from %s done!", self.conn_id)
self.log.debug("results: %s", results)
yieldTriggerEvent({"status": "success", "results": results})

Have added tests for the new deferrable operator path, trigger serialization/execution, and async hook behavior.

MWE

Tested on a setup of influxdb:3-core with a file object store, two rows written to a home table over the v3 line-protocol endpoint, read back through the influxdb3 CLI:

image

The async client on its own against the same server, to isolate it from Airflow:

c=InfluxDBClient3(host="http://localhost:8181", token=TOKEN, database="airflow_demo")
print(asyncio.run(c.query_async("SELECT * FROM home")).to_pydict())
image

Then through the operator - the deferrable task alongside the sync one running the same query, so the two paths could be compared:

blocking=InfluxDB3Operator(task_id="query_blocking", sql=SQL, influxdb3_conn_id=CONN)
deferred=InfluxDB3Operator(task_id="query_deferred", sql=SQL, influxdb3_conn_id=CONN, deferrable=True)
blocking>>deferred

query_deferred deferred to the triggerer, the trigger fired a success event, and the task resumed from QUEUED with the same two records the sync task returned:

image
Was generative AI tooling used to co-author this PR?
  • Yes (please specify the tool below)

Generated-by: GPT-5.4 following the guidelines
All code changes done as a result (and also this description) were manually driven, edited reviewed by me.

Signed-off-by: subhramit <subhramit.bb@live.in>
Signed-off-by: subhramit <subhramit.bb@live.in>
Signed-off-by: subhramit <subhramit.bb@live.in>
@subhramitsubhramit changed the title Add deferrable mode to InfluxDB3OperatorAdd deferrable mode to InfluxDB3OperatorAug 22, 2026
@subhramit

subhramit commented Aug 22, 2026

Copy link
Copy Markdown
ContributorAuthor

Ah, I think I missed a lowest-direct-dependencies edge case here. The spellcheck also fails, will address both in the next commit.

Update - fixed.

Signed-off-by: Subhramit Basu <subhramit.bb@live.in>
@subhramit

subhramit commented Aug 23, 2026

Copy link
Copy Markdown
ContributorAuthor

cc'ing @eladkal for visibility, in case any specific codeowners are to be requested for review.

@eladkal

Copy link
Copy Markdown
Contributor

cc @arpitrathore can you take a look?

@subhramit

subhramit commented Aug 23, 2026

Copy link
Copy Markdown
ContributorAuthor

cc @arpitrathore can you take a look?

+1 I hope... I have cc'ed them in the issue as well, but they seem to have been away from GitHub since quite some time now after their last comment expressing that they wanted to work on this.

@subhramit

subhramit commented Aug 25, 2026

Copy link
Copy Markdown
ContributorAuthor

cc @arpitrathore can you take a look?

+1 I hope... I have cc'ed them in the issue as well, but they seem to have been away from GitHub since quite some time now after their last comment expressing that they wanted to work on this.

Adding some other recent committers in case the PR gets lost

@bbovenzi, @Lee-W, @hussein-awala,@kaxil,
@amoghrajesh, @o-nikolas,@uranusjr, @jason810496, @vincbeck,
@jedcunningham, @potiuk if they can help review

After this is done, I wish to work on #67109 as a follow-up.

@eladkaleladkal left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left few comments.
Can you please confirm if this was tested against a real influx environment?

Comment threadproviders/influxdb/src/airflow/providers/influxdb/hooks/influxdb3.py Outdated
Comment on lines +43 to +47
:param deferrable: Run the query from the triggerer instead of holding a worker slot for its
duration. Requires ``influxdb3-python>=0.12.0``. Note that InfluxDB 3 streams results over
Arrow Flight rather than exposing a job that can be polled, so the whole result set still flows back
through XCom -- deferring helps with long-running queries returning modest result sets
(aggregations, freshness probes), not with very large extracts.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please cleanup here. Explain just what is needed.
Requires influxdb3-python>=0.12.0 is not helpful. The provider already set this min version so users don't need to make sure they hae it.

To be honest I am not sure I understand what this warning means.

@subhramitsubhramitAug 27, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was just meaning to say that since the InfluxDB 3 client doesn’t expose a poll-based async job model here (see PR desc for details), deferrable=True releases the worker slot while the query runs in the triggerer, but the full result still comes back through XCom when the task resumes. So the main benefit is for long-running queries with relatively small result sets, not for very large extracts

Refined with better wording now

Comment on lines +63 to +64
InfluxDB 3 streams query results over a single Arrow Flight call rather than exposing a job that
can be polled, so the trigger awaits the query once instead of polling at an interval, and there

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be a statement over how Influxdb operate. Can you please update the info with a link to the Influx docs? This is needed to confirm that are assumptions are right and in case that Influx change how it works we can track the change/

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added, and improved the statements a bit for clarity

useful for long-running queries with small-to-moderate result sets. For very large extracts,
keep using :class:`~airflow.providers.influxdb.hooks.influxdb3.InfluxDB3Hook` from a Python task.

Deferrable mode requires ``influxdb3-python>=0.12.0`` and a running ``triggerer``.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be removed

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@subhramit

subhramit commented Aug 27, 2026

Copy link
Copy Markdown
ContributorAuthor

Can you please confirm if this was tested against a real influx environment?

Yeah.
Can give an MWE as well.

Edit - added to the PR description.

@subhramit
subhramit requested a review from eladkalAugust 27, 2026 20:14
Signed-off-by: subhramit <subhramit.bb@live.in>
Signed-off-by: Subhramit Basu <subhramit.bb@live.in>
Signed-off-by: subhramit <subhramit.bb@live.in>
Signed-off-by: subhramit <subhramit.bb@live.in>
@subhramit

subhramit commented Aug 27, 2026

Copy link
Copy Markdown
ContributorAuthor

@eladkal I noticed a separate dependency issue while working on this. InfluxDB3Hook.query() and query_async() import pandas at runtime, but the provider doesn't declare a it as an extra or convert missing-pandas imports into an optional-feature error, which means users would hit a ModuleNotFoundError when using those query paths if they don't have pandas installed.

I took the opportunity and covered that in 1b7294b by adding the pandas extra and raising AirflowOptionalProviderFeatureException from those paths when pandas isn’t installed, like how it’s done in

importpandasaspd
exceptImportErrorase:
fromairflow.providers.common.compat.sdkimportAirflowOptionalProviderFeatureException
raiseAirflowOptionalProviderFeatureException(e)

and also in transfers/sql_to_s3.py

@subhramitsubhramit changed the title Add deferrable mode to InfluxDB3OperatorAdd deferrable support to InfluxDB3OperatorAug 27, 2026
Signed-off-by: Subhramit Basu <subhramit.bb@live.in>
@subhramit

subhramit commented Aug 29, 2026

Copy link
Copy Markdown
ContributorAuthor

@eladkal@arpitrathore gentle nudge.

@ashbashb left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pr also needs to add async get_conn support please(as fetching a connection on a worker requires network traffic so can block)

(Non exhaustive review)

Comment on lines +66 to +67
single async query call. The trigger therefore awaits one query call instead of polling at an
interval, and there is no ``poll_interval`` parameter.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure we need to mention there's no poll interval in the docs. It makes sense in the pr as there issue talked about it, but just going to the docs most people will have no context on this (and its not universal on deferrable operators either )

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair, removed

from airflow.models import Connection


class InfluxDB3AsyncQueryNotAvailableError(RuntimeError):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given we depend on an updated version of theof influx client is this possible to hit?

@subhramitsubhramitAug 30, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was being defensive but yeah ideally this should never hit. Removed.

Comment threadproviders/influxdb/src/airflow/providers/influxdb/hooks/influxdb3.py Outdated
f"Result type: {type(result).__module__}.{type(result).__name__}"
)

return await asyncio.to_thread(_convert_dataframe_to_records, result)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the different return type on Async? The sync path returns the pandas object directly doesn't it?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Serialization constraint, but youre right it's in the wrong place, the DataFrame cant cross a TriggerEvent, so I converted in the hook. It should be the trigger's job. Moving it there.

from airflow.sdk.definitions.context import Context


def _convert_dataframe_to_records(dataframe: pd.DataFrame) -> list[dict[str, Any]]:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplication from the hook fn

@subhramitsubhramitAug 30, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eladkal wanted this to be private in #71976 (comment), so I wasn't sure what to do as the only other option was to create a private helper utils file for this tiny one-line method.

Maybe when I work on the sensor I'll need it anyway so I'll move this to a file now.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since python allows it, I relaxed module encapsulation a bit, but only inside the provider's internal implementation to not create a new file and still reuse it in e6a177c. See if that is alright or we should indeed go with a new file

:param query: SQL query string
:return: List of dictionaries representing query results
"""
client = await asyncio.to_thread(self.get_conn)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those does move it off the main loop but isn't the best way of doing this.

Please look in other hooks for an awit self.aget_conn() or similar

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was unaware of this, thanks. Changed to that async-hook pattern

Comment threadproviders/influxdb/tests/system/influxdb/example_influxdb3.py Outdated
task_id="query_data_deferrable",
sql="SELECT * FROM \"temperature\" WHERE time > now() - INTERVAL '1 hour'",
influxdb3_conn_id="influxdb3_default",
deferrable=True,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferrable is true for other operators that support it isn't it?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not by default, in providers the usual pattern is tp follow the global Airflow setting and fall back to false (see airbyte example)

Keeping it true here also matches other provider system examples for the async variant (see S3 and livy examples)

"""Test async query with InfluxDB 3.x."""
pd = pytest.importorskip("pandas")

self.influxdb3_hook.client = mock.Mock()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If at all reasonable is rather we didn't mock the client, but instead the http transport/reaponse underneath it.

At the very least, this mock needs a spec parameter to enforce its "shape"

@subhramitsubhramitAug 30, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, enforced the shape in the mocks.

P.S. I did have a look at pushing the seam lower but for query()/ query_async()
that would mean mocking upsteam client internals rather than the hook contract. IMO point of these tests are to validate our interaction with InfluxDBClient3 and the DataFrame/type handling around it, so I kept them at the client boundary

subhramitand others added 7 commits August 30, 2026 18:11
Co-authored-by: Ash Berlin-Taylor <ash_github@firemirror.com>
Co-authored-by: Ash Berlin-Taylor <ash_github@firemirror.com>
Signed-off-by: subhramit <subhramit.bb@live.in>
Signed-off-by: subhramit <subhramit.bb@live.in>
Signed-off-by: subhramit <subhramit.bb@live.in>
Signed-off-by: subhramit <subhramit.bb@live.in>
Signed-off-by: subhramit <subhramit.bb@live.in>
@subhramit
subhramit requested a review from ashbAugust 30, 2026 13:47
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

InfluxDB3Operator: add deferrable variant

3 participants

@subhramit@eladkal@ashb