diff --git a/src/data/blog/en/interview/database-senior.md b/src/data/blog/en/interview/database-senior.md index 14bbd39..e831b36 100644 --- a/src/data/blog/en/interview/database-senior.md +++ b/src/data/blog/en/interview/database-senior.md @@ -15,6 +15,67 @@ The database is the layer that actually decides whether your system scales. Inte > Mindset: recite facts and you're mid-level. Walk through a tradeoff with real numbers and a production failure mode, and you've earned the "senior" checkbox. Every section below ends with the drill an interviewer actually runs. +## Interview question ladder (Junior → Mid → Senior) + +> Drill these out loud. Junior = "do you know the concept"; Mid = "do you know the tradeoffs"; Senior = "can you defend a decision under pressure, with a number and a postmortem." + +### Junior — foundations + +- **Q: What's the difference between a clustered and a non-clustered index?** + A: A clustered index _is_ the table — rows are stored in its order (InnoDB's `PRIMARY KEY`), so there's exactly one per table; lookups by PK are one B-tree walk to the row. A non-clustered (secondary) index is a separate B-tree whose leaves point back to the clustered key, so a secondary-index lookup is two hops: index → clustered key → row. + +- **Q: Name the four SQL transaction isolation levels.** + A: Read uncommitted, read committed, repeatable read, serializable — in increasing strictness. They trade concurrency for anomaly prevention. + +- **Q: What does a primary key do that a unique constraint doesn't?** + A: A PK is the clustered key (InnoDB) — it defines physical row order and is non-null + unique. A unique constraint is just a non-clustered uniqueness guarantee; you can have several. + +- **Q: What is a foreign key, and what does `ON DELETE CASCADE` do?** + A: A FK constrains a column to exist in another table's referenced column. `CASCADE` makes deleting the parent delete (or null, with `SET NULL`) the dependent rows — convenient, but a mass delete can lock/cascade harder than you expect. + +- **Q: Why does `SELECT *` hurt?** + A: It pulls every column (more I/O, more network), defeats covering indexes (the index can't satisfy the query alone), and breaks when columns are added/removed. Name the columns you need. + +### Mid — tradeoffs & pitfalls + +- **Q: Why is `WHERE YEAR(created_at) = 2026` a full scan even when `created_at` is indexed?** + A: A function on the column hides it from the B-tree, so the optimizer can't do a range seek — it scans every row, applies the function, filters. Rewrite as a raw-column range: `created_at >= '2026-01-01' AND created_at < '2027-01-01'`. Same trap: `LIKE '%x%'`, arithmetic on the column, implicit casts. + +- **Q: When is a composite index useful, and what's the leading-column rule?** + A: Composite indexes serve queries that filter on a _prefix_ of the columns, left to right (the leftmost-prefix rule). `(a, b, c)` helps `WHERE a=?`, `(a,b)=?`, `(a,b,c)=?` but NOT `WHERE b=?` alone. Put the most selective / most-filtered-leading column first, but also the one that benefits equality predicates. + +- **Q: N+1 query — what is it and how do you kill it?** + A: You fetch N parents, then one query per parent for its children = N+1 round trips. Fix: a single `JOIN`/`IN` batch, or `@BatchSize`/`fetch join` in ORM. The tell: latency that never shows in any single slow-query log because each call is ~1 ms. + +- **Q: Why is a 2000-connection pool worse than a 50-connection one?** + A: Connections are a _bounded_ resource the DB must schedule. Past the DB's `max_connections` every new request times out; more connections also mean more context-switch and lock contention on the DB side. Size by Little's law (`TPS × avg_query_time`), not by box core count. + +- **Q: Read committed vs repeatable read — what anomaly does each still allow?** + A: Read committed still allows _non-repeatable reads_ (same row differs between reads in the same txn). Repeatable read still allows _phantom reads_ (a range query returns different rows). Serializable prevents both — at the cost of concurrency (often via range locks / SSI). + +### Senior — design & defense + +- **Q: Size the connection pool for a service doing 1,000 req/s with 20 ms avg query time. Now what if 10% of calls take 5 s?** + A: `1000 × 0.02s = 20` connections is the steady-state number; `cores × 10` is a fine starting heuristic and HikariCP defaults to 10. But the 10% at 5 s case needs `1000 × 0.1 × 5 = 500` connections _if_ every slow call holds one — which means a handful of slow queries can exhaust the pool and stall the 90% fast path. The senior move is a _separate_ bounded pool (or timeout + circuit breaker) for the slow path so it can't starve the fast one. + +- **Q: "Indexes make everything fast." Defend or refute — with the write-side cost.** + A: Refute. Every index is maintained on every `INSERT`/`UPDATE`/`DELETE`: more B-tree walks, more page splits, more WAL. A write-heavy table with 8 indexes pays 8× the index-maintenance tax and slower inserts. The defense: index for the queries you actually run; drop the vanity indexes; consider a read replica for heavy analytical reads. + +- **Q: A report says "the DB averages 0.1 ms but the app takes 800 ms." Where do you look first?** + A: The _pool_, not the DB. If the thread pool and connection pool both queue, requests wait in line for a connection while the DB sits idle. Check pool saturation, `connectionTimeout`, and whether `wait` time dwarfs `query` time. The fix is rarely "bigger DB." + +- **Q: Walk me through a phantom read appearing in production and how you closed it.** + A: A batch processes "all unpaid orders," another txn inserts a new unpaid order in the same range mid-batch → the batch misses it (or double-counts on retry). Defense: `REPEATABLE READ`/`SERIALIZABLE` with range locks, or `SELECT … FOR UPDATE SKIP LOCKED` to claim rows atomically so concurrent workers don't collide. Name the isolation level and the lock type. + +- **Q: You need to add an index to a 2-billion-row table with zero downtime. How?** + A: Online/DDL tools (`CREATE INDEX CONCURRENTLY` in Postgres; InnoDB online DDL with `ALGORITHM=INPLACE, LOCK=NONE`) build the index without blocking writes — but they still add load and can take hours at scale; do it in a maintenance window, monitor replication lag, and have a rollback. Never `LOCK=TABLE` on a hot table. + +#### Self-check + +- [ ] Junior: explain clustered vs non-clustered, the 4 isolation levels, PK vs unique, FK cascade, why `SELECT *` hurts. +- [ ] Mid: rewrite a function-on-column predicate to a range, state the leftmost-prefix rule, kill an N+1, size a pool with Little's law, name the anomaly each isolation level still allows. +- [ ] Senior: defend connection-pool sizing under a slow-query tail, quantify the write-side index cost, trace a phantom-read incident, and add a 2B-row index online without downtime. + ## 1. Indexing — where interviews go to die "Add an index" is the beginner answer. The senior answer explains why the index is three or four B-tree levels tall, which columns go in which order, why the optimizer still refuses to touch it, and what a hot index costs you on every write you didn't plan for. diff --git a/src/data/blog/en/interview/java-core-senior.md b/src/data/blog/en/interview/java-core-senior.md index 39fce49..381fbdd 100644 --- a/src/data/blog/en/interview/java-core-senior.md +++ b/src/data/blog/en/interview/java-core-senior.md @@ -15,6 +15,67 @@ A junior knows Java syntax. A senior knows **what the JVM is doing, why it behav > Mindset: "it depends, and here's the trade-off" beats reciting facts every time. The moment you answer with a tradeoff, a number, or a postmortem instead of a definition, you've cleared the bar. +## Interview question ladder (Junior → Mid → Senior) + +> Drill these out loud. Junior = "do you know the concept"; Mid = "do you know the tradeoffs"; Senior = "can you defend a decision under pressure, with a number and a postmortem." + +### Junior — foundations + +- **Q: What's the difference between `==` and `.equals()` in Java?** + A: `==` compares references (are they the same object); `.equals()` compares _value_ (overridable). And `Integer.valueOf` caches -128..127, so `==` on boxed integers "works" in that range and bites at 128 — a bug that passes tests and dies in prod. + +- **Q: What are the four pillars of OOP?** + A: Encapsulation, abstraction, inheritance, polymorphism — but a senior can say _why_ each one exists (e.g. encapsulation bounds the blast radius of change), not just the words. + +- **Q: What's the difference between `ArrayList` and `LinkedList`?** + A: `ArrayList` is a growable array — O(1) random access, O(n) insert/delete in the middle; `LinkedList` is a doubly-linked list — O(1) add/remove at ends, O(n) access. Default to `ArrayList` unless you insert/remove constantly at the head. + +- **Q: What does `final` mean on a class / method / variable?** + A: `final` class = no subclass; `final` method = no override; `final` variable = assigned once. A `final` reference can still mutate the object it points to (the reference is fixed, not the state). + +- **Q: What's the difference between `String`, `StringBuilder`, and `StringBuffer`?** + A: `String` is immutable (every concat allocates). `StringBuilder` is mutable and non-synchronized (fast, single-thread). `StringBuffer` is the synchronized twin (use only when shared across threads). In a loop, `StringBuilder` avoids a storm of throwaway `String` objects. + +### Mid — tradeoffs & pitfalls + +- **Q: Why is double-checked locking broken without `volatile`?** + A: The unsynchronized read can observe a _partially constructed_ singleton — the reference store can float before the constructor's writes, so another thread sees a non-null but half-built instance. `volatile` creates the constructor-write → read happens-before edge that closes it. + +- **Q: `i++` on a `volatile int` — is it safe?** + A: No. `volatile` gives visibility + ordering, not atomicity. `i++` is read-modify-write; two threads can both read 41 and both write 42. Use `AtomicInteger` (one CAS) or `LongAdder` under heavy contention (several× faster because it stripes across cells). + +- **Q: What is false sharing and how do you fix it?** + A: Two independent fields on the same 64-byte cache line ping-pong across cores on every write, even in lock-free code. A per-thread `long[]` counter is the classic victim. `@Contended` (JEP 142) pads fields onto separate lines; a coherence miss costs ~100 ns per ping. + +- **Q: `synchronized` vs `ReentrantLock` — when do you reach for which?** + A: Uncontended `synchronized` is nearly free (a mark-word update, ~tens of ns); contended pays a park/unpark into the kernel (microseconds). `ReentrantLock` adds `tryLock(timeout)`, multiple `Condition`s, and fairness — but you must `unlock()` in `finally` and prefer `tryLock(2, SECONDS)` so a stuck lock can't hang the thread. + +- **Q: `CompletableFuture.thenApplyAsync` runs on which pool, and what breaks?** + A: On `ForkJoinPool.commonPool()` (parallelism = cores−1). Blocking work (JDBC, sleep) inside it starves the pool and stalls everything downstream even when the box is idle. Pass an explicit executor sized for the blocking work (or use virtual threads). + +### Senior — design & defense + +- **Q: A service does 2,000 req/s, each blocks ~50 ms in JDBC. Size the thread pool. Now 10% take 5 s — what changes?** + A: `2000 × 0.05 = 100` workers steady-state (Little's law). But the 10%-at-5s tail needs `2000 × 0.1 × 5 = 1000` workers _if_ every slow call holds one — so a few slow queries exhaust the pool and stall the fast 90%. Senior move: bound the pool, use `CallerRunsPolicy` for backpressure, and isolate the slow path on its own executor with a deadline. + +- **Q: When do virtual threads help, when not, and what still pins a carrier after JDK 24?** + A: They help I/O-bound blocking (millions of concurrent HTTP/DB calls). They don't help CPU-bound work (still N CPUs). After JEP 491 (JDK 24) `synchronized` no longer pins; residual pinning is native frames (JNI/FFM), class loading, and local file I/O on Linux. A `ReentrantLock` never pinned — `LockSupport.park` unmounts the virtual thread. + +- **Q: You see `OutOfMemoryError: Metaspace` after a redeploy with no class added. First command?** + A: `jcmd VM.native_memory` and `-XX:MaxMetaspaceSize`. The cause is a classloader leak: something roots the old loader (static field, JDBC driver in `DriverManager`, cached proxy) so metadata never unloads. A few MB per redeploy becomes 2 GB after a hundred deploys. + +- **Q: A hot method is still slow after 10 minutes of traffic. What's the JIT possibly doing, and how do you prove it?** + A: Tiered compilation (C1→C2) with OSR; it may be **megamorphic** (too many receiver types to inline) or stuck recompiling past `-XX:CompileThreshold`. Prove with `-Xlog:jit+compilation=debug` — you'll see recompilation churn, not "we need a bigger box." + +- **Q: `SELECT * FROM orders WHERE YEAR(created_at)=2026` is slow though indexed — wait, that's SQL. What's the Java analog that bites just as hard?** + A: Calling a method inside a loop that does a `findById` per element (N+1), or `computeIfAbsent` recursively on the same key (Java 8 deadlocks the bin). Both hide O(n²) / deadlock behind innocent-looking loops — the senior tells are "where does it hide," not "does it exist." + +#### Self-check + +- [ ] Junior: `==` vs `.equals`, the four pillars, `ArrayList` vs `LinkedList`, `final`, `String` vs `StringBuilder`. +- [ ] Mid: fix DCL with `volatile`, why `volatile int++` isn't safe, false sharing + `@Contended`, `synchronized` vs `ReentrantLock`, the `commonPool` trap. +- [ ] Senior: size a pool with Little's law under a slow tail, state when virtual threads help/pin, diagnose a Metaspace leak, prove a JIT issue from a log, name the Java-side N+1 / `computeIfAbsent` traps. + ## 1. Heap, GC, and the pause math Expect: "What happens when you `new` an object?" A mid answer stops at "it goes on the heap." A senior talks about **where**, **how fast**, and **what the pause costs** — because that's what actually bites in production. Every question in this section has a numeric answer; interviewers listen for the number, not the noun. diff --git a/src/data/blog/en/interview/kafka-senior.md b/src/data/blog/en/interview/kafka-senior.md index d7d90d1..4ce5755 100644 --- a/src/data/blog/en/interview/kafka-senior.md +++ b/src/data/blog/en/interview/kafka-senior.md @@ -17,6 +17,67 @@ A junior knows the three delivery semantics. A senior can narrate the exact inst > Mindset: recite semantics and you're mid-level. Walk through a tradeoff with real numbers and a production failure mode, and you've earned the "senior" checkbox. Every section below ends with the drill an interviewer actually runs. +## Interview question ladder (Junior → Mid → Senior) + +> Drill these out loud. Junior = "do you know the concept"; Mid = "do you know the tradeoffs"; Senior = "can you defend a decision under pressure, with a number and a postmortem." + +### Junior — foundations + +- **Q: What are the three delivery semantics in Kafka?** + A: At-most-once (may lose), at-least-once (may duplicate), exactly-once (no loss, no dup). Producers default to at-least-once; consumers must dedupe or make processing idempotent to approach exactly-once. + +- **Q: What's a topic, partition, and consumer group?** + A: A topic is a log; it's split into partitions (each an ordered, immutable sequence). A consumer group is a set of consumers sharing the work — each partition is consumed by exactly one member of the group. + +- **Q: What does a consumer offset represent?** + A: The position of the next record to read in a partition. Committed offsets let a consumer resume after a restart or rebalance. `auto-commit` commits periodically; manual commit gives you control over the consume-process-commit boundary. + +- **Q: What's the difference between a queue and a topic?** + A: A traditional queue delivers each message to one consumer; a Kafka topic broadcasts to all consumer groups that subscribe. That's why one topic can feed analytics, audit, and the core service at once. + +- **Q: What does `acks=all` mean?** + A: The producer waits for the leader _and_ all in-sync replicas to acknowledge the write before considering it successful — stronger durability, at the cost of latency. `acks=1` waits only for the leader; `acks=0` fires and forgets. + +### Mid — tradeoffs & pitfalls + +- **Q: `enable.idempotence=true` — does it protect the Postgres write on the other side of my consumer?** + A: No. Idempotence only de-dupes producer→broker retries within Kafka. Once your consumer writes to Postgres, a redelivery (crash before offset commit) writes again. Exactly-once end-to-end needs an idempotent _sink_ (upsert by key) or transactional outbox. + +- **Q: A single poison record silently stalls one partition for hours while dashboards stay green. Why, and the fix?** + A: The consumer throws on that record, never commits the offset, and Kafka redelivers it forever — a 1-record poison blocks the whole partition. Fix: a dead-letter queue (route the bad record after N retries) and alert on consumer lag, which is the metric that actually shows the stall. + +- **Q: How do you pick partition count?** + A: By throughput and consumer parallelism, not a guess: `partitions ≈ max(target_producer_MBps / per_partition_MBps, target_consumer_instances)`. More partitions = more parallelism but also more open files, more rebalances, and longer election if a broker dies. + +- **Q: What causes a rebalance storm, and why does it freeze your queues?** + A: Consumers repeatedly join/leave the group (slow poll, long GC pause, heartbeat timeout), triggering a rebalance that revokes all partitions, pauses consumption, and reassigns. Fix: tune `session.timeout.ms`/`heartbeat.interval.ms`, keep poll loops fast, and use cooperative rebalancing (incremental) where possible. + +- **Q: Consumer lag is climbing — where do you look first?** + A: Whether it's a _throughput_ problem (the consumer can't keep up — add instances/partitions) or a _processing_ problem (each record is slow — a slow downstream call). Lag per partition tells you if it's one hot partition or global. The metric that proves the _consumer_, not the broker, is the bottleneck is consumer lag vs broker CPU. + +### Senior — design & defense + +- **Q: You need exactly-once across "Kafka → consume → write Postgres." Design it.** + A: Either (a) transactional outbox in Postgres + a relay that publishes to Kafka atomically with the business write (the DB is the source of truth), or (b) idempotent sink: consumer upserts by a deterministic key and commits the offset in the same local transaction. `enable.idempotence` + `ack=all` covers the producer; the sink covers the consumer. Name which you picked and why. + +- **Q: A partition leader election took 30 s and every dashboard around it stayed green. Explain.** + A: Broker failure triggers leader election for that partition's replicas; until a new ISR leader is elected, that partition is unavailable for writes — but other partitions and other services are fine, so global dashboards look healthy. The tell is per-partition unavailability + producer timeouts, not a system-wide red. Fix: more replicas, faster `election.timeout`, and producers that retry with backoff. + +- **Q: Size a cluster for 50 MB/s ingest with 3-day retention at 1 KB records. How many brokers?** + A: 50 MB/s × 3 days = ~13 TB raw; ×replication factor 3 = ~39 TB, ÷usable-per-broker (say 5 TB) ≈ 8 brokers minimum, plus headroom for rebalancing. Throughput per broker is ~hundreds of MB/s, so brokers are disk/retention-bound here, not CPU. State the assumption and the knob you'd watch (disk, not cores). + +- **Q: Walk me through a duplicate-payment incident caused by a rebalance and how you closed it.** + A: Consumer processed a payment, crashed before committing the offset, rebalance reassigned the partition, redelivery processed it again. Close it with idempotent processing (dedupe by `paymentId` in a unique DB constraint) so the redelivery is a no-op. The postmortem: offset-commit timing, not Kafka itself, was the bug. + +- **Q: When would you NOT use Kafka for this?** + A: For request/response or low-latency RPC, a queue/topic adds a hop and at-least-once semantics you must design around. For a single-producer/single-consumer with tight latency, a direct call or a lighter broker may be simpler. Kafka earns its keep with fan-out, replay, and decoupling at scale — name the case where it's overkill. + +#### Self-check + +- [ ] Junior: the 3 semantics, topic/partition/consumer-group, what an offset is, queue vs topic, `acks=all`. +- [ ] Mid: why idempotence doesn't protect the sink, poison-record+DLQ, partition sizing math, rebalance-storm cause, lag-as-the-metric. +- [ ] Senior: design exactly-once end-to-end, explain a 30 s leader election, size a cluster by retention, trace a duplicate-payment rebalance incident, name when Kafka is the wrong tool. + ## 1. The log is the product — partitions, offsets, order Kafka is not a message queue that happens to be fast. It's a **distributed, immutable, append-only commit log**. That framing is the whole senior answer: everything else — consumer groups, retention, even "exactly once" — is a consequence of the log, not a feature bolted on top. diff --git a/src/data/blog/en/interview/microservices-senior.md b/src/data/blog/en/interview/microservices-senior.md index 4350651..d22d165 100644 --- a/src/data/blog/en/interview/microservices-senior.md +++ b/src/data/blog/en/interview/microservices-senior.md @@ -15,6 +15,67 @@ Microservices interviews test judgment more than knowledge. The most senior answ > Mindset: a junior lists patterns; a senior narrates failure modes. When you answer with a number, a postmortem, or "here's the tradeoff and when I'd flip it," you've cleared the bar. +## Interview question ladder (Junior → Mid → Senior) + +> Drill these out loud. Junior = "do you know the concept"; Mid = "do you know the tradeoffs"; Senior = "can you defend a decision under pressure, with a number and a postmortem." + +### Junior — foundations + +- **Q: What's the difference between a monolith and microservices?** + A: A monolith is one deployable handling all domains; microservices are independently deployable services split by business capability, each with its own data. The tradeoff is team autonomy + independent scaling vs distributed complexity. + +- **Q: What is the single most important rule about service databases?** + A: Each service owns its data and exposes it only through its API — no shared database. Shared DBs quietly couple services and turn a "micro" architecture into a distributed monolith. + +- **Q: What's an API gateway for?** + A: It's the front door: routing, auth, rate limiting, and aggregation in one place, so individual services don't each reimplement cross-cutting concerns. (Though over-centralizing logic in the gateway is its own trap.) + +- **Q: What's service discovery?** + A: How services find each other's network locations at runtime (registry like Consul/Eureka, or DNS-based). Without it, you hardcode addresses and can't scale or relocate instances. + +- **Q: Synchronous vs asynchronous communication — what's the difference?** + A: Sync (HTTP/gRPC) waits for a response; the caller is blocked. Async (message/event) fires and moves on; the consumer processes later. Async decouples and absorbs spikes, but adds eventual-consistency reasoning. + +### Mid — tradeoffs & pitfalls + +- **Q: What is a distributed transaction and why is 2PC usually rejected?** + A: 2PC (two-phase commit) tries to make a cross-service write atomic, but it holds locks across services and fails badly under partial failure — the classic "distributed transaction is a latency and availability bomb." The senior answer is SAGA + outbox + eventual consistency. + +- **Q: What's the SAGA pattern and when do you use it?** + A: A SAGA is a sequence of local transactions, each with a compensating action to undo the previous step on failure. Use it when you must keep multiple services consistent without 2PC. Tradeoff: you accept _eventual_ consistency and must handle compensations and out-of-order events. + +- **Q: What's the outbox pattern and why do you need it?** + A: Write the business change and the event to publish in the _same_ local DB transaction (an "outbox" table), then a relay publishes the event. It solves the dual-write problem (DB committed, but the message broker call failed → lost event, or vice versa → duplicate). The relay makes the event eventually consistent. + +- **Q: What's the circuit breaker, and what are its states?** + A: It wraps a failing downstream call and trips open after a threshold of errors, failing fast instead of piling up threads. States: closed → open (reject) → half-open (probe one call). Without it, one slow dependency cascades into a full outage (the "all my dependencies are healthy but I'm down" incident). + +- **Q: What's a distributed monolith and how do you recognize it?** + A: Services that can't be deployed or scaled independently because they share a DB, call each other synchronously in request paths, or block on each other's deploys. Tell: you can't ship one service without coordinating a release train. The cure is real bounded contexts + async where possible, not more boxes. + +### Senior — design & defense + +- **Q: "Design a microservice." What's the most senior first sentence?** + A: Often "don't, yet" — or "which slice of the monolith do we carve first, and how do we keep shipping during the carve?" Nobody gets points for drawing boxes. The senior move is sequencing the extraction so each step is independently deployable and rollback-safe. + +- **Q: A downstream payment service is slow and now YOUR service is timing out and OOMing. Walk the incident.** + A: No timeout + no circuit breaker → your threads block on the slow call, the pool fills, requests queue, the heap fills with waiting contexts → cascade. Fix: per-call deadline (`tryLock`/HTTP timeout), circuit breaker to fail fast, bulkheads so one dependency can't consume all threads, and backpressure. Name the exact knob. + +- **Q: You need cross-service consistency for "reserve seat + charge card." Design it without 2PC.** + A: SAGA: reserve seat (local txn + event) → charge card (local txn + event) → if charge fails, compensate by releasing the seat. Outbox on each step so events are reliable. Idempotent handlers (events can redeliver). State the consistency window and what the user sees during it. + +- **Q: How do you keep a rolling deploy safe when services depend on each other's new APIs?** + A: Backward-compatible changes first (add, don't break), consumer-tolerant parsing, and contract tests in CI. Deploy the _provider_'s compatible change, then the _consumer_'s new call. Blue-green or canary so a bad deploy affects a slice, not everyone. DB migrations are forward/backward compatible (additive columns, no destructive rename until unused). + +- **Q: When would you deliberately NOT split a service?** + A: When the cost of the distributed system (network, consistency, ops, tracing) outweighs the benefit — a cohesive domain that changes together should stay one deployable. Splitting for "scalability" a service that's CPU-light is a fake win that multiplies failure modes. Judgment > dogma. + +#### Self-check + +- [ ] Junior: monolith vs microservices, database-per-service, what a gateway/discovery is, sync vs async. +- [ ] Mid: why 2PC is rejected, SAGA + compensation, the outbox pattern, circuit-breaker states, recognize a distributed monolith. +- [ ] Senior: "don't, yet" as the first answer, narrate a cascade incident with the exact fix, design a SAGA for a real flow, safe rolling-deploy sequencing, argue when NOT to split. + ## 1. The distributed-monolith trap Premature decomposition gives you **network calls instead of method calls**, distributed transactions, and 10× operational cost with none of the benefit. Every monolith you split pays an upfront tax: the network. A same-DC HTTP round trip is **~0.1–0.5 ms**; an in-process method call is **~1 ns**. You are voluntarily moving 2–3 orders of magnitude slower and calling it architecture. diff --git a/src/data/blog/en/interview/oop-senior.md b/src/data/blog/en/interview/oop-senior.md index c857856..1c37720 100644 --- a/src/data/blog/en/interview/oop-senior.md +++ b/src/data/blog/en/interview/oop-senior.md @@ -15,6 +15,67 @@ Object-oriented programming is the entry ticket. A junior recites "a class is a > Mindset: when the interviewer says "your team needs a new feature," the senior response is never "add a branch." It's "which axis of change is this — and what do I build that I won't have to edit tomorrow?" Definitions pass juniors; **decisions under constraints** clear the senior bar. +## Interview question ladder (Junior → Mid → Senior) + +> Drill these out loud. Junior = "do you know the concept"; Mid = "do you know the tradeoffs"; Senior = "can you defend a decision under pressure, with a number and a postmortem." + +### Junior — foundations + +- **Q: What does each SOLID letter stand for?** + A: S — Single Responsibility, O — Open/Closed, L — Liskov Substitution, I — Interface Segregation, D — Dependency Inversion. A junior can recite them; a senior can point at the code that violates one and the cost of fixing it. + +- **Q: What's the difference between an interface and an abstract class?** + A: An interface is a pure contract (no state, multiple inheritance); an abstract class can hold state and provide partial implementation (single inheritance). Use an interface to define a role; an abstract class to share code among close relatives. + +- **Q: What's the difference between inheritance and composition?** + A: Inheritance = "is-a" (shares the parent's implementation); composition = "has-a" (holds an instance and delegates). Composition is usually favored because it's more flexible and less coupled. + +- **Q: What is polymorphism, in one sentence?** + A: One interface, many implementations — the caller writes against the abstraction and the runtime picks the concrete behavior (method override, or interface dispatch). + +- **Q: What are the four pillars of OOP again — and which one does `private` belong to?** + A: Encapsulation, abstraction, inheritance, polymorphism. `private`/`protected` are encapsulation — hiding state behind a controlled surface so change stays local. + +### Mid — tradeoffs & pitfalls + +- **Q: Why is "favor composition over inheritance" more than a slogan?** + A: Inheritance couples you to the parent's implementation and breaks when requirements cross-cut (a class needs two behaviors from two parents — but Java has single inheritance). Composition lets you swap a behavior at runtime via an injected dependency. The trap: a deep hierarchy where every change ripples up and down the tree. + +- **Q: Tell me a concrete Open/Closed violation and the fix.** + A: A `InvoiceCalculator` with `if (type == PDF) … else if (type == XLSX)` — every new format edits the class (not closed for modification). Fix: a `Renderer` interface + one class per format, selected by a map. Now adding a format means adding a class, not editing existing code. + +- **Q: What's the Liskov violation people actually ship?** + A: A subclass that strengthens a precondition or weakens a postcondition — e.g. `Square extends Rectangle` but `setWidth` must also set height, breaking the rectangle contract callers rely on. The fix is usually "don't force the IS-A" — model them as siblings under a common abstraction instead. + +- **Q: Why is a "fat interface" a problem, and what's the fix?** + A: An interface with 12 methods forces every implementer to stub behavior it doesn't need (the `RemoteControl` with `startCar` on a `ToyCar`). Fix: split into role interfaces (`Printable`, `Scannable`) so clients depend only on what they use — Interface Segregation. + +- **Q: Dependency Inversion — what's the difference between it and "depend on abstractions"?** + A: DIP says high-level modules shouldn't depend on low-level ones; both depend on abstractions, and the binding happens at the edge (constructor injection). The win: you can swap the Postgres repo for an in-memory one in a test without touching the service. Without it, business logic is welded to the DB driver. + +### Senior — design & defense + +- **Q: "Add CSV export to the report." Where does the code go, and what do you refuse to do?** + A: Refuse the `if/else` in the existing class (OCP violation). Add a `ReportExporter` interface, a `CsvExporter` implementation, register it, and inject/select by format. The senior tell: I know _which axis of change_ this is (output format) and I isolate it so the next format is additive, not invasive. + +- **Q: You inherited a 6-level inheritance tree that nobody understands. What do you do — rewrite it or leave it?** + A: Don't rewrite on day one. First, characterize behavior with characterization tests so I can refactor without silent breakage. Then flatten the risky parts to composition incrementally, behind those tests, one subclass at a time. A big-bang rewrite of working code is how you create a worse incident. + +- **Q: When is inheritance actually the right call over composition?** + A: When there's a genuine "is-a" with shared _implementation_ that won't diverge — e.g. `BaseEntity` with id/version/audit fields, or a `Template Method` where the skeleton is stable and only steps vary. Forgive the coupling because the abstraction is stable. Name the case where composition would be ceremony. + +- **Q: Defend "interface for every dependency" — and where it becomes cargo-cult.** + A: An interface per dependency is great when there are two implementations or you test against a fake. It's cargo-cult when a class has one caller and zero alternate implementations — you've added a layer of indirection for no benefit. Senior judgment: introduce the seam when the second implementation (or the test) actually appears, not preemptively. + +- **Q: Walk me through a design that "followed SOLID" but was awful to work in.** + A: A `UserService` split into 14 tiny classes behind 14 interfaces — every change touched six files, and the "abstractions" had one implementation each (ceremony, not engineering). The lesson: SOLID serves changeability and testability, not file count. I'd collapse the single-impl interfaces and keep only the seams that earn their keep. + +#### Self-check + +- [ ] Junior: SOLID letters, interface vs abstract class, inheritance vs composition, polymorphism, what `private` belongs to. +- [ ] Mid: why composition-over-inheritance, a real OCP violation + fix, a shipped LSP break, fat-interface fix, DIP vs "depend on abstractions." +- [ ] Senior: where new code goes without violating OCP, how to safely refactor a deep hierarchy, when inheritance is right, interface-cargo-cult, a SOLID-but-awful design postmortem. + ## 1. SOLID — the applied version, with the traps "Define SOLID" is a screen for juniors. Seniors get asked to apply it, then to defend the places where applying it naively is wrong. Walk all five, but be ready to go deeper on the three that actually bite in production: Open/Closed, Dependency Inversion, and Liskov. diff --git a/src/data/blog/en/interview/senior-mindset-senior.md b/src/data/blog/en/interview/senior-mindset-senior.md index f256557..154d4da 100644 --- a/src/data/blog/en/interview/senior-mindset-senior.md +++ b/src/data/blog/en/interview/senior-mindset-senior.md @@ -17,6 +17,67 @@ Think of it as the difference between a line cook who can follow a recipe and th > Mindset: recite a framework and you're mid-level. Walk through a tradeoff with real numbers, a production failure mode, and an honest "I'd measure before I'd commit," and you've earned the senior checkbox. Every section ends with the drill an interviewer actually runs. +## Interview question ladder (Junior → Mid → Senior) + +> Drill these out loud. Junior = "do you know the concept"; Mid = "do you know the tradeoffs"; Senior = "can you defend a decision under pressure, with a number and a postmortem." + +### Junior — foundations + +- **Q: Why do behavioral interviews exist — what are they really testing?** + A: Not whether you're "nice," but whether you can own ambiguity, communicate trade-offs, and make the people around you better. The technical loop proves you _can_ do the work; the behavioral loop decides if you're safe to point at production at 2 a.m. + +- **Q: What's the difference between a junior and a senior mindset in one line?** + A: A junior is handed a task and executes it. A senior is handed a _problem_ and owns the outcome — they question the premise, scope the risk, and communicate what they'd trade to hit the deadline. + +- **Q: What's a "trade-off" and why do interviewers love the word?** + A: Every technical choice has a cost somewhere else (latency vs consistency, speed vs correctness, simplicity vs flexibility). Naming the trade-off proves you understand the system, not just the feature. + +- **Q: What does "ownership" mean to you?** + A: It means the work isn't done when the code merges — it's done when it's correct in production and the next person can operate it. You write the runbook, you watch the dashboards, you answer the 2 a.m. page. + +- **Q: Why is "I don't know" a valid senior answer?** + A: Because a senior who guesses and commits is more dangerous than one who says "I'd measure before I'd decide." Honest uncertainty with a plan to resolve it beats confident bluffing that ships a bug. + +### Mid — tradeoffs & pitfalls + +- **Q: Tell me about a time you made a mistake. (The classic.)** + A: Pick a real one with a clear arc: what happened → what you missed → what you changed (monitoring, a test, a process). The trap is blaming a teammate or describing a mistake with no lesson. The senior tell is the _system_ fix, not "I was more careful." + +- **Q: How do you push back on a deadline you think is unrealistic?** + A: With data, not emotion: here's the scope, here's the risk if we cut the test, here are three options (ship partial / slip date / add a person). Offer the trade-off and let the business choose — don't just say "no" or silently miss it. + +- **Q: How do you handle a junior who keeps breaking the build?** + A: Not by shaming. Pair once, add a pre-push check or a CI gate they can't bypass, and make the failure cheap and local. The senior move is fixing the _system_ (the guardrail), not the _person_. + +- **Q: "Walk me through a hard decision you made." What makes a good answer?** + A: A real decision with a real cost — you picked X, accepted Y as the downside, and stated the metric you'd watch to know if you were wrong. Vague "I decided to refactor" stories with no downside named read as mid-level. + +- **Q: How do you communicate bad news (an outage, a slipped date) to non-engineers?** + A: Directly, early, with the impact and the plan — not jargon, not hiding. "Search is degraded for ~5% of users, we've isolated it to the indexer, ETA 30 min, here's the customer-facing message." Calm and specific beats "we're working on it." + +### Senior — design & defense + +- **Q: Your team is stuck between "ship Friday" and "do it right." You're the senior — what do you actually do?** + A: Refuse the false binary. Slice the work: ship the safe 80% Friday, flag the risky 20% as a tracked follow-up with an owner and a date, and be explicit about the debt you're taking on. The decision is documented, not whispered. Name the metric that would make you refuse to ship. + +- **Q: Two seniors disagree on architecture in front of the team. How do you handle it?** + A: Make it a decision, not a debate: each states the trade-off, you time-box the argument, and you decide (or escalate with a clear recommendation). A team that watches leaders argue indefinitely learns that consensus is optional and ships nothing. The senior owns the call and explains the _reason_, not just the verdict. + +- **Q: You inherited an on-call rotation where everyone is burned out. Fix it.** + A: Treat it as a systems problem: are the pages real or noisy? Add alert tuning + runbooks so 2 a.m. pages are actionable. Share the load, cap consecutive shifts, and — most importantly — fix the top-repeat offender so the volume drops. Burnout is usually a _signal of a bad system_, not weak engineers. + +- **Q: "Tell me about a time you leveled up a teammate." Prove it.** + A: A concrete story: you saw a gap (they feared the deploy process), you paired/shadowed/rote a runbook, and six weeks later they owned it solo. The senior bar isn't "I'm smart" — it's "the people around me got better because of how I worked." + +- **Q: A stakeholder asks for a feature that's a bad idea. What do you say?** + A: "Here's what I'd build instead and why — the risk I see in the original ask is X, with this number behind it. If you still want the original, I'll build it, but I want you to see the trade-off first." You respect the decision while making the cost visible. That's senior, not subservient. + +#### Self-check + +- [ ] Junior: why behavioral exists, junior vs senior in one line, what a trade-off is, what ownership means, why "I don't know" is valid. +- [ ] Mid: a real mistake-with-arc, how to push back with data, fixing the build-breaker via guardrails, a hard-decision story with a downside, communicating bad news clearly. +- [ ] Senior: refuse the ship-vs-right binary with a slice, resolve a leadership disagreement as a decision, fix on-call burnout as a system, prove you leveled up someone, surface a bad-idea trade-off respectfully. + ## 1. Narrate trade-offs — the shape of a senior answer A senior doesn't answer "which is better?" with a name. They say: _"it depends — here are the trade-offs, and given X I'd pick Y because…"_ That sentence is the entire interview distilled. The interviewer is not grading your pick; they're grading the _shape_: do you know what each option costs, and can you tie the choice to a concrete constraint? diff --git a/src/data/blog/en/interview/spring-boot-senior.md b/src/data/blog/en/interview/spring-boot-senior.md index bd1764e..a579d9e 100644 --- a/src/data/blog/en/interview/spring-boot-senior.md +++ b/src/data/blog/en/interview/spring-boot-senior.md @@ -15,6 +15,67 @@ Most senior Java backend roles are Spring Boot roles. Interviewers expect you to > Mindset: name the annotation and you're mid-level. Walk through the proxy internals with a production failure mode and the numbers, and you've cleared the bar. Every section below ends with the drill an interviewer actually runs. +## Interview question ladder (Junior → Mid → Senior) + +> Drill these out loud. Junior = "do you know the concept"; Mid = "do you know the tradeoffs"; Senior = "can you defend a decision under pressure, with a number and a postmortem." + +### Junior — foundations + +- **Q: What is IoC / DI, in plain words?** + A: Inversion of Control means the framework creates and wires your objects instead of you `new`-ing them. Dependency Injection is the mechanism — dependencies are passed in (constructor) rather than fetched. The container owns the lifecycle; you declare what you need. + +- **Q: What's the difference between `@Component`, `@Service`, `@Repository`, `@Controller`?** + A: They're all stereotypes that register a bean; the specific one is semantic + adds behavior (`@Repository` translates JDBC exceptions to Spring's `DataAccessException`). Use the right one so the intent is clear and AOP applies correctly. + +- **Q: What is `@Autowired` and what's the modern alternative?** + A: `@Autowired` injects a bean by type (field/setter/constructor). The modern default is **constructor injection** (no annotation needed on a single-constructor class) — it's testable, immutable, and fails fast at startup if a dependency is missing. + +- **Q: What does `@SpringBootApplication` do?** + A: It's three annotations in one: `@Configuration` (beans), `@EnableAutoConfiguration` (magic defaults from the classpath), and `@ComponentScan` (find beans in the package tree). That's why dropping a starter on the classpath turns on a feature. + +- **Q: What's the difference between `@RequestParam` and `@PathVariable`?** + A: `@RequestParam` binds a query param (`?id=5`); `@PathVariable` binds part of the URL path (`/users/5`). Mixing them up is a common junior bug — and `@PathVariable` is how you build REST resource URLs. + +### Mid — tradeoffs & pitfalls + +- **Q: Why does `@Transactional` silently not work on a self-invocation?** + A: The transaction advice is a _proxy_ around the bean; an internal `this.method()` call bypasses the proxy, so no transaction starts. Fix: move the method to another bean, or inject a self-proxy (`AopContext`) — but the real fix is structure, not tricks. This is the #1 "why isn't my rollback happening" bug. + +- **Q: Explain the bean lifecycle in two sentences an interviewer believes.** + A: Instantiate → populate dependencies (inject) → run `BeanPostProcessor`s (e.g. `@PostConstruct`, the ones that apply AOP proxies) → ready. The "two kinds of post-processors" are the key: some configure beans, some wrap them in proxies — and AOP only works because the proxy is applied at that step. + +- **Q: What's the difference between `@Transactional(propagation=...)` settings people actually need?** + A: `REQUIRED` (join or create — the default), `REQUIRES_NEW` (always a new txn, suspends the outer — use for audit logs that must survive the outer rollback), and `NOT_SUPPORTED` (run without a txn). The trap: `REQUIRES_NEW` for an inner call that _should_ roll back with the outer silently commits. + +- **Q: Why is field injection (`@Autowired` on a field) frowned upon?** + A: It's untestable (you can't pass a mock without reflection), mutable (the field can be reassigned), and hides required dependencies. Constructor injection makes the contract explicit and the object valid from construction — immutability by default. + +- **Q: What's the difference between `@Controller` and `@RestController`?** + A: `@Controller` returns a view name (server-rendered); `@RestController` is `@Controller` + `@ResponseBody` — it serializes the return value (JSON) straight to the body. For an API, `@RestController` is the day-to-day choice. + +### Senior — design & defense + +- **Q: A transaction held a DB connection while it called a slow partner API — the pool emptied and the whole service fell over. Walk it.** + A: `@Transactional` by default wraps the _entire_ method, including the HTTP call, so the connection is pinned for the partner's latency. Fix: keep the transaction tight — load data, commit, _then_ call the API (or do it in a separate, non-transactional method). Size the pool by `rps × hold_time` and isolate the slow call on its own pool with a deadline. + +- **Q: Auto-configuration "magically" turned on something you didn't want. How do you find and disable it?** + A: `spring-autoconfigure-metadata` + `Condition`s decide what's on; `spring.autoconfigure.exclude` disables a specific one, and `@ConditionalOnMissingBean` is why your own bean overrides the default. The senior move is to read the starter's auto-config class, not to guess — and to prefer explicit config for anything security-sensitive. + +- **Q: You have 200 `@Bean` methods and startup is 40 s. How do you cut it?** + A: Lazy initialization (`spring.main.lazy-initialization=true`) defers bean creation until first use; component-scan scopes kept tight; and look for beans doing I/O at construction (a connection tested in a `@PostConstruct` is a startup tax). Be honest: 40 s might be acceptable for a monolith — measure before optimizing. + +- **Q: `@Async` methods aren't running async. Why, and the fix?** + A: `@Async` needs a proxy _and_ an `@EnableAsync` config with a task executor; a self-invocation bypasses the proxy (same trap as `@Transactional`), and the default executor is a single-thread `SimpleAsyncTaskExecutor` (not pooled — it spawns a thread per call). Fix: enable it, inject a real `ThreadPoolTaskExecutor`, and call from another bean. + +- **Q: Design a `@RestController` for a money transfer — what cross-cutting concerns do you NOT skip?** + A: Idempotency key (duplicate POST = one transfer), input validation (`@Valid`), authorization (is this user allowed?), a transaction boundary that does _not_ include the notification call, structured logging with a trace id, and a clear error contract. The senior tell: the endpoint is mostly guardrails around a tiny business core. + +#### Self-check + +- [ ] Junior: IoC/DI in plain words, the stereotype annotations, `@Autowired` vs constructor injection, what `@SpringBootApplication` does, `@RequestParam` vs `@PathVariable`. +- [ ] Mid: why self-invocation breaks `@Transactional`, the two post-processor phases, propagation settings that bite, why field injection is bad, `@Controller` vs `@RestController`. +- [ ] Senior: narrate the transaction-holds-connection incident + fix, disable unwanted auto-config by reading it, cut startup time with measurement, fix `@Async` not running, list the guardrails a money-transfer endpoint needs. + ## 1. IoC and DI — the container is a contract, not a drawer Inversion of Control is _who owns `new`_. Dependency Injection is _how the wiring gets delivered_. Together they answer "who constructs this object and when" — the container owns the graph, you declare dependencies, it satisfies them. The depth lives in the two decisions that fall out: how you accept a dependency, and what you hand to a bean that outlives its scope. diff --git a/src/data/blog/en/interview/system-design-senior.md b/src/data/blog/en/interview/system-design-senior.md index 5055d3d..66c2dd0 100644 --- a/src/data/blog/en/interview/system-design-senior.md +++ b/src/data/blog/en/interview/system-design-senior.md @@ -17,6 +17,67 @@ A junior draws boxes. A senior narrates a tradeoff: "I'll cache the hot 1% in Re > Mindset: recite a diagram and you're mid-level. Walk through a tradeoff with real numbers and a production failure mode, and you've earned the "senior" checkbox. You don't need to design Twitter — you need to design the part of Twitter that would actually break first, and say so out loud. +## Interview question ladder (Junior → Mid → Senior) + +> Drill these out loud. Junior = "do you know the concept"; Mid = "do you know the tradeoffs"; Senior = "can you defend a decision under pressure, with a number and a postmortem." + +### Junior — foundations + +- **Q: What are the steps of a system-design answer?** + A: Clarify requirements (functional + non-functional: scale, latency, consistency) → estimate capacity → sketch the high-level components → dive into the 1-2 hardest parts → name the failure modes. Interviewers grade the _shape_ of your thinking, not a "correct" diagram. + +- **Q: What's the difference between latency and throughput?** + A: Latency = time for one request (ms); throughput = how many per second (req/s). A system can have low latency but low throughput (single-threaded) or high throughput but high tail latency (a queue). You optimize them with different levers. + +- **Q: What's a cache and why do we use one?** + A: A fast store (RAM) that holds the results of expensive work (DB query, compute) so repeated reads are cheap. The point: most read traffic hits a tiny hot set, so a cache turns a DB-bound path into a memory path (microseconds vs milliseconds). + +- **Q: SQL vs NoSQL — when do you pick which?** + A: Relational when you need ACID + joins + flexible queries on structured data. NoSQL (document/columnar/KV) when you need horizontal scale on a simple access pattern (single-key lookups, huge write volume). Pick by the _access pattern_, not the hype. + +- **Q: What's the difference between horizontal and vertical scaling?** + A: Vertical = bigger box (more CPU/RAM, hits a ceiling, downtime to resize). Horizontal = more boxes behind a load balancer (near-unlimited, needs statelessness + shared storage). The senior default is horizontal for stateless services. + +### Mid — tradeoffs & pitfalls + +- **Q: Cache aside vs write-through — when do you use each?** + A: Cache-aside (app reads cache, on miss loads DB and populates): simple, handles a cold cache gracefully, but a miss can stampede. Write-through (writes go to cache + DB together): reads are always fast, but every write pays the cache cost. The trap: choosing one without stating the write/read ratio of the workload. + +- **Q: "60-second staleness is fine." Now design the cache invalidation.** + A: TTL-based (expire after 60 s) is the simplest; event-based invalidation (on write, purge the key) is fresher but needs a reliable event. The senior names the _stale-read window_ the business accepts and designs to it — and knows that "cache invalidation" is the famous hard problem because deletes race with writes. + +- **Q: CAP theorem — pick two, and what does that actually mean?** + A: Under a network partition you trade Consistency (every node sees the same data) for Availability (every request gets a response). CP systems (e.g. strongly-consistent DBs) reject during a partition; AP systems (e.g. Dynamo-style) serve stale-but-present. "Pick two" is really "what do you sacrifice _during a partition_." + +- **Q: How would you shard a 10 TB user table?** + A: By a shard key (user_id hash) so each shard owns a range of keys and queries stay single-shard. The trap: a bad key (signup-date) creates hot shards; a join across shards becomes a scatter-gather. State the key, the resharding plan, and the cross-shard query you'll avoid. + +- **Q: What breaks first at 10× traffic — and how do you find out before it happens?** + A: Usually the single shared resource: one DB, one cache, one downstream. You don't guess — you load-test to find the knee, and you add a circuit breaker + backpressure so a slow dependency degrades gracefully instead of cascading. Name the _one_ resource you'd watch. + +### Senior — design & defense + +- **Q: Design a URL shortener for 100M new links/day, 1B reads/day. Size it.** + A: Writes ~1.2k/s, reads ~11.5k/s. A 7-char base62 key = ~3.5 trillion combos — plenty. Storage: 1B links × ~500 B = 500 GB + replicas. Reads dominate, so cache the hot 1% in Redis (serves ~99% of reads). The senior move is naming the bottleneck (read path) and solving _that_, not over-building. + +- **Q: A cache stampede just took down your DB on a hot key. Walk it and the fix.** + A: A popular key expires; 10k requests all miss simultaneously, all hit the DB, it falls over. Fix: request coalescing (single-flight — one request loads, others wait), jittered TTLs (keys don't all expire at once), and a hot-key local cache. The postmortem: the miss path, not the cache, was the danger. + +- **Q: Design for "99.99% available" — what does that actually cost?** + A: 99.99% = ~52 min/year downtime. It forces multi-AZ (one AZ dies, you survive), no single points of failure, and automated failover. The trade-off: 99.99% costs far more than 99.9% (redundancy, runbooks, game-days). Senior judgment: price the SLA and let the business choose, don't gold-plate by default. + +- **Q: You need strongly-consistent cross-region writes. Defend the design.** + A: That's expensive: synchronous replication across regions adds inter-region latency (tens of ms) to every write, and a partition means unavailability. The senior answer often is "don't" — keep the authoritative write in one region, replicate async for reads, and only pay the consistency cost for the specific records that need it (e.g. balances), not the whole system. + +- **Q: The naive cache is "where the outage hides." Give a concrete example.** + A: A cache that caches _errors_ or _empty results_ — a brief DB hiccup now serves "not found" for 60 s, so users see missing data even after the DB recovers. Or a cache that returns a stale price during a flash sale and oversells. The senior design treats the cache as a _copy with a freshness contract_, not a source of truth, and tests the stale window explicitly. + +#### Self-check + +- [ ] Junior: the steps of a design answer, latency vs throughput, what a cache is, SQL vs NoSQL, horizontal vs vertical scaling. +- [ ] Mid: cache-aside vs write-through, design invalidation to a staleness SLA, CAP under partition, shard-key choice + resharding, find the first-break resource. +- [ ] Senior: size a URL shortener end-to-end, narrate + fix a cache stampede, price a 99.99% SLA, defend cross-region consistency (usually "don't"), name a cache-outage hiding spot. + ## 1. The interview loop — what they're actually scoring The loop looks like a sequence of five steps. It is, but the sequence is a disguise — the scorecard is filled in during the first ten seconds of each step. diff --git a/src/data/blog/vi/interview/database-senior.md b/src/data/blog/vi/interview/database-senior.md index 69ebec9..85a331b 100644 --- a/src/data/blog/vi/interview/database-senior.md +++ b/src/data/blog/vi/interview/database-senior.md @@ -15,6 +15,67 @@ Database là tầng quyết định hệ thống có scale được thật hay k > Tư duy: nhả thuật ngữ thì bạn chỉ ở tầm mid-level. Đi qua một tradeoff bằng số thật và một failure mode trong production thì bạn chạm nốt "senior". Mỗi phần dưới đây đều kết bằng bài tập phỏng vấn viên thực sự hay chạy. +## Thang câu hỏi phỏng vấn (Junior → Mid → Senior) + +> Tự drill to tiếng. Junior = "bạn có biết khái niệm"; Mid = "bạn có biết tradeoff"; Senior = "bạn có thể bảo vệ quyết định dưới áp lực, kèm một con số và một postmortem." + +### Junior — nền tảng + +- **Q: Khác nhau giữa clustered và non-clustered index?** + A: Clustered index _chính là_ bảng — các row được lưu theo thứ tự của nó (ở InnoDB là `PRIMARY KEY`), nên mỗi bảng chỉ có đúng một cái; lookup theo PK là một lần đi qua B-tree tới row. Non-clustered (secondary) index là một B-tree riêng, lá của nó trỏ về clustered key, nên một lookup qua secondary index mất hai bước: index → clustered key → row. + +- **Q: Kể tên bốn mức cô lập transaction (isolation level).** + A: Read uncommitted, read committed, repeatable read, serializable — tăng dần độ nghiêm ngặt. Chúng đánh đổi concurrency lấy việc ngăn anomaly. + +- **Q: Primary key làm được gì mà unique constraint không làm?** + A: PK là clustered key (InnoDB) — nó định nghĩa thứ tự vật lý của row và vừa non-null vừa unique. Unique constraint chỉ là một lời hứa non-clustered về tính duy nhất; bạn có thể có nhiều cái. + +- **Q: Foreign key là gì, và `ON DELETE CASCADE` làm gì?** + A: FK ràng buộc một cột phải tồn tại ở cột được tham chiếu của bảng khác. `CASCADE` khi xoá parent sẽ xoá (hoặc set null với `SET NULL`) các row con — tiện, nhưng một vụ mass delete có thể khoá / cascade nặng hơn bạn tưởng. + +- **Q: Tại sao `SELECT *` lại tệ?** + A: Nó kéo mọi cột (nhiều I/O, nhiều network), phá việc covering index (index không tự thoả mãn query được), và gãy khi thêm/bớt cột. Hãy gọi tên từng cột bạn cần. + +### Mid — tradeoff & bẫy + +- **Q: Tại sao `WHERE YEAR(created_at) = 2026` lại full scan dù `created_at` có index?** + A: Hàm trên cột che giấu nó khỏi B-tree, nên optimizer không seek theo range được — nó scan từng row, áp hàm, lọc. Viết lại thành range trên cột gốc: `created_at >= '2026-01-01' AND created_at < '2027-01-01'`. Cùng bẫy: `LIKE '%x%'`, phép toán trên cột, ép kiểu ngầm. + +- **Q: Khi nào composite index có ích, và quy tắc cột dẫn đầu là gì?** + A: Composite index phục vụ các query lọc trên _tiền tố_ của các cột, từ trái sang phải (leftmost-prefix rule). `(a, b, c)` giúp `WHERE a=?`, `(a,b)=?`, `(a,b,c)=?` nhưng KHÔNG giúp `WHERE b=?`. Đặt cột chọn lọc nhất / hay dùng nhất ở equality lên đầu, nhưng cũng cân nhắc cột có lợi cho predicate. + +- **Q: N+1 query là gì và bạn giết nó thế nào?** + A: Bạn lấy N parent, rồi một query riêng cho mỗi parent để lấy con = N+1 round trip. Sửa: một `JOIN`/`IN` batch, hoặc `@BatchSize`/`fetch join` trong ORM. Dấu hiệu: latency không bao giờ lộ trong một slow-query log vì mỗi call chỉ ~1 ms. + +- **Q: Tại sao pool 2000 connection lại tệ hơn pool 50 connection?** + A: Connection là tài nguyên _có hạn_ mà DB phải schedule. Vượt `max_connections` của DB thì mọi request mới đều timeout; nhiều connection hơn cũng nghĩa là nhiều context-switch và lock contention hơn ở phía DB. Chọn size bằng Little's law (`TPS × avg_query_time`), không phải bằng core count của máy. + +- **Q: Read committed vs repeatable read — anomaly nào mỗi cái vẫn cho phép?** + A: Read committed vẫn cho phép _non-repeatable read_ (cùng một row khác nhau giữa hai lần đọc trong một txn). Repeatable read vẫn cho phép _phantom read_ (một range query trả về các row khác nhau). Serializable chặn cả hai — với giá là concurrency (thường qua range lock / SSI). + +### Senior — thiết kế & bảo vệ + +- **Q: Chọn size connection pool cho service 1,000 req/s với 20 ms avg query time. Giờ nếu 10% call mất 5 s thì sao?** + A: `1000 × 0.02s = 20` connection là con số steady-state; `cores × 10` là heuristic khởi điểm tốt và HikariCP mặc định là 10. Nhưng 10% call 5 s cần `1000 × 0.1 × 5 = 500` connection _nếu_ mỗi call chậm giữ một cái — nghĩa là vài query chậm có thể cạn pool và làm nghẽn 90% đường nhanh. Cách của senior là một pool _riêng_ có bound (hoặc timeout + circuit breaker) cho đường chậm để nó không thể làm đói đường nhanh. + +- **Q: "Index làm mọi thứ nhanh hơn." Bảo vệ hay phản bác — kèm chi phí phía write.** + A: Phản bác. Mọi index đều được duy trì trên mỗi `INSERT`/`UPDATE`/`DELETE`: thêm nhiều lần đi B-tree, thêm page split, thêm WAL. Một bảng write-heavy với 8 index trả thuế duy trì gấp 8 lần và insert chậm hơn. Cách phòng thủ: index cho những query bạn thật sự chạy; drop các index vô dụng; cân nhắc read replica cho mấy truy vấn analytical nặng. + +- **Q: Báo cáo nói "DB trung bình 0.1 ms nhưng app mất 800 ms." Bạn nhìn đâu trước?** + A: Cái _pool_, không phải DB. Nếu cả thread pool và connection pool đều xếp hàng, request đợi đến lượt lấy connection trong khi DB thì rỗi. Kiểm tra pool saturation, `connectionTimeout`, và xem `wait` time có áp đảo `query` time không. Sửa thường không phải "to hơn DB". + +- **Q: Đi qua một phantom read xuất hiện trong production và bạn đóng nó thế nào.** + A: Một batch xử lý "tất cả order chưa trả", một txn khác insert một order chưa trả mới cùng range giữa chừng → batch lọt nó (hoặc đếm trùng khi retry). Phòng thủ: `REPEATABLE READ`/`SERIALIZABLE` với range lock, hoặc `SELECT … FOR UPDATE SKIP LOCKED` để claim row nguyên tử nên các worker concurrent không đụng nhau. Nêu rõ isolation level và loại lock. + +- **Q: Bạn cần thêm index cho bảng 2 tỉ row mà zero downtime. Làm sao?** + A: Dùng online/DDL tool (`CREATE INDEX CONCURRENTLY` ở Postgres; InnoDB online DDL với `ALGORITHM=INPLACE, LOCK=NONE`) build index mà không block write — nhưng vẫn tăng tải và có thể mất hàng tiếng ở scale lớn; làm trong maintenance window, monitor replication lag, và có rollback. Đừng bao giờ `LOCK=TABLE` trên bảng nóng. + +#### Tự kiểm tra + +- [ ] Junior: giải thích clustered vs non-clustered, 4 isolation level, PK vs unique, FK cascade, tại sao `SELECT *` tệ. +- [ ] Mid: viết lại predicate hàm-trên-cột thành range, nêu leftmost-prefix rule, giết N+1, chọn size pool bằng Little's law, kể anomaly mỗi isolation level vẫn cho phép. +- [ ] Senior: bảo vệ connection-pool sizing dưới đuôi slow-query, định lượng chi phí index phía write, trace một vụ phantom-read, và thêm index 2B-row online không downtime. + ## 1. Indexing — nơi phỏng vấn hay "chết" "Thêm index" là câu trả lời của người mới. Câu trả lời của senior giải thích vì sao cây index cao ba hay bốn tầng B-tree, cột nào đứng trước cột nào, vì sao optimizer vẫn ngoảnh mặt làm ngơ, và một index "hot" tốn bạn bao nhiêu trên mỗi lần ghi bạn không hề lên kế hoạch. diff --git a/src/data/blog/vi/interview/java-core-senior.md b/src/data/blog/vi/interview/java-core-senior.md index 7f14f1b..a1be7f0 100644 --- a/src/data/blog/vi/interview/java-core-senior.md +++ b/src/data/blog/vi/interview/java-core-senior.md @@ -15,6 +15,67 @@ Junior biết cú pháp Java. Senior biết **JVM đang làm gì, tại sao nó > Tư duy: "tùy thuộc, và đây là đánh đổi" đánh bại đọc thuộc lòng mọi lúc. Khoảnh khắc bạn trả lời bằng một trade-off, một con số, hay một câu chuyện postmortem thay vì một định nghĩa — bạn đã qua vạch. +## Thang câu hỏi phỏng vấn (Junior → Mid → Senior) + +> Tự drill to tiếng. Junior = "bạn có biết khái niệm"; Mid = "bạn có biết tradeoff"; Senior = "bạn có thể bảo vệ quyết định dưới áp lực, kèm một con số và một postmortem." + +### Junior — nền tảng + +- **Q: Khác nhau giữa `==` và `.equals()` trong Java?** + A: `==` so sánh tham chiếu (có phải cùng một object); `.equals()` so sánh _giá trị_ (có thể override). Và `Integer.valueOf` cache từ -128..127, nên `==` trên Integer "chạy" trong khoảng đó rồi cắn ở 128 — bug qua hết test rồi chết trên prod. + +- **Q: Bốn trụ cột của OOP là gì?** + A: Encapsulation, abstraction, inheritance, polymorphism — nhưng senior phải nói được _tại sao_ mỗi cái tồn tại (vd encapsulation giới hạn bán kính chấn động của thay đổi), không chỉ nhắc từ. + +- **Q: Khác nhau giữa `ArrayList` và `LinkedList`?** + A: `ArrayList` là mảng co giãn — O(1) truy cập ngẫu nhiên, O(n) chèn/xoá ở giữa; `LinkedList` là danh sách liên kết hai chiều — O(1) thêm/xoá ở hai đầu, O(n) truy cập. Mặc định dùng `ArrayList` trừ khi bạn chèn/xoá liên tục ở đầu. + +- **Q: `final` lên class / method / biến nghĩa là gì?** + A: `final` class = không subclass; `final` method = không override; `final` biến = gán một lần. Một reference `final` vẫn cho phép mutate object nó trỏ tới (reference cố định, không phải state). + +- **Q: Khác nhau giữa `String`, `StringBuilder`, và `StringBuffer`?** + A: `String` bất biến (mỗi phép nối tạo object mới). `StringBuilder` mutable và không đồng bộ (nhanh, single-thread). `StringBuffer` là bản đồng bộ của nó (chỉ dùng khi chia sẻ giữa nhiều thread). Trong loop, `StringBuilder` tránh bão object `String` vứt đi. + +### Mid — tradeoff & bẫy + +- **Q: Tại sao double-checked locking gãy nếu thiếu `volatile`?** + A: Lệnh read không đồng bộ có thể thấy một singleton _đang xây dựng dở dang_ — store reference có thể nhảy lên trước các write của constructor, nên thread khác thấy reference khác null nhưng object mới làm xong một nửa. `volatile` tạo happens-before từ write-constructor → read, bịt lỗ hổng. + +- **Q: `i++` trên một `volatile int` — an toàn không?** + A: Không. `volatile` cho visibility + ordering, không cho tính nguyên tử. `i++` là read-modify-write; hai thread có thể cùng đọc 41 và cùng ghi 42. Dùng `AtomicInteger` (một CAS) hoặc `LongAdder` dưới tranh chấp cao (nhanh gấp vài lần nhờ stripe qua các cell). + +- **Q: False sharing là gì và sửa thế nào?** + A: Hai field độc lập nằm cùng một cache line 64 byte ping-pong qua các core mỗi lần ghi, ngay cả trong code lock-free. Nạn nhân kinh điển là mảng counter `long[]` per-thread. `@Contended` (JEP 142) đệm field ra các line riêng; một coherence miss tốn ~100 ns mỗi lần ping. + +- **Q: `synchronized` vs `ReentrantLock` — khi nào dùng cái nào?** + A: `synchronized` không tranh chấp gần như miễn phí (update mark-word, ~chục ns); tranh chấp phải park/unpark vào kernel (micro-giây). `ReentrantLock` thêm `tryLock(timeout)`, nhiều `Condition`, và fairness — nhưng bạn phải `unlock()` trong `finally` và ưu tiên `tryLock(2, SECONDS)` để lock kẹt không treo thread. + +- **Q: `CompletableFuture.thenApplyAsync` chạy trên pool nào, và gì gãy?** + A: Trên `ForkJoinPool.commonPool()` (parallelism = cores−1). Code blocking (JDBC, sleep) bên trong làm đói pool và kẹt mọi thứ phía sau dù box rỗi. Truyền một executor riêng cỡ cho việc blocking (hoặc dùng virtual thread). + +### Senior — thiết kế & bảo vệ + +- **Q: Một service 2.000 req/s, mỗi cái block ~50 ms trong JDBC. Chọn size thread pool. Giờ 10% mất 5 s — đổi gì?** + A: `2000 × 0,05 = 100` worker steady-state (Little's law). Nhưng đuôi 10%-tại-5s cần `2000 × 0,1 × 5 = 1000` worker _nếu_ mỗi call chậm giữ một cái — nên vài query chậm cạn pool và kẹt 90% đường nhanh. Cách của senior: bound pool, dùng `CallerRunsPolicy` tạo backpressure, và cô lập đường chậm trên executor riêng có deadline. + +- **Q: Virtual thread giúp khi nào, không khi nào, và gì vẫn pin carrier sau JDK 24?** + A: Giúp I/O-bound blocking (hàng triệu HTTP/DB call đồng thời). Không giúp CPU-bound (vẫn N CPU). Sau JEP 491 (JDK 24) `synchronized` không còn pin; pin tồn dư là native frame (JNI/FFM), class loading, và file I/O local trên Linux. `ReentrantLock` không bao giờ pin — `LockSupport.park` unmount virtual thread. + +- **Q: Thấy `OutOfMemoryError: Metaspace` sau mỗi redeploy mà không thêm class. Lệnh đầu tiên?** + A: `jcmd VM.native_memory` và `-XX:MaxMetaspaceSize`. Nguyên nhân là classloader leak: thứ gì đó giữ root cái loader cũ (static field, JDBC driver trong `DriverManager`, proxy cache) nên metadata không bao giờ unload. Vài MB mỗi redeploy thành 2 GB sau trăm lần deploy. + +- **Q: Một method nóng vẫn chậm sau 10 phút chạy. JIT có thể đang làm gì, và chứng minh thế nào?** + A: Tiered compilation (C1→C2) với OSR; nó có thể **megamorphic** (quá nhiều kiểu receiver để inline) hoặc kẹt recompile quá `-XX:CompileThreshold`. Chứng minh bằng `-Xlog:jit+compilation=debug` — bạn sẽ thấy recompilation churn, không phải "cần box to hơn". + +- **Q: `SELECT * FROM orders WHERE YEAR(created_at)=2026` chậm dù có index — à đó là SQL. Java analog nào cắn ngang nhiên thế?** + A: Gọi một method trong loop làm `findById` mỗi phần tử (N+1), hoặc `computeIfAbsent` đệ quy trên cùng một key (Java 8 deadlock cái bin). Cả hai giấu O(n²) / deadlock sau những loop vô tội — dấu hiệu senior là "nó giấu ở đâu", không phải "có tồn tại không". + +#### Tự kiểm tra + +- [ ] Junior: `==` vs `.equals`, bốn trụ cột, `ArrayList` vs `LinkedList`, `final`, `String` vs `StringBuilder`. +- [ ] Mid: sửa DCL bằng `volatile`, vì sao `volatile int++` không an toàn, false sharing + `@Contended`, `synchronized` vs `ReentrantLock`, cái bẫy `commonPool`. +- [ ] Senior: chọn size pool bằng Little's law dưới đuôi chậm, nêu lúc nào virtual thread giúp/pin, chẩn đoán Metaspace leak, chứng minh lỗi JIT từ log, chỉ mặt N+1 / `computeIfAbsent` phía Java. + ## 1. Heap, GC, và bài toán pause Họ sẽ hỏi: "Chuyện gì xảy ra khi bạn `new` một object?" Một ứng viên mid dừng ở "nó nằm trên heap." Senior nói về **ở đâu**, **nhanh bao nhiêu**, và **pause đáng giá gì** — vì đó mới là thứ cắn trên production. Mọi câu trong phần này đều có một đáp án bằng số; phỏng vấn viên lắng nghe con số, không phải danh từ. diff --git a/src/data/blog/vi/interview/kafka-senior.md b/src/data/blog/vi/interview/kafka-senior.md index 389bb61..f000083 100644 --- a/src/data/blog/vi/interview/kafka-senior.md +++ b/src/data/blog/vi/interview/kafka-senior.md @@ -17,6 +17,67 @@ Junior thuộc lòng ba delivery semantics. Senior kể được chính xác kho > Tư duy: nhả thuật ngữ thì bạn chỉ ở tầm mid-level. Đi qua một tradeoff bằng số thật và một failure mode trong production thì bạn chạm nốt "senior". Mỗi phần dưới đây đều kết bằng bài tập phỏng vấn viên thực sự hay chạy. +## Thang câu hỏi phỏng vấn (Junior → Mid → Senior) + +> Tự drill to tiếng. Junior = "bạn có biết khái niệm"; Mid = "bạn có biết tradeoff"; Senior = "bạn có thể bảo vệ quyết định dưới áp lực, kèm một con số và một postmortem." + +### Junior — nền tảng + +- **Q: Ba delivery semantics trong Kafka là gì?** + A: At-most-once (có thể mất), at-least-once (có thể trùng), exactly-once (không mất, không trùng). Producer mặc định at-least-once; consumer phải dedupe hoặc làm processing idempotent để tiệm cận exactly-once. + +- **Q: Topic, partition, và consumer group là gì?** + A: Topic là một log; nó chia thành các partition (mỗi cái là một chuỗi immutable có thứ tự). Consumer group là tập consumer chia sẻ công việc — mỗi partition được tiêu thụ bởi đúng một thành viên của group. + +- **Q: Consumer offset đại diện cho gì?** + A: Vị trí của record tiếp theo cần đọc trong một partition. Offset đã commit cho consumer resume sau restart hoặc rebalance. `auto-commit` commit định kỳ; manual commit cho bạn kiểm soát biên consume-process-commit. + +- **Q: Khác nhau giữa queue và topic?** + A: Queue truyền thống giao mỗi message cho một consumer; topic broadcast cho mọi consumer group subscribe. Đó là lý do một topic có thể nuôi analytics, audit, và service chính cùng lúc. + +- **Q: `acks=all` nghĩa là gì?** + A: Producer chờ leader _và_ mọi in-sync replica acknowledge write mới coi là thành công — bền vững hơn, đổi bằng latency. `acks=1` chỉ chờ leader; `acks=0` bắn và quên. + +### Mid — tradeoff & bẫy + +- **Q: `enable.idempotence=true` — nó có bảo vệ cú ghi Postgres nằm ở phía bên kia consumer không?** + A: Không. Idempotence chỉ dedupe retry producer→broker trong nội bộ Kafka. Khi consumer ghi Postgres, một redelivery (crash trước khi commit offset) ghi lại. Exactly-once end-to-end cần một _sink_ idempotent (upsert by key) hoặc transactional outbox. + +- **Q: Một poison record âm thầm làm kẹt một partition suốt vài giờ trong khi dashboard vẫn xanh. Tại sao, và sửa?** + A: Consumer throw trên record đó, không bao giờ commit offset, Kafka redeliver nó mãi mãi — một record độc chặn cả partition. Sửa: dead-letter queue (route record xấu sau N retry) và alert trên consumer lag — metric thực sự cho thấy sự kẹt. + +- **Q: Chọn số partition thế nào?** + A: Bằng throughput và parallelism của consumer, không đoán mò: `partitions ≈ max(target_producer_MBps / per_partition_MBps, target_consumer_instances)`. Nhiều partition = parallelism hơn nhưng cũng nhiều file mở, nhiều rebalance, và election lâu hơn nếu broker chết. + +- **Q: Rebalance storm là gì, tại sao nó đóng băng queue?** + A: Consumer join/leave group liên tục (poll chậm, GC pause dài, heartbeat timeout), kích rebalance thu hồi mọi partition, tạm dừng tiêu thụ, rồi assign lại. Sửa: tune `session.timeout.ms`/`heartbeat.interval.ms`, giữ poll loop nhanh, và dùng cooperative rebalancing (incremental) nếu được. + +- **Q: Consumer lag tăng — nhìn đâu trước?** + A: Đây là bài toán _throughput_ (consumer không kịp — thêm instance/partition) hay _processing_ (mỗi record chậm — một downstream call chậm). Lag per-partition cho biết là một partition nóng hay toàn cục. Metric chứng minh _consumer_, không phải broker, là nút thắt là consumer lag vs broker CPU. + +### Senior — thiết kế & bảo vệ + +- **Q: Cần exactly-once cho "Kafka → consume → ghi Postgres". Thiết kế.** + A: Hoặc (a) transactional outbox trong Postgres + một relay publish sang Kafka nguyên tử với business write (DB là source of truth), hoặc (b) idempotent sink: consumer upsert by key deterministic và commit offset trong cùng một local transaction. `enable.idempotence` + `ack=all` cover producer; sink cover consumer. Nêu bạn chọn cái nào và tại sao. + +- **Q: Một cuộc leader election partition mất 30 s mà mọi dashboard xung quanh vẫn xanh. Giải thích.** + A: Broker chết kích election leader cho replica của partition đó; cho tới khi một ISR leader mới được bầu, partition không available cho write — nhưng partition khác và service khác vẫn ổn, nên dashboard toàn cục vẫn xanh. Dấu hiệu là per-partition unavailability + producer timeout, không phải đỏ toàn hệ thống. Sửa: thêm replica, `election.timeout` nhanh hơn, và producer retry với backoff. + +- **Q: Chọn size cluster cho 50 MB/s ingest, retention 3 ngày, record 1 KB. Bao nhiêu broker?** + A: 50 MB/s × 3 ngày ≈ 13 TB raw; ×replication factor 3 ≈ 39 TB, ÷usable-per-broker (vd 5 TB) ≈ 8 broker tối thiểu, cộng headroom rebalancing. Throughput mỗi broker ~hàng trăm MB/s, nên broker bị bound bởi disk/retention ở đây, không phải CPU. Nêu giả định và knob bạn sẽ canh (disk, không phải core). + +- **Q: Đi qua một vụ duplicate-payment do rebalance và bạn đóng nó thế nào.** + A: Consumer xử lý xong một payment, crash trước khi commit offset, rebalance assign lại partition, redelivery xử lý lại. Đóng bằng idempotent processing (dedupe by `paymentId` trong một unique DB constraint) để redelivery thành no-op. Postmortem: timing commit offset, không phải Kafka, là bug. + +- **Q: Khi nào bạn KHÔNG dùng Kafka cho việc này?** + A: Với request/response hoặc RPC latency thấp, queue/topic thêm một hop và at-least-once semantics bạn phải thiết kế xung quanh. Với single-producer/single-consumer latency chặt, một call trực tiếp hoặc broker nhẹ hơn có thể đơn giản hơn. Kafka tỏa sáng với fan-out, replay, và decoupling ở scale — hãy chỉ mặt trường hợp nó bị overkill. + +#### Tự kiểm tra + +- [ ] Junior: 3 semantics, topic/partition/consumer-group, offset là gì, queue vs topic, `acks=all`. +- [ ] Mid: vì sao idempotence không bảo vệ sink, poison-record+DLQ, bài toán size partition, nguyên nhân rebalance-storm, lag-là-metric. +- [ ] Senior: thiết kế exactly-once end-to-end, giải thích leader election 30 s, size cluster bằng retention, trace vụ duplicate-payment do rebalance, chỉ mặt khi Kafka là sai công cụ. + ## 1. Cái log chính là sản phẩm — partitions, offsets, và thứ tự Kafka không phải một message queue "tình cờ nhanh". Nó là một **distributed, immutable, append-only commit log**. Cái khung đó chính là câu trả lời senior: mọi thứ khác — consumer group, retention, kể cả "exactly once" — là hệ quả của cái log, không phải một feature gắn thêm lên trên. diff --git a/src/data/blog/vi/interview/microservices-senior.md b/src/data/blog/vi/interview/microservices-senior.md index 6abda1b..df42e7a 100644 --- a/src/data/blog/vi/interview/microservices-senior.md +++ b/src/data/blog/vi/interview/microservices-senior.md @@ -15,6 +15,67 @@ Phỏng vấn microservices test **phán đoán** nhiều hơn kiến thức. C > Tư duy: junior liệt kê patterns; senior kể lại failure modes. Khi bạn trả lời bằng một con số, một câu chuyện postmortem, hay một câu "đây là đánh đổi và lúc nào tôi lật ngược nó" — bạn đã qua vạch. +## Thang câu hỏi phỏng vấn (Junior → Mid → Senior) + +> Tự drill to tiếng. Junior = "bạn có biết khái niệm"; Mid = "bạn có biết tradeoff"; Senior = "bạn có thể bảo vệ quyết định dưới áp lực, kèm một con số và một postmortem." + +### Junior — nền tảng + +- **Q: Khác nhau giữa monolith và microservices?** + A: Monolith là một deployable gánh mọi domain; microservices là các service deploy độc lập chia theo business capability, mỗi cái có data riêng. Tradeoff là autonomy team + scale độc lập vs độ phức tạp phân tán. + +- **Q: Quy tắc quan trọng nhất về database của service?** + A: Mỗi service sở hữu data của nó và chỉ expose qua API — không shared database. Shared DB ngầm coupling các service và biến kiến trúc "micro" thành một distributed monolith. + +- **Q: API gateway dùng để làm gì?** + A: Là cửa trước: routing, auth, rate limiting, aggregation trong một chỗ, nên các service lẻ không phải tự implement cross-cutting concern. (Dù dồn logic vào gateway quá mức cũng là một bẫy riêng.) + +- **Q: Service discovery là gì?** + A: Cách các service tìm địa chỉ mạng của nhau lúc runtime (registry như Consul/Eureka, hoặc DNS-based). Thiếu nó bạn hardcode address và không scale hay relocate instance được. + +- **Q: Giao tiếp đồng bộ vs bất đồng bộ — khác gì?** + A: Sync (HTTP/gRPC) chờ response; caller bị block. Async (message/event) bắn rồi đi tiếp; consumer xử lý sau. Async decouple và hấp thụ spike, nhưng thêm tư duy eventual-consistency. + +### Mid — tradeoff & bẫy + +- **Q: Distributed transaction là gì và tại sao 2PC thường bị loại?** + A: 2PC (two-phase commit) cố làm một write xuyên service nguyên tử, nhưng nó giữ lock xuyên service và gãy nát dưới partial failure — kinh điển là "distributed transaction là quả bom latency và availability." Câu trả lời senior là SAGA + outbox + eventual consistency. + +- **Q: SAGA pattern là gì và khi nào dùng?** + A: SAGA là chuỗi các local transaction, mỗi cái có một compensating action để undo bước trước nếu fail. Dùng khi bạn phải giữ nhiều service nhất quán mà không cần 2PC. Tradeoff: chấp nhận _eventual_ consistency và phải xử lý compensation cùng event out-of-order. + +- **Q: Outbox pattern là gì và tại sao cần?** + A: Ghi business change và event cần publish trong _cùng một_ local DB transaction (một bảng "outbox"), rồi một relay publish event. Nó giải quyết dual-write problem (DB committed nhưng gọi broker fail → mất event, hoặc ngược lại → trùng). Relay làm event eventual consistent. + +- **Q: Circuit breaker là gì, các trạng thái?** + A: Nó bọc một call downstream fail và trip open sau một ngưỡng lỗi, fail nhanh thay vì chất đống thread. Trạng thái: closed → open (reject) → half-open (thử một call). Thiếu nó, một dependency chậm cascade thành outage toàn cục (vụ "dependency khỏe mà tôi lại down"). + +- **Q: Distributed monolith là gì và nhận biết thế nào?** + A: Những service không deploy/scale độc lập được vì share DB, gọi sync trên request path, hoặc block lẫn nhau khi deploy. Dấu hiệu: bạn không ship nổi một service mà không phối hợp release train. Thuốc là bounded context thực sự + async где có thể, không phải thêm box. + +### Senior — thiết kế & bảo vệ + +- **Q: "Thiết kế một microservice." Câu đầu tiên senior nhất là gì?** + A: Thường là "chưa, đừng" — hoặc "khoét phần nào của monolith trước, và ship thế nào trong lúc khoét?" Không ai được điểm vì vẽ thêm hộp. Cách của senior là xếp trình tự extraction sao cho mỗi bước deploy độc lập và rollback an toàn. + +- **Q: Một payment service downstream chậm và giờ service CỦA BẠN timeout rồi OOM. Đi vụ incident.** + A: Không timeout + không circuit breaker → thread block trên call chậm, pool đầy, request xếp hàng, heap đầy các context chờ → cascade. Sửa: per-call deadline (`tryLock`/HTTP timeout), circuit breaker fail nhanh, bulkhead để một dependency không ăn hết thread, và backpressure. Nêu đúng cái knob. + +- **Q: Cần nhất quán xuyên service cho "reserve seat + charge card." Thiết kế không dùng 2PC.** + A: SAGA: reserve seat (local txn + event) → charge card (local txn + event) → nếu charge fail, compensate bằng release seat. Outbox trên mỗi bước để event tin cậy. Handler idempotent (event có thể redeliver). Nêu consistency window và user thấy gì trong đó. + +- **Q: Giữ một rolling deploy an toàn khi service phụ thuộc API mới của nhau?** + A: Backward-compatible change trước (thêm, không break), consumer-tolerant parsing, và contract test trong CI. Deploy change tương thích của _provider_ trước, rồi call mới của _consumer_. Blue-green hoặc canary để deploy xấu chỉ ảnh hưởng một slice. DB migration tương thích hai chiều (thêm column, không rename phá hủy đến khi unused). + +- **Q: Khi nào bạn CỐ Ý không tách service?** + A: Khi chi phí hệ phân tán (network, consistency, ops, tracing) vượt lợi ích — một domain gắn kết thay đổi cùng nhau nên ở một deployable. Tách "vì scalability" một service CPU-light là win giả nhân bản failure mode. Judgment > dogma. + +#### Tự kiểm tra + +- [ ] Junior: monolith vs microservices, database-per-service, gateway/discovery là gì, sync vs async. +- [ ] Mid: vì sao 2PC bị loại, SAGA + compensation, outbox pattern, trạng thái circuit-breaker, nhận biết distributed monolith. +- [ ] Senior: "chưa, đừng" là câu đầu, kể vụ cascade với đúng cách sửa, thiết kế SAGA cho một flow thật, xếp trình tự rolling-deploy an toàn, biện luận khi KHÔNG tách. + ## 1. Bẫy distributed-monolith Chia nhỏ sớm quá mang lại **network call thay cho method call**, distributed transaction, và **10× chi phí vận hành** mà không được lợi gì. Mọi monolith bạn tách đều nộp một khoản thuế trả trước: cái network. Một round trip HTTP cùng datacenter là **~0,1–0,5 ms**; một method call trong process là **~1 ns**. Bạn đang tự nguyện chậm hơn 2–3 bậc độ lớn và gọi đó là kiến trúc. diff --git a/src/data/blog/vi/interview/oop-senior.md b/src/data/blog/vi/interview/oop-senior.md index b0aa3b8..bdac779 100644 --- a/src/data/blog/vi/interview/oop-senior.md +++ b/src/data/blog/vi/interview/oop-senior.md @@ -15,6 +15,67 @@ Lập trình hướng đối tượng là chiếc vé vào cửa. Junior đọc > Tư duy: khi phỏng vấn viên nói "team của anh cần một tính năng mới," câu trả lời senior không bao giờ là "thêm một nhánh `if`." Nó là "trục thay đổi này nằm ở đâu — và tôi dựng thứ gì mà mai tôi không phải sửa?" Định nghĩa thì qua cửa junior; **quyết định dưới ràng buộc** mới qua vạch senior. +## Thang câu hỏi phỏng vấn (Junior → Mid → Senior) + +> Tự drill to tiếng. Junior = "bạn có biết khái niệm"; Mid = "bạn có biết tradeoff"; Senior = "bạn có thể bảo vệ quyết định dưới áp lực, kèm một con số và một postmortem." + +### Junior — nền tảng + +- **Q: Mỗi chữ cái SOLID nghĩa là gì?** + A: S — Single Responsibility, O — Open/Closed, L — Liskov Substitution, I — Interface Segregation, D — Dependency Inversion. Junior đọc thuộc; senior chỉ được chỗ code vi phạm một cái và cái giá sửa nó. + +- **Q: Khác nhau giữa interface và abstract class?** + A: Interface là một hợp đồng thuần túy (không state, đa kế thừa); abstract class giữ được state và cung cấp implementation một phần (đơn kế thừa). Dùng interface để định nghĩa một vai trò; abstract class để share code giữa anh em gần nhau. + +- **Q: Khác nhau giữa inheritance và composition?** + A: Inheritance = "is-a" (share implementation của parent); composition = "has-a" (giữ một instance và delegate). Composition thường được ưu tiên vì linh hoạt và ít coupled hơn. + +- **Q: Polymorphism trong một câu?** + A: Một interface, nhiều implementation — caller viết against abstraction và runtime chọn behavior cụ thể (method override, hoặc interface dispatch). + +- **Q: Bốn trụ cột OOP lại — và `private` thuộc về cái nào?** + A: Encapsulation, abstraction, inheritance, polymorphism. `private`/`protected` là encapsulation — giấu state sau một mặt điều khiển để thay đổi nằm local. + +### Mid — tradeoff & bẫy + +- **Q: Tại sao "favor composition over inheritance" hơn một khẩu hiệu?** + A: Inheritance coupling bạn vào implementation của parent và gãy khi requirement cross-cut (một class cần hai behavior từ hai parent — nhưng Java chỉ đơn kế thừa). Composition cho bạn swap behavior lúc runtime qua dependency inject. Bẫy: một cây kế thừa sâu nơi mỗi thay đổi lan cả lên lẫn xuống. + +- **Q: Nêu một Open/Closed violation cụ thể và cách sửa.** + A: Một `InvoiceCalculator` với `if (type == PDF) … else if (type == XLSX)` — mỗi format mới lại sửa class (không đóng cho modification). Sửa: một interface `Renderer` + một class mỗi format, chọn bằng một map. Thêm format giờ là thêm class, không sửa code cũ. + +- **Q: Liskov violation người ta thực sự ship là gì?** + A: Một subclass tăng cường precondition hoặc yếu đi postcondition — vd `Square extends Rectangle` nhưng `setWidth` cũng phải set height, phá contract rectangle caller依赖. Sửa thường là "đừng ép IS-A" — model chúng như sibling dưới một abstraction chung. + +- **Q: "Fat interface" có vấn đề gì, sửa thế nào?** + A: Một interface 12 method ép mọi implementer stub behavior nó không cần (cái `RemoteControl` có `startCar` trên `ToyCar`). Sửa: tách thành role interface (`Printable`, `Scannable`) để client chỉ dependency thứ nó dùng — Interface Segregation. + +- **Q: Dependency Inversion — khác gì "depend on abstractions"?** + A: DIP nói module cao không nên dependency module thấp; cả hai dependency abstraction, và binding xảy ra ở rìa (constructor injection). Lợi: bạn swap repo Postgres thành in-memory trong test mà không động service. Thiếu nó, business logic hàn chết vào DB driver. + +### Senior — thiết kế & bảo vệ + +- **Q: "Thêm CSV export cho report." Code đi đâu, và gì bạn từ chối làm?** + A: Từ chối cái `if/else` trong class cũ (phạm OCP). Thêm interface `ReportExporter`, implementation `CsvExporter`, register nó, rồi inject/select by format. Dấu hiệu senior: tôi biết _trục thay đổi_ này là gì (output format) và cô lập nó để format sau là additive, không xâm lấn. + +- **Q: Bạn kế thừa một cây kế thừa 6 tầng không ai hiểu. Làm gì — viết lại hay để đó?** + A: Đừng viết lại ngày một. Đầu tiên, characterize behavior bằng characterization test để refactor không break ngầm. Rồi flatten dần phần rủi ro thành composition, sau các test đó, từng subclass một. Big-bang rewrite code đang chạy là cách bạn tạo ra một incident tệ hơn. + +- **Q: Khi nào inheritance thực sự đúng hơn composition?** + A: Khi có một "is-a" thực sự với implementation shared _không_ phân kỳ — vd `BaseEntity` với id/version/audit field, hoặc Template Method mà skeleton ổn định và chỉ các bước thay đổi. Tha thứ coupling vì abstraction ổn định. Nêu trường hợp composition sẽ thành ceremony. + +- **Q: Bảo vệ "interface cho mọi dependency" — và đâu nó thành cargo-cult.** + A: Một interface mỗi dependency tuyệt vời khi có hai implementation hoặc bạn test against một fake. Nó thành cargo-cult khi một class có một caller và zero alternate implementation — bạn thêm một lớp indirection vô ích. Judgment senior: introduce seam khi implementation thứ hai (hoặc test) thực sự xuất hiện, không phải phòng trước. + +- **Q: Đi qua một thiết kế "follow SOLID" nhưng kinh khủng khi làm việc.** + A: Một `UserService` bị xé thành 14 class nhỏ sau 14 interface — mỗi thay đổi động 6 file, và các "abstraction" chỉ có một implementation (ceremony, không phải engineering). Bài học: SOLID phục vụ changeability và testability, không phải số lượng file. Tôi sẽ collapse các single-impl interface và giữ chỉ những seam thực sự đáng. + +#### Tự kiểm tra + +- [ ] Junior: chữ cái SOLID, interface vs abstract class, inheritance vs composition, polymorphism, `private` thuộc về đâu. +- [ ] Mid: vì sao composition-over-inheritance, một OCP violation thật + sửa, một LSP break đã ship, sửa fat-interface, DIP vs "depend on abstractions". +- [ ] Senior: code mới đi đâu không phạm OCP, refactor an toàn một cây sâu, khi nào inheritance đúng, interface-cargo-cult, postmortem một thiết kế SOLID-mà-awful. + ## 1. SOLID — bản áp dụng thực tế, và các cái bẫy "Nêu định nghĩa SOLID" chỉ là màn sàng lọc junior. Senior thì bị hỏi cách **áp dụng**, rồi bị ép bảo vệ những chỗ mà áp dụng theo sách vở là sai. Đi qua cả năm, nhưng hãy sẵn sàng đào sâu ba cái thực sự cắn trên production: Open/Closed, Dependency Inversion, và Liskov. diff --git a/src/data/blog/vi/interview/senior-mindset-senior.md b/src/data/blog/vi/interview/senior-mindset-senior.md index d7e5aed..9ab2ea0 100644 --- a/src/data/blog/vi/interview/senior-mindset-senior.md +++ b/src/data/blog/vi/interview/senior-mindset-senior.md @@ -17,6 +17,67 @@ Hãy nghĩ tới khác biệt giữa một đầu bếp line cook làm theo côn > Tư duy: đọc thuộc một framework thì bạn chỉ ở tầm mid-level. Đi qua một trade-off bằng số thật, một failure mode trong production, và một câu "tôi sẽ đo trước khi chốt" trung thực — thì bạn chạm nốt "senior". Mỗi phần dưới đây đều kết bằng bài drill mà phỏng vấn viên thực sự chạy. +## Thang câu hỏi phỏng vấn (Junior → Mid → Senior) + +> Tự drill to tiếng. Junior = "bạn có biết khái niệm"; Mid = "bạn có biết tradeoff"; Senior = "bạn có thể bảo vệ quyết định dưới áp lực, kèm một con số và một postmortem." + +### Junior — nền tảng + +- **Q: Tại sao có phỏng vấn behavioral — họ thực sự test gì?** + A: Không phải bạn có "ngoan" không, mà là bạn có sở hữu sự mơ hồ, giao tiếp trade-off, và khiến người xung quanh tốt hơn không. Vòng kỹ thuật chứng minh bạn _làm được_; vòng behavioral quyết định bạn có an toàn để chỉ tay vào production lúc 2h sáng không. + +- **Q: Khác nhau giữa tư duy junior và senior trong một câu?** + A: Junior được giao một task và thực thi nó. Senior được giao một _vấn đề_ và sở hữu kết quả — họ chất vấn tiền đề, khoanh vùng rủi ro, và giao tiếp cái họ sẽ đổi để kịp deadline. + +- **Q: "Trade-off" là gì và tại sao phỏng vấn viên thích từ đó?** + A: Mọi lựa chọn kỹ thuật đều có một cái giá ở chỗ khác (latency vs consistency, tốc độ vs đúng đắn, đơn giản vs linh hoạt). Nêu được trade-off chứng minh bạn hiểu hệ thống, không chỉ tính năng. + +- **Q: "Ownership" với bạn nghĩa là gì?** + A: Là công việc chưa xong khi code merge — nó xong khi nó đúng trên production và người kế tiếp vận hành được. Bạn viết runbook, canh dashboard, trả lời page lúc 2h sáng. + +- **Q: Tại sao "tôi không biết" là một câu trả lời senior hợp lệ?** + A: Vì một senior đoán rồi chốt nguy hiểm hơn một senior nói "tôi sẽ đo trước khi quyết." Không chắc chắn trung thực kèm kế hoạch giải quyết đánh bại tự tin giả vờ ship một bug. + +### Mid — tradeoff & bẫy + +- **Q: Kể về một lần bạn mắc lỗi. (Kinh điển.)** + A: Chọn một cái thật có cấu trúc rõ: chuyện gì → bạn bỏ sót gì → bạn đổi gì (monitoring, một test, một process). Bẫy là đổ lỗi teammate hoặc kể một lỗi không bài học. Dấu hiệu senior là sửa ở _hệ thống_, không phải "tôi cẩn thận hơn". + +- **Q: Bạn push back một deadline bạn thấy phi thực tế thế nào?** + A: Bằng data, không bằng cảm xúc: đây là scope, đây là rủi ro nếu cắt test, đây là ba option (ship partial / lùi date / thêm người). Đưa trade-off để business chọn — đừng chỉ nói "không" hay lặng lẽ trễ. + +- **Q: Xử lý một junior liên tục break build thế nào?** + A: Không phải chì chiết. Pair một lần, thêm pre-push check hoặc CI gate họ không bypass được, và làm cho failure rẻ và local. Cách của senior là sửa _hệ thống_ (cái guardrail), không phải _con người_. + +- **Q: "Đi qua một quyết định khó bạn từng làm." Câu trả lời tốt là gì?** + A: Một quyết định thật với một cái giá thật — bạn chọn X, chấp nhận Y là downside, và nêu metric bạn sẽ canh để biết mình sai. Những câu "tôi quyết định refactor" mù mờ không nêu downside đọc như mid-level. + +- **Q: Giao tiếp tin xấu (outage, trễ date) với non-engineer thế nào?** + A: Trực tiếp, sớm, kèm impact và kế hoạch — không jargon, không giấu. "Search degraded cho ~5% user, chúng tôi cô lập vào indexer, ETA 30 phút, đây là customer-facing message." Bình tĩnh và cụ thể đánh bại "chúng tôi đang xử lý". + +### Senior — thiết kế & bảo vệ + +- **Q: Team bạn kẹt giữa "ship Thứ Sáu" và "làm cho đúng." Bạn là senior — thực sự làm gì?** + A: Từ chối cái nhị phân giả. Cắt việc: ship 80% an toàn Thứ Sáu, flag 20% rủi ro thành follow-up có owner và date, và nói rõ cái debt bạn đang gánh. Quyết định được ghi chép, không thì thầm. Nêu metric mà tại đó bạn sẽ từ chối ship. + +- **Q: Hai senior bất đồng kiến trúc trước team. Bạn xử lý sao?** + A: Biến nó thành quyết định, không phải tranh luận: mỗi người nêu trade-off, bạn time-box cuộc cãi, và bạn chốt (hoặc escalate kèm recommend rõ). Một team xem leader cãi không hồi kết học được rằng consensus là tùy chọn và chẳng ship gì. Senior sở hữu cú chốt và giải thích _lý do_, không chỉ verdict. + +- **Q: Bạn kế thừa một on-call rotation mà ai cũng burn out. Sửa.** + A: Coi là bài toán hệ thống: page có thật hay noise? Thêm alert tuning + runbook để page 2h sáng actionable. Share tải, cap ca liên tiếp, và — quan trọng nhất — sửa cái top-repeat offender để volume giảm. Burnout thường là _tín hiệu của hệ thống xấu_, không phải engineer yếu. + +- **Q: "Kể về lúc bạn nâng tầm một teammate." Chứng minh.** + A: Một câu chuyện cụ thể: bạn thấy một gap (họ sợ quy trình deploy), bạn pair/shadow/viết runbook, và sáu tuần sau họ own nó solo. Bar senior không phải "tôi thông minh" — là "người quanh tôi tốt lên vì cách tôi làm việc". + +- **Q: Một stakeholder đòi một tính năng là ý tồi. Bạn nói gì?** + A: "Đây là gì tôi sẽ build thay và tại sao — rủi ro tôi thấy ở cái gốc là X, với con số này đằng sau. Nếu bạn vẫn muốn cái gốc, tôi build, nhưng tôi muốn bạn thấy trade-off trước." Bạn tôn trọng quyết định trong khi làm chi phí visible. Đó là senior, không phải phục tùng. + +#### Tự kiểm tra + +- [ ] Junior: tại sao có behavioral, junior vs senior một câu, trade-off là gì, ownership nghĩa là gì, vì sao "tôi không biết" hợp lệ. +- [ ] Mid: một lỗi-thật-có-cấu-trúc, push back bằng data, sửa build-breaker qua guardrail, câu chuyện quyết định khó có downside, giao tiếp tin xấu rõ ràng. +- [ ] Senior: từ chối nhị phân ship-vs-đúng bằng cách cắt, giải quyết bất đồng lãnh đạo thành quyết định, sửa on-call burnout như hệ thống, chứng minh bạn nâng tầm người, surface một ý-tồi trade-off một cách tôn trọng. + ## 1. Narrate trade-off — hình dạng của một câu trả lời senior Senior không trả lời "cái nào hơn?" bằng một cái tên. Họ nói: _"tùy thuộc — đây là trade-off, và với X tôi chọn Y vì…"_ Câu đó là cả buổi phỏng vấn được cô đặc lại. Phỏng vấn viên không chấm lựa chọn của bạn; họ chấm _hình dạng_: bạn có biết mỗi phương án phải trả giá gì, và có gắn lựa chọn vào một ràng buộc cụ thể không? diff --git a/src/data/blog/vi/interview/spring-boot-senior.md b/src/data/blog/vi/interview/spring-boot-senior.md index cae9f82..e022a75 100644 --- a/src/data/blog/vi/interview/spring-boot-senior.md +++ b/src/data/blog/vi/interview/spring-boot-senior.md @@ -15,6 +15,67 @@ tags: > Tư duy: nêu được tên annotation thì bạn ở tầm mid-level. Đi qua nội tại proxy với một failure mode production và con số thật, bạn đã vượt bar. Mỗi phần dưới đây đều kết bằng bài tập phỏng vấn viên thực sự hay chạy. +## Thang câu hỏi phỏng vấn (Junior → Mid → Senior) + +> Tự drill to tiếng. Junior = "bạn có biết khái niệm"; Mid = "bạn có biết tradeoff"; Senior = "bạn có thể bảo vệ quyết định dưới áp lực, kèm một con số và một postmortem." + +### Junior — nền tảng + +- **Q: IoC / DI là gì, bằng lời đơn giản?** + A: Inversion of Control nghĩa là framework tạo và wire các object thay vì bạn tự `new`. Dependency Injection là cơ chế — dependency được truyền vào (constructor) thay vì tự fetch. Container sở hữu lifecycle; bạn khai báo thứ bạn cần. + +- **Q: Khác nhau giữa `@Component`, `@Service`, `@Repository`, `@Controller`?** + A: Đều là stereotype đăng ký một bean; cái cụ thể là ngữ nghĩa + thêm behavior (`@Repository` dịch JDBC exception thành Spring's `DataAccessException`). Dùng đúng cái để intent rõ và AOP áp dụng đúng. + +- **Q: `@Autowired` là gì và alternative hiện đại?** + A: `@Autowired` inject một bean by type (field/setter/constructor). Mặc định hiện đại là **constructor injection** (không cần annotation trên class đơn-constructor) — testable, immutable, và fail fast lúc startup nếu thiếu dependency. + +- **Q: `@SpringBootApplication` làm gì?** + A: Là ba annotation trong một: `@Configuration` (beans), `@EnableAutoConfiguration` (default ma thuật từ classpath), và `@ComponentScan` (tìm bean trong cây package). Đó là lý do thả một starter lên classpath là bật một feature. + +- **Q: Khác nhau giữa `@RequestParam` và `@PathVariable`?** + A: `@RequestParam` bind một query param (`?id=5`); `@PathVariable` bind một phần của URL path (`/users/5`). Nhầm hai cái là bug junior kinh điển — và `@PathVariable` là cách bạn dựng REST resource URL. + +### Mid — tradeoff & bẫy + +- **Q: Tại sao `@Transactional` im lặng không chạy trên self-invocation?** + A: Transaction advice là một _proxy_ quanh bean; một call nội bộ `this.method()` bypass proxy, nên không transaction nào bắt đầu. Sửa: chuyển method sang bean khác, hoặc inject self-proxy (`AopContext`) — nhưng sửa thật là cấu trúc, không phải trick. Đây là bug #1 "tại sao rollback của tôi không xảy ra". + +- **Q: Giải thích bean lifecycle trong hai câu interviewer tin.** + A: Instantiate → populate dependency (inject) → chạy `BeanPostProcessor`s (vd `@PostConstruct`, những cái áp proxy AOP) → sẵn sàng. "Hai loại post-processor" là key: một vài configure bean, một vài wrap chúng trong proxy — và AOP chỉ chạy vì proxy được áp ở bước đó. + +- **Q: Khác nhau giữa `@Transactional(propagation=...)` người ta thực sự cần?** + A: `REQUIRED` (join hoặc create — mặc định), `REQUIRES_NEW` (luôn txn mới, suspend cái ngoài — dùng cho audit log phải sống sót khi outer rollback), và `NOT_SUPPORTED` (chạy không txn). Bẫy: `REQUIRES_NEW` cho một inner call mà _nên_ rollback với outer thì lại commit im lặng. + +- **Q: Tại sao field injection (`@Autowired` trên field) bị chê?** + A: Nó untestable (không pass mock không qua reflection), mutable (field có thể gán lại), và giấu dependency bắt buộc. Constructor injection làm contract rõ và object valid từ lúc construct — immutable by default. + +- **Q: Khác nhau giữa `@Controller` và `@RestController`?** + A: `@Controller` trả về một view name (server-rendered); `@RestController` là `@Controller` + `@ResponseBody` — nó serialize giá trị trả về (JSON) thẳng vào body. Với một API, `@RestController` là lựa chọn ngày thường. + +### Senior — thiết kế & bảo vệ + +- **Q: Một transaction giữ DB connection trong khi gọi một partner API chậm — pool cạn và whole service ngã. Đi vụ đó.** + A: `@Transactional` mặc định wrap _toàn bộ_ method, gồm cả HTTP call, nên connection bị pin cho latency của partner. Sửa: giữ transaction chặt — load data, commit, _rồi_ gọi API (hoặc làm ở một method riêng không transactional). Size pool bằng `rps × hold_time` và cô lập call chậm trên pool riêng có deadline. + +- **Q: Auto-configuration "ma thuật" bật một thứ bạn không muốn. Tìm và tắt thế nào?** + A: `spring-autoconfigure-metadata` + `Condition`s quyết định gì bật; `spring.autoconfigure.exclude` tắt một cái cụ thể, và `@ConditionalOnMissingBean` là lý do bean của bạn override default. Cách của senior là đọc class auto-config của starter, không đoán — và ưu tiên config tường minh cho thứ nhạy cảm bảo mật. + +- **Q: Bạn có 200 `@Bean` method và startup mất 40 s. Cắt thế nào?** + A: Lazy initialization (`spring.main.lazy-initialization=true`) trì hoãn tạo bean đến lần dùng đầu; giữ component-scan scope chặt; và tìm những bean làm I/O lúc construct (một connection test trong `@PostConstruct` là một startup tax). Trung thực: 40 s có thể chấp nhận cho monolith — đo trước khi optimize. + +- **Q: `@Async` method không chạy async. Tại sao, và sửa?** + A: `@Async` cần một proxy _và_ config `@EnableAsync` với một task executor; self-invocation bypass proxy (cùng bẫy `@Transactional`), và default executor là `SimpleAsyncTaskExecutor` single-thread (không pooled — nó spawn một thread mỗi call). Sửa: enable nó, inject một `ThreadPoolTaskExecutor` thật, và gọi từ bean khác. + +- **Q: Thiết kế một `@RestController` cho money transfer — cross-cutting concern nào bạn KHÔNG bỏ qua?** + A: Idempotency key (duplicate POST = một transfer), input validation (`@Valid`), authorization (user này có quyền?), một transaction boundary không gồm call notification, structured logging với trace id, và một error contract rõ. Dấu hiệu senior: endpoint phần lớn là guardrail quanh một core business nhỏ xíu. + +#### Tự kiểm tra + +- [ ] Junior: IoC/DI bằng lời đơn giản, stereotype annotations, `@Autowired` vs constructor injection, `@SpringBootApplication` làm gì, `@RequestParam` vs `@PathVariable`. +- [ ] Mid: vì sao self-invocation phá `@Transactional`, hai phase post-processor, propagation settings cắn, tại sao field injection tệ, `@Controller` vs `@RestController`. +- [ ] Senior: kể vụ transaction-giữ-connection + sửa, tắt unwanted auto-config bằng cách đọc nó, cắt startup time có đo, sửa `@Async` không chạy, liệt kê guardrail một money-transfer endpoint cần. + ## 1. IoC và DI — container là một hợp đồng, không phải cái ngăn kéo Inversion of Control là _ai sở hữu `new`_. Dependency Injection là _wiring được chuyển tới bằng cách nào_. Cùng nhau chúng trả lời "ai tạo object này và khi nào" — container sở hữu đồ thị, bạn khai báo dependency, nó thỏa mãn. Chiều sâu nằm ở hai quyết định phát sinh: bạn nhận dependency theo cách nào, và bạn trao gì cho một bean sống lâu hơn scope của nó. diff --git a/src/data/blog/vi/interview/system-design-senior.md b/src/data/blog/vi/interview/system-design-senior.md index 866e4a7..3a14f32 100644 --- a/src/data/blog/vi/interview/system-design-senior.md +++ b/src/data/blog/vi/interview/system-design-senior.md @@ -17,6 +17,67 @@ Junior vẽ ô vuông. Senior kể chuyện tradeoff: "Tôi cache 1% key nóng t > Tư duy: nhả ra một diagram thì bạn chỉ ở tầm mid-level. Đi qua một tradeoff bằng số thật và một failure mode ngoài production, và bạn chạm được ô "senior". Bạn không cần thiết kế Twitter — bạn cần thiết kế đúng phần của Twitter sẽ gãy đầu tiên, và nói to điều đó ra. +## Thang câu hỏi phỏng vấn (Junior → Mid → Senior) + +> Tự drill to tiếng. Junior = "bạn có biết khái niệm"; Mid = "bạn có biết tradeoff"; Senior = "bạn có thể bảo vệ quyết định dưới áp lực, kèm một con số và một postmortem." + +### Junior — nền tảng + +- **Q: Các bước của một câu trả lời system-design?** + A: Làm rõ yêu cầu (functional + non-functional: scale, latency, consistency) → ước lượng capacity → phác họa component cấp cao → đào sâu 1-2 phần khó nhất → nêu failure mode. Interviewer chấm _hình dạng_ tư duy, không phải diagram "đúng". + +- **Q: Khác nhau giữa latency và throughput?** + A: Latency = thời gian cho một request (ms); throughput = bao nhiêu request mỗi giây (req/s). Một hệ có thể latency thấp nhưng throughput thấp (single-threaded) hoặc throughput cao nhưng tail latency cao (một queue). Bạn tối ưu chúng bằng các đòn bẩy khác nhau. + +- **Q: Cache là gì và tại sao dùng?** + A: Một store nhanh (RAM) giữ kết quả của việc tính toán đắt đỏ (DB query, compute) nên read lặp rẻ. Điểm: hầu hết read traffic đánh vào một hot set nhỏ, nên cache biến một path bound-DB thành path memory (micro-giây vs milli-giây). + +- **Q: SQL vs NoSQL — khi nào chọn cái nào?** + A: Relational khi cần ACID + join + query linh hoạt trên data có cấu trúc. NoSQL (document/columnar/KV) khi cần horizontal scale trên một access pattern đơn giản (single-key lookup, write volume lớn). Chọn bằng _access pattern_, không phải hype. + +- **Q: Khác nhau giữa horizontal và vertical scaling?** + A: Vertical = box to hơn (thêm CPU/RAM, gặp trần, downtime để resize). Horizontal = nhiều box sau một load balancer (gần không giới hạn, cần statelessness + shared storage). Mặc định senior cho service stateless là horizontal. + +### Mid — tradeoff & bẫy + +- **Q: Cache aside vs write-through — khi nào dùng cái nào?** + A: Cache-aside (app read cache, miss thì load DB và populate): đơn giản, xử lý cold cache đẹp, nhưng một miss có thể stampede. Write-through (write đi vào cache + DB cùng lúc): read luôn nhanh, nhưng mọi write trả giá cache cost. Bẫy: chọn một cái mà không nêu write/read ratio của workload. + +- **Q: "Stale 60 giây là ổn." Giờ thiết kế cache invalidation.** + A: TTL-based (expire sau 60 s) đơn giản nhất; event-based invalidation (khi write, purge key) tươi hơn nhưng cần một event tin cậy. Senior nêu _stale-read window_ business chấp nhận và thiết kế tới nó — và biết "cache invalidation" là bài toán khó kinh điển vì delete đua với write. + +- **Q: CAP theorem — chọn hai, và thực sự nghĩa là gì?** + A: Dưới một network partition bạn đánh đổi Consistency (mọi node thấy cùng data) lấy Availability (mọi request có response). CP systems (vd strongly-consistent DBs) reject trong partition; AP systems (vd Dynamo-style) phục vụ stale-but-present. "Chọn hai" thực sự là "bạn hy sinh gì _trong lúc partition_". + +- **Q: Bạn sẽ shard một bảng user 10 TB thế nào?** + A: Bằng một shard key (hash user_id) để mỗi shard sở hữu một range key và query nằm single-shard. Bẫy: một key tệ (signup-date) tạo hot shard; một join cross-shard thành scatter-gather. Nêu key, kế hoạch reshard, và cross-shard query bạn sẽ tránh. + +- **Q: Gì gãy đầu tiên ở 10× traffic — và biết trước khi xảy ra thế nào?** + A: Thường là một shared resource duy nhất: một DB, một cache, một downstream. Bạn không đoán — bạn load-test để tìm cái knee, và thêm circuit breaker + backpressure để một dependency chậm degrade nhẹ thay vì cascade. Nêu _một_ resource bạn sẽ canh. + +### Senior — thiết kế & bảo vệ + +- **Q: Thiết kế một URL shortener cho 100M link mới/ngày, 1B read/ngày. Size nó.** + A: Writes ~1,2k/s, reads ~11,5k/s. Một key 7-char base62 = ~3,5 nghìn tỷ combo — dư sức. Storage: 1B link × ~500 B = 500 GB + replica. Reads áp đảo, nên cache 1% nóng trong Redis (phục vụ ~99% reads). Cách của senior là nêu bottleneck (read path) và giải quyết _cái đó_, không over-build. + +- **Q: Một cache stampede vừa làm đổ DB của bạn trên một hot key. Đi vụ đó và cách sửa.** + A: Một hot key expire; 10k request cùng miss, cùng đánh DB, nó ngã. Sửa: request coalescing (single-flight — một request load, những cái khác chờ), jittered TTL (key không cùng expire một lúc), và một hot-key local cache. Postmortem: miss path, không phải cache, là nguy hiểm. + +- **Q: Thiết kế cho "99,99% available" — thực sự tốn gì?** + A: 99,99% = ~52 phút downtime/năm. Nó ép multi-AZ (một AZ chết, bạn sống), không single point of failure, và automated failover. Trade-off: 99,99% tốn nhiều hơn 99,9% (redundancy, runbook, game-day). Judgment senior: price cái SLA và để business chọn, đừng gold-plate mặc định. + +- **Q: Bạn cần strongly-consistent cross-region writes. Bảo vệ thiết kế.** + A: Đắt: synchronous replication xuyên region thêm inter-region latency (hàng chục ms) vào mọi write, và một partition nghĩa là unavailability. Câu trả lời senior thường là "đừng" — giữ authoritative write ở một region, replicate async cho read, và chỉ trả giá consistency cho những record cụ thể cần nó (vd balance), không phải whole system. + +- **Q: "Cache ngây thơ là nơi ẩn náu của outage." Cho một ví dụ cụ thể.** + A: Một cache cache cả _error_ hoặc _empty result_ — một DB hiccup ngắn giờ phục vụ "not found" 60 s, nên user thấy data thiếu ngay cả khi DB đã hồi phục. Hoặc một cache trả giá stale trong flash sale và oversell. Thiết kế senior coi cache như một _bản copy có freshness contract_, không phải source of truth, và test stale window rõ ràng. + +#### Tự kiểm tra + +- [ ] Junior: các bước của một câu trả lời design, latency vs throughput, cache là gì, SQL vs NoSQL, horizontal vs vertical scaling. +- [ ] Mid: cache-aside vs write-through, thiết kế invalidation tới một staleness SLA, CAP dưới partition, chọn shard-key + reshard, tìm resource gãy đầu tiên. +- [ ] Senior: size một URL shortener end-to-end, kể + sửa một cache stampede, price một SLA 99,99%, bảo vệ cross-region consistency (thường "đừng"), chỉ mặt chỗ ẩn náu của cache-outage. + ## 1. Vòng lặp phỏng vấn — họ thực sự chấm điểm cái gì Vòng lặp trông như năm bước tuần tự. Đúng vậy, nhưng thứ tự đó chỉ là ngụy trang — bảng điểm được điền trong mười giây đầu của mỗi bước.