تصور کنید ۷۵ دقیقه تلاش مستمر و هزینهٔ توکنهای گرانقیمت را در یک لحظه از دست بدهید. این کابوس برای توسعهدهندهای رخ داد که در ۸۰٪ مسیر تولید ۳۰۰ سؤال امتحانی، با یک خطای اتصال مواجه شد و تمام دادههای ذخیره شده در حافظه موقت، ناپدید شدند. او روایت میکند: «تماشا کردم که ۷۵ دقیقه پیشرفت در یک چشم به هم زدن محو شد.» این اتفاق نشان میدهد که چگونه یک خطای سادهٔ ConnectionResetError میتواند ساعتها زمان پردازش و اعتبارات گرانقیمت API را به طور کامل نابود کند، اگر اسکریپت شما صرفاً به ذخیرهسازی در حافظه (In-memory storage) متکی باشد.
فرآیندهای دستهای (Batch processes) که زمان اجرای طولانی دارند، ذاتاً شکننده هستند. وقتی هزاران بار APIهای مدلهای زبانی بزرگ (LLM) را فراخوانی میکنید، ناپایداری شبکه یا تایماوتهای سمت سرور، احتمالات نیستند، بلکه قطعیت هستند. بسیاری از برنامهنویسان در ابتدای مسیر، از الگوی «سادهلوحانه» استفاده میکنند: جمعآوری تمام نتایج در یک لیست و نوشتن آنها در فایل، تنها پس از پایان کامل حلقه.
این رویکرد شبیه ساختن یک آسمانخراش است که تصمیم میگیرید پیریزی آن را فقط بعد از نصب سقف نهایی کنید. اگر در طبقه بیستم طوفانی بوزد، کل سازه فرو میریزد چون هیچ بخشی در طول مسیر مهار نشده بود. در دنیای هوش مصنوعی زاینده (Generative AI) — که مثل آشپزی با دستورالعملهای پیچیده است و هر خطا میتواند کل مواد اولیه را هدر دهد — این یعنی پذیرش ریسک کامل در برابر ناپایداری شبکه. دقیقاً به همین ترتیب است که لیستهای موجود در حافظه در هنگام شکست API رفتار میکنند.
زمینه و بستر شکست
این توسعهدهنده در حال مطالعه برای یک آزمون صلاحیت بود و متوجه شد که مجموعه سؤالات رسمی برای آمادگی کافی نیستند. برای حل این مشکل، تصمیم گرفت از هوش مصنوعی زاینده برای ایجاد مسائل مشابه استفاده کند. او یک اسکریپت پایتون به نام generate_questions.py نوشت که طراحی شده بود تا سرفصلهای آزمون را دریافت کرده و برای هر فصل، ۳۰۰ سؤال به همراه توضیحات تولید کند.
برای ۷۵ دقیقه، همه چیز عالی پیش میرفت و نوار پیشرفت (Progress Bar) بهطور منظم حرکت میکرد. اما درست زمانی که اسکریپت به نزدیکی پایان میرسید، کنسول با یک خطای مهلک متوقف شد: requests.exceptions.ConnectionError: ('Connection aborted.', ConnectionResetError(104, 'Connection reset by peer')).
پس از بررسی پوشه خروجی، توسعهدهنده متوجه شد که فایلها کاملاً خالی هستند. از آنجایی که دادهها در RAM ذخیره شده بودند، تمام زمان صرف شده و توکنهای API که برای آن ۲۵۰ سؤال مصرف شده بود، عملاً تبخیر شدند.
کالبدشکافی این شکست
اسکریپت اولیه از یک حلقه ساده پایتون برای تولید سؤالات بر اساس سرفصلها استفاده میکرد. منطق برنامه ساده بود اما نقص مرگباری داشت:
- مقداردهی اولیه: یک لیست خالی به نام
all_questions = []برای نگهداری نتایج ایجاد شده بود. - حلقه: یک حلقه
forبرای ۳۰۰ بار اجرا میشد و در هر تکرار، API هوش مصنوعی را فراخوانی میکرد. - ذخیرهسازی در حافظه: هر سؤال تولید شده از طریق دستور
all_questions.append(new_question)به لیست اضافه میشد. - ثبت دیرهنگام: دستور نوشتن لیست
all_questionsدر یک فایل JSON، خارج از حلقه قرار داشت؛ به این معنی که این دستور تنها پس از موفقیتآمیز بودن ۳۰۰مین فراخوانی اجرا میشد.
وقتی خطای ConnectionResetError رخ داد، لیست all_questions که فقط در RAM ناپایدار وجود داشت، حذف شد. چون برنامه هرگز به دستور نوشتن در فایل نرسید، پوشه خروجی خالی ماند.

پیادهسازی مکانیزم نقطه بازرسی
برای حل این مشکل، توسعهدهنده یک «مکانیزم نقطه بازرسی» (Checkpoint Mechanism) را برای دستیابی به تکرارپذیری (Idempotency) پیاده کرد. تکرارپذیری تضمین میکند که اجرای چندینبارهٔ یک اسکریپت، نتیجهای یکسان تولید کند بدون اینکه کار تکراری انجام شود یا پیشرفت قبلی از دست برود. اگر اسکریپت متوقف شود، میتوان آن را با همان دستور قبلی دوباره اجرا کرد تا دقیقاً از همان جایی که متوقف شده بود، ادامه دهد.
قلب این راهکار، تغییر فرمت ذخیرهسازی از یک آرایهٔ واحد JSON به فرمت JSONL (JSON Lines) است. در JSONL، هر خط یک شیء JSON مستقل است. این ساختار برای این مورد خاص ایدهآل است زیرا به اسکریپت اجازه میدهد دادههای جدید را به انتهای فایل اضافه (Append) کند، بدون اینکه نیاز باشد کل محتوای قبلی را بخواند یا بازنویسی کند.
جزئیات فنی راهکار
کد بازنگری شده از یک گردش کار خاص برای تضمین عدم فقدان داده استفاده میکند. پیادهسازی شامل مراحل زیر است:
تأیید وضعیت (State Verification):
- اسکریپت یک فایل نقطه بازرسی تعریف میکند (مثلاً
app_factory/exams/{exam_slug}/generated.jsonl). - در هنگام شروع، با استفاده از
os.path.exists()بررسی میکند که آیا این فایل وجود دارد یا خیر. - اگر فایل موجود باشد، اسکریپت آن را خط به خط میخواند، JSONها را تجزیه میکند تا مقادیر
chapter_idرا استخراج کرده و آنها را به یک مجموعه (Set) به نامcompleted_chaptersاضافه کند.
- اسکریپت یک فایل نقطه بازرسی تعریف میکند (مثلاً
پرش از وظایف (Task Skipping):
- اسکریپت روی فصلهای سرفصلها پیمایش میکند.
- سپس بررسی میکند:
if chapter["id"] in completed_chapters: continue. - این کار مانع از آن میشود که اسکریپت توکنهای API را برای فصلهایی که قبلاً روی دیسک ذخیره شدهاند، هدر دهد.
ثبت فوری (Immediate Persistence):
- اسکریپت API را برای فصل جاری فراخوانی میکند:
new_items = call_genai_api(chapter["prompt"]). - سپس بلافاصله فایل نقطه بازرسی را در حالت Append باز میکند:
with open(checkpoint_file, "a", encoding="utf-8") as f:. - هر مورد با
chapter_idمربوطه برچسبگذاری شده و به صورت یک رشته JSON به همراه یک خط جدید (\n) نوشته میشود.
- اسکریپت API را برای فصل جاری فراخوانی میکند:
جزئیات پیادهسازی کد
این منطق در تابعی به نام generate_with_checkpoint(exam_slug) کپسوله شده است. فرآیند با بارگذاری سرفصلها و مقداردهی اولیه مجموعه completed_chapters آغاز میشود. با خواندن فایل .jsonl موجود در ابتدا، اسکریپت نقشهای از کارهای انجام شده ایجاد میکند.
هنگام پردازش یک فصل، اسکریپت فقط خروجی AI را ذخیره نمیکند، بلکه نتیجه را در متادادهها میپیچد. با ایجاد item_with_meta = {"chapter_id": chapter["id"], **item}، توسعهدهنده تضمین میکند که هر خط در فایل خروجی، خود-توصیفگر (Self-describing) است. این امر فرآیند بازیابی را بسیار ساده میکند: اسکریپت صرفاً به دنبال chapter_id در فایل میگردد تا تصمیم بگیرد که آیا فراخوانی API را نادیده بگیرد یا خیر.
این معماری، فرآیند را از یک قمار «همه یا هیچ» به یک خط لوله (Pipeline) مقاوم تبدیل میکند. اگر اتصال در سؤال ۲۵۱ قطع شود، ۲۵۰ مورد اول پیش از آن، بهصورت ایمن روی دیسک نوشته شدهاند.
پیامدهای گستردهتر مهندسی
این الگو فراتر از اسکریپتهای ساده هوش مصنوعی است و یکی از اصول بنیادی سیستمهای توزیعشده و مهندسی داده محسوب میشود. توسعهدهنده اشاره کرد که این منطق «ذخیره در حین پیشرفت» در ابزارهای زیرساختی حرفهای نیز منعکس شده است. به عنوان مثال، Terraform از فایلهای وضعیت (State Files) برای ردیابی وضعیت فعلی منابع استفاده میکند؛ اگر یک استقرار (Deployment) متوقف شود، Terraform به فایل وضعیت مراجعه میکند تا تشخیص دهد چه چیزهایی قبلاً ایجاد شدهاند و از تخریب یا تکرار منابع موجود جلوگیری کند.
برای کسانی که خط لولههای تولید بازیابیافزا (RAG) — شبیه دانشآموزی که قبل از جواب دادن، اول کتاب را باز میکند تا دقیق بنویسد — یا مجموعههای دادهٔ مصنوعی در مقیاس بزرگ میسازند، این رویکرد اجباری است. سربار اندک ناشی از عملیات ورودی/خروجی (I/O) مکرر دیسک، در مقایسه با هزینه از دست دادن ساعتها زمان GPU یا API، ناچیز است.
در زمینه توسعه مدرن AI، جایی که هزینههای توکن میتواند به سرعت افزایش یابد، «طراحی برای شکست» یک ضرورت مالی است. یک اسکریپت مقاوم فقط زمان را ذخیره نمیکند، بلکه با تضمین ذخیره هر توکن پرداخت شده، از بودجه محافظت میکند.
درسهای نهایی
درس اصلی این است که هر اسکریپتی که شامل APIهای خارجی یا زمان اجرای طولانی است، باید با این فرض طراحی شود که «قطع شدن اتفاق خواهد افتاد».
- رویکرد سادهلوحانه: «همه چیز را تمام کن، سپس ذخیره کن.» (ساده، اما شکننده).
- رویکرد مقاوم: «به صورت تدریجی پیش برو، مدام ذخیره کن.» (کمی بیشتر تلاش میطلبد، اما تابآور است).
چه در وباسکرپینگ باشد، چه در پردازش دادههای مقیاس بزرگ یا تامین زیرساخت، افزودن مکانیزم نقطه بازرسی، ابزار را بهطور تصاعدی پایدارتر میکند. اگرچه از دست دادن ۷۵ دقیقه کار یک «شهریه» دردناک بود، اما تضمین میکند که توسعههای آینده همین اشتباه را تکرار نخواهند کرد.
گام بعدی شما
- تمام اسکریپتهای خود را که بیش از ۵ دقیقه زمان اجرا دارند، از ذخیرهسازی در RAM به ذخیرهسازی تدریجی در فایل منتقل کنید.
- برای دادههای ساختاریافته در حجم بالا، بهجای JSON از فرمت JSONL استفاده کنید تا عملیات Append را بهینه کنید.
- منطق «بررسی وضعیت پیش از اجرا» را به توابع فراخوانی API خود اضافه کنید تا از مصرف توکنهای تکراری جلوگیری شود.
اما مدیریت این دادهها در مقیاس میلیونی، چالشهای جدیدی در زمینهٔ بازیابی ایجاد میکند — در تحلیل ما دربارهٔ پایگاههای داده برداری (Vector Databases) این موضوع را بررسی کردهایم.




گفتگو