Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I make PHP scripts timeout gracefully while waiting for long-running MySQL queries?

I have a PHP site which runs quite a lot of database queries. With certain combinations of parameters, these queries can end up running for a long time, triggering an ugly timeout message. I want to replace this with a nice timeout message themed according to the rest of my site style.

Anticipating the usual answers to this kind of question:

  1. "Optimise your queries so they don't run for so long" - I am logging long-running queries and optimising them, but I only know about these after a user has been affected.

  2. "Increase your PHP timeout setting (e.g. set_time_limit, max_execution_time) so that the long-running query can finish" - Sometimes the query can run for several minutes. I want to tell the user there's a problem before that (e.g. after 30 seconds).

  3. "Use register_tick_function to monitor how long scripts have been running" - This only gets executed between lines of code in my script. While the script is waiting for a response from the database, the tick function doesn't get called.

In case it helps, the site is built using Drupal (with lots of customisation), and is running on a virtual dedicated Linux server on PHP 5.2 with MySQL 5.

like image 856
Mark B Avatar asked May 14 '10 10:05

Mark B


1 Answers

There is no asynchronous mysql calls and no scope for forking lightweight threads.

While you could split your PHP code into two tiers and use a connection between them which you can invoke asynchronously, the problem with this approach is the DB tier will still try to run the query after the upper tier has given up on getting the results back - potentially blocking the DBMS for other users. (you're more likely to get more frequent requests for pages which are timing out).

You'll have the same problem if you push the timeout handling up into a reverse proxy sitting in front of the webserver.

The most sensible place to implement the timeout is the database itself - but AFAIK, mysql does not support that.

So next option is building a proxy between the PHP and the database - this could be self-contained - generating 2 lighweight threads for each request (1 to run the query, 2nd as a watchdog to kill the first if it takes too long) - but this not only requires writing code in a lnaguage which supports lightweight threads, but also defining a protocol for communications with the PHP.

However taking a different approach to the proxy model - you could spawn a separate PHP process using proc_open and set the stdout stream to be non-blocking - that way your PHP can continue to run and check to see if the proxy has run the query. If it times out, then as the parent of the proxy, it can signal it to shutdown (proc_terminate()) which should stop the query running on the database.

Certainly, it's going to mean a lot of development work.

It may prove a lot simpler to set up one or more slave DBMS to run your slow queries against - potentially with smart load balancing. Or look at other ways of making the slow queries go faster - like pre-consolidation.

HTH

C.

like image 89
symcbean Avatar answered Oct 20 '22 02:10

symcbean