dbldatagen

repository·master·Indexed 19 days ago

https://github.com/databrickslabs/dbldatagen

A PySpark synthetic data generator from Databricks Labs designed to create large-scale, repeatable datasets for testing, benchmarking, and demonstrations. It features a core engine using DataGenPlan for complex schemas with primary/foreign keys, a high-level DataGenerator API for custom distributions, and a Datasets class for standard templates. Supports Databricks Runtime 13.3 LTS+, PySpark 3.4.1, and Unity Catalog Shared access mode (Runtime 13.2+).

Tokens
100.6K
Snippets
268
Records
366
Agent score
67%

What's inside dbldatagen

  1. Overview of Databricks Labs Data Generator (dbldatagen)

    master

    The Databricks Labs Data Generator (dbldatagen) is a Spark-based Python library designed to generate realistic synthetic data at scale. It leverages Spark DataFrames and Spark SQL to produce data that can be written to various storage formats, saved as Delta tables, or manipulated using standard Spark APIs.

    Key capabilities include:

    • Generating billions of rows of data efficiently using Spark clusters.
    • Specifying row counts and Spark partition counts for distribution.
    • Controlling data via numeric, time, and date ranges.
    • Using random or repeatable seed values (including weighting for discrete values).
    • Template-based text generation and string formatting.
    • Using SQL-based expressions to control or augment column generation.
    • Supporting Delta Live Tables (DLT) for both streaming and batch operations.
    • Supporting Unity Catalog enabled clusters.
    • Providing pluggable standard datasets for quick prototyping.
  2. Overview of Databricks Labs Data Generator

    master

    Databricks Labs Data Generator (dbldatagen) is a tool designed to generate large volumes of synthetic data within a Databricks notebook or a standard Spark application.

    Key capabilities include:

    • Schema-driven generation: Define data generation specs using an existing schema or by creating a new schema on the fly.
    • PySpark Integration: The generator produces PySpark DataFrames, which can be exposed to Scala or R-based Spark applications via temporary views.
    • Environment Compatibility: It can be installed via %pip install and is compatible with environments like Delta Live Tables.
  3. Use ArrayColumn to create repeated values

    master

    Use ArrayColumn to produce variable-length arrays of elements. This is ideal for repeated values like tags or scores. Each row's array length is randomly determined within the range [min_length, max_length].

    Configuration Fields

    • element (required): The generation strategy for each element in the array. This must be a strategy model (not a ColumnSpec).
    • min_length: Inclusive minimum length per row (default 1). Set to 0 to allow sometimes-empty arrays.
    • max_length: Inclusive maximum length per row (default 5).

    Key Constraints

    • Maximum length: max_length is capped at 1000. For larger fan-outs, restructure your data as a long/narrow table instead of a wide array.
    • Unsupported strategies: FakerColumn and ForeignKeyColumn cannot be used as the element strategy. To use them, wrap them in a StructColumn.
    • Null behavior: An array column with null_fraction=1.0 produces Spark NULL values, which are distinct from empty arrays []. There is no per-element null_fraction.
    • Fixed length: To produce an array with a fixed length, set min_length == max_length.
    from dbldatagen.core import ColumnSpec
    from dbldatagen.core.spec.schema import ArrayColumn, ValuesColumn, RangeColumn
    
    # Array of categorical values
    ColumnSpec(
        name="tags",
        gen=ArrayColumn(
            element=ValuesColumn(values=["sale", "new", "popular", "clearance"]),
            min_length=1,
            max_length=4,
        ),
    )
    
    # Array of numbers from a range
    ColumnSpec(
        name="scores",
        gen=ArrayColumn(element=RangeColumn(min=0, max=100), min_length=2, max_length=5),
    )
  4. Declare Primary Keys on a TableSpec

    master

    Neither SequenceColumn nor UUIDColumn automatically marks a column as a primary key. You must explicitly declare the key using the PrimaryKey object within the TableSpec.

    PrimaryKey Configuration:

    • columns: A list[str] containing the names of the columns forming the key, in declaration order.
    • Composite Keys: You can provide multiple column names for a composite primary key.
    • Constraints:
      • Each name must match a ColumnSpec.name defined in the table's columns.
      • Duplicate column names in the PrimaryKey list are rejected at plan time.
      • Important: While composite PKs are supported, ForeignKeyRef cannot point to a table with a composite PK; foreign keys must target single-column primary keys.
    • Requirement: A PrimaryKey is only strictly required if another table in your generation plan will use a ForeignKeyRef to point to this table.
    # Example of a single-column PK
    TableSpec(
        name="customers",
        primary_key=PrimaryKey(columns=["customer_id"]),
        columns=[
            ColumnSpec(name="customer_id", gen=SequenceColumn()),
        ]
    )
    
    # Example of a composite PK
    TableSpec(
        name="order_items",
        primary_key=PrimaryKey(columns=["order_id", "item_id"]),
        columns=[
            ColumnSpec(name="order_id", gen=SequenceColumn()),
            ColumnSpec(name="item_id", gen=SequenceColumn()),
        ]
    )
  5. Generate nested JSON structures (Structs, Arrays, Maps)

    master

    To generate complex nested types like struct, array, or map that will be converted to JSON when saved, you can use the expr attribute within withColumn.

    Important: When using the expr attribute, it will override other column data generation rules for that specific column.

    To simplify the process, you can use dg.INFER_DATATYPE as the type argument, which allows dbldatagen to infer the schema directly from your SQL expression.

    import dbldatagen as dg
    
    # Using expr with explicit type
    testDataSpec = (
        dg.DataGenerator(spark, name="device_data_set")
        .withColumn("event_info", 
                     dg.StructType([dg.StructField('event_type', dg.StringType()), 
                                    dg.StructField('event_ts', dg.TimestampType())]),
                     expr="named_struct('event_type', event_type, 'event_ts', event_ts)",
                     baseColumn=['event_type', 'event_ts'])
    )
    
    # Using dg.INFER_DATATYPE to simplify
    testDataSpec = (
        dg.DataGenerator(spark, name="device_data_set")
        .withColumn("event_info", 
                     dg.INFER_DATATYPE,
                     expr="named_struct('event_type', event_type, 'event_ts', event_ts)")
    )
  6. Use TimestampColumn to sample event times

    master

    Use TimestampColumn when you need to sample independent event times within a specific window (e.g., a signup_date for each customer).

    Important: TimestampColumn is a sampler, not a clock. It draws independent values from the range for each row and does not produce sequential or monotonic timestamps. If you need evenly-spaced or sequential timestamps (e.g., one row every hour), use a SequenceColumn combined with an ExpressionColumn instead.

    from dbldatagen.core.spec.schema import TimestampColumn
    from dbldatagen.core.spec.schema import ColumnSpec
    
    # Samples signup dates between 2022 and 2024
    ColumnSpec(
        name="signup_date",
        gen=TimestampColumn(start="2022-01-01", end="2024-12-31"),
    )
  7. Map file fields to DataGenPlan models

    master

    The JSON/YAML file format is a direct serialization of the Pydantic models used in the Python API. The field names in your file must match the model field names:

    • Top-level fields: tables, seed.
    • Table fields: name, rows, primary_key, columns.
    • Column fields: name, dtype, gen, foreign_key.
    • Generation strategies: Defined under gen using a strategy tag (e.g., sequence, values, timestamp, range, foreign_key).
    • Distributions: Defined under distribution using a type tag (e.g., zipf, weighted, uniform).
    • Foreign Keys: Defined in a sibling foreign_key block alongside the gen: {strategy: foreign_key} block.
  8. Behavior of seed_from correlation

    master

    The seed_from mechanism correlates a column to another column's value such that a specific source-key value always produces the same constant derived value. It does not provide per-row jitter.

    Workaround: Combine the seed_from base value with an independent noise column using an ExpressionColumn to introduce variance.

  9. Authoring plans using the Schema form (Pydantic models)

    master

    You can define a DataGenPlan by explicitly constructing ColumnSpec objects and passing specific strategy models (like SequenceColumn or RangeColumn) to each column. This approach is equivalent to the structure used when deserializing a plan from a JSON or YAML specification file. This style provides high granularity and is useful when you want to mirror a static configuration file in your code.

    from dbldatagen.core import DataGenPlan, TableSpec, ColumnSpec, PrimaryKey
    from dbldatagen.core.spec.schema import SequenceColumn, RangeColumn, DataType
    
    plan = DataGenPlan(
        seed=42,
        tables=[
            TableSpec(
                name="orders",
                rows=1000,
                primary_key=PrimaryKey(columns=["order_id"]),
                columns=[
                    ColumnSpec(name="order_id", gen=SequenceColumn(start=1, step=1)),
                    ColumnSpec(name="amount", dtype=DataType.DOUBLE,
                               gen=RangeColumn(min=5.0, max=500.0)),
                ],
            ),
        ],
    )
  10. Generate predictable foreign keys using hashing

    master

    To create join-ready data across multiple tables, you can generate predictable foreign keys using the baseColumnType="hash" option. This allows you to derive a unique, repeatable key from an existing column (like a customer_id) without needing to manage complex sequences.

    When using hashing to generate keys:

    • Use baseColumn to specify the source column.
    • Use baseColumnType="hash" to trigger the hashing logic.
    • For large datasets, consider using decimal types for IDs to prevent overflow when working with large hashed values.
    • Note that while hashing is highly efficient for generating predictable keys, you should still call .dropDuplicates() on the resulting DataFrame to handle potential hash collisions in smaller datasets.
    # Generating a device_id that is a hash of the customer_id
    customer_dataspec = (
        dg.DataGenerator(spark, rows=50000, partitions=8)
        .withColumn("customer_id", "decimal(10)", minValue=1000, uniqueValues=50000)
        .withColumn("device_id", "decimal(10)", minValue=1000000000, 
                    baseColumn="customer_id", baseColumnType="hash")
    )
    
    # Ensure uniqueness after generation
    df_customers = customer_dataspec.build().dropDuplicates(["device_id"])
  11. Requirements for Foreign Key relationships

    master

    A ForeignKeyRef target table must declare a single-column PrimaryKey. Composite (multi-column) primary keys cannot be used as targets for foreign keys and will be rejected at resolve_plan.

    Workaround:

    • Provide the parent table with a single-column PK.
    • Or, split the relationship into a derived single-column key.