Overview

13 Project: Finding the fastest way to get around NYC

The chapter turns prior Airflow concepts into a practical, end-to-end project that answers a simple question: if you travel between two NYC neighborhoods right now, is a taxi or a Citi Bike faster? It assembles a reproducible environment using Docker Compose that includes mock data sources for taxis and bikes, an S3-compatible object store (MinIO), a PostgreSQL database, and a lightweight web app. The objective is to design a robust pipeline that continuously ingests, transforms, and serves results so they can be queried and visualized.

Two live-like feeds drive the use case: Yellow Cab CSV files exposed by a basic file server in rolling 15-minute batches, and Citi Bike rides exposed by an authenticated REST API with configurable lookback windows. Because taxi files expire after an hour, the pipeline persists “raw” snapshots to MinIO for reproducibility and later reprocessing, aligning both sources on 15-minute intervals and scheduling the DAG accordingly. Instead of relying on generic operators, custom Python tasks use requests and the S3Hook, credentials are managed via Airflow connections, and idempotency is built in by safely skipping objects that already exist. The chapter advocates a clear data layout—separating raw from processed—to enable repeatable runs and shared consumption.

For transformation, Citi Bike station coordinates are mapped to taxi zones to make the two sources comparable, then standardized and written as Parquet to a processed area. To avoid code duplication, the chapter introduces a reusable PandasOperator that composes three callables—read, transform, write—around a DataFrame, making it easy to swap formats and steps. When publishing to the database, idempotency is enforced by overwriting processed outputs and adding an execution-date column, paired with a transactional delete-then-insert to prevent duplicate rows. The result is a 15-minute cadence pipeline that updates PostgreSQL and powers a web app showing, by neighborhood pair and time of day, whether taxis or Citi Bikes are faster on average.

Our Docker Compose file creates several services. Our task is to load data from the REST API and file share and transform it to eventually view the fastest method of transportation on the resulting web page.
NYC Yellow Cab zones plotted with Citi Bike station locations
Mapping Citi Bike stations (dots) to Yellow Cab zones enables accurate comparison but neglects the fact that rides within one zone can vary in distance. Ride A is obviously shorter than ride B. By averaging all ride times from Greenwich Village South to East Village, you lose such information.
A custom S3 hostname can be set, but not where you would expect it.
Screenshot of the MinIO interface showing a file written to /datalake/raw/citibike, and the filename templated with data_interval_start.
First two tasks of the NYC transportation DAG downloading data
Data exported to the MinIO storage. We have MinIO under our own control and can always refer to these files later.
We cannot control the structure of data in external systems. In our own systems, it’s logical to store data according to the life cycle of data. For example, unedited data is stored in Raw, derived and transformed data is stored in Processed, and data sets ready for transfer are stored in Export.
Web application displaying results stored in the PostgreSQL database, continuously updated by the Airflow DAG

Summary

  • We cannot control the structure of data in external systems. In our own systems, it’s logical to store data according to the life cycle of data.
  • Many organizations apply fine-grained separations between the stages of data. No one structure suits all; your project and the project’s requirements will determine how to best structure the movement of data.
  • When an operator’s functionality does not fulfill a need, you must resort to calling a function with a PythonOperator or implement your own operator.
  • We generalized the execution of Pandas transformations in a custom written PandasOperator for reusability and code deduplication
  • The tasks using the PandasOperator can provide a specific callable for reading into a pandas Dataframe and writing that Dataframe to a specific output format

FAQ

What is the goal of the NYC transportation project in this chapter?The project builds an Airflow pipeline that continuously determines whether a Yellow Cab or a Citi Bike is faster between NYC neighborhoods at specific times. It extracts recent ride data, processes it, stores results in Postgres, and serves them via a small web app.
Which services does the provided Docker Compose file start, and what are the key ports?It starts data sources and infrastructure: Airflow (web UI 8080, metastore 5432), Citi Bike API (8082, basic auth citibike/cycling), Taxi static file server (8081), MinIO (console 9001; S3 API inside network), three Postgres databases (5433 taxi, 5434 citibike, 5435 results), and a results web app (8083).
How are the two data sources aligned in time for extraction?The DAG is scheduled every 15 minutes (cron "*/15 * * * *"). Taxi files are published in 15-minute chunks on the file server and kept for one hour. The Citi Bike API is queried for the last 15 minutes via /recent/minute/15 so both sources cover the same interval per run.
Why is MinIO used, and how do you point Airflow’s S3Hook to it?MinIO provides S3-compatible object storage to persist raw and processed data for reproducibility. Configure the S3 connection in Airflow with an endpoint_url in the connection extras (e.g., AIRFLOW_CONN_S3=aws://...@/?endpoint_url=http://minio:9000), so S3Hook talks to MinIO instead of AWS.
Why not use SimpleHttpOperator for downloads, and what is used instead?SimpleHttpOperator doesn’t handle persisting responses. The chapter uses PythonOperator with requests to fetch data and S3Hook/MinIO client to store it, giving full control over auth, filenames, and storage layout.
How is idempotency handled when uploading raw files to object storage?S3Hook.load_string doesn’t overwrite existing keys. The taxi download task wraps the upload in a try/except and skips keys that already exist (catching ValueError), making retries and reruns safe without failures or duplicates.
How are Citi Bike station coordinates compared to Yellow Cab zones?Bike station latitude/longitude points are mapped to taxi zones, enabling like-for-like comparisons at the zone level. This simplifies the problem but loses within-zone distance variability.
How are similar transformations implemented without code duplication?A custom PandasOperator composes three callables: input_callable (reads into a DataFrame), transform_callable (optional, DataFrame in/out), and output_callable (writes the DataFrame). You can swap pd.read_csv vs. pd.read_parquet or different writers without rewriting the operator.
Why structure data into raw and processed folders?To ensure reproducibility and reuse. Raw stores minimally edited snapshots of source data (especially important when sources only expose recent state), while processed holds transformed, analysis-ready outputs that can be read repeatedly without recomputation.
How is idempotency ensured when writing processed results to Postgres?Add a column like airflow_execution_date to the target table. In a single transaction, delete any rows for the current execution date, then insert the new batch. This prevents duplicates and makes reruns deterministic.

pro $24.99 per month

  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose one free eBook per month to keep
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime

lite $19.99 per month

  • access to all Manning books, including MEAPs!

team

5, 10 or 20 seats+ for your team - learn more


choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Data Pipelines with Apache Airflow, Second Edition ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Data Pipelines with Apache Airflow, Second Edition ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Data Pipelines with Apache Airflow, Second Edition ebook for free