Data Lakes and Lakehouses

Topics Covered

Data Lake Architecture

Why Object Storage Won

Schema-on-Read

Common File Formats

The Data Swamp Problem

Data Lake Zones

The Lakehouse Pattern

How a Lakehouse Works

ACID Transactions on Object Storage

Time Travel

Schema Enforcement and Evolution

Lakehouse vs. Warehouse vs. Lake

Hybrid Architectures

Open Table Formats

Apache Iceberg

Delta Lake

Apache Hudi

Choosing a Format

Migration Path

Row-Level Operations

Common Features Across All Three

Catalog and Governance

Format Interoperability

Query Engines for Analytics

Trino (Presto) for Federated SQL

Predicate Pushdown and Filter Optimization

Apache Spark for Batch Processing

Amazon Athena for Serverless SQL

Partition Strategies

Compaction and Optimization

Choosing the Right Engine

Cost Optimization Strategies

When Not to Use a Lakehouse

A data lake is a storage system that holds raw data in its native format on cheap object storage like Amazon S3, Azure Blob Storage, or Google Cloud Storage. Unlike a data warehouse that requires you to define a schema before loading data (schema-on-write), a data lake lets you dump any file in any format and figure out the structure later (schema-on-read). This flexibility is why data lakes became the default landing zone for organizations drowning in data from web logs, IoT sensors, mobile apps, and third-party feeds.

The data lake concept emerged in the early 2010s when organizations realized that traditional warehouses could not keep up with the volume, variety, and velocity of modern data. A warehouse requires you to model the data before loading it. When you have 50 data sources producing JSON, CSV, Parquet, images, and unstructured text at different rates, modeling everything upfront is impractical. The data lake inverts this: store everything first, ask questions later.

Five things a bucket full of Parquet files simply cannot do, and the table format that adds each one back.

Why Object Storage Won

Object storage charges $0.023 per GB per month on S3 Standard. A traditional data warehouse charges $40 or more per TB per month for compute-coupled storage. At 100 TB, that is $2,300/month versus $400,000/month. This cost difference is not marginal. It is the reason every major company moved raw data out of warehouses and into lakes.

Object storage also scales without limits. There is no cluster to resize, no disk to provision. You write files and the storage layer handles replication, durability (11 nines on S3), and availability. This operational simplicity matters as much as the cost savings.

The architecture is straightforward: producers (web servers, mobile apps, IoT devices, third-party feeds) write files to S3 using simple PUT operations. There is no ingestion server to manage, no schema to register, no capacity to plan. A new data source starts writing files and the lake grows automatically. This write-anything-anytime model is why data lakes became the universal landing zone.

The decoupling of storage from compute is the architectural foundation. In a traditional warehouse, storage and compute are bundled: you provision a Redshift cluster with a fixed amount of storage and CPU, and you pay for both whether or not queries are running. In a data lake, S3 stores the data and you attach compute (Spark, Trino, Athena) only when you need to process it. This means a data lake with 100 TB of data costs the same whether you run 1,000 queries per day or zero. The compute cost scales independently with actual query volume.

This decoupling also enables multi-tenancy without resource contention. Different teams can run different query engines against the same data simultaneously. The marketing team runs Athena queries while the data engineering team runs Spark transformations while the ML team trains models, all reading from the same S3 data without competing for the same compute resources. In a warehouse, these workloads would contend for the same cluster's CPU and memory, requiring careful workload management and priority queuing.

Schema-on-Read

In a data lake, you store data first and interpret it later. A JSON log file sits next to a CSV export sits next to a Parquet analytics table. When you query, the query engine reads the file, infers or applies a schema, and returns results. This means the same data can be read with different schemas by different teams. The marketing team reads campaign fields from a JSON event log while the engineering team reads latency fields from the same file.

The tradeoff is obvious: without enforced schema, data quality degrades over time. A producer changes a field name from user_id to userId and downstream consumers break silently. No constraint prevents duplicate records, null values in required fields, or incompatible type changes. This is manageable at small scale and catastrophic at large scale.

Consider a concrete example. An event pipeline writes JSON records with a timestamp field as a Unix epoch integer. A new version of the producer starts writing ISO 8601 strings instead. Both are valid JSON. Both land in S3 without error. But when a Spark job reads the partition expecting integers, it fails on the string records. In a warehouse, the schema would have rejected the string at write time. In a data lake, the error surfaces days later when someone queries.

Another common failure mode is silent data loss through overwrite. Two pipelines write to the same S3 prefix. Pipeline A writes a file named events-2024-03-15.parquet. Pipeline B writes a different file with the same name 30 minutes later, silently overwriting Pipeline A's data. S3 does not prevent this because it has no concept of file locking or transactions. The data from Pipeline A is gone with no audit trail and no recovery option unless you have S3 versioning enabled.

Common File Formats

Data lakes store data in several formats, each with different tradeoffs:

JSON and CSV are human-readable and universal. Every programming language can parse them. But they are inefficient for analytics: JSON repeats field names in every record, CSV has no type information, and neither supports column pruning. A query that needs two columns out of fifty still reads all fifty.

Parquet and ORC are columnar binary formats designed for analytics. They store data column-by-column, so a query reading two columns skips the other forty-eight entirely. They compress aggressively because values in a column share the same type and often repeat. A Parquet file is typically 3-5x smaller than the equivalent CSV. Parquet also stores metadata at the file footer including min/max values per column per row group, which allows query engines to skip entire row groups that cannot contain matching data. ORC provides similar capabilities and is the default format in the Hive ecosystem.

A fifty column table written by row and then by column, with what a three column query has to read in each.

Avro is a row-based binary format with an embedded schema. It is the standard for streaming pipelines (Kafka) because row orientation matches append-heavy workloads and the embedded schema enables schema evolution without a central registry. When a Kafka producer writes an Avro record, the schema is registered in a schema registry and the record includes a schema ID. A consumer reads the schema ID, fetches the schema, and deserializes the record. If the producer adds a new field, the consumer's older schema ignores it gracefully.

The format choice creates a natural two-layer architecture in most data lakes. The ingestion layer uses Avro (or JSON) for low-latency, record-at-a-time writes from streaming sources. A periodic ETL job (hourly or daily) converts the ingestion layer into Parquet files optimized for analytical queries. This conversion step is where columnar encoding, compression, and partitioning are applied.

Interview Tip

When an interviewer asks about data lake file formats, the key insight is that the choice depends on the access pattern. Columnar formats (Parquet, ORC) win for analytics where you read few columns across many rows. Row formats (Avro, JSON) win for operational workloads where you read or write entire records. Most production data lakes use Parquet for the analytical layer and Avro for the ingestion layer.

The Data Swamp Problem

A data lake becomes a data swamp when nobody can find, trust, or use the data in it. This happens predictably:

No catalog: Teams dump files into S3 prefixes with ad-hoc naming. Six months later, nobody knows what /raw/events/2024/03/batch_7.parquet contains or which pipeline produced it.

No schema enforcement: A producer changes a column type from integer to string. Downstream Spark jobs fail with type errors, but only when they run, which might be days later.

No access control: Sensitive PII data sits in the same bucket as public analytics data. Compliance audits become nightmares.

No ACID transactions: A writer crashes halfway through updating a partition. Readers see partial data. There is no rollback, no isolation, no consistency guarantee.

No data lineage: When a downstream report shows wrong numbers, tracing the problem back to the source file, the pipeline that produced it, and the transformation that corrupted it requires manual detective work across logs, S3 prefixes, and Airflow DAGs.

These problems are not theoretical. They are the exact reasons the lakehouse pattern was invented. A 2020 survey by Databricks found that 85% of big data projects fail, and the most common reason is that teams cannot trust the data in their lake.

Data Lake Zones

Production data lakes organize files into zones that reflect the data's maturity:

Landing zone (raw/bronze): Data arrives exactly as produced. No transformation, no deduplication, no schema enforcement. This zone is the system of record. If anything goes wrong downstream, you can always re-process from the raw data.

Curated zone (cleaned/silver): Data has been validated, deduplicated, schema-enforced, and converted to Parquet. This is where most analytical queries start. A Spark or Flink job reads the landing zone, applies transformations, and writes here with ACID guarantees.

Consumption zone (aggregated/gold): Business-ready tables optimized for specific use cases. Pre-aggregated metrics, joined dimensions, feature stores for ML. Dashboards and applications query this zone.

This zone architecture creates a clear contract: the landing zone is append-only and never modified after write. The curated zone is reprocessable from the landing zone. The consumption zone is reprocessable from the curated zone. If a bug corrupts the gold layer, you rerun the silver-to-gold job. If a schema change breaks the silver layer, you rerun the bronze-to-silver job. The raw data is always the ultimate source of truth.

The zone pattern also enables access control boundaries. The landing zone may contain PII that only the data engineering team can access. The curated zone has PII masked or tokenized. The consumption zone contains only aggregated, anonymized data that business analysts can query freely.

Not every organization needs all three zones on day one. Start with a landing zone and a single curated zone. Add the consumption zone when you have enough use cases to justify pre-aggregation. The important principle is that each zone is reprocessable from the zone below it, so you can always recover from data quality issues without losing the raw source.

A well-designed zone architecture also simplifies access control. Raw data with PII stays in the bronze zone with restricted access. The silver zone applies PII masking (hashing email addresses, truncating IP addresses, tokenizing names). The gold zone contains only aggregated, anonymized data that is safe for broad access. This layered approach satisfies compliance requirements (GDPR, CCPA, HIPAA) without requiring complex row-level security policies on a single table.

Each zone typically lives in a separate S3 bucket or prefix with distinct IAM policies. This ensures that even if an analyst's credentials are compromised, they can only access the gold zone data that is already anonymized. The bronze zone containing raw PII requires separate, tightly controlled credentials that only the data engineering team holds.

Naming conventions matter in this architecture. A common pattern is s3://company-data-lake-bronze/source_name/year/month/day/ for the raw zone, s3://company-data-lake-silver/domain/table_name/ for curated tables, and s3://company-data-lake-gold/use_case/table_name/ for consumption tables. Consistent naming makes automation, monitoring, and cost attribution straightforward. Tagging each bucket with a cost-center tag enables AWS Cost Explorer to show exactly how much each team spends on storage, which is essential for chargeback models in large organizations.