← Kỹ sư dữ liệu← Data Engineer
Kỹ sư dữ liệuData Engineer19 Th7, 2026Jul 19, 202621 phút đọc16 min read

Nền tảng lập trình & Công cụProgramming & Tooling Foundations

Thuộc bộ kiến thức Data Engineer Roadmap.

Tổng quan

Data engineering, xét cho cùng, là một ngành software engineering áp dụng vào việc di chuyển và định hình dữ liệu. Trước khi nói đến pipeline, warehouse hay orchestration tool, một data engineer cần một bộ kỹ năng lập trình nhỏ nhưng được chọn lọc kỹ, xuất hiện trong hầu như mọi tác vụ hàng ngày: viết SQL để query và transform dữ liệu ở quy mô lớn, viết Python để kết nối các hệ thống lại với nhau và xử lý dữ liệu bên ngoài database, sử dụng Linux thành thạo vì đó là nơi gần như toàn bộ hạ tầng dữ liệu vận hành, và dùng Git một cách kỷ luật vì code pipeline vẫn là code và xứng đáng được đối xử nghiêm túc như bất kỳ production software nào.

Note này cố tình mang tính nền tảng — đây là lớp mà mọi phần khác trong roadmap (modeling, warehousing, ETL/ELT, orchestration, streaming) được xây dựng lên trên. Có được sự thành thạo SQL và Python, thoải mái với shell Linux, và version control kỷ luật trước tiên, thì phần còn lại của roadmap sẽ chỉ là áp dụng những kỹ năng này vào các bài toán ngày càng lớn và chuyên biệt hơn, thay vì phải học lập trình từ đầu. Chúng ta cũng sẽ xem xét khi nào các ngôn ngữ khác (Scala, Java, Go) có ý nghĩa, tại sao khối lượng dữ liệu thay đổi cách ta suy luận về độ phức tạp thuật toán, và tại sao ngành công nghiệp ngày càng ưu tiên các công cụ declarative hơn là script imperative để mô tả pipeline.

Kiến thức nền tảng

SQL là kỹ năng cốt lõi không thể thương lượng

Nếu một data engineer chỉ được thành thạo một ngôn ngữ, đó phải là SQL. Mọi relational store và hầu hết analytical store — Postgres, MySQL, Snowflake, BigQuery, Redshift, Databricks SQL, DuckDB — đều nói SQL, và ngay cả các công cụ “NoSQL”-adjacent (Spark SQL, Trino, Presto, Athena) cũng hội tụ về SQL như query interface cho analytics. SQL là declarative: bạn mô tả kết quả mình muốn, còn query planner sẽ quyết định cách đạt được nó — đây chính là mức abstraction mà phần lớn công việc transform dữ liệu nên hoạt động ở đó.

Một bộ kỹ năng SQL ở mức production đi xa hơn nhiều so với SELECT ... WHERE:

Ví dụ thực hành: CTE + window function

Xét một bảng chứa doanh thu theo ngày của từng cửa hàng, và cần tính cho mỗi cửa hàng running total doanh thu cùng thứ hạng của từng ngày theo doanh thu:

WITH daily_revenue AS (
    SELECT
        store_id,
        order_date,
        SUM(order_amount) AS revenue
    FROM orders
    WHERE order_date >= DATE '2026-01-01'
    GROUP BY store_id, order_date
)
SELECT
    store_id,
    order_date,
    revenue,
    SUM(revenue) OVER (
        PARTITION BY store_id
        ORDER BY order_date
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS running_total_revenue,
    RANK() OVER (
        PARTITION BY store_id
        ORDER BY revenue DESC
    ) AS revenue_rank_in_store
FROM daily_revenue
ORDER BY store_id, order_date;

CTE (daily_revenue) thực hiện phép aggregation một lần và đặt cho nó một cái tên dễ đọc; query bên ngoài sau đó áp dụng hai window function độc lập lên trên nó. SUM(...) OVER (PARTITION BY store_id ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) tạo ra một running total được reset theo từng cửa hàng, và RANK() OVER (PARTITION BY store_id ORDER BY revenue DESC) xếp hạng doanh thu mỗi ngày trong phạm vi cửa hàng đó — tất cả mà không làm gộp mất grain theo ngày qua GROUP BY. Pattern này (CTE để định hình các dòng cơ sở, window function để thêm ngữ cảnh running/ranking) là một trong những building block phổ biến nhất trong SQL analytics cũng như trong model dbt.

Python cho data engineering

Python thống trị data engineering vì một lý do đơn giản: đây là ngôn ngữ chung xuyên suốt mọi lớp của modern data stack. Việc khám phá dữ liệu (exploration) và prototyping diễn ra trong pandas trên laptop; cùng một logic đó, khi vượt quá khả năng của một máy đơn, được viết lại (thường gần như 1:1) bằng PySpark để xử lý phân tán; và lớp orchestration lập lịch cũng như giám sát toàn bộ — Apache Airflow, Dagster, Prefect — bản thân nó cũng Python-native, nghĩa là pipeline được định nghĩa hoàn toàn bằng code Python (hoặc các hàm được decorate bằng Python), không phải một DSL độc quyền. Tính liên tục này — cùng một ngôn ngữ từ notebook khám phá đến DAG production — chính là điều khiến Python trở thành mô liên kết (connective tissue) của ngành, còn quan trọng hơn bất kỳ thư viện đơn lẻ nào.

Các thư viện chủ chốt mà data engineer thường xuyên dùng đến:

Thư việnMục đích
pandasThao tác dữ liệu dạng bảng trong bộ nhớ — exploration, transform quy mô nhỏ-vừa, join/aggregation nhanh
pyarrowĐịnh dạng columnar in-memory (Apache Arrow) và I/O Parquet/CSV nhanh; là nền tảng bên dưới pandas 2.x, tương tác với Spark, và DuckDB
requestsGọi HTTP đến REST API — phần lớn logic “extract” trong ETL là requests.get() đến một API của vendor nào đó
sqlalchemyBộ công cụ SQL và ORM độc lập với database — kết nối và ghi dữ liệu vào relational database từ code Python
pysparkAPI Python cho Apache Spark — xử lý phân tán khi dữ liệu không còn vừa trên một máy

Ví dụ thực hành: CSV → transform → database

Một script “extract, transform, load” nhỏ nhưng tiêu biểu — đọc một file CSV, làm sạch và tái định hình bằng pandas, rồi ghi vào một bảng Postgres bằng SQLAlchemy:

import pandas as pd
from sqlalchemy import create_engine

# Extract: read the raw CSV
df = pd.read_csv("daily_orders.csv", parse_dates=["order_date"])

# Transform: clean, cast, and derive columns
df = df.dropna(subset=["store_id", "order_amount"])
df["order_amount"] = df["order_amount"].astype("float64")
df["order_date"] = df["order_date"].dt.date
df["revenue_bucket"] = pd.cut(
    df["order_amount"],
    bins=[0, 50, 200, float("inf")],
    labels=["low", "medium", "high"],
)

# Load: write to a Postgres table, replacing existing data for idempotency
engine = create_engine("postgresql+psycopg2://etl_user:secret@localhost:5432/warehouse")
df.to_sql(
    "stg_orders",
    con=engine,
    schema="staging",
    if_exists="replace",
    index=False,
    chunksize=10_000,
    method="multi",
)

Ngay cả một script nhỏ như thế này cũng minh họa những mối quan tâm thực tế: chunksizemethod="multi" gộp các lệnh insert thành batch thay vì từng dòng một (rất quan trọng khi có nhiều hơn vài nghìn dòng), if_exists="replace" khiến việc chạy lại (rerun) trở nên idempotent thay vì bị nhân đôi dữ liệu, và bước transform chính là nơi các quy tắc chất lượng dữ liệu (loại bỏ null, ép kiểu, phân nhóm) được áp dụng trước khi dữ liệu đến warehouse.

Các ngôn ngữ khác: Scala, Java, Go

Python không phải là ngôn ngữ duy nhất một data engineer sẽ gặp, và biết khi nào mỗi ngôn ngữ còn lại có ý nghĩa sẽ tiết kiệm thời gian:

Với hầu hết các vai trò data engineering, SQL và Python đã bao phủ hơn 90% công việc hàng ngày; Scala, Java, và Go trở nên liên quan khi stack cụ thể của team (Spark nặng, Hadoop/Kafka legacy, hoặc công cụ đòi hỏi hiệu năng cao) yêu cầu.

Kiến thức Linux nền tảng cho data engineer

Gần như toàn bộ hạ tầng dữ liệu — database, cụm Spark/Hadoop, scheduler Airflow, broker Kafka, VM và container trên cloud — đều chạy trên Linux. Ngay cả khi laptop hàng ngày của bạn là macOS hay Windows, các server, container, và CI runner thực thi pipeline của bạn đều là Linux, nên sự thoải mái với shell không phải là tùy chọn.

Các công cụ thiết yếu dùng hàng ngày:

Lệnh / khái niệmDùng để làm gì
grepTìm kiếm text nhanh — tìm một chuỗi lỗi trên nhiều file log (grep -r "ERROR" /var/log/pipeline/)
awkXử lý text theo trường (field) — trích xuất/tổng hợp cột nhanh trên dữ liệu CSV hoặc dòng log (awk -F',' '{sum+=$3} END{print sum}' data.csv)
sedStream editing — thay thế và dọn dẹp tại chỗ (sed -i 's/NULL/\\N/g' export.csv)
cronLập lịch job theo thời gian (crontab -e) — cách lập lịch script nguyên thủy, vẫn phổ biến trước/song song với Airflow
systemdQuản lý service (systemctl status, journalctl -u) — chạy một tiến trình dài hạn (một Kafka consumer, một API) như một service được quản lý, tự khởi động lại khi lỗi
Shell scripting (bash)Glue code — nối chuỗi các công cụ CLI, kiểm tra exit code, bọc một job Python với các bước trước/sau

Một ví dụ nhanh kết hợp vài công cụ trên — đếm số dòng lỗi theo từng giờ từ một file log:

grep "ERROR" pipeline.log \
  | awk '{print $1, $2}' \
  | cut -c1-13 \
  | sort \
  | uniq -c \
  | sort -rn

Kiểu one-liner này — ghép nối vài công cụ nhỏ, có thể kết hợp (composable) với nhau — thường nhanh hơn viết một script Python cho việc kiểm tra log hoặc dữ liệu nhanh, ad hoc, và phản xạ này chuyển trực tiếp sang việc debug pipeline chạy trên server từ xa nơi không có sẵn một IDE đầy đủ.

Git và GitHub cho code pipeline

Code pipeline — các phép transform SQL, model dbt, DAG Airflow, job Spark, infrastructure-as-code — là production software và xứng đáng có cùng kỷ luật version control như bất kỳ codebase ứng dụng nào, chứ không phải các file “final_v2.sql” tùy tiện. Git theo dõi mọi thay đổi với lịch sử đầy đủ, cho phép làm việc song song qua branch, và (kết hợp với GitHub/GitLab) cung cấp pull request để review code trước khi bất cứ thứ gì đến được production.

Một vài điểm đặc thù cho repo pipeline dữ liệu:

Để tìm hiểu sâu hơn về cơ chế Git — object model, chiến lược branching, kỷ luật commit, và thực hành code review — xem ../backend/en/04-version-control-and-collaboration.md; mọi nội dung ở đó áp dụng trực tiếp cho một repo pipeline.

Cấu trúc dữ liệu & thuật toán trong data engineering

Các buổi phỏng vấn software engineering nói chung thường nhấn mạnh phân tích Big-O như một mục tiêu tự thân; với data engineer, cùng một trực giác đó lại quan trọng vì một lý do rất thực tế: khối lượng dữ liệu thay đổi cách tính toán. Một vòng lặp lồng nhau O(n^2) chạy tức thời trên 1.000 dòng có thể trở thành một job chạy nhiều giờ (hoặc không thể chạy được) trên 100 triệu dòng, và khác biệt giữa cách tiếp cận O(n)O(n log n) có thể là ranh giới giữa việc pipeline hoàn thành trong khung giờ đã lên lịch hay không hoàn thành được chút nào.

Các cấu trúc thường xuyên xuất hiện:

Bài học thực tế: trước khi tối ưu code, hãy tự hỏi “độ phức tạp Big-O của thao tác này là gì, và nó scale như thế nào với khối lượng dữ liệu thực tế của chúng ta, chứ không phải một test fixture nhỏ?” Một join, một groupby, hay một full-table scan mà một senior data engineer theo bản năng sẽ kiểm tra lại kỹ ở quy mô lớn — chính là kiểu suy luận này.

Cách tiếp cận declarative và imperative

Một chủ đề lặp lại xuyên suốt modern data stack là sự ưu tiên các công cụ declarative hơn imperative ở bất cứ đâu công việc cho phép:

Cách tiếp cậnVí dụBạn mô tả…
DeclarativeSQL, dbt, TerraformTrạng thái đích mong muốn; công cụ tự tìm cách đạt được nó
ImperativeScript Python thô, logic DAG Airflow viết tayChuỗi bước chính xác cần thực thi

SQL khai báo hình dạng của kết quả và để lại chiến lược thực thi cho query planner. dbt khai báo SQL của một model cùng các dependency của nó (qua ref()), và dbt sẽ tự tìm ra thứ tự thực thi (DAG) và, ở mỗi lần chạy, chỉ cần áp dụng phần chênh lệch. Terraform khai báo trạng thái infrastructure mong muốn, và terraform plan/apply tính toán rồi áp dụng phần diff. Đây là lý do các công cụ declarative thường idempotent theo mặc định — chạy lại khi không có gì thay đổi là một no-op — và tại sao chúng dễ suy luận, dễ review, dễ rollback hơn: phần chênh lệch giữa hai trạng thái của file mô tả trạng thái mong muốn chính là thay đổi, không có sự mơ hồ ẩn giấu kiểu “lần chạy trước để lại gì”.

Code imperative (một script Python, hay logic task của một DAG Airflow) vẫn có vị trí thiết yếu — control flow của orchestration, gọi API bên ngoài, phân nhánh điều kiện phức tạp, và bất cứ điều gì một DSL declarative không thể diễn đạt gọn gàng — nhưng nó đẩy trách nhiệm về idempotency và error recovery sang cho kỹ sư. Một pipeline imperative viết tốt sẽ chủ động xây dựng tính idempotent (upsert thay vì insert, kiểm tra điều kiện tiên quyết, các pattern kiểu if_exists="replace" như trong ví dụ trước); một pipeline declarative có được phần lớn điều đó miễn phí. Xu hướng ngành ưu tiên SQL/dbt cho logic transform và Terraform cho infrastructure, trong khi giữ Python/Airflow ở lớp orchestration và glue mà chúng thực sự làm tốt, phản ánh chính sự đánh đổi này.

Khái niệm chính

Lựa chọn ngôn ngữ và công cụ

Bảng dưới đây tóm tắt nên dùng công cụ nào và khi nào, xuyên suốt mọi nội dung ở trên. Xem ./08-etl-elt-and-data-pipelines.md để biết các công cụ này kết hợp thành pipeline hoàn chỉnh ra sao, và ./09-workflow-orchestration.md để biết công cụ orchestration lập lịch và giám sát chúng thế nào.

Ngôn ngữ / công cụTrường hợp sử dụng chính trong data engineeringKhi nào nên dùng
SQLQuery và transform dữ liệu trong warehouse/lake; lớp transform của ELTLuôn luôn — mặc định cho bất kỳ transform nào có thể diễn đạt bằng query
Python (pandas)Exploration, prototyping, transform quy mô nhỏ-vừa trong bộ nhớPhân tích cục bộ, notebook, dữ liệu vừa với bộ nhớ trên một máy
Python (PySpark)Xử lý phân tán dữ liệu quá lớn cho một máyTransform batch quy mô nhiều GB/TB, join qua các dataset khổng lồ
Python (Airflow/Dagster/Prefect)Orchestration — lập lịch, quản lý dependency, giám sát pipelineĐiều phối pipeline nhiều bước với dependency, retry, cảnh báo
dbt (SQL + Jinja)Lớp transform declarative trên nền warehouseModeling và test logic transform với version control và CI
ScalaJob Spark hiệu năng cao, native trên các team nặng về JVMJob nhiều UDF Spark mà chi phí serialization của PySpark gây hại
JavaBảo trì/mở rộng Hadoop, Kafka Connect, tích hợp client KafkaHạ tầng big-data legacy, môi trường chuẩn hóa JVM
GoCông cụ và dịch vụ dữ liệu hiệu năng cao, footprint thấpConnector tùy chỉnh, dịch vụ streaming nhẹ, công cụ CLI
TerraformInfrastructure as code declarative cho tài nguyên nền tảng dữ liệuCấp phát warehouse, cluster, IAM, storage bucket có thể tái tạo được
Bash / Linux CLIGlue code, kiểm tra nhanh, lập lịch, quản lý serviceKiểm tra log/dữ liệu ad hoc, bọc job, lập lịch cron, service systemd
Git / GitHubVersion control và review cho tất cả những gì kể trênLuôn luôn — mọi artifact pipeline thuộc về một repo

Best Practices

  1. Học SQL thật sâu trước mọi thứ khác. Window function, CTE, và query plan mang lại giá trị trong hầu như mọi tác vụ bạn sẽ làm; hãy coi đây là kỹ năng cốt lõi, không phải phụ lục cho “lập trình thật sự”.
  2. Prototype trong pandas, scale lên PySpark, giữ logic dễ nhận ra. Viết code pandas khám phá với tư duy hướng đến việc nó sẽ được chuyển sang Spark như thế nào giúp tránh phải viết lại đau đớn về sau.
  3. Đẩy logic transform vào SQL/dbt declarative khi có thể. Dành Python/Airflow cho orchestration, control flow, và những gì SQL thực sự không diễn đạt được — đừng viết lại một JOIN trong vòng lặp Python.
  4. Chủ động làm cho code imperative idempotent. Upsert thay vì insert, các pattern if_exists="replace"/MERGE, và kiểm tra điều kiện tiên quyết rõ ràng — giả định rằng mọi job sẽ được chạy lại, vì nó sẽ được chạy lại.
  5. Đọc query plan trước khi tối ưu mù quáng. EXPLAIN ANALYZE (hoặc query plan trong Spark UI) cho biết một join đang broadcast, shuffle, hay scan — đoán mò lãng phí thời gian kỹ thuật.
  6. Đối xử với code pipeline như production code. PR, review, lint/test trong CI, và một chiến lược branching thật sự — một model SQL bị hỏng là một sự cố (incident), không phải một lỗi gõ phím.
  7. Chủ động chọn Scala/Java/Go, không phải theo mặc định. Ghép ngôn ngữ với điểm nghẽn thực tế (hiệu năng UDF Spark, hạ tầng Kafka/Hadoop có sẵn, một dịch vụ throughput cao nhẹ) thay vì vì mới lạ.
  8. Giữ nền tảng Linux luôn sắc bén. One-liner grep/awk/sed và sự thoải mái với systemd/cron tiết kiệm thời gian debug một pipeline từ xa nhiều hơn hẳn so với việc dùng một IDE đầy đủ.
  9. Tư duy Big-O ở đúng quy mô dữ liệu thực tế. Test fixture 1.000 dòng sẽ không bộc lộ một join O(n^2) sụp đổ ở 100 triệu dòng — hãy suy luận về tốc độ tăng trưởng, không chỉ tính đúng đắn.

Tài liệu tham khảo

Part of the Data Engineer Roadmap knowledge base.

Overview

Data engineering is, at its core, a software engineering discipline applied to the movement and shape of data. Before pipelines, warehouses, or orchestration tools enter the picture, a data engineer needs a small, well-chosen set of programming skills that show up in almost every task: writing SQL to query and transform data at scale, writing Python to glue systems together and process data outside the database, using Linux comfortably because that is where almost all data infrastructure runs, and using Git because pipeline code is still code and deserves the same rigor as any other production software.

This note is deliberately foundational — it is the layer everything else in the roadmap (modeling, warehousing, ETL/ELT, orchestration, streaming) is built on. Get SQL and Python fluency, comfort with a Linux shell, and disciplined version control in place first, and the rest of the roadmap becomes about applying those skills to progressively larger and more specialized problems rather than learning to program from scratch. We will also look at where other languages (Scala, Java, Go) matter, why data volume changes how you reason about algorithmic complexity, and why the industry increasingly prefers declarative tools over imperative scripts for describing pipelines.

Fundamentals

SQL is the non-negotiable core skill

If a data engineer can only be fluent in one language, it must be SQL. Every relational and most analytical data stores — Postgres, MySQL, Snowflake, BigQuery, Redshift, Databricks SQL, DuckDB — speak SQL, and even “NoSQL”-adjacent tools (Spark SQL, Trino, Presto, Athena) converge on it as the query interface for analytics. SQL is declarative: you describe what result you want, and the query planner decides how to get it, which is exactly the abstraction level most data transformation should live at.

A production-grade SQL skill set goes well beyond SELECT ... WHERE:

Worked example: CTE + window function

Consider a table of daily order totals per store and the need to compute, per store, a running total of revenue and a rank of each day by revenue:

WITH daily_revenue AS (
    SELECT
        store_id,
        order_date,
        SUM(order_amount) AS revenue
    FROM orders
    WHERE order_date >= DATE '2026-01-01'
    GROUP BY store_id, order_date
)
SELECT
    store_id,
    order_date,
    revenue,
    SUM(revenue) OVER (
        PARTITION BY store_id
        ORDER BY order_date
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS running_total_revenue,
    RANK() OVER (
        PARTITION BY store_id
        ORDER BY revenue DESC
    ) AS revenue_rank_in_store
FROM daily_revenue
ORDER BY store_id, order_date;

The CTE (daily_revenue) does the aggregation once and gives it a readable name; the outer query then applies two independent window functions over it. SUM(...) OVER (PARTITION BY store_id ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) produces a running total that resets per store, and RANK() OVER (PARTITION BY store_id ORDER BY revenue DESC) ranks each day’s revenue within its store — all without collapsing the daily grain via GROUP BY. This pattern (CTE to shape the base rows, window functions to add running/ranking context) is one of the most common building blocks in analytics SQL and dbt models alike.

Python for data engineering

Python dominates data engineering for a simple reason: it is the common language across every layer of the modern data stack. Exploration and prototyping happen in pandas on a laptop; the same logic, once it outgrows a single machine, is rewritten (often almost 1:1) in PySpark for distributed processing; and the orchestration layer that schedules and monitors all of it — Apache Airflow, Dagster, Prefect — is itself Python-native, meaning pipelines are literally defined as Python code (or Python-decorated functions), not a proprietary DSL. This continuity — the same language from exploratory notebook to production DAG — is what makes Python the connective tissue of the field, more than any single library.

Key libraries a data engineer reaches for constantly:

LibraryPurpose
pandasIn-memory tabular data manipulation — exploration, small-to-medium transforms, quick joins/aggregations
pyarrowColumnar in-memory format (Apache Arrow) and fast Parquet/CSV I/O; the backbone under pandas 2.x, Spark interop, and DuckDB
requestsHTTP calls to REST APIs — a huge share of “extract” logic in ETL is requests.get() against some vendor API
sqlalchemyDatabase-agnostic SQL toolkit and ORM — connecting to and writing into relational databases from Python code
pysparkPython API for Apache Spark — distributed processing when data no longer fits on one machine

Worked example: CSV → transform → database

A small, representative “extract, transform, load” script — read a CSV, clean and reshape it with pandas, and write it into a Postgres table with SQLAlchemy:

import pandas as pd
from sqlalchemy import create_engine

# Extract: read the raw CSV
df = pd.read_csv("daily_orders.csv", parse_dates=["order_date"])

# Transform: clean, cast, and derive columns
df = df.dropna(subset=["store_id", "order_amount"])
df["order_amount"] = df["order_amount"].astype("float64")
df["order_date"] = df["order_date"].dt.date
df["revenue_bucket"] = pd.cut(
    df["order_amount"],
    bins=[0, 50, 200, float("inf")],
    labels=["low", "medium", "high"],
)

# Load: write to a Postgres table, replacing existing data for idempotency
engine = create_engine("postgresql+psycopg2://etl_user:secret@localhost:5432/warehouse")
df.to_sql(
    "stg_orders",
    con=engine,
    schema="staging",
    if_exists="replace",
    index=False,
    chunksize=10_000,
    method="multi",
)

Even this small script illustrates real-world concerns: chunksize and method="multi" batch inserts instead of one row at a time (critical once you have more than a few thousand rows), if_exists="replace" makes a rerun idempotent rather than duplicating data, and the transform step is where data quality rules (dropping nulls, type casting, bucketing) live before data ever reaches the warehouse.

Other languages: Scala, Java, Go

Python is not the only language a data engineer will encounter, and knowing when each of the others matters saves time:

For most data engineering roles, SQL and Python cover 90%+ of daily work; Scala, Java, and Go become relevant as the team’s specific stack (heavy Spark, legacy Hadoop/Kafka, or performance-critical tooling) demands them.

Linux basics for data engineers

Nearly all data infrastructure — databases, Spark/Hadoop clusters, Airflow schedulers, Kafka brokers, cloud VMs and containers — runs on Linux. Even when your day-to-day laptop is macOS or Windows, the servers, containers, and CI runners executing your pipelines are Linux, so comfort with the shell is not optional.

Essential day-to-day tools:

Command / conceptWhat it’s for
grepFast text search — finding an error string across log files (grep -r "ERROR" /var/log/pipeline/)
awkField-based text processing — quick column extraction/aggregation on CSV or log lines (awk -F',' '{sum+=$3} END{print sum}' data.csv)
sedStream editing — in-place substitutions and cleanup (sed -i 's/NULL/\\N/g' export.csv)
cronTime-based job scheduling (crontab -e) — the original, still-common way to schedule scripts before/alongside Airflow
systemdService management (systemctl status, journalctl -u) — running a long-lived process (a Kafka consumer, an API) as a managed, restart-on-failure service
Shell scripting (bash)Glue code — chaining CLI tools, checking exit codes, wrapping a Python job with pre/post steps

A quick example combining several of these — count error lines per hour from a log file:

grep "ERROR" pipeline.log \
  | awk '{print $1, $2}' \
  | cut -c1-13 \
  | sort \
  | uniq -c \
  | sort -rn

This kind of one-liner — pipe a handful of small, composable tools together — is often faster than writing a Python script for a quick, ad-hoc log or data inspection, and the muscle memory transfers directly to debugging pipelines running on remote servers where a full IDE isn’t available.

Git and GitHub for pipeline code

Pipeline code — SQL transformations, dbt models, Airflow DAGs, Spark jobs, infrastructure-as-code — is production software and deserves the same version control discipline as any application codebase, not ad hoc “final_v2.sql” files. Git tracks every change with full history, enables parallel work via branches, and (paired with GitHub/GitLab) provides pull requests for code review before anything reaches production.

A few things specific to data pipeline repos:

For the underlying Git mechanics — the object model, branching strategies, commit hygiene, and code review practices in depth — see ../backend/en/04-version-control-and-collaboration.md; everything there applies directly to a pipeline repo.

Data structures & algorithms for data engineering

General software engineering interviews emphasize Big-O analysis for its own sake; for a data engineer, the same intuition matters for a very practical reason: data volume changes the calculus. An O(n^2) nested loop that is instantaneous on 1,000 rows becomes a multi-hour (or unrunnable) job on 100 million rows, and the difference between an O(n) and an O(n log n) approach can be the difference between a pipeline finishing in a scheduled window or not finishing at all.

Structures that come up constantly:

The practical takeaway: before optimizing code, ask “what is the Big-O of this operation, and how does that scale with our actual data volume, not a small test fixture?” A join, a groupby, or a full-table scan that a senior data engineer instinctively double-checks at scale is exactly this kind of reasoning.

Declarative vs. imperative approaches

A recurring theme across the modern data stack is a preference for declarative tools over imperative ones wherever the task allows it:

ApproachExamplesYou describe…
DeclarativeSQL, dbt, TerraformThe desired end state; the tool figures out how to get there
ImperativeRaw Python scripts, hand-rolled Airflow DAG logicThe exact sequence of steps to execute

SQL declares the shape of the result and leaves execution strategy to the query planner. dbt declares a model’s SQL and its dependencies (via ref()), and dbt figures out execution order (the DAG) and, on each run, only needs to apply the difference. Terraform declares the desired infrastructure state, and terraform plan/apply compute and apply the diff. This is why declarative tools tend to be idempotent by default — running them again when nothing changed is a no-op — and why they are easier to reason about, review, and roll back: the diff between two states of the desired-state file is the change, with no hidden “what did the last run leave behind” ambiguity.

Imperative code (a Python script, or an Airflow DAG’s task logic) still has an essential place — orchestration control flow, calling external APIs, complex conditional branching, and anything a declarative DSL can’t express cleanly — but it pushes the responsibility for idempotency and error recovery onto the engineer. A well-written imperative pipeline explicitly builds in idempotency (upserts instead of inserts, checked preconditions, if_exists="replace"-style patterns as in the earlier example); a declarative one gets much of that for free. The industry trend of preferring SQL/dbt for transformation logic and Terraform for infrastructure, while keeping Python/Airflow to the orchestration and glue layer they’re actually good at, reflects this trade-off.

Key Concepts

Language and tool selection

The following table summarizes what to reach for and when, across everything covered above. See ./08-etl-elt-and-data-pipelines.md for how these tools compose into full pipelines, and ./09-workflow-orchestration.md for how orchestration tooling schedules and monitors them.

Language / toolPrimary use case in data engineeringWhen to reach for it
SQLQuerying and transforming data in warehouses/lakes; the transformation layer of ELTAlways — the default for any transform expressible as a query
Python (pandas)Exploration, prototyping, small-to-medium in-memory transformsLocal analysis, notebooks, data that fits in memory on one machine
Python (PySpark)Distributed processing of data too large for one machineMulti-GB/TB-scale batch transforms, joins across huge datasets
Python (Airflow/Dagster/Prefect)Orchestration — scheduling, dependency management, monitoring of pipelinesCoordinating multi-step pipelines with dependencies, retries, alerting
dbt (SQL + Jinja)Declarative transformation layer on top of a warehouseModeling and testing transformation logic with version control and CI
ScalaNative, high-performance Spark jobs on JVM-heavy teamsSpark UDF-heavy jobs where PySpark serialization overhead hurts
JavaMaintaining/extending Hadoop, Kafka Connect, Kafka client integrationsLegacy big-data infrastructure, JVM-standardized shops
GoHigh-performance, low-footprint data tooling and servicesCustom connectors, lightweight streaming services, CLI tools
TerraformDeclarative infrastructure as code for data platform resourcesProvisioning warehouses, clusters, IAM, storage buckets reproducibly
Bash / Linux CLIGlue code, quick inspection, scheduling, service managementAd hoc log/data checks, wrapping jobs, cron scheduling, systemd services
Git / GitHubVersion control and review for all of the aboveAlways — every pipeline artifact belongs in a repo

Best Practices

  1. Learn SQL deeply before anything else. Window functions, CTEs, and query plans pay off on essentially every task you’ll do; treat them as core skill, not an afterthought to “real” programming.
  2. Prototype in pandas, scale in PySpark, keep the logic recognizable. Writing exploratory pandas code with an eye toward how it would translate to Spark avoids a painful rewrite later.
  3. Push transformation logic into declarative SQL/dbt where possible. Reserve Python/Airflow for orchestration, control flow, and things SQL genuinely can’t express — don’t reimplement a JOIN in a Python loop.
  4. Make imperative code idempotent on purpose. Upserts over inserts, if_exists="replace"/MERGE patterns, and explicit precondition checks — assume every job will be rerun, because it will.
  5. Read query plans before optimizing blindly. EXPLAIN ANALYZE (or the Spark UI’s query plan) tells you whether a join is broadcasting, shuffling, or scanning — guessing wastes engineering time.
  6. Treat pipeline code like production code. PRs, review, CI linting/tests, and a real branching strategy — a broken SQL model is an incident, not a typo.
  7. Reach for Scala/Java/Go deliberately, not by default. Match the language to the actual bottleneck (Spark UDF performance, existing Kafka/Hadoop infra, a lightweight high-throughput service) rather than novelty.
  8. Keep Linux fundamentals sharp. grep/awk/sed one-liners and comfort with systemd/cron save far more time debugging a remote pipeline than reaching for a full IDE.
  9. Think in Big-O at your actual data scale. Test fixtures of 1,000 rows will not reveal an O(n^2) join that falls over at 100 million rows — reason about growth, not just correctness.

References