Skip to content

Commit efbdc4d

Browse files
authored
Merge pull request #178 from estie-inc/feat/gzip-prealloc
feat: preallocate gzip decode buffer
2 parents 377ff88 + 684afa3 commit efbdc4d

3 files changed

Lines changed: 33 additions & 5 deletions

File tree

src/bench_support.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ pub fn parse_inline_result_table(
4848
}
4949

5050
pub fn decode_gzip_chunk(body: Bytes) -> Result<Bytes> {
51-
rowset::decode_gzip_chunk(body).map_err(crate::Error::from)
51+
rowset::decode_gzip_chunk(body, None).map_err(crate::Error::from)
5252
}
5353

5454
pub fn inline_rows_to_result_table(

src/rowset/gzip.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ use flate2::bufread::GzDecoder;
55

66
use crate::error::ProtocolError;
77

8-
pub(crate) fn decode_gzip_chunk(body: Bytes) -> std::result::Result<Bytes, ProtocolError> {
8+
const MAX_GZIP_PREALLOCATED_BYTES: usize = 128 * 1024 * 1024;
9+
10+
pub(crate) fn decode_gzip_chunk(
11+
body: Bytes,
12+
expected_uncompressed_size: Option<usize>,
13+
) -> std::result::Result<Bytes, ProtocolError> {
914
if body.is_empty() {
1015
return Ok(body);
1116
}
@@ -16,7 +21,8 @@ pub(crate) fn decode_gzip_chunk(body: Bytes) -> std::result::Result<Bytes, Proto
1621

1722
if body[0] == 0x1f && body[1] == 0x8b {
1823
let mut decoder = GzDecoder::new(&body[..]);
19-
let mut decoded = Vec::new();
24+
let mut decoded =
25+
Vec::with_capacity(gzip_preallocated_capacity(expected_uncompressed_size));
2026
decoder
2127
.read_to_end(&mut decoded)
2228
.map_err(ProtocolError::gzip_decode)?;
@@ -26,6 +32,12 @@ pub(crate) fn decode_gzip_chunk(body: Bytes) -> std::result::Result<Bytes, Proto
2632
}
2733
}
2834

35+
fn gzip_preallocated_capacity(expected_uncompressed_size: Option<usize>) -> usize {
36+
expected_uncompressed_size
37+
.filter(|size| *size <= MAX_GZIP_PREALLOCATED_BYTES)
38+
.unwrap_or(0)
39+
}
40+
2941
#[cfg(test)]
3042
mod tests {
3143
use std::error::Error as StdError;
@@ -35,12 +47,26 @@ mod tests {
3547

3648
#[test]
3749
fn malformed_gzip_is_protocol_error() {
38-
let err: Error = decode_gzip_chunk(Bytes::from_static(b"\x1f\x8bgarbage"))
50+
let err: Error = decode_gzip_chunk(Bytes::from_static(b"\x1f\x8bgarbage"), None)
3951
.unwrap_err()
4052
.into();
4153

4254
assert_eq!(err.kind(), ErrorKind::Protocol);
4355
assert!(err.to_string().contains("gzip decompression failed"));
4456
assert!(StdError::source(&err).is_some());
4557
}
58+
59+
#[test]
60+
fn gzip_capacity_hint_uses_bounded_expected_size() {
61+
assert_eq!(gzip_preallocated_capacity(Some(1024)), 1024);
62+
assert_eq!(
63+
gzip_preallocated_capacity(Some(MAX_GZIP_PREALLOCATED_BYTES)),
64+
MAX_GZIP_PREALLOCATED_BYTES
65+
);
66+
assert_eq!(
67+
gzip_preallocated_capacity(Some(MAX_GZIP_PREALLOCATED_BYTES + 1)),
68+
0
69+
);
70+
assert_eq!(gzip_preallocated_capacity(None), 0);
71+
}
4672
}

src/rowset/parser.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,9 +501,11 @@ pub(crate) async fn parse_remote_chunk_result_table_async(
501501
}
502502
};
503503
let query_id_for_work = Arc::clone(&query_id);
504+
let expected_uncompressed_size = workload.uncompressed_bytes;
504505

505506
let parse_work_result = execute_parse_work(workload, blocking_parse_limiter, move || {
506-
let bytes = decode_gzip_chunk(body).map_err(QueryScopedRepr::from)?;
507+
let bytes =
508+
decode_gzip_chunk(body, expected_uncompressed_size).map_err(QueryScopedRepr::from)?;
507509
parse_table_with_shape(
508510
schema,
509511
query_id_for_work,

0 commit comments

Comments
 (0)