Extract and Load: the data pipeline
This page explains what the agent writes to the data lake, how the raw tables and views are built, and the two mechanisms that keep large tables manageable: field partitions and incremental updates.
Data lake directory structure
For each table (or for each field-based partition of each table, when enabled) the agent
generates one CSV file and uploads it. The file has the same name as the table, with a
.csv extension, unless datalake_s3_table is configured. The layout is:
s3://<bucket>/[<base prefix>/]<table name>/[<base partitions>/][field partitions/]<table name>.csv
Where:
bucket — the
panorama_raw_data_bucketsetting.base prefix — optional subdirectory grouping tables of the same kind of system (for example
openedx). It can receive files from multiple sources as long as the table names and field structure match.table name — base location of the data lake table. All text files inside this directory must have exactly the same column structure.
base partitions — partitions common to a whole installation, written in Hive format. These are not source fields but appear as columns in the data lake. For multiple Open edX installations the default is
lms=<LMS_HOST>, for examplelms=openedx.example.com.field partitions — optional. For large tables you can split the source into multiple CSV files by the value of one or more fields. The field is removed from the CSV and becomes a partition column. The default Open edX setting partitions
courseware_studentmodulebycourse_id.
Raw tables
create-datalake-tables registers each uploaded table as an external Athena table over
the CSV files. Key characteristics of the generated table:
Every column is declared as
string(the raw layer does no typing).Base partitions and field partitions become partition columns.
The table uses the OpenCSV serde with a comma separator,
"quote character,\escape character, and skips one header line.The
LOCATIONpoints ats3://<bucket>/<base prefix>/<table>/(the partition subdirectories are discovered underneath).
Because the table is external, dropping it (drop-datalake-tables) removes only the
catalog entry, not the CSV data in S3.
Table views (first transformation layer)
create-table-views creates a CREATE OR REPLACE VIEW over each raw table. This is the
first layer of the pipeline and its job is to make the string data usable:
Numeric types (
INT,TINYINT,SMALLINT,MEDIUMINT,BIGINT) becomeTRY_CAST(... AS BIGINT).Floating types (
FLOAT,DOUBLE,DECIMAL) becomeTRY_CAST(... AS DOUBLE).Date/time types (
DATETIME,DATE,TIMESTAMP,TIME,YEAR) are parsed withdate_parse(..., '%Y-%m-%d %H:%i:%s.%f')insideTRY(...).JSON and string types get
NULLIF(<field>, 'NULL'), so the literal textNULLbecomes a realNULL.Base partitions are appended to the view as additional columns.
The default view name is <base_prefix>_table_<table name>. These typed views are what the
next pipeline layers (and the BI tool) build on.
Curated views
Further pipeline steps are implemented as additional Athena views that join the typed table
views, filter them, and add calculated fields. The repository includes ready-made Open edX
examples under openedx_views/:
openedx_view_enrollments.sql— a wide enrollment view joiningstudent_courseenrollmentwith users, profiles, grades, course overviews, course structures, country coordinates, course access roles, and manual enrollment history. It derives fields such asdays_since_enrolled,percent_grade,is_student, and role flags.openedx_view_student_courseaccessrole.sql— aggregates course access roles per user and course into boolean flags (is_course_staff,is_course_instructor, and so on).openedx_view_student_manualenrollment.sql— collapses the manual enrollment audit into one row per enrollment with the last transition, counts, and ordered history.
These are provided as SQL you run in Athena to build the curated layer that dashboards read.
Field partitions
To partition a table by a field, add a partitions section listing the fields under
partition_fields:
tables:
- name: courseware_studentmodule
datalake_table_name: courseware_studentmodule_raw
partitions:
partition_fields:
- course_id
fields:
...
Each distinct value in the specified column produces a separate CSV file, placed in a Hive
format subdirectory such as course_id=<value>. The partition field is removed from the CSV
body and becomes a partition column in the data lake.
Warning
This can create a very large number of subdirectories. Plan field partitions carefully.
Incremental updates
Incremental updates let periodic runs upload only the partitions that changed recently, instead of the whole table. A table must be partitioned by field to support incremental updates.
To enable them, add a timestamp_field and an interval to the table’s partitions
section:
tables:
- name: courseware_studentmodule
datalake_table_name: courseware_studentmodule_raw
partitions:
interval: 2 hour
partition_fields:
- course_id
timestamp_field: modified
fields:
...
timestamp_fieldmust be adatetimefield holding the last-modified time.intervaluses MySQL interval syntax. As a rule of thumb, set it to double your query period: if you extract hourly, use a 2 hour interval.
How it works. The agent first queries the distinct values of the partition fields that
have records with a timestamp within the last interval from now. Then it updates only the
field-based partitions that changed. Note that the whole matching partition is rewritten,
not just the changed rows.
This is why the first run uses --force: it uploads every partition
once, including those older than the interval, so the data lake starts complete.