{"id":41,"date":"2026-10-03T16:33:40","date_gmt":"2026-10-03T16:33:40","guid":{"rendered":"https:\/\/hoho.live\/luda\/index.php\/2026\/10\/03\/how-loki-query-responses-are-gzipped-on-the-way-to-a-log-viewer\/"},"modified":"2026-10-04T09:48:27","modified_gmt":"2026-10-04T01:48:27","slug":"how-loki-query-responses-are-gzipped-on-the-way-to-a-log-viewer","status":"publish","type":"post","link":"https:\/\/hoho.live\/luda\/index.php\/2026\/10\/03\/how-loki-query-responses-are-gzipped-on-the-way-to-a-log-viewer\/","title":{"rendered":"How Loki query responses are gzipped on the way to a log viewer"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><strong>Short answer:<\/strong> Loki gzips <code>query_range<\/code> when <code>frontend.compress_responses<\/code> is true and the client sends <code>Accept-Encoding: gzip<\/code>. On 3.4.2 the help text for <code>-querier.compress-http-responses<\/code> says that default is true. It is not the value the process runs with. <code>RegisterFlags<\/code> then registers <code>-frontend.support-parquet-encoding<\/code> on that same bool, with default false, so gzip stays off until the YAML key is set. That second registration is a bug. It is still there in 3.6.17 and 3.7.8. Main corrects it in <a href=\"https:\/\/github.com\/grafana\/loki\/pull\/23885\">#23885<\/a>, which those releases do not contain.<\/p>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n<h4 class=\"wp-block-heading\"><strong>What query_range returns<\/strong><\/h4>\n\n<p class=\"wp-block-paragraph\"><code>GET \/loki\/api\/v1\/query_range<\/code> returns JSON. With <code>compress_responses<\/code> true and <code>Accept-Encoding: gzip<\/code>, the body is gzip and the response carries <code>Content-Encoding: gzip<\/code> and <code>Vary: Accept-Encoding<\/code>. A short body stays plain JSON and has no <code>Content-Encoding<\/code>.<\/p>\n\n\n<p class=\"wp-block-paragraph\">In <code>pkg\/lokifrontend\/config.go<\/code> the first <code>BoolVar<\/code> points <code>-querier.compress-http-responses<\/code> at <code>compress_responses<\/code> and sets it true. The next <code>BoolVar<\/code> points <code>-frontend.support-parquet-encoding<\/code> at that same field and sets it false. Go writes a flag&#8217;s default into the variable when the flag is registered, so the second call wins. <code>-help<\/code> still prints the first flag&#8217;s own default, true. The <code>support_parquet_encoding<\/code> field is never attached to its flag.<\/p>\n\n\n<p class=\"wp-block-paragraph\">A config file that omits the key leaves the field false. On 3.4.2 the expanded config showed <code>compress_responses: false<\/code>, and the same query came back as plain JSON. Setting the key and restarting produced gzip. Read the expanded config after the reload.<\/p>\n\n\n<p class=\"wp-block-paragraph\">One 500-line <code>query_range<\/code> was 103,278 bytes as identity and 10,655 bytes as gzip.<\/p>\n\n\n<p class=\"wp-block-paragraph\">The push is a different body. Promtail&#8217;s default POST to <code>\/loki\/api\/v1\/push<\/code> is snappy-compressed protobuf. This flag does not change that POST.<\/p>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n<h4 class=\"wp-block-heading\"><strong>What the client does with the body<\/strong><\/h4>\n\n<p class=\"wp-block-paragraph\">A Go <code>net\/http<\/code> client that does not set <code>Accept-Encoding<\/code> itself asks for gzip and decompresses before the application reads the body. Grafana&#8217;s datasource is in that class, so turning the key on does not break it.<\/p>\n\n\n<p class=\"wp-block-paragraph\">Python&#8217;s <code>urllib<\/code> does not. If the request sets <code>Accept-Encoding: gzip<\/code>, <code>urlopen<\/code> returns the compressed bytes. <code>json.load<\/code> on that body fails. Decompress only when <code>Content-Encoding<\/code> is gzip, then parse. A response with no such header is already JSON.<\/p>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n<h4 class=\"wp-block-heading\"><strong>The working path<\/strong><\/h4>\n\n<ol class=\"wp-block-list\">\n<li>On 3.4.2 through 3.7.8, set <code>frontend.compress_responses: true<\/code>.<\/li>\n<li>Reload the process and read the expanded config. The field has to say true.<\/li>\n<li>Call <code>query_range<\/code> with <code>Accept-Encoding: gzip<\/code>.<\/li>\n<li>If <code>Content-Encoding<\/code> is gzip, decompress, then parse JSON. If the header is absent, parse the bytes as JSON.<\/li>\n<\/ol>\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/grafana\/loki\/pull\/23885\">#23885<\/a> binds <code>-frontend.support-parquet-encoding<\/code> to <code>support_parquet_encoding<\/code>. A binary that contains that change keeps the documented default, and the YAML key is optional.<\/p>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n<h4 class=\"wp-block-heading\"><strong>Approaches that look simpler and fail<\/strong><\/h4>\n\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Approach<\/th>\n<th>Why it fails<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Trust <code>-help<\/code> once the flags have been registered<\/td>\n<td>The parquet flag is registered on <code>compress_responses<\/code> and writes false<\/td>\n<\/tr>\n<tr>\n<td><code>json.load<\/code> the body after Python urllib sends <code>Accept-Encoding: gzip<\/code><\/td>\n<td>urllib does not decompress. The first bytes are the gzip header<\/td>\n<\/tr>\n<tr>\n<td>Wait for a 3.6 or 3.7 release<\/td>\n<td>3.6.17 and 3.7.8 still register the parquet flag on <code>compress_responses<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n\n\n<p class=\"wp-block-paragraph\"><code>-help<\/code> prints each flag&#8217;s own default. The expanded config is the value the process uses.<\/p>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n<h4 class=\"wp-block-heading\"><strong>When you can omit the key<\/strong><\/h4>\n\n<p class=\"wp-block-paragraph\">Omit <code>frontend.compress_responses<\/code> on a build that contains #23885. That is a source change, not a switch inside 3.4.2. Until the binary has that registration, set the key.<\/p>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n<h4 class=\"wp-block-heading\"><strong>Why this stays published<\/strong><\/h4>\n\n<p class=\"wp-block-paragraph\">We hit this on Loki 3.4.2: <code>query_range<\/code> answered with plain JSON while the flag help said gzip was on. <strong>The parquet flag is registered on <code>compress_responses<\/code> and clears it. Set <code>frontend.compress_responses: true<\/code> until the binary contains the #23885 fix.<\/strong><\/p>\n\n\n<p class=\"wp-block-paragraph\">\u672c\u6587\u7531 HoHo \u8207 AI \u5354\u4f5c\u6574\u7406\uff0c\u6700\u5f8c\u66f4\u65b0\u65bc 2026 \u5e74 10 \u6708 4 \u65e5\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Short answer: Loki gzips query_range when frontend.compress_responses is true and the client sends Accept-Encoding: gzip. On 3.4.2 the help text for -querier.compress-http-responses says that default is true. It is not the value the process runs with. RegisterFlags then registers -frontend.support-parquet-encoding on that same bool, with default false, so gzip&hellip;<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7,17,19],"tags":[21,20,14],"class_list":["post-41","post","type-post","status-publish","format-standard","hentry","category-homelab","category-logging","category-loki","tag-grafana","tag-gzip","tag-loki"],"_links":{"self":[{"href":"https:\/\/hoho.live\/luda\/index.php\/wp-json\/wp\/v2\/posts\/41","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hoho.live\/luda\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/hoho.live\/luda\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/hoho.live\/luda\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/hoho.live\/luda\/index.php\/wp-json\/wp\/v2\/comments?post=41"}],"version-history":[{"count":2,"href":"https:\/\/hoho.live\/luda\/index.php\/wp-json\/wp\/v2\/posts\/41\/revisions"}],"predecessor-version":[{"id":52,"href":"https:\/\/hoho.live\/luda\/index.php\/wp-json\/wp\/v2\/posts\/41\/revisions\/52"}],"wp:attachment":[{"href":"https:\/\/hoho.live\/luda\/index.php\/wp-json\/wp\/v2\/media?parent=41"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/hoho.live\/luda\/index.php\/wp-json\/wp\/v2\/categories?post=41"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/hoho.live\/luda\/index.php\/wp-json\/wp\/v2\/tags?post=41"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}