دسکتاپ، Worker و محاسبات محلی · فصل ۳ — Identity، Health، Lease و SQLite Spool · 185
Local SQLite WAL Spool برای Offline Progress
اپ یا worker محلی را اجرا میکند اما device identity، privilege، offline state و fleet behavior را طراحی نکرده است. موضوع جاری: «Local SQLite WAL Spool برای Offline Progress». میتواند «Local SQLite WAL Spool برای Offline Progress» را برای یک node محلی با identity، state، privilege و recovery boundary مناسب طراحی کند.
خلاصهٔ درس
اپ یا worker محلی را اجرا میکند اما device identity، privilege، offline state و fleet behavior را طراحی نکرده است. موضوع جاری: «Local SQLite WAL Spool برای Offline Progress».
میتواند «Local SQLite WAL Spool برای Offline Progress» را برای یک node محلی با identity، state، privilege و recovery boundary مناسب طراحی کند.
خروجی این درس
میتواند «Local SQLite WAL Spool برای Offline Progress» را برای یک node محلی با identity، state، privilege و recovery boundary مناسب طراحی کند.
1. مسئله واقعی: progress نباید با قطع شبکه ناپدید شود
اگر worker فقط progress را در RAM نگه دارد، crash میتواند evidence را از بین ببرد؛ اگر هم پس از reconnect هر چیزی را کورکورانه دوباره بفرستد، duplicate و reorder ایجاد میشود. خروجی این درس Offline Progress WAL Spool & Replay Ledger v1 است: یک spool تکنودی که append محلی، event identity، replay، remote ACK، retry و retention را به شواهد صریح تبدیل میکند. هدف «exactly once جادویی» نیست؛ هدف یک مسیر auditable و recoverable است.
2. مرز مالکیت LOC-012
LOC-012 فقط spool محلی SQLite و replay/ACK آن را مالک است. lease ownership و renew/cancel در LOC-011 تعریف میشود و authorization عمل روی node در LOC-013. WAL نیز distributed queue یا network filesystem نیست. اگر coordinator قطع باشد، local commit فقط میگوید event روی این node طبق contract محلی ثبت شده؛ نمیگوید coordinator آن را پذیرفته یا job واقعاً complete شده است.
3. WAL چه چیزی میدهد و چه چیزی نمیدهد
در WAL mode، writer تغییرات را ابتدا به write-ahead log append میکند و checkpoint بعداً committed content را به database اصلی منتقل میکند. Reader میتواند همزمان با writer کار کند، اما writer WAL در هر لحظه یکی است. این mechanics برای workload محلی مفید است، ولی schema صف، idempotency، replay order، ACK یا retention policy را SQLite برای شما طراحی نمیکند.
4. WAL یک boundary تکماشینه است
WAL به shared-memory coordination وابسته است؛ بنابراین آن را مثل یک فایل queue روی network share طراحی نکنید. فایل -wal بخشی از persistent state دیتابیس است و در copy/move باید boundary دیتابیس را کامل ببینید. معماری این درس عمداً single-node است: هر node spool خودش را دارد و coordinator فقط از طریق پروتکل replay/ACK با آن تعامل میکند.
5. schema باید event identity را مستقل از row identity نگه دارد
حداقل فیلدها عبارتاند از event_id، job_id، lease_generation، event_sequence، payload_digest، spool_state، retry_count، next_retry_at، remote_ack و timestampهای اصلی. event_id یا idempotency key باید در retry ثابت بماند. یک row id دیتابیس برای dedupe بین node و coordinator کافی نیست، چون semantic identity باید در دو طرف یکسان باشد.
6. append باید transactional باشد
ساخت event و تغییر state مربوط به آن باید در یک transaction محلی انجام شود. مسیر پایه: validate payload → BEGIN → insert event با PENDING → ثبت digest/sequence/generation → COMMIT. فقط پس از COMMIT میتوان event را durable-local دانست. اگر SQLITE_BUSY یا خطای قبل از COMMIT رخ دهد، نباید event را committed فرض کرد؛ outcome باید BUSY/DEFER بماند و retry bounded داشته باشد.
7. state machine: PENDING، SENT، ACKED و FAILED
PENDING یعنی محلی committed ولی هنوز remote acceptance اثبات نشده است. SENT یعنی حداقل یک transmission attempt با همان identity انجام شده اما ACK قطعی ثبت نشده. ACKED فقط پس از evidence صریح coordinator است. FAILED برای exhaustion یا policy-defined terminal failure است، نه هر خطای گذرا. Restart نباید SENT بدون ACK را به ACKED تبدیل کند؛ باید eligibility برای replay را دوباره محاسبه کند.
8. replay باید deterministic و ordered باشد
برای یک stream منطقی، replay را بر اساس job_id + lease_generation + event_sequence مرتب کنید و فقط eventهای eligible و unacknowledged را بردارید. Cursor صرفاً optimization است؛ منبع حقیقت row state و ACK evidence است. اگر event 103 response گم کرده، retry همان event_id و همان payload digest را میفرستد. ساخت event جدید برای همان progress، dedupe را دور میزند.
9. idempotency جلوی duplicate logical update را میگیرد
اگر coordinator event 103 را پذیرفته ولی response در شبکه گم شده باشد، node احتمالاً 103 را دوباره میفرستد. remote side باید identity پایدار را بشناسد و duplicate را به یک logical update دوم تبدیل نکند. node نیز duplicate ACK را بهعنوان reconciliation evidence ثبت میکند، نه اینکه sequence جدید بسازد. «send یک بار» تضمین نیست؛ «retry با identity ثابت + ACK reconciliation» contract است.
10. Remote ACK مرز retirement است
Local COMMIT، successful socket write یا HTTP 2xx بدون binding کافی، بهتنهایی مجوز حذف event نیست. retirement/compaction فقط وقتی eligible میشود که ACK معتبر برای همان event/sequence/generation ثبت شده و retention policy نیز اجازه دهد. حتی ACKED را میتوان برای یک retention window کوتاه نگه داشت تا audit/recovery قابل بازسازی باشد. unacknowledged data هرگز برای کوچککردن WAL حذف نمیشود.
11. Retry و backoff باید bounded و قابل ممیزی باشند
برای failure گذرا retry_count، last_attempt_at، next_retry_at و reason را ثبت کنید. Backoff باید از policy و شرایط device محاسبه شود، نه از loop بینهایت. SQLITE_BUSY، network unavailable و remote 5xx یکسان نیستند؛ هرکدام trigger و budget جدا میخواهند. اگر retry budget تمام شود، outcome را FAILED یا DEFER کنید و recheck/escalation مشخص بدهید.
12. Checkpoint را با health spool اشتباه نگیرید
Checkpoint committed WAL frames را به database اصلی منتقل میکند، اما long-lived reader میتواند completion را متوقف کند و WAL رشد کند. wal_size، نتیجه checkpoint و BUSY state را monitor کنید. اگر checkpoint کامل نمیشود، policy درست monitor/backoff و رفع reader pressure است؛ پاککردن دستی -wal یا حذف PENDING/SENT event برای کمکردن حجم، evidence را خراب میکند.
13. Crash recovery باید از database truth شروع شود
سناریو: event 104 locally committed شده و process قبل از upload crash میکند. بعد از reopen، 104 باید از row committed بازیابی شود و اگر ACK ندارد replay-eligible شود. برای 101–102 که قبلاً ACK شدهاند replay لازم نیست؛ 103 که remote پذیرفته ولی response گم شده با همان identity retry میشود. recovery از memory cursor یا «آخرین چیزی که فکر میکردیم ارسال شد» شروع نمیشود.
14. Failure Probeهای تعیینکننده
چهار shortcut را رد کنید: (1) COMMIT محلی و crash قبل از upload → event باید باقی بماند؛ (2) response گمشده بعد از remote acceptance → retry با همان identity و duplicate reconciliation؛ (3) long reader و checkpoint ناقص → state را DEGRADED/BUSY نگه دارید و unacked row را حذف نکنید؛ (4) event=SENT ولی ACK ندارد و node restart میشود → طبق policy دوباره replay-eligible شود، نه compaction-eligible. Lab expected labelها را به learner نشان نمیدهد.
15. Transfer به edge device کندتر
همان contract را از workstation SSD به edge device تکنودی کندتر با offline window طولانیتر ببرید. identity، ordering، ACK و retirement invariant هستند، اما checkpoint cadence، retry backoff و retention bound باید از storage/latency مشاهدهشده دوباره محاسبه شوند. baseline threshold را کپی نکنید. نتیجه میتواند DEGRADED یا DEFER باشد، ولی نباید برای راحتی storage، event بدون ACK را حذف یا ACK را حدس بزند.
ارزیابی
پاسخنامهی این درس در بستهی آموزش وجود ندارد؛ خودت جواب بده و صادقانه علامت بزن. «نیاز به مرور» درس را به صف تکرار میفرستد.
LOC-012-AS1 application
Build the Offline Progress WAL Spool & Replay Ledger v1 for events 101–104. Show event identity, local commit, state, replay eligibility, ACK evidence, and compaction eligibility.
LOC-012-AS2 diagnosis
A process crashes after event 104 COMMIT but before first upload. Explain which evidence must survive and what must not be inferred.
LOC-012-AS3 decision
A long reader prevents checkpoint completion while unacknowledged events remain. Issue a bounded decision with reason and recheck trigger.
LOC-012-AS4 transfer
Move the spool to a slower edge device with a longer offline window. Recompute checkpoint cadence, retry backoff and retention bounds without changing identity/ACK invariants.
ملاکهای ارزیابی (rubric)
A sufficient submission must make the owned single-node spool boundary explicit; bind event identity, sequence and payload digest; preserve PENDING/SENT/ACKED/FAILED distinctions; distinguish local COMMIT from remote ACK; replay only eligible unacknowledged events in deterministic order; preserve unacknowledged data under checkpoint pressure; state non-claims; and recompute the transfer decision from changed device evidence rather than copying baseline thresholds.
منابع و تازگی
مطالعهٔ موارد (case studies)
فایلهای آزمایشگاه
Implement the missing policy functions in starter.py using only the supplied synthetic cases. Keep stable event identity across retries, preserve explicit spool states, never infer remote ACK from local COMMIT, and never delete unacknowledged events because of checkpoint pressure. The changed-context edge-device case must recompute backoff/checkpoint/retention choices from its observations.
Run python run_checks.py for learner-visible structural checks. Hidden expected decision labels are not included in this folder.