keepcalculator

06 Numbers and Powers

Batch Lineage: Logging Parent IDs for Inputs and Outputs

Card No. 06·30374 · Updated · Standard formula, shown below

Yes, batch lineage traces inputs and outputs by logging parent IDs—each output record stores the identifiers of the input records (or upstream batches) that produced it, enabling full provenance reconstruction. This is the core mechanism in data pipelines, ML training sets, and ETL jobs, where every derived dataset can be traced back to its exact sources.

Why Parent IDs Matter in Batch Lineage

Batch lineage answers the question: “Where did this data come from, and what did it produce?� Without parent IDs, you only know that a job ran, not which specific records influenced a result. Logging parent IDs creates a directed acyclic graph (DAG) of data dependencies, where each node is a dataset or record and each edge is a parent-child relationship. This graph is essential for debugging anomalies, auditing compliance (e.g., GDPR data subject requests), and reproducing experiments in machine learning.

How Parent ID Logging Works

In practice, a batch job reads input records, each with a unique ID (e.g., a UUID or database primary key). When writing output, the job appends a column like parent_ids containing the list of input IDs that contributed to that output row. For aggregations (e.g., sum or average), the parent IDs are all the input rows in the group. For joins, the parent IDs come from both sides. For transformations like filtering, the parent ID is simply the input row’s ID.

Consider a simple example: a job that reads user events (each with event_id) and computes daily counts per user. The output row for user 123 on 2025-01-01 might have parent_ids = [evt_1001, evt_1002, evt_1003]. If a bug is later found in event processing, you can immediately identify which daily counts were affected.

Implementation Patterns

There are several common ways to store parent IDs:

  • Embedded list column: Add a column of type array or JSON to the output table. Simple, but can bloat storage if fan-out is large.
  • Separate lineage table: Store (child_id, parent_id) pairs in a dedicated table. More normalized, allows efficient queries, but requires extra writes.
  • Log file with job metadata: Record parent IDs in a log file alongside the job run ID, not in the output itself. Useful when you don’t want to alter the output schema.
  • Data versioning systems: Tools like Delta Lake, Iceberg, or DVC automatically track file-level lineage, but they don’t always record record-level parent IDs unless you explicitly add them.

Choosing the Right Approach

The best method depends on your query patterns and storage constraints:

  • If you need to trace individual records frequently, an embedded column is fastest for reads.
  • If you need to answer “which outputs came from this input?â€� (reverse lineage), a separate table with an index on parent_id is better.
  • If your outputs are large and you rarely trace, a log file may suffice.
  • For compliance, you may need to retain lineage for years, so consider storage costs.

Common Pitfalls and Misconceptions

A frequent misconception is that lineage requires capturing every intermediate step. In reality, you only need to log parent IDs at the points where you want to trace—typically at the final output. Intermediate steps can be recomputed if needed.

Another pitfall is not handling large fan-in. A row that aggregates 10 million records would have a parent_ids list of 10 million entries, which is impractical. Solutions include storing a range (e.g., min and max IDs) if IDs are sequential, or storing a hash of the input set instead of individual IDs.

Also, beware of ID collisions across different source systems. Always prefix or namespace your parent IDs (e.g., events:evt_1001) to avoid ambiguity.

Costs and Performance Trade-offs

Logging parent IDs adds storage and write overhead. For each output row, you must serialize and store the list, which can increase size significantly. In a high-volume pipeline, this can double or triple storage costs. However, the benefit is that you avoid recomputing lineage from scratch later, which is often far more expensive.

Query performance also matters. If you store parent IDs as a JSON array, filtering for a specific parent ID may require a full scan unless you use a specialized index (e.g., a GIN index in PostgreSQL). A separate lineage table with a b-tree index on parent_id is usually more efficient for reverse lookups.

Alternatives to Parent ID Logging

If you cannot modify your output schema, consider these alternatives:

  • Job-level lineage: Log which input files were read and which output files were written, without record-level detail. This is coarser but much cheaper.
  • Data contract with upstream: Have upstream systems tag records with a batch_id, then propagate that batch_id downstream. This gives batch-level lineage, not record-level.
  • Recomputation: If your transformations are deterministic, you can replay the job with a debug flag that logs parent IDs only for a specific output row. This avoids permanent storage.

Frequently Asked Questions

Does parent ID logging work for streaming data?

Yes, but it’s trickier. In streaming, you don’t have fixed batches, so you need to assign a window or micro-batch ID to each input record, then log that as the parent. Tools like Kafka Streams allow you to propagate headers, but you must explicitly add the parent ID to the output.

Can I trace lineage if I didn’t log parent IDs originally?

Not directly. You can only reconstruct lineage if you have the original input data and a deterministic transformation. You could re-run the job with logging enabled, but if the input has changed, you won’t get the exact original lineage.

What’s the difference between lineage and data provenance?

They are often used interchangeably, but lineage typically refers to the relationship between datasets (e.g., table A → table B), while provenance includes the full history of how a piece of data came to be, including transformations and who ran them. Parent ID logging is a key part of both.

How do I handle parent IDs when merging multiple inputs?

In a join, you can concatenate the parent IDs from both sides, or store them as a nested structure. For example, parent_ids = {'left': [id1, id2], 'right': [id3]}. This makes it clear which side contributed which IDs.

Is there a standard format for lineage data?

There are open standards like W3C PROV, but they are often too heavy for practical use. Most systems use custom JSON or relational schemas. For interoperability, you might adopt a simple convention like parent_ids as a comma-separated string, but be aware of escaping issues.

Does logging parent IDs violate privacy regulations?

It can, if the parent IDs themselves are personal data. You might need to pseudonymize them or store them separately with access controls. Always consult your legal team, but generally, lineage data should be treated as sensitive as the data itself.

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.

More cards in this drawer

The whole Numbers and Powers drawer →