gpiccione-ship-it opened a new issue, #44241:
URL: https://github.com/apache/superset/issues/44241

   ### Bug description
   
   ### Summary
   
   `get_columns_description` executes the user's SQL **twice**, back to back on 
the same cursor.
   The result is read from the second execution, so the first is discarded 
work. Beyond the direct
   cost, this means a **statement-level timeout bounds only half the 
operation**.
   
   ### The code (6.1.0, `superset/connectors/sqla/utils.py`)
   
   ```python
   def get_columns_description(...):
       # TODO(villebro): refactor to use same code that's used by
       #  sql_lab.py:execute_sql_statements
       ...
               query = database.apply_limit_to_sql(query, limit=limit)
               mutated_query = database.mutate_sql_based_on_config(query)
               cursor.execute(mutated_query)
               db_engine_spec.execute(cursor, mutated_query, database)
               result = db_engine_spec.fetch_data(cursor, limit=limit)
   ```
   
   `cursor.execute(mutated_query)` and `db_engine_spec.execute(cursor, 
mutated_query, database)` run
   the same statement one after the other; `fetch_data` then reads the cursor 
left by the second. The
   first call appears to serve no purpose — the result set it produces is 
replaced by the next line.
   
   ### Measured (Superset 6.1.0, tenant database MariaDB 10.6.22)
   
   On a dataset whose SQL is a `UNION` over ~7.19M rows, with 
`max_statement_time = 300` in force on
   the connection:
   
   * the column probe ran for **557 s** and completed. This is not a broken 
cap: the cap is per
     **statement**, and there are two;
   * after wrapping the probe on our side, the same probe takes **0.171 s**.
   
   ### Why the second consequence matters more than the first
   
   An administrator who sets `max_statement_time = 300` believes they have 
capped what a dataset
   author can cost the database. They have capped **one statement**; the real 
ceiling is **N × cap**,
   and N is decided by the code, not by them. *A cap per statement does not 
limit an operation.*
   
   ### A related observation on the same path
   
   `apply_limit_to_sql` uses `limit_method = FORCE_LIMIT` and appends the limit 
**at the tail**. On a
   `UNION`, `LIMIT 0` therefore costs as much as `LIMIT 1` (> 20 s in our 
measurement), because the
   union is materialised first. Wrapping instead of appending — `SELECT * FROM 
(…) AS probe WHERE 1=0`
   — costs **0.005 s** and is equivalent for reading column metadata.
   
   We had measured `SELECT * FROM (…) t LIMIT 0` at 0.045 s and concluded the 
probe was cheap. It was
   not, because the code does not build that shape.
   
   ### Suggested fix
   
   * remove the `cursor.execute(mutated_query)` line immediately before
     `db_engine_spec.execute(...)`;
   * for metadata probing, wrap the statement rather than appending a `LIMIT`, 
so that rows can be
     discarded before being materialised.
   
   ### Note
   
   We are aware of #29885, which added the `mutate_sql_based_on_config` hook 
visible above — that hook
   is what allowed us to work around the cost locally. The point of this report 
is that the path
   itself does twice the work it needs.
   
   ### Screenshots/recordings
   
   _No response_
   
   ### Superset version
   
   6.1.0
   
   ### Python version
   
   Not applicable
   
   ### Node version
   
   Not applicable
   
   ### Browser
   
   Not applicable
   
   ### Additional context
   
   Data source being queried: MariaDB 10.6.22 (tenant database; the metadata 
database is MySQL 8.4)
   Deployment: Docker Swarm, custom image built FROM apache/superset:6.1.0
   Python: 3.10.21 — what the official apache/superset:6.1.0 image ships; the 
dropdown only offers
   3.11 and 3.12, so I selected "Not applicable"
   
   Customizations on this same code path, declared so the numbers can be read 
correctly:
   - a session-level cap is applied to tenant connections (max_statement_time = 
300) through
     DB_CONNECTION_MUTATOR. This is what made the double execution visible in 
the first place:
     the probe completed after 557 s while a 300 s per-statement cap was in 
force.
   - we now install a SQL_QUERY_MUTATOR that rewrites the probe statement, 
turning a trailing
     "LIMIT 0" into "SELECT * FROM (...) AS probe WHERE 1=0".
     The 557 s figure was measured WITHOUT that mutator; the 0.171 s figure is 
the same probe
     WITH it. Nothing else on this path is modified.
   
   ### Checklist
   
   - [x] I have searched Superset docs and Slack and didn't find a solution to 
my problem.
   - [x] I have searched the GitHub issue tracker and didn't find a similar bug 
report.
   - [x] I have checked Superset's logs for errors and if I found a relevant 
Python stacktrace, I included it here as text in the "additional context" 
section.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to