Skip to main content

Parquet

Apache Parquet is a column-oriented binary storage format optimized for analytical workloads. Originally developed within the Apache Hadoop ecosystem, Parquet provides efficient compression and encoding schemes for large-scale data processing.

The writer targets the Parquet 2.9 format. Any schema conforming to it is written correctly; the tables below describe which parts VirtualMetric actively understands.

Binary Layout

SectionInternal NameDescriptionPossible Values / Format
File Headermagic4-byte magic number identifying Parquet filesASCII: PAR1 (hex: 50 41 52 31)
Row Grouprow_group_metadataMetadata for each row groupContains column chunk metadata and statistics
column_chunkData for each column in the row groupCompressed and encoded column data
File FootermetadataFile-level metadata including schema and row groupsThrift-encoded metadata structure
metadata_lengthLength of metadata section4-byte little-endian integer
magicFooter magic numberASCII: PAR1 (hex: 50 41 52 31)

Column Storage Example

Row-based storage (traditional):

id,name,last_name,age
1,John,Buck,35
2,Jane,Doe,27
3,Joe,Dane,42

Column-based Storage (Parquet):

id: [1, 2, 3]
name: [John, Jane, Joe]
last_name: [Buck, Doe, Dane]
age: [35, 27, 42]

JSON Schema Definition

A Parquet schema is declared in VirtualMetric as a single JSON object whose keys are field names and whose values are field definitions. Definitions nest via the fields property to describe records and lists of records. The schema is parsed into a Parquet schema tree and used by the writer when producing .parquet files.

{
"user_id": { "type": "STRING", "requirement": "required" },
"score": { "type": "INT", "bitWidth": 32, "signed": true },
"metadata": {
"fields": {
"region": { "type": "STRING" },
"tier": { "type": "STRING" }
}
}
}

The top level is always an object — there is no wrapping fields, name, or schema key.

A record is declared by omitting type and giving fields, as metadata does above. There is no GROUP type: a definition carrying any type at all is treated as a leaf, and an unrecognized value fails the schema with unknown type.

Field Definition Properties

PropertyTypeApplies toDescription
typestringevery fieldLogical type (see below); required for every leaf field
fieldsobjectgroups, complex listsNested fields; presence promotes the field to a record/group
requirementstringevery fieldrequired, optional, or recommended. Default: optional
repeatedbooleanevery fieldIf true, the field is repeated (array-like); equivalent to type: LIST
optionalbooleanevery fieldMarks the field nullable in the internal mapping; does not change repetition
bitWidthintegerINTInteger bit width: 8, 16, 32, or 64. Required when type is INT
signedbooleanINTtrue for signed, false for unsigned. Default: unsigned
logicalTypestringINT64TIMESTAMP_NANOS, TIMESTAMP_MICROS, or TIMESTAMP_MILLIS to mark a timestamp
unitstringINT64 ts, TIMETime unit NANOS, MICROS, or MILLIS; inferred from logicalType if unset
precisionintegerDECIMALTotal number of digits
scaleintegerDECIMALNumber of digits after the decimal point
typeLengthintegerFIXED_LEN_BYTE_ARRAYLength in bytes; must be greater than zero
compressionstringevery fieldAccepted but not used at parse time (see Compression Codecs)
adjustedToUtcbooleantimestamp fieldsAccepted but not used; reserved for future use

Any property not in this table is ignored on parse.

Repetition is decided in order: if type is LIST or repeated is true, the field is repeated; otherwise if requirement is required, the field is required; otherwise it is optional (the default, covering optional, recommended, and unset).

Supported Types

typeNotes
STRINGUTF-8 string
BOOLEANBoolean
INTGeneric integer; requires bitWidth; honors signed. Only bitWidth 32 and 64 survive the validator
INT32Fixed 32-bit signed integer
INT64Fixed 64-bit signed integer; with logicalType TIMESTAMP_* becomes a timestamp
INT96Legacy 96-bit integer (decoded as INT64 internally). Dropped by the validator
FLOAT32-bit floating point
DOUBLE64-bit floating point
BYTE_ARRAYVariable-length binary. Dropped by the validator
FIXED_LEN_BYTE_ARRAYFixed-length binary; requires typeLength greater than zero. Dropped by the validator
DECIMALFixed-point decimal; requires precision, scale recommended. Dropped by the validator
DATECalendar date (no time). Dropped by the validator
DATETIMETimestamp at microsecond precision
TIMETime of day; honors unit, defaults to MILLIS. Dropped by the validator
JSONStored as a UTF-8 string, logically tagged as JSON. Dropped by the validator
LISTRepeated field; see Lists

For DECIMAL, the storage backing is chosen automatically from precision: up to 9 digits uses INT32, up to 18 uses INT64, and beyond 18 uses FIXED_LEN_BYTE_ARRAY.

warning

The schema validator — the stage that runs before encoding, both in the Enforce Schema processor and in the file sender when a target declares a schema — recognizes a narrower set of types than the schema parser accepts. It carries through STRING, BOOLEAN, INT, INT32, INT64, FLOAT, DOUBLE, DATETIME, and the list forms of those scalar types.

Any other declared type — BYTE_ARRAY, FIXED_LEN_BYTE_ARRAY, DECIMAL, DATE, TIME, JSON, INT96, and lists whose element is a record — reaches the validator's default branch, which removes the field from the record. The schema itself is accepted, no error is raised, and the column is simply absent from the output.

INT is only carried at bitWidth 32 or 64. A bitWidth of 8 or 16 produces the internal types INT8 and INT16, which the validator does not recognize either, so those columns are dropped as well.

Keep fields you need on the wire to the recognized types until this is addressed.

Lists

A list takes one of three shapes:

{
"tags": { "type": "LIST" },
"scores": {
"type": "LIST",
"fields": { "element": { "type": "INT", "bitWidth": 32, "signed": true } }
},
"events": {
"type": "LIST",
"fields": {
"ts": { "type": "INT64", "logicalType": "TIMESTAMP_MILLIS" },
"name": { "type": "STRING" }
}
}
}

tags is the shorthand form (a list of strings); scores names a single element type; events is a list of records. Writing "repeated": true on any scalar produces a repeated field without type: LIST.

Requirement Filtering

The Enforce Schema processor's requirement_filter field selects which fields a schema emits: an empty value or all includes every field, while a comma-separated list of required, optional, and recommended includes only matching fields. This lets one schema produce either a required-only projection or a full projection without rewriting it.

Reference Example

{
"id": { "type": "STRING", "requirement": "required" },
"received_at": { "type": "INT64", "logicalType": "TIMESTAMP_MICROS", "requirement": "required" },
"severity": { "type": "INT", "bitWidth": 32, "signed": false },
"score": { "type": "DOUBLE" },
"tags": { "type": "LIST" },
"payload": { "type": "STRING" },
"user": {
"fields": {
"id": { "type": "STRING", "requirement": "required" },
"email": { "type": "STRING" }
}
}
}

Field mapping uses dot notation (user.id, user.email). Schemas in this format can be registered as reusable Library entries and referenced by name from the Enforce Schema and Check Schema processors, or supplied inline.

Encoding Types

Encoding is selected automatically per column by the writer. It is not a configuration option—the table below is a reference for reading the files DataStream produces.

EncodingInternal NameDescriptionUse Case
PlainPLAINValues encoded back to back without compressionDefault fallback for all data types
DictionaryRLE_DICTIONARYValues replaced with dictionary indices using RLERepeated string values, low-cardinality columns
Run Length / Bit-Packing HybridRLECombination of bit-packing and run length encodingRepetition/definition levels, dictionary indices, booleans
Delta Binary PackedDELTA_BINARY_PACKEDDelta encoding with binary packing for integersINT32, INT64 with sequential or clustered values
Delta Length Byte ArrayDELTA_LENGTH_BYTE_ARRAYDelta-encoded lengths followed by concatenated dataVariable-length byte arrays
Delta Byte ArrayDELTA_BYTE_ARRAYIncremental/front compression storing prefix lengthsBYTE_ARRAY, FIXED_LEN_BYTE_ARRAY with common prefixes
Byte Stream SplitBYTE_STREAM_SPLITScatters bytes to separate streams for better compressionFLOAT, DOUBLE, INT32, INT64 (added in Parquet 2.8)

Deprecated Encodings

EncodingInternal NameDescriptionReplacement
Plain DictionaryPLAIN_DICTIONARYLegacy dictionary encoding in data pagesUse RLE_DICTIONARY in data pages
Bit PackedBIT_PACKEDFixed-width bit-packing without paddingUse RLE hybrid encoding

Compression Codecs

Set with the compression field on a target. The following values are recognized:

ValueCodecBest For
zstandardZSTDBest balance of speed and compression ratio
zstdZSTD. An alias for zstandardBest balance of speed and compression ratio
snappySNAPPYGeneral-purpose, balanced performance
gzipGZIPStorage-constrained environments
brotliBROTLIHigh compression ratio needs
lz4LZ4_RAWLow-latency applications

Omitting the compression field applies zstandard.

Note that lz4 selects LZ4_RAW, not the deprecated LZ4 codec, and that lz4_raw is not itself a recognized value.

warning

An unrecognized value is not an error. The file is written uncompressed and no warning is emitted, so a misspelled codec is silent. The six values above are the only ones that compress.