Netflix Iceberg

repository·master·Indexed 19 days ago

https://github.com/netflix/iceberg

A high-performance table format for large-scale tabular data that uses a metadata-driven approach to provide snapshot isolation, atomic commits, and efficient distributed planning. It replaces traditional Hive-style directory layouts by tracking individual data files. The project includes core libraries (iceberg-api, iceberg-core), optional format modules (parquet, orc), and engine integrations for Spark, Presto, and Pig. Note: This project has been donated to the Apache Software Foundation.

Tokens
5.7K
Snippets
16
Records
26
Agent score
67%

What's inside netflix-iceberg

  1. Note on Iceberg Project Status and Migration

    master

    IMPORTANT: Iceberg has moved to Apache

    This repository is no longer the primary location for Iceberg. The project has been donated to the Apache Software Foundation. For the latest development, documentation, and community interaction, please use the following resources:

  2. Identify the correct Iceberg module for your use case

    master

    Iceberg is organized into several library modules depending on your integration needs:

    Core Library Modules:

    • iceberg-api: Contains the public Iceberg API.
    • iceberg-core: The primary implementation of the Iceberg API and support for Avro data files. Processing engines should depend on this module.
    • iceberg-common: Utility classes used across other modules.
    • iceberg-parquet: Optional module for tables backed by Parquet files.
    • iceberg-orc: Optional module for tables backed by ORC files (experimental).
    • iceberg-hive: Implementation for tables backed by the Hive Metastore Thrift client.

    Engine Integration Modules:

    • iceberg-spark: Implementation of Spark's Datasource V2 API. Use iceberg-runtime for a shaded version.
    • iceberg-presto-runtime: A shaded runtime jar for Presto integration.
    • iceberg-pig: Implementation of Pig's LoadFunc API.
    • iceberg-data: A client library for reading Iceberg tables from JVM applications.
  3. Understand the Iceberg table format model

    master

    Iceberg is a table format for large, slow-moving tabular data that tracks individual data files instead of relying on directory structures.

    Core Concepts:

    • Metadata Files: Maintain the table state, including schema, partitioning configuration, properties, and a list of snapshots. All changes to the table state are performed via atomic operations that replace the old metadata file with a new one.
    • Snapshots: Represent the complete set of data files in a table at a specific point in time. This provides snapshot isolation, ensuring readers see a consistent view of the data without locks.
    • Manifest Files: Store the actual data file locations, partition data, and metrics. Snapshots are composed of the union of files found in their associated manifest files.
    • Atomic Commits: Because changes are metadata-only operations, Iceberg enables safe file-level operations like compaction and appending late data without the need for expensive file renames (which are problematic on object stores like S3).
  4. Understand ManifestEntry status

    master

    A ManifestEntry represents an entry within an Iceberg manifest file. Each entry tracks the state of a DataFile relative to a snapshot. The Status enum defines whether a file is part of the current snapshot or how it relates to previous ones:

    • EXISTING: The file was already present in the table before the current snapshot.
    • ADDED: The file is new and was added in the current snapshot.
    • DELETED: The file was removed in the current snapshot.
    com.netflix.iceberg.ManifestEntry.Status
  5. Apply vs Commit schema changes

    master

    The UpdateSchema API distinguishes between calculating a new schema and persisting it:

    • apply(): Returns a new Schema object representing the state of the schema after all pending additions, deletions, updates, and renames are applied. This does not modify the table.
    • commit(): Persists the changes to the table by updating the table metadata and committing the operation via TableOperations.
  6. Convert literals to different types with Literal.to(Type)

    master

    Once you have a Literal<T>, you can attempt to convert it to a different Iceberg Type using the .to(Type type) method. This is useful when a constant value needs to be coerced into a specific schema type for a filter expression.

    Behavioral Notes:

    • If the conversion is valid (e.g., converting an Integer literal to a Long type), it returns a new Literal of the target type.
    • If the conversion is invalid or impossible (e.g., converting a String to an Integer), it returns null.
    • For AboveMax and BelowMin literals, .to(Type) always throws UnsupportedOperationException.
    • Some conversions involve specific logic, such as StringLiteral being able to parse ISO-formatted strings into Date, Time, or Timestamp literals.
  7. Understand the GenericDataFile implementation

    master

    In Iceberg, GenericDataFile is a concrete implementation of the DataFile interface. It serves as a metadata object representing a single data file within a table. It implements several interfaces including IndexedRecord and StructLike, allowing it to be used with Avro reflection and other structured data processing frameworks.

    Key properties captured by a GenericDataFile include:

    • File Identity: path() (the file path) and format() (the FileFormat).
    • Partitioning: partition() (returns a StructLike representing the partition values).
    • Statistics: recordCount(), fileSizeInBytes(), blockSizeInBytes(), and various column-level metrics like columnSizes(), valueCounts(), nullValueCounts(), lowerBounds(), and upperBounds().
    • Ordering: sortColumns() and fileOrdinal().

    Because it implements IndexedRecord, it can be instantiated via Avro reflection using an org.apache.avro.Schema constructor, which is particularly useful when reading manifest files.

  8. Setup Iceberg in a Spark environment

    master

    To use Iceberg with Spark, you must create a Spark session and include the iceberg-runtime JAR in your environment. In a notebook environment like Jupyter, you can use the %AddJar magic command to load the JAR file.

    // 1. Create a Spark session
    spark
    
    // 2. Add the iceberg-runtime Jar
    %AddJar file:///home/user/iceberg-runtime-0.1.3.jar
  9. Append files to an Iceberg table

    master

    To add existing data files to an Iceberg table, use the newFastAppend API. This process typically involves:

    1. Listing partition files using SparkTableUtil.listPartition.
    2. Loading the table via HadoopTables.
    3. Using table.newFastAppend to create an append operation.
    4. Calling appendFile(file.toDataFile(table.spec)) for each file.
    5. Calling commit() to finalize the changes.

    For a single large commit (creating one manifest instead of many), consider using table.newAppend.commit.

    import com.netflix.iceberg.hadoop.HadoopTables
    import org.apache.hadoop.conf.Configuration
    
    // Assuming 'partitions' DataFrame is available from SparkTableUtil.partitionDF
    partitions.repartition(100).flatMap { row =>
    
        // List the partition and read Parquet footers to get metrics
        SparkTableUtil.listPartition(row.getMap[String, String](0).toMap, row.getString(1), row.getString(2))
    
    }.repartition(10) 
     .mapPartitions { files =>
    
        val tables = new HadoopTables(new Configuration())
        val table = tables.load("hdfs:/tmp/tables/job_metrics_tmp")
    
        // Use fast appends to create a manifest for the new files
        val append = table.newFastAppend
    
        files.foreach { file =>
            append.appendFile(file.toDataFile(table.spec))
        }
    
        // Commit the new files
        append.commit()
    
        Seq.empty[String].iterator
    
    }.count
  10. Configure TableScan planning with worker pools

    master

    The TableScan planning process can be parallelized using a worker pool to improve performance when scanning multiple manifests. This behavior is controlled by the system property SCAN_THREAD_POOL_ENABLED. By default, this is set to true.

    If PLAN_SCANS_WITH_WORKER_POOL is enabled and the snapshot contains more than one manifest, planFiles() will use a ParallelIterable backed by the project's internal planner and worker pools.

    # Enable or disable parallel planning via system properties
    -Diceberg.scan.thread.pool.enabled=true
  11. Create an Iceberg table in HDFS

    master

    You can create an Iceberg table in HDFS using HadoopTables and SparkSchemaUtil. SparkSchemaUtil allows you to derive the schema and partition specification directly from an existing Spark table, which is useful for migrating data to Iceberg.

    Note: When using Spark objects inside transformations or closures, use a code block to avoid capturing Spark configurations in closures.

    import org.apache.hadoop.fs.Path
    import com.netflix.iceberg.hadoop.HadoopTables
    import com.netflix.iceberg.spark.SparkSchemaUtil
    
    val path = "hdfs:/tmp/tables/job_metrics_tmp"
    
    {
        val conf = spark.sparkContext.hadoopConfiguration
        val fs = new Path(path).getFileSystem(conf)
        fs.delete(new Path(path), true /* recursive */ )
    
        val tables = new HadoopTables(conf)
        // Derive schema and spec from an existing Spark table
        val schema = SparkSchemaUtil.schemaForTable(spark, "default.job_metrics")
        val spec = SparkSchemaUtil.specForTable(spark, "default.job_metrics")
    
        tables.create(schema, spec, path)
    
        // Verify the schema
        tables.load(path).schema
    }