Nguồn dữ liệu & IngestionData Sources & Ingestion
Thuộc bộ kiến thức Data Engineer Roadmap.
Tổng quan
Mọi data pipeline đều bắt đầu từ một nơi nằm ngoài tầm kiểm soát trực tiếp của bạn: một application database do team product sở hữu, một API của SaaS bên thứ ba, một stream các event clickstream, một thư mục chứa file CSV mà ai đó thả vào object storage, hoặc một cảm biến gửi số liệu mỗi giây. Data ingestion là công việc di chuyển dữ liệu từ những nguồn này vào hệ thống mà bạn có thể lưu trữ, biến đổi và phân tích — data warehouse, data lake, hoặc một streaming platform.
Ingestion nằm ở đầu ngoài cùng của data stack, nghĩa là các lỗi ở đây tốn kém nhất để debug: một transformation lỗi thì thường lộ ra ngay qua một dbt test, nhưng một batch event bị API mất kết nối và âm thầm bỏ sót có thể không ai để ý suốt nhiều tuần, âm thầm làm sai lệch mọi báo cáo downstream. Bài viết này sẽ đi qua dữ liệu đến từ đâu, những mối quan tâm thực tế khi thu thập dữ liệu một cách có trách nhiệm và đáng tin cậy, cơ chế của hai mô hình ingestion phổ biến nhất — batch và streaming — cùng kỹ thuật nối giữa replication ở tầng storage và ingestion: Change Data Capture (CDC).
Kiến thức nền tảng
Các nhóm nguồn dữ liệu
Data engineer thường lấy dữ liệu từ một số nhóm nguồn lặp đi lặp lại, mỗi nhóm có hình dạng, tần suất và kiểu lỗi khác nhau.
| Loại nguồn | Ví dụ điển hình | Hình dạng dữ liệu | Kiểu cập nhật |
|---|---|---|---|
| Application database (OLTP) | PostgreSQL/MySQL phục vụ web app | Dòng dữ liệu chuẩn hóa cao | Insert/update/delete liên tục |
| API / công cụ SaaS bên thứ ba | Stripe, Salesforce, HubSpot, Google Analytics | JSON qua REST/GraphQL | Kéo theo yêu cầu, bị giới hạn rate |
| File thả vào storage | CSV/Parquet/JSON trong S3, SFTP, GCS | File phẳng, dump theo lô | Thả file định kỳ (hàng giờ/ngày) |
| Event stream / log | Kafka topic, application log, clickstream | Event JSON/Avro bán cấu trúc | Liên tục, khối lượng lớn |
| Thiết bị IoT / cảm biến | Cảm biến nhiệt độ, thiết bị GPS, máy móc công nghiệp | Số liệu nhỏ, tần suất cao | Liên tục, thường ở edge |
| Bộ dữ liệu công khai / chính phủ | Tổng cục thống kê, data.gov, WHO, cơ quan khí tượng | CSV/JSON/API, đôi khi tải hàng loạt | Phát hành thưa (hàng tháng/năm) |
| Webhook | Callback từ payment provider, event GitHub, event Slack | Thông báo JSON được push đến | Theo sự kiện, được đẩy đến bạn |
Application database (hệ thống OLTP). Đây là các database đứng sau bản thân sản phẩm — các bảng ghi lại đơn hàng, người dùng, giao dịch khi chúng xảy ra. Chúng được tối ưu cho các transaction đọc/ghi nhỏ và nhanh, không phải cho các phép quét lớn mà phân tích dữ liệu đòi hỏi, đó là lý do data engineer trích xuất (extract) từ chúng thay vì query trực tiếp phục vụ báo cáo.
API và công cụ SaaS bên thứ ba. Dữ liệu marketing, sales, support, thanh toán thường nằm ngoài hạ tầng của chính bạn, trong các công cụ như Salesforce, Stripe, Zendesk. Con đường duy nhất vào là API của nhà cung cấp, đi kèm schema riêng, cơ chế phân trang (pagination) riêng và rate limit riêng (nói kỹ hơn bên dưới).
File thả vào storage. Một pattern phổ biến, ít công nghệ nhưng rất thực tế: một đối tác hoặc team nội bộ export một file CSV/Parquet/JSON và đặt vào bucket S3, server SFTP, hoặc một ổ đĩa chia sẻ theo lịch. Đơn giản để tạo ra, nhưng dễ vỡ — định dạng file, dấu phân cách, encoding có thể trôi dạt âm thầm nếu không ai sở hữu một schema contract rõ ràng.
Event stream và log. Application log, event clickstream, và message queue (Kafka, Kinesis, Pub/Sub) tạo ra dòng chảy liên tục các bản ghi nhỏ, bán cấu trúc. Đây là nguồn tự nhiên cho streaming ingestion.
Thiết bị IoT và cảm biến. Các thiết bị vật lý — cảm biến công nghiệp, thiết bị GPS, đồng hồ đo thông minh — phát ra số liệu nhỏ với tần suất cao, thường qua mạng hạn chế (MQTT là giao thức phổ biến ở đây), và thường cần tổng hợp ở edge trước khi đến được cloud.
Bộ dữ liệu công khai và chính phủ. Tổng cục thống kê, cổng dữ liệu mở, cơ quan quản lý công bố các bộ dữ liệu có cấu trúc, thường dưới dạng tải hàng loạt hoặc API khiêm tốn, cập nhật theo nhịp chậm (hàng tháng, hàng năm). Hữu ích để làm giàu dữ liệu (ví dụ join mã bưu điện với dữ liệu nhân khẩu học) nhưng hiếm khi là nguồn nghiệp vụ chính.
Webhook. Thay vì bạn poll API của nhà cung cấp, nhà cung cấp chủ động push một event đến endpoint HTTP mà bạn expose ngay khi điều gì đó xảy ra (thanh toán thành công, PR được merge). Mô hình này đảo ngược mô hình pull thông thường và đòi hỏi service ingestion của bạn phải luôn sẵn sàng nhận push một cách đáng tin cậy — xem ./11-streaming-and-realtime-data.md để hiểu cách xử lý kiểu ingress theo sự kiện này.
Hiểu cách dữ liệu được sinh ra trước khi xây dựng pipeline
Một pipeline chỉ đáng tin cậy bằng chính hệ thống sinh ra dữ liệu mà nó đọc. Trước khi viết một dòng code extract nào, việc trả lời ba câu hỏi về hệ thống upstream sẽ rất đáng giá:
- Schema có ổn định không — Team sở hữu nguồn có version hóa API hoặc migration database của họ không? Một cột mới có xuất hiện âm thầm, hay bạn sẽ được báo trước? Một bảng OLTP thuộc một service đang phát triển tích cực thay đổi thường xuyên hơn nhiều so với một bộ dữ liệu chính phủ ít biến động.
- Volume và pattern lưu lượng — Dữ liệu được sinh ra đều đặn nhỏ giọt, hay theo đợt trong ngày (ví dụ một batch job ban đêm ngay trong hệ thống nguồn)? Một ingestion giả định tốc độ ổn định sẽ bất ngờ trước một đợt tăng đột biến 100 lần vào lúc nửa đêm.
- Ai sở hữu (ownership) — Bạn thực sự có thể trao đổi với ai khi có sự cố? Một database OLTP nội bộ có team engineering bạn có thể page; một bộ dữ liệu công khai thì cùng lắm chỉ có một mailing list. Biết ranh giới ownership từ đầu quyết định bạn cần code ingestion phòng thủ đến mức nào (ví dụ validate schema chặt chẽ và cảnh báo khi nguồn không có chủ sở hữu rõ ràng hoặc là nguồn bên ngoài).
Coi nguồn dữ liệu như một hộp đen “cứ cung cấp dữ liệu” là nguyên nhân gốc phổ biến nhất khiến pipeline dễ vỡ. Vài phút trao đổi với team sở hữu hệ thống upstream — hoặc đọc changelog của họ — tiết kiệm nhiều ngày chữa cháy về sau.
Những cân nhắc khi thu thập dữ liệu
Ngoài hình dạng kỹ thuật của dữ liệu, việc thu thập dữ liệu một cách có trách nhiệm còn liên quan đến vài yếu tố thực tế không thể bỏ qua:
- Quyền sở hữu dữ liệu và thỏa thuận truy cập. Trước khi ingest từ một hệ thống bên thứ ba, cần xác nhận ai sở hữu dữ liệu, bạn được phép làm gì với nó theo hợp đồng, và được giữ nó trong bao lâu. Điều này đặc biệt quan trọng với dữ liệu chia sẻ bởi đối tác hoặc khách hàng, nơi một data processing agreement (DPA) có thể quy định rõ ranh giới.
- Rate limit trên API. Mọi API của nhà cung cấp đều áp một giới hạn nào đó lên khối lượng request (request/giây hoặc/ngày). Ingestion job phải tôn trọng các giới hạn này — vượt quá sẽ khiến API key của bạn bị throttle hoặc cấm, và việc xây dựng cơ chế backoff tự động là điều bắt buộc phải có (xem phần API ingestion bên dưới).
- Privacy và consent tại thời điểm thu thập. Nếu dữ liệu chứa thông tin cá nhân, bạn cần biết người dùng cuối có đồng ý (consent) cho việc dữ liệu đó được thu thập và sử dụng theo cách pipeline của bạn định làm hay không. Bài viết này chỉ nêu vấn đề; phần xử lý sâu hơn — các quy định như GDPR/CCPA, xử lý PII, ẩn danh hóa — nằm ở
./15-data-security-and-compliance.md.
Khái niệm chính
Batch, streaming và micro-batch ingestion
Quyết định thiết kế lớn đầu tiên cho bất kỳ ingestion pipeline nào là khi nào dữ liệu di chuyển: tất cả cùng lúc theo lịch, liên tục ngay khi được sinh ra, hay ở đâu đó giữa hai thái cực này.
Batch ingestion kéo một khối dữ liệu theo lịch — hàng giờ, hàng đêm, hàng tuần. Đây là mô hình đơn giản nhất để xây dựng và suy luận: một job chạy, extract những gì thay đổi (hoặc toàn bộ), load, rồi kết thúc. Các công cụ như Airflow điều phối kiểu pipeline này một cách tự nhiên. Đánh đổi là độ trễ (latency): dữ liệu chỉ mới bằng lần chạy gần nhất, nên một batch chạy đêm nghĩa là dữ liệu của hôm qua mới có sẵn hôm nay.
Streaming (real-time) ingestion xử lý từng bản ghi (hoặc micro-record) ngay khi nó đến, thường qua một message broker như Kafka, Kinesis, Pub/Sub, đưa vào một stream processor (Flink, Spark Structured Streaming, ksqlDB). Latency giảm xuống còn vài giây hoặc ít hơn, nhưng độ phức tạp vận hành tăng mạnh: bạn phải xử lý event đến không đúng thứ tự, exactly-once so với at-least-once delivery, backpressure, và stateful processing. Nội dung đầy đủ ở ./11-streaming-and-realtime-data.md.
Micro-batching là điểm cân bằng thực dụng ở giữa: thay vì xử lý từng event một hoặc chờ trọn một ngày, bạn ingest các batch nhỏ theo một chu kỳ ngắn — ví dụ mỗi một đến năm phút. Cách này nắm bắt phần lớn lợi ích về độ tươi mới của streaming trong khi vẫn giữ được phần lớn sự đơn giản vận hành của batch, và đây chính là điều mà trigger mặc định của Spark Structured Streaming, hay một Airflow DAG chạy theo lịch dày, thực hiện.
| Loại ingestion | Latency | Độ phức tạp | Cơ chế trigger điển hình |
|---|---|---|---|
| Batch | Phút đến giờ (hoặc ngày) | Thấp | Cron/scheduler (ví dụ 2 giờ sáng mỗi đêm) |
| Micro-batch | Giây đến phút | Trung bình | Chu kỳ cố định ngắn (ví dụ mỗi 1–5 phút) |
| Streaming | Mili giây đến giây | Cao | Liên tục, theo sự kiện |
Lựa chọn giữa các mô hình này là đánh đổi giữa mức độ “tươi” mà nghiệp vụ thực sự cần và mức độ phức tạp vận hành mà team có thể gánh vác. Đa số use case phân tích (dashboard hàng ngày, báo cáo hàng tuần) được phục vụ tốt bởi batch hoặc micro-batch; phát hiện gian lận, gợi ý real-time, cảnh báo vận hành mới đáng để trả giá cho streaming thực sự.
Change Data Capture (CDC)
Extract lại toàn bộ bảng nguồn mỗi lần chạy — gọi là full extraction — thì đơn giản nhưng lãng phí và chậm một khi bảng phát triển lên vài triệu dòng. Change Data Capture (CDC) giải quyết vấn đề này bằng cách chỉ capture các thay đổi ở cấp độ dòng (insert, update, delete) đã xảy ra kể từ lần extract trước, thay vì kéo lại toàn bộ bảng.
Có hai cách phổ biến để triển khai CDC:
Log-based CDC đọc transaction log nội bộ của database — cơ chế mà database đã dùng sẵn cho replication và crash recovery của chính nó — và biến mỗi thay đổi trong log thành một event. Debezium là công cụ mã nguồn mở chuẩn cho việc này: nó đọc binlog của MySQL hoặc write-ahead log (WAL) của PostgreSQL và phát ra một event Kafka cho mỗi insert, update, delete. Vì tận dụng một log mà database vốn đã duy trì sẵn, log-based CDC:
- Tạo tải bổ sung tối thiểu lên database nguồn (không cần các query polling lặp lại).
- Bắt được cả delete, điều mà cách tiếp cận query-based về bản chất không thể thấy được (một dòng bị xóa không để lại dấu vết nào để poll).
- Giữ được thứ tự và capture mọi thay đổi trung gian, không chỉ trạng thái mới nhất.
Query-based CDC thay vào đó poll bảng nguồn định kỳ, so sánh một cột timestamp updated_at (hoặc một ID tăng dần) để tìm các dòng đã thay đổi kể từ lần poll trước. Cách này dễ thiết lập hơn — không cần truy cập vào nội bộ database — nhưng có những hạn chế thực sự: không thể phát hiện hard delete (một dòng biến mất thì không xuất hiện trong query WHERE updated_at > last_poll), tạo thêm tải đọc lặp lại lên database nguồn, và bỏ sót bất kỳ dòng nào thay đổi rồi đổi lại giữa hai lần poll.
| Log-based CDC | Query-based CDC | |
|---|---|---|
| Tải lên nguồn | Thấp (đọc log, không đọc bảng) | Cao hơn (query polling lặp lại) |
| Bắt được delete | Có | Không (trừ khi dùng cờ soft-delete) |
| Độ phức tạp thiết lập | Cao hơn (cần quyền truy cập log, ví dụ bật binlog/WAL) | Thấp hơn (chỉ cần quyền truy cập SQL) |
| Đảm bảo thứ tự | Mạnh (log có thứ tự) | Yếu (phụ thuộc thời điểm poll) |
| Công cụ điển hình | Debezium, AWS DMS, Fivetran | Job polling SQL tự viết |
Với các hệ thống production có khối lượng ghi đáng kể, log-based CDC là cách tiếp cận được ưu tiên chính vì nó bảo vệ database nguồn khỏi tải bổ sung và cho một bức tranh đầy đủ, có thứ tự về mọi thay đổi — kể cả những delete mà query-based polling sẽ âm thầm bỏ sót.
Cơ chế Extract & Load
Full extraction so với incremental extraction. Full extraction kéo toàn bộ dataset mỗi lần chạy; đơn giản và tự phục hồi (mọi lỗi trong quá khứ bị xóa sạch ở lần chạy tiếp theo) nhưng không scale tốt khi bảng lớn dần. Incremental extraction chỉ kéo các dòng mới hoặc thay đổi kể từ lần chạy trước — dùng một cột timestamp, một ID tự tăng, hoặc CDC — và là lựa chọn mặc định một khi nguồn đủ lớn khiến việc reload toàn bộ quá chậm hoặc quá tốn kém.
Idempotent load. Một nguyên tắc reliability cốt lõi: một ingestion job phải an toàn khi được chạy lại. Nếu job lỗi giữa chừng, hoặc vô tình bị trigger hai lần, chạy lại nó không được tạo ra dòng trùng lặp hay đếm trùng metric. Các kỹ thuật phổ biến gồm upsert theo một khóa tự nhiên hoặc khóa surrogate (INSERT ... ON CONFLICT DO UPDATE / MERGE) thay vì append mù quáng, ghi vào một staging table rồi hoán đổi nó vào một cách atomic, và loại bỏ trùng lặp (deduplicate) theo unique event ID trước khi ghi cuối cùng. Idempotency là điều cho phép bạn retry ingestion job đã lỗi mà không sợ hãi — một tính chất dễ bỏ qua lúc ban đầu nhưng tốn kém để bổ sung sau này.
Watermarking và checkpointing. Để biết cần extract tiếp theo cái gì, một pipeline incremental cần theo dõi nó đã đi đến đâu — một watermark (ví dụ: “updated_at được extract lần cuối = 2026-07-18 23:00:00”) hoặc một checkpoint (ví dụ: offset của một Kafka consumer). Trạng thái này phải được lưu bền vững (trong một control table, một metadata store, hoặc cơ chế track offset riêng của streaming platform) và chỉ được cập nhật sau khi một batch được xác nhận đã ghi thành công — nếu không, một crash xảy ra giữa lúc extract và lúc cập nhật checkpoint có thể âm thầm bỏ sót hoặc ghi trùng dữ liệu.
API như một nguồn dữ liệu
Ingest từ một REST API bên thứ ba kéo theo một loạt mối quan tâm thực tế không xuất hiện khi đọc trực tiếp từ database:
- Pagination. Đa số API giới hạn số bản ghi mỗi response và yêu cầu các request tiếp theo dùng offset, số trang, hoặc cursor token. Pagination dựa trên cursor bền vững hơn trước việc dữ liệu thay đổi giữa lúc extract (pagination dựa trên offset có thể bỏ sót hoặc lặp lại dòng nếu bản ghi bị insert/xóa giữa các trang).
- Rate limiting và backoff. API trả về HTTP 429 (Too Many Requests) hoặc tương tự khi bạn vượt quá khối lượng gọi được phép. Một ingestion job production cần exponential backoff kèm jitter, và nên tôn trọng bất kỳ header
Retry-Afternào API cung cấp, thay vì liên tục dội vào endpoint theo một lịch retry cố định. - Authentication. Các API đơn giản dùng một API key tĩnh gửi qua header hoặc query parameter; các API nhạy cảm hơn dùng OAuth 2.0, đòi hỏi quản lý việc làm mới token (access token thường hết hạn sau vài phút đến vài giờ) như một phần của ingestion job, chứ không phải một bước setup một lần.
Xử lý tốt ba yếu tố này chính là phần lớn sự khác biệt giữa một script “curl trong cron job” mong manh và một ingestion pipeline đáng tin cậy chống lại một API bên thứ ba.
So sánh các pattern ingestion
Kết hợp cơ chế extract với mô hình thời gian cho ra bốn pattern thực tế:
| Pattern | Latency | Độ phức tạp | Phù hợp nhất với |
|---|---|---|---|
| Full batch | Giờ đến ngày | Thấp | Bảng nhỏ/ít thay đổi, backfill lần đầu |
| Incremental batch | Phút đến giờ | Trung bình | Bảng lớn có cột updated_at/ID đáng tin cậy |
| CDC | Giây đến phút | Trung bình–cao | Bảng OLTP lớn cần replication gần real-time, kể cả delete |
| Streaming | Mili giây đến giây | Cao | Use case theo sự kiện: phát hiện gian lận, dashboard trực tiếp, cảnh báo |
Trong thực tế, đa số data platform dùng kết hợp: CDC hoặc incremental batch cho replication OLTP vào warehouse, batch pull cho dữ liệu API bên thứ ba ít thay đổi nhanh, và streaming dành riêng cho nhóm use case thực sự cần latency dưới một phút. Sau khi dữ liệu đã vào hệ thống, nó chảy tiếp vào transformation — xem ./08-etl-elt-and-data-pipelines.md — và cần được kiểm tra tính đúng đắn cũng như theo dõi lineage, nội dung ở ./14-data-quality-governance-and-metadata.md.
Best Practices
- Trao đổi với chủ sở hữu nguồn trước khi xây dựng. Hiểu độ ổn định của schema, pattern volume, và biết liên hệ ai trước khi viết code extract — đa số sự cố ingestion bắt nguồn từ một thay đổi upstream không được ghi lại mà không ai báo trước.
- Ưu tiên log-based CDC hơn query polling cho các nguồn OLTP production. Nó tác động ít hơn lên nguồn và là cách duy nhất đáng tin cậy để bắt được delete.
- Làm cho mọi load đều idempotent. Thiết kế upsert theo một khóa ổn định, hoặc dùng hoán đổi staging table atomic, để việc chạy lại một job lỗi luôn an toàn.
- Theo dõi tiến độ bằng watermark/checkpoint bền vững, chỉ cập nhật sau khi ghi thành công. Không bao giờ cập nhật watermark trước khi chắc chắn dữ liệu đã vào đích.
- Validate schema ngay tại ranh giới ingestion. Bắt được thay đổi phá vỡ (breaking change) của nguồn (đổi tên cột, đổi type) bằng một schema check ngay lúc ingest, không phải ba tầng transformation phía sau.
- Xử lý rate limit của API bằng exponential backoff và tôn trọng
Retry-After, thay vì retry theo chu kỳ cố định có thể làm tình trạng throttle tệ hơn. - Chọn pattern ingestion theo yêu cầu độ trễ thực tế của nghiệp vụ, không theo trào lưu. Batch và micro-batch đơn giản hơn để vận hành và là lựa chọn đúng cho phần lớn khối lượng công việc phân tích; dành streaming cho các use case thực sự cần nó.
- Log và cảnh báo các bất thường về volume ingestion. Một cú sụt (hoặc tăng) đột ngột về số dòng từ một nguồn thường là dấu hiệu đầu tiên của một thay đổi schema upstream hoặc một sự cố — bắt được nó ngay tại ingestion, không phải khi nhìn thấy trên dashboard.
Tài liệu tham khảo
Part of the Data Engineer Roadmap knowledge base.
Overview
Every data pipeline starts somewhere outside of your control: an application database that a product team owns, a third-party SaaS API, a stream of clickstream events, a folder of CSV files someone drops into object storage, or a sensor sending readings every second. Data ingestion is the discipline of moving data from these sources into a system where you can store, transform, and analyze it — a data warehouse, a data lake, or a streaming platform.
Ingestion sits at the very front of the data stack, which means its failures are the most expensive to debug: a broken transformation is visible in a dbt test, but a silently dropped batch of events from a flaky API can go unnoticed for weeks and quietly corrupt every downstream report. This note covers where data comes from, the practical concerns of collecting it responsibly and reliably, and the mechanics of the two dominant ingestion patterns — batch and streaming — plus the technique that bridges storage-level replication with ingestion: Change Data Capture (CDC).
Fundamentals
Categories of data sources
Data engineers typically pull from a handful of recurring source types, each with a different shape, cadence, and failure mode.
| Source type | Typical example | Data shape | Update pattern |
|---|---|---|---|
| Application databases (OLTP) | PostgreSQL/MySQL backing a web app | Highly normalized rows | Continuous inserts/updates/deletes |
| Third-party APIs / SaaS tools | Stripe, Salesforce, HubSpot, Google Analytics | JSON over REST/GraphQL | Pull on demand, rate-limited |
| Files dropped in storage | CSV/Parquet/JSON in S3, SFTP, GCS | Flat files, batch dumps | Periodic drops (hourly/daily) |
| Event streams / logs | Kafka topics, application logs, clickstream | Semi-structured JSON/Avro events | Continuous, high volume |
| IoT devices / sensors | Temperature sensors, GPS trackers, industrial equipment | Small, frequent readings | Continuous, often at the edge |
| Public / government datasets | Census bureau, data.gov, WHO, weather agencies | CSV/JSON/APIs, sometimes bulk downloads | Infrequent (monthly/yearly) releases |
| Webhooks | Payment provider callbacks, GitHub events, Slack events | JSON push notifications | Event-driven, pushed to you |
Application databases (OLTP systems). These are the databases behind the product itself — the tables that record orders, users, and transactions as they happen. They are optimized for fast, small read/write transactions, not for the large scans that analytics requires, which is why data engineers extract from them rather than query them directly for reporting.
Third-party APIs and SaaS tools. Marketing, sales, support, and payments data usually lives outside your own infrastructure, in tools like Salesforce, Stripe, or Zendesk. The only way in is the vendor’s API, which comes with its own schema, pagination scheme, and rate limits (covered below).
Files dropped in storage. A common, low-tech but very real pattern: a partner or internal team exports a CSV/Parquet/JSON file and lands it in an S3 bucket, an SFTP server, or a shared drive on a schedule. Simple to produce, but fragile — file formats, delimiters, and encodings drift silently unless someone owns a schema contract.
Event streams and logs. Application logs, clickstream events, and message queues (Kafka, Kinesis, Pub/Sub) produce a continuous flow of small, semi-structured records. This is the natural source for streaming ingestion.
IoT devices and sensors. Physical devices — industrial sensors, GPS trackers, smart meters — emit small readings frequently, often over constrained networks (MQTT is common here), and often need edge aggregation before they reach the cloud.
Public and government datasets. Census bureaus, open-data portals, and regulatory agencies publish structured datasets, often as bulk downloads or modest APIs, updated on slow cadences (monthly, yearly). Useful for enrichment (e.g., joining zip codes to demographic data) but rarely a primary business source.
Webhooks. Instead of you polling a vendor’s API, the vendor pushes an event to an HTTP endpoint you expose the moment something happens (a payment succeeds, a PR is merged). This inverts the usual pull model and requires your ingestion service to be reliably available to receive pushes — see ./11-streaming-and-realtime-data.md for handling this kind of event-driven ingress.
Understanding data generation before you build
A pipeline is only as reliable as the system generating the data it reads. Before writing a single line of extraction code, it pays to answer three questions about the upstream system:
- Schema stability — Does the source team version their API or database migrations? Will a new column silently appear, or will you be told in advance? An OLTP table owned by an actively developed service changes far more often than a slow-moving government dataset.
- Volume and traffic patterns — Is data produced at a steady trickle, or in daily bursts (e.g., a nightly batch job in the source system itself)? Ingestion that assumes a steady rate will be surprised by a 100x spike at midnight.
- Ownership — Who can you actually talk to when something breaks? An internal OLTP database has an engineering team you can page; a public dataset has, at best, a mailing list. Knowing the ownership boundary up front determines how defensively you need to code your ingestion (e.g., strict schema validation and alerting when the source is unowned or external).
Treating the source as a black box that “just provides data” is the most common root cause of fragile pipelines. A few minutes talking to the team that owns the upstream system — or reading its changelog — saves days of firefighting later.
Data collection considerations
Beyond the technical shape of the data, collecting it responsibly involves a few non-negotiable practicalities:
- Data ownership and access agreements. Before ingesting from a third-party system, confirm who owns the data, what you are contractually allowed to do with it, and for how long you may retain it. This is especially relevant for data shared by partners or customers, where a data processing agreement (DPA) may define the boundaries.
- Rate limits on APIs. Every vendor API imposes some cap on request volume (requests/second or/day). Ingestion jobs must respect these limits — exceeding them gets your API key throttled or banned, and building automatic backoff is table stakes (see the API ingestion section below).
- Privacy and consent at the point of collection. If the data includes personal information, you need to know whether the end user consented to that data being collected and used the way your pipeline intends. This note only flags the concern; the deeper treatment — regulations like GDPR/CCPA, PII handling, anonymization — lives in
./15-data-security-and-compliance.md.
Key Concepts
Batch, streaming, and micro-batch ingestion
The first major design decision for any ingestion pipeline is when data moves: all at once on a schedule, continuously as it’s produced, or somewhere in between.
Batch ingestion pulls a chunk of data on a schedule — hourly, nightly, weekly. It’s the simplest model to build and reason about: a job runs, extracts what changed (or everything), loads it, and finishes. Tools like Airflow orchestrate this style of pipeline naturally. The tradeoff is latency: data is only as fresh as the last run, so a nightly batch means yesterday’s data is available today.
Streaming (real-time) ingestion processes each record (or micro-record) as it arrives, typically through a message broker like Kafka, Kinesis, or Pub/Sub, feeding a stream processor (Flink, Spark Structured Streaming, ksqlDB). Latency drops to seconds or less, but the operational complexity rises sharply: you now need to think about out-of-order events, exactly-once vs. at-least-once delivery, backpressure, and stateful processing. Full treatment in ./11-streaming-and-realtime-data.md.
Micro-batching is the pragmatic middle ground: instead of processing one event at a time or waiting a full day, you ingest small batches on a short interval — every one to five minutes, for example. This captures most of the freshness benefit of streaming with much of the operational simplicity of batch, and is what tools like Spark Structured Streaming’s default trigger, or a frequently-scheduled Airflow DAG, effectively implement.
| Ingestion type | Latency | Complexity | Typical trigger |
|---|---|---|---|
| Batch | Minutes to hours (or days) | Low | Cron/scheduler (e.g., nightly at 2 AM) |
| Micro-batch | Seconds to minutes | Medium | Short fixed interval (e.g., every 1–5 min) |
| Streaming | Milliseconds to seconds | High | Continuous, event-driven |
Choosing between them is a tradeoff between how fresh the business actually needs the data to be and how much operational complexity the team can support. Most analytics use cases (daily dashboards, weekly reports) are well served by batch or micro-batch; fraud detection, real-time recommendations, and operational alerting justify the cost of true streaming.
Change Data Capture (CDC)
Re-extracting an entire source table on every run — a full extraction — is simple but wasteful and slow once a table grows past a few million rows. Change Data Capture (CDC) solves this by capturing only the row-level changes (inserts, updates, deletes) that happened since the last extraction, instead of pulling the whole table again.
There are two common ways to implement CDC:
Log-based CDC reads the database’s internal transaction log — the mechanism the database already uses for its own replication and crash recovery — and turns each logged change into an event. Debezium is the standard open-source tool here: it reads MySQL’s binlog or PostgreSQL’s write-ahead log (WAL) and emits a Kafka event for every insert, update, and delete. Because it taps into a log the database already maintains, log-based CDC:
- Puts minimal extra load on the source database (no repeated polling queries).
- Captures deletes, which query-based approaches structurally cannot see (a deleted row leaves no trace to poll for).
- Preserves ordering and captures every intermediate change, not just the latest state.
Query-based CDC instead polls the source table periodically, comparing a updated_at timestamp (or a monotonically increasing ID) to find rows that changed since the last poll. It’s easier to set up — no access to database internals is required — but it has real limitations: it cannot detect hard deletes (a row that vanishes doesn’t show up in a WHERE updated_at > last_poll query), it adds repeated read load to the source database, and it misses any row that changed and changed back between polls.
| Log-based CDC | Query-based CDC | |
|---|---|---|
| Source load | Low (reads log, not tables) | Higher (repeated polling queries) |
| Captures deletes | Yes | No (without soft-delete flags) |
| Setup complexity | Higher (needs log access, e.g., binlog/WAL enabled) | Lower (just SQL access) |
| Ordering guarantees | Strong (log is ordered) | Weak (depends on poll timing) |
| Typical tooling | Debezium, AWS DMS, Fivetran | Custom SQL polling jobs |
For production systems with meaningful write volume, log-based CDC is the preferred approach precisely because it protects the source database from extra load and gives a complete, ordered picture of every change — including deletes that query-based polling would silently miss.
Extract & load mechanics
Full vs. incremental extraction. A full extraction pulls the entire dataset every run; it’s simple and self-healing (any past error is wiped out on the next run) but doesn’t scale well as tables grow. Incremental extraction pulls only new or changed rows since the last run — using a timestamp column, an auto-incrementing ID, or CDC — and is the default choice once a source is large enough that a full reload would be too slow or costly.
Idempotent loads. A core reliability principle: an ingestion job should be safe to re-run. If a job fails halfway through, or is accidentally triggered twice, re-running it must not create duplicate rows or double-count metrics. Common techniques include upserting on a natural or surrogate key (INSERT ... ON CONFLICT DO UPDATE / MERGE) instead of blind appends, writing to a staging table and swapping it in atomically, and deduplicating on a unique event ID before the final write. Idempotency is what lets you retry failed ingestion jobs without fear — a property that’s easy to skip early on and expensive to retrofit later.
Watermarking and checkpointing. To know what to extract next, an incremental pipeline needs to track how far it has already gotten — a watermark (e.g., “last extracted updated_at = 2026-07-18 23:00:00”) or a checkpoint (e.g., a Kafka consumer offset). This state must be stored durably (in a control table, a metadata store, or the streaming platform’s own offset tracking) and updated only after a batch is confirmed to have landed successfully — otherwise a crash between extraction and checkpoint update can silently skip or duplicate data.
APIs as a data source
Ingesting from a third-party REST API involves a set of practical concerns that don’t come up when reading directly from a database:
- Pagination. Most APIs cap the number of records per response and require follow-up requests using an offset, a page number, or a cursor token. Cursor-based pagination is more robust to data changing mid-extraction (offset-based pagination can skip or repeat rows if records are inserted/deleted between pages).
- Rate limiting and backoff. APIs return HTTP 429 (Too Many Requests) or similar when you exceed their allowed call volume. A production ingestion job needs exponential backoff with jitter, and should respect any
Retry-Afterheader the API provides, rather than hammering the endpoint on a fixed retry schedule. - Authentication. Simple APIs use a static API key sent as a header or query parameter; more sensitive ones use OAuth 2.0, which requires managing token refresh (access tokens typically expire in minutes to hours) as part of the ingestion job, not as a one-time setup step.
Handling these three well is most of what separates a fragile “curl in a cron job” script from a reliable ingestion pipeline against a third-party API.
Comparing ingestion patterns
Putting the extraction mechanism and the timing model together gives four practical patterns:
| Pattern | Latency | Complexity | Best fit |
|---|---|---|---|
| Full batch | Hours to days | Low | Small/slow-changing tables, first-time backfills |
| Incremental batch | Minutes to hours | Medium | Large tables with a reliable updated_at/ID column |
| CDC | Seconds to minutes | Medium–high | Large OLTP tables needing near-real-time replication, including deletes |
| Streaming | Milliseconds to seconds | High | Event-driven use cases: fraud detection, live dashboards, alerting |
In practice, most data platforms use a mix: CDC or incremental batch for OLTP replication into the warehouse, batch pulls for third-party API data that doesn’t change quickly, and streaming reserved for the subset of use cases that genuinely need sub-minute latency. Once data lands, it flows into transformation — see ./08-etl-elt-and-data-pipelines.md — and needs to be checked for correctness and tracked for lineage, covered in ./14-data-quality-governance-and-metadata.md.
Best Practices
- Talk to the source owner before building. Understand schema stability, volume patterns, and who to contact before writing extraction code — most ingestion incidents trace back to an undocumented upstream change nobody flagged.
- Prefer log-based CDC over query polling for production OLTP sources. It’s lower-impact on the source and is the only reliable way to capture deletes.
- Make every load idempotent. Design upserts on a stable key, or use atomic staging-table swaps, so re-running a failed job is always safe.
- Track progress with durable watermarks/checkpoints, updated only after a successful write. Never update the watermark before you’re sure the data landed.
- Validate schemas at the ingestion boundary. Catch a source’s breaking change (renamed column, changed type) with a schema check on ingest, not three transformation layers downstream.
- Handle API rate limits with exponential backoff and respect for
Retry-After, rather than fixed-interval retries that can make throttling worse. - Choose the ingestion pattern by actual business latency requirements, not by what’s trendy. Batch and micro-batch are simpler to operate and are the right choice for the majority of analytics workloads; reserve streaming for use cases that need it.
- Log and alert on ingestion volume anomalies. A sudden drop (or spike) in row counts from a source is often the first sign of an upstream schema change or an outage — catch it at ingestion, not at the dashboard.