1212// See the License for the specific language governing permissions and
1313// limitations under the License.
1414
15- use common_arrow:: parquet:: read:: read_metadata_async;
15+ use std:: io:: SeekFrom ;
16+
17+ use common_arrow:: parquet:: metadata:: ThriftFileMetaData ;
1618use common_exception:: ErrorCode ;
1719use common_exception:: Result ;
1820use common_expression:: TableSchemaRef ;
21+ use futures:: AsyncRead ;
22+ use futures:: AsyncSeek ;
23+ use futures_util:: AsyncReadExt ;
24+ use futures_util:: AsyncSeekExt ;
1925use opendal:: Operator ;
2026use opendal:: Reader ;
2127use storages_common_cache:: InMemoryItemCacheReader ;
@@ -31,6 +37,7 @@ use storages_common_table_meta::meta::TableSnapshotStatistics;
3137use storages_common_table_meta:: meta:: TableSnapshotStatisticsVersion ;
3238
3339use super :: versioned_reader:: VersionedReader ;
40+ use crate :: io:: read:: meta:: meta_readers:: thrift_file_meta_read:: read_thrift_file_metadata;
3441
3542pub type TableSnapshotStatisticsReader =
3643 InMemoryItemCacheReader < TableSnapshotStatistics , LoaderWrapper < Operator > > ;
@@ -107,12 +114,15 @@ impl Loader<SegmentInfo> for LoaderWrapper<(Operator, TableSchemaRef)> {
107114impl Loader < BloomIndexMeta > for LoaderWrapper < Operator > {
108115 async fn load ( & self , params : & LoadParams ) -> Result < BloomIndexMeta > {
109116 let mut reader = bytes_reader ( & self . 0 , params. location . as_str ( ) , params. len_hint ) . await ?;
110- let meta = read_metadata_async ( & mut reader) . await . map_err ( |err| {
111- ErrorCode :: Internal ( format ! (
112- "read file meta failed, {}, {:?}" ,
113- params. location, err
114- ) )
115- } ) ?;
117+ // read the ThriftFileMetaData, omit unnecessary conversions
118+ let meta = read_thrift_file_metadata ( & mut reader)
119+ . await
120+ . map_err ( |err| {
121+ ErrorCode :: StorageOther ( format ! (
122+ "read file meta failed, {}, {:?}" ,
123+ params. location, err
124+ ) )
125+ } ) ?;
116126
117127 BloomIndexMeta :: try_from ( meta)
118128 }
@@ -126,3 +136,107 @@ async fn bytes_reader(op: &Operator, path: &str, len_hint: Option<u64>) -> Resul
126136 } ;
127137 Ok ( reader)
128138}
139+
140+ mod thrift_file_meta_read {
141+ use common_arrow:: parquet:: error:: Error ;
142+ use parquet_format_safe:: thrift:: protocol:: TCompactInputProtocol ;
143+
144+ use super :: * ;
145+
146+ // the following code is copied from crate `parquet2`, with slight modification:
147+ // return a ThriftFileMetaData instead of FileMetaData while reader parquet metadata,
148+ // to avoid unnecessary conversions.
149+ //
150+ // It takes about 18s, to convert one million ThriftFileMetaData objects to parquet::metadata::FileMetaData,
151+ // and the memory usage of FileMetaData is also larger.
152+
153+ const HEADER_SIZE : u64 = PARQUET_MAGIC . len ( ) as u64 ;
154+ const FOOTER_SIZE : u64 = 8 ;
155+ const PARQUET_MAGIC : [ u8 ; 4 ] = [ b'P' , b'A' , b'R' , b'1' ] ;
156+
157+ /// The number of bytes read at the end of the parquet file on first read
158+ const DEFAULT_FOOTER_READ_SIZE : u64 = 64 * 1024 ;
159+
160+ async fn stream_len (
161+ seek : & mut ( impl AsyncSeek + std:: marker:: Unpin ) ,
162+ ) -> std:: result:: Result < u64 , std:: io:: Error > {
163+ let old_pos = seek. seek ( SeekFrom :: Current ( 0 ) ) . await ?;
164+ let len = seek. seek ( SeekFrom :: End ( 0 ) ) . await ?;
165+
166+ // Avoid seeking a third time when we were already at the end of the
167+ // stream. The branch is usually way cheaper than a seek operation.
168+ if old_pos != len {
169+ seek. seek ( SeekFrom :: Start ( old_pos) ) . await ?;
170+ }
171+
172+ Ok ( len)
173+ }
174+
175+ fn metadata_len ( buffer : & [ u8 ] , len : usize ) -> i32 {
176+ i32:: from_le_bytes ( buffer[ len - 8 ..len - 4 ] . try_into ( ) . unwrap ( ) )
177+ }
178+
179+ pub async fn read_thrift_file_metadata < R : AsyncRead + AsyncSeek + Send + std:: marker:: Unpin > (
180+ reader : & mut R ,
181+ ) -> common_arrow:: parquet:: error:: Result < ThriftFileMetaData > {
182+ let file_size = stream_len ( reader) . await ?;
183+
184+ if file_size < HEADER_SIZE + FOOTER_SIZE {
185+ return Err ( Error :: OutOfSpec (
186+ "A parquet file must containt a header and footer with at least 12 bytes" . into ( ) ,
187+ ) ) ;
188+ }
189+
190+ // read and cache up to DEFAULT_FOOTER_READ_SIZE bytes from the end and process the footer
191+ let default_end_len = std:: cmp:: min ( DEFAULT_FOOTER_READ_SIZE , file_size) as usize ;
192+ reader
193+ . seek ( SeekFrom :: End ( -( default_end_len as i64 ) ) )
194+ . await ?;
195+
196+ let mut buffer = vec ! [ ] ;
197+ buffer. try_reserve ( default_end_len) ?;
198+ reader
199+ . take ( default_end_len as u64 )
200+ . read_to_end ( & mut buffer)
201+ . await ?;
202+
203+ // check this is indeed a parquet file
204+ if buffer[ default_end_len - 4 ..] != PARQUET_MAGIC {
205+ return Err ( Error :: OutOfSpec (
206+ "Invalid Parquet file. Corrupt footer" . into ( ) ,
207+ ) ) ;
208+ }
209+
210+ let metadata_len = metadata_len ( & buffer, default_end_len) ;
211+ let metadata_len: u64 = metadata_len. try_into ( ) ?;
212+
213+ let footer_len = FOOTER_SIZE + metadata_len;
214+ if footer_len > file_size {
215+ return Err ( Error :: OutOfSpec (
216+ "The footer size must be smaller or equal to the file's size" . into ( ) ,
217+ ) ) ;
218+ }
219+
220+ let reader = if ( footer_len as usize ) < buffer. len ( ) {
221+ // the whole metadata is in the bytes we already read
222+ let remaining = buffer. len ( ) - footer_len as usize ;
223+ & buffer[ remaining..]
224+ } else {
225+ // the end of file read by default is not long enough, read again including the metadata.
226+ reader. seek ( SeekFrom :: End ( -( footer_len as i64 ) ) ) . await ?;
227+
228+ buffer. clear ( ) ;
229+ buffer. try_reserve ( footer_len as usize ) ?;
230+ reader. take ( footer_len) . read_to_end ( & mut buffer) . await ?;
231+
232+ & buffer
233+ } ;
234+
235+ // a highly nested but sparse struct could result in many allocations
236+ let max_size = reader. len ( ) * 2 + 1024 ;
237+
238+ let mut prot = TCompactInputProtocol :: new ( reader, max_size) ;
239+ let meta = ThriftFileMetaData :: read_from_in_protocol ( & mut prot) ?;
240+ Ok ( meta)
241+ }
242+ }
0 commit comments