<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>نوشته های یادستان</title><link>https://virgool.ir/feed/@yaadestan</link><description></description><language>fa</language><pubDate>2026-11-11 07:34:35</pubDate><image><url>https://files.virgool.ir/upload/users/4916193/avatar/M18AS4.png</url><title>یادستان</title><link>https://virgool.ir/@yaadestan</link></image><item><title>خطایی که هیچ لاگی نداشت</title><link>https://virgool.ir/@yaadestan/خطایی-که-هیچ-لاگی-نداشت-v43dwfzsxjhg</link><description>خطایی که هیچ لاگی نداشتیک کانال تلگرام یک روز کامل ساکت ماند. سرویس بالا بود، لاگ‌ها تمیز بودند، داشبورد سبز بود. و دقیقاً همین، سخت‌ترین بخش ماجرا بود.این نوشته دربارهٔ یک باگ نیست. دربارهٔ دسته‌ای از خرابی‌هاست که سیستم‌های ما را بی‌صدا از کار می‌اندازند: خرابی‌هایی که هیچ‌جا خودشان را گزارش نمی‌کنند. اگر کدی در production دارید، احتمالاً همین حالا یکی از این‌ها را دارید و خبر ندارید.ماجرا از یک جملهٔ ساده شروع شد«دیروز تلگرام هیچی ارسال نکرد، ببین چرا.»یک ربات Go که هر شب خبرهای فناوری را خلاصه می‌کند و در یک کانال می‌گذارد. روی سرور خودش، جدا از سایت اصلی.اولین کاری که هر کسی می‌کند: ssh به سرور، journalctl -u &lt;service&gt;.Connection timed out during banner exchangeسرور جواب نمی‌داد. ولی وقتی پورت را تست کردم، باز بود:82.x.x.x:22 -&gt; Trueخب پس سرور زنده است و فقط sshd گیر کرده؟ این تحلیل، دو ساعت وقت من را گرفت. و کاملاً غلط بود.درس اول: ابزار سنجش هم می‌تواند دروغ بگویدیک بار دیگر سنجیدم، این‌بار با زمان‌بندی:connect=0.000965s   ttfb=0.000000sنُه‌دهم میلی‌ثانیه، برای سروری در اروپا؟ این سرعتِ localhost است.یعنی چیزی روی شبکهٔ خودِ من دست‌دادن TCP را جواب می‌داد و بعد بسته‌ها را می‌انداخت. آن True یک مثبتِ کاذب بود.نتیجه‌ای که باید همان اول می‌گرفتم: از این ماشین اصلاً به آن سرور دسترسی ندارم. سرور مشکلی نداشت؛ مسیر داشت.اگر ابزارتان چیزی می‌گوید که با فیزیک نمی‌خواند — تأخیر شبکه‌ای که از سرعت نور کمتر است، حافظه‌ای که منفی است، تستی که در صفر ثانیه پاس می‌شود — به ابزار شک کنید، نه به فیزیک.درس دوم: نبودِ خطا، دلیلِ نبودِ خرابی نیستسرور را عوض کردیم. روی میزبان جدید، اسکریپت استقرار یک canary اجرا می‌کند: قبل از اینکه باینری زنده را دست بزند، نسخهٔ جدید را با -check صدا می‌زند تا ببیند اصلاً کار می‌کند یا نه.worker: config fetched — active=true channels=1 sources=66&#xA;worker: check FAILED: Telegram token rejected:&#xA;  Post &#34;https://api.telegram.org/bot&lt;token&gt;/getMe&#34;: net/http: TLS handshake timeoutدو خط، دو خبر متضاد:خط اول: ارتباط با سرور اصلی سالم است. راز مشترک درست است، کانفیگ گرفته شده.خط دوم: به تلگرام نمی‌رسد.و مهم‌تر: خطا می‌گوید «token rejected»، ولی علتش TLS handshake timeout است. یعنی توکن اصلاً بررسی نشده — درخواست هرگز به مقصد نرسیده. پیام خطا، علت را اشتباه اسم برده بود.علت واقعی: بن‌بستی که وانمود می‌کند باز استapi.telegram.org هم رکورد A دارد (IPv4) هم AAAA (IPv6). روی آن میزبان سنجیدم:curl -4 https://api.telegram.org/   -&gt;  302, TLS در ۴۰ms&#xA;curl -6 https://api.telegram.org/   -&gt;  هیچ‌وقت هیچ‌چیز&#xA;curl -6 https://cloudflare-dns.com/ -&gt;  301  (پس IPv6 خودِ ماشین سالم است)مسیر IPv6 به تلگرام از آن ارائه‌دهنده یک بن‌بست بود. ولی نه از آن بن‌بست‌های محترم که اتصال را رد می‌کنند — از آن‌هایی که دست‌دادن TCP را کامل می‌کنند و بعد هر چیزی را می‌بلعند.و همین جزئیات، ماجرا را از یک خرابی معمولی به یک تلهٔ واقعی تبدیل می‌کند.چرا هیچ درمان خودکاری جواب نداد۱. Happy Eyeballs (RFC 8305). استاندارد مدرن می‌گوید وقتی مقصد هم IPv4 دارد هم IPv6، هر دو را مسابقه بده و هرکدام زودتر وصل شد را نگه دار. Go همین کار را می‌کند.اینجا IPv6 وصل می‌شود. مسابقه را می‌برد. بعد آویزان می‌ماند. Happy Eyeballs دقیقاً به‌خاطر همین حالت شکست می‌خورد: معیارش «اتصال» است، نه «کار کردن».۲. /etc/gai.conf. درمان کلاسیک برای «IPv4 را ترجیح بده»:precedence ::ffff:0:0/96  100&#xA;اضافه‌اش کردم، canary را دوباره زدم — دقیقاً همان خطا. چون resolverِ خالصِ Go اصلاً gai.conf را نمی‌خواند؛ سیاست RFC 6724 را خودش پیاده کرده.این را حدس نزدم. امتحان کردم. و این دومین باری بود که در همین ماجرا، «راه‌حل بدیهی» در عمل هیچ کاری نکرد.درس سوم: درمان را کجا بگذاریمسه گزینه روی میز بود:خاموش‌کردن IPv6 در کرنلکار می‌کند، ولی وضعیتِ سرور است — با بازساختِ ماشین بی‌صدا برمی‌گرددپین‌کردن IP در /etc/hostsامروز کار می‌کند، روزی که تلگرام شماره عوض کند می‌پوسداصلاح در خودِ باینریبا کد سفر می‌کند، نسخه‌بندی می‌شود، و نمی‌تواند فراموش شودسومی را انتخاب کردم:// اول IPv4، و اگر نشد برگشت به شبکهٔ اصلی.&#xA;// میزبانی که واقعاً فقط IPv6 دارد باز هم کار می‌کند —&#xA;// فقط هزینهٔ یک dialِ شکست‌خورده را می‌دهد.&#xA;func dialIPv4First(ctx context.Context, network, addr string) (net.Conn, error) {&#xA;&#x9;d := &amp;net.Dialer{Timeout: 15 * time.Second, KeepAlive: 30 * time.Second}&#xA;&#x9;if conn, err := d.DialContext(ctx, &#34;tcp4&#34;, addr); err == nil {&#xA;&#x9;&#x9;return conn, nil&#xA;&#x9;}&#xA;&#x9;return d.DialContext(ctx, network, addr)&#xA;}و برای اینکه ثابت شود واقعاً کد درستش کرده، قبل از استقرار، تغییرِ gai.conf را برگرداندم. اگر canary سبز می‌شد، دیگر جای شکی نبود.سبز شد.اصل کلی: اگر خرابی با بازساختِ سرور برمی‌گردد، درمانش را در سرور نگذارید. حالتِ سرور، مستنداتِ نانوشته است؛ و مستنداتِ نانوشته با اولین تعویضِ ماشین از بین می‌رود.و بعد، درسی که از همه گران‌تر بودوسط همین کار، در لاگ چیز دیگری دیدم:worker: summarise &#34;...&#34; failed: 401 Unauthorized: {&#34;message&#34;:&#34;User not found.&#34;}کلید سرویس هوش مصنوعی هم باطل بود. یعنی حتی با تلگرامِ سالم، هیچ خلاصه‌ای ساخته نمی‌شد و چیزی برای انتشار نمی‌ماند.دو علتِ مستقل، همزمان، برای یک نشانه. و این خیلی رایج‌تر از چیزی است که فکر می‌کنیم: وقتی سیستمی مدتی بدون نظارت می‌ماند، خرابی‌ها جمع می‌شوند. اولی را که پیدا کردید، فرض نکنید تمام شد.چرا هیچ‌کدام از این‌ها در لاگ ورکر نبوداینجا اصل ماجراست.آن ربات یک ویژگی داشت که تا آن روز کسی متوجهش نشده بود: وقتی ارسال به تلگرام رد می‌شود، هیچ ردی در لاگِ خودش نمی‌ماند. پست ساخته می‌شود، در پایگاه داده ثبت می‌شود، و فقط فراخوانِ آخر شکست می‌خورد. journalctl کاملاً سالم به نظر می‌رسد.قبلاً همین باعث شده بود یک منبع خبری ماه‌ها هیچ‌چیز منتشر نکند و کسی نفهمد.و لایهٔ دوم: لاگِ ورکر روی سرورِ خودش بود — تنها جایی که اپراتور نمی‌تواند نگاه کند. وقتی آن سرور از دسترس خارج شد، آن لاگ عملاً وجود نداشت.راه‌حل: سیستم باید بتواند بگوید چه‌اش شدهسه چیز اضافه کردیم به پنل مدیریت:۱. نوار فعالیت ۱۴ روزه، با ستون برای هر روز — حتی روزهای خالی.این جزئیات مهم‌تر از چیزی است که به نظر می‌رسد. یک GROUP BY ساده برای روزی که ربات نچرخیده هیچ ردیفی برنمی‌گرداند. و «ردیف نیست» در چشمِ آدم «مشکلی نبود» خوانده می‌شود. راه‌حل یک خط SQL است:WITH d AS (&#xA;  SELECT generate_series(&#xA;    (now() AT TIME ZONE &#39;Asia/Tehran&#39;)::date - 13,&#xA;    (now() AT TIME ZONE &#39;Asia/Tehran&#39;)::date,&#xA;    interval &#39;1 day&#39;&#xA;  )::date AS day&#xA;)&#xA;SELECT d.day, count(*) FILTER (WHERE p.status = &#39;posted&#39;)&#xA;FROM d LEFT JOIN posts p ON (p.created_at AT TIME ZONE &#39;Asia/Tehran&#39;)::date = d.day&#xA;GROUP BY d.day ORDER BY d.day;روزِ ساکت حالا یک صفرِ دیده‌شدنی است، نه یک شکاف. (و تهران، نه UTC — «دیروزِ» کاربر با دیروزِ سرور یکی نیست، و باکتِ UTC یک شبِ ایران را بین دو ردیف می‌شکند.)۲. یک جملهٔ حکم، نه یک مشت عدد.این دو حالت در شمارندهٔ «پست‌های امروز» کاملاً یکسان‌اند:ربات اصلاً اجرا نشد → صفر پستربات اجرا شد و هر ارسال رد شد → صفر پستو دو تعمیر کاملاً متفاوت می‌خواهند. پس پنل حالا خودش می‌گوید کدام است، به ترتیبِ علتِ بیرونی‌تر: ورکر آفلاین ← ربات خاموش ← «ساخته شد ولی نرفت» ← «اصلاً ساخته نشد».۳. لاگِ ورکر، در پنل، با زمان.به‌جای دست‌زدن به ۴۵ نقطهٔ log.Printf، ضبط‌کننده خودش را به‌عنوان مقصدِ اضافهٔ لاگرِ استاندارد نصب می‌کند:log.SetOutput(io.MultiWriter(os.Stderr, recorder))&#xA;یک خط، و هر log.Printf در هر جای باینری ضبط می‌شود — از جمله در نقطه‌ای که سالِ بعد اضافه می‌شود و کسی یادش نمی‌ماند دستی ثبتش کند.حلقه کران‌دار است (۵۰۰ خط) و قدیمی‌ترین خط را دور می‌ریزد. این هم عمدی است: ورکری که به سرور نمی‌رسد به کارش ادامه می‌دهد و بافرِ بی‌کران یک قطعیِ شبکه را به OOM تبدیل می‌کند. و دلیلِ گیرکردنِ یک سیستم تقریباً همیشه در کاری است که آخر کرده.و یک تلهٔ آخر، که فوراً گیرمان انداختبعد از اینکه همه‌چیز درست شد، پنل هنوز یک کادر قرمز نشان می‌داد: همان خطای کلید هوش مصنوعی.ولی آن خطا دو ساعت قبل اتفاق افتاده بود و از آن به بعد همه‌چیز سالم بود. مشکل این بود که آن فیلد زمان نداشت.last_error_at = CASE&#xA;  WHEN EXCLUDED.last_error = &#39;&#39; THEN NULL&#xA;  WHEN EXCLUDED.last_error IS DISTINCT FROM t.last_error THEN now()&#xA;  ELSE t.last_error_at&#xA;ENDخطایی که نمی‌توانید تاریخ‌گذاری کنید، خطایی است که نمی‌توانید ببندید. و پنلی که خطای مرده را مثل خطای زنده نشان می‌دهد، به آدم یاد می‌دهد قرمزها را نادیده بگیرد — که بدترین چیزی است که یک سیستم مانیتورینگ می‌تواند به شما یاد بدهد.حالا اگر بعد از خطا اجرای موفقی بوده، پنل آن را خاکستری و با برچسبِ «این خطا گذشته است» نشان می‌دهد.چک‌لیستی که از این ماجرا درآمداگر سرویسی دارید که کاری را خودکار انجام می‌دهد، این چند سؤال را از خودتان بپرسید:۱. اگر امروز هیچ کاری نکند، از کجا می‌فهمم؟ اگر جواب «وقتی کسی شکایت کند» است، مانیتورینگ ندارید.۲. «صفر کار» و «صفر موفقیت از ده تلاش» را از هم تشخیص می‌دهم؟ اگر نه، نصفِ تشخیص‌ها را کور می‌زنید.۳. متن خطاهایم زمان دارند؟ خطای بی‌تاریخ، هشدارِ همیشگی است؛ و هشدارِ همیشگی یعنی بی‌هشداری.۴. لاگ‌هایم جایی هستند که واقعاً می‌توانم بخوانمشان؟ لاگی که فقط روی ماشینِ خراب هست، دقیقاً وقتی لازمش دارید در دسترس نیست.۵. آیا خرابی‌ام با بازساختِ سرور برمی‌گردد؟ اگر بله، درمان را در جای اشتباه گذاشته‌اید.۶. روز خالی در گزارشم «صفر» است یا «هیچ ردیف»؟ این تفاوتِ یک خط SQL است و تفاوتِ دیدن و ندیدنِ یک هفته سکوت.و یک حرف آخرهیچ‌کدام از این‌ها دانشِ پیچیده‌ای نبود. IPv6 و A/AAAA را همه می‌دانند. generate_series یک تابع معمولی است. io.MultiWriter یک خط است.چیزی که سخت بود، عادتِ سنجیدن به‌جای حدس‌زدن بود: وقتی Test-NetConnection گفت پورت باز است، حرفش را باور نکردن. وقتی gai.conf «باید» جواب بدهد، امتحان کردن. و وقتی تعمیر جواب داد، برگرداندنِ راه‌حلِ موقت تا معلوم شود واقعاً کدام یکی کار کرده.این عادت را نمی‌شود از روی مقاله یاد گرفت. فقط با ساختن و خراب کردن و دوباره ساختن به دست می‌آید.ما در یادستان دقیقاً همین را درس می‌دهیم: نُه خودآموز فارسی — از الگوریتم و ساختار داده تا هوش مصنوعی، داده، وب و رباتیک — که در هر فصلشان چیزی می‌سازی و اندازه می‌گیری، نه اینکه فقط بخوانی. ۵۰۱ فصل، همه رایگان، با آزمایشگاه زنده داخل مرورگر.اگر این نوشته برایتان جالب بود، احتمالاً بنیاد را دوست خواهید داشت — خودآموزی که کلاً دربارهٔ همین است: چطور قبل از اجرا پیش‌بینی کنی کدت روی دادهٔ واقعی چه می‌کند.</description><category>یادستان</category><author>یادستان</author><pubDate>Thu, 06 Aug 2026 16:40:49 +0330</pubDate></item><item><title>الکترونیک یاد بگیر بدون اینکه حتی یک قطعه بخری فقط توی مرورگر خودت</title><link>https://virgool.ir/@yaadestan/الکترونیک-یاد-بگیر-بدون-اینکه-حتی-یک-قطعه-بخری-y7ognc0mywtw</link><description>بیشترِ آدم‌هایی که می‌خواهند الکترونیک یاد بگیرند، سرِ همان قدمِ اول متوقف می‌شوند. نه چون کنجکاو نیستند — چون قدمِ اول خرید است. برد بورد، مقاومت، LED، برد، سیم. بعد انتظار برای پست. بعد اولین مدار که کار نمی‌کند و نمی‌دانی مشکل از سیم است، از قطعه، از کد، یا از فهمِ خودت.خبرِ خوب این است که این ترتیب اجباری نیست. می‌شود اول یاد گرفت و بعد خرید. و جالب اینجاست که چند کارِ مهم هست که فقط بدون قطعهٔ واقعی می‌شود انجامشان داد.۱. می‌توانی عمداً خرابش کنیروی میز، اتصالِ کوتاه یعنی یک قطعهٔ سوخته و بوی بد. در شبیه‌ساز یعنی یک عدد که عوض می‌شود.این تفاوتِ کوچکی نیست. یادگیریِ واقعی وقتی اتفاق می‌افتد که بفهمی چیزی چرا کار نمی‌کند، نه وقتی که ببینی کار می‌کند. مقاومت را از مدارِ LED بردار و ببین چه می‌شود. LED را برعکس بگذار. مقاومت را صد برابر کن. هر کدام از این‌ها روی میز یا هزینه دارد یا اصلاً امتحان‌کردنی نیست، و هر کدام یک درسِ ماندگار است.من هیچ‌وقت ندیدم کسی با خواندنِ «مقاومت جریان را محدود می‌کند» واقعاً بفهمدش. ولی هر کسی که یک بار مقاومت را برداشته و دیده چه اتفاقی می‌افتد، دیگر یادش نمی‌رود.۲. می‌توانی جریان را ببینی، نه اینکه تصور کنیجریان و ولتاژ برای تازه‌کارها انتزاعی‌اند، چون نامرئی‌اند. روی میز هم تا مولتی‌متر نداشته باشی و بلد نباشی کجا بگذاری‌اش، نامرئی می‌مانند.در شبیه‌ساز، پراب را می‌گذاری روی هر نقطه از مدار و عدد را می‌خوانی. بعد یک چیز را عوض می‌کنی و می‌بینی عدد چطور تکان می‌خورد. همان انتزاعِ کتاب، تبدیل می‌شود به چیزی که با انگشتت حرکتش می‌دهی.و نکتهٔ مهم: در یک شبیه‌سازِ درست، آن عدد ساختگی نیست. پشتِ صحنه مدار به یک دستگاه معادله تبدیل شده و حل شده. نورِ LED از روی همان جریانِ حساب‌شده می‌آید. برای همین است که وقتی مقاومت را بزرگ می‌کنی، نور دقیقاً همان‌قدر کم می‌شود که در واقعیت کم می‌شد.۳. می‌توانی ده بار در یک ساعت اشتباه کنیروی میز، هر تغییر یعنی سیم درآوردن، جابه‌جا کردن، دوباره بستن. یک بعدازظهر شاید پنج مدار.در مرورگر، همان پنج مدار ده دقیقه است. و چون هزینهٔ هر آزمایش تقریباً صفر است، چیزهایی را امتحان می‌کنی که وگرنه امتحان نمی‌کردی — همان «یعنی اگر این کار را بکنم چه می‌شود؟»هایی که آدم روی میز به خودش اجازه‌شان نمی‌دهد چون حوصله می‌برد.۴. می‌توانی قبل از خرید بفهمی چه لازم داریاین یکی خیلی عملی است. وقتی چند هفته در شبیه‌ساز کار کرده باشی، دقیقاً می‌دانی چه قطعاتی لازم داری و چرا. به‌جای خریدنِ یک «پکِ کامل» که نصفش هیچ‌وقت باز نمی‌شود، فهرستی داری که خودت به آن رسیده‌ای.پس چرا اصلاً قطعهٔ واقعی؟چون شبیه‌ساز واقعیت نیست، و هر کسی که خلافش را بگوید دارد چیزی می‌فروشد.چیزهایی هست که فقط روی میز یاد می‌گیری: سیمِ شل که شایع‌ترین خرابیِ دنیای واقعی است و در شبیه‌ساز اصلاً وجود ندارد. قطعه‌ای که داغ می‌شود. موتوری که زیرِ بارِ واقعی کند می‌شود. پایه‌هایی که برچسب ندارند و باید از دیتاشیت پیدایشان کنی.نگاهِ درست این است: شبیه‌ساز جایگزینِ سخت‌افزار نیست، مقدمه‌اش است. جایی است که ده بار اشتباه می‌کنی تا وقتی قطعهٔ واقعی رسید، بدانی داری چه می‌کنی.از کجا شروع کنیما یک شبیه‌ساز مدار آنلاین ساخته‌ایم که فارسی است، رایگان است، و در مرورگر اجرا می‌شود — بدون نصب و بدون ساختن حساب. مدار را واقعاً حل می‌کند، یعنی خازن روی منحنیِ واقعی شارژ می‌شود و analogRead عددی برمی‌گرداند که از ولتاژِ حل‌شدهٔ همان گره می‌آید.ساده‌ترین جای شروع این‌هاست:- مدار مقاومت و LED — مقاومت را عوض کن و نور را ببین- شارژ خازن — منحنیِ واقعی، نه انیمیشن- اولین چشمک‌زن با کدو اگر خواستی عمیق‌تر بروی، در مقالهٔ کاملِ شبیه‌ساز مدار آنلاین نوشته‌ایم که چطور با شبیه‌ساز جدی یاد بگیری نه اینکه فقط باهاش بازی کنی — از جمله یک عادتِ ساده که تفاوتِ بین تماشا و یادگیری است: قبل از زدنِ دکمهٔ اجرا، اول حدست را بگو.اگر به کارت آمد، یک ❤️ بزن تا به دستِ کسانی که همین گیر را دارند هم برسد. و اگر خودت شبیه‌ساز خوبی می‌شناسی یا تجربه‌ای داری، در کامنت بنویس — کنجکاوم بدانم دیگران از کجا شروع کرده‌اند.</description><category>یادستان</category><author>یادستان</author><pubDate>Wed, 05 Aug 2026 18:01:05 +0330</pubDate></item></channel></rss>