Hi all, we are currently facing a problem where a ...
# ask-community
g
Hi all, we are currently facing a problem where a flow run in Prefect Cloud takes significantly more time (factor 20/25) than the same flow run in Prefect Server. The flow retrieves data from a mysql database and inserts the data into a sql server database. When running the flow in Prefect Server, with Docker storage and a Docker agent I get around 1000 rows/sec insertions into the sql server db. While in Prefect Cloud, with Docker storage and a Kubernetes agent I get around 40 rows/sec insertions. Any ideas on the cause of this performance issue?
m
Hi. Please tell which one execution layer in both setups, and how you count rows per second? Total time of flow or exactly SQL part?
k
Hi @g.suijker! This sounds odd. Are Cloud and Server running the same Agent? Where does the SQL Server live? Are you using mapping for insertions?
g
@Marko Herkaliuk, timing is on the SQL part that inserts the rows. @Kevin Kho, for Cloud we use a Kubernetes Agent and for Server a Docker Agent. Think the SQL server is running on a remote server. What do you mean by mapping for insertions? I do not use a mapping task if that's what you mean.
m
maybe it's network delays? where you have the database deployed and where exactly the execution layer in both setup.
g
the weird thing is that according to my devops colleagues the Kubernetes agent is running closer to the remote server than the Docker agent running on my laptop. So the network delay should be less.
k
Yes that is what I meant by map. I was thinking it was something to do with concurrent connections. I think it’s really hard to pinpoint the issue here unless you run the Docker agent with Cloud in the same place you ran your Kubernetes agent. That will allow us to compare.
m
Can you look at the monitoring of your database when during the test queries? Maybe at that time, there was a different load on the database, or queries to the table in which the data was inserted.
👍 1
g
What also struck me was that after we switched to the new payment plan with unlimited concurrency, the performance on Cloud for this specific run deteriorated (running times increased by 2x). I do not understand how that could happen, since it is just one flow without mapping or dependent tasks.
@Marko Herkaliuk, will ask the dba to monitor while running the queries.
k
That is very puzzling. The cloud subscription shouldn’t affect your flow runtime (since Prefect doesn’t run the code). Was your flow completing at a consistent time before?
d
Copy code
When running the flow in Prefect Server, with Docker storage and a Docker agent I get around 1000 rows/sec insertions into the sql server db.
While in Prefect Cloud, with Docker storage and a Kubernetes agent I get around 40 rows/sec insertions.
Hi @g.suijker!
It sounds like your kubernetes cluster and docker container may have different resource constraints for execution
Would you mind sharing your Flow’s config for both cases? (the Run Configs, Results, Executor, etc)
Copy code
What also struck me was that after we switched to the new payment plan with unlimited concurrency, the performance on Cloud for this specific run deteriorated (running times increased by 2x). I do not understand how that could happen, since it is just one flow without mapping or dependent tasks.
Depending on where/when your Flow Runs are being executed in K8s, the increased concurrency may have put additional resource strain on your cluster. Your Flow’s config for the docker vs. k8s setups should help us debug 😄
g
I'm not sure what you mean exactly, but I do not set anything other than the label on within the run config.
Copy code
for Prefect Server:
storage = Docker(
        python_dependencies=["pyodbc", "pandas", "PyMySQL"],
        dockerfile = 'dockerfile',
        )

run_config = DockerRun(labels=['kube-office'])

with Flow(f"{flow_name}", storage=storage, run_config=run_config) as flow:

------------------
Prefect Cloud:
docker_storage = Docker(
        registry_url="xxx",
        image_name=f"prefect/{flow_name}",
        image_tag="latest",
        python_dependencies=["pyodbc", "pandas", "PyMySQL"],
        dockerfile = 'dockerfile',
        )
run_config = KubernetesRun(labels=['kube-office'])

with Flow(f"{flow_name}", storage=storage, run_config=run_config) as flow:

Using the same dockerfile for both
d
This makes sense, then! The default resource allocations (memory, CPU, etc) can be very different for Docker and Kubernetes. In your Kubernetes Run Config, try specifying
cpu_limit
,
cpu_request
,
mem_limit
, and
mem_request
to make sure the Kubernetes job that the Flow Run executes in has the proper resources available
If you find that specifying resources doesn’t solve your issue, please let us know
and feel free to follow up with any questions you have 😄
g
Thanks for the help all, will give it a shot.
d
👍
g
Hi guys, we tried a couple of different resource allocations. But none of them resulted in a significant performance increase. We ran some local tests on the same flow: 1. By running the flow with flow.run(), inserting one chunk of data into mssql took on average between 300 - 400ms. 2. By running the flow on Prefect Server, inserting one chunk of data into into mssql took on average between 2800 - 3000ms. Where is this difference coming from?
d
Hi @g.suijker have you configured any checkpointing or results for your tasks?
g
Hi @Dylan, I have set checkpointing to False on all tasks and did not specifiy any Result subclass
z
Are you measuring actual insertion time or are you measuring time per task?
and are you running an insertion per task or many insertions in a single task?
g
I'm measuring the actual insertion time and doing many insertions within a single task (chunking the full dataset and inserting each chunk)
z
I see. In your above message you noted that the insertion time was per-chunk. We'd expect a couple seconds of spin-up/down time for tasks that are reporting their states to the API. Are you still running one test on Kubernetes and one on Docker? It's going to be very hard to pin down where the difference is if you're running on entirely different architecture.
g
I understand, that's what making it difficult to track down the issue. Last tests we did with the insertion time per-chunk was on my local machine, where the test with flow.run() and with LocalRun & Local Storage via prefect server gave the same performance while the test with DockerRun & Docker storage was around 10x slower. I tried changing Docker resources but that didn't seem to make a difference.
We finally found the issue. The bad performance was due to closing the database connection after each insert query (this flow is doing a lot of insert query's within a loop). On my Windows laptop closing the connection did not result in a lower insert speed, but within a Docker container it did. So when running the flow with flow.run() there was no performance issue, but running the flow via Cloud or Server with Docker storage and a Docker/Kubernetes Agent there was. The query execution function first looked like:
Copy code
def sqlServerExecute(query, params=None):
    """Execute query with values provided to sqlServer"""

    dwh_connection_string = connection_string

    connection = pyodbc.connect(dwh_connection_string)

    try:
        cursor = connection.cursor()
        cursor.fast_executemany = True
        if params:
            cursor.executemany(query, params)
        else:
            cursor.execute(query)
        connection.commit()

    except (Exception, pyodbc.DatabaseError):
        connection.rollback()
        raise

    finally:
        cursor.close()
        connection.close()
Which we changed to, where the connection was made before the insert loop and closed after :
Copy code
def sqlServerExecute(query, connection, params=None):
    """Execute query with values provided to sqlServer"""
    try:
        cursor = connection.cursor()
        cursor.fast_executemany = True
        if params:
            cursor.executemany(query, params)
        else:
            cursor.execute(query)
        connection.commit()
    except (Exception, pyodbc.DatabaseError):
        connection.rollback()
        raise
    finally:
        cursor.close()
This resulted in equal insert performance locally (flow.run()) and Cloud with Docker storage and Kubernetes Agent.
When I look at the source code for the SqlServerExecute tasks https://github.com/PrefectHQ/prefect/blob/5de58efaba956b431335d99acab07eaf6a362e1b/src/prefect/tasks/sql_server/sql_server.py#L103, the connection is getting closed after each query as well. I guess more people might encounter the same issue then.
👍 1
To be more precise, the issue appeared due to Connection Pooling available on my Windows machine, but not within the Docker Container. Any ideas on how I can enable Connection Pooling within the Docker Container?
z
Very interesting! Thanks for reporting back @g.suijker -- I'm not sure how to enable connection pooling. Perhaps https://github.com/mkleehammer/pyodbc/issues/774 will be relevant?