Building a Secure, Cost-Efficient Data Lake on AWS with S3, Glue, Lake Formation, and Athena
A practical walkthrough of a layered AWS data lake architecture: how to organize S3 zones, catalog data with AWS Glue, govern access with Lake Formation, and query efficiently with Athena while keeping costs under control.
A data lake lets you store structured, semi-structured, and unstructured data at any scale and decide how to use it later. On AWS, the most common foundation is Amazon S3, combined with AWS Glue for cataloging and ETL, AWS Lake Formation for governance, and Amazon Athena for serverless SQL. This article walks through a layered architecture that works well in practice, along with the design decisions that matter most for performance, security, and cost.
Core building blocks
Storage: Amazon S3 provides durable, low-cost object storage for all zones.
Ingestion: Amazon Data Firehose, AWS DMS, AWS Glue, and AWS Transfer Family bring batch and streaming data into S3.
Catalog: The AWS Glue Data Catalog is the central metadata store for tables and schemas.
Transformation: AWS Glue ETL and Amazon EMR clean, convert, and enrich data.
Governance: AWS Lake Formation, IAM, and AWS KMS provide fine-grained permissions and encryption.
Consumption: Amazon Athena, Amazon Redshift Spectrum, and Amazon QuickSight query and visualize data.
Ingestion: Amazon Data Firehose, AWS DMS, AWS Glue, and AWS Transfer Family bring batch and streaming data into S3.
Catalog: The AWS Glue Data Catalog is the central metadata store for tables and schemas.
Transformation: AWS Glue ETL and Amazon EMR clean, convert, and enrich data.
Governance: AWS Lake Formation, IAM, and AWS KMS provide fine-grained permissions and encryption.
Consumption: Amazon Athena, Amazon Redshift Spectrum, and Amazon QuickSight query and visualize data.
Organize the lake into zones
A single bucket full of mixed files becomes unmanageable quickly. Separate your data by maturity, either with prefixes inside one bucket or, preferably for stronger isolation, with separate buckets per zone.
Raw (landing) zone: Data exactly as it arrived from the source, never modified. This is your replayable source of truth.
Curated (processed) zone: Cleaned, validated, deduplicated data stored in a columnar format such as Parquet.
Consumption (analytics) zone: Aggregated, business-ready datasets shaped for dashboards, reports, and machine learning features.
Curated (processed) zone: Cleaned, validated, deduplicated data stored in a columnar format such as Parquet.
Consumption (analytics) zone: Aggregated, business-ready datasets shaped for dashboards, reports, and machine learning features.
A clean prefix convention makes partitioning and permissions far easier. For example:
s3://example-data-lake-raw/sales/orders/ingest_date=2026-10-01/
s3://example-data-lake-curated/sales/orders/order_date=2026-10-01/
s3://example-data-lake-analytics/sales/daily_revenue/
s3://example-data-lake-curated/sales/orders/order_date=2026-10-01/
s3://example-data-lake-analytics/sales/daily_revenue/
Ingest data reliably
Match the ingestion tool to the source.
Streaming events (clickstreams, IoT, application logs): Amazon Data Firehose can buffer records and deliver them to S3, optionally converting JSON to Parquet on the way in.
Relational databases: AWS Database Migration Service (DMS) supports a full load plus ongoing change data capture (CDC) into S3.
Files from partners or on-premises systems: AWS Transfer Family or scheduled AWS Glue jobs.
Relational databases: AWS Database Migration Service (DMS) supports a full load plus ongoing change data capture (CDC) into S3.
Files from partners or on-premises systems: AWS Transfer Family or scheduled AWS Glue jobs.
Whatever you choose, land the data in the raw zone first and transform it afterward. Keeping the original data lets you fix transformation bugs by reprocessing instead of re-extracting from the source.
Catalog everything with AWS Glue
The Glue Data Catalog is the shared metadata layer that Athena, Redshift Spectrum, EMR, and Glue ETL all read from. You can populate it with Glue crawlers, which infer schemas and detect new partitions, or define tables explicitly with DDL or infrastructure as code. Crawlers are convenient for exploration, but for production tables, explicit definitions give you predictable schemas and avoid surprise column changes when upstream data drifts.
Convert to columnar formats and partition wisely
The two biggest levers for Athena cost and speed are file format and partitioning. Athena charges by the amount of data scanned, so storing data as compressed Parquet and partitioning by commonly filtered columns can cut scanned data dramatically.
Here is an Athena CTAS statement that converts raw JSON into partitioned, Snappy-compressed Parquet:
CREATE TABLE curated.orders
WITH (
format = 'PARQUET',
write_compression = 'SNAPPY',
external_location = 's3://example-data-lake-curated/sales/orders/',
partitioned_by = ARRAY['order_date']
) AS
SELECT
order_id,
customer_id,
amount,
order_date
FROM raw.orders_json;
WITH (
format = 'PARQUET',
write_compression = 'SNAPPY',
external_location = 's3://example-data-lake-curated/sales/orders/',
partitioned_by = ARRAY['order_date']
) AS
SELECT
order_id,
customer_id,
amount,
order_date
FROM raw.orders_json;
In an Athena CTAS statement, the partition columns must be the last columns in the SELECT list.
Partitioning guidelines:
Partition on columns that appear in most WHERE clauses, typically a date.
Avoid over-partitioning. Millions of tiny partitions slow down query planning and create many small files.
Aim for files in the range of roughly 128 MB to 1 GB. Many tiny files hurt performance, so compact them periodically with a Glue job or a CTAS or INSERT INTO rewrite.
Avoid over-partitioning. Millions of tiny partitions slow down query planning and create many small files.
Aim for files in the range of roughly 128 MB to 1 GB. Many tiny files hurt performance, so compact them periodically with a Glue job or a CTAS or INSERT INTO rewrite.
Govern access with Lake Formation
IAM policies alone become hard to manage as the number of datasets and teams grows. AWS Lake Formation adds a central permission model on top of the Glue Data Catalog.
Grant database-, table-, column-, and row-level permissions to IAM roles and users.
Use LF-tags (tag-based access control) to grant access by classification, for example sensitivity=pii, instead of naming every table.
Apply data filters so analysts see only the rows and columns they are entitled to.
Share datasets across AWS accounts without copying the data.
Use LF-tags (tag-based access control) to grant access by classification, for example sensitivity=pii, instead of naming every table.
Apply data filters so analysts see only the rows and columns they are entitled to.
Share datasets across AWS accounts without copying the data.
Layer on the basics as well: enable S3 Block Public Access on every bucket, encrypt data at rest with SSE-KMS, require TLS in transit, and turn on AWS CloudTrail and S3 server access logging for auditability.
Query with Athena
With data cataloged and stored as partitioned Parquet, analysts can query it directly with standard SQL and no cluster to manage:
SELECT order_date, SUM(amount) AS revenue
FROM curated.orders
WHERE order_date BETWEEN DATE '2026-09-01' AND DATE '2026-09-30'
GROUP BY order_date
ORDER BY order_date;
FROM curated.orders
WHERE order_date BETWEEN DATE '2026-09-01' AND DATE '2026-09-30'
GROUP BY order_date
ORDER BY order_date;
Because the filter is on the partition column, Athena reads only the matching partitions rather than the entire table. For workloads that need heavier joins or a persistent warehouse, Amazon Redshift Spectrum can query the same S3 data alongside Redshift tables.
Consider open table formats
Plain Parquet files on S3 are append-friendly but awkward for updates, deletes, and schema evolution. Open table formats such as Apache Iceberg add ACID transactions, time travel, and safer schema changes on top of S3. Athena and AWS Glue both support Iceberg tables, so it is worth evaluating them if your lake needs record-level updates or CDC merges rather than append-only loads.
Control costs
Use S3 lifecycle rules to move older raw data to cheaper storage classes such as S3 Standard-IA or S3 Glacier.
Enable S3 Intelligent-Tiering for data with unpredictable access patterns.
Use Athena workgroups to set per-query and per-workgroup data scan limits.
Compress data and compact small files regularly.
Tag buckets and jobs so you can attribute spend to teams and projects.
Enable S3 Intelligent-Tiering for data with unpredictable access patterns.
Use Athena workgroups to set per-query and per-workgroup data scan limits.
Compress data and compact small files regularly.
Tag buckets and jobs so you can attribute spend to teams and projects.
Common pitfalls
- Skipping the raw zone. Without it, you cannot reprocess after a transformation bug.
- Letting crawlers manage production schemas. Unexpected schema drift can silently break downstream queries.
- Over-partitioning. More partitions are not always better, and tiny files hurt performance.
- Granting broad S3 access instead of using Lake Formation. This makes least-privilege access nearly impossible to maintain.
- No data ownership. Every dataset needs a clear owner, a documented schema, and a defined quality check.
Conclusion
A well-built AWS data lake is less about any single service and more about discipline: separate zones by data maturity, store curated data in partitioned columnar formats, catalog everything centrally, and enforce governance through Lake Formation from the start. Get these foundations right, and you can add streaming ingestion, machine learning pipelines, or new analytics tools later without redesigning the platform.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article