duckdb / duckdb/duckdb-httpfs

On slower networks "Invalid Error: Failed to decompress GZIP block: data error" for files that work on faster networks

Open
#366 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C++
Stars
60
Forks
100
Avg merge
1h 50m
Merged PRs (30d)
25

Description

Hi,

I noticed that some queries over parquet files can error out with Invalid Error: Failed to decompress GZIP block: data error when executed on slower networks.
Same query works fine on a faster network.
It also works fine when duckdb was limited to using a single thread

Since I'm not a C++ developer, I used Claude (Fable) to look into this.
This is the gist of it (I've highlighted all Claude contributions quoted)

http_timeout (default 30 s) is enforced as CURLOPT_TIMEOUT, a cap on total transfer time. On a slow connection a range GET can exceed it, and curl kills the transfer mid-body — after the 206 status line and part of the body have already arrived. The client delivers the partial body to the read buffer anyway, retries fail the same way, and the final failed response is returned to the caller rather than thrown. TryRangeRequest (src/httpfs.cpp:505, unchanged on main) then accepts it:

if (res->Success() || res->status == HTTPStatusCode::PartialContent_206 || ...) {
    return true;   // partial buffer accepted as a complete read
}
// HasRequestError() only consulted below

A 206 status line only means the server accepted the range request — it can't promise the body arrived. The check answers a transport-layer question with an HTTP-layer field. Two extra details:

  • On current core every clean 2xx already has Success()==true, so the || status == 206 clause (a workaround per its TODO comment) matches only failed transfers.
  • try_request comes from HTTPFileHandle::auto_fallback_to_full_file_download (httpfs.hpp:91), hardcoded true and never synced from SET auto_fallback_to_full_download — no setting can surface the failure (verified, scenario D).

As far as I can tell #336 is already dealing with the root cause (killing the transfer), but it seems that the check should also make sure there is no actual request error before returning true.

-		if (res->Success() || res->status == HTTPStatusCode::PartialContent_206 ||
-		    res->status == HTTPStatusCode::Accepted_202) {
+		if ((res->Success() || res->status == HTTPStatusCode::PartialContent_206 ||
+		     res->status == HTTPStatusCode::Accepted_202) &&
+		    !res->HasRequestError()) {

The rejected response falls through to the existing error handling, so the RangeRequestNotSupported → full-download fallback is preserved. Companions worth doing: sync the auto_fallback_to_full_download setting onto the handle; don't deliver partial bodies to content_handler on curl errors.

Hope this is helpful.
I can also share a repro script that can deterministically reproduce the error.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/httpfs.cpp around TryRangeRequest at line 505 and read HTTPFileHandle::auto_fallback_to_full_file_download in httpfs.hpp:91. Reproduce with a parquet query on a slower network and compare multi-threaded versus single-threaded behavior. Done means failed range transfers are handled as errors rather than accepted as complete responses, while the existing fallback behavior remains intact.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
networking
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
62/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.