11 تمرین ۱ — معماری یک فروشگاه آنلاینExercise 1 — an online shop’s architecture
بازگشت به صورت تمرینBack to the exercise · ارزیابی و سطحAssessment and level
پاسخ ۶۰ثانیهایThe 60-second answer
صورت مسئله و فرضهاProblem and assumptions
- نوشتن سفارش ≈ ۲۳ در ثانیه میانگین و ≈ ۲۳۱ در اوج (نرخهای آموزشی از حساب فصل ۲).
- اهداف تازگی قراردادهای آموزشیاند: کش ثانیهها، جستوجو دهها ثانیه، گزارش دوره بعد، مدل بازآموزی بعد.
- مصرفکنندهها at-least-once هستند؛ هر بهروزرسانی نما باید idempotent و قابل بازپخش باشد.
- Order write ≈ 23/s average, ≈ 231/s peak (teaching rates from chapter 2 math).
- Freshness targets are teaching contracts: cache seconds, search tens of seconds, report next period, model next retrain.
- Consumers are at-least-once; every view update must be idempotent and replayable.
چرا این روش و این عددها؟Why this method and these numbers?
چرا outbox؟ چون commit سفارش و انتشار پیام دو عمل جدا هستند و crash میتواند بینشان بیفتد؛ ثبت تعهد انتشار در همان تراکنش این شکاف را میبندد. تازگی کش چند ثانیه و جستوجو چند ده ثانیه فرض محصولاند: صفحهٔ تأیید سفارش از مرجع میخواند، ولی کشف سفارش در جستوجو میتواند عقب بماند. اگر کاربر برای تصمیم مالی از جستوجو استفاده کند، این فرض دیگر کافی نیست و مسیر خواندن باید قویتر شود.
Why an outbox? Order commit and message publication are separate actions; a crash can fall between them. Persisting publication intent in the order transaction closes that gap. Seconds for cache freshness and tens of seconds for search are product assumptions: confirmation reads the authority while discovery can lag. Financial decisions through search require a stronger read contract.
مدل ذهنیMental model
حل گامبهگامStep by step
- نام بردن مرجع پایگاه سفارشها = مرجع: تنها جایی که «سفارش ثبت شد» رسمیت پیدا میکند. نوشتن همگام و بادوام (تراکنش + تکثیر حداقلی).
- کش: مشتق ناپایدار کش وضعیت = مشتق ناپایدار: فقط خواندن را سریع میکند. با سفارش تازه ابطال یا پرشدن دوباره؛ از دست رفتنش بیضرر.
- جستوجو: مشتق قابل بازسازی ایندکس جستوجو = مشتق قابل بازسازی: از رویداد «سفارش ایجاد شد» تغذیه میشود؛ تأخیر چندثانیهای مجاز ولی تحویل باید حتمی باشد.
- گزارش و مدل: مشتقهای دستهای گزارش ماهانه = مشتق دستهای روی بازه بسته؛ مدل پیشنهاد = مشتق آماری با بازآموزی دورهای. تکسفارش هیچکدام را تکان نمیدهد.
- یکبار انتشار، مصرف idempotent انتشار: فقط مرجع همگام؛ بقیه ناهمگام از یک لاگ تغییر (outbox/CDC) با مصرف idempotent. commit بدون انتشار = relay تلاش میکند، نه نوشتن نصفه.
- Name the record Orders database = record: the only place where 'the order exists' becomes official. Synchronous, durable writes (transaction + minimal replication).
- Cache: volatile derivative Status cache = volatile derivative: only speeds reads. On a new order, invalidate or refill; losing it is harmless.
- Search: rebuildable derivative Search index = rebuildable derivative: fed by the 'order created' event; seconds of lag are acceptable but delivery must be eventual and reliable.
- Report and model: batch derivatives Monthly report = batch derivative over a closed window; recommendation model = statistical derivative with periodic retraining. One order moves neither.
- Publish once, consume idempotently Publication: synchronous to the record only; everything else async from one change log (outbox/CDC) with idempotent consumption. Commit-without-publication = the relay retries, never a half-write.
مثال محاسبهشدهWorked example
اندازههای آموزشی: سطر سفارش ≈ ۱ کیلوبایت در مرجع؛ همان سفارش بهصورت سند جستوجو ≈ ۲ کیلوبایت، ورودی کش ≈ ۰٫۵ کیلوبایت، سطر گزارش ≈ ۰٫۲ کیلوبایت. در اوج ۲۳۱ سفارش در ثانیه، لاگ ≈ ۰٫۲ مگابایت در ثانیه حمل میکند — ناچیز. قراردادهای تازگی (آموزشی): کش ≤ ۵ ثانیه با ابطال کلید، جستوجو ≤ ۶۰ ثانیه با مصرف رویداد، گزارش = بچ شبانه بعد، مدل = بازآموزی هفتگی بعد. حداقل معماری: یک primary + یک standby، یک لاگ بادوام، یک صف برای هر گروه مصرفکننده — هر چیز بیشتر دلیل نامدار میخواهد.
Teaching sizes: an order row ≈ 1 KB in the record; the same order as a search document ≈ 2 KB, as a cache entry ≈ 0.5 KB, as a report row ≈ 0.2 KB. At 231 orders/s peak, the log carries ≈ 0.2 MB/s — trivial. Freshness contracts (teaching): cache ≤ 5 s via key invalidation, search ≤ 60 s via event consume, report = next nightly batch, model = next weekly retrain. Minimum architecture: one primary + one standby, one durable log, one queue per consumer group — anything more needs a named reason.
دانلود نمودار مستقلDownload standalone diagram
- بالا: Client با POST /orders به Order API میزند — تنها فراخوانی همگام کل داستان.
- API سطر سفارش بهعلاوه سطر outbox را اتمیک در Orders DB ثبت میکند، commit ok میگیرد و 201 + order id برمیگرداند. نوشتن همگام همینجا تمام میشود.
- Outbox Relay لاگ را از offset نگهداشتهشده poll میکند، رویدادهای order-created شماره ۱ تا ۵ را میگیرد و رویدادهای ۱ و ۲ از ۵ را به Search Consumer منتشر میکند.
- جریان میشکند: relay با offset نگهداشتهشده crash میکند. بعد از restart از همان offset ادامه میدهد، لاگ رویدادهای ۱ تا ۵ را دوباره تحویل میدهد و relay هر ۵ را با تکراریها بازنشر میکند.
- مصرفکننده با کلید شناسه سفارش در Search Index بهروزرسانی upsert میکند و ok, duplicates harmless میگیرد — پنج رویداد دقیقاً روی پنج سفارش ایندکسشده همگرا میشوند.
- Top: the Client sends POST /orders to the Order API — the only synchronous call in the whole story.
- The API commits the order row plus its outbox row atomically in the Orders DB, gets commit ok, and returns 201 + order id. The synchronous write ends here.
- The Outbox Relay polls the log from its kept offset, receives order-created events 1-5, and publishes events 1-2 of 5 to the Search Consumer.
- The stream breaks: the relay crashes with its offset kept. On restart it resumes from the kept offset, the log redelivers events 1-5, and the relay republishes all 5, duplicates included.
- The consumer upserts by order id into the Search Index and gets ok, duplicates harmless — five events converge on exactly five indexed orders.
نقطهٔ تصمیم: نقطه تصمیم پیکان commit اتمیک است: سطر سفارش بهعلاوه سطر outbox در یک تراکنش. هرچه پاییندستش است — pollها، انتشارها، تحویلهای دوباره — قابل تلاش مجدد است.
رد خرابی: شکست چیندار را در باند Crash + recovery دنبال کن: دو رویداد منتشر شده، بعد سکوت — offset نگهداشتهشده همان چیزی است که restart را از ادامه ممکن میکند نه از صفر. زنجیره چیندار دوم (redeliver و republish) همان مسیر تکراریها-بیضررند است.
تضمین نشاندادهشده: شکل ترتیب را تضمین میکند: هیچ نمایی قبل از commit مرجع نوشته نمیشود و هر سفارش commitشده بالاخره به مصرفکننده میرسد. سقف مدتش را تضمین نمیکند.
تضمین نشانندادهشده: سقفهای تازگی و سه نمای دیگر بیرون این شکلاند — مال گام بعدند. تحویل exactly-once هم ادعا نشده: تکراریها رخ میدهند و با upsert جذب میشوند.
Decision point: The decision point is the atomic-commit arrow: order row plus outbox row in one transaction. Everything downstream of it — polls, publishes, redeliveries — is retryable.
Failure trace: Trace the dashed break in the Crash + recovery band: two events published, then silence — the kept offset is what lets restart resume instead of starting over. The second dashed chain (redeliver, republish) is the duplicates-are-harmless path.
Guarantee shown: The figure guarantees order: no view is written before the record commits, and every committed order eventually reaches the consumer. It does not bound how long that takes.
Not shown: Freshness bounds and the other three views are outside this figure — they belong to the next step. Exactly-once delivery is not claimed either: duplicates happen and are absorbed by upsert.
خودآزماییSelf-check
اگر relay بعد از انتشار ۲ رویداد از ۵ بمیرد، در restart چه میخواند و چرا تکراریهای تحویلدوباره ایندکس را خراب نمیکنند؟
پاسخ: از offset نگهداشتهشده poll را ادامه میدهد، پس لاگ رویدادهای ۱ تا ۵ را دوباره تحویل میدهد و هر پنج را بازنشر میکند. ایندکس درست میماند چون مصرفکننده با کلید شناسه سفارش upsert میکند — رویداد تکراری همان سطر را بازنویسی میکند نه اینکه سطر دوم بسازد.
The relay crashes after publishing 2 of 5 events. What does it read on restart, and why don't the redelivered duplicates corrupt the index?
Answer: It resumes polling from its kept offset, so the log redelivers events 1-5 and it republishes all five. The index stays correct because the consumer upserts by order id — a repeated event rewrites the same row instead of adding a second one.
دانلود نمودار مستقلDownload standalone diagram
- چپ: Orders DB، تنها جعبه حقیقت. هر commit در همان تراکنش یک سطر outbox میافزاید — بهازای هر commit یک پیکان به لاگ.
- وسط: لاگ Outbox / CDC، یک جریان مرتب. گذرگاه عمودی برای هر نما یک انشعاب میاندازد — هر نما همان انتشار واحد را میخواند، هرکدام با سرعت خودش.
- انشعابهای بالا: Status Cache با کلید ابطال میشود، تازگی ظرف ۵ ثانیه (آموزشی)؛ Search Index رویداد را مصرف میکند، تازگی ظرف ۶۰ ثانیه (آموزشی).
- انشعابهای پایین: Monthly Report استخراج بچ شبانه میبرد، تازگی بچ بعد (آموزشی)؛ Recommender Model خوراک بازآموزی هفتگی میبرد، تازگی بازآموزی بعد (آموزشی). یک سفارش هیچکدام را تکان نمیدهد.
- هر انشعاب میتواند بازپخش کند: کش یا ایندکس انداختهشده لاگ را از قدیمیترین offset لازم دوباره میخواند — افق نگهداری کران میکند بازسازی تا کجا به عقب میتواند برود.
- Left: the Orders DB, the only truth box. Each commit appends an outbox row in the same transaction — one arrow into the log per commit.
- Middle: the Outbox / CDC Log, one ordered stream. The vertical bus drops one tap per view — every view reads the same publication, each at its own pace.
- Top taps: the Status Cache invalidates by key, fresh within 5 seconds (teaching); the Search Index consumes the event, fresh within 60 seconds (teaching).
- Bottom taps: the Monthly Report takes a nightly batch extract, fresh next batch (teaching); the Recommender Model takes a weekly retrain feed, fresh next retrain (teaching). One order moves neither.
- Any tap can replay: a dropped cache or index re-reads the log from the oldest needed offset — the retention horizon bounds how far back a rebuild can go.
نقطهٔ تصمیم: نقطه تصمیم لاگ واحد وسط است: تازگی قرارداد هر نما روی انتشار مشترک است، نه خاصیت مسیر نوشتن. نماهای سریع هزینه کار بهازای رویداد میدهند؛ نماهای کند هزینه کار دستهای.
رد خرابی: commit بدون انتشار را دنبال کن: جعبه حقیقت تیره است ولی انشعابها صبر میکنند — relay تا جاری شدن لاگ تلاش میکند و نماها عقب میمانند بعد همگرا میشوند. بعد ایندکس پاکشده را دنبال کن: فقط جعبهاش تیره میشود و انشعابش با خواندن دوباره لاگ رنگش میکند.
تضمین نشاندادهشده: شکل انتشار واحد مرتب را تضمین میکند: هر نما بالاخره هر سفارش commitشده را به ترتیب لاگ میبیند. سقف lag نما را تضمین نمیکند — چهار قرارداد میکنند و هرکدام باید اندازهگیری شود.
تضمین نشانندادهشده: snapshot سازگار روی نماها در یک لحظه بیرون این شکل است — نماها از قصد با سرعتهای متفاوت همگرا میشوند.
Decision point: The decision point is the single log in the middle: freshness is a per-view contract on a shared publication, not a property of the write path. Fast views pay per-event work; slow views pay batch work.
Failure trace: Trace commit-without-publication: the truth box is dark while the taps wait — the relay retries until the log flows, and views lag then converge. Then trace a wiped index: only its box goes dark, and its tap re-reads the log to repaint it.
Guarantee shown: The figure guarantees single ordered publication: every view eventually sees every committed order in log order. It does not bound view lag — the four contracts do, and each must be measured.
Not shown: A consistent snapshot across views at one instant is outside this figure — views converge at different speeds by design.
خودآزماییSelf-check
کدام نما سفارش تازه را اول میبیند و کدام آخر، و سازوکار کران هرکدام چیست؟
پاسخ: اول کش (ابطال کلید، ظرف ۵ ثانیه، آموزشی)؛ بعد جستوجو (مصرف رویداد، ظرف ۶۰ ثانیه، آموزشی)؛ بعد گزارش (بچ شبانه بعد) و آخر مدل (بازآموزی هفتگی بعد). هر چهار سازوکار را بگو — فقط «ناهمگام» جواب نیست.
Which view sees a new order first and which last, and what mechanism bounds each?
Answer: First the cache (key invalidation, within 5 seconds, teaching); then search (event consume, within 60 seconds, teaching); then the report (next nightly batch) and last the model (next weekly retrain). State all four mechanisms — async alone is not an answer.
دانلود SVG مستقلDownload standalone SVG
- چپ: پایگاه سفارشها، تنها جعبه تیره — system of record. نوشتن همگام اینجا تمام میشود.
- وسط: لاگ outbox/CDC — بهازای هر commit یک پیکان داخل، یک جریان مرتب بیرون. relay تا تحویل تلاش میکند.
- راست: چهار نما — کش (ابطال)، جستوجو (مصرف)، گزارش (دستهای)، مدل (بازآموزی) — هرکدام با برچسب قرارداد تازگیاش.
- پیکان چین: مسیر بازپخش از لاگ به هر نما — داستان بازسازی ایندکس یا کش انداختهشده.
- Left: the orders database, the only dark box — the system of record. The synchronous write ends here.
- Middle: the outbox/CDC log — one arrow in per commit, one ordered stream out. The relay retries until delivery.
- Right: the four views — cache (invalidate), search (consume), report (batch), model (retrain) — each labelled with its freshness contract.
- Dashed arrow: the replay path from the log to any view — the rebuild story for a dropped index or cache.
نقطهٔ تصمیم: نقطه تصمیم سطر outbox داخل تراکنش سفارش است: «دو نوشتن» را به «یک نوشتن اتمیک بهعلاوه relay» تبدیل میکند. هر پیکان پاییندستش قابل تلاش مجدد است.
رد خرابی: commit بدون انتشار را روی شکل دنبال کن: جعبه سفارش تیره است (commit شده) ولی پیکان لاگ چشمک میزند — جعبه relay تا جاری شدن پیکان تلاش میکند. هر چهار نما صبر میکنند؛ هیچکدام داده نمیسازند. بعد ایندکس گمشده را دنبال کن: فقط جعبهاش خاکستری میشود و پیکان بازپخش از لاگ دوباره رنگش میکند.
تضمین نشاندادهشده: شکل ترتیب انتشار واحد را نشان میدهد: هیچ نمایی قبل از commit مرجع بهروز نمیشود. سقف lag نما را نشان نمیدهد — قراردادها نشان میدهند و lag هر نما باید اندازهگیری شود.
تضمین نشانندادهشده: تراکنشهای چندسفارشی و اثرهای exactly-once به دنیای بیرون بیرون این شکلاند.
Decision point: The decision point is the outbox row inside the order transaction: it turns 'two writes' into 'one atomic write plus a relay'. Every arrow downstream of it is retryable.
Failure trace: Trace commit-without-publication on the figure: the order box is dark (committed) while the log arrow blinks — the relay box retries until the arrow flows. All four views wait; none invents data. Then trace a lost index: only its box goes grey, and the replay arrow from the log repaints it.
Guarantee shown: The figure shows single-publication ordering: no view updates before the record commits. It does not bound view lag — the contracts do, and lag must be measured per view.
Not shown: Cross-order transactions and exactly-once effects on the outside world are outside this figure.
خودآزماییSelf-check
اگر ایندکس جستوجو کامل پاک شود، چطور بدون از دست رفتن سفارش برمیگردی؟
پاسخ: ایندکس جستوجو را کامل پاک و از لاگ بدون هیچ سفارش گمشده بازسازی کن — بعد بگو لاگ باید کدام offsetها را نگه دارد تا این ممکن شود و سفارشهای قدیمیتر از افق نگهداری چه میشوند.
If the search index is fully wiped, how do you recover with zero order loss?
Answer: Wipe the search index and rebuild it from the log with zero order loss — then state which offsets the log must retain to make that possible, and what happens to orders older than the retention horizon.
گزینههای جایگزینAlternatives
- نوشتن دوگانه همگام (مرجع + کش/ایندکس در یک درخواست): فقط وقتی قابل دفاع است که کپی مشتق کوچک و کماهمیت باشد و تأخیر اضافه بودجه شده باشد — مثلاً ابطال یک کلید کش داخل درخواست. هرگز برای ایندکس جستوجو، گزارش یا مدل.
- Synchronous dual write (record + cache/index in one request): only defensible when the derived copy is tiny, loss-tolerant and the extra latency is budgeted — e.g. invalidating one cache key inside the request. Never for search indexes, reports or models.
سناریوهای خرابیFailure scenarios
- commit بدون انتشار: سطر outbox در همان تراکنش سفارش است، پس relay بعد از crash به تلاش ادامه میدهد. نماها عقب میمانند ولی همگرا میشوند؛ هیچچیز نصفه نوشته نمیشود.
- تحویل تکراری: رویداد تلاششده دوباره تمیز اعمال میشود چون مصرفکنندهها با کلید شناسه سفارش upsert میکنند. حالت مسموم را هم طراحی کن — رویداد خراب بعد از N تلاش به dead-letter میرود و هرگز لاگ را قفل نمیکند.
- از دست رفتن نما: انداختن ایندکس جستوجو یا کش هیچ داده بادوامی را گم نمیکند — لاگ را از قدیمیترین offset لازم بازپخش و بازسازی کن. افق نگهداری که بازپخش را کران میکند اعلام شود.
- Commit without publication: the outbox row is in the same transaction as the order, so the relay keeps retrying after a crash. Views lag but converge; nothing is half-written.
- Duplicate delivery: a retried event re-applies cleanly because consumers key by order id and upsert. Design the poison case too — a malformed event goes to dead-letter after N tries, never blocking the log.
- View loss: dropping the search index or cache loses nothing durable — replay the log from the oldest needed offset and rebuild. State the retention horizon that bounds the replay.
کد قابل اجراRunnable code
اجراشده و تأییدشده در همین محیطExecuted and verified in this environment
این کد آموزشی و سادهسازیشده است؛ فقط برای آشنایی با ایده. کد واقعی پروداکشن ملاحظات بسیار بیشتری دارد و هیچکس اینشکلی کد پروداکشن نمیزند.Simplified teaching code for building intuition only — not production code; nobody ships production code like this.
import java.util.*;
// Teaching demo: atomic commit (order + outbox row), relay with a crash,
// idempotent consumers keyed by order id. No orders lost, no half-views.
public class OutboxDemo {
record Order(long id, String item) {}
record Event(long orderId, long seq) {}
public static void main(String[] a) {
List<Order> record = new ArrayList<>();
List<Event> log = new ArrayList<>();
long seq = 0;
for (long i = 1; i <= 5; i++) { record.add(new Order(i, "item-" + i)); log.add(new Event(i, ++seq)); }
Set<Long> published = new HashSet<>();
int crashAfter = 2, offset = 0, restarts = 0;
Map<Long, String> searchView = new HashMap<>();
int duplicates = 0;
while (offset < log.size()) {
Event e = log.get(offset);
if (published.size() == crashAfter && restarts == 0) { restarts++; continue; }
if (!published.add(e.orderId())) { duplicates++; }
else searchView.put(e.orderId(), "doc-" + e.orderId());
offset++;
}
int before = searchView.size();
for (Event e : log) { if (!published.add(e.orderId())) duplicates++; else searchView.put(e.orderId(), "doc-" + e.orderId()); }
System.out.println("record orders = " + record.size());
System.out.println("log events = " + log.size());
System.out.println("relay restarts = " + restarts);
System.out.println("duplicates seen = " + duplicates);
System.out.println("search docs = " + searchView.size() + " (changed by redelivery: " + (searchView.size() != before) + ")");
if (record.size() != 5 || searchView.size() != 5 || restarts != 1) throw new AssertionError();
System.out.println("OK: no loss, no half-write, redelivery harmless");
}
}
اجرا: javac OutboxDemo.java && java OutboxDemo — فقط استاندارد جاوا، فوری.
خروجی مورد انتظار: record orders=5، log events=5، relay restarts=1، duplicates=5، search docs=5 بدون تغییر با بازپخش؛ OK: no loss, no half-write. (خروجی واقعی همین محیط)
Run: javac OutboxDemo.java && java OutboxDemo — stdlib Java only, instant.
Expected output: record orders=5, log events=5, relay restarts=1, duplicates=5, search docs=5 unchanged by redelivery; OK: no loss, no half-write. (actual output of this environment)
آزمون صحت و معیار عملیاتیCorrectness checks and operational criteria
پاسخ سؤالهای پیگیریFollow-up answers
پیگیری (سینیور): اگر مشتری بلافاصله بعد از ثبت سفارش آن را در جستوجو نبیند، تجربه کاربر را چطور حفظ میکنی؟Follow-up (Senior): How do you preserve the user experience if a new order is not yet visible in search?
هرگز با نوشتن همگام به ایندکس تظاهرش را درنیاور. مسیر ناهمگام را نگه دار و تجربه را جور دیگر حفظ کن: بعد از ثبت، سفارش خود کاربر را از مرجع نشان بده (read-your-writes) با یادداشت «جستوجو ظرف چند ثانیه بهروز میشود»؛ کوئریهای خود کاربر اول به مرجع + حالت pending بروند. قرارداد تازگی صادق میماند و فرد متأثر lag را هرگز نمیبیند.
Never fake it by writing to the index synchronously. Keep the async path and protect the experience another way: after placing the order, show the user their own order from the record (read-your-writes) with a 'search updates within seconds' note; route the user's own queries to record + pending state first. The freshness contract stays honest, and the person affected never sees the lag.
اشتباهات رایجCommon mistakes
- fan-out همگام به مرجع + کش + ایندکس در یک درخواست: یک نمای کند سفارش را fail میکند و crash میان نوشتنها اختلاف دائمی میسازد. اول مرجع، بعد انتشار ناهمگام.
- حقیقت دانستن کش («سریعتر است پس فقط از کش بخوان»): انداختن کش بعد میشود data loss. کش همیشه باید مرجع را پشتش داشته باشد.
- Synchronous fan-out to record + cache + index in one request: one slow view fails the order, and a crash between writes leaves permanent disagreement. Record first, propagate async.
- Treating the cache as truth ('it is faster, so read only from cache'): a dropped cache then becomes data loss. Caches must always have the record behind them.
منابع:Sources: Amazon · SDE III interview preparation · Debezium · outbox event router
22 تمرین ۲ — تصمیم توزیعExercise 2 — the distribution decision
بازگشت به صورت تمرینBack to the exercise · ارزیابی و سطحAssessment and level
پاسخ ۶۰ثانیهایThe 60-second answer
صورت مسئله و فرضهاProblem and assumptions
- رکورد ≈ ۱ کیلوبایت؛ خواندن/نوشتن ≈ ۱۰:۱؛ ضریب اوج ۱۰؛ رشد سالانه ۳۰٪ — همه اعداد آموزشیاند، با ردپای واقعی بازبینی شوند.
- SLO آموزشی: نوشتن سفارش p95 زیر ۳۰۰ms، RPO ≤ ۱ دقیقه، RTO ≤ ۵ دقیقه.
- ماندگاری = قید حقوقی مکانی؛ تحلیلی = نیاز ایزولاسیون بار.
- Record ≈ 1 KB; read/write ≈ 10:1; 10x peak factor; 30% yearly growth — all teaching numbers, revisit with real traces.
- SLO (teaching): p95 order write < 300 ms, RPO ≤ 1 min, RTO ≤ 5 min.
- Residency = legal placement constraint; analytics = workload isolation need.
چرا این روش و این عددها؟Why this method and these numbers?
روزانه دو میلیون، نرخ است نه اندازهٔ cluster: تقسیم بر ۸۶۴۰۰ حدود ۲۳٫۱۵ سفارش/ثانیه و ضرب در ضریب آموزشی ۱۰ حدود ۲۳۱٫۵ میدهد. با رکورد ۱ KB، دادهٔ خام روزانه حدود ۲ GB است؛ index، replica و backup جدا اضافه میشوند. رشد ۳۰٪ با V(t)=V0×1.3^t محاسبه میشود چون هر سال روی حجم سال قبل اعمال میشود. هدف ۳۰۰ms و RPO یک دقیقه/RTO پنج دقیقه از این فرمول نتیجه نمیشوند؛ فرضهای نیاز محصولاند که باید با تحمل تأخیر، زیان داده و زمان توقف تأیید شوند.
Two million per day is a rate, not a cluster size: divide by 86400 for 23.15 orders/s and multiply by the teaching peak factor ten for 231.5/s. At 1 KB/record raw data is about 2 GB/day, before indexes, replicas and backups. Annual 30% growth compounds as V(t)=V0×1.3^t. The 300ms target and one-minute RPO/five-minute RTO are separate product assumptions requiring latency, data-loss and downtime evidence.
مدل ذهنیMental model
حل گامبهگامStep by step
- سایز صادقانه بار اول حساب سرانگشتی: ۲ میلیون در روز ≈ ۲۳ میانگین و ≈ ۲۳۱ اوج در ثانیه؛ ضربدر ۳ پیام داخلی زیر هزار در ثانیه میماند. یک primary سالم این را جذب میکند.
- عملیاتی: تکگره تا tripwire سفارشها (عملیاتی): شروع تکگره + standby برای دسترسپذیری و بکاپ. شارد فقط بعد از tripwire نامدار: توان تکنویسنده، جا شدن دیسک/RAM یا رشد انفجاری قطعی.
- تحلیلی: انبار از روز اول گزارشهای فروش (تحلیلی): از روز اول جدا برای ایزولاسیون بار. استخراج ساعتی/شبانه به انبار ستونی؛ اسکن سنگین هرگز به پایگاه سفارشها نخورد.
- ماندگاری: مکان نه توپولوژی ماندگاری (حقوقی): استقرار منطقهای با مرز روشن؛ فقط داده بیهویت/تجمیعی بیرون میرود. این جواب قانون است نه توان.
- Size the load honestly Rough math first: 2M/day ≈ 23/s average, ≈ 231/s at 10x peak; ×3 internal messages stays under a thousand/s. One healthy primary absorbs this.
- Operational: single until a tripwire Orders (operational): start single-node + standby for availability and backup. Shard only past a named tripwire: single-writer throughput, disk/RAM fit, or certain explosive growth.
- Analytical: warehouse from day one Sales reports (analytical): separate from day one for workload isolation. Hourly/nightly extract into a columnar warehouse; heavy scans never touch the orders DB.
- Residency: placement, not topology Residency (legal): a regional deployment with a clear boundary; only anonymized/aggregated data leaves. This answers the law, not throughput.
مثال محاسبهشدهWorked example
ظرفیت آموزشی: ۲ میلیون سطر در روز × ۱ کیلوبایت ≈ ۲ گیگ در روز ≈ ۰٫۷ ترابایت در سال بهعلاوه ایندکس — سالها روی یک جعبه جا میشود. نوشتن ۲۳۱ در ثانیه در اوج دو مرتبه بزرگی زیر سقف راحت یک primary است. پس جدول حکم اینطور خوانده میشود: تکگره روی نوشتن/اندازه/هزینه سبز تا tripwire (مثلاً پایدار بالای ۵هزار نوشتن در ثانیه یا داده بزرگتر از ۳ برابر RAM)؛ primary+رپلیکا روی مقیاس خواندن و بقای AZ سبز و روی رشد نوشتن قرمز؛ شاردشده روی رشد نوشتن سبز و روی هزینه عملیات قرمز — فقط بعد از tripwire بهدست میآید.
Teaching capacity: 2M rows/day × 1 KB ≈ 2 GB/day ≈ 0.7 TB/year plus indexes — fits on one box for years. Writes 231/s peak are two orders of magnitude below a single primary's comfortable limit. So the verdict table reads: single-node green on writes/size/cost until the tripwire (e.g. sustained > 5k writes/s or data > RAM × 3); primary+replica green on read scale and AZ survival, red on write growth; sharded green on write growth, red on operational cost — earned only past the tripwire.
دانلود SVG مستقلDownload standalone SVG
- میلههای آبی را بخوان: میانگین 23.1/s و اوج 231.5/s با ضریب ۱۰ — هر دو عدد آموزشیاند و روی میلهها برچسب خوردهاند.
- میلههای سبز ضریب ۳ پیام داخلی را اعمال میکنند: 69.4/s میانگین و 694.4/s در اوج — هنوز زیر هزار در ثانیه.
- کارت هلویی را بخوان: 0.75 TB در سال یعنی دیسک ۴ ترابایتی حدود 5.4 سال دوام میآورد و اوج نوشتن ۲۰ برابر زیر tripwire پنجهزارتایی است.
- نوار پایین را سالبهسال جلو برو: پنج قطعه سبز و یک تکه زرد — نقطهای که دیسک پر میشود همان tripwire افق دیسک در گام ۲ است.
- Read the blue bars: 23.1/s average, 231.5/s at a 10x peak — both teaching numbers, labelled on the bars.
- The green bars apply the 3-internal-messages fan-out: 69.4/s average, 694.4/s at peak — still under a thousand per second.
- Read the peach card: 0.75 TB/year means the 4 TB disk lasts about 5.4 years, and peak writes sit 20x below the 5k/s tripwire.
- Walk the bottom strip year by year: five green segments and one yellow stub — the point where the disk fills is the disk-horizon tripwire decided in step 2.
نقطهٔ تصمیم: پیام نمودار: بار امروز روی یک جعبه جا میشود؛ تصمیم معماری را عددهای tripwire میگیرند نه حس مقیاس.
رد خرابی: رشد ۵ برابری را روی همین میلهها امتحان کن: اوج به حدود 1150/s میرسد — هنوز زیر tripwire نوشتن، ولی نوار دیسک به حدود یک سال میرسد.
تضمین نشاندادهشده: این نمودار نرخها و افق دیسک را با اعداد آموزشی نشان میدهد؛ هرگز جای اندازهگیری بار خودت را نمیگیرد.
تضمین نشانندادهشده: درباره اینکه کدام طرح میبرد چیزی نمیگوید — آن کار درخت تصمیم گام ۲ و جدول مونتاژ است.
Decision point: The point of the plot: the current load fits on one box; tripwire numbers make the architecture call, not a feeling of scale.
Failure trace: Replay 5x growth on these same bars: peak reaches about 1150/s — still below the write tripwire, but the disk strip shrinks to about one year.
Guarantee shown: This plot shows rates and the disk horizon with teaching numbers; it never replaces measuring your own workload.
Not shown: It says nothing about which design wins — that job belongs to the step-2 decision tree and the assembly table.
خودآزماییSelf-check
فقط با همین اعداد، چرا اوج 231.5/s شاردینگ را تحمیل نمیکند؟
پاسخ: چون حدود ۲۰ برابر زیر tripwire پنجهزارتایی تکنویسنده است و داده سالها روی یک جعبه جا میشود — شارد فقط بعد از گذر از یک tripwire نامدار شروع میشود.
On these numbers alone, why does a 231.5/s peak not force sharding?
Answer: Because it sits about 20x below the 5k/s single-writer tripwire and the data fits one box for years — sharding starts only past a named tripwire.
دانلود نمودار مستقلDownload standalone diagram
- از Size the load شروع کن و دروازه اول را بپرس: نوشتن پایدار گذشته از 5k/s؟ «بله» یعنی Sharded — تنها مسیری که توان نوشتن میخرد.
- اگر «نه»، دروازه دوم: داده گذشته از افق دیسک (5.4 سال روی ۴ ترابایت یا ۳ برابر RAM)؟ «بله» دوباره Sharded است.
- اگر هنوز «نه»، دروازه سوم: بقای AZ لازم است؟ «بله» یعنی Primary + replicas و «نه» یعنی Single + standby — شروع پیشفرض.
- زنجیره چیندار پایین را جا نینداز: Warehouse و Regional placement به هیچ دروازهای وابسته نیستند و از روز اول برقرارند.
- Start at Size the load and ask gate one: sustained writes past 5k/s? Yes means Sharded — the only path that buys write throughput.
- If no, gate two: data past the disk horizon (5.4 years on 4 TB, or 3x RAM)? Yes is Sharded again.
- If still no, gate three: is AZ survival required? Yes means Primary + replicas, no means Single + standby — the default start.
- Do not skip the dashed chain at the bottom: Warehouse and Regional placement depend on no gate and hold from day one.
نقطهٔ تصمیم: هر یال «بله» یک نقطه تصمیم است: عددی را نام میبرد که رد شدنش معماری را عوض میکند — بدون گذر، بدون حرکت.
رد خرابی: خرابی نصفشدن بودجه را روی درخت دنبال کن: برگ Sharded قرمزتر میشود چون هزینه عملیاتش پرداختنی نیست — پس قبل از دست زدن به توپولوژی، تازگی و رپلیکا بریده میشوند.
تضمین نشاندادهشده: درخت نشان میدهد کدام tripwire کدام طرح را میخرد؛ اعدادش آموزشیاند و باید روی بار خودت بازسنجی شوند.
تضمین نشانندادهشده: درباره انتخاب shard key یا مکانیک rebalance چیزی نمیگوید — آن مصاحبه فصل ۷ است.
Decision point: Each yes-edge is a decision point: it names the number whose crossing changes the architecture — no crossing, no move.
Failure trace: Trace the halved-budget failure on the tree: the Sharded leaf turns redder because its ops cost is unaffordable — so freshness and replicas are cut before topology is touched.
Guarantee shown: The tree shows which tripwire buys which design; its numbers are teaching values to re-measure on your own workload.
Not shown: It says nothing about shard-key choice or rebalance mechanics — that is the chapter 7 interview.
خودآزماییSelf-check
کدام دو جواب درخت به هیچ tripwire وابسته نیستند و چرا؟
پاسخ: Warehouse چون ایزولاسیون بار است نه حجم — و Regional placement چون قاعده حقوقی مکانی است نه دلیل مقیاس.
Which two tree answers depend on no tripwire, and why?
Answer: Warehouse, because it is workload isolation rather than volume — and Regional placement, because it is a legal placement rule rather than a scaling reason.
دانلود SVG مستقلDownload standalone SVG
- سطر اول: تکگره + standby — کدام خانهها در ۲ میلیون در روز سبزند (نوشتن، اندازه، هزینه عملیات) و کدام tripwire برعکسشان میکند.
- سطر دوم: primary + رپلیکا — رپلیکا چه میخرد (مقیاس خواندن، بقای AZ) و چه چیزی هرگز نمیخرد (توان نوشتن).
- سطر سوم: شاردشده — تنها سطری که خانه نوشتنش بعد از tripwire سبز میماند، و خانه هزینه عملیاتی که قیمتش را میگوید.
- ستونهای کناری: تحلیلی (همیشه انبار) و ماندگاری (مکان نه توپولوژی) — یکبار جواب داده میشوند نه per-row.
- Row one: single-node + standby — which cells are green at 2M/day (writes, size, ops cost) and which tripwire would flip them.
- Row two: primary + replicas — what replicas buy (read scale, AZ survival) and what they never buy (write throughput).
- Row three: sharded — the only row whose write cell stays green past the tripwire, and the ops-cost cell that prices it.
- Side columns: analytics (always the warehouse) and residency (placement, not topology) — answered once, not per row.
نقطهٔ تصمیم: نقطههای تصمیم خانههای tripwireاند: هرکدام عددی را نام میبرند که رد شدنش معماری را عوض میکند. بدون گذر، بدون حرکت.
رد خرابی: خرابی رشد ۵ برابری با بودجه نصف را روی همان جدول دنبال کن: خانههای نوشتن و اندازه سطر تکگره قرمز میشوند ولی خانه هزینه عملیات سطر شارد قرمزتر — و به همین دلیل جواب قبل از دست زدن به توپولوژی، تازگی و رپلیکا را میبرد.
تضمین نشاندادهشده: جدول نشان میدهد هر طرح از کدام حد میگذرد و اجرایش چه هزینهای دارد. سیستمت را سایز نمیکند — اعداد tripwire باید روی بار خودت دوباره اندازهگیری شوند.
تضمین نشانندادهشده: درباره انتخاب shard key یا مکانیک rebalance چیزی نمیگوید — آن مصاحبه فصل ۷ است که در سطر مهاجرت ارجاع شده.
Decision point: The decision points are the tripwire cells: each names the number whose crossing changes the architecture. No crossing, no move.
Failure trace: Trace the 5x-growth, half-budget failure on the same table: the single-node row's write and size cells turn red, but the sharded row's ops-cost cell turns redder — which is why the answer cuts freshness and replicas before touching topology.
Guarantee shown: The table shows which limit each design crosses and what each costs to run. It does not size your system — the tripwire numbers must be re-measured on your workload.
Not shown: It says nothing about shard-key choice or rebalancing mechanics — that is chapter 7's interview, referenced in the migration row.
خودآزماییSelf-check
سه عددی را نام ببر که حکمت را از تکگره به شاردشده برعکس میکنند.
پاسخ: خواندن: ظرفیت باقیمانده هر AZ در اوج بهعلاوه ثانیههای failover. نوشتن: سقف تکنویسنده (۲۳۱ در ثانیه خیلی پایینترش است) و lag تکثیر. اگر هر عددی نامعلوم است حکم «اول اندازه بگیر» است نه یک طرح.
Name the three numbers that would flip your verdict from single-node to sharded.
Answer: Reads: each AZ's surviving capacity at peak plus failover seconds. Writes: single-writer limit (231/s is far below it) and replication lag. If any one number is unknown, the verdict is 'measure first', not a design.
گزینههای جایگزینAlternatives
- تکگره بزرگتر با standby را بیشتر نگه دار: تا وقتی نوشتنها جا میشوند، working set در RAM است و downtime یک AZ قابل تحمل است معتبر است. هزینه: مهاجرت سختتر بعدی — اعداد tripwire را حالا بنویس تا جابهجایی تصمیم باشد نه غافلگیری.
- Stay single-node longer with a bigger box and a standby: valid while writes fit, working set fits RAM, and one AZ of downtime is survivable. Cost: a harder migration later — state the tripwire numbers now so the move is a decision, not a surprise.
سناریوهای خرابیFailure scenarios
- مرگ primary: standby بالا میآید (primary قدیم فنس)، صف نوشتنهای اوج را در ثانیههای failover بافر میکند؛ تحلیلی چون از انبار میخواند اصلاً نمیفهمد.
- هجوم تحلیلی: گزارش بلکفرایدی مستقیم پایگاه سفارشها را اسکن میکند. نگهبان ساختاری است — گزارشها فقط به انبار میخورند — بهعلاوه استخراج شبانه با بودجه بار کراندار.
- Primary death: standby promotes (fenced old primary), queue buffers peak writes during the seconds of failover; analytics never notices because it reads the warehouse.
- Analytics stampede: a Black-Friday report scans the orders DB directly. The guard is structural — reports only touch the warehouse — plus a nightly extract with a bounded load budget.
کد قابل اجراRunnable code
اجراشده و تأییدشده در همین محیطExecuted and verified in this environment
این کد آموزشی و سادهسازیشده است؛ فقط برای آشنایی با ایده. کد واقعی پروداکشن ملاحظات بسیار بیشتری دارد و هیچکس اینشکلی کد پروداکشن نمیزند.Simplified teaching code for building intuition only — not production code; nobody ships production code like this.
// Teaching math: 2M orders/day vs single-node tripwires. All inputs labelled.
public class CapacityDemo {
public static void main(String[] a) {
double ordersPerDay = 2_000_000, peakFactor = 10;
double avg = ordersPerDay / 86_400, peak = avg * peakFactor;
double rowBytes = 1024, bytesPerDay = ordersPerDay * rowBytes;
double tbPerYear = bytesPerDay * 365 / 1e12;
double singleWriterTrip = 5_000;
double diskTB = 4; // teaching box
double yearsOnDisk = diskTB / tbPerYear;
System.out.printf("avg orders/s = %.2f%n", avg);
System.out.printf("peak orders/s = %.1f%n", peak);
System.out.printf("data/year = %.2f TB%n", tbPerYear);
System.out.printf("years on disk = %.1f%n", yearsOnDisk);
System.out.println("write tripwire = " + (peak < singleWriterTrip ? "HOLD (single-node)" : "CROSSED (shard)"));
System.out.println("size tripwire = " + (yearsOnDisk > 3 ? "HOLD (fits for years)" : "CROSSED"));
if (Math.abs(avg - 23.15) > 0.01 || Math.abs(peak - 231.5) > 0.1) throw new AssertionError();
System.out.println("OK: single-node + standby earns it; sharding not triggered");
}
}
اجرا: javac CapacityDemo.java && java CapacityDemo — فقط استاندارد جاوا، فوری. ورودیها را با بار خودت عوض کن.
خروجی مورد انتظار: avg=23.15، peak=231.5، data/year=0.75TB، years on disk=5.4؛ هر دو tripwire HOLD. (خروجی واقعی همین محیط)
Run: javac CapacityDemo.java && java CapacityDemo — stdlib Java only, instant. Swap inputs for your workload.
Expected output: avg=23.15, peak=231.5, data/year=0.75TB, years on disk=5.4; both tripwires HOLD. (actual output of this environment)
آزمون صحت و معیار عملیاتیCorrectness checks and operational criteria
پاسخ سؤالهای پیگیریFollow-up answers
پیگیری (استف): با پنج برابر شدن حجم و نصف شدن بودجه، کدام بخش را اول تغییر میدهی؟Follow-up (Staff): With fivefold data growth and half the budget, which component do you change first?
به این ترتیب ببر: (۱) اول تازگی تحلیلی — استخراج شبانه بهجای ساعتی، گزارشها تحمل میکنند؛ (۲) کلاس standby و تعداد رپلیکا — یک standby بماند (ایمنی) ولی رپلیکاهای خواندن حذف و خواندنها با دیدهبانی تأخیر از primary داده شوند؛ (۳) آخر: شاردینگ دیرتر نه زودتر — شارد کردن با بودجه نصفشده راههای خرابی را دو برابر میکند. هرگز بکاپ و drill بازیابی را نبر: ارزانترین راه برای از دست دادن همهچیز، صرفهجویی روی چیزی است که بازگشتت را ثابت میکند.
Cut in this order: (1) analytics freshness first — nightly instead of hourly extracts, reports tolerate it; (2) standby class and replica count — keep one standby (safety) but drop read replicas, serve reads from the primary with a latency watch; (3) last: shard later, not sooner — sharding under a halved budget doubles the ways to fail. Never cut backups or the restore drill: the cheapest way to lose everything is saving on the thing that proves you can come back.
اشتباهات رایجCommon mistakes
- «مقیاس بزرگ است پس میکروسرویس/شارد از روز اول». هزینه عملیات توزیعشده و درد اشکالزدایی را بدون عدد گذر میخری.
- قاطی کردن residency با sharding: قانون میگوید داده کجاست، نه چند primary داری. یک استقرار منطقهای سازگار جواب قانون است؛ شاردینگ جواب توان است.
- 'Big scale, so shards and microservices from day one.' You buy distributed-ops cost and debugging pain with no crossing number to justify it.
- Confusing residency with sharding: the law says where data lives, not how many primaries you run. One compliant region deployment answers the law; sharding answers throughput.
منابع:Sources: Amazon · SDE III interview preparation · Atlassian · engineering interview handbook
33 تمرین ۳ — چالش GDPRExercise 3 — the GDPR challenge
بازگشت به صورت تمرینBack to the exercise · ارزیابی و سطحAssessment and level
پاسخ ۶۰ثانیهایThe 60-second answer
صورت مسئله و فرضهاProblem and assumptions
- فروشگاه لاگ فقطافزودنی است؛ کلیدهای رمز per-user برای گزینه shredding هست.
- مهلتها (مثلاً ۳۰ روز) اعداد آموزشیاند — واقعیها تفسیر حقوقی میخواهند و نامطمئن اعلام میشوند.
- بکاپها با نگهداری منقضی میشوند؛ restore فقط حالت زنده را بازپخش میکند، هرگز tombstoneهای پاکشده را نه.
- Append-only event log as the store; per-user encryption keys exist for the shredding option.
- Deadlines (e.g. 30 days) are teaching numbers — real ones need legal interpretation, stated as uncertain.
- Backups expire by retention; restores replay live state only, never purged tombstones.
چرا این روش و این عددها؟Why this method and these numbers?
حذف را در همهٔ کپیها دنبال میکنیم چون پاککردن سطر مرجع، backup و مشتق را تغییر نمیدهد. crypto-shredding فقط وقتی مؤثر است که کلید واقعاً حذف شود و همهٔ کپیهای قابلشناسایی همان مرز رمز را رعایت کنند. عدد ۳۰ روز مهلت قانونی این تمرین نیست؛ بازهٔ نمونه برای برنامهریزی است. retention واقعی باید از هدف داده، الزام مربوط و امکان restore به دست بیاید؛ restore نیز باید فهرست حذفهای معتبر را دوباره اعمال کند.
Deletion follows every copy because removing an authoritative row does not alter backups or derived views. Crypto-shredding depends on actual key destruction and coverage of every identifiable copy. Thirty days is a planning example, not a legal deadline here. Derive retention from purpose, applicable obligations and restore behavior; restores must reapply the durable deletion registry.
مدل ذهنیMental model
حل گامبهگامStep by step
- جدایی PII از اول محتوای حساس را از اول جدا کن: لاگ فقط شناسه نگه دارد و PII در فروشگاه کلیددار که حذف واقعی را میفهمد. خواندن دومرحلهای قیمتش است.
- shred برای اثر فوری shredding رمزنگاری: payload هر کاربر با کلید خودش رمز میشود؛ حذف = نابودی کلید. فوری و بدون بازنویسی — ولی مدیریت کلید per-user، و کلید لو رفته حذف را خنثی میکند.
- tombstone برای لاگ tombstone + فشردهسازی: «حذف کاربر X» را الصاق کن و موقع بازسازی/فشردهسازی رکوردهایش را رد کن. داده تا بازنویسی روی دیسک/بکاپ میماند — کش و ایندکس مشتق هم پاک شوند.
- انقضا و بازنویسی نگهداری کراندار + بازنویسی دورهای: سگمنتهای قدیم را منقضی و بدون داده حذفشده بازنویسی کن. هزینه عملیاتی بهعلاوه از دست رفتن تاریخچه کامل — قیمتش را علنی بگو.
- Separate PII up front Split sensitive payload from the start: the log keeps IDs, PII lives in a keyed store that supports real deletion. Two-step reads are the price.
- Shred for instant effect Crypto-shredding: each user's payload encrypted with a per-user key; deletion = key destruction. Instant, no rewrite — but key management per user, and a leaked key defeats it.
- Tombstone the log Tombstone + compaction: append 'delete user X', filter those records on rebuild/compaction. Data lingers on disk and backups until rewritten — derived caches and indexes purge too.
- Expire and rewrite Bounded retention + periodic rewrite: expire old segments and rewrite them without deleted data. Operational cost plus loss of full history — price it openly.
دانلود نمودار مستقلDownload standalone diagram
- ستون اول: record با حذف مستقیم بسته میشود و رسید حذف میدهد؛ شاخه shred همانجا گزینه فوری است: نابودی کلید per-user با رسید نابودی.
- cache کلیدها را پاک میکند و index بدون کاربر بازسازی میشود — نماهای مشتق با لاگ پاکسازی و رکورد بازسازی بسته میشوند.
- لاگ دو سلاح دارد: زنجیره tombstone و فشردهسازی روی ستون فقرات، یا shred بهعنوان برگ جدا. داده تا بازنویسی میماند — با عدد نگهداری آموزشی صادقانه گفته میشود.
- بکاپها با انقضای نگهداری و داده آموزش با کنارگذاری در بازآموزی بعد بسته میشوند — با lag مستند، نه پنهان. گره راستیآزمایی آخر چک میکند هیچ خواندن زندهای کاربر را برنگرداند.
- First column: the record closes with a direct delete and a delete receipt; the shred branch beside it is the instant option: per-user key destruction with a destruction receipt.
- Cache purges its keys and the index rebuilds without the user — derived views close with a purge log and a rebuild record.
- The log has two weapons: the tombstone-and-compact chain on the spine, or shred as a separate leaf. Data lingers until rewritten — said honestly, with the teaching retention number.
- Backups close by retention expiry and training data by exclusion at the next retrain — with documented, not hidden, lag. The final verify node checks that no live read returns the user.
نقطهٔ تصمیم: نقطه تصمیم برچسب هر برگ است: هر کپی سازوکار و مدرکش را نام میبرد. کپی بدون سلاح همان یافته است — ثبتش کن، حذفش نکن.
رد خرابی: اگر کلید لو رفته باشد shred شکست میخورد؛ اگر کپی ثبتنشدهای باشد هیچ برگی ندارد و همانجا پیداست؛ اگر lag آموزش مستند نشود، انطباق دروغین است.
تضمین نشاندادهشده: شکل ترتیب و مدرک هر کپی را نشان میدهد. انطباق حقوقی را ثابت نمیکند — فقط سازوکار و مدرک را.
تضمین نشانندادهشده: درباره کپیهای ثبتنشده و unlearning مدل چیزی نمیگوید؛ مهلتهای واقعی تفسیر حقوقی میخواهند و نامعلوم میمانند.
Decision point: The decision point is each leaf's label: every copy names its mechanism and its evidence. A copy with no weapon is the finding — register it, do not drop it.
Failure trace: A leaked key defeats shredding; an unregistered copy has no leaf and shows exactly there; undocumented training lag is false compliance.
Guarantee shown: The figure shows each copy's order, mechanism and evidence. It does not prove legal compliance — only mechanism and evidence.
Not shown: It says nothing about unregistered copies and model unlearning; real deadlines need legal interpretation and stay uncertain.
خودآزماییSelf-check
هر شش کپی را نام ببر و برای هرکدام بگو سازوکار حذف و مدرکش چیست.
پاسخ: مرجع: حذف مستقیم با رسید؛ کش: پاکسازی با لاگ؛ ایندکس: بازسازی با رکورد؛ لاگ: tombstone و فشردهسازی با آفست و رکورد، یا shred با رسید نابودی؛ بکاپ: انقضا با رکورد؛ آموزش: کنارگذاری با لاگ و lag مستند.
Name all six copies; for each, state the deletion mechanism and its evidence.
Answer: Record: direct delete with receipt; cache: purge with log; index: rebuild with record; log: tombstone and compact with offset and record, or shred with destruction receipt; backup: expiry with record; training: exclusion with log and documented lag.
دانلود نمودار مستقلDownload standalone diagram
- سه فلش اول: الصاق delete-user-X با مدرک آفست، نابودی کلید با رسید، و انتشار واترمارک حذف — که چون مصرفکننده آفلاین است نگه داشته میشود.
- مصرفکننده برمیگردد و میخواهد سرو کند؛ نرده امنیتی میگوید اول از واترمارک جلو بزن. خواندن کهنهاش به کلید نابودشده میخورد و بسته-fail میشود — نشت نمیکند.
- شاخه موازی: restore بکاپ را از میان tombstoneها بازپخش میکند؛ فقط حالت زنده برمیگردد و چیزی زنده نمیشود. بکاپ قدیمیتر از نگهداری نابود میشود، نه بازپخش.
- First three arrows: the delete-user-X append with its offset evidence, key destruction with its receipt, and the deletion-watermark publish — held because the consumer is offline.
- The consumer returns and asks to serve; the security fence says catch up past the watermark first. Its stale read hits the destroyed key and fails closed — nothing leaks.
- Parallel branch: restore replays the backup through the tombstones; only live state returns, nothing resurrects. Backups older than retention are destroyed, never replayed.
نقطهٔ تصمیم: نقطه تصمیم واترمارک است: جلو زدن از آن شرط سرو است، نه توصیه.
رد خرابی: سرو قبل از جبران عقبماندگی یعنی داده حذفشده بیرون میرود؛ restore بدون tombstone یعنی زنده شدن مردهها.
تضمین نشاندادهشده: شکل نرده و بازپخش tombstoneدار را نشان میدهد؛ ثابت نمیکند همه مصرفکنندهها ثبتاند.
تضمین نشانندادهشده: درباره مصرفکنندههای ناشناخته و مهلت واقعی — که تفسیر حقوقی میخواهد — ساکت است.
Decision point: The decision point is the watermark: catching up past it is the condition for serving, not advice.
Failure trace: Serving before catching up leaks deleted data; restore without tombstones resurrects the dead.
Guarantee shown: The figure shows the fence and the tombstoned replay; it does not prove every consumer is registered.
Not shown: It is silent on unknown consumers and on the real deadline — which needs legal interpretation.
خودآزماییSelf-check
مصرفکننده آفلاین برمیگردد؛ قبل از سرو چه باید بکند و چرا خواندنش امن است؟
پاسخ: باید از واترمارک حذف جلو بزند وگرنه نرده جلویش را میگیرد؛ خواندن کهنهاش به کلید نابودشده میخورد و بسته-fail میشود پس نشت نمیکند.
The offline consumer returns; what must it do before serving, and why is its read safe?
Answer: It must catch up past the deletion watermark or the fence blocks it; its stale read hits the destroyed key and fails closed, so nothing leaks.
دانلود SVG مستقلDownload standalone SVG
- لاینهای بالا: مرجع (حذف مستقیم)، کش و ایندکس (پاکسازی + بازسازی بدون کاربر) — کپیهای آسان که اول بسته میشوند.
- لاین وسط: لاگ — tombstone الصاق، سگمنتها فشرده، یا کلید per-user برای ناخوانایی فوری shredded میشود.
- لاینهای پایین: بکاپها (انقضا با نگهداری، restore لاگ tombstone را بازپخش میکند) و داده آموزش (کنارگذاری در بازآموزی بعد با lag مستند).
- لاین نقطهچین + میله watermark: مصرفکننده آفلاین که تا عقبماندگی جبران نشود فنس است — نگهبان سطح استف.
- Top lanes: record (direct delete), cache and index (purge + rebuild without the user) — the easy copies, closed first.
- Middle lane: the log — tombstone appended, segments compacted, or the per-user key shredded for instant unreadability.
- Bottom lanes: backups (expire by retention, restore replays tombstones) and training data (exclude next retrain, documented lag).
- Dotted lane + watermark bar: the offline consumer, fenced until caught up — the staff-level guard.
نقطهٔ تصمیم: نقطههای تصمیم برچسبهای سلاح per-copyاند: هر کپی سازوکار و مدرکش را نام میبرد (رسید نابودی کلید، رکورد فشردهسازی، لاگ پاکسازی). کپی بدون سلاح همان یافته است.
رد خرابی: مصرفکننده آفلاین را روی همان شکل دنبال کن: لاینش نقطهچین میماند ولی بقیه توپر میشوند و میله watermark جلوی سرو کردنش را میگیرد — خواندنهایش به کلید نابودشده میخورند و بسته-fail میشوند. بعد restore را دنبال کن: پیکان بازپخش از جعبه tombstone میگذرد و بکاپ قدیمیتر از نگهداری خط میخورد نه بازپخش.
تضمین نشاندادهشده: شکل بسته شدن هر کپی و دو نگهبان زنده شدن (watermark + restore tombstoneدار) را نشان میدهد. انطباق حقوقی را اثبات نمیکند — فقط سازوکار و مدرکش را.
تضمین نشانندادهشده: کپیهای ثبتنشده و unlearning مدل بیرون ردیابیاند — صریح نام برده میشوند نه بیصدا انداخته.
Decision point: The decision points are the per-copy weapon labels: each copy names its mechanism and its evidence (key-destruction receipt, compaction record, purge log). A copy with no weapon is the finding.
Failure trace: Trace the offline consumer on the same figure: its lane stays dotted while others turn solid, and the watermark bar stops it from serving — its reads hit the destroyed key and fail closed. Then trace a restore: the replay arrow passes through the tombstone box, and the backup older than retention is crossed out, not replayed.
Guarantee shown: The figure shows every copy closing and the two resurrection guards (watermark + tombstoned restore). It does not prove legal compliance — only the mechanism and its evidence.
Not shown: Unregistered copies and model unlearning are outside the trace — named explicitly, not silently dropped.
خودآزماییSelf-check
دادهات بعد از حذف کجا باقی میماند و مالک هر بقا کیست — مهندسی یا حقوقی؟
پاسخ: یک درخواست حذف را روی کاغذ از API تا لاگ، بکاپ، کش، ایندکس و گزارش دنبال کن و نقطهای که داده باقی میماند را پیدا کن. بعد جواب بده: کدام بقا کار مهندسی میخواهد و کدام وکیل؟
Where does data survive your deletion, and who owns each survival — engineering or legal?
Answer: Paper-trace one erasure from API to log, backup, cache, index and reports, and find where data survives. Then answer: which of those survivals needs engineering work, and which needs a lawyer?
گزینههای جایگزینAlternatives
- بازنویسی کامل لاگ با هر حذف (فشردهسازی که رکوردهای کاربر را فوری میاندازد): قویترین تضمین و بیشترین هزینه — سگمنتها را بازنویسی میکند، بکاپها را باطل میکند و مصرفکنندهها را متوقف میکند. برای لاگهای کوچک یا سختگیرانهترین مهلتها نگه دار؛ وگرنه tombstone + نگهداری کراندار + shredding ترکیب عملی است.
- Full log rewrite on every deletion (compaction that drops the user's records immediately): strongest guarantee, highest cost — rewrites segments, invalidates backups, pauses consumers. Reserve for small logs or the strictest deadlines; otherwise tombstone + bounded retention + crypto-shredding compose the practical answer.
سناریوهای خرابیFailure scenarios
- مصرفکننده قدیم آفلاین: tombstone را از دست داده و هنوز داده کاربر را سرو میکند. نگهبان watermark حذف است — مصرفکنندهها باید از watermark جلو بزنند بعد سرو کنند و کلیدهای کاربران shredded بسته-fail میشوند (بدون کلید = بدون خواندن).
- زنده شدن با restore: بکاپ ماه پیش قدیمیتر از حذف است و کاربر را زنده برمیگرداند. نگهبان: restoreها از لاگ tombstone میگذرند (watermarkهای حذف جزو restoreاند) و بکاپهای قدیمیتر از نگهداری نابود میشوند نه «شاید لازم شد».
- نشت داده آموزش: مدل الگوهای کاربر را قبلاً یاد گرفته. نگهبان: کنار گذاشتن در بازآموزی بعد، مستندسازی صادقانه lag، و ترجیح feature store با حذف سطری بهجای dump منجمد.
- Offline old consumer: it missed the tombstone and still serves the user's data. Guard with a deletion watermark — consumers must catch up past the watermark before serving, and keys for shredded users fail closed (no key = no read).
- Restore resurrection: last month's backup predates the deletion and replays the user back to life. Guard: restores replay through the tombstone log (deletion watermarks are part of the restore), and backups older than retention are destroyed, not kept 'just in case'.
- Training-data leak: the model already learned the user's patterns. Guard: exclude on next retrain, document the lag honestly, and prefer feature stores that support row-level deletion over frozen dumps.
کد قابل اجراRunnable code
اجراشده و تأییدشده در همین محیطExecuted and verified in this environment
این کد آموزشی و سادهسازیشده است؛ فقط برای آشنایی با ایده. کد واقعی پروداکشن ملاحظات بسیار بیشتری دارد و هیچکس اینشکلی کد پروداکشن نمیزند.Simplified teaching code for building intuition only — not production code; nobody ships production code like this.
import java.util.*;
// Teaching demo: one erasure across six copies + offline consumer watermark
// + restore replay that must NOT resurrect.
public class DeletionDemo {
public static void main(String[] a) {
long user = 42;
Set<Long> record = new HashSet<>(Set.of(user, 7L));
Map<Long, String> cache = new HashMap<>(Map.of(user, "status", 7L, "ok"));
Map<Long, String> index = new HashMap<>(Map.of(user, "doc", 7L, "doc7"));
List<String> log = new ArrayList<>(List.of("create:42", "create:7"));
Set<Long> tombstones = new HashSet<>();
Map<Long, byte[]> keys = new HashMap<>(Map.of(user, new byte[]{1}, 7L, new byte[]{2}));
record.remove(user); cache.remove(user); index.remove(user);
log.add("delete:42"); tombstones.add(user); keys.remove(user);
long watermark = log.size();
Map<Long, String> staleConsumer = new HashMap<>(Map.of(user, "doc", 7L, "doc7"));
long consumerOffset = 2;
boolean mayServe = consumerOffset >= watermark;
consumerOffset = watermark;
staleConsumer.remove(user);
List<String> backup = List.of("create:42", "create:7");
Set<Long> restored = new HashSet<>();
for (String e : backup) {
String[] parts = e.split(":");
long id = Long.parseLong(parts[1]);
if (!tombstones.contains(id)) restored.add(id);
}
System.out.println("record holds user = " + record.contains(user));
System.out.println("cache/index hold user = " + (cache.containsKey(user) || index.containsKey(user)));
System.out.println("key shredded = " + !keys.containsKey(user));
System.out.println("offline served stale = " + mayServe);
System.out.println("consumer clean now = " + !staleConsumer.containsKey(user));
System.out.println("restore resurrected = " + restored.contains(user));
if (record.contains(user) || cache.containsKey(user) || index.containsKey(user)
|| keys.containsKey(user) || mayServe || staleConsumer.containsKey(user) || restored.contains(user))
throw new AssertionError();
System.out.println("OK: six copies closed, straggler fenced, restore clean");
}
}
اجرا: javac DeletionDemo.java && java DeletionDemo — فقط استاندارد جاوا، فوری.
خروجی مورد انتظار: هر شش کپی false/تمیز، کلید shredded، مصرفکننده آفلاین سرو نکرد، restore زنده نکرد؛ OK. (خروجی واقعی همین محیط)
Run: javac DeletionDemo.java && java DeletionDemo — stdlib Java only, instant.
Expected output: all six copies false/clean, key shredded, offline consumer did not serve, restore clean; OK. (actual output of this environment)
آزمون صحت و معیار عملیاتیCorrectness checks and operational criteria
پاسخ سؤالهای پیگیریFollow-up answers
پیگیری (استف): اگر یک مصرفکننده قدیمی هنگام حذف آفلاین باشد چه تضمینی داری؟Follow-up (Staff): What happens when an old consumer is offline throughout a deletion?
تا برنگردد هیچ تضمین سرتاسری نیست. مصرفکننده آفلاین با watermark فنس میشود: تا از watermark حذف جلو نزند حق سرو خواندن ندارد و کلیدهای per-user ازدسترفتهاش قبلاً نابود شدهاند، پس خواندنهایش بسته-fail میشوند نه نشتی. جواب صادق پنجره را نام میبرد: حذف وقتی کامل است که کندترین مصرفکننده ثبتشده از watermark جلو بزند — آن lag را per-consumer رصد کن و روی عقبماندهها هشدار بده. کپیهای ثبتنشده (dump لپتاپ، اسکرینشات) بیرون تضمینهای مهندسیاند؛ صریح بگو.
No end-to-end guarantee until it returns. The offline consumer is fenced by the watermark: it may not serve reads until it has consumed past the deletion watermark, and per-user keys it missed are already destroyed, so its reads fail closed instead of leaking. The honest answer names the window: deletions complete when the slowest registered consumer acknowledges past the watermark — track that lag per consumer and alert on stragglers. Unregistered copies (a laptop dump, a screenshot) are outside engineering guarantees; say so explicitly.
اشتباهات رایجCommon mistakes
- فقط رکورد زنده را حذف کردن و فراموش کردن بکاپ، کش، ایندکس و لاگ. حذف باید سرتاسری با چکلیست per-copy تعریف شود.
- نقل عدد نگهداری بهعنوان قانون («GDPR میگوید ۳۰ روز»). مقرره اصول و مهلت per-basis میگذارد؛ عدد دقیق حالت تو تفسیر حقوقی میخواهد — مهندسها مالک سازوکار و مدرکاند نه خوانش حقوقی.
- Deleting only the live record and forgetting backups, caches, indexes and the log. Deletion must be defined end-to-end, with a checklist per copy.
- Quoting a retention number as law ('GDPR says 30 days'). The regulation sets principles and deadlines per basis; the exact number for your case needs legal interpretation — engineers own the mechanism and the evidence, not the legal reading.
منابع:Sources: European Commission · GDPR principles · Atlassian · engineering interview handbook