পাঠ ২৮ · ২৯-এর মধ্যে · মডিউল ৪
Home / AI Courses / Data Engineering / প্রজেক্ট: end-to-end ML pipeline

প্রজেক্ট: end-to-end ML pipeline

Capstone — ingest, transform, store, train, serve
১৫ মিনিট পড়া Capstone · Advanced Airflow + Spark + dbt + ML

এই পাঠে যা শিখবেন

  • কীভাবে real-world business problem থেকে architecture নকশা — ম্যাচ করা stack selection
  • প্রতিটি tool (Airflow, Spark, dbt) কেন এই জায়গায় — অন্য option-এর সাথে compare
  • Bronze-Silver-Gold medallion architecture — production-এ ১০০ table-এর organization
  • Feature store, model training loop, এবং low-latency serving — full ML lifecycle

১ · Business problem — fraud detection at "Daak Pay"

ভাবুন একটি কাল্পনিক Bangladesh MFS — Daak Pay (bKash-style)। ২০ million registered user, daily ৫ million transaction। ২০২৪-এ fraud loss ছিল মাসে ১২ কোটি টাকা — primarily SIM-swap, phishing, mule account। CEO target — "fraud loss ৬ মাসে ৫০% কমাতে চাই।"

Data scientist বলেন: "আমার gradient boosting model AUC ০.৯২। শুধু production data চাই।"

Data engineer-এর কাজ এখন শুরু:

  • ৫ million daily txn — কীভাবে <১ সেকেন্ড latency-তে model-কে score করাবেন?
  • Daily retrain — labeled data কোথা থেকে?
  • Feature consistency — training ও serving-এ একই value কীভাবে নিশ্চিত?
  • Compliance — Bangladesh Bank-এর AML rule, regulatory report?
  • Cost — $50K/month ceiling।
Pipeline-এর ৭ স্তর

১) Source: PostgreSQL (transactions OLTP)।
২) Ingest: Debezium CDC → Kafka।
৩) Bronze: raw event in Iceberg, immutable।
৪) Silver: cleaned, deduplicated, schema-enforced।
৫) Gold: business-ready, dimensional models।
৬) Feature: ML feature store (Feast / online + offline)।
৭) Model + Serve: MLflow → FastAPI inference।

২ · Architecture overview

Daak Pay fraud detection — full pipeline 5M txn/day · <100ms inference · daily retrain 🏦 Source PostgreSQL OLTP 📥 Ingest Debezium → Kafka 🥉 Bronze Iceberg raw 🥈 Silver Spark clean 🥇 Gold dbt marts 📊 BI Tableau 🧮 Feast Feature Store (online + offline) user_30d_txn_count · last_login_hours · velocity 🤖 MLflow Training XGBoost · daily retrain Airflow DAG · 02:00 BST ⚡ FastAPI Inference <100ms p99 Kubernetes 3 pods 📈 Monitor Drift · latency · cost Prom + Grafana + Slack 🌬️ Airflow orchestrator — schedule, retry, alert, lineage emit
৭-layer pipeline — Airflow সব orchestrate করে। প্রতিটি layer-এর responsibility আলাদা ও testable।

৩ · Source — PostgreSQL OLTP

Daak Pay-এর primary database PostgreSQL ১৫। প্রতিটি transaction transactions table-এ insert হয় — বাংলাদেশ-এ multiple data center (Dhaka primary, Chattogram DR)। OLTP-এর কাজ — sub-100ms write, ACID। Analytics-এর জন্য direct query করা ভুল — production load-এ impact করবে।

SQL · source schema
-- transactions table (PostgreSQL)
CREATE TABLE transactions (
  txn_id          BIGSERIAL PRIMARY KEY,
  sender_user_id  BIGINT NOT NULL,
  receiver_msisdn VARCHAR(15) NOT NULL,
  amount_bdt      NUMERIC(14,2) NOT NULL CHECK (amount_bdt > 0),
  txn_type        VARCHAR(20),  -- send_money, cash_out, payment
  status          VARCHAR(20),  -- pending, success, failed
  channel         VARCHAR(20),  -- app, ussd, agent
  device_id       VARCHAR(64),
  ip_address      INET,
  geo_lat         NUMERIC(9,6),
  geo_lon         NUMERIC(9,6),
  created_at      TIMESTAMPTZ DEFAULT NOW(),
  updated_at      TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_txn_sender_time ON transactions(sender_user_id, created_at DESC);
CREATE INDEX idx_txn_status_time ON transactions(status, created_at DESC);

-- WAL-level logical replication enabled for CDC
ALTER SYSTEM SET wal_level = 'logical';

    
wal_level = logical activate করায় Debezium PostgreSQL-এর Write-Ahead Log থেকে প্রতিটি row-level change capture করতে পারবে — INSERT, UPDATE, DELETE। OLTP-এ কোনো extra load নেই — replication-এর free ride।

৪ · Ingest — Debezium + Kafka

DebeziumDebeziumRedHat-এর open-source CDC platform — PostgreSQL, MySQL, MongoDB-এর change events Kafka-তে stream করে। বহু production team-এর staple। Kafka Connect-এর উপর চলে। প্রতিটি table-এর change → Kafka topic — যেমন daakpay.public.transactions।

JSON · Debezium connector config
{
  "name": "daakpay-postgres-connector",
  "config": {
    "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
    "database.hostname": "pg-primary.daakpay.internal",
    "database.port": "5432",
    "database.user": "debezium_reader",
    "database.password": "${file:/secrets/db.pwd}",
    "database.dbname": "daakpay_prod",
    "topic.prefix": "daakpay",
    "table.include.list": "public.transactions,public.users,public.accounts",
    "plugin.name": "pgoutput",
    "snapshot.mode": "initial",
    "decimal.handling.mode": "double",
    "schema.history.internal.kafka.bootstrap.servers": "kafka:9092",
    "schema.history.internal.kafka.topic": "schema-changes.daakpay",
    "transforms": "unwrap,route",
    "transforms.unwrap.type": "io.debezium.transforms.ExtractNewRecordState",
    "transforms.unwrap.drop.tombstones": "false",
    "transforms.route.type": "org.apache.kafka.connect.transforms.RegexRouter",
    "transforms.route.regex": "daakpay\\.public\\.(.*)",
    "transforms.route.replacement": "cdc.daakpay.$1"
  }
}

    
Connector deploy করার সাথে সাথেই — cdc.daakpay.transactions topic-এ প্রতিটি row change JSON হিসেবে আসবে। Schema registry (Confluent বা Apicurio) Avro/Protobuf schema track করে — schema evolution-এ downstream consumer ভাঙবে না।

৫ · Bronze layer — raw immutable storage

Bronze = source-of-truth, কোনো transformation নয়। Apache Iceberg table-এ append-only, partitioned by ingestion date। Compliance-এর জন্য — ৭ বছর retain।

Python · Spark Structured Streaming → Iceberg
from pyspark.sql import SparkSession
from pyspark.sql.functions import col, current_timestamp, to_date

spark = (SparkSession.builder
    .appName("bronze_txn_ingest")
    .config("spark.sql.extensions",
            "org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions")
    .config("spark.sql.catalog.lake", "org.apache.iceberg.spark.SparkCatalog")
    .config("spark.sql.catalog.lake.type", "glue")
    .config("spark.sql.catalog.lake.warehouse", "s3://daakpay-lake/")
    .getOrCreate())

raw_stream = (spark.readStream
    .format("kafka")
    .option("kafka.bootstrap.servers", "kafka:9092")
    .option("subscribe", "cdc.daakpay.transactions")
    .option("startingOffsets", "latest")
    .option("failOnDataLoss", "false")
    .load())

# Kafka value JSON parse + ingestion metadata
parsed = (raw_stream
    .selectExpr("CAST(value AS STRING) AS payload",
                "topic", "partition", "offset", "timestamp")
    .withColumn("ingest_ts", current_timestamp())
    .withColumn("ingest_date", to_date("ingest_ts")))

(parsed.writeStream
    .format("iceberg")
    .outputMode("append")
    .option("path", "lake.bronze.transactions_raw")
    .option("checkpointLocation", "s3://daakpay-checkpoints/bronze_txn/")
    .partitionBy("ingest_date")
    .trigger(processingTime="1 minute")
    .start())

    
Micro-batch ১ মিনিট — latency vs cost trade-off। প্রতিটি record-এ ingestion metadata (offset, partition, ingest_ts) — debugging-এ অপরিহার্য। Iceberg-এর partitioning auto-evolve, schema change এ যাত্রা ভাঙে না। Checkpoint S3-এ — pod restart হলেও state preserve।

৬ · Silver layer — cleansed ও validated

Silver-এ JSON payload parse করে strongly-typed columns, schema enforcement, deduplication, এবং Great Expectations data quality check।

Python · Spark — bronze → silver
from pyspark.sql.functions import from_json, col, to_timestamp
from pyspark.sql.types import (StructType, StructField, LongType,
                               StringType, DoubleType, TimestampType)

txn_schema = StructType([
    StructField("txn_id", LongType()),
    StructField("sender_user_id", LongType()),
    StructField("receiver_msisdn", StringType()),
    StructField("amount_bdt", DoubleType()),
    StructField("txn_type", StringType()),
    StructField("status", StringType()),
    StructField("channel", StringType()),
    StructField("device_id", StringType()),
    StructField("ip_address", StringType()),
    StructField("geo_lat", DoubleType()),
    StructField("geo_lon", DoubleType()),
    StructField("created_at", StringType()),
])

bronze = spark.readStream.format("iceberg").load("lake.bronze.transactions_raw")

silver = (bronze
    .withColumn("data", from_json(col("payload"), txn_schema))
    .select("data.*", "ingest_ts")
    .withColumn("created_at", to_timestamp("created_at"))
    .filter(col("amount_bdt").isNotNull() & (col("amount_bdt") > 0))
    .filter(col("status").isin("success", "failed", "pending"))
    .dropDuplicates(["txn_id"])
    .withColumn("event_date", col("created_at").cast("date")))

(silver.writeStream
    .format("iceberg")
    .outputMode("append")
    .option("path", "lake.silver.transactions")
    .option("checkpointLocation", "s3://daakpay-checkpoints/silver_txn/")
    .partitionBy("event_date")
    .trigger(processingTime="2 minutes")
    .start())

    
Schema-on-write — invalid record drop। Idempotent — txn_id-এ dedupe করায় Kafka replay safe। Partition by event_date (ingest নয়) — analyst query natural। Bronze-এ ingest_date partition immutability rule, silver-এ business date practical query।

৭ · Gold layer — dbt dimensional model

Medallion architectureMedallion architectureDatabricks-এর popularize করা bronze-silver-gold pattern — raw → cleaned → business-ready। প্রতিটি layer-এ আলাদা SLA, schema rule ও audience।-এ Gold layer = dbt territory। Star schema, dimensional modeling, SCD-Type-2 history।

SQL · dbt model fact_transactions
-- models/marts/fact_transactions.sql
{{
  config(
    materialized='incremental',
    unique_key='txn_id',
    on_schema_change='fail',
    incremental_strategy='merge',
    partition_by={'field': 'event_date', 'data_type': 'date'},
    cluster_by=['sender_user_id'],
    meta={
      'owner': 'risk-data-team@daakpay.com.bd',
      'pii_level': 'restricted',
      'retention_days': 2555,
      'sla_hours': 2
    }
  )
}}

WITH txn AS (
  SELECT * FROM {{ ref('stg_silver_transactions') }}
  {% if is_incremental() %}
    WHERE event_date >= DATE_SUB(CURRENT_DATE(), INTERVAL 2 DAY)
  {% endif %}
),

users AS (SELECT * FROM {{ ref('dim_users') }}),

agents AS (SELECT * FROM {{ ref('dim_agents') }})

SELECT
  t.txn_id,
  t.created_at AS event_at,
  CAST(t.created_at AS DATE) AS event_date,
  t.sender_user_id,
  u.user_segment,
  u.kyc_level,
  u.account_age_days,
  t.receiver_msisdn,
  t.amount_bdt,
  t.txn_type,
  t.status,
  t.channel,
  t.device_id,
  t.geo_lat,
  t.geo_lon,
  -- pre-compute risk signals (cheap features)
  CASE
    WHEN t.amount_bdt >= 25000 THEN 'high'
    WHEN t.amount_bdt >= 5000  THEN 'medium'
    ELSE 'low'
  END AS amount_band,
  TIMESTAMP_DIFF(t.created_at, u.last_login_at, MINUTE) AS minutes_since_login
FROM txn t
LEFT JOIN users u USING (sender_user_id)
LEFT JOIN agents a ON t.receiver_msisdn = a.agent_msisdn

    
Incremental + merge — শুধু last 2 day-এর data process। on_schema_change='fail' — accidental schema drift early catch। Meta block — DataHub-এ harvested, owner ও SLA visible। 2-hour SLA — fraud detection-এ real-time-এর কাছাকাছি।

৮ · Feature store — Feast

Feature storeFeature StoreML feature-এর centralized repository — training (offline) ও serving (online) উভয়ের জন্য consistent values, versioning, lineage সহ। Feast, Tecton, Hopsworks — popular। ML-এর সবচেয়ে undervalued infrastructure। দু'টি জায়গায় same feature-এর consistent value চাই — training (last 2 year history) ও serving (last 30 sec real-time)।

Python · Feast feature definitions
# features/user_velocity.py
from datetime import timedelta
from feast import Entity, FeatureView, Field, FileSource, RedisOnlineStore
from feast.types import Float32, Int64

user = Entity(name="user", join_keys=["sender_user_id"])

# Offline source — Iceberg/Snowflake table
txn_source = FileSource(
    name="silver_txn_source",
    path="s3://daakpay-lake/silver/transactions/",
    timestamp_field="event_at",
    created_timestamp_column="ingest_ts",
)

user_velocity_fv = FeatureView(
    name="user_velocity",
    entities=[user],
    ttl=timedelta(days=1),
    schema=[
        Field(name="txn_count_30d",   dtype=Int64),
        Field(name="txn_amount_30d",  dtype=Float32),
        Field(name="txn_count_1h",    dtype=Int64),
        Field(name="distinct_recv_24h", dtype=Int64),
        Field(name="avg_amount_30d",  dtype=Float32),
        Field(name="velocity_score",  dtype=Float32),
    ],
    source=txn_source,
    online=True,
)

    
Feast দু'টি store maintain করে — offline (training-এ batch query) এবং online (Redis, ms-latency)। Materialization job nightly run করে — silver table থেকে aggregate compute, Redis-এ push। Training data fetch ও inference fetch একই API — feature skew impossible।

৯ · Model training — MLflow + XGBoost

Python · daily training job
import mlflow
import xgboost as xgb
import pandas as pd
from sklearn.metrics import roc_auc_score, average_precision_score
from feast import FeatureStore
from datetime import datetime, timedelta

mlflow.set_tracking_uri("http://mlflow.daakpay.internal:5000")
mlflow.set_experiment("fraud_detection_daily")

# 1. Training labels — last 60 days, fraud confirmed
labels = pd.read_sql("""
    SELECT txn_id, sender_user_id, event_at,
           CASE WHEN fraud_label IS NOT NULL THEN 1 ELSE 0 END AS y
    FROM analytics.fact_transactions f
    LEFT JOIN risk.fraud_confirmed c USING (txn_id)
    WHERE event_date BETWEEN DATE_SUB(CURRENT_DATE(), INTERVAL 60 DAY)
                         AND DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY)
""", conn)

# 2. Get features at the time of each transaction (point-in-time correct!)
fs = FeatureStore(repo_path="features/")
training_df = fs.get_historical_features(
    entity_df=labels,
    features=[
        "user_velocity:txn_count_30d",
        "user_velocity:txn_amount_30d",
        "user_velocity:distinct_recv_24h",
        "user_velocity:velocity_score",
        "user_profile:account_age_days",
        "user_profile:kyc_level",
        "device_risk:device_score",
    ],
).to_df()

X = training_df.drop(columns=["txn_id", "sender_user_id", "event_at", "y"])
y = training_df["y"]

# 3. Train + log
with mlflow.start_run(run_name=f"daily_{datetime.now():%Y%m%d}"):
    model = xgb.XGBClassifier(
        n_estimators=400, max_depth=6, learning_rate=0.05,
        scale_pos_weight=50,  # imbalanced — fraud rare
        eval_metric="aucpr", tree_method="hist",
    )
    model.fit(X, y)

    auc  = roc_auc_score(y, model.predict_proba(X)[:, 1])
    aupr = average_precision_score(y, model.predict_proba(X)[:, 1])

    mlflow.log_metric("auc", auc)
    mlflow.log_metric("aupr", aupr)
    mlflow.log_param("rows", len(X))
    mlflow.xgboost.log_model(model, "model",
        registered_model_name="fraud_detector")

    # Auto-promote if metrics threshold met
    if aupr > 0.65:
        client = mlflow.MlflowClient()
        v = client.get_latest_versions("fraud_detector",
                                       stages=["None"])[0].version
        client.transition_model_version_stage(
            "fraud_detector", v, "Production",
            archive_existing_versions=True)

    
get_historical_features — point-in-time-correct join। প্রতিটি transaction-এর সময় feature value কী ছিল — না future leak, না stale। AUPR threshold ০.৬৫ — fraud detection-এ AUC-এর চেয়ে practical (rare positive class)।

১০ · Inference service — FastAPI <100ms

Python · FastAPI inference
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from feast import FeatureStore
import mlflow.xgboost
import time, logging

app = FastAPI(title="Daak Pay fraud scorer")
logger = logging.getLogger("fraud_api")

fs = FeatureStore(repo_path="/app/features")
model = mlflow.xgboost.load_model("models:/fraud_detector/Production")

class TxnIn(BaseModel):
    txn_id: int
    sender_user_id: int
    amount_bdt: float
    device_id: str
    ip_address: str

@app.post("/score")
async def score(txn: TxnIn):
    t0 = time.perf_counter()

    # 1. fetch online features (Redis ~5ms)
    feats = fs.get_online_features(
        features=[
            "user_velocity:txn_count_30d",
            "user_velocity:txn_amount_30d",
            "user_velocity:distinct_recv_24h",
            "user_velocity:velocity_score",
            "user_profile:account_age_days",
            "user_profile:kyc_level",
        ],
        entity_rows=[{"sender_user_id": txn.sender_user_id}],
    ).to_dict()

    # 2. Build feature vector
    row = [
        feats["txn_count_30d"][0] or 0,
        feats["txn_amount_30d"][0] or 0,
        feats["distinct_recv_24h"][0] or 0,
        feats["velocity_score"][0] or 0,
        feats["account_age_days"][0] or 0,
        feats["kyc_level"][0] or 0,
        device_risk_lookup(txn.device_id),  # external Redis
    ]

    # 3. Predict
    score = float(model.predict_proba([row])[0][1])
    decision = "block" if score > 0.85 else ("review" if score > 0.45 else "approve")

    latency_ms = (time.perf_counter() - t0) * 1000
    logger.info(f"txn={txn.txn_id} score={score:.3f} dec={decision} lat={latency_ms:.1f}ms")

    return {
        "txn_id": txn.txn_id,
        "score": round(score, 4),
        "decision": decision,
        "latency_ms": round(latency_ms, 2),
        "model_version": "fraud_detector_v_prod",
    }

@app.get("/health")
async def health():
    return {"status": "ok"}

    
Latency budget: Redis fetch ৫ms, model predict ১০ms, network ১৫ms — total p99 < ৫০ms। Kubernetes-এ ৩ replica, HPA-driven autoscale। প্রতিটি call audit log → S3 → daily fraud analyst review।

১১ · Airflow DAG — orchestration

Python · Airflow daily DAG
from airflow import DAG
from airflow.providers.cncf.kubernetes.operators.pod import KubernetesPodOperator
from airflow.providers.dbt.cloud.operators.dbt import DbtCloudRunJobOperator
from airflow.operators.python import PythonOperator
from datetime import datetime, timedelta

default_args = {
    "owner": "risk-data-team",
    "retries": 2,
    "retry_delay": timedelta(minutes=10),
    "email_on_failure": True,
    "email": ["risk-oncall@daakpay.com.bd"],
}

with DAG(
    dag_id="fraud_pipeline_daily",
    default_args=default_args,
    schedule="0 2 * * *",          # 02:00 BST daily
    start_date=datetime(2025, 1, 1),
    catchup=False,
    max_active_runs=1,
    tags=["fraud", "ml", "production"],
) as dag:

    # 1. dbt build silver → gold
    dbt_run = DbtCloudRunJobOperator(
        task_id="dbt_marts",
        dbt_cloud_conn_id="dbt_cloud",
        job_id=42,
        check_interval=60,
        timeout=3600,
    )

    # 2. Materialize Feast features
    feast_apply = KubernetesPodOperator(
        task_id="feast_materialize",
        image="daakpay/feast-runner:1.4.0",
        cmds=["bash", "-c"],
        arguments=["feast materialize-incremental $(date -u +%Y-%m-%dT%H:%M:%S)"],
        get_logs=True,
    )

    # 3. Train model
    train = KubernetesPodOperator(
        task_id="train_model",
        image="daakpay/ml-trainer:0.9.2",
        cmds=["python", "-m", "training.daily"],
        resources={"requests": {"memory": "16Gi", "cpu": "4"}},
        get_logs=True,
    )

    # 4. Validate before promotion
    validate = PythonOperator(
        task_id="validate_model",
        python_callable=lambda **ctx: assert_min_aupr(threshold=0.65),
    )

    # 5. Promote + restart inference pods (rolling)
    promote = KubernetesPodOperator(
        task_id="promote_and_rollout",
        image="daakpay/k8s-helper:1.0",
        cmds=["bash", "-c"],
        arguments=["kubectl rollout restart deploy/fraud-api -n risk"],
    )

    # 6. Daily metrics report
    report = PythonOperator(
        task_id="slack_summary",
        python_callable=post_slack_summary,
    )

    dbt_run >> feast_apply >> train >> validate >> promote >> report

    
Single DAG-এ পুরো daily lifecycle। প্রতিটি task isolated container — failure-এ retry, dependency clean। Slack summary end-of-pipeline — "yesterday: ৫.১M txn scored, ৩৪০ blocked, AUPR ০.৭১"। DAG পুরো 2 ঘণ্টায় শেষ — SLA guarantee।

১২ · Monitoring ও observability

Production-এ "no monitoring = no production"। ৪ ধরনের monitor:

  • Pipeline health: DAG success rate, task duration, freshness lag (silver-এ data কতটা stale)।
  • Data quality: Great Expectations — null %, range, uniqueness; বিচ্যুতিতে DAG fail।
  • Model performance: AUC drift (rolling 7-day), score distribution shift (KS test), feature drift (PSI)।
  • Service health: p99 latency, error rate, throughput, Redis hit rate।

Tooling — Prometheus metric scrape, Grafana dashboard, PagerDuty/Slack alert। Cost monitoring — daily Snowflake/BigQuery spend Slack-এ post। Anomaly detection ML-driven — bill ১০x spike?

Common production failures we plan for: Kafka rebalance storm, Iceberg compaction backlog, dbt incremental break (when source schema changes silently), Redis OOM, model staleness during training failure (auto-fallback to last good Production version)।

১৩ · Compliance ও governance

  • Bangladesh Bank AML reporting: daily Suspicious Transaction Report (STR) — automated dbt model, manual reviewer sign-off।
  • Data residency: primary copy Bangladesh DC; cloud (S3) Singapore region with BB-approved DPA।
  • PII handling: NID, MSISDN — sensitive tier; column-level encryption (AES-GCM); access via Just-in-time approval।
  • Audit trail: every model decision logged with feature snapshot — appeals process, regulator inquiry, legal hold compatible।
  • Right to explanation: SHAP value cached for each block — customer support team explanation provide করতে পারে।

১৪ · Cost বিশ্লেষণ

Estimated monthly cost (production, ৫M daily txn):

  • Iceberg storage (S3) — ৫ TB hot + ৩০ TB cold = $৫২০।
  • Spark Streaming (EMR/Databricks) — 24/7 small cluster = $২,৪০০।
  • Snowflake compute (dbt run) — 4hr/day Medium = $৬০০।
  • Kafka (MSK) — 3 broker m5.large = $৯০০।
  • Redis (ElastiCache) — r6g.xlarge × 2 = $৪৫০।
  • FastAPI Kubernetes — 3 pod EKS = $২৭০।
  • MLflow + Airflow infra = $৩০০।
  • Egress, monitoring, misc = $৪৬০।
  • Total ≈ $৫,৯০০/month ≈ ৭ লক্ষ টাকা।

Fraud loss reduction ৫০% × মাসিক ১২ কোটি = ৬ কোটি savings। ROI ৮৫x। Capstone-এ এটাই আসল lesson — engineering = leverage।

Take-home challenge: এই পুরো architecture-কে আপনার laptop-এ Docker Compose-এ run করার চেষ্টা করুন। Postgres + Kafka + Spark + MinIO + Iceberg + Airflow + MLflow + FastAPI — সব containers। ১ সপ্তাহের project, কিন্তু আপনি data engineer career-এ এই blueprint forever ব্যবহার করবেন।

১৫ · Lessons learned — production-এ যা বইয়ে নেই

  • Idempotency-ই অর্ধেক যুদ্ধ: Kafka offset replay, Spark checkpoint loss, Airflow re-run — সবকিছুতে dedupe key দরকার।
  • Schema evolution painful: upstream একটি column nullable করলেই — dbt tests কাঁদে, downstream report ভাঙে। Schema registry + contract testing investment।
  • Data quality > algorithm: XGBoost vs LightGBM-এর difference ১% AUC; missing feature 7% AUC drop। Engineering effort feature consistency-তে।
  • Observability cheap, ignorance expensive: ১% extra cost = 10x faster debugging।
  • Backfill কঠিন: পুরাতন data-তে নতুন feature compute — point-in-time correctness rare-bug। Feast-এর offline store life-saver।
  • Stack proliferation evil: "ছোট ছোট" tool যোগ — ১৪ months পর ২০ system maintain। Ruthless consolidation।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ Daily retrain চলছে কিন্তু model AUPR হঠাৎ ০.৭১ থেকে ০.৪৮-এ drop। Pipeline কাঁদেনি, alert নেই। Investigation কোথা থেকে শুরু?

এটি ML production-এর সবচেয়ে stressful debug — silent drift। Pipeline metric green কিন্তু business metric (fraud loss) বেড়ে যাচ্ছে।

Step 1 — Drift type identification:

  • Feature drift: input distribution বদলেছে। PSI (Population Stability Index) প্রতিটি feature-এর — > ০.২৫ মানে significant drift।
  • Label drift: fraud rate বেড়েছে/কমেছে — sometimes attacker pattern shift করেছে।
  • Concept drift: X→y সম্পর্ক বদলেছে। Same feature, different outcome।
  • Data quality issue: upstream null %, schema break — কোনো feature constant হয়ে গেছে।

Step 2 — Diagnostic queries:

  • Recent training data feature distribution vs ৩০ days ago — Grafana panel।
  • Feature importance — কোন feature top contributor, এবং তার drift score?
  • Class balance — fraud rate stable? নতুন attack vector?
  • Data freshness — feature store last_update timestamps।

Common root causes (frequency order):

  • (১) Feast materialization gap: Redis online store stale; offline-online split। Training distribution ≠ serving।
  • (২) Source schema change: upstream device_id nullable হয়েছে — model একটি 0/null distinction-এ depend করত।
  • (৩) Label leakage shift: fraud_confirmed table-এর filter logic বদলেছে — আগে fraud হিসেবে count হত যা এখন না।
  • (৪) Adversarial attack: fraudster শিখেছে — pattern বদলেছে। Most concerning।
  • (৫) Feature engineering bug: dbt model-এ NULL handling change — silent।
  • (৬) Hyperparameter regression: auto-tune-এ overfitting।

Investigation techniques:

  • Time-travel — গত week-এর model-এ এই week-এর data → AUPR কত? যদি stable, model-এ সমস্যা; volatile হলে data drift।
  • SHAP analysis — best vs worst-day prediction-এর feature contribution comparison।
  • Slice analysis — কোন user segment-এ degrade? Channel-wise, geo-wise, kyc_level-wise।
  • Feature ablation — একটি একটি করে feature drop, কোনটি drop-এ AUPR retain?

Mitigation:

  • Last good model rollback — instant production stabilize।
  • Drift-detection auto-trigger — PSI threshold-এ retrain force।
  • Champion-Challenger — production-এ A/B যেকোনো নতুন model শুরুতে ৫% traffic-এ।
  • Adversarial robustness — fraud team-এর সাথে regular review, attack pattern intelligence।

মূল কথা: ML in production = continuous learning system। Static model মৃত; living system দরকার — drift detection, auto-retrain, manual review loop। যারা "deployed = done" ভাবে — তারাই ৬ মাস পর rebuild করে।

প্র ০২ Spark Structured Streaming না Flink না Kafka Streams? এই pipeline-এ কেন Spark বাছলেন? অন্যান্য কোন situation-এ অন্য choice?

Streaming framework selection — architecture-এর সবচেয়ে long-term consequential decision। Migration painful, lock-in deep।

তিন framework-এর comparison:

Spark Structured Streaming:

  • Micro-batch (default) বা continuous (experimental)। Latency: 100ms-2s।
  • Strengths: SQL-friendly, batch + stream unified, mature ecosystem, Iceberg/Delta first-class।
  • Weaknesses: True low-latency (< 100ms) unsuitable, complex stateful processing limited।
  • Best fit: ETL-heavy stream, lake/lakehouse target, batch-stream unification, team Spark-experienced।

Apache Flink:

  • True streaming, event-time-first। Latency: 10-100ms।
  • Strengths: complex windowing, true exactly-once, stateful processing best-in-class, sub-second SLA।
  • Weaknesses: steeper learning curve, smaller community than Spark, Java/Scala-heavy (PyFlink improving)।
  • Best fit: real-time analytics, fraud detection < 100ms, complex CEP (complex event processing)।

Kafka Streams:

  • Library, not framework — embed in your Java app।
  • Strengths: zero infrastructure (uses Kafka itself), low latency, simple operational model।
  • Weaknesses: Java-only practical, limited transformation library, Kafka-only source/sink।
  • Best fit: microservice-internal streaming, simple enrichment, single-purpose app।

আমাদের Daak Pay-এ Spark কেন:

  • Bronze ingestion + silver transformation একই engine — operational simplicity।
  • Iceberg natively integrated।
  • Team-এ Spark engineers বেশি (PySpark mature)।
  • Latency requirement 1-2 minute acceptable — analytical pipeline।
  • Inference layer separately ms-latency-তে FastAPI-এ — streaming-এর কাজ analytical।

কখন Flink choose করতাম:

  • If fraud scoring-এর জন্য true real-time stream-এ inline scoring চাইতাম (skip FastAPI)।
  • Complex windowing — "user-এর last 30 sec activity-এর feature live compute"।
  • Sub-100ms end-to-end SLA।
  • Strict exactly-once across joins।

কখন Kafka Streams যথেষ্ট:

  • Microservice-এ Kafka topic-from-topic enrichment।
  • Single team, single language (Java/Kotlin)।
  • No SQL/analytical workload।
  • Simple deduplication, format conversion।

Hybrid pattern (real-world):

  • Kafka Streams microservice-এ — per-event enrichment।
  • Spark Structured Streaming — bronze/silver lake ingestion।
  • Flink — real-time fraud scoring (if Daak Pay scale 10x grow)।

মূল কথা: "Best framework" context-dependent। Latency budget, team skill, integration footprint, operational complexity — সব factor। Wrong choice ৬ মাসে evident, migration ১২+ মাসের project। Right choice — system invisible, just works।

প্র ০৩ Backfill নাইটমেয়ার — ১৮ মাস history-তে নতুন feature compute করতে চান। কী যত্ন নিতে হবে যেন training/serving consistent থাকে?

Backfill ML pipeline-এর সবচেয়ে subtle পরীক্ষা। Naive approach 90% সময় bug-এ শেষ — point-in-time leakage, schema mismatch, scale issue।

Common backfill failure modes:

  • Future leakage: backfill এ "user's lifetime fraud rate" — এই metric backfill row-এর সময় available ছিল না।
  • Schema drift: ১৮ মাস আগে column existed না — naive query null-ful।
  • Time zone bug: UTC vs BST mix — daily aggregate ৬ ঘণ্টা off।
  • Resource explosion: ১ বছরের data daily batch — ৩৬৫x normal load, cluster crash।
  • Incremental contamination: backfill চলাকালীন daily incremental run — race condition।

Best practice — point-in-time correctness:

  • প্রতিটি feature definition-এ timestamp column বাধ্যতামূলক।
  • Feast-এর get_historical_features AS-OF JOIN — entity-এর time-এ feature value lookup।
  • Example: txn at 2024-03-15 14:23:01 — user_velocity feature 2024-03-15 14:23:00-এ যা ছিল।

Step-by-step backfill plan:

  • (১) Schema audit: ১৮ মাসে কী কী schema change? — version history check। Missing column-এর জন্য default rule।
  • (২) Time partitioning: backfill 1-month chunk-এ — independent, retryable। Spark DAG: month1 → month2 → ...।
  • (৩) Resource isolation: backfill পৃথক cluster-এ। Production unaffected।
  • (৪) Idempotency: চক বা partition-level checkpoint। Failure-এ resume।
  • (৫) Validation: recent month-এ backfill output vs incremental output — match হওয়া উচিত। Discrepancy = bug।
  • (৬) Atomic publish: backfilled feature staging table-এ। Sanity check pass করলে production-এ swap।

Tooling:

  • Spark with Iceberg time travel — historical state query।
  • dbt snapshots — slowly-changing dimensions historical version।
  • Delta Lake/Iceberg branching — new feature isolated branch-এ test।
  • Feature store — backfill API explicit, online store overwrite নয়।

Common pitfall — "future-knowledge" features:

  • "Total fraud transactions in user's history" — at time T, only past T fraud known।
  • "Average txn amount last 30 days" — relative to T, not today।
  • "Account age" — at time T, not now। দিন difference।
  • "Was account ever blocked" — block before T only।

Validation strategy:

  • Recent overlap test — last 7 days backfill vs incremental match?
  • Distribution drift — backfilled feature distribution stable across months?
  • Spot check — random ১০০ row manual verify।
  • Model retrain on backfilled — performance not degrade?

মূল কথা: Backfill engineering = data archaeology + careful temporal logic + paranoid validation। ৮০% bug "training works, production fails" — root cause backfill correctness। Feature store-এর primary value এই point-in-time correctness, যা hand-rolled SQL-এ achieve করা নরক।

প্র ০৪ Bangladesh Bank quarterly audit — এই pipeline-এ আপনি কী compliance evidence prepare রাখবেন? আজকে DPA ২০২৩ enforce হলে কী বদলে যেত?

Daak Pay-এ regulatory compliance non-negotiable। Audit fail মানে administrative action, license risk, board-level escalation।

Pre-audit preparation checklist:

(১) Data localization documentation:

  • Architecture diagram — Bangladesh DC primary, Singapore S3 secondary (BB-approved)।
  • Cross-border transfer log — কী data কোথায় গেছে, কেন।
  • Data Processing Agreement (DPA) cloud vendor-এর সাথে।

(২) Access audit trail:

  • প্রতিটি system-এ ৪ quarter-এর login + query log।
  • Privileged access — root, DBA — session recording।
  • Quarterly access review — terminated employee accounts deactivated।
  • JIT access policy — production access auto-expire।

(৩) Transaction integrity:

  • WAL → Bronze immutability proof — Iceberg snapshot history।
  • Checksum validation — source row count = bronze row count।
  • Reconciliation report — Postgres COUNT vs warehouse COUNT, discrepancy explanation।

(৪) AML/STR (Suspicious Transaction Report):

  • Daily STR generation logic documented।
  • Alert investigation log — analyst review, decision rationale।
  • BFIU (Bangladesh Financial Intelligence Unit) submission timestamp।

(৫) Model governance:

  • Model card প্রতিটি version-এর — training data, features, fairness audit।
  • Decision auditability — SHAP value cached, "কেন এই txn block?" explanation।
  • Bias testing — protected demographics-এ outcome parity check।
  • Approval workflow — model promotion human reviewer signed off।

(৬) Incident management:

  • Past quarter-এর incident log — false positive lock-out, system outage, data leak (if any)।
  • Root cause + remediation document।
  • Customer complaint resolution timeline।

(৭) Retention & deletion:

  • ৭-year transaction retention proof — automated lifecycle policy।
  • Right-to-erasure execution log — কেন partial delete (legal hold)।

DPA ২০২৩ (যদি enforce হয়) — pipeline change:

  • Consent management: প্রতিটি PII processing-এর জন্য explicit user consent। Consent withdrawal-এ data flow halt।
  • DPO (Data Protection Officer) role: formal appointment, board reporting।
  • DPIA (Data Protection Impact Assessment): fraud model — high-risk automated decision। Formal assessment, public summary।
  • Pseudonymization: NID, MSISDN — analytics environment-এ tokenized version। Reverse mapping vault-এ।
  • Breach notification: 72-hour reporting to regulator + affected user।
  • Cross-border transfer: stricter controls — Singapore S3 use-এ user consent + adequacy decision।
  • Algorithmic transparency: automated decision (block) — user has right to explanation, human review request।
  • Children's data: minor accounts (where applicable) — extra protection।

Implementation impact:

  • Schema change — consent_state, consent_timestamp প্রতিটি PII record-এ।
  • dbt model — consent-aware filter, withdrawn user-এর data exclude।
  • Feature store — consent-controlled feature serving।
  • Inference API — consent missing হলে graceful degradation (rule-based fallback)।
  • Audit log — consent change immutable trail।

Cost implication:

  • DPO hiring — senior role, monthly ১.৫-৩ lakh।
  • Tokenization vault — additional infrastructure।
  • Legal review — quarterly DPIA।
  • Total: pipeline build cost ১৫-২৫% increase।

মূল কথা: Compliance retrofit একটি rebuild। Day-1 থেকে privacy-by-design, audit-by-design, governance-by-design — সবচেয়ে cheap insurance। যিনি "compliance later" ভাবেন — তিনি ১২ মাস পর double effort-এ rebuild করেন। Bangladesh-এর fintech licensing — compliance-track-record একটি competitive moat।

অনুশীলন

  1. Local প্রজেক্ট: এই pipeline-এর একটি minimal version — Postgres + Kafka + Spark + DuckDB + FastAPI — Docker Compose-এ run করুন। Dummy 1000 transaction generate করে end-to-end flow test করুন।

    একটি starter docker-compose.yml structure:

    services:
      postgres:
        image: postgres:15
        environment:
          POSTGRES_DB: daakpay
          POSTGRES_USER: dev
          POSTGRES_PASSWORD: dev
        command: ["postgres", "-c", "wal_level=logical"]
    
      kafka:
        image: bitnami/kafka:3.6
        environment:
          KAFKA_CFG_NODE_ID: 0
          KAFKA_CFG_PROCESS_ROLES: controller,broker
          KAFKA_CFG_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
    
      debezium:
        image: debezium/connect:2.5
        depends_on: [kafka, postgres]
        environment:
          BOOTSTRAP_SERVERS: kafka:9092
          GROUP_ID: 1
    
      spark:
        image: bitnami/spark:3.5
        volumes: ["./jobs:/jobs"]
    
      api:
        build: ./api
        ports: ["8000:8000"]

    Run: docker compose up -d। Postgres-এ insert → Kafka topic-এ JSON → Spark streaming bronze → silver → API consume।

    Real version GitHub-এ push করুন। DE portfolio-র জন্য সবচেয়ে valuable artifact।

  2. Architecture critique: এই pipeline-এ ৩টি weakness খুঁজুন। কীভাবে improve?

    Weakness ১ — Single Kafka cluster, single Spark cluster: Single point of failure। Multi-AZ deployment + cross-region replication (MirrorMaker 2) দিন।

    Weakness ২ — Daily batch retrain too slow for adversarial fraud: ২৪-hour gap-এ attacker pattern shift। Online learning বা hourly retrain (with drift trigger)। Champion-challenger A/B।

    Weakness ৩ — FastAPI synchronous Redis call blocking: Network blip = latency spike। Async Redis client + circuit breaker + cache fallback (last-known feature value)।

    Bonus — Compliance gap: consent-aware processing এ DPA enforce হলে retrofit। Day-1 design।

  3. Cost estimate: Daak Pay scale 10x বাড়লে (50M daily txn) — bottleneck কোথায় হবে? কোন components এ scale-out দরকার?

    Likely bottlenecks:

    • Kafka: 5M → 50M = 10x throughput। Partition count বাড়ানো (per-topic)। Brokers ৩ → ৯+। Compression (lz4)।
    • Spark Streaming: shuffle bottleneck। Cluster autoscale, partition tuning, SSD-based shuffle storage।
    • Redis online store: 10x QPS — Redis Cluster sharding বাধ্যতামূলক।
    • FastAPI: stateless — horizontal scale easy। Pod 3 → 30। HPA + cluster autoscaler।
    • Iceberg: small file problem — compaction job critical। Per-day micro-partition optimize।
    • dbt: incremental model — partition-pruning aggressive। Warehouse scale up (M → L)।

    Cost projection: linear না — sub-linear (some fixed cost), super-linear (some quadratic — joins)। Roughly $৫,৯০০ → $৪০,০০০-৫০,০০০। FinOps practice critical।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

Capstone hands-on: এই pipeline-এর code repo cloning করে পুরোটা locally চালান। GitHub README-এ steps। Cloud-এ deploy করতে চাইলে Google Colab-এ training notebook চালান, MLflow remote tracking server use করুন।
পূর্ববর্তী পাঠ
পাঠ ২৭ · Cost optimization কৌশল