Skip to content

🧩 Practice Hub Asset Pack — Cross-Track Foundations

🎯 Mục tiêu

Pack này đóng vai trò mẫu chuẩn Markdown-first cho Practice Hub PENALGO:

  • Có thể copy-paste trực tiếp vào VitePress mà không cần backend hay login
  • Mỗi bài đều có metadata tối thiểu để dùng lại trong Local-First practice system
  • Phủ đủ 8 lesson clusters đầu vào bằng một pack tổng hợp, tránh lặp pattern

Hợp đồng block tái sử dụng

Mỗi block trong pack này tuân theo cùng một contract:

Thành phầnBắt buộcMục đích
Header containerXác định loại bài (quiz, parsons, spot-the-bug, scenario-choice, architecture-dragdrop)
Metadata 3 dòngGắn độ khó, thời gian ước tính, khái niệm tiên quyết
Bối cảnh thực chiếnGiữ bài tập bám vào engineering workflow, không thành câu hỏi học vẹt
Lời giải + reasoningGiải thích trade-off, failure mode, và tại sao distractor sai
Tương thích Local-FirstKhông cần server-side grading, không cần tài khoản, không phụ thuộc API riêng

✅ Checklist triển khai

  • Metadata trong từng block giữ đúng thứ tự: Độ khóThời gian ước tínhKhái niệm tiên quyết
  • Distractor phải là phương án “nghe hợp lý” với junior engineer, không phải đáp án ngớ ngẩn
  • Explanation phải trả lời được câu hỏi: “Nếu làm sai ngoài production thì hỏng ở đâu?”
  • Nếu sau này nối sang runtime tương tác, block vẫn tách được thành prompt + answer + explanation
  • Không bài nào yêu cầu state server; tối đa chỉ cần lưu tiến độ trên localStorage

Ma trận cluster → asset trong pack

Lesson clusterAsset trong pack
Penalgo C++ Series Phase 1 (Modules 1–3)1 Quiz
Docker Masterclass1 Spot-the-Bug, 1 Scenario-Choice
Infrastructure & DevOps1 Spot-the-Bug
AI Foundations1 Quiz
System Design foundation1 Scenario-Choice, 1 Architecture Drag & Drop idea
Python Engineering1 Parsons
Algorithms & Data Structures1 Parsons
SQL Mastery1 Quiz

1) ::: quiz blocks

🧠 Quiz — C++ Phase 1 — `std::vector` reallocation và con trỏ treo

  • Độ khó: Trung bình
  • Thời gian ước tính: 4-6 phút
  • Khái niệm tiên quyết: std::vector, object lifetime, reallocation

Bối cảnh: Bạn viết order-matching engine cho game server. Team đang cache Order* trỏ vào phần tử trong std::vector<Order> activeOrders. Sau một đợt traffic burst, một số pointer bắt đầu trỏ vào dữ liệu rác dù code “không hề delete”.

Câu hỏi: Hướng xử lý nào đúng nhất ở cấp độ thiết kế?

  • [x] A) reserve() đủ capacity trước burst, và tránh giữ pointer/reference sống lâu nếu vector còn có thể reallocate
  • [ ] B) Gọi shrink_to_fit() sau mỗi batch để memory luôn gọn và ổn định địa chỉ
  • [ ] C) Đưa std::vector<Order> lên heap bằng new để địa chỉ phần tử không đổi
  • [ ] D) Đổi Order* thành const Order* để compiler chặn invalidation

💡 Giải thích: Vấn đề nằm ở buffer nội bộ của std::vector, không nằm ở chỗ object vector đang ở stack hay heap. Khi vector tăng capacity, nó cấp phát buffer mới rồi move/copy phần tử sang đó; mọi pointer, reference, iterator cũ đều có thể invalid. shrink_to_fit() còn làm tình hình tệ hơn vì nó cũng có thể gây reallocation. const chỉ bảo vệ mutation qua pointer, không bảo vệ lifetime. Trong production, cache pointer “sống lâu hơn vòng đời buffer” là một lỗi 3AM-debug kinh điển.

🧠 Quiz — AI Foundations — Accuracy cao nhưng model vẫn vô dụng

  • Độ khó: Trung bình
  • Thời gian ước tính: 4-5 phút
  • Khái niệm tiên quyết: imbalanced data, confusion matrix, PR-AUC

Bối cảnh: Bài toán fraud detection có tỷ lệ positive chỉ 0.3%. Model mới đạt 99.7% accuracy, nhưng khi audit lại thì recall cho fraud gần như bằng 0.

Câu hỏi: Bước engineering nào nên làm trước tiên?

  • [ ] A) Giữ accuracy làm metric chính rồi train thêm nhiều epoch để model “học sâu hơn”
  • [x] B) Đổi sang precision/recall, PR-AUC và xem lại confusion matrix trên tập validation stratified
  • [ ] C) Tăng learning rate để model bắt được pattern hiếm nhanh hơn
  • [ ] D) Thêm nhiều feature ngẫu nhiên để model có thêm tín hiệu

💡 Giải thích: Với dữ liệu lệch lớp nặng, accuracy rất dễ tạo ảo giác thành công. Một model đoán toàn bộ là negative vẫn có thể đạt accuracy gần 99.7%. Việc cần làm đầu tiên không phải “train mạnh hơn”, mà là đo đúng thứ cần đo: recall, precision, PR-AUC, false negative cost. Nếu chọn sai metric, bạn tối ưu nhầm objective và đẩy rủi ro fraud ra production mà dashboard vẫn báo xanh.

🧠 Quiz — SQL Mastery — Chọn composite index đúng thứ tự

  • Độ khó: Nâng cao
  • Thời gian ước tính: 5-7 phút
  • Khái niệm tiên quyết: composite index, leftmost prefix, range scan

Bối cảnh: Bảng orders có hàng chục triệu dòng. Query nóng nhất là:

sql
SELECT id, total_amount, created_at
FROM orders
WHERE tenant_id = ?
  AND created_at BETWEEN ? AND ?
ORDER BY created_at DESC
LIMIT 50;

Câu hỏi: Index nào hợp lý nhất?

  • [ ] A) CREATE INDEX idx_orders_created_tenant ON orders(created_at DESC, tenant_id);
  • [x] B) CREATE INDEX idx_orders_tenant_created ON orders(tenant_id, created_at DESC);
  • [ ] C) CREATE INDEX idx_orders_tenant ON orders(tenant_id); và một index riêng cho created_at
  • [ ] D) CREATE FULLTEXT INDEX idx_orders_search ON orders(tenant_id, created_at);

💡 Giải thích: Đây là mẫu điển hình cho rule equality trước, range sau. tenant_id = ? giúp thu hẹp partition logic trước; sau đó engine scan theo created_at trong đúng tenant và có thể phục vụ luôn ORDER BY created_at DESC LIMIT 50. Đảo ngược thứ tự index khiến database khó tận dụng prefix hiệu quả. Hai index riêng thường dẫn đến planner phải cân nhắc bitmap/index merge hoặc vẫn sort lại. FULLTEXT hoàn toàn sai bài toán vì đây không phải search text.


2) ::: parsons blocks

🧩 Parsons Problem — Python Engineering — Context manager cho transaction an toàn

  • Độ khó: Trung bình
  • Thời gian ước tính: 6-8 phút
  • Khái niệm tiên quyết: contextmanager, transaction boundary, exception propagation

Nhiệm vụ: Sắp xếp lại các dòng để tạo context manager quản lý cursor. Yêu cầu:

  • Thành công thì commit
  • Có exception thì rollback
  • Dù thế nào cũng phải close cursor

Các dòng bị xáo trộn:

  1. connection.rollback()
  2. @contextmanager
  3. connection.commit()
  4. def managed_cursor(connection):
  5. try:
  6. yield cursor
  7. finally:
  8. cursor = connection.cursor()
  9. except Exception:
  10. cursor.close()
  11. from contextlib import contextmanager
  12. raise
✅ Thứ tự đúng và reasoning
python
from contextlib import contextmanager

@contextmanager
def managed_cursor(connection):
    cursor = connection.cursor()
    try:
        yield cursor
        connection.commit()
    except Exception:
        connection.rollback()
        raise
    finally:
        cursor.close()

Giải thích: commit() chỉ được gọi sau khi phần việc bên trong yield hoàn tất mà không ném exception. Nếu có lỗi, rollback() phải chạy trước rồi re-raise để caller biết transaction thất bại; nuốt exception ở đây là cách nhanh nhất để tạo dữ liệu nửa vời mà không ai phát hiện. finally đảm bảo cleanup tài nguyên ngay cả khi rollback cũng ném lỗi ở tầng trên.

🧩 Parsons Problem — Algorithms & Data Structures — BFS cho shortest path trên graph không trọng số

  • Độ khó: Trung bình
  • Thời gian ước tính: 6-8 phút
  • Khái niệm tiên quyết: queue, visited set, BFS layer-by-layer

Nhiệm vụ: Sắp xếp lại code để trả về độ dài đường đi ngắn nhất từ start tới target trong graph không trọng số.

Các dòng bị xáo trộn:

  1. if neighbor not in visited:
  2. def shortest_path(graph, start, target):
  3. visited = {start}
  4. node, dist = queue.popleft()
  5. queue.append((neighbor, dist + 1))
  6. if node == target:
  7. queue = deque([(start, 0)])
  8. return dist
  9. while queue:
  10. visited.add(neighbor)
  11. return -1
  12. for neighbor in graph[node]:
  13. from collections import deque
✅ Thứ tự đúng và reasoning
python
from collections import deque

def shortest_path(graph, start, target):
    queue = deque([(start, 0)])
    visited = {start}

    while queue:
        node, dist = queue.popleft()
        if node == target:
            return dist
        for neighbor in graph[node]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append((neighbor, dist + 1))

    return -1

Giải thích: Điểm hay bị sai là đánh dấu visited ngay lúc enqueue, không phải lúc dequeue. Nếu chờ tới dequeue mới đánh dấu, cùng một node có thể bị đẩy vào queue nhiều lần qua các cạnh khác nhau, làm nổ memory và phá reasoning về layer. BFS đúng bản chất sẽ duyệt theo từng “vòng tròn khoảng cách”, nên lần đầu gặp target cũng chính là đường đi ngắn nhất.


3) Spot-the-Bug exercises

🐛 Spot-the-Bug — Docker Masterclass — Dockerfile phá cache và tự bắn vào chân lúc build

  • Độ khó: Trung bình
  • Thời gian ước tính: 5-7 phút
  • Khái niệm tiên quyết: build cache, devDependencies, multi-stage build

Bối cảnh: CI phàn nàn Docker build vừa chậm vừa hay fail sau khi team chuyển TypeScript compiler vào devDependencies.

dockerfile
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci --omit=dev
RUN npm run build
CMD ["node", "dist/server.js"]

Câu hỏi: Hãy chỉ ra 2 lỗi thiết kế chính.

✅ Đáp án và cách sửa

Lỗi 1 — COPY . . quá sớm làm vỡ build cache

Mọi thay đổi nhỏ ở source code đều làm invalid cache của npm ci, kéo CI chậm đi đáng kể. Đúng hơn là copy package.json/package-lock.json trước, install deps, rồi mới copy source.

Lỗi 2 — Cắt devDependencies trước bước build

npm run build thường cần TypeScript, esbuild, vite, webpack hoặc các plugin chỉ nằm trong devDependencies. Cắt sớm khiến build fail hoặc build ra artifact không nhất quán.

Cách sửa tối thiểu:

dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
CMD ["node", "dist/server.js"]

Engineering reasoning: Build stage và runtime stage có trách nhiệm khác nhau. Builder cần toolchain đầy đủ; runtime chỉ cần artifact và production deps. Trộn hai phase vào một image thường vừa chậm, vừa to, vừa khó audit CVE.

🐛 Spot-the-Bug — Infrastructure & DevOps — Deploy script “chạy được” nhưng không production-safe

  • Độ khó: Nâng cao
  • Thời gian ước tính: 6-8 phút
  • Khái niệm tiên quyết: least privilege, atomic deploy, health check

Bối cảnh: Một deploy script chạy ổn trên staging nên team định đem thẳng lên production.

bash
#!/usr/bin/env bash
set -e

cd /srv/orders/current
chmod -R 777 .
tar -xzf /tmp/release.tar.gz -C .
systemctl restart orders-api

Câu hỏi: Tìm 3 vấn đề ops có thể biến lần deploy thành incident.

✅ Đáp án và cách sửa

Vấn đề 1 — chmod -R 777 . phá vỡ least privilege

Cho toàn bộ app directory quyền ghi thực thi cho mọi user là mở cửa cho accidental overwrite lẫn privilege abuse. Production không dùng 777 như “băng keo sửa nhanh”.

Vấn đề 2 — Giải nén đè trực tiếp lên thư mục current

Đây là deploy không atomic. Nếu tar extract dở dang hoặc release lỗi, bạn có thể để application ở trạng thái nửa cũ nửa mới và rollback rất khó.

Vấn đề 3 — Restart xong là coi như thành công

systemctl restart thành công chỉ chứng minh command được thực thi, không chứng minh process đã healthy. Không có health check và rollback strategy là invitation cho outage kéo dài.

Hướng sửa production-first:

  1. Giải nén vào release directory mới, ví dụ /srv/orders/releases/2026-03-09-01
  2. Giữ permission tối thiểu cần thiết thay vì 777
  3. Swap symlink current theo kiểu atomic
  4. Chạy health check sau restart; fail thì rollback ngay

Engineering reasoning: Deployment tốt không chỉ “copy file rồi restart”, mà phải tối ưu cho recoverability. Khi hệ thống hỏng lúc 3 giờ sáng, rollback an toàn mới là thứ cứu đội on-call.


4) Scenario-Choice exercises

🎯 Scenario Choice — Docker Masterclass — Giảm image size mà không tạo thêm technical debt

  • Độ khó: Trung bình
  • Thời gian ước tính: 5-7 phút
  • Khái niệm tiên quyết: multi-stage build, runtime hardening, supply-chain hygiene

Bối cảnh: Node.js API hiện có image ~850MB. Cold start chậm, CVE scan rất nhiều, pull image ở edge region mất gần 1 phút. Team cần một bước cải tiến có tác động lớn nhưng không làm pipeline khó maintain hơn.

Bạn chọn hướng nào?

  • [ ] A) Đổi sang ubuntu:latest rồi chạy apt upgrade ở mỗi build để package nào cũng “mới nhất”
  • [x] B) Tách builder/runtime bằng multi-stage, chỉ copy dist + production dependencies sang runtime nhỏ gọn, chạy process bằng non-root user
  • [ ] C) Nén image thành file .tar.gz trước khi push registry để giảm thời gian pull
  • [ ] D) Giữ nguyên Dockerfile, chỉ thêm docker system prune -a ở server sau mỗi deploy

💡 Giải thích: Multi-stage + runtime hardening là thay đổi đúng bản chất kiến trúc image: bỏ build tool khỏi final image, giảm attack surface, giảm thời gian scan và pull. ubuntu:latest làm build kém reproducible hơn. Nén tar trước khi push không thay đổi cách registry phân phối layers. docker system prune chỉ dọn rác ở host, không làm image phát hành tốt hơn.

🎯 Scenario Choice — System Design foundation — Cache strategy cho catalog read-heavy

  • Độ khó: Nâng cao
  • Thời gian ước tính: 6-8 phút
  • Khái niệm tiên quyết: cache-aside, TTL, invalidation, read/write ratio

Bối cảnh: Product catalog service đang chịu 40K read RPS200 write RPS. DB primary bắt đầu nóng. Business chấp nhận dữ liệu stale tối đa 60 giây cho người dùng anonymous.

Thiết kế nào phù hợp nhất ở giai đoạn này?

  • [ ] A) Chỉ dùng in-memory cache trên mỗi pod, không TTL, không invalidation để latency thấp nhất
  • [x] B) Dùng Redis theo mô hình cache-aside, TTL ngắn có jitter, và invalidation có mục tiêu khi write thành công
  • [ ] C) Chuyển toàn bộ write sang cache write-through, coi Redis là source of truth, SQL chỉ để backup
  • [ ] D) Bật sticky session để mỗi user luôn dính vào một pod, tận dụng memory cache của pod đó

💡 Giải thích: Bài toán này read-heavy, write ít, stale tolerance rõ ràng — cache-aside là lựa chọn cân bằng nhất giữa đơn giản và hiệu quả. TTL + jitter giúp tránh cache stampede đồng loạt, còn invalidation theo key giữ dữ liệu nóng tương đối mới. In-memory cache per-pod (A) khó đồng bộ và dễ lệch dữ liệu khi scale out. Redis làm source of truth (C) đẩy thêm complexity vận hành không cần thiết ở giai đoạn này. Sticky session (D) giải quyết sai tầng bài toán.


5) Architecture Drag & Drop idea

🏗️ Architecture Drag & Drop — System Design foundation — Request path cho public catalog read-heavy

  • Độ khó: Trung bình
  • Thời gian ước tính: 8-10 phút
  • Khái niệm tiên quyết: load balancing, cache hierarchy, read replica

Bối cảnh: Bạn thiết kế hệ thống catalog cho người dùng anonymous. Mục tiêu:

  • p95 latency dưới 120ms
  • 95% traffic là read
  • Có thể chấp nhận dữ liệu stale ngắn hạn

Các block cần ghép vào kiến trúc:

  • CDN / Edge Cache
  • L7 Load Balancer
  • Catalog API
  • Redis Cache
  • PostgreSQL Read Replica

Sơ đồ trống:

text
┌────────────┐    ┌────────────────┐    ┌──────────────┐    ┌──────────────┐
│   Client   │───▶│      [Ô A]     │───▶│    [Ô B]     │───▶│    [Ô C]     │
└────────────┘    └────────────────┘    └──────────────┘    └──────┬───────┘


                                                             ┌──────────────┐
                                                             │    [Ô D]     │
                                                             └──────┬───────┘


                                                             ┌──────────────┐
                                                             │    [Ô E]     │
                                                             └──────────────┘

Luật ghép:

  1. Request anonymous nên ưu tiên chặn từ tầng ngoài vào trong
  2. API chỉ nên chạm DB khi cache miss
  3. Database không nên nằm trực tiếp sau load balancer
✅ Gợi ý placement + reasoning

Placement đề xuất:

  1. Ô A sau ClientCDN / Edge Cache
  2. Ô B tiếp theo → L7 Load Balancer
  3. Ô C ở cuối chuỗi request chính → Catalog API
  4. Ô D ngay dưới API → Redis Cache
  5. Ô E cuối cùng → PostgreSQL Read Replica

Flow đúng:

Client → CDN → L7 Load Balancer → Catalog API → Redis Cache → PostgreSQL Read Replica (khi miss)

Engineering reasoning: CDN hấp thụ một phần lớn request anonymous ngay ở edge; L7 LB phân phối traffic tới app tier; Catalog API là nơi gắn auth policy, response shaping, cache key policy; Redis giữ hot objects để giảm tải database; Read replica bảo vệ primary khỏi read storm. Nếu đặt DB gần edge hoặc bỏ qua cache layer, latency và blast radius đều tăng mạnh khi traffic spike.


Cách dùng pack này trong Local-First Practice Hub

✅ Checklist triển khai

  • Có thể copy từng block sang lesson page riêng mà không cần đổi cú pháp
  • Nếu sau này nối với runtime, chỉ cần parse metadata đầu block và phần answer/explanation
  • Không block nào yêu cầu chấm điểm phía server; lời giải đã self-contained qua details
  • Có thể lưu tiến độ đọc/đã làm bằng localStorage, nhưng ngay cả khi không lưu thì nội dung vẫn usable
  • Các block đã cố ý dùng context production để phù hợp workflow “đọc doc rồi luyện nhanh”