Page MenuHomePhabricator

[BLZMI] 🐛 Proxy service does not take the timeout parameter
Closed, ResolvedPublic

Description

timeout parameters are not passed by the proxy layer to the qlever endpoint.

A/C

  • Timeout parameters are passed to the QLever API

Details

Event Timeline

trueg changed the task status from Open to In Progress.Aug 11 2026, 2:51 PM
trueg claimed this task.

@karapayneWMDE what timeout values are you using? I suppose you want to raise it from the default?

No, we want to lower it – see $wgWBQualityConstraintsSparqlMaxMillis in InitialiseSettings.php, we set it to just 5 s (rather than 60 s) because we expect queries to be fast (and were worried about overloading the query service; also, PHP requests have their own time limit anyway, and we may run multiple SPARQL requests in a single PHP request for constraint checks).

@Lucas_Werkmeister_WMDE we are currently pondering whether to use a query parameter or a custom header for the timeout. WDYT?

A query parameter seems more usable to me. (I’m pretty sure I’ve manually added &timeout= to query URLs in the browser in the past, where I wouldn’t be able to add a header.) I doubt anything in SPARQL 1.1 Protocol forbids other (nonstandard) parameters.

Change #1327630 had a related patch set uploaded (by Lerickson; author: Lerickson):

[operations/deployment-charts@master] Bump wdqs-proxy 0.4.0 -> 0.5.0.

https://gerrit.wikimedia.org/r/1327630

Change #1327630 merged by jenkins-bot:

[operations/deployment-charts@master] Bump wdqs-proxy 0.4.0 -> 0.6.0.

https://gerrit.wikimedia.org/r/1327630

Hi,
This should be working now. I've updated the proxy running in prod to include this change: https://gitlab.wikimedia.org/repos/wikidata-platform/wdqs/wdqs-proxy/-/merge_requests/58

Let me know if you have any issues, thanks!

What’s the expected syntax? &timeout=1 produces a JSON error ({"errorType":"bad_request","message":"Invalid timeout value: 1"}), while &timeout=1s produces a 400 Bad Request generic error page.

Uh, also I just noticed something… the parameter we send in SparqlHelper::runQuery() is maxQueryTimeMillis, not timeout. (Introduced in this change with improvements in this follow-up.) I think I might have just imagined the timeout parameter name? 🤦️

(Or is that found anywhere else and WBQC sends maxQueryTimeMillis for some other reason?)

What’s the expected syntax? &timeout=1 produces a JSON error ({"errorType":"bad_request","message":"Invalid timeout value: 1"}),

How are you encoding the request?

&timeout=1s Should be correct.

Here's a smoke test with a GET

$ curl -G 'https://query-next.wikidata.org/sparql' --data-urlencode 'query=SELECT * WHERE { ?s ?p ?o } LIMIT 1'  --data-urlencode 'timeout=1s'  -H 'Accept: application/sparql-results+json' -H 'User-Agent: <redacted>' | jq
  % Total    % Received % Xferd  Average Speed  Time    Time    Time   Current
                                 Dload  Upload  Total   Spent   Left   Speed
100    330 100    330   0      0   1954      0                              0
{
  "head": {
    "vars": [
      "s",
      "p",
      "o"
    ]
  },
  "results": {
    "bindings": [
      {
        "s": {
          "type": "uri",
          "value": "http://www.wikidata.org/entity/Q10000000"
        },
        "p": {
          "type": "uri",
          "value": "http://wikiba.se/ontology#identifiers"
        },
        "o": {
          "datatype": "http://www.w3.org/2001/XMLSchema#int",
          "type": "literal",
          "value": "0"
        }
      }
    ]
  },
  "meta": {
    "query-time-ms": 27,
    "result-size-total": 1
  }
}
`

and encoded in a POST form param:

$ curl 'https://query-next.wikidata.org/sparql' --data-urlencode 'query=SELECT * WHERE { ?s ?p ?o } LIMIT 1'  --data-urlencode 'timeout=1s' -H 'Accept: application/sparql-results+json'  -H 'User-Agent: <redacted>' | jq
{
  "head": {
    "vars": [
      "s",
      "p",
      "o"
    ]
  },
  "results": {
    "bindings": [
      {
        "s": {
          "type": "uri",
          "value": "http://www.wikidata.org/entity/Q10000000"
        },
        "p": {
          "type": "uri",
          "value": "http://wikiba.se/ontology#identifiers"
        },
        "o": {
          "datatype": "http://www.w3.org/2001/XMLSchema#int",
          "type": "literal",
          "value": "0"
        }
      }
    ]
  },
  "meta": {
    "query-time-ms": 30,
    "result-size-total": 1
  }
}

while &timeout=1s produces a 400 Bad Request generic error page.

I can't repro. Even without the LIMIT I get a 200 (and about 56MB of streamed result set).
Could you past me the error you see? Did it come from our service, or higher up in the stack?

&timeout=1s looks like this for me:

image.png (1,920×1,080 px, 81 KB)

x-request-id: ee2ccfae-175d-4b87-96f0-2385190a1b48 if you want to look further.

Hey again! Would you be able to provide a command that we can run to try to repro the failure? e.g. can you try to echo the request I am finding it hard to tell what's going on with the PHP code you linked and I'm not sure I'll reconstruct the request exactly as you have it. We've tested it with Gabriele's syntax above and also with query params, like this:

curl "https://query-next.wikidata.org/sparql?query=select+%2A+where+%7B+%3Fs+%3Fp+%3Fo+%7D+limit+3&timeout=1s"

Note that the quotes around the URL are necessary with an & in the URL (this tripped me up before I realized, because it works without quotes if there's only 1 query param). But I'm not able to reproduce a generic Bad Request.

I tried to find your specific request in our logs but couldn't. This returns nothing:

spark-sql (default)> select * from event.wdqs_external_sparql_query_v2 where http.request_headers["x-request-id"]="ee2ccfae-175d-4b87-96f0-2385190a1b48";

I believe my syntax is correct because the same query worked when I used an x-request-id that was definitely logged.

Anyway I'm just hoping for something I can reproduce locally so I can troubleshoot iteratively & try to figure out what's breaking down. Thanks!

The command is “I open this link in my browser”. (If it was a shell command, I wouldn’t have posted a browser screenshot…) I tried Firefox’s “Copy as cURL” feature, but if I try to send what should be the same request from the terminal, I get a JSON parse_error back instead, so I have no idea what’s going on there. I can give you the command anyway, in case that helps:

curl 'https://query-next.wikidata.org/sparql?query=SELECT%20*%20WHERE%20{%20%3Fs%20%3Fp%20%3Fo.%20}&timeout=1s' \
  -H 'User-Agent: redacted' \
  -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' \
  -H 'Accept-Language: en-US,en;q=0.9,de;q=0.8' \
  -H 'Accept-Encoding: gzip, deflate, br, zstd' \
  -H 'Connection: keep-alive' \
  -H 'Cookie: redacted=redacted' \
  -H 'Upgrade-Insecure-Requests: 1' \
  -H 'Sec-Fetch-Dest: document' \
  -H 'Sec-Fetch-Mode: navigate' \
  -H 'Sec-Fetch-Site: cross-site' \
  -H 'Sec-Fetch-User: ?1' \
  -H 'Priority: u=0, i' \
  -H 'Pragma: no-cache' \
  -H 'Cache-Control: no-cache' \
  -H 'TE: trailers'

But, again, so far I can’t reproduce the error outside my browser. (But it keeps happening – e.g. right now with x-request-id: 4e3a384c-86f6-4a6a-a749-75caf6e73ed4, x-cache: cp3073 int, x-cache-status: int-front, server: Varnish.)

The PHP code is unrelated – my point there was that I might have given you an incorrect parameter name in T433935#12212771, and the parameter should really have been &maxQueryTimeMillis. Although, on the other hand, Blazegraph seems to support both? (&timeout=1s and &maxQueryTimeMillis=1 both seem to result in a java.util.concurrent.TimeoutException (though one of them gets marked as a download with partial results and the other one shows only the stack trace directly in the browser, idk why).

Hmm, your URL works for me when I URL-encode the curly braces too:

curl "https://query-next.wikidata.org/sparql?query=SELECT%20*%20WHERE%20%7B%20%3Fs%20%3Fp%20%3Fo.%20%7D&timeout=1s"

A parse error would be consistent with a 400 Bad Request in your browser. Would you let me know if encoding the brackets helps?

&timeout=1s looks like this for me:

image.png (1,920×1,080 px, 81 KB)

x-request-id: ee2ccfae-175d-4b87-96f0-2385190a1b48 if you want to look further.

Judging by the screenshot, I don't your request hit the wdqs endpoint (AFAIK that 404 page comes from the edge / varnish).

You’re right, URL-encoding the braces (%7B, %7D) seems to fix the issue. (I’m not sure how I got the broken version… I thought I started by writing the query in the UI and then getting the /sparql request from the network panel, but if I try that now, I get encoded braces. 🤷️)

Anyway, the timeout doesn’t seem to have an effect there? It seems to be returning results (many, many results) for much longer than 1s.

Hey again! My suspicion is that qlever starts streaming back results ASAP and then cuts off the stream after 1s. But it can take longer than that to actually print or render the results for you, so the entire request takes longer than 1s. It's just the qlever process that cuts off after 1s.

When I try your query with timeout=1s, I do see this at the end of the stream of results:

 !!!!>># An error has occurred while exporting the query result. Unfortunately due to limitations in the HTTP 1.1 protocol, there is no better way to report this than to append it to the incomplete result. The error message was:
Operation timed out.

And when I update the timeout to 1ms, just to be really sure the timeout is actually respected, I get a gateway timeout error with some info about what Qlever was already starting to do:

curl "https://query-next.wikidata.org/sparql?query=SELECT%20*%20WHERE%20%7B%20%3Fs%20%3Fp%20%3Fo.%20%7D&timeout=1ms"
{"errorType":"gateway_timeout","message":"{\n    \"exception\": \"Operation timed out. Last operation: IndexScan OPS ?s ?p ?o\",\n    \"query\": \"PREFIX  schema: <http://schema.org/>\\nPREFIX  bd:   <http://www.bigdata.com/rdf#>\\nPREFIX  ps:   <http://www.wikidata.org/prop/statement/>\\nPREFIX  wdt:  <http://www.wikidata.org/prop/direct/>\\nPREFIX  wikibase: <http://wikiba.se/ontology#>\\nPREFIX  mwapi: <https://www.mediawiki.org/ontology#API/>\\nPREFIX  xsd:  <http://www.w3.org/2001/XMLSchema#>\\nPREFIX  skos: <http://www.w3.org/2004/02/skos/core#>\\nPREFIX  rdfs: <http://www.w3.org/2000/01/rdf-schema#>\\nPREFIX  wd:   <http://www.wikidata.org/entity/>\\nPREFIX  p:    <http://www.wikidata.org/prop/>\\nPREFIX  rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\\nPREFIX  hint: <http://www.bigdata.com/queryHints#>\\nPREFIX  wdno: <http://www.wikidata.org/prop/novalue/>\\nPREFIX  entity: <http://www.wikidata.org/entity/>\\n\\nSELECT  *\\nWHERE\\n  { ?s  ?p  ?o }\\n\",\n    \"resultsize\": 0,\n    \"runtimeInformation\": {\n        \"cache_status\": \"computed\",\n        \"children\": [],\n        \"column_names\": [\n            \"?o\",\n            \"?p\",\n            \"?s\"\n        ],\n        \"description\": \"IndexScan OPS ?s ?p ?o\",\n        \"details\": null,\n        \"estimated_column_multiplicities\": [\n            3.8919425010681152,\n            136227.859375,\n            7.562781810760498\n        ],\n        \"estimated_operation_cost\": 8929327576,\n        \"estimated_size\": 8929327576,\n        \"estimated_total_cost\": 8929327576,\n        \"operation_time\": 30,\n        \"original_operation_time\": 0,\n        \"original_total_time\": 0,\n        \"result_cols\": 3,\n        \"result_rows\": 0,\n        \"status\": \"cancelled\",\n        \"total_time\": 30\n    },\n    \"status\": \"ERROR\",\n    \"time\": {\n        \"computeResult\": 31,\n        \"total\": 31\n    }\n}"}

I think this all points at the timeout working, but perhaps the streaming behavior we see here is what's unexpected for you? What was your expected result with a 1s timeout here---no results and merely a timeout error message?

What was your expected result with a 1s timeout here---no results and merely a timeout error message?

Either that, or that it would stop streaming results to me after 1 s, or at least not long afterwards. I didn’t expect the proxy(?) to cache so many results in memory that it can keep streaming them to me for tens of seconds (at a fairly high data rate) after the QLever backend has already stopped providing results.

But you’re right, with a differently expensive query I can get a more expected timeout from QLever.

curl -s -A 'redacted' -G --data-urlencode 'query=SELECT * WHERE { ?item rdfs:label ?itemLabel. FILTER(STRSTARTS(SHA256(?itemLabel), "0000")) } LIMIT 1' --data-urlencode timeout=1s 'https://query-next.wikidata.org/sparql' | jq '{ errorType, message: .message | fromjson }'

Which still leaves the question of whether timeout is even the right parameter name or not…

wdqs-proxy does not cache result sets, it transparently (more or less) passes your query and parametrization to QLever.
timeout is honored by the database, which will kill the query when expired. It is not a timeout applied to the request lifetime itself.

I need to triple check that we don't cache in the cdn though.

Either that, or that it would stop streaming results to me after 1 s, or at least not long afterwards.

I suspect what happened here is that the query completed in <1 second, and QLever started streaming the result set. So the latency you see is network bound. Materializing and streaming data can take longer than actually executing the query.

However I would expect QLever to set appropriate content length / transfer encoding (on http 1..1) headers. But this does not seem to be the case here. @lerickson we need to check if we are not masking headers in the proxy response.

However I would expect QLever to set appropriate content length / transfer encoding (on http 1..1) headers. But this does not seem to be the case here.

Actually, if I force HTTP1.1 I see that the response is chunked (with HTTP/2 we lose the header, since framing chunks by default).

$ curl --http1.1  'https://query-next.wikidata.org/sparql' --data-urlencode 'query=SELECT * WHERE { ?s ?p ?o }'  --data-urlencode 'timeout=1s'  -H 'Accept: application/sparql-results+json' -H 'User-Agent: <redacter>' -sD - -o /dev/null

HTTP/1.1 200 OK
content-type: application/sparql-results+json;charset=UTF-8
date: Fri, 28 Aug 2026 10:53:21 GMT
server: istio-envoy
age: 19
vary: Accept-Encoding
x-cache: cp3073 miss, cp3073 pass
x-cache-status: pass
strict-transport-security: max-age=106384710; includeSubDomains; preload
report-to: { "group": "wm_nel", "max_age": 604800, "endpoints": [{ "url": "https://intake-logging.wikimedia.org/v1/events?stream=w3c.reportingapi.network_error&schema_uri=/w3c/reportingapi/network_error/1.0.0" }] }
nel: { "report_to": "wm_nel", "max_age": 604800, "failure_fraction": 0.05, "success_fraction": 0.0}
x-client-ip: 2a02:a456:df9f:1:40e3:adf6:cdcd:7bd2
set-cookie: WMF-Uniq=3Pdb9qf7GVuEEsb-Mo6PbwPKAAAAAFvdEQxFQ43yIhnTW8XkoGOhcBu_41VC8lXF;Domain=.wikidata.org;Path=/;HttpOnly;secure;SameSite=None;Expires=Sat, 28 Aug 2027 00:00:00 GMT
transfer-encoding: chunked
x-request-id: 566c83a3-55b3-4b39-9e67-38f9df32699a
x-analytics: 
server-timing: cache;desc="pass", host;desc="cp3073",co_id;desc="2185368541"

If I read it correcly:

  • x-cache: cp3073 miss, cp3073 pass: it was a cache miss, then passed through to the origin
  • x-cache-status: pass means the CDN deliberately didn't cache it
  • transfer-encoding: chunked the response was streamed (this should have been set by QLever).

With HTTP/2, the absence of a content-length header should inform that the response is streamed.

@lerickson we need to check if we are not masking headers in the proxy response.

We do not. False alarm :)

Qlever supports the "timeout" query parameter which allows to set the timeout for individual queries. The timeout is specified as a number followed by one of the supported suffixes (ns, us, ms, s, min, h).

To avoid any confusion, the proxy now supports the same values for a timeout parameter. However, simply forwarding the timeout is not enough, as the proxied request itself also has a timeout. Thus, the proxy enforces its configured timeout as a maximum timeout, restricting users from going crazy with their custom timeout values.

For GET and "normal" (in which the body is the query) POST requests the timeout is a query parameter. For urlencoded form POST requests the timeout can be specified in the body as another form parameter.