প্রজেক্ট: end-to-end ML pipeline
এই পাঠে যা শিখবেন
- কীভাবে 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।
১) 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
৩ · 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 করবে।
-- 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।
{
"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"
}
}
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।
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())
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।
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())
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।
-- 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
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)।
# 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,
)
৯ · Model training — MLflow + XGBoost
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
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"}
১১ · Airflow DAG — orchestration
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
১২ · 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?
১৩ · 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।
১৫ · 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_idnullable হয়েছে — 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_featuresAS-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।
অনুশীলন
-
Local প্রজেক্ট: এই pipeline-এর একটি minimal version — Postgres + Kafka + Spark + DuckDB + FastAPI — Docker Compose-এ run করুন। Dummy 1000 transaction generate করে end-to-end flow test করুন।
একটি starter
docker-compose.ymlstructure: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।
-
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।
-
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-এ আপনার পরবর্তী পদক্ষেপ
- পাঠ ২৯ · কোর্সের চূড়ান্ত পর্যালোচনা পরবর্তী পাঠ ২৯টি পাঠের synthesis — career path, BD market outlook।
- পাঠ ২৭ · Cost optimization কৌশল আগের পাঠ এই pipeline-এর monthly bill কীভাবে $৫,৯০০-এ রাখা — সেই সব technique।
- পাঠ ১৩ · Apache Airflow পরিচিতি এই পাঠের সাথে সম্পর্কিত Airflow basics — এই DAG-এর ভিত্তি।
- সব AI Courses দেখুন ABCL TECH Python, ML, DL, NLP, CV, GenAI, RL, MLOps — সব AI কোর্স একসাথে।