From the project root, start the containers in the background:
docker compose up -d
Check their status:
docker ps
docker compose ps -a and docker compose logs <service-name>.RustFS provides S3-compatible object storage for this lab.
01-raw and create or navigate to the prefix people/.inputs/people1.csv so its object path is 01-raw/people/people1.csv.01-raw is the bucket. people/ is a prefix in that bucket; S3-compatible storage does not require it to be a real directory.From the project root, build and start the configured CLI container:
docker compose --env-file .env -f aws-cli/compose.yaml build
docker compose --env-file .env -f aws-cli/compose.yaml run --rm aws-cli
Run the following commands inside the AWS CLI container. These use the bucket names from the main instructions:
aws --endpoint-url http://rustfs:9000 s3 mb s3://01-raw
aws --endpoint-url http://rustfs:9000 s3 mb s3://02-curated
aws --endpoint-url http://rustfs:9000 s3 mb s3://03-processed
aws --endpoint-url http://rustfs:9000 s3 ls
Upload the sample CSV and verify that it is present:
aws --endpoint-url http://rustfs:9000 s3 cp ./inputs/people1.csv s3://01-raw/people/people1.csv
aws --endpoint-url http://rustfs:9000 s3 ls s3://01-raw/people/
When finished with the CLI container, type:
exit
./inputs/people1.csv exists in the CLI container's mounted working directory. If your supplied file has a different name or location, adjust the source path.
In this part, Spark reads the CSV file directly from object storage.
Start the interactive Spark SQL shell:
docker compose exec --user spark spark /opt/spark/bin/spark-sql
Enable column headers in query output:
SET spark.sql.cli.print.header=true;
Query the CSV file directly. The example uses people1.csv, uploaded in the previous step:
SELECT *
FROM csv.`s3a://01-raw/people/people1.csv`;
Here, a SQL script registers a table over the raw CSV data. The table definition is stored in Hive Metastore; the source CSV remains in object storage.
Register the table and perform initial data load:
SOURCE /sql/01_create_people.sql;
Verify:
SELECT * FROM lake_demo.people;
inputs/people2.csv to bucket 01-raw, under prefix people/: 01-raw/people/people2.csv.REFRESH TABLE refreshes cached metadata):REFRESH TABLE lake_demo.people;
SELECT * FROM lake_demo.people;
SHOW TABLES IN lake_demo;
DESCRIBE EXTENDED lake_demo.people;
When finished, exit Spark SQL:
quit;
In this part, Spark creates and queries an Iceberg table using the configured Iceberg catalog.
Start the Iceberg-enabled Spark SQL shell:
docker compose exec --user spark spark-iceberg /opt/spark/bin/spark-sql
Enable column headers:
SET spark.sql.cli.print.header=true;
Run the script that creates the Iceberg table from the Hive Metastore catalog table:
SOURCE /sql/02_create_people_iceberg.sql;
Verify the data and inspect the catalog entry:
SELECT * FROM iceberg.lakehouse_demo.people_iceberg;
SHOW TABLES IN iceberg.lakehouse_demo;
DESCRIBE TABLE EXTENDED iceberg.lakehouse_demo.people_iceberg;
List the table's snapshots:
SOURCE /sql/03_get_snapshots.sql;
Insert a row using the supplied script:
SOURCE /sql/04_insert_people.sql;
Verify the updated table:
SELECT * FROM iceberg.lakehouse_demo.people_iceberg;
List snapshots again and compare the results:
SOURCE /sql/03_get_snapshots.sql;
Finally, query an earlier version of the table:
SOURCE /sql/05_get_old_data.sql;
/sql/05_get_old_data.sql and replace its example snapshot ID with the actual ID of the older snapshot shown by /sql/03_get_snapshots.sql. Do not use the newest snapshot ID if you want to see the earlier table state.docker compose down
This stops and removes the Compose containers and network. Named volumes and persisted object-storage data are generally retained unless you explicitly remove them.
docker compose restart spark
docker compose up -d --force-recreate spark
docker volume rm data_lakehouse_demo_hive_data_v3
If Docker reports that the volume is in use, stop the relevant service first. Confirm the actual volume name with docker volume ls.