<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>پست‌های انتشارات اسنپ</title>
        <link>https://virgool.io/snapp-eng/feed</link>
        <description>بلاگ تیم فنی و محصول اسنپ
engineering@snapp.cab</description>
        <language>fa</language>
        <pubDate>2026-06-16 17:35:00</pubDate>
        <image>
            <url>https://files.virgool.io/upload/publication/jaoqwalhu46r/ihczza.jpeg</url>
            <title>اسنپ</title>
            <link>https://virgool.io/snapp-eng</link>
        </image>

                    <item>
                <title>سیستم‌ یادگیری ماشین برای پیش‌بینی ETA، داستان یک بلوغ</title>
                <link>https://virgool.io/snapp-eng/%D8%B3%DB%8C%D8%B3%D8%AA%D9%85-%DB%8C%D8%A7%D8%AF%DA%AF%DB%8C%D8%B1%DB%8C-%D9%85%D8%A7%D8%B4%DB%8C%D9%86-%D8%A8%D8%B1%D8%A7%DB%8C-%D9%BE%DB%8C%D8%B4-%D8%A8%DB%8C%D9%86%DB%8C-eta-%D8%AF%D8%A7%D8%B3%D8%AA%D8%A7%D9%86-%DB%8C%DA%A9-%D8%A8%D9%84%D9%88%D8%BA-oi0crxocax4j</link>
                <description>منبع عکس: Erik Johansson، ارتقا کیفیت توسط Let&#039;s Enhanceمقدمهپیش‌بینی ETA (تخمین زمان رسیدن سفر) یکی از مهمترین سرویس‌های زیر مجموعه نقشه در هر اپلیکیشن تاکسی اینترنتی است. در اسنپ از سرویس ETA در جاهای گوناگون مثل قیمت گذاری سفر، پیشنهاد سفر به راننده و نمایش تخمین زمان رسیدن به راننده و مسافر در مراحل مختلف سفر، استفاده می‌شود. بنابراین دقت ETA همواره یکی از مهمترین متریک‌ها در تیم نقشه است و تغییرات هرچند کوچک آن می‌تواند متریک‌های مهم بیزینسی را تغییر دهد.به طور کلاسیک برای محاسبه ETA از روش‌های مبتنی بر دیتای ترافیکی و مسیریابی استفاده میشود. به این صورت که با در اختیار داشتن داده‌‌‌های live و historical ترافیکی می‌توان با استفاده از الگوریتم‌های مسیریابی، مدت زمان حدودی هر سفر را پیش‌بینی کرد. در سال‌های اخیر برای افزایش دقت ETA،از روش‌های یادگیری ماشین در کنار روش‌های مسیریابی، و یا به صورت مستقل استفاده شده است .در این مقاله قصد داریم داستان استفاده از سیستم‌ یادگیری ماشین برای کمک به محاسبه ETA و چالش‌های مهندسی آن، و همینطور رویکرد ما برای حل این چالش‌ها رو با شما به اشتراک بگذاریم.از آنجایی که تجربه‌های جدی استفاده از سیستم‌های یادگیری ماشین در فضای پروداکشن و در مقیاس بالا خیلی در دسترس نیست، سعی کردیم نگاه ما در این مقاله به جای بیان مشکلات و چالش های الگوریتمی و دیتا‌ساینسی و یا بهبود دقت مدل‌ها، نگاه به چالش‌های این سیستم با هدف رسیدن به یک سیستم قابل اتکا، مقیاس پذیر و قابل نگه‌داری باشد.فاز اول، تلاش برای کشف یک مسیردر ابتدای مسیر پروژه‌های یادگیری ماشین، مهمترین مسئله، سریع بودن در رسیدن به یک نمونه اولیه است که اثبات کند مسئله با استفاده از روش‌های یادگیری ماشین قابل حل است (PoC). دلیل این رویکرد و تفاوت آن نسبت به دیگر پروژه‌های توسعه نرم افزار این است که در مسائل یادگیری ماشین یک ابهام بزرگ برای مهندسان، تیم محصول و بیزینس وجود دارد که آیا مسئله اصولا قابل حل شدن با روش های یادگیری ماشین است یا خیر؟ دلیل این ابهام هم متفاوت بودن هر مسئله در یادگیری ماشین با توجه به دیتای موجود، نوع فرمول بندی مسئله و روش‌های حل آن است. به طور مثال هیچ نمونه مشابهی در کارهای منتشر شده وجود ندارد که نشان دهد مسئله پیشبینی یا بهبود ETA (دقیقا با داده های موجود در اسنپ) قابل حل با روش X است. حال این مسئله را مقایسه کنید با توسعه یک API که تا حد خیلی زیادی برای اکثر چالش‌های آن صدها ابزار، Best Practice و نمونه‌های موفق وجود دارد. بنابراین در شروع پروژه‌های یادگیری ماشین هدف اصلی برطرف کردن ابهامات و رسیدن به یک راه حل اولیه قابل قبول و انجام آزمایشات متنوع در سریع ترین زمان ممکن است و داشتن یک ساختار منظم برای توسعه، کدهای تمیز و رعایت همه‌ی اصول مهندسی نرم افزار از اهمیت کمتری برخوردار است.همین الگو در پروژه بهبود ETA اسنپ هم در دستور کار بود. در ابتدا سعی کردیم همه توانمان را بر روی مطالعه روش‌های منتشر‌ شده، جمع آوری و تمیز کردن داده، و آزمایش سناریو های مختلف صرف کنیم. پس از چندین ماه فاز تحقیق و توسعه به راه‌حلی رسیدیم که حداقل نیازمندی‌های بیزینس را چه از نظر دقت و چه از نظر سرعت و مصرف منابع برطرف میکرد. با توجه به اهمیت ETA در سرویس‌های مختلف اسنپ به سرعت یک سرویس برای serve کردن مدل را توسعه دادیم و پس از چند مرحله تست سرویس بر روی production مستقر شد.خروجی این مرحله اولیه، تعداد زیادی Jupyter Notebook بود که آزمایشات مختلف بر روی آن‌ها انجام شده بود و همینطور یک سرویس API بسیار ابتدایی با یک endpoint صرفا جهت serve مدل اولیه.چالش های فاز اولهمانطوری که در بخش قبلی اشاره کردیم، از آنجایی که هدف ما در فاز اولیه صرفا رسیدن به یک جواب معقول برای مسئله بود، بسیاری از استاندارد‌‌های توسعه در این فاز رعایت نشده بود و ادامه دادن پروژه ازینجا به بعد با شرایط فعلی کمی چالش برانگیز بود. در ادامه به تعدادی از این چالش‌ها اشاره میکنیم.نبود مستند سازی به شکل منظم و ساختارمند باعث شده بود پس از مدتی با اضافه شدن افراد جدید و خروج برخی از نیرو‌ها از پروژه، میزان زیادی از تاریخچه آزمایشات قبلی موجود نباشد و گاها مجبور به تکرار آزمایشات با افراد جدید باشیم.کد‌های پروژه همگی بر روی تعداد زیادی Jupyter Notebook بود که روی هر کدام، آزمایشات مختلفی انجام شده بود؛ بخش های زیادی از کدها کامنت بودند، ترتیب اجرای کد‌ها مشخص نبود، بخش های زیادی از کدها در نوت‌بوک های مختلف تکرار شده بودند، و در کل ادامه دادن آزمایشات قبلی و ایجاد بهبود بر روی همین کد‌ها تا حد زیادی ناممکن بود.آموزش مدل‌ها با داده‌های به روز، کاری پرهزینه بود چرا که تعداد زیادی فرآیند دستی و غیر بهینه باید اجرا انجام میشد، که هم ساعت‌های زیادی از تیم مهندسی میگرفت و هم از طرفی احتمال خطا در هر بخش را زیاد می‌کرد.مدل پیشنهادی یک مدل بسیار سنگین از نظر منابع مصرفی بود که هم زمان آموزش زیادی را می‌طلبید، و هم ذخیره سازی مدل‌ها را با چالش همراه میکرد. از طرفی با وجود این مدل حجیم، سرویس نهایی سرویسی گران و پرمصرف از نظر مصرف منابع سخت افزاری مثل رم، سی پی یو و جی پی یو بود.سیستم serving مدل‌ها بسیار نابالغ بود و observability و scalability لازم را نداشت. به عبارت دیگر فرآیند دیپلوی کردن یک مدل جدید هزینه مهندسی بالایی داشت و از طرفی سیستم monitoring و tracing مناسبی برای پایش سیستم و پیدا کردن خطاهای احتمالی موجود نبود.‌امکان تشخیص کاهش دقت مدل‌ها در طول زمان به صورت آنلاین وجود نداشت و هر بار تیم مهندسی با چند روز تاخیر متوجه کاهش دقت مدل می‌شد که reliability سیستم از نظر پایداری دقت را کاهش میداد.به طور خلاصه موارد بالا باعث شده بود تا نگهداری سیستم به طور reliable و scalable کاری بسیار پرهزینه باشه و از طرفی فرآیند‌های بهبود دقت و اجرای ایده‌های جدید هم با کندی زیادی پیش برود.فاز دوم، تلاش برای بلوغپس از مشاهده چالش‌هایی که در بخش قبل عنوان کردیم، فاز دوم توسعه این سیستم یادگیری ماشین را با هدف ارائه سیستمی قابل نگهداری و قابل اتکا آغاز کردیم. به طور عمومی در این مرحله از توسعه، پس از اینکه از feasible بودن حل مسئله با استفاده از روش‌های یادگیری ماشین اطمینان حاصل شد، تلاش می‌شود سیستم اولیه با استفاده از best practice های مهندسی نرم افزار و MLOps برای استفاده در محیط های پروداکشنی بازنگری شود.در ادامه این بخش به مروری اجمالی از ماژول‌های استفاده شده در این فاز برای غلبه بر چالش‌های ذکر شده می‌پردازیم.پروسه مستند‌سازی منظمیک بخش بسیار مهم از پروژه‌های یادگیری ماشین، دانشی است که در طول پروژه و با انجام آزمایشات مختلف در تیم به وجود می‌آید. این دانش که گنج بزرگی برای حال و آینده پروژه است، در صورت مستند‌‌سازی ناقص و غیر منظم، پس از مدتی از بین می‌رود و باعث ‌می‌شود مجبور به تکرار آزمایشات قبلی شویم و یا نتوانیم به درستی برای آینده پروژه تصمیم بگیریم. اولین اقدامی که در این فاز انجام دادیم توسعه یک روش مستند‌سازی بود. به این شکل که پس از هر آزمایش، تیم باید یک کارت مستندسازی برای آن پر میکرد که خلاصه‌ای از مهمترین اطلاعات آن آزمایش بود. اطلاعاتی که در هر کارت پر میشد شامل فرضیه ای که این آزمایش در تلاش تست آن بود، دیتای استفاده شده، روش آزمایش و نتایج حاصل بود. سعی کردیم این کارت تا حد ممکن خلاصه باشد تا پر کردن آن هزینه اضافه برای تیم نداشته باشد و از طرفی شامل همه موارد ضروری باشد. از طرفی پر کردن این کارت‌ها را به بخشی از Definition of Done برای تسک‌ها تبدیل کردیم تا از پر شدن منظم آن در طول زمان اطمینان کسب کنیم. با این کار آرشیوی ارزشمند از تاریخچه پروژه تهیه شد که به سوالات زیادی در آینده پاسخ می‌داد و باعث میشد تیم صرفا بر روی کارهای خلاقانه جدید تمرکز کند و بتواند از مشاهده کار‌های گذشته دانش انباشته‌ای را کسب کند.نمونه کارت مستند سازی هر آزمایش کد‌های ماژولاریکی از مواردی که در اکثر پروژه‌های یادگیری ماشین رعایت نمی‌شود رعایت حداقل استاندارد‌های مهندسی نرم افزار مانند نوشتن کد‌های ماژولار و تمیز است. اکثر اوقات وقتی در مورد پروژه‌های یادگیری ماشین فکر می‌کنیم تعدادی فایل ‌ Jupyter Notebook به ذهن می‌آید که پر از کد‌های کثیف، پرینت های طولانی، کد‌های کامنت شده و در مجموع مشخص نبودن روند کد‌ها است. همانطور که در فاز قبلی گفتیم این موضوع به ماهیت آزمایش محور پروژه‌های یادگیری ماشین برمیگردد. در ابتدای پروژه مهندسین تمام تمرکز خود را به اکتشاف مسئله و تست ایده‌های مختلف می‌گذارند که ابزاری تعاملی مثل Jupyter Notebook انتخاب مناسبی برای این نوع از اکتشافات و بررسی سریع نتیجه است. با گذشت زمان و بلوغ نسبی پروژه این رویکرد باعث کند شدن آزمایشات و عدم توانایی در استفاده از ابزار‌های مختلف برای Scale کردن پروژه می‌شود. به عنوان مثال اگر بخواهیم یک پایپ لاین آموزش اتوماتیک را توسعه دهیم، باید کد‌هایی تمیز و ساختارمند داشته باشیم.از این رو در این بخش پس از تحقیق بر روی چندین ابزار و فریمورک به ابزار Kedro رسیدیم که هدف آن همین ماژولار کردن کدهای یادگیری ماشین بر طبق استاندارد‌های توسعه نرم افزار بود. پس از این سعی کردیم همه کد‌های موجود در Jupyter Notebook ها را بر طبق این فریمورک بازنویسی کنیم. در انتها موفق به توسعه یک کد بیس تمیز، ماژولار، و با ویژگی اضافه یا کم کردن یک قابلیت به هر بخش از فرایند آموزش شدیم. این کد بیس جدید شامل چهار پایپ لاین کاملا جدا از هم برای جمع آوری داده، پیش پردازش داده، آموزش مدل و ارزیابی مدل بود. هر پایپلاین شامل تعدادی node بود که هر یک وظیفه ای را بر عهده داشتند. همه پایپ لاین ها و node ها به راحتی با یک فایل Yaml قابل مدیریت و تغییر بود و آزمایشات مختلف را بسیار سریع و منظم میکرد. حالا کافی بود بجای گشتن در کدهای تو در تو برای تغییر یک پارامتر (مثل learning rate) صرفا در فایل کانفیگ، پارامتر مورد نظر را تغییر دهیم. از طرفی این کدبیس جدید قابلیت‌های دیگری مثل integrate کردن فرایند آموزش با ابزارهای دیگر هم به ما داد. مثلا در سیستم آموزش اتوماتیک که جلو‌تر به آن می پردازیم، در هر راند از آموزش اتوماتیک صرفا یک فایل کانفیگ آماده و آموزش شروع میشد. در انتها ویژگی مهم دیگر این ساختار امکان آسان اضافه کردن ماژول‌های جدید برای آزمایشات بود. به طور مثال اگر تیم سعی داشت تا یک feature جدید را به مدل اضافه کند به راحتی به پایپلاین پیش پردازش داده ها رجوع می کرد، ایده خود را در قالب یک node پیاده سازی میکرد و در فایل کانفیگ آن node را فراخوانی میکرد.ماژولار کردن پایپلاین آموزش با Kedroبهینه سازی مدلبه دلیل تمرکز زیاد بر دقت مدل در گام‌‌های اولیه، احتمال استفاده از مدل‌های حجیم و غیر بهینه بسیار زیاد است. در فاز اولیه شما اصلا ایده‌ای ندارید که راه‌حل شما به جواب معقولی می‌رسد یا نه. پس دلیلی وجود ندارد که ذهن خود را درگیر بهینه بودن راه‌حل از نظر استفاده از منابع یا سریع بودن بکنید. اما زمانی که از این مرحله عبور کردیم و به یک راه‌حل معقول رسیدیم همه این دغدغه ها اهمیت پیدا می‌کنند. پس در این مرحله، ماجراجویی ما در جهت بهینه کردن مدل‌ها به هدف کم کردن منابع مصرفی، افزایش سرعت پاسخ مدل و مقیاس پذیر بودن مدل در عین حفظ دقت اولیه آغاز شد. همانطور که می‌دانیم در بسیاری از موارد یک مصالحه (Trade off) بین دقت مدل و سبک بودن آن برقرار است و محدودیت‌های بیزینسی مثل حداقل دقت قابل قبول و یا حداکثر میزان منابع مصرفی مجاز تعیین کننده این موضوع هستند که تا چه میزان می‌توانیم دقت را فدای بهینه کردن راه‌حل ها کنیم. در این مرحله چندین گزینه برای بهینه کردن مدل پیش روی ما بود. یک گزینه بهینه کردن هایپر پارامتر‌های مدل در جهت کوچک کردن مدل بود. تعداد لایه‌های شبکه‌های عصبی یا میزان عمق درخت در روش‌های مبتنی بر درخت تصمیم از جمله این موارد هستند. گزینه دیگر استفاده از یک runtime بهینه تر مانند استفاده از LightGBM به جای Scikit Learn یا استفاده از ONNX به جای TensorFlow بود. پس از آزمایشات مختلف بر روی هر دوی این مسیر‌ها به راه‌حلی رسیدیم که از نظر منابع مصرفی حدود ۵۰ برابر کمتر، از نظر سرعت حدود ۲ برابر سریعتر و تقریبا با همان دقت سابق بود. این مدل مسیر‌های جدیدی مانند استفاده از این راه‌حل در شهر‌های مختلف یا آموزش مدل بر روی فضای ابری اسنپ را به ما داد.تشخیص دریفتنگهداری سیستم‌های یادگیری ماشین از جوانبی شبیه به نگهداری دیگر سیستم‌های نرم‌افزاریست. هر دو این سیستم‌ها نیازمند نگهداری در زمان بروز خطاهای انسانی در مرحله توسعه، بروز خطاهای سخت افزاری و معیوب شدن آنها و همچنین بروز باگ‌های پیش بینی نشده نرم‌افزاری هستند. سیستم‌های یادگیری ماشین در ابعاد دیگری نیز نیازمند انواعی از نگهداری هستند که فقط مختص خود آنهاست. از آنجایی که مدل‌های یادگیری ماشین در تعامل مستقیم با دنیای بیرون هستند و دقت آنها تابعی از رفتار محیط است، در صورت بروز تغییراتی در رفتار محیط امکان کاهش دقت این سیستم‌ها وجود دارد که نیازمند نگهداری و توجه است. به مجموعه تغییرات محیط که باعث کاهش دقت مدل‌ها می شود، دریفت (Drift) می‌گویند که به دسته های مختلفی، مثل Data Drift که شامل تغییر توزیع داده‌های ورودی مدل است، و یا Concept Drift که به تغییر رابطه ورودی مدل با خروجی قابل انتظار مربوط می‌شود، تقسیم‌بندی می‌شوند. در صورت بروز Drift سیستم نیازمند تغییراتی است تا دقت سیستم حفظ شود. مهمترین و سرراست ترین کاری که در زمان بروز دریفت می‌توان انجام داد، آموزش مجدد مدل با داده‌های جدیدتر است (Retraining). در سیستم پیشبینی ETA ما هم بروز دریفت‌های مختلف مثل تغییرات پترن ترافیکی به دلایل مختلف مثل بازگشایی مدارس در مهرماه یا تغییرات ترافیکی در روزهای بارانی یا برفی و یا هر پدیده از قبل پیش‌بینی نشده‌، باعث کاهش دقت مدل می‌شد که علاقمند بودیم با توسعه یک سیستم اتوماتیک، این دریفت‌ها را تشخیص دهیم و قبل از آنکه دقت مدل به حد غیر قابل قبولی برسد، اقدامی در جهت تطبیق سیستم با شرایط به وجود آمده انجام دهیم.برای توسعه این سیستم پس از مطالعه و بررسی روش‌ها و ابزار‌های مختلف تصمیم گرفتیم که از ابزار DeepCheck که راه‌حل های مختلفی برای تشخیص انواع دریفت در اختیار قرار‌ می‌داد، استفاده کنیم. معماری این سیستم به این شکل بود که در هر ساعت، یکبار داده‌های موجود این ساعت با داده‌های همین ساعت در هفته‌های گذشته مقایسه می‌شد و یک متریک Drift Score در سیستم مانیتورینگ ثبت می‌شد. اگر در یک بازه زمانی مشخص تعداد مشخصی از Drift Score ها از حد آستانه‌ای بیشتر بود، سیستم مانیتورینگ یک Alert تولید می‌کرد تا تیم فنی از بروز Drift مطلع شود و اقدام لازم را انجام دهد.سیستم آموزش اتوماتیکدر سیستم‌های بالغ یادگیری ماشین یکی از متریک‌ها برای ارزیابی کارایی سیستم، زمان مورد نیاز برای آموزش یک مدل تا Deploy کردن مدل آموزش دیده است. در سیستم پیش‌بینی ETA هم علاقمند بودیم در کمترین زمان بتوانیم مدلی را آموزش دهیم. به این منظور کدبیس ماژولار شده ای که در بخش‌های قبل ذکر کردیم را به عنوان یک DAG که مخفف (Directed Acyclic Graph) است در سرویس Apache AirFlow که یک Workflow Orchestrator برای مدیریت کردن اجرای تسک‌های مختلف است، درآوردیم. با این کار کل فرآیند Retrain کردن مدل با زدن یک دکمه اجرا می‌شد و نتایج داخل ابزار Experiment management ما که یک سرویس MLFlow بود ذخیره ‌می‌شد. توسعه این DAG به ما امکان Retrain کردن اتوماتیک، در بازه‌های زمانی مشخص مثلا هفته‌ای یک بار و  یا بر اساس یک رویداد مشخص، مثل اعلام آلرت از سمت سیستم تشخیص دریفت را می‌داد. با توسعه این سیستم و اتصال آن به سیستم تشخیص دریفت مطمئن شدیم که مدل همواره می‌تواند خود را به سرعت با شرایط مختلف سازگار نگه دارد و از طرفی اگر قرار بود به هر دلیلی مدل را آموزش دهیم به جای صرف مدت زیادی از زمان نیروی فنی برای جمع آوری داده، آموزش مدل و ارزیابی آن، صرفا با زدن یک دکمه فرایند آموزش را اجرا می‌شد.بلوغ سیستم سروینگهمانطور که قبلتر شاره کردیم در فاز اولیه سیستم serve کردن مدل‌های آموزش دیده، یک سیستم بسیار ساده بود. با بلوغ سیستم، بروز نیازمندی‌های جدیدی مثل نیاز به تعامل API با دیگر سیستم‌ها مثل سیستم آموزش اتوماتیک و یا نیاز به Serve شدن تعداد بیشتری مدل که در شرایط گوناگون نیاز به اجرا داشتند، باعث شد تا در این فاز سعی کنیم سیستم سروینگ را بهبود دهیم. در این مرحله یک کد Extendable برای افزودن انواع مدل‌ها با انواع پیش‌پردازش‌ها توسعه داده شد تا بر اساس شرایط مختلف بتواند، مدل‌ها با کانفیگ‌های مختلفی را اجرا کنند. از طرف دیگر، امکان Serve چندین مدل به طور همزمان به این سرویس اضافه شد و همچنین با افزودن چندین endpoint سعی کردیم تعامل این سرویس با اجزای دیگر سیستم را برقرار سازیم.پایپلاین آموزش اتوماتیکدستاورد‌هابا توسعه ماژول‌هایی که در بخش قبل ذکر کردیم موفق شدیم:مدت زمان آموزش تا Deploy یک مدل را از حدود ۱ روز، به ۲ ساعت کاهش دهیم.هزینه نیروی مهندسی برای کار‌های تکراری، مانند Retrain کردن مدل‌ها را به حداقل برسانیم و تمرکز نیرو‌های متخصص را بر توسعه روش‌های جدید و بهبود کلی سیستم نگه‌ داریم.مشاهده پذیری (Observability) سیستم را با مانیتور کردن مداوم رفتار مدل و رفتار محیط بهبود ببخشیم.در پاسخگویی به تغییرات چابک‌تر باشیم.امکان تست سریع ایده‌های جدید را داشته باشیم.دانش انباشته از آزمایشات مختلف را به طور منظم نگهداری کنیم.و در مجموع یک سیستم یادگیری ماشین قابل اتکا، قابل نگهداری و مقیاس پذیر را توسعه دهیم.مسیر پیش رودر ادامه مسیر برای بلوغ بیشتر این سیستم سعی داریم تا پایپلاین‌های داده‌های ورودی برای آموزش و inference را بهینه و غنی کنیم که در این راستا استفاده از تکنولوژی‌های feature store را در دست بررسی داریم. همچنین استفاده از inference server ها برای serve کردن مدل‌ها با قابلیت‌های بیشتر مثل امکان A/B Testing، Canary Testing، auto scaling و batch inference را در دست بررسی داریم که در این راستا، در حال تست ابزار‌هایی مثل Seldon و KServe هستیم.جمع بندیدر این مقاله سعی کردیم روایتی از یک مسئله واقعی یادگیری ماشین و چالش‌های استفاده آن در محیط‌های پروداکشنی حساس را بیان کنیم و رویکرد ما نسبت به حل مشکلات و چالش‌ها را شرح دهیم. امیدواریم در ادامه بتوانیم از این دست از مقاله‌ها که چالش‌هایی کمتر پرداخته شده، در حوزه یادگیری ماشین و علوم داده را عنوان می‌کند را بیشتر منتشر کنیم.در انتها از همه اعضای تیم فنی و پروداکت تیم ETA Enabling نقشه اسنپ تشکر می‌کنم که با تلاش و انرژی همیشگی ساخت یک سیستم قابل اتکا را ممکن کردند.</description>
                <category>اسنپ</category>
                <author>سهیل کوهی</author>
                <pubDate>Mon, 07 Apr 2025 12:41:07 +0330</pubDate>
            </item>
                    <item>
                <title>یک آزمایش سریع شعله‌ور</title>
                <link>https://virgool.io/snapp-eng/%DB%8C%DA%A9-%D8%A2%D8%B2%D9%85%D8%A7%DB%8C%D8%B4-%D8%B3%D8%B1%DB%8C%D8%B9-%D8%B4%D8%B9%D9%84%D9%87-%D9%88%D8%B1-vpdpw093lkpl</link>
                <description>A Blazing Fast Experimentهدف و محتوای این پست احتمالا از عنوانش قابل برداشت باشه، توی این پست قصد داریم آزمایشی که توی اون، یکی از سرویس های Golangای اسنپ رو با Rust بازنویسی کردیم توضیح بدیم.سرویس تست A/Bسرویسی که برای این آزمایش سراغش رفتیم سرویس تست A/B اسنپ هست که طیف گسترده‌ای از آزمایش‌ها با اون انجام می‌شن و فیچر‌های جدید اسنپ با کمک اون برای کاربرها منتشر می‌شن. وظیفه‌ی این سرویس اینه که مشخص کنه یک قابلیت جدید برای چه کاربرهایی فعال باشه، که اینکار رو با کنترل Feature Flagها انجام میده. این Feature Flagها برای گروه‌های خاصی از کاربر‌ها تعریف می‌شن، که این گروه‌ها یا بر اساس معیار‌های تصادفی دسته‌بندی می‌شن یا با بعضی ویژگی‌های منحصر به فردتر مثل نوع دستگاه یا ورژن اپلیکیشن.قوانینی که باهاش این گروه‌ها مشخص می‌شن داخل ساختار داده‌ای به اسم Rule تعریف می‌شن. پس سرویس تست A/B در واقع یک mapping از Rule ها به Feature Flagها هست.و اصلی‌ترین endpoint این سرویس، Evaluate نام داره، که با گرفتن مشخصات یک کاربر، Feature Flagهایی که برای این کاربر فعال هست رو برمی‌‌گردونه.مقیاس کاری سرویستقریبا منتشر کردن تمام ویژگی‌های جدید اسنپ، با این سرویس شروع می‌شه. پس، از نقطه شروع کار برنامه، تا انجام دادن خیلی از کارهای جزئی‌تر مثل گرفتن سفر، دیدن مشخصات راننده و... همگی به این سرویس نیاز دارن.هرکدوم از این درخواست‌ها شامل:یک یا چند کاربر برای بررسیتعداد مشخص  Feature Flag برای بررسیهست و خروجی هر درخواست شامل لیست Feature Flagهای فعال برای هرکدوم از کاربر‌های فرستاده شده خواهد بود.استک فنیLanguage: GoPlatform: KubernetesWeb Framework: Echo, grpc-goRuntime: NativeRuntime Container: Alpineعملکرد و سرعتنکات زیر در نمودار‌ها و آزمایش رعایت شده:تمامی اندازه‌گیری‌ها با درصدی از درخواست‌های واقعی و در محیط Production انجام شده‌.در هر Request بین 50 الی 500 کاربر وجود داره. این درخواست‌ها جزو سنگین‌ترین درخواست‌ها از نظر بار محاسباتی محسوب می‌شن.پاد‌ها در Worker Nodeهای یکسان اجرا شده‌اند و از نظر زیرساختی کاملا مشابه هستن.منابع مشخص شده (request) برای هر Pod شامل:۱ هسته CPU (Request)۱ گیگابایت Memory (Request)در این نمودارها لود، بین دو پاد از اپلیکیشن پخش شده: نرخ درخواست‌هازمان پاسخگویی P99زمان پاسخگویی P90میانگین مصرف Memoryمیانگین مصرف CPU استک فنی جدید (Rust)Language: Rust (musl target)Platform: KubernetesWeb Framework: Axum(hyper, tower-http), tonicRuntime: TokioRuntime Container: Alpineاجرای اولیه آزمایششرایط و محیط آزمایش دقیقا مشابه نسخه اصلی برنامه است.این نتایج با اجرای برنامه با 5 پاد به دست اومده که مجموعا ۵ گیگابایت مموری و ۵ هسته CPU مصرف شده. نرخ درخواست‌ها (بین ۵ پاد)زمان پاسخگویی P99زمان پاسخگویی P90 میانگین مصرف Memoryمیانگین مصرف CPUهمونطور که مشخص هست، این نتایج فاصله خیلی زیادی از انتظارات ما داره. در واقع هدف از انتخاب Rust برای این آزمایش سریع‌تر شدن سرویس، درگیری کمتر منابع و افزایش Throughtput بود که هیچ کدوم داخل اجرای اولیه آزمایش مشاهده نشد!بعد از انجام دادن کمی Profiling و تحلیل رفتار اپلیکیشن، متوجه مصرف قابل توجه CPU برای Memory Allocation ها شدیم و با دنبال کردن این سرنخ به نتایج مشابه در اپلیکیشن‌های مشابه رسیدیم.توی این پست، به عملکرد ضعیف musl allocator، که پیاده‌سازی پیشفرض target انتخاب شده در مرحله build و مخصوص توزیع alpine هست، اشاره شده و نویسنده ادعای بهبود قابل توجه performance با glibc allocator رو کرده.در اولین تلاش برای بهبود، base image و build target برنامه رو تغییر دادیم که از glibc allocator استفاده بشه.این تغییر، رفتار غیر منطقی برنامه در لود‌های بالا رو بهبود داد. ولی یک رفتار ناخواسته جدید اضافه شد. الگوی مصرف حافظه در حالت idle با malloc (glibc)سرویس ما برای بهبود سرعت، بخشی از اطلاعات مورد نیاز رو به صورت دوره‌ای از دیتابیس دریافت می‌کنه و اون رو داخل Memory ذخیره می‌کنه تا در استفاده‌های بعدی دسترسی به دیتابیس نیاز نباشه.حجم این اطلاعات در نهایت عددی بین ۱۰۰ تا ۱۵۰ مگابایت می‌شه ولی تبدیل این اطلاعات از یک داده با فرمت yaml/json به ساختمان‌داده‌های آماده سمت برنامه نیازمند یک‌سری memory allocation هاست. مثل هر memory allocation دیگه (مثلا حافظه لازم برای پردازش یک در‌خواست) انتظار ما از برنامه اینه که بعد از انجام و از اسکوپ خارج شدن متغیر‌های میانی، حافظه به سیستم عامل برگرده و بتونیم از اون حافظه برای بقیه عملیات‌های برنامه استفاده کنیم.ولی اتفاقی که با malloc (glibc) افتاد، این بود که قسمت «برگشتن حافظه به سیستم عامل» برای این memory allocationهای میانی (که توسط کتابخونه serde سمت rust انجام می‌شد) فقط در یک سری شرایط خاص انجام می‌شد. نتیجه این رفتار رو توی نمودار بالا می‌بینید، برنامه در حالت idle قسمت‌های حافظه اختصاص داده شده رو به سیستم‌ عامل برنمی‌گردونه و در نهایت وقتی به memory limit می‌رسه توسط پلتفرم اجرایی (کوبرنتیز) restart می‌شه. رفتاری تا حدودی مشابه در این پست شرح داده شده و کنترل این رفتار (نمونه) خارج از اسکوپ برنامه اصلیه و نیازمند ارتباط مستقیم با allocator و کرنل هست. البته باید اشاره کنیم که این رفتار در حالت idle مشاهده می‌شه و allocation های کوچکتر (برای هندل کردن requestها) باعث افزایش مصرف مموری با این الگو نمی‌‌شن.راه حلخب همونطور که دیدیم نه musl allocator و نه glibc allocator نتونستن به راحتی مشکل رو حل کنن. دو گزینه مطرح دیگه که با هدف استفاده توی اپلیکیشن‌های concurrent وجود دارن،Jemalloc - CrateMimalloc - Crateهستن. بنچمارک‌هایی از این allocator ها توی Rust انجام شده(یک نمونه) و از نظر پرفورمنس عملکرد مشابهی با malloc (glibc) داشتن. در قدم بعدی آزمایش، ما اپلیکیشن رو با استفاده از jemalloc به عنوان global allocator اجرا کردیم که نتایج رو در نمودار های زیر می بینیم.لازم به ذکره در این آزمایش لود به صورت موازی بین اپلیکیشن‌های Go و Rust پخش شده و هر دو نمودار جهت مقایسه قرار داده شدن.اجرای اول: تقسیم لود بین ۲ پاد از هر اپلیکیشن (۲ پاد Rust و ۲ پاد Go)نرخ درخواست‌هازمان پاسخگویی P99  زمان پاسخگویی P90میانگین مصرف Memory میانگین مصرف CPUاجرای دوم: تقسیم لود بین یک پاد از هر اپلیکیشن با هدف ارزیابی عملکرد با منابع محدودنرخ درخواست‌ها  زمان پاسخگویی P99زمان پاسخگویی P90میانگین مصرف Memoryمیانگین مصرف CPUنتیجهنسخه نوشته شده با Rust با وجود منابع یکسان، هم سرعت پاسخگویی بهتر داشت و هم در مدیریت منابع بهینه‌تر عمل کرد. خلاصه‌ای از نتیجه آزمایش رو توی جدول زیر آوردیم.جدول مقایسه نتایج آزمایش این نتایج خیلی هم دور از انتظار نیست، performance و سرعت اجرای‌ برنامه یکی از اصلی ترین اهداف زبان Rust هست در صورتی که هدف اصلی Go الزاما سرعت نیست. این موارد کمک قابل توجهی به این تفاوت سرعت عملکرد می‌کنن:عدم نیاز به Garbage Collectorسخت‌گیری‌های کامپایلر برای استفاده صحیح از Heap و Stack نسبت به Go که از Heap Escape جلوگیری بشه.اولویت‌دادن به performance نسبت به قابلیت‌ها و ویژگی‌های نهایی ارائه شده به کاربر در Rustوجود macro‌ها و جامع بودن Generic‌ها که باعث می‌شن یک‌سری محاسبات و بهینه‌سازی‌ها به جای انجام‌شدن در runtime در compile time برنامه ‌انجام بشن.البته همین مورد باعث پیچیده‌ و زمان‌گیرتر شدن compile برنامه‌های Rust میشه.اهداف دیگه‌ای که در Go با اولویت بالاتری بهشون پرداخته شده شامل:پشتیبانی بسیار گسترده‌تر standard library و در نتیجه‌اش پایداری بیشتر برنامه‌ها.انتقال پیچیدگی‌های پیاده‌سازی به هسته زبان و standard library و اولویت دادن به API پایدارتر و ساده‌تر.ترکیب دو مورد بالا باعث راحت‌تر توسعه دادن کتاب‌‌خونه‌ها و framework های جدید شده که اکوسیستم خیلی غنی‌تری برای Go به وجود آورده.مدیریت چالش‌های اجرایی متعدد (مثل چالش مدیریت حافظه که در پیاده سازی اولیه با Rust با اون مواجه شدیم) در native runtime زبان.طراحی و پیاده سازی یک سیستم نیازمند در نظر گرفتن پارامتر‌های مختلفی از جمله نرخ تغییرات، اهمیت زمان پاسخ، میزان بار سیستم و... هست و نمیشه یک نسخه برای همه سیستم‌ها نوشت. با وجود سریع‌تر بودن Rust در این استفاده، همچنان عملکرد Go برای این برنامه با درنظر گرفتن حجم درخواست‌ها کاملا قابل قبوله. همینطور اکوسیستم قوی Go و در نتیجه‌اش سرعت زیاد در توسعه ویژگی‌های جدید با اون باعث شده که ما برای این سرویس از Go استفاده کنیم.ممنونیم که تا اینجا همراهی کردید و این پست رو خوندید :) اگر به کار کردن با این تکنولوژی‌ها و در این مقیاس علاقه دارید، خوشحال می‌شیم که رزومه‌هاتون رو از طریق آدرس engineering@snapp.cab یا از سایت فرصت های شغلی Careers - Jobs - Snapp برای ما ارسال کنید.موفق باشید</description>
                <category>اسنپ</category>
                <author>محمد فاطمی</author>
                <pubDate>Mon, 27 Jan 2025 16:41:42 +0330</pubDate>
            </item>
                    <item>
                <title>جمع آوری و تحلیل داده های قبل از سفر در تیم نقشه اسنپ</title>
                <link>https://virgool.io/snapp-eng/%D8%AC%D9%85%D8%B9-%D8%A2%D9%88%D8%B1%DB%8C-%D9%88-%D8%AA%D8%AD%D9%84%DB%8C%D9%84-%D8%AF%D8%A7%D8%AF%D9%87-%D9%87%D8%A7%DB%8C-%D9%82%D8%A8%D9%84-%D8%A7%D8%B2-%D8%B3%D9%81%D8%B1-%D8%AF%D8%B1-%D8%AA%DB%8C%D9%85-%D9%86%D9%82%D8%B4%D9%87-%D8%A7%D8%B3%D9%86%D9%BE-qaavzxrcoygu</link>
                <description>یکی از بزرگترین چالش‌های اسنپ، سردرآوردن از رفتار کاربران و برخورد آن‌ها با اپلیکیشن درخواست خودروی اسنپ(Snapp Cab یا اپلیکیشن مسافر) است. حجم زیاد داده‌ها و پیچیدگی ذاتی‌ آن‌ها در کنار مسائلی مثل حفظ امنیت داده‌ها و کاربران و ... همه دست به دست هم میدن تا این چالش رو سخت‌تر هم بکنن. در این مقاله نگاهی به تلاش ما برای ساخت سیستم و پایپ لاینی(pipeline) برای مدیریت و نگهداری این حجم از داده برای تیم نقشه‌ اسنپ میندازیم. در اسنپ منظورمان از pre-ride چیست؟اگر تا به حال از اسنپ استفاده کرده باشید، حتما قبل از زدن دکمه &quot;درخواست سفر&quot;  به آیتم‌هایی مثل انتخاب مبدا یا مقصد روی نقشه، استفاده از گزینه‌های سفر یا انتخاب مکان‌های پیشنهادی یا مکان‌های منتخب و … برخورد کردید. ما در تیم نقشه اسنپ به هر رخداد (event) ایی که از لحظه باز کردن اپلیکیشن مسافر ( یا Snapp Cab) تا لحظه شروع &quot;جستجوی راننده&quot; و قبل از دادن درخواست سفر  انجام میشه pre-ride event میگیم.pre-ride sessionدانستن اینکه کاربران اسنپ چطوری با این سرویس‌های &quot;قبل از سفر&quot; یا pre-ride تعامل میکنن و کیفیت این سرویس‌ها چقدر است برای ما اهمیت زیادی داره و به تیم ما کمک میکنه تصمیم‌هایی مبتنی بر داده‌های واقعی برای آینده محصولات خودمون بگیریم.چالش‌های فنی مسیراگر نگیم همه، اکثر سیستم‌ها و سرویس‌های اسنپ  یک Big Data System محسوب میشن.کلان داده(Big Data) معمولاً به مجموعه داده‌هایی گفته می‌شود که بیش از حد بزرگ یا پیچیده هستند بطوریکه نمی‌توان با نرم‌افزارهای عادی و روش های متداول آن‌ها را پردازش یا تحلیل کرد.چالش‌های کار کردن با این حجم از داده کم نیست، داریم در مورد صد‌ها یا هزاران write در ثانیه صحبت میکنیم،  معمولا به منابع سخت افزاری زیاد به همراه نرم افزار‌های خاصی که برای کارکردن با کلان داده طراحی شدن نیاز است. این مقاله آشنایی خوبی در مورد مبحث کلان داده به شما میده اما اگر دوست دارید عمیق‌تر یادبگیرید کتاب Designing Data-Intensive Applications منبع فوق العاده‌ای برای آشنایی با طراحی و تحلیل همچین سیستم هایی هست. به طور کلی، طراحی سیستم‌های کلان داده‌، جز کارهای سهل ممتنع به حساب میاد! در نگاه اول و روی کاغذ شاید ساده باشه ولی در اجرا بسیار دشوار میشه!تعریف و شرح مسئلهقبل از اینکه به یک دنبال راه‌ حل فنی باشیم، بهتره مسئله رو شفاف کنیم. فرض کنید میخواییم سیستمی طراحی کنیم که در تحلیل داده‌های pre-ride به ما کمک کنه:ابتدا باید به این سوالات پاسخ بدیم:این داده‌ها چقدر اهمیت دارن؟این داده‌ها برای چه مدتی باید ذخیره بشن؟حجم داده‌ها چقدر است؟ با چه سرعتی تولید میشن؟تحلیل داده‌ها به چه صورت خواهد بود؟ (آنلاین و real-time، آفلاین و …)آیا data loss یا از دست رفتن داده مهم است؟ (availability)پاسخ ما، با توجه به نیازمندی‌های تیم از محصول،  به صورت زیر است:بیشتر تعامل‌های کاربر ( به خصوص آن‌هایی که با نقشه سر و کار دارند) در pre-ride به صورت کوتاه‌مدت (برای دسترسی سریعتر و راحتتر) و بلندمدت ذخیره بشهمتریک‌های مهم (مثل مدت زمان سرچ کاربر، زمان سرویس دهی و …) به صورت آنلاین و تا جای ممکن لحظه‌ای استخراج و مانیتور بشنسرویس‌ها تا جای ممکن تلاش کنند پایدار(stable) و در دسترس باشناز دست دادن داده برای بازه‌های بسیار کوتاه زمانی یا به صورت اتفاقی اهمیت زیادی ندارهاز این جا به بعد کلماتی مثل لاگ(log) و داده(data) با معنی نسبتا یکسان و مشابه استفاده میشن. منظور از متریک (metric) هم در این متن یک پارامتر است که کیفیت یا کارایی یکی سیستم رو مشخص میکنه. مثلا فرض کنید نیاز داریم بدونیم آیا خیابون‌های نقشه با سرعت کافی لود میشوند یا خیر، برای این کار نیاز است متریک‌های مرتبطی تعریف کنیم، اون‌ها رو جمع آوری و مانیتور کنیم.  همچنین جهت خلاصه کردن مطلب، در این نوشته وارد روش ارسال داده‌ها از اپلیکیشن مسافر اسنپ به سرویس‌های بک‌اند نمیشیم و فرض میکنیم این داده‌ها به نحوی وارد سیستم ما شدن.تلاش‌های قبلیقبل از اینکه به راه حل و سیستم جدیدی که تیم ما طراحی کرده برسیم،‌ بیاید نگاهی به یک تلاش قبلی بکنیم. قبل از طراحی و پیاده‌سازی سیستم جدید (که جلوتر توضیح داده شده) سیستمی به اسم  SSP یا Search Stream Processing  یکی از اولین پروژه‌های تیم نقشه جهت ارزیابی سرویس جستجو(search) روی نقشه‌ی اسنپ بود. این سرویس با Java نوشته شده بود و از Apache Flink برای پروسس کردن دیتا استفاده میکرد. لاگ‌های ورودی از طریق Logstash جابجا میشدن و پس از عبور از یک Kafka به درون یک دیتابیس Elastic Search قرار میگرفتن. در اینجا بود که با استفاده از Kibana بالاخره میتونستید لاگ‌ها و متریک ها رو ببینید! فکر میکنم واضح باشد که این سیستم سربار و پیچیدگی زیادی داره، از مصرف منابع گرفته تا نگهداری و توسعه دادن آن. با گذشت زمان مشخص شد که لازم است از اول یک مجموعه از سرویس ها رو طراحی و پیاده‌سازی کنیم که به نیازهای ما پاسخ بده، چه نیازهای فعلی و چه نیازهایی که ممکن است در آینده پدیدار بشن. دقت کنید که به طور خلاصه ما به دنبال یک تعادل مناسب بین سه فاکتور مهم: scalability ,maintainability ,reliability هستیم. در ادامه که به طراحی یک سیستم جدید میپردازیم سعی میکنیم به طور ضمنی این پارامتر‌ها را رعایت کنیم.یک راه حل مدرن چه شکلی خواهد بود؟شاید واژگانی مثل: ETL (Extraction, Transformation, Loading)ELT (Extraction, Loading, Transformation)ETTL (Extraction, Transformation, Transportation, Loading) به گوشتون خورده باشه. خیلی از راه حل‌های مدرن شرکت‌های بزرگ برای مقابله با این حجم از داده‌ها مبتنی بر همین کلمات است!داده ها رو جمع کن (Extract)روی داده‌هات transformation ایی که لازم داری رو انجام بده.Aggregation, Splitting, Mapping, Filtering, Joining, Duplication همه نمونه‌هایی از این transformation ها هستن که ممکنه روی داده ورودی اعمال بشن.داده‌های مرحله قبل رو در یک محل ذخیره سازی موقت ( و اولیه) که توانایی مدیریت این حجم از داده رو داشته باشد قرار بده (transportation)در صورت لزوم و برای استفاده های بلند مدت تر، داده‌های مرحله قبل رو به یک دیتابیس (database) ثانویه و قابل اتکا ( در بلند مدت) انتقال بده. به این نوع از دیتابیس ها data warehouse گفته میشود.همانطور که میبینید، در گام‌های ۳ و ۴، دیتای خام، یا تبدیل شده، در یک دیتابیس ذخیره میشه و بنابراین هرکسی که نیاز به تحلیل و بررسی اون‌ها داشته باشه میتونه از این دیتابیس‌های اولیه و ثانویه استفاده کنه. اصولا دیتابیس اولیه(یا موقت) برای استفاده‌های لحظه‌ای تر مثل real-time analysis استفاده میشه.البته دقت کنید که معماری سیستم ما قرار نیست کاملا وفادار به قرارداد‌های عمومی مثل ETL باشد. در بعضی از جاها ETL، بعضی جاها ELT و در بعضی از جاها ETTL میتونه باشه! بستگی داره از چه زاویه‌ای نگاه کنی!!!معماری کلیطبق صحبت‌های قبلی، به نظر میرسه که سیستم ما قراره چند بخش اساسی داشته باشه:‌پایپ لاینی برای انتقال داده‌هاسرویس یا سرویس‌هایی برای اعمال transformation هامحلی برای ذخیره‌سازی داده‌های اولیه و نهاییپایپ لاینی برای انتقال دادهدر کل انتقال لاگ/دیتا بین سرویس‌ها و ابزار‌های مختلف کار ساده‌ای نیست. یک ابزار جدید و قدرتمند برای اینکار Vector است. Vector کمی با سرویس‌های نام آشنایی مثل Kafka یا RabbitMQ یا NATS و حتی Logstash فرق داره. این ابزار به طور خاص طراحی شده که به برنامه نویس‌ها امکان ساخت پایپ لاین های دیتای سبک و بسیار سریع رو بده. Vector سریع و بهینه است، به راحتی روی سرور deploy و آماده استفاده در محیط production میشه. مکانیزم کلی به این صورت است که شما یک یا چند Source به عنوان ورودی و یک یا چند Sink به عنوان خروجی تعریف میکنید.  در کانفیگ خود برنامه هم میشه یک سری transformation تعریف کرد. در واقع Vector به خودی خود یک پایپ لاین ETL کامل است! البته چون این ابزار انعطاف پذیری کافی رو به ما نمیداد، ازش صرفا برای انتقال داده استفاده کردیم و ترجیح دادیم که این پیچیدگی‌ها رو به نرم‌افزار‌های خودمون انتقال بدیم تا یک سرویس خارجی. استفاده از Vector باعث شد به نسبت Kafka ایی که در پایپ لاین قبلی داشتیم حجم زیادی در منابع صرفه جویی کنیم (در حدود ۱۰ برابر!). سرویس‌هایی برای اعمال transformation هاخب، وقت اون رسیده که دست به کد بشیم! با زبان Go سرویس‌/میکروسرویس‌هایی رو توسعه میدیم که وظیفه تغییر دادن دیتا و استخراج متریک‌های مورد نیاز ما رو بر عهده میگیرن. تلاش ما این است که اکثر Business Logic مثل اینکه چه نوع از لاگ‌هایی ورودی معتبر هستن و … در این سرویس‌های Validate بشوند. این سرویس‌‌ها باید  Stateless باشند تا امکان مقیاس پذیری افقی رو (Horizontal scaling) برای ما فراهم کنن. همچنین انتخاب زبان Go هم بی دلیل نیست، اولا که این زبان برای محیط‌ کلاد (Cloud) و برای سرویس‌هایی که قراره حجم زیادی از ریکوئست‌ها رو مدیریت کنن انتخاب بی نظیری هست. از طرفی عمده سرویس‌های اسنپ نیز با Go نوشته شدن بنابراین منطقی ترین انتخاب برای ما همین Go میتونه باشه.محلی برای ذخیره دادهدر اینجا میخواهیم از Clickhouse استفاده کنیم. دیتابیس Clickhouse در وهله اول یک دیتابیس OLAP(Online Analytical Processing) است. این دسته از دیتابیس‌ها برای کار ما عالی هستن چون طراحی شدن که روی حجم زیادی از داده، کوئری‌های آنالتیکال اجرا کنن. OLAP vs OLTPتجربه ما نشون داده که این دیتابیس تمام معیار‌های ما برای یک دیتابیس خوب رو تیک میزنه! به راحتی مقایس(scale) پیدا میکنه، به اندازه کافی بالغ شده و سرعت فوق العاده بالایی داره، از منابع سخت افزاری خود به خوبی استفاده و ویژگی‌های زیادی رو در اختیار توسعه دهنده قرار میده و در نهایت، SQL رو هم پشتیبانی میکنه. البته که برای استفاده از تمام پتانسیل‌های Clickhouse باید آستین ها رو بالا بزنی و یادبگیری چطوری ازش درست استفاده بکنی چون این دیتابیس Config های متفاوت و زیادی داره که معمولا در مواجه با حجم زیاد داده اهمیت پیدا میکنن.سیستم نهاییخلاصه صحبت‌های بالا رو میتوان در دیاگرم زیر، که همان پایپ لاین جمع‌اوری داده ما است، ببینیم:این سیستم از بخش‌های زیر تشکیل شده:فرآیندهایی برای process کردن داده‌های ورودی و تبدیل آن‌ها به متریک و داده‌های قابل استفادهذخیره موقت خروجی بخش ۱ برای استفاده‌های کوتاه مدت (مثلا بررسی وضعیت فعلی کیفیت سرویس‌هایی مثل سرچ نقشه، تایل‌ها، مکان‌های منتخب و …)ذخیره دائمی خروجی بخش ۱(نتیجه پراسس ها) برای تحلیل‌های عمیق تر توسط تحلیل‌گران داده یا مهندسان اسنپ یا استفاده از داده‌ها برای مدل‌های آماری و …بهتره اشاره کنم که داده‌ها به صورت رمزنگاری شده جابه‌جا و ذخیره میشن که از امنیت آن‌ها مطمئن باشیم. سیستم ارائه شده به دلیل استفاده از ترکیب Go, Clickhouse, Vector به مراتب کمتر از قبل منابع استفاده میکنه، درحالی که سرعت بالاتری هم داره و آسونتر توسعه پیدا میکنه.یک سوال: به نظرتون اگر قرار بود بر اساس مدل ETL  یا ETTL یا مدل‌های مشابه، این سیستم را تقسیم میکردیم، کدوم بخش E بود؟ کدوم بخش T یا T بود؟! کجاها L میشد؟پایاندر این نوشته چالش‌های مربوط به جمع‌ آوری pre-ride events رو بررسی کردیم. مهمترین این چالش‌ها، حجم زیاده داده و مشکلات مرتبط به آن و طراحی سیستمی بود که انعطاف پذیری‌های کافی رو داشته باشه. با بررسی تکنولوژی‌های موجود به این نتیجه رسیدیم که بهترین انتخاب برای ابزارها چی میتونه باشه و سپس سیستمی ارائه دادیم که نیازمندی‌های ما رو برطرف بکنه. این سیستم طی یک سال گذشته در تیم ما توسعه پیدا کرده(و همچنان در حال بهبود و پیشرفته!) و به ما کمک کرده شناخت بهتری از رفتار کاربران اسنپ داشته باشیم. اینطوری تصمیم‌های بهتری برای آینده و وضعیت فعلی محصولاتمون میگیریم. شما هم میتونید با الگو گرفتن از این سیستم، پایپ لاین دیتای منحصر به تیم و شرکت خودتون رو طراحی کنید. در ضمن، اگه حس می‌کنین از پروژه‌هایی شبیه به این خوشتون میاد و در نتیجه علاقه دارید به تیم ما ملحق بشید، خوشحال می‌شیم که رزومه‌هاتون رو از طریق آدرس engineering@snapp.cab یا از سایت فرصت های شغلی Careers - Jobs - Snapp برای ما ارسال کنید. موفق باشیدتشکر ویژه از اعضای تیم آدرس:‌ علی حیدرآبادی،‌ امیر سالاری، پارسا پردل،‌ سپهر رفیعی، روزبه ترابیان، محمد جعفری، راضیه مصیبی و علی رسولی :) </description>
                <category>اسنپ</category>
                <author>Farzin Nasiri</author>
                <pubDate>Sun, 10 Mar 2024 16:02:36 +0330</pubDate>
            </item>
                    <item>
                <title>ترافیک شهری در اسنپ - بخش یک - چالش‌های پایپ‌لاین محصول مبتنی بر یادگیری ماشین</title>
                <link>https://virgool.io/snapp-eng/%D8%AA%D8%B1%D8%A7%D9%81%DB%8C%DA%A9-%D8%B4%D9%87%D8%B1%DB%8C-%D8%AF%D8%B1-%D8%A7%D8%B3%D9%86%D9%BE-%D8%A8%D8%AE%D8%B4-%DB%8C%DA%A9-iavlj43cauw9</link>
                <description>Photo by Payton Mcdonald on Unsplash    چکیدهیادگیری‌ماشین یکی از موضوعات داغ این روزهای فضای صنعت در جهان است و شرکت‌های مختلف برای حل بسیاری از مسائل خود از آن استفاده می‌کنند. در کشور ما، مسائل مرتبط با فضای یادگیری‌ماشین  بیشتر در فضای آکادمیک دنبال می‌شد. اما این روزها با توجه به توسعه و رشد شرکت‌ها، حجم داده‌هایی که تولید و نگه‌داری می‌شوند نیز افزایش پیداکرده‌ است. در نتیجه این فرصت برای ما نیز فراهم‌ شده‌ است که با استفاده از یادگیری‌ماشین به برخی از مسائل پاسخ بدهیم.پس از آن که شرکت‌ها برای حل مسائل خود به روش‌های یادگیری‌ماشین روی‌ آوردند، با چالش‌هایی مواجه شدند که جنس آن‌ها با چالش‌ها در فضای آکادمیک متفاوت بود. از فرایند یادگیری‌ماشین به عنوان فرایندی که منجر به تولید یک محصول می‌شود می‌توان به عنوان مهمترین چالش افراد فعال در این حوزه اشاره‌کرد. به همین دلیل موضوعات و مفاهیمی مانند MLOps و DataOps ظهور پیداکرده‌ اند و به دنبال این مفاهیم ابزارهایی برای تحقق بخشیدن به آن‌ها ایجاد‌ شده‌ است.ما قصد داریم در قالب یک سری مقالات بیان کنیم که با چه مشکلاتی در تیم ترافیک نقشه اسنپ روبرو هستیم و چه راه‌حل‌هایی برای آن‌ها انتخاب کرده ایم.در مقاله زیر نیز تصویر کلی از معماری محاسبه ترافیک در تیم ترافیک نقشه اسنپ معرفی شده است که می‌توانید به آن مراجعه کنید. https://engineering.snapp.ir/%D8%AA%D8%B1%D8%A7%D9%81%DB%8C%DA%A9-%D8%B4%D9%87%D8%B1%DB%8C-%D8%AF%D8%B1-%D8%A7%D8%B3%D9%86%D9%BE-%D8%A8%D8%AE%D8%B4-%D8%B5%D9%81%D8%B1-x6u0qxmf2qec مقدمهیکی از وظایف تیم ترافیک محاسبه تخمین مدت زمان سفر (ETA)، برای راننده‌ها پیش از هر سفر است. به همین دلیل نیاز داریم که در ابتدا تخمینی از وضعیت ترافیکی حال حاضر شهرها را داشته‌ باشیم و در ادامه با استفاده از آن، پیش‌بینی کنیم که در آینده وضعیت ترافیکی به چه شکلی خواهد بود. سپس با استفاده از اطلاعات و مشخصات مبدا و مقصد سفر، تخمینی از مدت زمان سفر را محاسبه‌ کنیم. در نتیجه مسئله تخمین مدت زمان سفر به شکل زیر تقسیم خواهد شد:محاسبه‌ی وضعیت فعلی ترافیک  پیش‌بینی وضعیت ترافیک در آیندهبرای حل کردن این دو مسئله، که خود نیز به مسائل دیگری شکسته می‌شوند، از روش‌های یادگیری‌ماشین استفاده‌ می‌کنیم. در این مقاله از مسئله ETA  به عنوان یک Case-Study استفاده‌ می‌کنیم و با نگاهی Blackbox به یک مدل یادگیری‌ماشین، بیان می‌کنیم چه چالش‌هایی بر سر راه تولید این نوع محصول وجود خواهد داشت و راه‌حل‌ها و ابزارهای خود را تشریح خواهیم کرد.چالش‌های تعریف مسالهفرض کنید می‌خواهیم سیستمی طراحی‌کنیم تا با کمک آن بتوانیم به مساله پیش‌بینی وضعیت ترافیک در ساعت‌های آینده پاسخ دهیم. در همین ابتدا با پرسش‌های زیر روبرو خواهیم بود: چگونه این مساله را با استفاده از روش‌های یادگیری‌ماشین می‌توانیم حل کنیم؟ برای ایجاد مدل از چه داده‌ها و با چه ویژگی‌هایی استفاده کنیم؟ آیا داده‌های ما از کیفیت مناسب برخوردار هستند؟ چگونه می‌توانیم کیفیت عملکرد سیستم خود را با استفاده از مدل ساخته شده ارزیابی‌کنیم؟ چگونه می‌توانیم مشکلات سیستم را متوجه شویم و خطاهای آن را استخراج‌کنیم؟  کارهایی را که از سوالات بالا استخراج می‌شود چگونه اولویت‌بندی کنیم؟این پرسش‌ها بخشی از سوالاتی است که در فرایند تولید یک محصول مبتنی بر یادگیری‌ماشین در ذهن ایجاد می‌شود. در ادامه ما با استفاده از  چارچوب محاسبه ترافیک مرتبط با ETA به این سوالات پاسخ خواهیم داد. در مساله پیش‌بینی ترافیک، برای اینکه بتوانیم از یک روش یادگیری‌ماشین استفاده کنیم، نیازمند به یک پایپ‌لاین محاسباتی هستیم. وظیفه‌ی این پایپ‌لاین این است که داده، یا به عبارتی Feature-Vector مورد نیاز مدل مارا ساخته و به عنوان ورودی به مدل ما بدهد. شکل زیر تصویری ساده‌سازی شده از معماری این سیستم است.تصویر ۱: معماری اولیه محاسبه ترافیکتصویر بالا نشان می‌دهد که GPSها از طریق کلاینت (اپلیکیشن راننده) به سمت سرور فرستاده می‌شوند و داخل یک صف قرار می‌گیرند. در ادامه سرویس Traffic-Estimator داده‌ی GPS‌ها را Consume کرده و ترافیک فعلی را محاسبه می‌کند. در گام بعدی سرویس ‌Traffic-Predictor با استفاده از داده‌های ترافیک حال و زمان گذشته (Feature-Vector) به عنوان ورودی، ترافیک آینده را پیش‌بینی خواهد کرد.در بخش زیر چالش‌هایی را که در ابتدای راه برای راه‌اندازی این سیستم  با آن روبرو بودیم، تشریح خواهیم کرد.چالش‌های پایپ‌لایننگهداری داده‌های حجیم: یکی از چالش‌هایی که در ابتدای امر با آن روبرو هستیم پردازش کردن حجم زیادی از داده‌های GPS راننده‌ها است. به عنوان مثال اگر تصور کنیم ۱میلیون راننده در حال سفر داشته باشیم و سیستم ما به گونه‌ای باشد که هر ۱۰ ثانیه، اپلیکیشن موقعیت راننده را به سمت سرور ارسال کند، باید ۶ میلیون درخواست در دقیقه را بتوانیم پردازش کنیم. به عبارت دیگر ۱۰۰k rps، که عدد بسیار بزرگی خواهد بود. به همین دلیل نیازمند ابزارهای BigData هستیم. از این رو، در قدم اول تصمیم گرفتیم GPS‌ها را در Apache Kafka نگه‌داری کنیم تا سرویس‌های مختلفی که نیازمند این داده هستند نیز بتوانند از آن‌ها استفاده کنند. همچنین به دلیل نرخ بالای ایجاد داده GPS، عملکرد سرویس‌ها دچار مشکل نشود.تصویر ۲: آپاچی کافکا، پلتفرم توزیع شده‌ در پردازش استریم کیفیت داده: یکی دیگر از چالش‌هایی که با آن روبرو هستیم کیفیت داده است. از آن جا که دستگاه GPS گوشی‌های هوشمند دارای خطا هستند و همچنین در برخی از نقاط شهر مثل مناطقی که دارای ساختمان‌های بلند  و یا مناطقی که دارای ملاحظات امنیتی هستند، عملکرد GPS گوشی ها با اختلالاتی مواجه می‌شود. این اختلالات باعث ایجاد نویز در گزارش موقعیت راننده‌ها می‌شود. این اختلالات باعث  ایجاد خطا  ( ۱ تا بیش از ۱۰ها متر ) و یا مخدوش شدن در گزارش موقعیت راننده به سمت سرور می‌شود. برای حل این مشکل از الگوریتم‌های Hidden Markov استفاده می‌کنیم تا بتوانیم خطاهای ناشی از GPSهای گزارش شده را تا حدی کاهش دهیماز طرف دیگر مناطقی از شهر وجود دارد که میزان کافی از GPSها در برخی ساعت‌های شبانه روز گزارش نمی‌شود به همین دلیل در این نقاط ما دچار مشکل Missing در داده خواهیم بود. برای حل این مشکل نیز از روش‌های Imputation استفاده می‌کنیم که پرداختن به جزئیات این موضوعات خارج از چارچوب این مقاله خواهد بود. برای آشنایی با بخشی از این روش‌ می‌توانید به مقاله زیر مراجعه کنید: https://engineering.snapp.ir/usage-of-matrix-factorization-to-predict-offline-speeds-at-snapp-aqszq458uajh تصویر۳:  کاوریج ترافیک محاسبه شده در بخشی از شهر تهران، با استفاده از سیستم محاسبه ترافیک تیم نقشه اسنپ، برای مدت زمان ۱۵ دقیقه، با استفاده از GPSهای ارسال شده‌ی رانندگان در حال سفر.پردازش داده‌های حجیم: برای محاسبه ترافیک نیازمندیم که بتوانیم سرعت ماشین‌هایی که از خیابان‌ها عبور می‌کنند محاسبه کنیم و به هر خیابان سرعتی بر اساس سرعت راننده‌های در حال عبور از آن را نسبت دهیم. به عبارت دیگر این سرعت بیانگر این خواهد بود که  به طور متوسط یک راننده با چه سرعتی، در زمان مشخص می‌تواند از خیابان مورد نظر عبور کند. به عنوان مثال در اتوبان همت یک راننده در ساعت ۲۱ شب با سرعت متوسط ۷۰ کیلومتر بر ساعت می‌تواند عبور کند. با فرض اینکه از فرمول زیر برای محاسبه سرعت بخواهیم استفاده کنیم،Median-Speed = Median(Distance(GPS2, GPS1) / (T2-T1))باید بتوانیم این پردازش را در زمانی مشخص انجام دهیم. یعنی اگر ما بخواهیم سرعت متوسط در بازه زمانی ۵دقیقه اخیر را به عنوان سرعت ترافیک فعلی حساب کنیم زمان محاسبه‌ی این پردازش نباید بیش از ۵دقیقه طول بکشد. زیرا این کار هر ۵دقیقه باید به شکل متناوب انجام شود و اگر زمان محاسبه یکی از این batch jobها بیشتر از این مدت زمان طول بکشد داده ارزش زمانی خود را از دست داده و همچنین با batch job زمان بعدی دچار تداخل خواهد شد. در نتیجه نیازمند پلتفرمی هستیم که بتواند این قدرت پردازشی را به ما بدهد و همچنین scalable باشد. برای دستیابی به این امر، به سراغ روش‌های پردازش توزیع شده رفته ایم. یکی از شناخته‌ شده‌ ترین تکنولوژی‌ها در این حوزه Apache Spark است که قابلیت پردازش داده به شکل Stream و ‌Batch را به ما می‌دهد.تصویر۴: با اتصال اسپارک به سورس‌های مختلف از آن به عنوان پلتفرم پردازش داده‌های بچ و استریم استفاده می‌کنیم. نرخ بالا در نوشتن داده‌های پردازش شده: بعد از محاسبه‌ی داده‌ی ترافیکی و پیش‌بینی ترافیک برای آینده، نیاز داریم که این داده را به منظور اهدافی مانند مانیتورینگ، تحلیل داده و آموزش مدل یادگیری ذخیره سازی کنیم. به همین دلیل نیازمند به دیتابیسی هستیم که سرعت نوشتن روی آن بالا بوده تا در برابر نرخ بالای نوشتن تحمل پذیری بالایی داشته باشد. به همین خاطر باید دیتابیس خود را به شکل کلاستر نگه‌داری کنیم و همچنین داده‌ها را به شکل رپلیکیت شده روی آن ذخیره کنیم تا در صورت خرابی بتوان همچنان به سیستم اتکا کرد و اگر یکی از نودهای دیتابیس دچار خرابی شد فرایند محاسبه ترافیک دچار مشکل نشود و همچنین بعد از برطرف شدن مشکل سیستم خودش بتواند این داده را روی نود فیکس شده دوباره لود کند. اصطلاحا دیتابیسی با قابلیت Relaiblity، Fault tolerancy و Self-Healing مناسب نیاز داشتیم. برای دستیابی به این دیتابیس سراغ Apache‌ Cassandra رفتیم که علی رغم پیچیدگی‌هایی که از لحاظ نگهداری و طراحی مناسب به سیستم اضافه می‌کرد، کمک شایانی برای پشت سر گذاشتن این چالش کرد.تصویر ۵: کلاستر آپاچی کاساندرا که به عنوان دیتابیس نگه‌دارنده داده‌های ترافیکی استفاده می‌شوددیتابیس Apache Cassandra یکی از دیتابیس‌های  NoSQL از خانواده‌ی دیتابیس‌های Column-Family است که قابلیت استقرار Distributed را داراست و این اطمینان را به شما به عنوان یک معمار نرم افزار می‌دهد که در صورت بالا رفتن نرخ داده، می‌توان دیتابیس را به راحتی با اضافه کردن نود، Scale out کرد. از نکاتی که در طراحی داده مدل این دیتابیس باید به آن توجه داشت این است که جدول‌ها در این دیتابیس بر اساس Query‌هایی که نیاز داریم طراحی می‌شود. به بیان دیگر در این دیتابیس ما نتایج کوئری‌هایی که نیاز داریم در قالب دیتا در جدول‌ها ذخیره می‌کنیم و با مدل Entity-Relationship که در دیتابیس‌های Relational با آن آشنا هستیم قدری متفاوت است. از این رو، طراحی جدول‌ها در این دیتابیس  Query-Based است در حالیکه در دیتابیس‌های Relational طراحی جدول‌ها  Entity-Based است. همچنین  نکته‌ی دیگری که باید در هنگام طراحی این دیتابیس توجه داشته باشیم این است که طوری تنظیمات مربوط به partitioning داده را انجام دهیم که داده‌‌ با Partition-Size‌های همسان در نودها توزیع شود. این موضوع باعث می‌شود که عملیات روی دیتابیس با سرعت بهتری انجام شود. طراحی بهینه‌ی ساختار داده‌ای یکی دیگر از چالش‌های ما در طراحی این سیستم بود. زیرا ما نیاز داشتیم که داده را با سرعت بالایی روی دیتابیس بنویسیم و هم به منظور استفاده در پیش‌بینی ترافیک که به داده‌ی گذشته ترافیکی نیاز داشت، با سرعت بالایی بخوانیم تا زمان اجرای پایپ‌لاین بیش از حد مجاز طول نکشد.مدل ‌پیش‌بینی: به عنوان اولین مدل، به دنبال روشی بودیم که به عنوان Baseline کار خود قرار دهیم و بتوانیم روش‌های پیچیده‌تر را به کمک آن مقایسه و ارزیابی کنیم. به همین دلیل مدل AutoRegression را به عنوان اولین مدل پیش‌بینی خود انتخاب کردیم و با استفاده از آن و Feature-Vectorی که درایه‌های آن ترافیک فعلی و ترافیک در یک ساعت گذشته‌ هر خیابان است، برای یک ساعت آینده هر خیابان، ترافیک را پیش‌بینی کردیم. به بیان دیگر برای پیش‌بینی ترافیک آینده، از میانگین وزن‌دار ترافیک حال و گذشته‌ی هر خیابان بهره بردیم.تا این‌جا بیان کردیم که برای راه‌اندازی یک سیستم محاسبه و پیش‌بینی ترافیک با یک مدل و فرمول اصطلاحا Baseline با چه چالش‌هایی روبرو بودیم. اما این پایان کار برای سیستم ما نبود و بعد از لانچ این سیستم ما با چالش‌هایی روبرو شدیم که در ادامه آن‌ها را برای شما بازگو خواهیم کرد.چالش‌های سیستم Baselineوقتی یک سیستم Baseline را لانچ می‌کنیم با چالش‌های جدیدی روبرو می‌شویم. فرض کنید مدل یادگیری‌ماشینی train کرده ایم و در آزمایشات به دقت خوبی رسیده ایم و آن را دیپلوی کرده ایم.در ادامه چالش‌هایی که ما با آن روبرو شدیم را ذکر خواهیم کرد:چگونه باید متوجه بشویم که آیا عملکرد سیستم خوب است یا خیر. آیا سرعت‌های محاسبه شده برای خیابان‌ها، که نشانگر میزان ترافیک آن خیابان‌ها است، به اندازه کافی دقت دارد؟ در اولین قدم دریافتیم که دچار مشکل عدم وجود یک Ground-Truth برای مقایسه عملکرد خود با آن هستیم.چه تعریفی برای عملکرد خوب داشته باشیم. اگر به لحاظ متریک‌های یادگیری‌ماشین مدل دقت خوبی داشته باشد، آیا کافی است؟ چگونه تاثیر کار خود را بر روی متریک‌های بیزینسی اندازه‌گیری کنیم؟اگر به طریقی متوجه شدیم که عملکرد ما نیاز به بهبود دارد، حال کدام بخش از این پایپ‌لاین را باید اصلاح کنیم؟ بخشی که خطاهای GPS‌ها را برطرف می‌کند؟ بخشی که سرعت ماشین‌ها را محاسبه می‌کند و یا بخشی که پیش‌بینی ترافیک را دارد؟ به بیان دیگر Troubleshooting یکی دیگر از چالش‌های موجود در این سیستم و سیستم‌های مبتنی بر یادگیری‌ماشین است.اگر دریافتیم که مدل پیش‌بینی ترافیک ما باعث پایین آمدن دقت سیستم شده و می‌خواهیم مدل ساده پیش‌بینی ترافیک خود را بهبود دهیم و از مدل بهتری استفاده کنیم، مدل جدید با توجه به ساختار فعلی ما با چه مشکلات و محدودیت‌هایی روبرو خواهد بود؟ آیا با توجه به نیازها مجبور می‌شویم تا بخش یا کل پایپ‌لاین خود را تغییر دهیم؟ این تغییرات چه میزان هزینه برای ما خواهد داشت؟چگونه از آسیب دوباره‌کاری در مرحله تحلیل داده‌ها و انجام آزمایشات برای پیدا کردن مدل بهینه در امان بمانیم؟ با توجه به این موضوع که تعداد آزمایش‌ها به خودی خود بسیار زیاد است چگونه نتایج آزمایش‌ها و مدل‌های ساخته شده‌ی قبلی را مدیریت کنیم تا بتوانیم به آن‌ها رجوع کنیم و یا با رفتن اعضای تیم و آمدن اعضای جدید دچار چالش کمتری شویم.داده‌ی مورد نیاز برای آموزش مدل‌ها را چگونه جمع‌آوری کنیم که به شکل دستی نباشد و این داده‌ها را در مرحله‌ی آنلاین چگونه فراهم کنیم تا مدل بتواند عمل پیش‌بینی را انجام دهد و زمان اجرا نیز از حد مجاز بیشتر نشود.چگونه مدل Candidate را قبل از دیپلوی روی محیط پروداکشن بتوانیم تست کنیم که آیا به اندازه کافی عملکرد خوبی دارد یا خیر؟جمع‌بندیبعد از مطالعات متعدد دریافتیم که برای روبرو شدن با چالش‌های فوق که برخی از آن‌ها راه حل قطعی نیز ندارند نیازمند این هستیم که باید نگاه enterprise به ساختار و فرایندهای تولید محصول مبتنی بر یادگیری‌ماشین داشته باشیم و با به‌کارگیری Best-Practice‌هایی مانند MLOPs و DataOps و استفاده از ابزارهایی که باعث می‌شود بتوانیم این practice‌ها را به کار ببریم، بتوانیم انعطاف‌پذیری قابل قبولی در برابر چالش‌های مطرح شده داشته باشیم. این موضوع باعث می‌شود بتوانیم ۰ تا ۱۰۰ فرایند تولید یک مدل پیش‌بینی را به شکل یک Workflow با انعطاف پذیری قابل قبول داشته باشیم.در مقالات آینده تلاش می‌کنیم نقش تکنولوژی‌هایی را که در این مسیر به ما کمک کرده‌‌اند برای شما بیان کنیم.در انتها، اگر احساس می‌کنید به پروژه‌هایی شبیه به پروژه‌ی بالا علاقه‌مندید و تمایل دارید به تیم ما ملحق شوید، خوشحال می‌شویم که رزومه‌های خود را از طریق آدرس engineering@snapp.cab برای ما ارسال کنید.</description>
                <category>اسنپ</category>
                <author>محمدرضا جعفری</author>
                <pubDate>Wed, 07 Sep 2022 16:44:43 +0430</pubDate>
            </item>
                    <item>
                <title>ترافیک شهری در اسنپ - بخش صفر</title>
                <link>https://virgool.io/snapp-eng/%D8%AA%D8%B1%D8%A7%D9%81%DB%8C%DA%A9-%D8%B4%D9%87%D8%B1%DB%8C-%D8%AF%D8%B1-%D8%A7%D8%B3%D9%86%D9%BE-%D8%A8%D8%AE%D8%B4-%D8%B5%D9%81%D8%B1-x6u0qxmf2qec</link>
                <description>مقدمهیکی از مهم‌ترین نیازمندی‌های سرویس اسنپ اطلاع از وضعیت ترافیکی فعلی خیابون‌ها و معابر سطح شهر و همینطور پیش‌بینی وضعیت آن‌ها در دقایق و ساعت‌های آینده و استفاده از این داده‌ها برای ارائه سرویس‌های مختلف نقشه است. توی این پست میخواییم در مورد کاربرد‌‌‌ها، چالش‌ها و چگونگی انجام این کار تو تیم نقشه اسنپ توضیحات کوتاهی بدیم تا دریچه‌ای برای پست‌های آینده باشه.KeplerGL©تصویر ۱ - اتصال مبدا و مقصد سفر‌های اسنپ در یک بازه چند دقیقه ای    وضعیت ترافیکی چیست و چه استفاده‌هایی داره؟منظور ما از وضعیت ترافیکی معابر این است که اگر شما با یک خودرو وارد یک خیابان بشید با چه سرعتی میتونید در اون حرکت کنید. برای مثال یک بخش از اتوبان همت رو در نظر بگیرید. در ساعت‌های مختلف از روز ما شاهد سرعت‌های مختلفی هستیم که از ۸۰ کیلومتر بر ساعت شروع شده و در ساعت‌های اوج ترافیک به زیر ۱۰ کیلومتر در ساعت هم میرسه.ما با دونستن وضعیت لحظه‌ای خیابون‌ها میتوانیم اون رو به کاربران راننده و مسافر نشون بدیم و همینطور با پیش‌بینی وضعیت ترافیکی میتونیم تخمین خودمون از طول سفر و زمان رسیدن به مقصد رو بهبود بدیم که باعث میشه نه تنها قیمت‌گذاری دقیق‌تری رو داشته باشیم بلکه کاربران مسافر و راننده هم خواهند توانست زمان خودشون رو دقیق‌تر برنامه‌ریزی کنن. این‌ها بخش کوچکی از استفاده‌هایی هستن که میشه از این داده‌ها کرد.تصویر ۲ - ترافیک نقشه اسنپ در ساعت‌های مختلفما برای محاسبه وضعیت ترافیکی خیابون‌ها مثل سایر سرویس‌های مطرح از داده‌های GPS راننده‌ها استفاده می‌کنیم. راننده‌های اسنپ در زمان در دسترس بودن و همینطور در طول سفر در بازه‌های چند ثانیه ای اطلاعات مکانی خودشون رو به سرور‌های اسنپ ارسال می‌کنند. با جمع آوری و تحلیل این داده‌های مکانی ما می‌توانیم یک تخمین از وضعیت ترافیکی معابری که راننده‌های اسنپ در اون‌ها حضور دارن داشته باشیم.سرویس‌های متنوعی از این داده استفاده میکنند تا بتوانند نتایج دقیقی رو تولید بکنند که چند تا از اونها شامل موارد زیر می باشد:محاسبه زمان رسیدن به مقصد (ETA): این سرویس زمان رسیدن از یک نقطه به نقطه‌ی دیگری رو محاسبه میکنه. خروجی این سرویس یکی از مهم‌ترین ورودی‌های سرویس قیمت‌گذاری هستش.محاسبه مسیر بهینه (Routing): این سرویس مسیر بهینه برای رسیدن از یک نقطه به نقطه ی دیگری رو محاسبه می‌کنه. راننده با استفاده از این سرویس میتونن در کوتاه‌ترین زمان به مبدا و مقصد سفر برسند.پیدا کردن بهینه‌ترین راننده: این سرویس از بین راننده‌های موجود در یک منطقه راننده‌ای رو که در سریع‌ترین زمان ممکن می‌تونه به یک نقطه مشخص برسه رو پیدا می کنه.استفاده از این داده‌ها به این سادگی‌ها هم نیست، چالش‌های مختلفی وجود داره که در ادامه این پست بیشتر بهشون می‌پردازیم.چالش‌های موجود در محاسبه وضعیت ترافیکی و استفاده از آنهمانطور که قبل‌تر هم اشاره شد محاسبه وضعیت ترافیکی دارای چالش‌های علمی و فنی متعددی هست که حل کردن هر کدوم از اون‌ها نیاز به صرف زمان و انرژی قابل توجهی داره (طاده در اینجا به‌شکل مفصل در مورد یکی از این چالش‌ها نوشته). توی این بخش به چند تا از بزرگ‌ترین چالش‌ها اشاره می‌کنیم و به‌صورت خلاصه به راهکار‌هایی که براشون داریم اشاره می‌کنیم.چالش اول: مدل کردن داده‌های نقشهاولین نیازمندی برای کار با داده‌های نقشه مدل کردن این داده‌ها به ساختاری است که توسط کامپیوتر پردازش بشه. روش‌های مختلفی برای این کار وجود داره که مورد استفاده ترینشون تبدیل نقشه به یک گراف جهت داره. استاندارد‌های مختلفی در سطح جهان وجود داره که همه بتونن داده‌های نقشه رو با هم به اشتراک بگذارن. معروف‌ترین و محبوب‌ترین استاندارد هم داده‌های Openstreet Map یا همون OSM هستش. متاسفانه این استاندارد همه نیازمندی‌های موجود ما رو برطرف نمی‌کنه. برای مثال روش تقسیم‌بندی خیابون‌هاش مناسب تحلیل‌های ترافیکی نیستش. علاوه بر اون روشی که این استاندارد داده‌ها رو ذخیره می‌کنه دست ما رو تو استفاده از داده‌های دیگه در کنار هم می‌بنده.برای حل این مشکل ما از یک استاندارد گرافی دیگه که برای حل این مشکلات ساخته شده استفاده می‌کنیم که بر روی استاندارد داده‌های OSM سوار میشه. توی این روش هر خیابونی یک مشخصه (id) منحصر به فرد داره که در طول زمان تغییری نمی‌کنه. علاوه بر اون، توانایی شناسایی داده‌های مشترک در بین چند داده و استاندارد مختلف رو داره که به ما اجازه میده علاوه بر داده‌های OSM از داده‌های دیگه هم استفاده بکنیم. در پست‌های آتی بیشتر در مورد این ابزار صحبت خواهیم کرد.تصویر ۳ - تصویر فرضی از یک تقاطع و گراف مدل سازی شده آنچالش دوم: خطای داده‌های GPSهمونطور که می‌دونید، گوشی‌های ما با استفاده از امواج فرستاده شده از ماهواره‌های GPS موقعیت مکانی خودش رو تخمین می‌زنه. متاسفانه به دلایل مختلف ممکنه این تخمین با خطاهایی همراه باشه که از دلایل اون می‌شه به وجود ساختمون‌های بلند، بازتاب محیطی امواج، و همینطور نویز‌های موجود اشاره کرد. زمانی که این اتفاق میوفته ما از مکان دقیق راننده اطلاع نداریم و در نتیجه نمی‌تونیم با قطعیت بفهمیم که راننده در چه خیابونی در حال حرکت هستش.تصویر ۴ - نقاط دارای خطای ثبت شده توسط GPS راننده (نقاط قرمز) و مسیر واقعی که راننده طی کرده (خط سبز)به تکنیکی که بتونه این مشکل رو حل کنه Map Matching میگن. راه‌ حل‌های مختلفی برای حل این چالش وجود داره که هرکدوم خاصیت‌های خودشون رو دارن. بعضی‌هاشون خیلی سریع هستن ولی دقت کمی دارن و بعضی‌های دیگه دقیق‌تر اما با محاسبات سنگین‌تری هستن. ساده‌ترین راه حل موجود اینه که نقطه‌ی ثبت شده رو به نزدیک‌ترین خیابون متصل کنیم و فرض بگیریم که مکان واقعی راننده توی اون خیابون هستش. متاسفانه این روش از دقت خوبی برخوردار نیست و خیلی جاها مارو دچار مشکل میکنه. در بین راهکار‌های موجود ما به سراغ استفاده از مدل‌های پنهان مارکوف (Hidden Markov Models) رفتیم. توضیح در مورد این که این روش دقیقا چطوری کار میکنه از حوصله این پست خارجه و میذاریمش برای پست‌های بعدی و در زیر یک توضیح خیلی سریع در موردش میدیم.توی این روش که به اختصار به اسم HMM Map Matching شناخته میشه ما فقط به نقطه فعلی نگاه نمیکنیم بلکه میاییم به موقعیت‌های مکانی قبلی کاربر که مربوط به ثانیه‌ها و دقایق قبل هستش نگاه میکنیم. اینطوری وقتی بین چند گزینه احتمالی شک داریم بهتر میتونیم از انتخاب نهایی موقعیت مکانی کاربر مطمئن بشیم.تصویر ۵ - نقاط ثبت شده توسط دستگاه GPS (طوسی) به همراه نقاط واقعی راننده (آبی) و نقاط محاسبه شده توسط الگوریتم HMM (سبز) چالش سوم: حجم زیاد داده‌هایکی دیگه از چالش‌هایی که ما در این مسیر بهش برخوردیم حجم زیاد داده‌های GPS بود که به سمت ما میومد. شکل زیر (تصویر ۶) تصویری از نقاط GPS ای هستش که فقط در طول چند دقیقه و فقط در شهر شیراز به سمت ما ارسال شده. میتونید حدس بزنید که تحلیل این داده‌ها که به ۲.۴ میلیون نقطه در دقیقه میرسن میتونه کار چالش برانگیزی باشه.تصویر ۶ - نقاط GPS راننده‌های شیراز در بازه ی زمانی چند دقیقه ©KeplerGL  تصویر ۷ یک نمودار خیلی کلی از مسیر داده‌های ترافیکی ما (Traffic Pipeline) هستش. تمام این مراحل باید بتونن اونقدر سریع انجام بشن که داده‌های خروجیشون قابل استفاده به‌صورت زنده باشن. ما در بازه‌های کوتاه ۱۵‌دقیقه ای داده‌های GPS رو جمع میکنیم و این پردازش‌هارو روش انجام میدیم. از اونجایی که سیستم ما به‌صورت واقعی زنده نیست و در بازه‌های کوتاه کارش رو انجام میده بهش پردازش Micro Batch هم گفته میشه.تصویر ۷ - شمای کلی از پایپلاین داده‌های ترافیکهمونطور که میدونید راه‌های زیادی برای سریع کردن پردازش‌ها وجود داره. یکی از راه‌ها اسکیل (Scale) کردن سرویس‌ها به‌صورت عمودی (Vertically) هستش. در مورد ما هزینه سروری که اونقدری سخت افزار قوی‌ای داشته باشه که بتونه نیاز‌های مارو جواب بده زیاده و روش منطقی‌ای برای ما نیست. پس ما به سمت اسکیل کردن افقی (Horizontally) رفتیم. برای این که بتونیم این کار رو انجام بدیم لازم بود که بتونیم تمام معماریمون رو طوری طراحی کنیم که توانایی پردازش موازی و همزمان داشته باشن. فریمورک‌ها و ابزار‌های مختلفی برای رسیدن به این نتیجه وجود دارن که ما از بین اون‌ها Apache Spark رو انتخاب کردیم و معماری Pipeline مون رو با اون طراحی و پیاده سازی کردیم.تصویر ۸ - بخشی از stage های پایپلاین ترافیک در اسنپچالش چهارم: ابزار مسیریابی (Routing Engine)سرویس‌های مختلف نقشه مثل مسیریابی راننده‌ها و تخمین زمان رسیدن به مقصد (ETA) علاوه بر داده‌های ترافیکی نیاز به ابزاری سریع و دقیق جهت مسیریابی دارن. به سرویسی که این وظیفه رو انجام میده Routing Engine گفته میشه. ابزار‌های متن باز مختلفی در این زمینه موجود هستند که هرکدوم از نقاط قوت و ضعف مختلفی دارن. ما تو اسنپ به ابزاری نیاز داریم که بتونه داده‌های ترافیکی زنده رو پردازش بکنه، توانایی اعمال محدودیت‌های ترافیکی مختلف مثل طرح ترافیک رو داشته باشه و در کنار همه ی این‌ها سریع باشه. متاسفانه هیچ‌کدوم از ابزار‌هایی که ما میشناسیم همه‌ی نیازمندی‌های مارو برطرف نمیکنن در نتیجه ما توی اسنپ به سمت استفاده و گسترش این ابزار‌های متن‌باز رفتیم تا با نیازمندی‌های ما منطبق بشه. توی پست‌های بعدی به‌شکل تخصصی‌تر به بررسی این ابزار می‌پردازیم.تصویر ۹ - مسیر بهینه محاسبه شده بین دو نقطه توسط Routing Engineپایانتوی این پست با محاسبه وضعیت ترافیکی خیابون‌ها در اسنپ و استفاده از این سرویس به همراه چالش‌هایی که داره به‌شکل کلی آشنا شدید. استفاده از این داده‌ها به موضوعاتی که به اونها اشاره شد محدود نیست و همینطور چالش‌هایی که در مسیر موجود هستند هم بیشتر از اون‌هایی هستند که تا اینجا دیدید. توی این پست تلاش شد که به‌شکل خلاصه با معماری و ادبیات موضوع آشنا بشید تا در پست‌های آینده بتونیم عمیق‌تر و مفصل‌تر به هر کدوم از بخش‌های این سیستم بپردازیم.در انتها از همتون ممنونم که تا اینجا با ما همراه بودید. امیدوارم که مطالبی که خوندید براتون جذاب بوده باشه. خوشحال می‌شم اگه سوالی دارید یا دوست دارید بیشتر صحبت کنیم کامنت بذارید یا به من پیام بدید.در ضمن، اگه حس می‌کنین از پروژه‌هایی شبیه به این خوشتون میاد و در نتیجه علاقه دارید به تیم ما ملحق بشید، خوشحال می‌شیم که رزومه‌هاتون رو از طریق آدرس engineering@snapp.cab برای ما ارسال کنید.</description>
                <category>اسنپ</category>
                <author>سینا بختیاری</author>
                <pubDate>Tue, 05 Jul 2022 16:06:15 +0430</pubDate>
            </item>
                    <item>
                <title>طراحی SDK نقشه با زبان برنامه‌نویسی گولنگ در اسنپ!</title>
                <link>https://virgool.io/snapp-eng/%D8%B7%D8%B1%D8%A7%D8%AD%DB%8C-sdk-%D9%86%D9%82%D8%B4%D9%87-%D8%A8%D8%A7-%D8%B2%D8%A8%D8%A7%D9%86-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D9%87-%D9%86%D9%88%DB%8C%D8%B3%DB%8C-%DA%AF%D9%88%D9%84%D9%86%DA%AF-%D8%AF%D8%B1-%D8%A7%D8%B3%D9%86%D9%BE-sq6p6dkz4lmt</link>
                <description>مقدمهتو این مقاله می‌خوام در مورد فرآیند طراحی و پیاده‌سازی SDK سرویس‌های مبتنی بر نقشه در اسنپ توضیح بدم.قبل از هرچیزی تعریف SDK رو بررسی می‌کنم. بعد سراغ چرایی نیاز به SDK برای سرویس‌های نقشه اسنپ می‌رم. نکات استخراج شده از بررسی‌هامون روی SDK های متن‌بازی که به خوبی طراحی شدن رو مطرح می‌کنم. و در آخر به نحوه‌ی پیاده‌سازی نیاز‌های منحصر به فرد خودمون با استفاده از زبان برنامه‌نویسی گولنگ اشاره می‌کنم.طراحی SDK نقشه با زبان برنامه‌نویسی گولنگ در اسنپ!تعریف SDKعبارت Software Development Kit یا به اختصار همون SDK، به مجموعه‌ای از ابزار برای راحت‌تر شدن توسعه نرم‌افزار تو یک زمینه‌ی خاص اطلاق میشه.معمولا یک SDK برای اجرا،‌ وابسته به یک یا چند API است. این API ها می‌تونن تحت وب باشند یا کاملا آفلاین. نکته‌ی مهم دیگه که تو SDK ها وجود دارند اینه که اکثرا فقط تو یک محیط خاص (مثلا زبان برنامه‌نویسی، سیستم‌عامل و یا یک سخت‌افزار خاص) اجرا میشن.چند مثال زیر نمونه‌های از SDK های تحت وب هستند که برای زبان‌برنامه‌نویسی گولنگ طراحی شدند:AWS SDK (Golang)Google Maps SDK (Golang) Dropbox SDK (Golang)چرا برای سرویس‌های نقشه‌ی اسنپ نیاز به SDK داشتیم؟ما توی تیم نقشه‌ی اسنپ (خودمون اسمپ صداش می‌کنیم!) سرویس‌های مختلفی فراهم می‌کنیم. بعضی از این سرویس‌ها عبارتند از: سرویس Reverse Geocode: کارایی این سرویس، تبدیل مختصات جغرافیایی به آدرس خوانا برای انسان است.سرویس Search: برای پیدا کردن مختصات مکانی محل‌های خاص در نقشه صرفا با استفاده از متن، از این سرویس استفاده میشه.سرویس Estimated Time of Arrival یا ETA: از این سرویس برای تخمین زمان یک سفر از نقطه‌ی «آ» به نقطه‌ی «ب» استفاده میشه.و ...این سرویس‌ها توسط خیلی از تیم‌های اسنپ، اسنپ‌باکس، اسنپ‌فود و ... استفاده می‌شن.نحوه‌ی استفاده‌‌ی این سرویس‌ها قبلا به این شکل بود که مستندات API ها (معمولا در قالب OpenAPI) در اختیار تیم‌های استفاده کننده قرار می‌گرفت و استفاده از API ها کاملا به عهده‌ی خود تیم بود.این تجربه‌ی توسعه زیاد جالب نبود. هر تیمی به روش خودش از API ها استفاده می‌کرد و در نتیجه کدبیس‌های متفاوتی برای پیاده‌سازی یک functionality در سازمان به وجود می‌اومد.در نتیجه تصمیم گرفتیم برای سرویس‌های اسمپ یک SDK بسازیم و در اختیار استفاده‌کنندگان قرار بدیم.نکات مهم در طراحی SDKبا بررسی SDK های متن‌باز و معروف، مخصوصا SDK های پیاده‌سازی شده با گولنگ، نکاتی رو استخراج کردیم که سعی کردیم اکثرشون رو با توجه به شرایط تیم و پروژه‌ها رعایت بکنیم. چند تا این نکات رو با هم می‌شکافیم.کاربر‌ها را غافل‌گیر نکنید!یک SDK خوب دقیقا همون کاری رو می‌کنه که کاربرش میگه. نه بیشتر نه کمتر! پیاده‌سازی بر اساس پیش‌فرض‌های ذهنی توسعه دهنده‌ی سرویس، که لزوما با پیش‌فرض‌های کاربر‌ان یکسان نیست، ممکنه باعث بشه در برخی از مواقع کاربر غافل‌گیر بشه.از کمترین Dependency ممکن استفاده کنید.استفاده از کتاب‌خانه‌های مختلف در پیاده‌سازی SDK باید به حداقل برسه. یکی از دلایلش اینه که در این صورت شما کاربر رو مجبور به استفاده از یک کتاب‌‌خانه می‌کنید. کاربر ممکنه به هر دلیلی نخواد یا نتونه از اون کتاب‌خانه استفاده بکنه. مثلا برخی از دلایل می‌تونن شبیه لیست زیر باشند: حجم کتاب‌خانه برای اون پروژه بیش‌از حد زیاده.اون پروژه داره از یک نسخه‌ی دیگه‌ای از کتاب‌خانه استفاده می‌کنه و با توجه به محدودیت بعضی از زبان‌های برنامه‌نویسی نمی‌تونه دو تا نسخه‌ از یک کتاب‌خانه رو همزمان داشته باشه.در برخی شرایط ممکنه اون کتاب‌خانه با سخت‌افزار سازگار نباشه.مشکل امنیتی توی کتاب‌خانه وجود داشته باشه. (شما هم یاد log4j افتادید؟ :)) )و ...رفتار ثابتی در SDK طراحی نکنید!نیاز‌های تیم توسعه‌ی کاربران شما می‌تونه تفاوت‌های زیادی داشته باشه. سعی کنید حتی الامکان همه‌ی رفتار‌های SDK قابل پیکربندی و شخصی‌سازی باشن.چند مثال:کاربر باید بتونه آدرس سرویس‌هایی که درخواست‌ها بهشون ارسال میشه رو شخصی‌سازی بکنه. کاربر می‌تونه از این ویژگی برای Mock کردن تست‌ها و یا بهره‌برداری از سرویس‌های دیگه به عنوان fallback استفاده بکنه.کاربر باید بتونه تنظیمات مربوط به کلاینت شبکه رو با توجه به نیاز‌ها و وضعیت شبکه‌ی خودش شخصی‌سازی بکنه. برای مثال توی گولنگ باید بشه http.Transport کلاینت‌های http رو override بکنه.تمام اطلاعات رو در اختیار کاربر بگذارید.هنگام پیاده‌سازی توجه داشته باشید که تمام اطلاعات رو در اختیار کاربر بگذارید. اگه اروری پیش میاد حتما اطلاعات کامل ارور رو در اختیار کاربر بگذارید که در صورت نیاز بتونه مشکلات رو سریع‌تر بفهمه و مرتفع بکنه. یا قابلیت لاگ‌کردن به صورت Verbose رو به صورت اختیاری برای SDK خودتون پیاده‌سازی کنید. در این صورت کاربر می‌تونه تمام اتفاقاتی که داخل SDK می‌افته رو مشاهده بکنه.سعی کنید SDK شما قابل mock کردن باشه.احتمالا از اهمیت تست‌نوشتن برای برنامه‌هایی که می‌نویسید با خبر هستید. پس از اونجا که تست‌کردن برای ما پسندیده است، برای کاربران SDK ما هم پسندیده‌است :)برای این منظور می‌تونید از interface ها در گولنگ استفاده کنید.ما تو اسمپ یک ورژن ماک‌شده‌ی آماده هم از SDK مون در اختیار کاربر قرار می‌دیم که کمتر زحمت بکشه. این کار رو با استفاده از mockgen انجام دادیم.اگر امکانش رو دارید کد‌های SDK رو generate کنید.معمولا کاربران از تکنولوژی‌های مختلفی برای استفاده‌از سرویس‌های ما به کار می‌گیرند. به خاطر همین موضوع بهتره رفتار‌ها و ویژگی‌های یک SDK رو با یک زبان میانی توصیف کنید و در نهایت از روی این توصیفات، SDK مربوط به زبان‌های برنامه‌نویسی مد نظرتون رو generate بکنید.قاعدتا این کار می‌تونه خیلی پیچیده و زمان‌بر باشه. پس زمانی سراغش برید که شرایط تیمتون مناسب باشه.نحوه‌ی پیاده‌سازی SDK نقشه اسنپ با در زبان گولنگساختار کد SDK پیاده‌سازی‌شده‌ی ما در دیاگرام زیر خلاصه شده:نمودار UML ساختار SDKاطلاعات پراستفاده و مشترک بین کلاینت‌های سرویس‌های مختلف در پکیج config نگه داری شده است.نکته: لازم به ذکر است که برای راحتی کاربران SDK می‌توان قابلیت خواندن کانفیگ از فایل، Environment variable و راه‌های دیگر را پیاده‌سازی کرد.برای هر سرویس یک پکیج جدا در نظر گرفته شده است. برای مثال در نمودار بالا پکیج ETA را مشاهده می‌کنید.در پکیج مربوط به هر سرویس، یک اینترفیس شامل تعریف قابلیت‌های سرویس وجود دارد.دو پیاده‌سازی از این اینترفیس‌ها وجود دارد که هر کدام کاربرد متفاوتی دارند. یکی از پیاده‌سازی‌ها که یک شی از config را نیز در اختیار دارد،‌جهت استفاده عادی و ارسال درخواست به سرور‌ها استفاده می‌شود.پیاده‌سازی دیگر جهت استفاده در Unit test های کاربران SDK و Mock کردن هرچه‌ راحت‌تر تست‌هاست.نکته‌ی بسیار مهم در طراحی interface هر سرویس، استفاده از context.Context گولنگ بوده. این امر باعث شده کنترل بیشتر و گسترش‌پذیری بسیار بالاتری داشته باشیم.پایانممنون که پست رو تا انتها خوندید. امیدوارم براتون مفید بوده باشه.در ضمن، اگه حس می‌کنین از پروژه‌هایی شبیه به این خوشتون میاد و در نتیجه علاقه دارید به تیم ما ملحق بشید، خوشحال می‌شیم که رزومه‌هاتون رو از طریق آدرس engineering@snapp.cab برای ما ارسال کنید. مراقب خودتون باشید!</description>
                <category>اسنپ</category>
                <author>میلاد ابراهیمی</author>
                <pubDate>Sat, 25 Jun 2022 16:31:23 +0430</pubDate>
            </item>
                    <item>
                <title>سرویس‌ورکر در پروژه CRA؛ مزایا و چالش‌ها</title>
                <link>https://virgool.io/snapp-eng/%D8%B3%D8%B1%D9%88%DB%8C%D8%B3-%D9%88%D8%B1%DA%A9%D8%B1-%D8%AF%D8%B1-%D9%BE%D8%B1%D9%88%DA%98%D9%87-cra-%D9%85%D8%B2%D8%A7%DB%8C%D8%A7-%D9%88-%DA%86%D8%A7%D9%84%D8%B4-%D9%87%D8%A7-hcuugh05oxix</link>
                <description>معمولا به منظور صرفه‌جویی در وقت برای شروع یک پروژه react، از create-react-app یا به طور مخفف CRA استفاده می‌کنیم. تا قبل از ورژن ۴ cra، وقتی پروژه جدیدی رو ایجاد می‌کردید، به صورت پیش فرض برای تمامی پروژه‌ها فایل service-worker.js در ساختار پروژه وجود داشت (که معمولا هم خودمون پاکش می‌کردیم ?).  این سرویس‌ورکر رجیستر نشده بود و کاربر بسته به نیازش میتونست اون رو رجیستر کنه و ازش استفاده کنه. از ورژن ۴ به بعد سرویس‌ورکر به یک ویژگی انتخابی در پروژه cra تبدیل شده و از طریق تمپلیتی مجزا قابل نصب خواهد بود. به همین دلیل اگر با دستور زیر اقدام به ساخت پروژه کنید، پروژه شما فاقد فایل‌ها و تنظیمات سرویس‌ورکر خواهد بود. npx create-react-app my-appاما اگر کاربر قصد داشته باشه از سرویس‌ورکر در پروژه خودش استفاده کنه و دوست داشته باشه که پروژه‌اش با این تنظیمات ایجاد بشه، cra یک تمپلیت مجزا آماده کرده که مخصوص برنامه‌های PWA است و سرویس‌ورکر درون اون تمپلیت پیاده‌سازی شده و تنظیمات ابتدایی اون انجام شده. برای ایجاد پروژه با تمپلیت pwa باید به شکل زیر عمل کنیم.ساخت پروژه CRA با تمپلیت PWAتوی این مقاله قصد داریم درباره سرویس‌ورکری که از طریق تمپلیت pwa در اختیار ما قرار میگیره صحبت کنیم و بررسی کنیم که این سرویس‌ورکر چطور ایجاد میشه، چه امکاناتی رو به طور پیش‌فرض در اختیار ما قرار میده و چندتا از ریزه‌کاری‌ و چالش‌هاش رو هم باهاتون در میان بذاریم.توجه داشته باشید بعضی از مفاهیمی که در این مقاله مطرح می‌شن صرفا مربوط به تمپلیت pwa میشن و نباید این مفاهیم رو به کل سرویس‌ورکر تعمیم داد.ساختار پروژه و تغییراتشساختار پروژه cra با تمپلیت pwaتصویر بالا ساختار پروژه ایجاد شده به وسیله تمپلیت pwa رو نشون میده. تفاوت بین پروژه ساخته شده از طریق تمپلیت pwa نسبت به پروژه‌ای که از طریق تمپلیت ساده cra ساخته میشه، شامل موارد زیر میشه:تغییرات درون فایل index.jsایجاد فایل serviceWorkerRegistration.js (ثبت‌نام سرویس‌ورکر)ایجاد فایل service-worker.js (پیاده‌سازی سرویس‌ورکر)علاوه بر این تغییرات، تنظیماتی هم در وب‌پک (webpack) اعمال میشه و پکیج‌هایی هم نصب میشن که در ادامه به اون‌ها اشاره خواهیم کرد.دست‌های پشت پرده تمپلیت pwa ??در این بخش میخوایم تغییراتی رو که به واسطه استفاده از تمپلیت pwa در پروژه امون ایجاد شده رو با جزئیات بیشتری مورد بررسی قرار بدیم و ببینیم هرکدوم از این تغییرات چه کاری انجام میدن و پیاده‌سازی سرویس‌ورکر به چه صورت انجام شده و این سرویس‌ورکر چه امکاناتی در اختیار ما قرار میده.فایل index.jsتغییرات اعمال شده در فایل index.js به واسطه استفاده از تمپلیت pwa، محدود میشه به یک تنها یک خط  کد  که وظیفه فراخوانی تابع رجیستر سرویس‌ورکر رو برعهده داره. اگر به داخل فایل index.js نگاهی بندازیم، میبینیم که به طور پیش‌فرض تابع unregister استفاده شده تا اگر کاربری نیاز به استفاده از سرویس‌ورکر نداشت، سرویس‌ورکری براش نصب نشه. اگر  اطمینان دارید که درون برنامه‌اتون از امکانات سرویس‌ورکر استفاده نخواهید کرد، میتونید این خط کد رو به همین صورت نگه دارید. اما در صورت نیاز به استفاده از سرویس‌ورکر و امکانات پیاده‌سازی شده داخلش، نیاز داریم تا تابع unregister رو به register  تغییر بدیم تا فرآیند ثبت سرویس‌ورکر از داخل فایل index.js شروع بشه.تغییرات ایجاد شده درون فایل indexبه طور پیش‌فرض سرویس‌ورکر براتون نصب نخواهد شد. برای استفاده از اون نیاز دارید که تابع unregister رو به register تغییر بدید.همونطور که مشاهده کردیم، فراخوانی تابع register داخل فایل index انجام میشه تا به محض اجرای این فایل که نقطه شروع برنامه react  هست، تابع register فراخوانی بشه و مرحله رجیستر سرویس‌‍‌ورکر نیز آغاز بشه. اگر ما فراخوانی تابع register رو به تاخیر بندازیم و اون رو  به فایل‌های داخلی منتقل کنیم این احتمال وجود داره که با مشکل روبرو بشیم.منطق پیاده‌سازی شده در تمپلیت pwa برای ثبت سرویس به این صورت هست که در تابع register یک event برای load شدن صفحه رجیستر میشه و داخل هندلر اون روند رجیستر شدن سرویس‌ورکر عملا شروع میشه. در صورتی که ما فراخوانی تابع regitster رو به کامپوننت‌های داخلی پروژه react منتقل کنیم، این event پس از load شدن صفحه رجیستر میشه و در نتیجه هیچ‌گاه فراخوانی نخواهد شد چون صفحه از قبل load شده.تصویر بالا منطق ساده شده تابع رجیستر رو نشون میده. همونطور که مشاهده میکنید سرویس ورکر تنها در صورتی رجیستر میشه که لود صفحه به پایان رسیده باشد. اگر ما تابع register رو بعد از load شدن صفحه فراخوانی کنیم، هیچگاه سرویس‌ورکر ما رجیستر نخواهد شد. در نتیجه، در تمپلیت pwa همواره سعی کنید که تابع register رو تا جای ممکن زود فراخوانی کنید.فایل serviceWorkerRegistration.jsدرون این فایل پیاده‌سازی توابع register و unregister انجام شده. تابع register وظیفه ثبت‌نام سرویس‌ورکر نزد بروزر رو برعهده داره. هر سرویس‌ورکر یک چرخه زندگی (life-cycle) داره که از سه مرحله ‌ثبت‌نام(register)، نصب(install) و فعال(active) تشکیل شده. مرحله اول یعنی رجیستر شدن که اولین مرحله از مراحل چرخه زندگی سرویس‌ورکر هست، مرحله‌ایه که ما به بروزر اعلام می‌کنیم که قصد استفاده از سرویس‌ورکر رو داریم و همچنین فایلی رو که کدهای مربوط به سرویس‌ورکر داخل اون قرار دارن رو به بروزر معرفی می‌کنیم. فایل سرویس‌ورکر معرفی شده به بروزر توسط تمپلیت pwa،  فایل service-worker.js است.این دقیقا همون کاری هست که تابع register نوشته توسط تمپلیت pwa، انجام میده؛ البته با کمی جزئیات بیشتر. این جزئیات شامل چک‌‌کردن یکسری از شرایط میشه که وجودشون برای نصب سرویس‌ورکر الزامی هست و اگر این شرایط فراهم نباشن سرویس‌ورکر وارد مرحله نصب نخواهد شد. از جمله این شرایط می‌توان به موارد زیر اشاره کرد: پشتیبانی بروزر از سرویس‌ورکر (بروزرهای پشتیبانی کننده)پروداکشن بودن محیط (به منظور جلوگیری از ساخت سرویس‌ورکر و ایجاد cache در محیط dev)اطمینان از معرفی فایل سرویس‌ورکر و موجود بودن آن فایل (در صورتی که لوکال باشه)اطمینان از لود شدن کامل صفحه (به منظور جلوگیری از تداخل فرآیند نصب سرویس‌ورکر و لود صفحه)چک کردن این شرایط مختص به تمپلیت ‌pwa هست و میشه این شرایط رو  بسته به نیازمون تغییر بدیم یا بهشون اضافه کنیم. دو سرویس ورکر در یک اقلیم نمیگنجن ?با معرفی فایل‌سرویس‌ورکر به بروزر، بروزر فایل مورد نظر رو دریافت میکنه و به اجرای دستورات موجود در این فایل می‌پردازه که به این مرحله، مرحله Install گفته میشه. اگر مرحله نصب به درستی انجام بشه event ارسال خواهد کرد تا به بروزر اطلاع‌رسانی کنه که نصب سرویس‌ورکر به پایان رسیده.  در این مرحله دو سناریوی متفاوت می‌تونه اتفاق بیوفته:از قبل سرویس‌ورکر دیگری برای domain ما ثبت نشده باشه. از قبل سرویس‌ورکری فعال وجود داشته باشه.اگر از قبل سرویس‌ورکری وجود نداشته باشه، سرویس‌ورکر جدید، بدون هیچ مشکل و مزاحمتی، وارد مرحله active میشه و آماده است که به پیام‌ها (messages) و eventهای ارسالی پاسخ بده. اما اگر سرویس‌ورکر دیگری از قبل وجود داشته باشه، از اونجایی که ما فقط می‌تونیم یک سرویس‌ورکر فعال داشته باشیم، سرویس‌ورکر جدید به حالت waiting میره و منتظر میمونه تا کار سرویس‌ورکر قدیمی به اتمام برسه. کار سرویس‌ورکر قدیمی تنها زمانی به پایان میرسه که صفحه مورد نظر در بروزر بسته بشه (اگر چندین tab از یک سایت باز باشه، باید تمامی tab ها بسته بشن). پس از اون، سرویس‌ورکر جدید میتونه جایگزین سرویس‌ورکر قدیمی بشه. این رفتاری هست که بروزر به طور پیش فرض از خودش نشون میده. و همین رفتار هم در تمپلیت پیش‌فرض pwa اتفاق میوفته. اما این امکان وجود داره که این رفتار برای برنامه ما رفتار ایده‌آلی نباشه. از این رو این قابلیت به وجود اومده که بتونیم این رفتار رو تغییر بدیم. به این صورت که میتونی با ارسال پیام خاصی به سرویس‌ورکر جدید اعلام کنیم که مرحله waiting رو نادیده بگیر و پس از نصب بلافاصله وارد مرحله active بشو. این کار در تمپلیت pwa از طریق ابجکت کانفیگی که به تابع رجیستر پاس میدیم، انجام میشه.کانفیگ تابع رجیسترتابع رجیستری که در قسمت قبل توضیحش دادیم، یک پارامتر اختیاری میگیره به اسم config که یک ابجکت هست. این ابجکت میتونه پراپرتی‌هایی با نام onUpdate و onSuccess داشته باشه که هر کدوم یک callback function هستند. این توابع در صورتی که توسط کانفیگ به تابع رجیستر پاس بدیمشون، در زمانی که نصب سرویس‌ورکر با موفقیت انجام شد، فراخوانی میشن.اگر از قبل سرویس‌ورکر دیگری برای domain ما ثبت نشده باشه، تابع onSuccess به همراه پارامتر registration فراخوانی میشه. اگر از قبل سرویس‌ورکری فعال باشه، در این صورت تابع onUpdate به همراه ابجکت registration فراخوانی میشه.config objectپارامتر registration که به عنوان ورودی به هر دو تابع onsuccess و onupdate پاس داده میشه، ابجکتی هست حاوی اطلاعات مربوط به ثبت‌نام سرویس‌ورکر. اطلاعاتی نظیر اینکه ثبت‌نام الان در چه مرحله‌ای قرار داره، اسکوپ سرویس‌ورکر چی هست، سرویس‌ورکر درون چه فایلی قرار داره و ...به طور پیش‌فرض، تمپلیت pwa کانفیگ رو به تابع رجیستر پاس نمیده. در نتیجه وقتی که سرویس‌ورکر جدید نصب میشه، اگر سرویس‌ورکر قدیمی در حال فعالیت باشه، باعث میشه که سرویس‌ورکر جدید به حالت waiting بره. در صورتی که بخوایم سرویس‌ورکر جدید بلافاصله جایگزین سرویس‌ورکر قدیمی بشه، می‌تونیم داخل کال‌بک onUpdate اقدام به ارسال پیام skip waiting به سرویس‌ورکر جدید کنیم. این کار باعث میشه که سرویس‌ورکر جدید مرحله waiting رو نادیده بگیره و بلافاصله سرویس‌ورکر قدیمی رو kill کنه و خودش رو جایگزین اون کنه. پس از ارسال پیام و جایگزین شدن سرویس‌ورکر جدید با سرویس‌ورکر قدیمی احتیاج داریم تا صفحه رو رفرش کنیم تا اگر منابع برنامه تغییر کرده باشن، منابع دریافت بشن. تصویر زیر نحوه ارسال پیام skip waiting از داخل تابع onUpdate رو نشون میده:ارسال پیام skip waitingهمونطور که توضیح داده شد، ابجکت registration حاوی اطلاعات ثبت‌نام سرویس‌ورکر هست. یکی از این اطلاعات status هست که معرف وضعیتی هست سرویس‌ورکر در اون لحظه توش قرار داره. اگر سرویس‌ورکر تازه نصب شده به حالت waiting رفته باشه، از طریق registration.waitnig قابل دسترسی هست. از این طریق به سرویس‌ورکر در حال انتظار دسترسی داریم و بهش پیامی ارسال می‌کنیم. این پیام از نوع SKIP_WAITING خواهد بود که نحوه گوش‌دادن و پاسخگویی به این پیام توسط تمپلیت pwa از قبل پیاده‌سازی شده و نیازی نیست که ما کاری انجام بدیم.با پیاده‌سازی رفتار ذکر شده در بالا، این احتمال وجود داره که با مشکل در آپدیت شدن سرویس‌ورکرتون مواجه بشید. این مشکل به واسطه نحوه پیاده‌سازی آپدیت در تمپلیت cra برمیگرده که در بخش بعدی به بررسی این مشکل میپردازیم.چالش آپدیت شدن بدون بستن برنامه ?در قسمت قبل دیدیم که با پاس دادن تابع onUpdate در داخل ابجکت config به تابع رجیستر، می‌تونیم به سرویس‌ورکر جدید اعلام کنیم که ‌به حالت waiting نرو و بلافاصله پس از نصب، جایگزین سرویس‌ورکر قدیمی بشو. ولی با تمپلیت pwa که در حال حاضر توسط cra ارائه میشه، در بروزرهای سافاری با چالش مواجه می‌شیم. مشکلی که باهاش روبرو هستیم این هستش که بروزرهای سافاری (به ویژه در ورژن‌های قدیمی‌تر) متوجه آپدیت شدن سرویس‌ورکر نمی‌شن. این مشکل از اونجا نشأت می‌گیره که توی IOS (و به تبع اون در سافاری) چرخه اجرای برنامه‌های PWA و سرویس‌ورکر متفاوت از این چرخه در دیگر بروزرها است. در بروزرهایی مثل کروم، ابتدا برنامه شروع به کار میکنه و زمانی که میرسه به مرحله رجیستر کردن سرویس‌ورکر، چک میکنه که آیا سرویس‌ورکر آپدیت شده یا خیر. این به اون معنی هست که برنامه ما زمان کافی داشته تا هندلر مربوط به ایونت onupdatefound رو برای سرویس‌ورکر قبلی رجیستر کنه تا از این طریق متوجه نصب سرویس‌ورکر جدید بشه و پس از نصب سرویس‌ورکر جدید، اقدام به فراخوانی تابع ‌onUpdate کنه. اما در IOS، به نظر میرسه که جستجو برای سرویس‌ورکر جدید قبل از اجرای برنامه اصلی اتفاق میوفته. به همین دلیل ابتدا سرویس‌ورکر جدید نصب میشه (این کار در زمان نمایش صفحه‌ای موسوم به splashscreen رخ میده) و پس از اون برنامه اصلی اجرا میشه و onupdatefound رو روی سرویس‌ورکر قدیمی رجیستر میکنه (رجیستر شدن event پس از وقوع اون اتفاق میوفته). در نتیجه سرویس‌ورکر قدیمی هیچگاه متوجه وقوع onupdatefound نمیشه، چون سرویس ورکر جدید زودتر نصب شده و به حالت waiting رفته. این مشکل منجر به این میشه که تابع onUpdate فراخوانی نشه و الزاما نیاز داشته باشیم تا برنامه رو به صورت دستی ببندیم تا آپدیت جدید اعمال بشه. علاوه بر مطالب ذکر شده در بالا، onupdatefound event در ورژن‌های قدیمی‌تر IOS به طور کلی وجود نداره و در نتیجه هیچگاه تریگیر نخواهد شد. به همین دلیل بروزر متوجه آپدیت شدن سرویس‌ورکر نمیشه.این مشکل به صورت یک issue باز در مخزن گیت‌هاب cra وجود داره و براش راه حل‌هایی هم پیشنهاد شده، که میتونید این issue رو در اینجا مشاهده کنید.فایل service-worker.js ⚙فایل دیگری که توسط تمپلیت pwa ساخته میشه، فایل service-worker.js هست. در مرحله رجیستر این فایل به عنوان فایلی که کدهای مربوط به سرویس‌ورکر درون اون قرار دارن، به بروزر معرفی شد. در واقع لاجیک سرویس‌ورکر و اینکه چه کار میخواد بکنه، در این فایل پیاده‌سازی شده. اگر فایل service-worker.js رو باز کنید مشاهده می‌کنید که یکسری عملکردهای پیش فرض برای سرویس‌ورکر توسط تمپلیت pwa داخل این فایل پیاده سازی شده. این عملکردها شامل موارد زیر میشه:پیش کش (pre-cache)پیاده‌سازی یک routing-cache (به منظور ذخیره‌سازی فایل‌های png داخل cache) رجیستر کردن event listener برای message eventsاین پیاده‌سازی‌ها که به صورت پیش‌فرض انجام شدن، این امکان رو به پروژهای ساخته شده از طریق تمپلیت pwa میدن که قابلیت موسوم به offline-first رو داشته باشن. نکته حائز اهمیت این هست که در پیاده‌سازی‌ این فیچرها از پکیج workbox استفاده شده. پکیچ workbox توسط شرکت گوگل توسعه داده شده و امروزه به یک استاندارد برای پیاده‌سازی سرویس‌ورکر تبدیل شده. workbox از چندین پکیج و پلاگین تشکیل شده که پیاده‌سازی سرویس‌ورکرها رو بسیار راحت می‌کنن و این امکان رو به ما میدن که به فرآیند توسعه سرویس‌ورکر سرعت بدیم. پرداختن به این پکیج و امکاناتش از موضوع این بحث خارج هست ولی ما در ادامه مباحثی رو که در cra از اون استفاده شده، به طور مختصر توضیح خواهیم داد. با ساخت پروژه از طریق تمپلیت pwa کلیه پکیج‌های ورک‌باکس به پروژه اضافه خواهد شد.اگر علاقه‌مند به آشنایی با پکیج workbox هستید می‌تونید از طریق این لینک بیشتر باهاش آشنا بشید.در ادامه امکاناتی رو که درون فایل سرویس‌ورکر پیاده‌سازی شدن رو مورد بررسی قرار میدیم.مفهوم pre-cachingمفهوم پیش-کش (pre-cache) به این معنی هست که با استفاده از سرویس‌ورکر (که روی یک thread مجزا اجرا میشه) کلیه قسمت‌های کد پروژه (chunkها) رو که در مرحله بیلد توسط وب‌پک ساخته میشه رو دانلود کنیم و داخل یک کش  در سمت کلاینت ذخیره کنیم. این کار باعث میشه کاربر بتونه، در شرایطی که به اینترنت دسترسی ندارد و یا ارتباطش سرعت مناسبی نداره هم از وب اپلیکیشن ما استفاده کنه.برای پیاده‌سازی سرویس‌ورکر و pre-caching در پروژه‌هایی که از webpack به عنوان ماژول باندلر استفاده میکنن، workbox از پکیجی استفاده میکنه به اسم workbox-webpack-plugin. این پکیج از دو پلاگین با نام های GenerateSW و InjectManifest ساخته شده که به وبپک اضافه میشن و ساخت سرویس ورکر رو به یکی از مراحل پروسه بیلد تبدیل میکنن. CRA به طور پیش فرض از پلاگین InjectManifest استفاده میکنه.اگر مقایسه بین این دو پلاگین و کاربرد هرکدوم رو بخونید به این لینک مراجعه کنید.پلاگین InjectManifest ?به صورت یک پلاگین به وب‌پک اضافه میشه و از ما انتظار داره که یک فایل بهش معرفی کنیم به عنوان سرویس ورکر (که این فایل به طور پیش‌فرض service-worker.js هست در تمپلیت pwa). سپس در مرحله بیلد پروژه، لیست فایلهای تولید شده در پروسه بیلد رو به فایل سرویس‌ورکر که از قبل بهش اعلام کردیم، تزریق (inject) میکنه. لیست فایل‌های ایجاد شده توسط وب‌پک در مرحله build رو میتونید از طریق پوشه build و درون فایل asset-manifest.json مشاهده کنید. تصویر زیر نمونه‌ای از این فایل رو به نمایش گذاشته: این لیست توسط پلاگین InjectManifest به داخل فایل‌ سرویس‌ورکر تزریق میشه و ما درون این فایل از طریق self.__WB_MANIFEST به این لیست دسترسی داریم. از همین مقدار به منظور شناسایی فایل‌های مورد نیاز برای pre-cache توسط سرویس‌ورکر استفاده میشه.  فایل‌های که پسوند map دارن، در این فرآیند نادیده گرفته میشن.هر بار که تغییری در فایلهای پروژه به وجود بیاد، هش چانک‌ها در زمان بیلد تغییر میکنه و همین امر باعث میشه که لیستی که InjectManifest به داخل سرویس‌ورکر تزریق میکنه عوض بشه و درنتیجه منجر به این میشه که بروزر متوجه اپدیت بشه و اقدام به نصب یک سرویس‌ورکر جدید کنه.اگر تصویر بالا رو به دقت نگاه کنید مشاهده خواهید کرد که دو فایل index.html و service-worker.js نیز در داخل لیست فایلهای خروجی وب پک وجود داره و در نتیجه از طریق injectManifest به داخل فایل سرویس‌ورکر  تزریق میشه و در نتیجه کش خواهد شد. اما برخلاف چانک‌های برنامه نام این دو فایل به صورت هش شده نیست. پس ممکنه این سوال پیش بیاد که سرویس ورکر از کجا متوجه تغییرات این دو فایل میشه؟گفتیم که پلاگین injectManifest آرایه‌ای از فایلهای تولید شده از مرحله بیلد رو به داخل فایل سرویس‌ورکر تزریق میکنه. این آرایه از ابجکت تشکیل شده که یک url دارن و یک revision. فایل‌هایی مثل index.html محتویات داخلشون هش میشه و داخل مقدار revision قرار میگیره. از این طریق تغییرات این فایل‌ها میتونه شناسایی بشه.  باقی فایل‌ها اطلاعات مربوط به revision اشون داخل url لحاظ شده.توجه داشته باشید که منطق و لاجیک pre-caching به طور کامل توسط پکیج ورک باکس پیاده سازی شده. وظیفه ما صرفا این هست که فایل‌های تولید شده در مرحله بیلد رو که نیاز به کش شدن هست رو بهش معرفی کنیم که این کار از طریق پاس دادن متغیر self.__WB_MANIFEST به تابع precacheAndRoute انجام میشه.پیاده‌سازی pre-cache توسط پکیج workboxبرای آشنایی بیشتر با متد precacheAndRoute  و دیگر امکانتش میتونید به این لینک رجوع کنید.مفهوم route-cachingیک سرویس‌ورکر به عنوان واسط بین کلاینت و سرور قرار میگیره و تمامی درخواست‌ها و پاسخ‌هایی رو که بین این دو رد و بدل میشه رو میتونه رصد کنه و مداخله کنه. زمانی که یک network request فرستاده میشه، یک fetch event ارسال میشه. ما میتونیم سرویس‌ورکرمون رو طوری برنامه‌ریزی کنیم که درخواست‌های ارسالی رو رصد کنه و در صورتی که درخواست ما برای دریافت یک source (مثلا یک عکس) بود، پاسخ دریافتی رو بگیره و  درون کش ذخیره کنه. پیاده‌سازی این مفاهیم از ابتدا توسط جاوااسکریپت چالش خاص خودش رو داره و بسیاری موارد هست که باید مدنظر قرار گرفته بشه، از جمله اینکه منابع با چه استراتژی ذخیره بشن، چه استراتژی برای جلوگیری از افزایش حجم کش در نظر گرفته بشه و ... . خوشبختانه ورک‌باکس در این زمینه هم به کمک ما میاد؛ تابعی داره به اسم registerRoute که در داخل پکیج workbox-routing وجود داره. یک route در ورک‌باکس از دو تابع تشکیل شده. تابع matching و تابع handling. درون تابع matching به url، request و پارامترهای ارسالی دسترسی داریم و از این طریق میتونی بررسی کنیم که آیا این درخواست مد نظر ما هست یا خیر. در صورتی که تابع matching مقدار true رو برگردونه، یعنی match اتفاق افتاده و تابع handler فراخوانی میشه. نکته مهم: تنها محدودیتی که برای matching وجود داره این هست که باید حتما synchronous باشه و هیچ کار asynchronous نمیتونیم داخلش انجام بدیم.درون تابع هندلر میتونیم تصمیم بگیریم که با request دریافت شده چه کار کنیم. به طور مثال از ارسال اون جلوگیری کنیم، مقداری رو به هدر درخواست اضافه کنیم و یا منتظر جواب بمونیم و جواب رو داخل کش ذخیره کنیم و ... . این امکان رو داریم که لاجیکش رو خودمون بنویسیم و یا  میتونیم از استراتژی‌های از پیش نوشته شده ورک‌باکس استفاده کنیم، این استراتژی‌ها سناریو‌های رایجی رو که مورد استفاده قرار میگیرن رو پیاده‌سازی کردن و ما صرفا با ساخت یک instance از کلاس مربروطه‌اشون ازشون استفاده میکنیم به جای تابع هندلر. این استراتژی ‌ها درون پکیج workbox-strategies پیاده‌سازی شدن.حال که با تابع registerRoute در ورکباکس تا حدودی آشنا شدیم، میتونید نحوه استفاده از این تابع رو در تمپلیت pwa در تصویر زیر مشاهده کنید.همونطور که در تصویر مشخص هست درون تابع matching بررسی شده که اگر url که بهش درخواست ارسال میشه با url فعلی ما برابر هست و پسوند فایل درخواستی برابر با png هست، در این صورت تابع هندلر اجرا بشه. برای تابع هندلر نیز از یکی از استراتژی‌های از ورک‌باکس استفاده شده به نام StaleWhileRevalidate و برای کانفیگ ابجکتی بهش پاس داده و نام کش رو تعیین کرده و برای کش محدودیت تعداد 50 رو قرار داده.رجیستر کردن message event ?آخرین اقدامی که در تمپلیت pwa برای عملکرد سرویس‌ورکر در نظر گرفتن، رجیستر کردن message event هست. سرویس ورکر چون در ترد مجزایی از ترد اصلی برنامه اجرا میشه، تنها از طریق ارسال message میتونه با ترد اصلی ارتباط برقرار کنه. ارسال پیام از طریق متد postMessage صورت میگیره. با ارسال پیام یک massage event تریگر میشه به همین دلیل در سمت سرویس‌ورکر یک event listener رجیستر شده تا به eventهای فراخوانی شده message واکنش نشون بده و هندلرش رو اجرا کنه. درون هندلر چک شده که اگر مسیج از نوع SKIP_WAITING بود، متد skipWaiting رو فراخوانی میکنه.به سوی بینهایت و فراتر از آن ?آنچه که تا اینجا گفته شده قابلیت‌هایی بود که به صورت پیش‌فرض توسط تمپلیت pwa پیاده سازی شده بود و در اختیار ما قرار گرفته بود، و این تنها نقطه ای برای شروع هست. ممکنه این قابلیت‌ها برای پروژه‌های کوچک و شاید متوسط کافی باشه ولی در پروژه‌های بزرگتر ما نیاز داریم تا علمکردها رو متناسب با نیازمون تغییر بدیم. میتونیم هر عملکردی دیگری رو که مدنظرمون هست داخل فایل service-worker.js اضافه کنیم. این پیاده‌سازی میتونه با استفاده از جاوااسکریپ و به وسیله توابع built-in صورت بگیره (که توصیه نمیشه) و هم میتونه به کمک پکیج workbox انجام بشه. در اینجا تنها محدودیتی که میتونه وجود داشته باشه، خود شما و خلاقیت شماست. نتیجه‌گیریهمونطور که دیدیم، استفاده از تمپلیت pwa به ما کمک میکنه تا بدون یک خط کد زدن و به ساده‌ترین شکل ممکن یک سرویس‌ورکر ایجاد کنیم که قابلیت offline-first رو برای برنامه ما به ارمغان میاره. اما در کنار این مزیت نباید از معایب و چالش‌های همراهش غافل بود. اگر ما از اتفاقاتی که در داخل این تمپلیت میوفته خبر نداشته باشیم نمیتونیم به درستی ازش استفاده کنیم. همچنین دیدیم که این تمپلیت تنها یک boilerplate برای ما ایجاد میکنه و ما باید برحسب نیاز خودمون اون رو توسعه بدیم و تغییراتی رو که مد نظرمون هست اعمال کنیم. در نتیجه آگاهی بیشتر نسبت به فرآیند داخلی این تمپلیت میتونه کار رو برای ما راحت‌تر  کنه.در این مقاله سعی کردیم تمپلیت pwa از CRA رو مورد بررسی قرار بدیم و با جزئیات شرح دادیم که وقتی از طریق این تمپلیت اقدام به ساخت یک پروژه react می‌کنیم،در واقع چه اتفاقاتی در پشت صحنه رخ میده، چطور یک سرویس‌ورکر برامون نصب میشه و این سرویس‌ورکر چه امکاناتی در اختیار ما قرار میده. امیدوارم که این مقاله تونسته باشه درک عمیق‌تر و درست‌تری از رفتار سرویس‌ورکر ارئه شده توسط cra بهتون داده باشه.  ♥منابع https://create-react-app.dev/docs/making-a-progressive-web-app/  https://developers.google.com/web/tools/workbox/guides/get-started  https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle </description>
                <category>اسنپ</category>
                <author>مانی صفارنیا</author>
                <pubDate>Mon, 09 May 2022 13:37:59 +0430</pubDate>
            </item>
                    <item>
                <title>تاثیر CI/CD در تیم اندروید اسنپ</title>
                <link>https://virgool.io/snapp-eng/%D8%AA%D8%A7%D8%AB%DB%8C%D8%B1-cicd-%D8%AF%D8%B1-%D8%AA%DB%8C%D9%85-%D8%A7%D9%86%D8%AF%D8%B1%D9%88%DB%8C%D8%AF-%D8%A7%D8%B3%D9%86%D9%BE-wdcxqnnljynh</link>
                <description>https://unsplash.com/photos/U3sOwViXhkYبرای بهبود پیوسته و همچنین کاهش هدر رفت زمان برنامه‌نویسان راه‌های مختلفی وجود دارد که یکی از آن راه‌ها استفاده از CI/CD می‌باشد.اجازه دهید در ابتدا تعریف CI/CD که در ویکیپدیا منتشر شده است را مرور کنیم.در مهندسی نرم‌افزار به‌طور کلی به مجموعه اعمال یکپارچه‌سازی مداوم و تحویل پیوسته یا استقرار پیوسته، CICD یا CI/CD می‌گویند. CI/CD وادار می‌کند که فرآیندهای ساخت، تست و استقرار برنامه‌ها به صورت خودکار انجام شوند. بدین وسیله پلی بین فعالیت‌های تیم‌های «توسعهٔ نرم‌افزار Development» و «عملیات فناوری اطلاعات Operations» ایجاد می‌شود. فعالیت‌های DevOps شامل توسعه پیوسته، آزمون پیوسته، یکپارچه‌سازی مداوم، استقرار پیوسته و نظارت مداوم بر نرم‌افزار در طول فرایند توسعه می‌باشد. اقدامات CI/CD زیربنای فعالیت‌های دواپس را تشکیل می‌دهد.علاوه بر موارد ذکر شده از دیگر دستاوردهای مهم CI/CD در تیم اندروید اسنپ٬ بهبود تجربه برنامه‌نویسان از توسعه نرم افزار بوده است.برای توضیح اینکه چطور و چگونه این دستاورد حاصل شد لازم است که اندکی درباره فرآیند فعلی توسعه تا انتشار در اسنپ بدون در نظر گرفتن CI/CD توضیح دهم و پس از آن نقش CI/CD در کاهش این زمان را مشخص کنم.این مقاله شامل دو بخش است، بخش اول توضیح خلاصه‌ای از نحوه پیاده سازی CI/CD در تیم اندروید اسنپ می‌باشد و بخش دوم شامل تاثیرات مثبت و منفی CI/CD بر عملکرد تیم است. در نتیجه اگر فقط به دنبال تاثیر CI/CD بر یک تیم نرم افزاری هستید می‌توانید تنها بخش دوم مقاله را دنبال کنید.بخش اول: تو بساز تا من بسازم.درباره CI/CD و اهمیت آن در صدها صفحه مختلف در اینترنت صحبت شده است و به همین دلیل درباره جزئیات دقیق و مرحله به مرحله پیاده سازی در اینجا صحبت نمی‌کنیم. ما در تیم اندروید اسنپ از Gitlab برای مدیریت کدها استفاده می کنیم. طبیعتا اولین و بهترین گزینه برای ما Gitlab CI است که توسط اپلیکیشن نصب شده روی سرور همراه با کمک تیم devops تنظیم و راه اندازی شده است. استفاده از این سرویس روی سرورهای اسنپ که در ایران میزبانی می‌شوند برای ما مزایای بسیاری دارد و خیالمان از بابت نگهداری و در دسترس بودن سرویس همیشه راحت است.برای شروع ما بر اساس ubuntu 20.04 برای اندروید یک custom Image - با توجه به اینکه باید docker image برای Gitlab استفاده کنیم - ساختیم و در آن محیط، jdk و بقیه sdk اندروید را دانلود و نصب کردیم.در شروع از این image و image استفاده کردیم ولی در نهایت ما نسخه کنترل‌پذیرتری نیاز داشتیم؛ به دلایلی که در ادامه به آنها اشاره خواهم کرد.استفاده از dexguardدر اپلیکیشن های اندرویدی استفاده از proguard ابزاری مرسوم برای مبهم سازی کد به منظور دشوار کردن مهندسی معکوس و همچنین بهینه سازی نسخه نهایی برای انتشار در store‌ های نرم‌افزاری می‌باشد. البته که proguard ابزاری بسیار خوب برای اهداف ذکر شده است ولی هدف نهایی تولید این ابزار بهینه‌سازی فایل apk نهایی بوده است و خُب شرکت تولید کننده ابزاری کامل‌تر جهت تامین اهداف امنیتی نرم‌افزار به نام dexguard را ارائه داده است که همزمان با R8 و proguard میتواند استفاده شود. برای اینکه بتوانیم از dexguard استفاده کنیم نسخه‌ای از sdk را به image انتقال دادیم تا مشکل build با dexguard حل شود و در زمان صرفه جویی شود. لایسنس‌ها را هم در env variable گذاشتیم و به این شکل برای هر پروژه میتوانیم از لایسنس خودش استفاده کنیم. در نهایت کافیست در فایل yml که برای ci درست کردیم این ۲ خط را به before script اضافه کنیم تا خیالمان از آماده بودن dexguard راحت شود.Before_script:
- echo &amp;quotdexguard_sdk_path=/dexGuard/lib&amp;quot &gt;&gt; gradle.properties
- echo &amp;quotsystemProp.dexguard.license=dexguard-license.txt&amp;quot &gt;&gt; gradle.propertiesاسکریپت کمکی!برای تسهیل در فرآیند آپلود apk نهایی٬ ارسال ایمیل/پیام‌های اطلاع رسانی و کارهای شبیه به این مورد یک سری اسکریپت نوشتیم. این اسکریپت‌ها وظایف مختلفی را انجام میدهند، از جمله این که اسم فایل apk یا aab را برای ما بر اساس ساختارهای توافق شده تیم مرتب میکنند و یا فایل‌ را روی Minio اپلود می‌کنند که در ادامه توضیح خواهم داد و یا اینکه در پیام رسان داخلی شرکت ‏(element) اطلاع رسانی می‌کنند.البته درست است که قابلیت آپلود روی object storage هایی شبیه S3 بعد از پیاده‌سازی ما در اسنپ به Gitlab اضافه شد اما ما با اسکریپت‌هایی که آماده کرده بودیم مشکلی نداشتیم و همچنان هم از اسکریپت خودمان استفاده میکنیم.- python3 /deployscript/deploy.py
--release.dir=app/build/outputs/apk/Dev/release
--app.name=Driver
--riot.hook=&amp;quot$RIOT_HOOK_ADDRESS&amp;quot
--riot.msg=&amp;quot$RIOT_MSG&amp;quot
--minio.url=&amp;quot$MINIO_URL
--minio.accessKey=&amp;quot$MINIO_ACCESSKEY&amp;quot
--minio.secretKey=&amp;quot$MINIO_SECRETKEY&amp;quot
--minio.bucket=&amp;quotdriver&amp;quot
--app.desc=&amp;quot$CI_COMMIT_MESSAGE&amp;quot
--app.branch=&amp;quot$CI_COMMIT_REF_NAME&amp;quot
--app.flavor=&amp;quotDev&amp;quotذخیره فایلامکان اینکه فایلهای نهایی را به عنوان Artifact در Gitlab ذخیره کرد وجود دارد، اما دسترسی به فایل و همچنین سرعت دانلود فایل مورد توافق تیم نبود. در نتیجه ما از Minio استفاده کردیم. برای هر اپلیکیشن bucket جدا تعریف کردیم و build های مربوط به flavor های مختلف در پوشه‌های مختلفی با timestamp خودشان ذخیره میشوند. مثلا فایل آپلود شده روی Minio به اسم زیر در باکت driver ذخیره می‌شود.تصویر۱: نمونه اسم فایل ذخیر شده بر روی سرورنگهداریبرای اینکه بتوانیم تکه های مختلف CI/CD را بهتر مدیریت کنیم، ایمیج‌ها را بر اساس نسخه dexguard تگ گذاری کردیم. همچنین برای سهولت در فرآیند تولید image جدید، ابتدا یک image اندروید ساخته شده است و image حاوی dexguard و همچنین اسکریپت‌های نهایی بر اساس image پایه اندروید ساخته می‌شود. به این شکل هر زمان نیاز بود که نسخه جدیدی از dexguard را پشتیبانی کنیم دیگر نیازی نیست که کل image اندروید از ابتدا ساخته شود.تصویر۲: نمونه image هایی که بر روی سرور وجود داردبخش دوم: کد! کد! تا پیروزیبعد از اضافه شدن CI/CD به پروژه‌ها بهبودهایی حاصل شد که در این قسمت به این موارد اشاره خواهم کرد. نیازهای مربوط به کار تیمی ما در زمان‌های مختلف راه حل‌های مختلفی داشتند که اکنون CI/CD با روش پیاده‌سازی شده فعلی تغییر مثبتی در آنها داشته است و performance تیم را بهبود خوبی داده است.حل نقطه گلوگاهی توسعهفرآیندی که برای توسعه یک feature جدید در اسنپ و احتمالا خیلی از شرکت‌های دیگر طی میشود به این شکل است:دریافت نیازمندی‌ها و مشخصات فیچر (spec) از تیم پروداکت و طراحی‌های مربوطه از تیم دیزاینبرگزاری جلسات discovery / solution designبرنامه‌ریزی (planning) اسپرینت پیش‌روشروع پیاده‌سازی و تحویل نمونه اولیه (release candidate)در این مراحل بخش گلوگاهی که ما با CI/CD در آن بهبود دادیم بخش چهارم بوده است. شکل زیر مرحله ۴ را به صورت شماتیک نشان میدهد:تصویر ۳: جریان کاری در پیاده سازی و تست یک featureدر مراحلی که در بالا مشخص شد برای تیم ما رساندن فایل apk که شامل دو بخش build گرفتن و همچنین آپلود apk برای تیم QA است در بعضی روزها بر اساس شرایط اینترنت به شدت خسته کننده و وقت گیر بوده است. همچنین در این بخش تغییرات ساده٬ زمان متفاوتی با تغییرات اصلی ندارند. مثلاً تغییر یک متن شامل دوباره build گرفتن و همچنین آپلود دوباره فایل میشود که برابر با زمان build و آپلود یک قابلیت اساسی و بزرگ است. ما به کمک CI/CD به طور محسوسی در زمان و انرژی که برنامه‌نویس در این بخش از دست می‌داد صرفه جویی کردیم. بعد از هر تغییری که نیاز به رساندن build جدید به تیم QA باشد کافیست برنامه‌نویس بر روی پنل Gitlab درخواست build دهد و از اینجا به بعد میتواند به ادامه کارهای توسعه بپردازد و رساندن APK نهایی را به CI/CD واگذار کند. Apk به طور کامل بر روی سرور ساخته و امضا (sign) شده و در نهایت بر روی ObjectStorage تیم اندروید آپلود می‌شود. پس از آن در گروه مشخصی پیامی حاوی لینک دانلود همراه با مشخصات branch و flavor و همچنین آخرین کامیت ارسال می‌شود. تیم QA از اینجا به بعد به راحتی میتواند Apk را دانلود و فرآیند QA را شروع کند. شکل زیر نمونه‌ای از پیام ارسال شده در گروه release است:تصویر ۵: نمونه پیام ارسال شده در elements برای تیماینکار همچنین یکپارچگی را بر روی سیستم ایجاد کرده است به طوری که Apk‌ ها در یک فضای مشخص جمع آوری شده‌اند و دسترسی به آخرین build یک branch ساده‌تر و سریعتر شده است. همچنین نیاز به اطلاع رسانی دوباره به تیم QA وجود ندارد و هر زمان برنچ آماده تست شود تیم QA به طور خودکار متوجه نسخه (build) جدید می‌شود و نهایتا اتلاف زمانی که نقطه گلوگاهی به خود اختصاص می‌داد به طور محسوسی کاهش پیدا می‌کند.حل مشکل کتابخانه‌هادر اسنپ کتابخانه‌ها و SDK‌ های درون سازمانی بر روی مخزنی جداگانه نگهداری می‌شوند. تا قبل از استفاده از CI/CD فرآیند انتشار روی مخزن باید به صورت دستی و توسط owner آن پروژه انجام می‌شد، اما پس از استفاده از CI/CD و انتشار خودکار، نسخه‌ای که روی برنچ main قرار می‌گیرد حالا دیگر نسخه منتشر شده و آخرین کد قرار گرفته بر روی مخازن با هم یکی هستند و احتمال خطای انسانی کاهش یافته است. همچنین CI/CD قبل از انتشار با اجرا کردن تست‌ها جلوی انتشار کدی که تست‌ها را pass نکرده است میگیرد. در کنار این موارد ما میتونیم به code coverage هم دسترسی داشته باشیم و طبیعتا در صورت کاهش coverage اقدامات آتی را انجام دهیم.تصویر۶: وضعیت code coverage در پروژه network module تصویر۷: پایپلاین بیلد برای کتابخانه UI-KIT بیلد پروداکشنزمانی که نیاز به نسخه پروداکشن برای مارکت‌های اندرویدی باشد طبیعتا env‌ ها و متغیر‌های بیلد دستخوش تغییراتی می‌شوند که توضیح آنها خارج از حوصله این متن است ولی در گذشته ما داکیومنتی مرحله به مرحله برای آماده سازی نسخه‌های مختلف داشتیم ولی حالا به کمک CI/CD نسخه‌های مختلف برای env‌های مختلف بر روی سرور آماده میشود. به این روش احتمال خطای انسانی در فرآیند انتشار کاهش چشمگیری خواهد داشت و همچنین زمان آماده‌سازی نسخه هم به همین شکل کاهش خوبی داشته است. دیگر اینکه همیشه آماده سازی نسخه نهایی وابسته به یک یا چند نفر بود که با فرآیند‌ها آشنایی داشتند و همچنین به env‌های خاص پروداکشن دسترسی داشتند اما حالا به کمک CI/CD این وابستگی‌ها برداشته شده و وظیفه تولید نسخه نهایی در حیطه کاری سرور است.تصویر۸: ساخت نسخه‌های مختلف بر اساس flavorکمک به کاهش خطاهای انسانیفرایند خودکار سازی اجرای تست‌ها و همچنین build پروژه پیش از merge کردن یک PR در Gitlab به کمک CI/CD کمک شایانی به جلوگیری از مشکلات بعد از merge دارد. قبل از این ممکن بود به‌روزرسانی یک کتابخانه که API جدیدی دارد باعث build نشدن پروژه به طور کلی شود ولی پس از CI/CD ما حتی قبل از merge از تغییرات code coverage هم مطمئن میشویم.تصویر۹: نمایش وضعیت تست‌ها برای هر PR در Gitlabانجام Static Analysisاگر سورس کد یا binary یک اپلیکیشن را بدون اجرا کردن آن و بدون در نظر گرفتن محیط اجرای برنامه بررسی کنیم در حقیقت static-analysis را انجام داده‌ایم. با استفاده از ابزارهای رایج static analysis می‌توان بررسی کد را قبل از مرج کردن یک PR یا بیلد شدن نهایی پروژه انجام داد. با این روش همیشه مطمئن می‌شویم استانداردهایی که برای کدبیس خود در نظر گرفته‌ایم رعایت میشوند و در نهایت تیم خوشحال‌تر و مطئمن‌تر به کارش ادامه میدهد. ابزارهای آماده و رایگان زیادی برای هر بخشی از کد وجود دارد به عنوان مثال می‌توان به Detekt اشاره کرد. نکته مهم در این ابزارها اینست که به طور پیوسته از صحت و تطابق کد با استانداردهای مشخص شده مطمئن شویم. در این زمینه نیز CI/CD به ما کمک می‌کند که این از انجام static analysis پیش از merge کردن branch جدید مطمئن شویم.انجام Security Analysisدرست مثل بخش قبلی انجام تسک‌های مربوط به چک لیست‌های امنیتی هم که توسط تیم امنیت انجام می‌شود می‌تواند تا حدی خودکار سازی شود یا حداقل به شروع فرآیندهای تیم امنیت توسط CI/CD سرعت داده شود. یکی از ابزارهای پیشنهادی تیم امنیت اسنپ برای بررسی کدبیس mobsf است. Mobsf یک چارچوب pen-testing کامل هست که به انجام خودکار static-analysis و dynamic-analysis و همچنین malware-analysis می‌پردازد. در اسنپ نسخه نهایی پیش از انتشار توسط CI/CD برای سرور mobsf تیم امنیت ارسال میشود تا بررسی‌های اولیه تیم امنیت بتواند راحت‌تر و سریعتر شروع شود.جمع‌بندیبا اینکه CI/CD تاثیر بسیار مطلوبی بر روی عملکرد تیم دارد ولی در نهایت این تصمیم شماست که بر اساس تیم خودتان٬ نوع پروژه و همچنین میزان منابع در دسترس تصمیم به استفاده از آن بگیرید. سرویس‌های زیادی سعی در حل مشکلات نگهداری CI/CD کردند که طبیعتا اگر پروژه شما بزرگتر از حد تعیین شده باشد یا اینکه close source باشید احتمالا گزینه رایگانی در اختیار نداشته باشید. به هر حال به روز نگهداشتن image‌ی که در ابتدا در موردش صحبت کردیم و همچنین نگهداری از runner‌هایی که قرار است نقش اجرا کننده دستورات را داشته باشند هزینه‌بر و زمان‌بر خواهد بود. پس پیش از اینکه به سمت این راهکار برای حل برخی از مشکلات تیم بروید حتما به فکر روش‌های نگهداری از سیستم CI/CD خود باشید تا در آینده همین CI/CD برای شما دست و پاگیر نباشد.</description>
                <category>اسنپ</category>
                <author>علی عبداللهی</author>
                <pubDate>Sun, 08 May 2022 18:44:30 +0430</pubDate>
            </item>
                    <item>
                <title>ارتباط HorizontalPodAutoscaler و تعیین تعداد پادها به صورت دستی در کوبرنتیس</title>
                <link>https://virgool.io/snapp-eng/%D8%A7%D8%B1%D8%AA%D8%A8%D8%A7%D8%B7-horizontalpodautoscaler-%D9%88-%D8%AA%D8%B9%DB%8C%DB%8C%D9%86-%D8%AA%D8%B9%D8%AF%D8%A7%D8%AF-%D9%BE%D8%A7%D8%AF%D9%87%D8%A7-%D8%A8%D9%87-%D8%B5%D9%88%D8%B1%D8%AA-%D8%AF%D8%B3%D8%AA%DB%8C-%D8%AF%D8%B1-%DA%A9%D9%88%D8%A8%D8%B1%D9%86%D8%AA%DB%8C%D8%B3-uisbdpmsnfjw</link>
                <description>   در اسنپ ده‌ها میکروسرویس با همکاری هم در حال سرویس دادن به میلیون‌ها کاربر راننده و مسافر هستند.  اکثر این میکروسرویس‌ها در یک کلود بر پایه کوبرنتیس که توسط تیم متخصص آن توسعه و نگه‌داری می‌شود در حال اجرا می‌باشند. دسترسی‌پذیر بودن این میکروسرویس‌ها در تمام طول روز شرط اصلی عملکرد صحیح اسنپ و برقراری سفر‌ها می‌باشد.    یکی از روش‌های افزایش دسترسی‌پذیری٬ تغییر مقایس به صورت افقی (Horizontal Scaling) می‌باشد. به طور خلاصه تغییر مقیاس به صورت افقی یعنی در صورت افزایش بار بر روی سیستم تعداد بیشتری از برنامه را برای پاسخ به آن بار آماده کنیم. همچنین در صورت کاهش بار بر روی سیستم عمل برعکس را انجام دهیم. در کوبرنتیس برای دستیابی به این مفهوم می‌توانیم از HorizontalPodAutoscaler استفاده کنیم. با استفاده از HPA می‌توانیم یک متریک مشخص را انتخاب کنیم (مثلا میزان استفاده از پردازنده) و اجازه دهیم با تغییر این متریک تعداد پادهای اجراکننده برنامه نیز به صورت خودکار تغییر کند. همچنین در کوبرنتیس ما می‌توانیم در فایل‌های کانفیگ حاوی manifest خود تعداد مد نظر پادها را مشخص کنیم. در این جا ممکن است برایتان سوال شود که تعداد پادی که HPA تعیین کرده و تعداد پادی که در فایل کانفیگ مشخص می‌کنیم چگونه با هم مرتبط هستند. در حقیقت بی‌توجهی به این سوال ممکن است منجر به عملکرد‌های ناخواسته و بسیار خطرناکی شود. در این نوشته سعی می‌کنیم این موضوع مهم را که اشتباه در آن بسیار متداول است در چند سناریو بررسی کنیم.سناریو اول   بیایید یک سناریو محتمل را بررسی کنیم. فرض کنید می‌خواهیم اپلیکیشن خود را با استفاده از یک deployment در کوبرنتیس دیپلوی کنیم. یک فایل ابتدایی مشخص کننده deployment ما می‌تواند به این صورت باشد:این فایل را در deployment.yaml ذخیره کرده و با این دستور اعمال می‌کنیم:kubectl apply -f deployment.yamlبا توجه به فیلد spec.replicas دو پاد از این اپلیکیشن درست شده و به حالت اجرا در می‌آیند. بعد از مدتی که بار روی اپلیکیشن زیاد شد تصمیم می‌گیریم که با استفاده از HPA اجازه دهیم که تعداد پادها با توجه به بار روی آن زیاد و کم شوند. برای مثال دستور زیر را در نظر بگیرید:kubectl autoscale deployment busybox --cpu-percent=70 --min=3 --max=20با استفاده از این دستور یک HPA می‌سازیم که با توجه به میزان استفاده از پردازنده تعداد پادها را بین ۳ تا ۲۰ عدد عوض می‌کند. تا این جا همه چیز مرتب است و HPA تعداد پادها را کنترل می‌کند. حالا فرض کنید در یک بعد از ظهر که تعداد پادها با توجه لود ۸ عدد شده تصمیم می‌گیریم ورژن جدید اپلیکیشن را دیپلوی کنیم. (در این متن ورژن جدید را در کامندی که echo می‌شود شبیه سازی کردیم). با توجه به استراتژی پیشفرض دیپلوی انتظار داریم که یک rolling update روی پادها انجام شود. یعنی با توجه به مقادیر maxUnavailable و maxSurge یک ReplicaSet جدید ساخته شده و پادهای جدید در آن ساخته شوند و همزمان پادهای ReplicaSet قدیمی متناظر با آن کم شوند. با این استراتژی دیپلوی ما یک دیپلوی بدون down time خواهد بود و اختلالی از سمت کاربران دیده نمی‌شود.تغییرات را با این دستور اعمال می‌کنیم. توجه داشته باشید که تغییر در spec.template باعث شروع یک rollout جدید می‌شود:kubectl apply -f deployment.yamlبعد از اجرای این دستور به جای آپدیت پله به پله‌ای که انتظار داشتیم به یک باره ۶ پاد از ۸ پادی که مشغول پردازش بار موجود بودند terminate شده و سپس ReplicaSet جدید با یک پاد ساخته شده و rolling updateی که انتظار داشتیم روی ۸ پاد انجام شود روی همان ۲ پاد انجام می‌شود. در نهایت بعد از مدتی دوباره HPA تعداد پادهای ReplicaSet جدید را با توجه به لود به ۸ می‌رساند. اتفاقی که افتاد باعث شد تا در مدتی٬ تمام باری که ۸ پاد تحمل می‌کردند به یک باره به ۲ پاد برسد. احتمال این که این ۲ پاد نتوانند این بار را تحمل کنتد و خود نیز از حالت running در بیایند کم نیست و این به معنی یک downtime کامل است!   برای این که مشکل پیش آمده را درک کنیم لازم است تا ابتدا درک درستی از نحوه اعمال کانفیگ‌ها (در این جا با دستور kubectl apply) داشته باشیم. در این روش ما یک فایل حاوی کانفیگ مد نظرمان را تعریف کرده و آن را با دستور kubectl apply اعمال می‌کنیم. این دستور تغییرات مد نظر ما را روی کانفیگ فعلی (live configuration) اعمال می‌کند. به همین دلیل به این روش declarative نیز می‌گویند. بعد از زدن دستور kubectl apply یک درخواست patch به سمت سرور فرستاده شده و در آن فیلدهایی که باید تغییر یابند مشخص می‌شوند. در این سناریو در فایلی که برای ورژن جدید مشخص کردیم مقدار فیلد spec.replicas را که HPA به ۸ تغییر داده بود به ۲ تغییر دادیم و در این صورت اتفاقی که پیش آمد امری بدیهی بود.سناریو دوم   برای حل این مشکل لازم است تا مقدار spec.replicas را که کنترل آن را به HPA دادیم مشخص نکنیم. برای همین تصمیم می‌گیریم تا در دیپلوی بعد این فیلد را کانفیگ حذف کنیم. در این صورت انتظار داریم که rolling update به صورت معقول انجام شود. فایل بعدی به این شکل خواهد بود:بعد از اعمال این فایل در کمال ناباوری همه پادها به جز یک پاد پاک شده و سپس آپدیت انجام می‌شود. این اتفاق مشابه سناریو اول بود که می‌خواستیم از آن دوری کنیم.   برای این که دلیل این اتفاق را بفهمیم به نحوه عمل کردن دستور kubectl apply برمی‌گردیم. همان طور که قبلا اشاره کردیم دستور apply تغییرات مورد نیاز را از فایلی که به آن دادیم محاسبه و در تغییرات موجود بر روی سرور اعمال می‌کند. نکته مهم در این عمل نحوه محاسبه تغییرات توسط apply است. با هربار اعمال دستور apply کانفیگ اعمال شده در یک annotation به نام kubectl.kubernetes.io/last-applied-configuration ذخیره می‌شود. وقتی می‌خواهیم یک سری تنظیمات جدید را با apply اعمال کنیم محتویات فایل با محتویات kubectl.kubernetes.io/last-applied-configuration مقایسه می‌شوند و هر فیلدی که در کانفیگ جدید حذف شده باشد از کانفیگ موجود در سرور حذف می‌شود. به این علت که در این آپدیت سه مقدار کانفیگ فعلی٬ کانفیگ جدید موجود در فایل و کانفیگ موجود در kubectl.kubernetes.io/last-applied-configuration مورد مقایسه قرار می‌گیرند به این آپدیت یک ادغام سه‌گانه نیز می‌گویند. در سناریو قبل اتفاقی که افتاد این بود که فیلد spec.replicas در kubectl.kubernetes.io/last-applied-configuration موجود بود و در فایل جدید کانفیگ ما این فایل را حذف کرده بودیم. دستور apply با مقایسه این دو نتیجه گرفت که فیلد spec.replicas که مقدار آن را آخرین باز HPA تنظیم کرده بود باید پاک شود و در نتیجه مقدار پیش فرض آن یعنی ۱ اعمال شد.سناریو سومبا این حساب اگر دوباره همین تنظیمات را در دیپلوی بعدی اعمال کنیم باید شاهد نتیجه مد نظرمان باشیم:بعد از زدن دستور apply ‌می‌بینیم که دیگر مشکل قبلی پیش نیامد و با توجه به استراتژی آپدیت تعداد پادها در طول rolling update متعادل ماند. دلیل این اتفاق این است که در بار آخر در مقدار موجود در kubectl.kubernetes.io/last-applied-configuration فیلد spec.replicas وجود نداشت (در سناریو قبل حذفش کرده بودیم) برای همین در مقدار فعلی spec.replicas که توسط HPA پر شده بود تغییری ایجاد نشد. می‌توانیم از این به بعد مشکل را حل شده در نظر بگیریم چرا که با توجه حذف spec.replicas از فایل‌ کانفیگی که در دیپلوی‌ها اعمال می‌شود دیگر مشکل تعارض تعداد replicaی مشخص شده در فایل و HPA را نداریم. همچنین با توجه به این که فیلد spec.replicas در kubectl.kubernetes.io/last-applied-configuration نیز حذف شده دیگر تفاوت آن با فایل کانفیگ اعمالی موجب تک پاد شدن اپلیکیشن نمی‌شود.  اما این حل شدن به قیمت یک downtime احتمالی در سناریو دوم حل شد! می‌توانیم بدون این هزینه اضافه نیز این مشکل را حل کنیم. برای همین فرض کنید به قبل از اجرای سناریو دوم برگشتیم و می‌خواهیم بدون پیش آمدن مشکل فیلد spec.replicas را حذف و کنترل آن را به HPA واگذار کنیم.سناریو چهارم   حالا که نحوه عملکرد apply در حذف فیلدها را می‌دانیم می‌توانیم این مشکل را به راحتی حل کنیم. در سناریو دوم علت به وجود آمدن مشکل مغایرت بین مقدار kubectl.kubernetes.io/last-applied-configuration و فایل اعمالی در مقدار spec.replicas بود که منجر به حذف spec.replicas و استفاده از مقدار پیش فرض آن شد. برای جلوگیری از این اتفاق کافی است تا مقدار spec.replicas را علاوه بر از فایل کانفیگ از kubectl.kubernetes.io/last-applied-configuration  نیز حذف کنیم.برای این کار ساده‌ترین راه استفاده از کامند edit-last-applied در apply است.kubectl apply edit-last-applied deployment busyboxبا این دستور یک ادیتور باز شده و در آن خط مربوط به spec.replicas را پاک می‌کنیم. سپس فایل کانفیگ را اعمال می‌کنیم:نتیجه دقیقا چیزی است که مد نظر داشتیم. rolling update با حفظ تعداد پادها انجام شده و عملکرد برنامه دچار هیچ مشکلی نمی‌شود.در حقیقت ما می‌توانستیم با دستور diff نیز از تغییر پیش آمده در سناریو دوم قبل از اعمال مطلع شویم. فایل سناریو دوم را در نظر بگیرید:اگر بدون تغییر در kubectl.kubernetes.io/last-applied-configuration دستور diff رو روی این فایل انجام دهیم نتیجه به این شکل خواهد بود:kubectl diff -f deployment.yaml...
-  replicas: 8
+  replicas: 1
...
-     - echo v2 &amp;&amp; sleep 3600
+     - echo v3 &amp;&amp; sleep 3600
...اما در صورتی که مانند سناریو چهارم عمل کنیم نتیجه diff به این صورت خواهد بود:...
-     - echo v2 &amp;&amp; sleep 3600
+     - echo v3 &amp;&amp; sleep 3600
...در نهایت توانستیم به راحتی تعیین تعداد پادها را بدون هیچ مشکل اضافه به HPA واگذار کنیم.   همان طور که در ابتدای متن نیز اشاره کردم٬ اسنپ با دارا بودن یک کلود بزرگ خصوصی بر پایه کوبرنتیس و با وجود چند ده میکروسرویس که به میلیون‌ها درخواست سفر در طول روز پاسخ می‌دهند یک فضای جذاب برای مواجه شدن با چالش‌های به روز فنی است. اگر دوست داشتید که به این تیم با‌انگیزه ملحق شوید روزمه‌‌تان را به engineering@snapp.cab ارسال کنید.مراجع و مطالعات بیشتر:https://kubernetes.io/docs/tasks/manage-kubernetes-objects/declarative-config/https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscalehttps://github.com/kubernetes/kubernetes/issues/25238تولید عکس کدها با سرویس ray.so انجام شده است.</description>
                <category>اسنپ</category>
                <author>پارسا اسکندرنژاد</author>
                <pubDate>Sun, 17 Apr 2022 15:43:57 +0430</pubDate>
            </item>
                    <item>
                <title>داستان یک همکاری - بازطراحی صفحهٔ اصلی سوپراپلیکیشن اسنپ</title>
                <link>https://virgool.io/snapp-eng/%D8%AF%D8%A7%D8%B3%D8%AA%D8%A7%D9%86-%DB%8C%DA%A9-%D9%87%D9%85%DA%A9%D8%A7%D8%B1%DB%8C-%D8%A8%D8%A7%D8%B2%D8%B7%D8%B1%D8%A7%D8%AD%DB%8C-%D8%B5%D9%81%D8%AD%D9%87%D9%94-%D8%A7%D8%B5%D9%84%DB%8C-%D8%B3%D9%88%D9%BE%D8%B1%D8%A7%D9%BE%D9%84%DB%8C%DA%A9%DB%8C%D8%B4%D9%86-%D8%A7%D8%B3%D9%86%D9%BE-awkg6aww6edt</link>
                <description>اسنپ برای اولین بار سوپراپلیکیشن را با هدف ایجاد بازاری بزرگ‌تر برای کاربرانش به بازار ایران معرفی کرد. ایدهٔ اصلی سوپراپلیکیشن، فراهم کردن بستری برای دسترسی راحت‌تر و سریع‌تر به سرویس‌های موجود مانند سرویس درخواست خودرو، سفارش غذا و خرید بلیت سفر بود. به‌علاوه، سوپراپلیکیشن بستری برای معرفی چندین سرویس جدید را نیز فراهم می‌کرد.هدف اصلی از معرفی سوپراپلیکیشن، دسترسی کاربران به تمام سرویس‌ها در یک اپلیکیشن و بی‌نیاز کردن آن‌ها از نصب چندین اپلیکیشن برای برطرف کردن نیازهای روزانه‌شان بود. این مفهوم برای کاربران ایرانی بسیار تازه و ناآشنا بود. این چالش مهم‌ترین دغدغهٔ تیم سوپراپلیکیشن اسنپ و آغازی بر تغییرات در بازطراحی صفحهٔ اصلی سوپراپلیکیشن بود.صفحه‌ی Homepage قبل از بازطراحیبیان مسئلهدر بازطراحی صفحهٔ اصلی با چهار مسئله روبه‌رو بودیم:عدم‌استفاده بهینه از فضای موجود در صفحهٔ اصلی، مانعی برای ارائه و معرفی سرویس‌ها بود.محتوای غیرمرتبط روی صفحهٔ اصلی کاربران را سردرگم می‌کرد.طراحی صفحهٔ اصلی نمی‌توانست امکان شخصی‌سازی را برای کاربران فراهم کند.مهم‌ترین مسئله، تجربهٔ کاربر بود که با وجود سه مشکل قبلی نیاز به بهبود و ارتقا داشت.چه چالش‌هایی داشتیم؟مانند هر تغییر دیگری می‌دانستیم که مشکلات و چالش‌هایی وجود دارد. با مشاهدهٔ رفتار کاربران و بررسی‌ داده‌ها، مهم‌ترین چالش‌های‌مان را در فهرستی جمع‌آوری کردیم:کاربران از همهٔ سرویس‌های موجود در صفحهٔ اصلی سوپراپلیکشن استفاده نمی‌کردند.مدت زمان ماندگاری کاربران در صفحهٔ اصلی پایین بود و به همین دلیل تمام سرویس‌ها را نمی‌دیدند.کاربران ایرانی به‌خوبی با مفهوم و کارکرد سوپراپلیکیشن‌ها آشنا نبودند.کاربران متوجه قابلیت کلیک روی برخی از بخش‌های صفحهٔ اصلی نمی‌شدند.چالش‌های محصول از کجا شروع کردیم؟قبل از اینکه وارد فرایند بازطراحی شویم، روی سه مسئلهٔ اصلی متمرکز شدیم تا راه برای‌مان روشن‌تر شود.۱. به چه تغییراتی نیاز داشتیم و چرا می‌خواستیم این تغییرات را انجام دهیم؟با بررسی دوبارهٔ صفحهٔ اصلی سوپراپلیکیشن متوجه شدیم که می‌توانیم استفادهٔ بهینه‌ای از فضای موجود در سوپراپلیکیشن داشته باشیم. همچنین، محل قرارگیری بنرها مناسب نبود و محتوای آن‌ها هم نظر کاربران را جلب نمی‌کرد. این دو عامل باعث می‌شد که کاربران به بنرها تبلیغاتی توجه نکنند. یک بنر سبز در بالای صفحه قرار گرفته بود که فضای زیادی را اشغال می‌کرد و تأثیرگذاری لازم برای قرار گرفتن در بالاترین بخش صفحه را نداشت. درعین‌حال، طراحی این صفحه انعطاف‌پذیری لازم برای حذف این بنر را نداشت.بعضی از سرویس‌ها مانند اسنپ کلاب در بخش‌های مختلف صفحه قرار گرفته بودند و به همین دلیل نرخ بازدید این سرویس‌ها زیاد بود. این اتفاق روی انتخاب سرویس‌های دیگر توسط کاربران تأثیر می‌گذاشت.۲. محتوای غیرمرتبط روی صفحهٔ اصلی که باعث سردرگمی کاربران می‌شد چه چیزهایی بودند؟ما می‌خواستیم کاربر در راحت‌ترین حالت ممکن بتواند از سرویس‌های موجود استفاده کند؛ درعین‌حال باید به کاربر انگیزهٔ لازم را برای ماندن در صفحهٔ اصلی می‌دادیم تا پیشنهادهای اختصاصی و سرویس‌های جدیدمان را ببیند. علاوه‌ بر این، قصد داشتیم کاربران را نسبت به بنرهای تبلیغاتی روی صفحهٔ اصلی مشتاق کنیم تا بتوانیم علاوه بر درخواست خودرو ثبت سفارش از سرویس‌های دیگر سوپراپلیکیشن را افزایش دهیم. هیچ‌کدام از این اهداف با بنرهای ایستا وشخصی‌سازی‌نشده(Static Banners) امکان‌پذیر نبود.۳. چه راهکارهایی برای بهبود تجربهٔ کاربری روی صفحهٔ اصلی سوپراپلیکیشن وجود داشت؟در ابتدا، فهرستی از مشکلات موجود درست کردیم. سپس، با اولویت‌بندی، فهرست‌مان را کوتاه‌تر کردیم تا بدانیم از کجا باید شروع کنیم و چگونه باید تجربهٔ بهتری برای کاربرانمان بسازیم. اولین مشکل این بود که پالت رنگی استفاده‌شده روی صفحهٔ اصلی بسیار متنوع بود و این تنوع رنگی هنگام انتخاب سرویس موردنظر باعث حواس‌پرتی و سردرگمی‌ کاربران می‌شد.به‌علاوه، ارتباط معناداری بین اجزای مختلف صفحهٔ اصلی ایجاد نشده بود و نحوهٔ نمایش اطلاعات و محتوا به‌هم‌ریخته بود.این مسائل باعث شده بود که کاربران با همهٔ سرویس‌های ارائه‌شده در این صفحه آشنایی نداشته باشند.پروسهٔ ارزیابی - مراحل اولیهفرایند تیم محصول و بازطراحی برای تغییر ساختار صفحهٔ اصلی سوپراپلیکیشنشناخت جزئیات یک صفحهٔ اصلی منسجم و یک‌پارچه کمک می‌کرد که سرویس‌های‌مان را براساس اولویت و علاقهٔ کاربران و کسب‌وکار نمایش بدهیم.بنابراین، تصمیم گرفتیم با تغییر ساختار صفحهٔ اصلی و اضافه کردن کارت‌های پویا و شخصی‌سازی‌شده‌(Dynamic Cards) مطابق با شهر سکونت کاربر ساختار صفحهٔ اصلی را بهبود ببخشیم.مراحل پیموده شده در جهت این بازطراحی ۱- در دنیای سوپراپلیکشن‌ها چه می‌گذرددر فرایند تحقیقات برای بازطراحی صفحهٔ اصلی جدیدمان، در هر قدمی که برای فرایند بازطراحی برمی‌داشتیم بر راه‌حل‌هایی با رویکرد طراحی‌محور تمرکز می‌کردیم. مقالات منتشرشده در وبلاگ‌های کسب‌وکارهای مشابه را مطالعه و بررسی کردیم. سپس، راه‌حل‌هایی برای استفادهٔ بهینه از فضا در صفحهٔ اصلی پیدا کردیم.هم‌زمان برای اضافه کردن کارت‌های پویا به صفحهٔ اصلی سوپراپلیکیشن، راه‌حل‌ و استراتژی‌ اپلیکیشن‌های محبوبی مانند گوجک(Gojek)، گرب(Grab)، علی پی(Alipay) و وی‌چت(WeChat) را تجزیه و تحلیل کردیم تا بتوانیم راه‌حلی برای افزایش نرخ کلیک(CTR)، جی ام وی(GMV) و نرخ بازگشت کاربران پیدا کنیم. درعین‌حال، می‌خواستیم کاربرانی که درخواست خودرو می‌دادند را تشویق کنیم تا از سرویس‌های دیگر سوپراپلیکیشن اسنپ هم استفاده کنند.نمونه‌های خارجی سوپراپلیکیشن‌ها۲- ایده‌پردازی برای بازطراحی صفحهٔ اولدر این مرحله، فهیم و دانا از تیم محصول سوپراپلیکیشن به همراه هومن و احسان از تیم طراحی در جلسات دیزاین اسپرینت(Design Sprint) برای ساختار جدید صفحهٔ اصلی ایده‌پردازی کردند.در زیر بعضی از ایده‌های مطرح‌شده در این جلسات را می‌بینیم:در صفحهٔ اول بیشترین کلیک را روی آیکون‌های ردیف اول داشتیم، می‌خواهیم در طراحی جدید بتوانیم نظر کاربران را به بیشتر از یک ردیف از آیکون‌ها جلب کنیم.می‌خواهیم دسترسی بهتری به قسمت های مختلف روی صفحهٔ اول ایجاد کنیم.می‌خواهیم اهمیت هر بخش از صفحهٔ اصلی را از نگاه کسب‌و‌کار و کاربر بدانیم.و …بعد از مشخص کردن اولویت‌ها، طراحان رابط کاربری شروع به طراحی حالت‌های متفاوت کردند. با همکاری بخش‌های مختلف در دیزاین اسپرینت‌های(Design sprints) مشخص، طرح‌های متفاوت طراحی‌ و بررسی شدند تا اینکه بالاخره طرح نهایی را با مدل بررسی متخصصین (‌Expert Review) انتخاب و طراحی منتخب را برای آزمون‌های موردنیاز آماده کردیم.بعد از این مرحله، تصمیم گرفتیم طراحی‌های منتخب را تبدیل به پروتوتایپ(نمونهٔ اولیه) کنیم و برای رسیدن به طراحی کاربر‌پسندتر و کاربردی‌تر آن‌ها را مورد آزمون قرار بدهیم.۳- پروتوتایپ کردن فرضیاتبه منظور:کاربر محور بودن و هم‌دلی با کاربرانپیدا کردن خطاها و اشکالات طراحی در سریع‌ترین زمان ممکنصرفه‌جویی در زمان و رسیدن به فرایند طراحی کارآمدتربه این دلیل که کاربران قبل از این هم از سوپراپلیکیشن اسنپ استفاده می‌کرده‌اند، اگر نمونهٔ اولیه با جزئیات بالا و مانند اپلیکیشن واقعی طراحی نمی‌شد ممکن بود کاربران هنگام انجام آزمون‌های کاربرپژوهی گیج شوند و این موضوع می‌توانست روی هدفمان از این آزمان‌‌ها تأثیر بگذارد. «پروتوپای» تنها ابزاری بود که می‌توانستیم با استفاده از آن حس استفاده از یک اپلیکیشن واقعی را برای کاربران تداعی کنیم.ساختار معماری اطلاعات۴- طراحی کارت‌های پویا و شخصی‌سازی‌شده(Dynamic Banners)در این مرحله به موازات بازطراحی ساختار اصلی صفحهٔ اول، طراحی کردن بنرها را شروع کردیم. ابتدا، صفحات اصلی همهٔ سرویس‌های موجود در سوپراپلیکیشن اسنپ را یک‌جا جمع کردیم تا بتوانیم همهٔ مدل‌های موجود را در طراحی جدید در نظر بگیریم.در قدم بعدی در جلسات دیزاین اسپرینت، دانا، هومن و احسان برای هر سرویس یک کارت منحصربه‌فرد در نظر گرفتند و اطلاعات موردنیاز مربوط به آن سرویس را یادداشت کردند تا مشخص شود که هر سرویس به چه اجزایی نیاز دارد. در نهایت، توانستیم اجزای(Components) هر کارت را بسازیم.برای نمایش محتوا براساس اولویت‌ و اهمیت‌ آن‌ها در صفحهٔ اصلی، مدل کسب‌وکار گروه اسنپ را مبنای کارمان قرار دادیم و به این نتیجه رسیدیم که باید برای بنرهای هر محصول سه اندازهٔ متفاوت داشته باشیم:کارت‌های کوچک برای پیشنهادهای مربوط به خدمات و محصولاتکارت‌های متوسط برای پیشنهادهای مربوط به فروشندگان و تأمین‌کنندگان میانیکارت‌های بزرگ برای پیشنهادهای مربوط به ارائه‌دهندگان اصلی سرویس‌هاهدفمان از این کار، نمایش پیشنهادها به‌طور هدفمند و روشن به کاربران بود و به‌علاوه می‌خواستیم سیستم پیشنهاد‌دهی براساس رفتار کاربران داشته باشیم.سه ساختار کسب‌و‌کار۵- نمونهٔ اولیه(MVP)برای اینکه فرضیه‌های‌مان را برای داشتن صفحهٔ اصلی پویا و شخصی‌سازی‌شده(Dynamic)، بسنجیم، از بنرهای ایستا(Static) موجود در صفحهٔ اصلی به‌عنوان نمونهٔ اولیه(MVP) استفاده کردیم.تیم بازاریابی از این بنرهای ایستا(Static) روی صفحهٔ اصلی برای نمایش پیشنهادها یا کمپین‌های تبلیغاتی استفاده می‌کرد اما این بنرها به‌دلیل تنوع رنگی زیاد، صفحه را خیلی شلوغ کرده بودند و این شلوغی باعث سردرگمی کاربران می‌شد.با پیاده‌سازی کارت‌های پویا دو هدف داشتیم:۱. کاهش مراحل موردنیاز برای ثبت یک سفارش۲. افزایش نرخ کلیک بر روی کارت/بنرهای صفحهٔ اصلی سوپراپلیکیشناولین قدم قانع کردن سرویس‌های مختلف برای معرفی و تبلیغ محبوب‌ترین خدمات یا محصولاتشان در بنرهای صفحهٔ اصلی بود. مثلاً معرفی ۵ دسته‌بندی محبوب در سفارش غذا، ۵ هتل یا مسیر پروازی برتر، ۵ خدمت محبوب از اسنپ دکتر و …کارت‌های Dynamicبا کمک روش «بررسی تخصصی» مدل منتخب برای ساخت پروتوتایپ اولیه را انتخاب کرده و برای شروع تست، بر روی طراحی جدید ساختار صفحه اولیه آماده شدیم. در نهایت با هدف داشتن صفحه‌ای پویا، مقدمات لازم برای ارزیابی نهایی را انجام دادیم.۶- کاربرپژوهی (UX Research)حالا وقت آن بود که گزینهٔ منتخبی که با «مدل بررسی متخصصین» برای تغییر ساختار صفحهٔ اصلی به آن رسیده بودیم و فرضیه‌های‌مان را در مورد شخصی‌سازی(Dynamic) کردن کارت‌ها به‌وسیلهٔ بنرهای ایستا مورد آزمون و اعتبارسنجی قرار دهیم.وقتی به مرحلهٔ آزمون رسیدیم با طراحی انتخابی‌مان می‌توانستیم پاسخ پرسش‌های زیر را از طریق پژوهش تجربهٔ کاربر به دست بیاوریم:آیا با جابه‌جا کردن نوار بالا و پایین در صفحهٔ اصلی، کاربران می‌توانند به‌راحتی محل جدید قرارگیری آیکون‌ها را پیدا کنند؟آیا با قرارگیری مهم‌ترین و پردرآمدترین سرویس‌های اسنپ روی نوار شناور پایین صفحه، کاربران می‌توانند به‌راحتی آن‌ها را پیدا کنند؟به چه میزان مفهوم «دسته‌بندی آیکون‌ها» برای کاربران قابل‌درک و کاربردی است؟به چه میزان جابه‌جایی و مسیر حرکت کاربران راحت است؟چه چیزی بنرها را برای کاربران جذاب می‌کند و باعث می‌شود روی آن‌ها کلیک کنند؟مدل ذهنی کاربران در مورد سوپراپلیکیشن چگونه است؟ساختار قبلی و تازهفرایند کاربرپژوهیمانند هر پروژهٔ کاربرپژوهی دیگر تیم کاربرپژوهی ما روش پژوهش را براساس پرسش‌هایی که داشتیم انتخاب کرد. برای پاسخ به پرسش‌های ۱ تا ۴ نیاز داشتیم نحوهٔ رفتار و تعامل کاربر با طراحی جدید را بررسی کنیم و برای پرسش‌های ۵ و ۶ باید متوجه طرز فکر کاربر می شدیم و مدل ذهنی او را نسبت به اسنپ به‌عنوان یک سوپراپلیکیشن درک کنیم.انتخاب روش کاربرپژوهی:برای پاسخ به پرسش‌های رفتاری به آزمون کاربردپذیری نیاز داشتیم و برای پاسخ به پرسش‌های نگرشی باید با کاربران مصاحبه می‌کردیم. نیره، مدیر تیم کاربرپژوهی، در جلسه‌ای با تیم دیزاین و محصول برای شروع فرایند کاربرپژوهی برنامه‌ریزی کرد. بعد از هم‌فکری به این نتیجه رسیدیم که این دو روش را با هم ترکیب کنیم و قبل از شروع آزمون کاربردپذیری با کاربران ۲۰ دقیقه مصاحبه کنیم و سوالاتمان را بپرسیم. سپس در ۲۰ دقیقه بعدی، براساس سناریوهایی که از قبل آماده کرده بودیم از کاربران آزمون کاربردپذیری بگیریم.چه کاربرانی را برای کاربرپژوهی انتخاب کردیم:گروه‌های کاربران را براساس رفتارشان و تعاملی که با سوپراپلیکیشن اسنپ داشتند انتخاب کردیم. در جلسهٔ ایده‌پردازی که با حضور دانا از تیم پروداکت، احسان و هومن از تیم طراحی و نیره از تیم کاربرپژوهی برگزار شد، به ۳ دسته زیر رسیدیم:۱. کاربرانی که در ۶ ماه گذشته در سوپراپلیکیشن اسنپ درخواست خودرو داشته‌اند.(۵ کاربر)۲. کاربرانی که در ۶ ماه گذشته در سوپراپلیکیشن اسنپ درخواست خودرو و سفارش غذا از اسنپ فود داشته‌اند.(۵ کاربر)۳. کاربرانی که در ۶ ماه گذشته در سوپراپلیکیشن اسنپ در بیشتر از ۲ سرویس سفارش ثبت کرده‌اند.(۵ کاربر)۴. کاربرانی که بعد از معرفی اسنپ به عنوان یک سوپراپلیکیشن به بازار، هیچ سفارشی ثبت نکرده بودند.(۵ کاربر)نتایج مصاحبه با کاربران:حدود ۸۵٪ از کاربران متوجه تبدیل شدن اسنپ به سوپراپلیکیشن نشده بودند و هیچ آشنایی با کلمهٔ «سوپراپلیکیشن» نداشتند.نمایش همهٔ سرویس‌های یک برند در یک اپلیکیشن برای کاربران، عبارت «همهٔ سرویس‌ها در یک اپلیکیشن» و «در دسترس بودن» را تداعی می‌کرد.یکی از اصلی‌ترین دلایلی که کاربران در سرویس‌های دیگر اسنپ سفارش ثبت نمی‌کردند، نامتوازن بودن سیستم عرضه و تقاضا و کمبود تأمین‌کننده، به‌ویژه در شهرهای کوچک و متوسط بود.کاربران به دنبال تجربهٔ یکپارچه در استفاده از همهٔ سرویس‌ها بودند.نتایج آزمون کاربردپذیری:همهٔ کاربران به‌راحتی سرویس تاکسی و غذا را که در طراحی جدید به نوار شناور پایین صفحه منتقل شده بود، پیدا کردند اما برای عادت کردن به جای جدید این آیکون‌ها به زمان نیاز دارند.(برای اندازه‌گیری منحنی یادگیری نیاز به آزمون A/B داریم)کاربران متوجه تفاوت تخفیف‌ها و اسنپ‌کلاب نمی‌شوند.کاربران روی صفحهٔ اصلی پیمایش(اسکرول) نمی‌کنند چون همچنان بر این باور هستند که این صفحه هنوز هم شلوغ است.(نیاز به اندازه‌گیری ‌SNR داریم)کاربران تمایلی به کلیک کردن روی بنرها/ کارت‌ها ندارند چون به دنبال پیشنهادهای شخصی‌سازی‌شده و یا ویژه هستند.مفهوم «آیکون در آیکون»برای کاربران ایرانی بسیار ناآشناست و با وجود علامتی که روی این دسته آیکون‌ها وجود داشت متوجه نشدند که اگر روی آیکون سرویس اصلی کلیک کنند آیکون‌های زیر مجموعهٔ آن سرویس را می‌توانند مشاهده و انتخاب کنند(به عنوان مثال، آیکون سفر یا اسنپ تریپ، چند سرویس زیرمجموعه دارد مانند بلیت هواپیما، قطار، اتوبوس و…)نتیجه‌گیری:برای طراحی جدید تصمیم داشتیم اگر نتیجهٔ آزمون برای بیشتر از ۷۰٪ کاربران مثبت شد پیاده‌سازی پروژه را شروع کنیم. اگر نرخ موفقیت بین ۵۰٪ تا ۷۰٪ بود براساس بازخوردهای کاربران به چرخه طراحی برگردیم و طراحی را ارتقا دهیم و در صورتی‌که نرخ موفقیت زیر۵۰٪ بود دوباره برای رسیدن به نمونه‌های جدید طراحی ایده‌پردازی کنیم.خوشبختانه، براساس نتایج آزمون کاربردپذیری نرخ موفقیت کاربران ۸۵٪ بود.همچنین، آزمون انجام‌شده روی نمونهٔ اولیه کارت‌های پویا نشان داد نرخ کلیک روی کارت‌ها۲۰٪ افزایش پیدا کرده بود.طراحی نهاییبعد از تمام شدن آزمون کاربرپژوهی با همکاری تیم تجربهٔ کاربری و تحلیل داده‌های به‌دست‌آمده، به طراحی نهایی رسیدیم و کار پیاده‌سازی بازطراحی صفحهٔ اصلی را با قابلیت‌ها و ویژگی‌های جدید شروع کردیم.برای کارت‌های پویا، هدف ما افزایش تعامل کاربران با این بخش و به وسیله‌ی نمایش بیشتر محتوای مرتبط با آن‌ها بود.طرح نهاییتعریف محصول جدیدانتظار داشتیم که با بازطراحی موارد زیر را بهبود دهیم:توجه کاربران را به صفحهٔ اصلی بیشتر کنیم و تلاش کنیم کاربران بیشتر از یک ردیف آیکون از سرویس‌ها را ببینند.محتوای پویا و مرتبط به کاربران نمایش دهیم.با شناور کردن نوار پایین صفحه آن را به شست کاربران نزدیک‌تر کنیم.از بخش ابتدایی صفحه برای نمایش خدمات و محصولاتی که اولویت کسب‌وکار ما هستند استفاده کنیم.صفحهٔ اصلی را سامان‌دهی و آن را کاربردی‌تر کنیم.می‌خواستیم چگونه این اهداف را دنبال کنیم؟استفاده از تمام فضای بالقوه در صفحهٔ اصلیبرای استفاده بهینه‌تر از فضا، تصمیم گرفتیم که از مفهوم « آیکون در آیکون» استفاده کنیم؛ یعنی خدمات زیرمجموعه یک سرویس را زیر آن قرار بدهیم. آیکون در آیکون به کاربران کمک می‌کرد تا راحت‌تر روی صفحهٔ اصلی جابه‌جا شوند.برای استفاده بهینه از فضای موجود در صفحهٔ اصلی، بنر سبز بالای صفحه را حذف کردیم.از‌آنجایی‌که سرویس‌های زیادی در سوپراپلیکیشن اسنپ خدمت ارائه می‌کنند، در کنار تیتر هر بنر، یک مربع‌ رنگی که برگرفته از هویت بصری سرویس‌ها بوده قرار دادیم تا به کاربران در تشخیص بهتر سرویس‌ها کمک می‌کنیم. مثلاً، برای نمایش اسنپ فود از مربع صورتی استفاده کردیم.نحوه‌ی ارائه سرویس‌های مختلفاز ترکیب عنوان‌ها و زیرعنوان‌ها برای بهبود میزان پیمایش(scroll) روی صفحهٔ اصلی استفاده کردیم.به‌علاوه، برای اینکه بتوانیم بیشتر از یک بنر را بعد از هر پیمایش نشان بدهیم،‌ پیمایش افقی روان(Smooth Scrolling) را اضافه کردیم تا کاربر با هر پیمایش روی صفحهٔ اصلی بتواند بنرهای بیشتری را ببیند.سپس قابلیت‌ها و قسمت‌های اصلی مرتبط به سوپراپلیکیشن شامل اسنپ کلاب و تخفیف ها را در یک نوار ناوبری(Navigation bar) در بالای صفحه قرار دادیم.همچنین، برای انتخاب راحت‌تر۴ سرویس پراستفادهٔ گروه اسنپ، آن‌ها را روی نواری شناور در پایین صفحه(floated bottom bar) قرار دادیم تا کاربر حتی زمان پیمایش روی صفحه هم بتواند به این سرویس ها دسترسی داشته باشد و همچنین به ناحیه قرارگیری شست دست هم نزدیک باشد.این ساختار به ما کمک کرد تا برای قرارگیری حساب کاربری، کیف پول و قابلیت‌هایی که می‌خواستیم در آینده اضافه کنیم جای بهتری پیدا کنیم. به‌این‌صورت کاربران می‌توانستند حساب کاربری‌شان را در صفحهٔ اصلی اپلیکیشن شخصی‌سازی کنند.افزایش تعامل کاربران با سوپراپلیکیشنبا این تغییرات توانستیم تعامل کاربران را با سوپراپلیکیشن افزایش بدهیم:کارت‌های پویا: کارت‌های روی صفحهٔ اصلی با هر بار باز شدن سوپراپلیکیشن تغییر می‌کنند و محتوایی براساس رفتار کاربر ارائه می‌دهند.بنرها/آیکون‌های کمپین‌های بازاریابی: این‌ها بنرهای ایستا، سرویس‌هایی هستند که می‌توانند تنها به گروهی از کاربران که از قبل مشخص شده‌اند، نمایش داده شوند.بنرهای کشویی (Sliders): در طراحی قبلی فقط یک مدل بنر(بنر ایستا) برای تمام پیشنهادها و کمپین‌های تیم بازاریابی داشتیم اما در طراحی جدید بنرهای کشویی را اضافه کردیم که به تیم محصول و بازاریابی اجازه می‌داد عملکرد بهتری داشته باشند. این مدل بنر جدید شامل قابلیت‌های جدیدی بود مثل، نرخ پیمایش (Scroll) سریع‌تر که هر ۵ ثانیه یک بنر جدید را به‌همراه نقاط راهنما نمایش می‌داد. این نقاط کاربر را راهنمایی می‌کرد که با پیمایش افقی (Horizontal Scroll) بنرهای بیشتری را ببیند.متون رهنما(Coach-marks): متون راهنمایی را اضافه کردیم تا به کاربران نشان بدهیم که پراستفاده‌ترین سرویس‌ها به نوار پایین صفحه منتقل شده‌اند.فلِش‌های راهنما: روی صفحهٔ اصلی فلِش‌ راهنما را برای ترغیب کاربران به پیمایش(Scroll) به پایین صفحهٔ اصلی اضافه کردیم تا کاربران را با بخش بنرها، آشنا کنیم.نمایش مطابق با شهر سکونت کاربر: قابلیت جدیدی را به صفحهٔ اصلی اضافه کردیم که فقط سرویس‌هایی را به کاربر نشان می‌دهد که در شهر سکونت او در دسترس هستند و کاربران بنرهای مربوط به سرویسی که در شهرشان در دسترس نیست را نمی‌بینند. (مثلاً نمایش ندادن سرویس فروشگاه یا اسباب‌کشی در شهرهایی که هنوز این سرویس‌ برای کاربران در دسترس نیست).بهبود تجربه کاربرالمان‌های رابط کاربری:با شناسایی مشکلاتی که داشتیم، تصمیم گرفتیم اصول طراحی رابط کاربری‌مان را با اصول سیستم طراحی(Design System) اندروید(Android) و آی‌او‌اس(iOS) هم‌سو کنیم. همچنین، فرایند بازطراحی و پیاده‌سازی را سریع‌تر کردیم تا کاربران بتوانند با تغییرات جدید به‌راحتی تعامل کنند.از طرف دیگر جامع بودن و مستندات سیستم طراحی گوگل متریال(Google material) برای سیستم‌های بزرگ و پیچیده‌ای مانند اسنپ مناسب است. با اضافه شدن سریع سرویس‌های جدید به گروه اسنپ، بیشتر از گذشته نیاز به یک سیستم طراحی دقیق احساس می‌شود.به زودی، در مقاله‌ای دیگر به جزئیات بیشتری در رابطه با سیستم طراحی اسنپ می‌پردازیم. اکنون چند مثال کوتاه بزنیم:سیستم تایپوگرافی ما ساختار مناسبی برای عناوین و متن‌های اصلی نداشت، اسکن چشمی اجزا برای کاربران سخت بود و محتوا آشفته و بی‌نظم به نظر می‌رسید. استفاده از اصول تایپوگرافی گوگل متریال به ما کمک کرد رابط کاربری منظم‌تری بسازیم و باعث شد محتوای متنی رابط کاربری مانند نوشته‌های روی بنرها و کارت‌ها خواناتر بشوند.رنگ‌های اصلی که در طراحی قبلی بودند را دوباره استفاده کردیم اما سیستم رنگی ما مشکلات دسترس‌پذیری داشت. به همین دلیل، در طراحی جدید کنتراست‌ها را تغییر دادیم. به عنوان مثال، برای رنگ اصلی برندمان، از رنگ سبز تیره‌تری که قبلاً در هویت بصری‌مان تعریف شده بود استفاده کردیم تا بتوانیم سوپراپلیکیشن را دسترس‌پذیرتر کنیم.به‌علاوه، با کمک کاربران و دریافت بازخورد از آن‌ها تمام حالت‌ها و کنتراست‌های رنگی را بررسی کردیم.چیدمان رنگیهمگام‌سازی راهنماهای هویت بصری:برای اینکه یک صفحهٔ اصلی منسجم و سرویس‌هایی هماهنگ داشته باشیم تصمیم گرفتیم تمام محتوای متنی، رنگ‌ها و طراحی‌ها را هم‌راستا کنیم.برای رسیدن به این نقطه، به یک «راهنمای ساختاریافته و قوانین طراحی مدون(Design guideline)» نیاز داشتیم. پس:باید طیف‌های رنگی برای هر سرویس مشخص کنیم.صفحهٔ اصلی جدید از الگویی جدید پیروی می‌کرد تا به کاربران برای درک راحت‌تر دسته‌بندی‌های جدید کمک کند.هویت بصری و کلامی اسنپ، ترکیبی از راحتی و جدیت است و این هویت باید در تمام بخش‌ها به صورت یکپارچه دیده شود.اجزای بصری، هماهنگی لازم را برای رسیدن به یکپارچگی و انسجام لازم نداشتند و باید همهٔ اجزای تصویر براساس سیستم طراحی جدید اصلاح و هماهنگ‌سازی شوند.معماری اطلاعات:پیش از شروع پروسهٔ بازطراحی، بازخوردهای متفاوتی از کاربرانی گرفتیم که قادر نبودند به راحتی با سرویس‌های ارائه شده در صفحهٔ اصلی تعامل کنند.همچین بر اساس نتایج «Heatmap» از یک سو و داده‌های کمی کسب‌شده از رفتار کاربران از سویی دیگر، ساختار داده در صفحهٔ اصلی را تغییر دادیم. این ساختار جدید کاملا بر اساس مدل ذهنی کاربران و بر مبنای نتایج مصاحبه‌های عمیق با برخی از کاربران و متد Card Sorting با آن‌ها بود.پیاده‌سازی و تحویلدر تمام طول فرایند دیزاین، جلسات بسیاری را با مدیر فنی برگزار کردیم تا امکان‌پذیری(Feasibility) پیاده سازی این تغییرات را به‌طورمرتب بررسی کنیم. در نهایت، بعد از آماده شدن نمونهٔ اولیه و نهایی‌شده طراحی، در جلسه‌ای برنامهٔ اجرای بازطراحی را با کل تیم فنی سوپراپ تهیه کردیم.این فرایند برای ما استرس‌زا و دشوار بود چرا که در حال ایجاد تغییر بزرگی روی محصول بودیم و این اتفاق دقیقاً قبل از پروژهٔ بزرگ بازسازی محصول اسنپ(Re-factoring project) بود و به‌علاوه همگی چشم‌به‌راه نتایج مثبت و امیدوارکننده‌ بودیم.پیاده‌سازی طراحی جدید را به ۴ بخش تقسیم کردیم:۱- سربرگ بالا (Top header)۲- نوار ناوبری بالا(Top navigation bar )۳- نوار پایین شناور(Floated bottom bar)۴- تغییرات رابط کاربریقصد داشتیم تمامی این موارد را در یک کوارتر(quarter) پیاده‌سازی کنیم که تقریباً زمان‌بندی فشرده‌ و کوتاهی محسوب می‌شد.برنامهٔ پیاده‌سازیمرحلهٔ دوم کاربرپژوهیروش آزمون A/B:بعد از تمام شدن مرحلهٔ پیاده‌سازی، براساس مدل آماری فرضیهٔ صفر (null-hypothesis statistical model) سناریوهای متفاوت را برای اجرای آزمون ‌A/B روی طراحی جدیدمان شروع کردیم. این آزمون با موتور آزمون (A/B test engine) که در تیم اسنپ پیاده‌سازی شده است و آن را ABaas یا AB test as a service می‌نامیم، اجرا شد.بدیهی است که نتایج اولیهٔ آزمون برای ما بسیار مهم بود. پس، سعی کردیم در اجرای آزمون بسیار بادقت عمل کنیم. خوشبختانه ابزار آزمون داخلی ما «ABaas» این امکان را فراهم می‌کرد که دسته‌بندی و طبقه‌بندی‌های درست و متنوعی از کاربران داشته باشیم و با دقت بالا و کمترین خطا این آزمون را اجرا کنیم.براساس شاخص‌های رفتاری، اولین گروه کاربران را برای آزمون انتخاب کردیم. برای این کار براساس مدل ‌RFM کاربران را طبقه‌بندی و گروه‌های مناسب را برای آزمون جدا کردیم. اولین گروه شامل کاربرانی بود که در ۶۰ روز گذشته حداقل ۲ بار درخواست خودرو داشتند یا از اسنپ فود استفاده کرده بودند. سپس، این دسته را روی نمودار آوردیم تا فراوانی آن‌ها را بسنجیم.نتایج آزمون ‌A/B:نرخ کلیک روی صفحهٔ اصلی افزایش پیدا کرد.میانگین نرخ کلیک روزانه و سفارش خدمات لُجستیک ( تاکسی، پیک، موتور و …)، اسنپ فود، اسنپ مارکت، هتل و فین‌تک(Fintech) هنگام آزمون افزایش پیدا کرد.متوجه شدیم که هر جا نرخ کلیک به‌طور چشم‌گیری افزایش پیدا کرده بود، درصد کمی کاهش در نرخ تبدیل برخی از سرویس‌ها داشتیم. با این حال، نرخ تبدیل اسنپ مارکت و تاکسی افزایش پیدا کرده بود.متوجه شدیم که با نمایش آیکون سرویس‌ها در دو ردیف (ردیف اول و نوار شناور پایین) میزان دیده شدن آیکون سرویس‌ها افزایش پیدا کرده بود.نرخ کلیک روی سرویس‌هایی که در نوار ناوبری بالا قرار داشتند افزایش پیدا کرده بود. این نوار شامل آیکون صفحهٔ اصلی، اسنپ کلاب و تخفیف‌ها می‌شود.با متعادل‌سازی(Normalize) نرخ نسبت تعداد دفعاتی که اپلیکیشن باز شده به نرخ کلیک در گروه‌های آزمون و کنترل توانستیم با ساختار طراحی جدید نرخ کلیک بیشتری را روی سرویس درخواست خودرو به دست آوریم.آزمون در ابتدا نشان داد که کاربران در پیدا کردن آیکون‌هایی که روی نوار شناور پایین صفحه قرار گرفتند، مشکل دارند اما به مرور زمان، مدت زمان پیدا کردن آیکون‌ها در نوار شناور پایین کاهش پیدا کرد و این مشکل برطرف شد.آیکون‌های جدیدمزایای بازطراحی صفحهٔ اصلی سوپراپلیکیشن به زبان اعدادکلیک روی صفحهٔ اصلی تقریباً ۲ برابر افزایش پیدا کرد.متوسط نرخ کلیک روی بخش بنرهای صفحهٔ اصلی در زمان انتشار ۲۸٪ افزایش پیدا کرد.متوسط نرخ کلیک روی آیکون‌ سرویس‌های درخواست خودرو، موتور، پیک و وانت ۱۲٪ افزایش پیدا کرد.نرخ کلیک روی نوار شناور در صفحهٔ اصلی ۱۰۰٪ افزایش پیدا کرد.میزان سفارش از سرویس‌های دیگر ۴٪ افزایش پیدا کرد.نرخ تبدیل سرویس‌های اصلی(اسنپ فود، اسنپ مارکت، اسنپ دکتر، اسنپ تریپ و…) تقریباً ۴٪ افزایش پیدا کرد.تعداد کاربرانی که روی صفحهٔ اصلی سوپراپلیکیشن کلیک می‌کنند تقریباً ۶۷٪ افزایش پیدا کرد.انتشار طراحی جدیددر ابتدا، طراحی جدید را برای ۱۰٪ از کاربران به‌مدت ۲ هفته منتشر و سپس نتیجه را بررسی کردیم. نتایج موفقیت‌آمیز بود. سپس، طراحی جدید را برای ۳۰٪ از کاربران منتشر و دوباره نتایج را بررسی کردیم. درآخر، طراحی جدید را برای ۵۰٪ از کاربران منتشر کردیم و بعد از اینکه به‌مدت ۱ ماه و به‌صورت مداوم همه جزئیات را با تیم تکنولوژی و کسب‌و‌کار مانیتور کردیم، تصمیم گرفتیم که طراحی جدید را در ۲۱ اردیبهشت‌ماه برای ۱۰۰٪ از کاربران اندروید منتشر کنیم.سخن آخرفرایند طراحی صفحهٔ اصلی شاید در ابتدا ساده به نظر می‌رسید اما به سفری جذاب و به‌یادماندنی تبدیل شد. این سفر بی‌شک بدون همراهی و هم‌دلی تیم‌های مختلف به سرانجام نمی‌رسید.پروژه‌ای که ۱٪ خطا می‌توانست هزینه‌های هنگفتی را در پی داشته باشد، تبدیل به یکی از پروژه‌های بزرگ و در عین حال سودآور اسنپ شد و همهٔ ما از تیم‌های مختلف را در کنار هم قرار داد.موفقیت، نتیجه‌ای قابل‌انتظار از همکاری بی‌نظیر ۳ رکن اصلی اسنپ یعنی تیم طراحی، تیم محصول و تیم تکنولوژی بود. در طی پروژه همراهی و هماهنگی در سطوح مدیریت تیم‌های مختلف باعث شد به نتایجی که مدنظرمان بود برسیم.این پروژه برای‌مان فقط یک پروژه نبود بلکه برای‌مان الگویی از یک همکاری فوق‌العاده ساخت که از آن بتوانیم در پروژه‌های آتی استفاده کنیم و با بهبود آن نمونه‌ای از کار تیمی منسجم و یک‌پارچه را به‌ نمایش بگذاریم.متشکریمشما قادر هستید تا نسخهٔ بصری این ارائه را در Behance و Dribbble و نسخه انگلیسی آن را در Medium بخوانید.اعضای این پروژه:هومن هاتفی - مدیر ارشد تیم طراحی در اسنپ (Linkedin, Dribbble, Twitter, Instagram, Behance)فهیم اختر - مدیر ارشد تیم مدیریت محصول در اسنپ (Linkedin)احسان مددی - طراح ارشد محصول در اسنپ (Linkedin, Dribbble, Behance)دانا ابراهیمی - مدیر محصول در اسنپ (Linkedin)نیره افضلی - سرپرست تیم کاربرپژوهی در اسنپ (Linkedin)نیلوفر اسلامی حقیقت - نویسندهٔ تجربه در اسنپ (Linkedin)</description>
                <category>اسنپ</category>
                <author>Danna Ebrahimi</author>
                <pubDate>Sun, 13 Feb 2022 13:05:19 +0330</pubDate>
            </item>
                    <item>
                <title>اپلیکیشن‌های Real-Time، از HTTP تا WebSocket</title>
                <link>https://virgool.io/snapp-eng/%D8%A7%D9%BE%D9%84%DB%8C%DA%A9%DB%8C%D8%B4%D9%86-%D9%87%D8%A7%DB%8C-real-time-%D8%A7%D8%B2-http-%D8%AA%D8%A7-websocket-rh73jq5sursn</link>
                <description>اپلیکشین‌های تحت وب، در ابتدا، مطابق با مدل client-server طراحی و پیاده سازی می‌شدند. این اپلیکیشن‌ها، نیاز ارتباطی خودشون رو با استفاده از HTTP که یک پروتکل client-server هست، برطرف می‌کردند. هر زمان که نیاز داشتند، درخواستی برای سرور ارسال می‌کردند و دیتای مورد نظر رو از سرور می‌گرفتند.پروتکل HTTP برای این نوع از وب اپلیکشین‌ها کاملا مناسب بود. اما با گذشت زمان، وب اپلیکیشن‌هایی با نیازمندی‌ جدید تحت عنوان وب اپلیکیشن‌های Real-Time شروع به گسترش کردند. منظور از real-time بودن اینه که وقتی رویدادی (event) توی سیستم اتفاق می‌افته، قبل از اینکه اهمیتش رو از دست بده، بتونیم نسبت بهش واکنش نشون بدیم. مثلا در اپلیکیشن تاکسی اسنپ، زمانی که راننده درخواست سفر رو قبول می‌کنه، در واقع یک event توی سیستم رخ داده. اپلیکیشن مسافر باید این قابلیت رو داشته باشه که بتونه در سریع‌ترین زمان ممکن، رخ دادن این اتفاق رو متوجه بشه و کاربر رو ازش مطلع کنه.درنتیجه، مدل ارتباطی client-server جوابگوی نیاز این نوع از وب اپلیکیشن‌ها نیست.توی این نوشته، Polling  رو به عنوان اولین و ساده‌ترین راه ارتباطی در اپلیکیشن‌های real-time بررسی می‌کنیم و باهم می‌بینیم که چرا بهتره از WebSocket استفاده کنیم و در حین پیاده سازیش، به چه نکاتی باید دقت کنیم.پیش‌نیازبرای خوندن این نوشته نیازه که با مفاهیم WebSocket ، HTTP و MQTT تا حدی آشنایی داشته باشید. اگر نیاز داشتید بیشتر راجع به این موضوع‌ها بدونید، می‌تونید از لینک هایی که براتون گذاشتم استفاده کنید.استفاده از Pollingاولین و ساده‌ترین راه از نظر پیاده‌سازی، استفاده از تکنیک پولینگه. پولینگ یه تکنیک بر پایه HTTP ست.عملکرد Polling به این صورته که به طور مداوم و در بازه‌های زمانی مساوی، درخواست‌هایی از سمت کلاینت برای سرور ارسال می‌شه و سرور event های جدید رو به کلاینت برمی‌گردونه.در واقع polling، یه راهیه برای تطبیق دادن مدل ارتباطی client-server با نیازمندی اپلیکیشن‌های real-time.دو روش پیاده‌سازی Polling۱- روش Short Polling: در بازه‌های زمانی مساوی، کلاینت درخواستش رو برای سرور ارسال می‌کنه و سرور پاسخ این درخواست رو بلافاصله به کلاینت برمی‌گردونه. اگرچه ممکنه در بیشتر مواقع event جدید رخ نداده باشه و کلاینت ریسپانس خالی دریافت کنه.به طور خلاصه، short polling به صورت زیر عمل می‌کنه:15:30:00: اسنپ من رسید؟15:30:01: نه!15:30:10: اسنپ من رسید؟15:30:11: نه!15:30:20: اسنپ من رسید؟15:30:21: بله ^^۲- روش Long Polling: بعد از اینکه کلاینت درخواستش رو برای سرور ارسال می‌کنه،‌ سرور سعی می‌کنه که تا جای ممکن connection کلاینت رو باز نگه داره و فقط زمانی که event جدیدی اتفاق افتاده بود و یا از نظر زمانی، به یک threshold ای رسیده بود، ریسپانس رو به کلاینت برگردونه.به طور خلاصه، long polling به صورت زیر عمل می‌کنه:15:30:00: اسنپ من رسید؟15:30:17: بله ^^چرا به جای  Polling از WebSocket استفاده کنیم؟روش Polling بر پایه HTTP ست و به همین علت، هم پیاده‌سازی راحتی داره و هم در بیشتر دستگاه‌ها پشتیبانی می‌شه. اما این روش، معایبی هم داره که باعث می‌شه نتونیم به عنوان یه روش کارآمد، ازش استفاده کنیم.معایب Polling۱- وقتی از روش polling استفاده می‌کنیم، هربار که قراره درخواست جدیدی ارسال بشه،‌ باید یک connection برقرار بشه، هدرهای HTTP پارس بشه، در سمت سرور برای دیتای جدید query زده بشه و ریسپانس ساخته و به کلاینت تحویل داده بشه. در نهایت هم resource cleanup انجام بشه. تکرار این روند، سربار زیادی داره.۲- تو روش Polling، ممکنه در بیشتر مواقع در اپلیکیشن event خاصی رخ نداده باشه و در نتیجه، سرور دیتای خاصی برای فرستادن نداشته باشه و  مجبور باشه که ریسپانس خالی برای کلاینت بفرسته.مثلا فرض کنید در اپلیکیشن تاکسی اسنپ، در حین سفر قراره ۵ تا ایونت اتفاق بی‌افته که اپلیکیشن کلاینت باید از اون‌ها با خبر بشه. اگر فرض کنیم مدت زمان کل سفر ۳۰ دقیقه و فاصله بین هر درخواست Polling با درخواست بعدی ۱۰ ثانیه باشه، در کل زمان سفر،  ۱۸۰ تا درخواست HTTP از سمت کلاینت برای سرور فرستاده می‌شه که از بین این تعداد، فقط ۵ تای اون‌ها ایونتی رو به دست کلاینت می‌رسونن و برای ما اهمیت دارن.در نتیجه، Polling روش خوب و ساده‌ایه ولی سربار زیادی داره. پس بهتره به دنبال یه روش دیگه باشیم.اگه دوست داشتید که بیشتر راجع به Long Polling بدونید، می‌تونید از این لینک استفاده کنید.استفاده از  WebSocketوب سوکت یه پروتکل ارتباطیه که به کمکش می‌تونیم بین کلاینت و سرور، یه ارتباط دوطرفه و پایدار داشته باشیم.زمانی که کلاینت می‌خواد یه کانکشن از نوع WebSocket داشته باشه، فقط کافیه که یک درخواست HTTP با تعدادی هدر استاندارد برای سرور ارسال کنه و سرور و کلاینت توافق کنن که TCP connection  فعلی رو upgrade کنن، اون رو باز نگه دارن و ازش برای انتقال پیام‌های بعدی استفاده کنن.هدر‌هایی که کلاینت باید ارسال کنه:Connection: UpgradeUpgrade: websocketدر WebSocket پیام‌ها باید چه شکلی باشن؟وب سوکت یه پروتکل سطح پایینه که بعد از برقراری ارتباط بین کلاینت و سرور، امکان ارسال دیتا رو به ما میده اما، مشخص نمی‌کنه که دیتا باید به چه شکلی باشه و این موضوع رو بر عهده کلاینت و سرور قرار میده. (دیتا باید از نوع binary یا text باشه اما ساختارش محدودیتی نداره).در نتیجه، برای استفاده بهتر از وب سوکت، کلاینت و سرور بهتره که یک پروتکل در سطح اپلیکیشن (application-level) انتخاب کنند که ساختار دیتا رو مشخص کنه. به بیان دیگه، ما نیاز به یک subprotocol داریم.چطوری باید SubProtocol رو مشخص کنیم؟در زمان ارسال HTTP Request اولیه، کلاینت لیستی از subprotocol‌های دلخواهش رو توی هدر Sec-WebSocket- Protocol می‌فرسته و سرور باید یکی از اون‌هارو انتخاب کنه و در ریسپانس همین درخواست، کلاینت رو از انتخاب خودش مطلع کنه.مثلا هدر request که از سمت کلاینت ارسال می‌شه به صورت زیره:Sec-WebSocket-Protocol: wamp, mqttو اگر به فرض مثال سرور mqtt رو به عنوان subprotocol انتخاب کنه، هدر زیر توی ریسپانس برای کلاینت ارسال می‌شه:Sec-WebSocket-Protocol:  mqtt زمانی که از HTTP استفاده می‌کنیم، با هر درخواست هدر‌ها و کوکی‌هایی هم به سمت سرور ارسال می‌شه که به کمک اون‌ها می‌تونیم احراز هویت رو انجام بدیم. اگه قرار باشه از webSocket استفاده کنیم که هیچ header و کوکی‌ای نداره، احراز هویت رو به چه صورت می‌تونیم انجام بدیم؟  احراز هویت در WebSocketدر پروتکل WebSocket، برخلاف HTTP، در هنگام ارسال درخواست، هدر و کوکی‌‌ای از سمت کلاینت ارسال نمی‌شه. در نتیجه، WebSocket نسبت به HTTP سربار کمتری داره، اما از طرفی، نیاز به یک راه برای  authenticate کردن کلاینت داره. subprotocol‌ای که برای WebSocket انتخاب می‌کنیم، می‌تونه راه‌حل‌هایی هم برای این موضوع داشته باشه.استفاده از MQTT به عنوان Subprotocolپروتکل MQTT،  یه پروتکل سبک شبکه است که هدفش تامین نیاز ارتباطی کلاینت‌هایی هست که ظرفیت باطری و bandwidth زیادی ندارند. MQTT طبق مدل publish-subscribe کار می‌کنه و می‌تونیم برای انتقال پیام ازش استفاده کنیم. در این مدل، به کسی که منتظر دریافت پیام هست، subscriber و به کسی که اون پیام رو ارسال می‌کنه، publisher گفته می‌شه.  وظیفه انتقال پیام‌ها از publisher به subscriber بر عهده broker هست. broker از مفهومی به اسم topic استفاده می‌کنه تا بتونه پیام‌های دریافتی از publisher ها رو برای subscriber‌ها فیلتر کنه. در نتیجه، یک subscriber فقط پیام‌هایی که نیاز داره رو دریافت کنه.کاربرد اصلی پروتکل MQTT در زمینه IOT ست و این پروتکل به صورت مستقیم در وب پیاده‌سازی نشده. اما  می‌تونیم با کمک WebSocket، از این پروتکل در وب اپلیکیشن‌ها هم استفاده کنیم. در واقع می‌تونیم از MQTT به صورت over webSocket استفاده کنیم.علت انتخاب MQTT به عنوان subprotocol برای webSocket،  قابلیت‌های خوب MQTT از جمله سبک بودن پروتکل، reliable بودن ارسال پیام‌ها و داشتن راه‌حل برای authentication است. پیاده‌سازی WebSocket به کمک MQTTبرای پیاده‌سازی webSocket و MQTT از پکیج MQTT.js استفاده می‌کنیم.  این پکیج پروتکل MQTT  رو بر روی webSocket پیاده‌سازی کرده و تمام نیازمندی‌های مطرح شده در webSocket رو شامل می‌شه.برای اینکه تمام قابلیت‌های مرتبط با MQTT رو در کنار هم داشته‌باشیم، در پیاده‌سازی از class استفاده می‌کنیم.MqttClient Classدر ابتدا، کلاس MqttClient رو تعریف می‌کنیم. در constructor این کلاس، دو چیز رو مشخص می‌کنیم؛ اول آدرس host ای که باید به اون متصل بشه و سپس topic ای که قراره پیام‌های مربوط به اون رو دریافت کنه.قبل از subscribe کردن روی یک تاپیک، نیاز داریم که به سرور متصل بشیم. این کار رو به کمک متد connect انجام می‌دیم.Connect Methodخروجی mqtt.connect ابجکت client هست که با کمک این ابجکت، به تمام قابلیت‌های MQTT دسترسی داریم.حالا می‌تونیم کلاینتمون رو connect و بعد از اون متد subscribe رو پیاده‌سازی کنیم.Subscribe methodبرای subscribe کردن، اول به سرور متصل می‌شیم. همچنین برای استفاده از client در زمان unsubsribe، اون رو به عنوان یک property از کلاس MqttClient ذخیره می‌کنیم.با استفاده از ایونت connect می‌تونیم متوجه بشیم که کلاینتمون چه زمانی به سرور متصل می‌شه.بعد از اتصال به سرور، می‌تونیم کلاینتمون رو روی تاپیک از قبل تعیین شده subscribe کنیم.در نهایت هم اگر پیامی از سمت سرور به دستمون برسه، با ایونت message می‌تونیم ازش مطلع بشیم.دیدیم که پیاده‌سازی MQTT نسبتا ساده‌ست و امکانات زیادی رو به ما میده. اما وقتی از  MQTT استفاده می‌کنیم، یک چالش خیلی مهم داریم که در قسمت بعدی قراره با هم بررسیش کنیم و براش راه‌حل پیدا کنیم. وقتی اپلیکیشن کلاینت به background میره چه اتفاقی می‌افته؟وقتی از پروتکل webSocket استفاده می‌کنیم و کاربر، اپلیکیشن رو به background می‌فرسته، ارتباط webSocket  قطع می‌شه و دیگه قادر به گرفتن مسیج‌های که از سمت سرور میاد، نیست.خود پروتکل webSocket قابلیت اتصال مجدد خودکار به سرور رو برای ما فراهم نمی‌کنه و این موضوع رو برعهده خودمون قرار داده. پس از بازگشت اپلیکیشن کلاینت به foreground، برای اتصال مجدد به سرور می‌تونیم از client library های نوشته‌شده برای webSocket مثل socket.io، ws و socktjs استفاده کنیم.خوشبختانه subprotocol ای که ما برای webSocket انتخاب کردیم، امکان اتصال مجدد رو هم بهمون میده. توی پکیج MQTT.js، برای تنظیم کردن وقفه بین هربار تلاش برای reconnect شدن می‌تونید از reconnectPeroid  استفاده کنید. همچنین برای اینکه بدونید چه زمانی MQTT داره سعی می‌کنه reconnect بشه، می‌تونید از ایونت reconnect و یا هوک transformWsUrl استفاده کنید.آیا ایونت‌هایی که در زمان disconnect بودن اتفاق می‌افتن از دست میرن؟زمانی که کلاینت disconnect می‌شه (مثلا اینترنت قطع می‌شه و یا اپلیکیشن به حالت background میره)، نمی‌تونه ایونت‌هایی که اتفاق می‌افته رو دریافت کنه؛ در نتیجه ممکنه دیتاها و ایونت‌های مهمی رو از دست بدیم. مثلا، زمانی که کاربر در اپلیکیشن تاکسی اسنپ منتظر رسیدن راننده‌ست و تا اون زمان صفحه گوشیش رو lock می‌کنه و گوشی رو در جیبش قرار می‌ده، ارتباط webSocket قطع می‌شه. اگر در همین حین، راننده به مبدا برسه و یا ایونت مهمی رخ بده، با توجه به قطع بودن ارتباط، اون ایونت‌هارو از دست می‌دیم. چطوری وجود ایونتی که قبل تر اتفاق افتاده و از دست رفته رو متوجه بشیم و او‌ن‌ رو به کاربر اطلاع بدیم؟استفاده از QOSیکی دیگه از امکانات خوبی که MQTT به ما می‌ده، QOS هست. QOS یا quality of service قراردادی‌ست بین گیرنده و ارسال کننده که وظیفه‌اش اطمینان از رسیدن مسیج به دست گیرنده‌ست. QOS سه تا مقدار مختلف می‌تونه بگیره:⭐️ مقدار ۰: دیتا یک‌بار ارسال می‌شه اما چک نمی‌شه که آیا توسط طرف دیگه دریافت شده یا نه.⭐️⭐️ مقدار ۱: تضمین می‌شه که گیرنده حداقل یک بار پیام رو دریافت می‌کنه؛ اما ممکنه که پیام تکراری هم دریافت کنه.نحوه عملکردش به این صورته که فرستنده بعد از ارسال پیام، اون رو ذخیره می‌کنه و اگر در زمان معقولی، سیگنال PUBACK رو از سمت گیرنده دریافت نکنه، پیام مجددا برای گیرنده ارسال می‌شه. درنتیجه در این حالت ممکنه گیرنده پیام تکراری دریافت کنه.⭐️⭐️⭐️ مقدار۲: این مقدار بالاترین سطح quality of service هست و تضمین می‌کنه که هر پیام دقیقا یک‌بار به دست گیرنده می‌رسه.نحوه عملکردش به این صورته که فرستنده بعد از ارسال پیام، اون رو ذخیره می‌کنه. گیرنده پس از دریافت پیام، سیگنال PUBREC رو برمی‌گردونه. اگر فرستنده این سیگنال رو دریافت نکنه، پیام مجددا برای گیرنده ارسال می‌شه.زمانی که فرستنده سیگنال PUBREC رو دریافت کرد، می‌تونه با خیال راحت پیامی که ذخیره کرده بود رو پاک کنه و بعد از انجام این کار، سیگنال PUBREL رو برای گیرنده ارسال می‌کنه. گیرنده هم بعد از دریافت این سیگنال، میتونه تمام state هایی که در سمت خودش ذخیره کرده رو پاک کنه. در نهایت هم سیگنال PUBCOM رو به دست فرستنده می‌رسونه و اون هم میتونه تمام state های مربوط به او پیام رو در سمت خودش پاک کنه.چنتا نکته مربوط به QOS:ازونجایی که broker وظیفه رسیدگی به QOS بین گیرنده و فرستنده رو به عهده داره، فرستنده و گیرنده میتونن QOS‌های مختلفی نسبت به هم داشته باشند.استفاده از QOS 2  تضمین می‌کنه که تمام پیام ها به دست گیرنده میرسه اما به علت بیشتر بودن مراحل بین فرستنده و گیرنده، این ارسال پیام نسبت به مقادیر 0  و  1 کندتر خواهد بود.زمانی که از QOS با مقادیر 1 و 2 استفاده میکنیم، پیامی که در سمت فرستنده ذخیره می‌شه در واقع داخل یک صف (queue) قرار میگیره.اگر تصمیم بگیریم که از QOS با مقدار 1 استفاده کنیم، اپلیکیشن کلاینت رو باید طوری پیاده‌سازی کنیم که با دریافت پیام‌های تکراری، مشکلی براش به وجود نیاد.پس برای حل مشکل پیام‌های از دست رفته در زمان disconnect بودن، می‌تونیم از QOS با مقادیر 1 یا 2 استفاده کنیم. در این حالت، زمانی که کلاینت مجددا connect می‌شه، تمام ایونت‌هایی که در زمان نبودنش رخ داده رو دریافت می‌کنه.اما استفاده از این روش، در همه شرایط ممکن نیست. می‌تونید حدس بزنید در چه شرایطی؟زمانی که اپلیکیشن ما تعداد زیادی کاربر داشته باشه، ذخیره کردن پیام‌ها در سمت سرور سربار بسیار زیادی خواهد داشت و ممکنه که مشکلات زیادی رو به وجود بیاره. در نتیجه، استفاده از این روش منطقی نخواهد بود.اگر نتونیم از QOS 2 استفاده کنیم، چیکار باید بکنیم؟هدف از داشتن event و فرستادن اون‌ها به سمت کلاینت، اینه که کلاینت بتونه بر‌اساس اینکه چه ایونتی رخ داده، یک state ای رو آپدیت کنه و در نتیجه‌ی آپدیت کردن اون state، تغییری در اپلیکیشن کلاینت رخ بده (مثلا پیامی به کاربر نشون داده بشه).ما با استفاده از QOS 2 این امکان رو داشتیم که کلاینتمون در لحظه connect شدن، بتونه ایونت‌هایی که در نبودنش رخ داده رو بگیره و براساس اون ایونت‌ها، state های خودش رو آپدیت کنه.پس در نهایت هدف اصلی ما، آپدیت کردن state هایی‌ست که در سمت اپلیکیشن کلاینت ذخیره شده‌اند.یه راه دیگه برای رسیدن به این هدف، پیاده‌سازی و استفاده از HTTP Endpoint ای‌ست که بتونه در هر لحظه، آپدیت‌ترین دیتاهارو به ما بده. مثلا زمانی که اپلیکیشن کلاینت از background به foreground برمیگرده، می‌تونیم از این endpoint استفاده کنیم و state های آپدیت شده رو بگیریم و در نهایت ذخیره کنیم. در نتیجه هر ایونتی که در زمان background بودن کلاینت رخ داده رو خواهیم داشت.مثلا برای زمانی که کلاینت از background به foreground می‌ره، می‌تونیم از کد زیر استفاده کنیم:در انتها از همتون ممنونم که وقت گذاشتید و این نوشته رو خوندید. امیدوارم که بهتون کمک کرده باشه. اگر پیشنهاد و یا ایده‌ای دارید، خوشحال می‌شم از طریق کامنت و یا راه‌های ارتباطی زیر باهام درمیون بگذارید.توییتر لینکدینریپازیتوری کدهای این نوشته رو هم می‌تونید اینجا ببینید.</description>
                <category>اسنپ</category>
                <author>Amirhossein Nouri</author>
                <pubDate>Tue, 08 Feb 2022 12:09:40 +0330</pubDate>
            </item>
                    <item>
                <title>Snapp.ir ‌v2 solution design [SSR without server]</title>
                <link>https://virgool.io/snapp-eng/snappir-v2-solution-design-ssr-without-server-gloampj9rqbu</link>
                <description>دقیق‌تر بگم Isomorphic با رندرترون. حالا داستان از کجا شروع شد؟نیاز بیزنس:یه وب سایت سریع و سئو-فرندلی میخوایم با دیزاین جدید که دیتای صفحات دینامیک باشه، یعنی هر لحظه مارکتینگ اراده کرد بتونه متن صفحات، متاتگ، عکس، ترتیب قرار گیری المان ها خلاصه تقریبا همه چیز روی پروداکشن عوض کنه.تیم فنی:سولوشن مدنظر ما SSR با یه کش معقول. جلسه تموم شد، رفتیم سراغ پیاده سازی نمونه اولیه که ببینیم عملی هست یا نه. عملی نبود :) چرا؟چون لایبری یو‌آی-کیت اسنپ برای اجرا روی Node طراحی نشده بود :/ یه کم تلاش کردیم که اون جاهایی که توی node ارور میداد رو یه جور دیگه بنویسیم، یه کاری کنیم که روی node بدون مشکل اجرا شه یوآی کیت و بتونیم Nextjs رو بیاریم بالا ولی خیلی دردسر داشت، تغییرات بنیادی لازم بود در حد دوباره نوشتن یوآی کیت از اول. اما چون پلن داشتیم که یوآی کیت ورژن ۲ رو در کوارتر های بعدی بنویسم که خیلی خفن تره، سبک تر، تم-دارک اینا باشه و البته سرور-ساید فرندلی، فعلا بیخیال تغییر ورژن ۱ شدیم و دیگه به ناچار  Next (کلا سولوشن هایی که روی Node اجرا میشد و متدهای window رو نداشت) کنار گذاشتیم و رفتیم در جستجو برای سولوشن‌های احتمالی بعدی. تنها گزینه با شرایط موجود همون CSR یا کلاینت-ساید-رندرینگ بود که وب سایت اسنپ هم یه SPA دیگه بشه برای خودش. برای رفع دشواری سئو در سایت های SPA هم گفتیم که از این سولوشن‌های داینامیک رندرینگ ( rendertron یا prerender.io ) استفاده میکنیم که برای کراولرها صفحه رو استاتیک رندر میکنه و خود گوگل هم تایید کرده بود که مشکلی نداره. رفتیم برای پیاده سازی.داینامیک رندرینگبه یه مشکل جدید برخوردیم،. برای اجرای این روش میخواستیم بر اساس User-Agent رکوئست رو تفکیک و هدایت کنیم به سمت رندرترون یا پادهای فرانت تو کلاود که SPA رو سرو میکردن. ولی وب سایت اسنپ پشت آروان بود با یه کش نیم ساعته و کراولرها هم از از اونجا میبینن سایت رو.از اونجایی ما میخواستیم assetهای متفاوتی بر اساس هدر یوزر ایجنت داشته باشیم باید یه جوری به آروان میگفتیم که روی این هدر خاص کش پالیسی متفاوتی داریم و مثلا اگه گوگل بات اومد، از کش بهش جواب نده، مستقیم بفرسته به کلاود تا ما از رندرترون جوابش رو بدیم، بدون کش، ریسپانس فرش، استاتیک، سئو عالی :)برای این کار میشه از هدر Vary استفاده کرد، که میاد به کش کلاینت‌ها میگه که اون مقداری که داخل Vary هست رو هم جز کلید-کش قرار بده. اما... vary گذاشتن رو یوزر-ایجینت تقریبا مثل اینه که کلا کش رو غیر فعال کنید. چرا؟به عنوان مثال فرق این دوتا یوزر ایجنت فقط توی نسخه‌ی iOS هستMozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Mobile/15E148 Safari/604.1وMozilla/5.0 (iPhone; CPU iPhone OS 12_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Mobile/15E148 Safari/604.1یعنی تقریبا هر دستگاهی یوزر ایجنت خاص خودش رو داره. بیلیون ترکیب مختلف از ورژن انواع دستگاه و نسخه‌ی بروزر و سیستم‌عامل و ...از اونجایی که رکوئست های اسنپ آی ار خیلی بالاست و کش آروان کمک بزرگی هست در کم کردن بار کلاود، این روش دانامیک رندرینگ هم به خاطر معماری زیرساخت عملا قابل پیاده سازی نبود.تصمیم بر این شد که همون SPA بزنیم و بیخیال بهینه‌سازی استایتک برا سئو بشیم. ولی این سولوشنی نبود که خیلی دلچسب باشه برامون،‌ کلاس فنی لازم رو نداشت :(ولی چپتر لید فرانت متعقد بود که میتونیم سورس کد رندرترون رو دستکاری کنیم جوری که فیت نیاز ما بشه، و همین طوری هم بود. چون رندرترون بای دیفالت اسکریت تگ رو حذف میکنه از نتیجه ( فیچر رکوئست آپشنال شدن ) و یه بیس تگ میذاره که لینک ها absolute بشن. که این برای ما دشواری ساز شد بود. چون ما میخواستیم  فقط پری-رندر رو داخل کلاود انجام بدیم و بقیه اینترکشن‌های کاربر با جاواسکریپت سمت کلاینت هندل بشه ( مدل Isomorphic ). اینطوری دیگه نیازی به دوتا ورژن مختلف نبود، یکی برای کاربر یکی برای کراولرها. نتیجه رو هم میتونستیم براحتی روی آروان کش کنیم، سئوی بسیار خوبی داره و کاربران عادی هم فواید از ‌صفحات استاتیک-رندر شده بهره میبردند.خلاصه یه چرخی داخل سورس کد رندرترون زدیم و کاستومایزش‌هایی که لازم داشتیم رو اعمال کردیم و دادیم به دواپس که یه چندتا پاد بیاره بالا ازش داخل کلاود.قدم بعدی این بود که  تمام رکوئست رو از رندرترون رد میکردیم و نتیجه استاتیک رو برگردونیم که راحت کش بشه. که با یه پراکسی-پس تو NGINX انجام شد.ترسیم ساده‌ شده ی پیاده‌سازی نهایییه چندتا نکته‌ی امنیتی و best-practice داره این روش در پیاده سازی رندرترون و حالت‌های race condition که ممکنه برای کش کردن نتیجه‌اش هنگام دیپلوی ورژن جدید پیش بیاد و نیازمندی کش انجین ایکس برای جلوی گیری از DDoS هست که چالش‌های بزرگی نبودند و با مشورت تیم سکوریتی هاردن کردیم. خلاصه برای کسایی که علاقه مند هستند:  اتک رندرترون که هاردن کردن اش با استفاده از renderOnly و urlPattern در کانفیگ هست.ریس-کاندیشن های کش در دیپلوی و جلوگیری از دی-داس، جفتش با کش انجینیکس قابل حله، فقط رندرترون نباید از بیرون قابل دیدن باشه و روی اون location یی که توی انجینکیس کانفیگ کردید کشه انجین ایکسی بذارید. درنهایت نتیجه‌ی این سولوشن دیزاین و پیاده‌سازیش رو میتونید روی https://snapp.ir مشاهده کنید. هر پیجی رو که  رکوئست بدید یک ریزالت سرور-ساید رندر شده تحویل میگیرید، بعد از اون جاواسکریپت لایبری فرانت ( preact ) لود میشه و اپلیکیشن Hydrate و اینترکتیو میشه. از این لحظه به بعد تمام اینترکشن‌های صفحه با کلایت هست و کلایت-ساید روتینگ ( با react-router ) که روت متناظرش lazy-load میشه و صفحه‌ی جدید رندر میشه. curl headers, homepage snapp.irو از لحاظ عملکرد هم نتیجه‌ی لایت هوس روی هوم پیج دیزاین جدید اگر تمایل داشتید در ادامه، پارت-۲، یه مقاله دیگه درباره‌ی جزئیات پیاده سازیش و نحوه‌ی کانفیگ کردن هم برای کسایی که علاقه مند بودن مینویسم. </description>
                <category>اسنپ</category>
                <author>محمدرضا اعلا</author>
                <pubDate>Sun, 06 Feb 2022 23:38:15 +0330</pubDate>
            </item>
                    <item>
                <title>وجب زدن اندروید در اسنپ!</title>
                <link>https://virgool.io/snapp-eng/vajab-snapp-bjvauonsxja8</link>
                <description>مقدمههمیشه تو برنامه نویسی اندروید یک چالش گریبان‌گیر همه‌ی ما بوده. چالشی که موقع پیاده‌سازی دیزاین(طراحی) هر صفحه زمان زیادی میگیره و بچه‌های تیم  QA هم موقع تست کلی ایرادهای متنوع پیدا میکنن و باید دوباره زمان بذاریم و رفعشون کنیم. این چالش چیزی نیست جز «سایزها و اندازه‌های مختلف گوشی‌های اندرویدی» که همه‌ی ما برنامه‌نویسای اندروید بارها باهاش دست و پنجه نرم کردیم.اگر شما هم با این چالش رو به رو شدین و دوست دارید بدونید تو تیم اندروید اسنپ cab چطوری رفع شده این مقاله میتونه براتون جالب باشه.يادآوری:بهتره برای شروع مفاهیم Pixel, DPI, DSP و SP رو با هم مرور کنیم.پیکسل (Pixel): کوچکترین عنصری که در یک صفحه نمایشگر آدرس‌دهی میشه تا تصویری رو نشون بده.اDP یا Dots Per Inch (که بهش PPI یا Pixel Per Inch هم میگن): تعداد پیکسل‌هایی که در یک اینچ مربع قرار میگیرند و اصطلاحا رزولوشن گفته میشه. وقتی میگیم رزولوشن یه صفحه نمایشگر 160dpi هست یعنی در یک اینچ مربع از صفحه نمایشگر ۱۶۰ پیکسل وجود دارد.طبق همین تعریف اگر بخوایم در نمایگشر شکلی بکشیم که ۲ پیکسل عرض و ۲ پیکسل طولش باشه همینطور که تو تصویر میبینید با توجه به رزولوشن نمایشگر اندازه‌ی شکل کوچک و بزرگ میشه.https://www.altova.com/بع عکس از اونجایی که اتفاقی که می‌افتاده اصلا خوشایند نیست واحد دیگه ای به اسم DP یا DensityIndependent Pixels تعریف شد که همونطور که از اسمش مشخصه به ما این امکان رو میده که شکل مورد نظرمون تو رزولوشن‌های مختلف یکسان و یک‌اندازه دیده بشه.SP یا Scalable Pixels : مانند DP مستقل از رزولوشن هست فقط با این تفاوت که فقط برای اندازه‌ی فونت‌ها استفاده میشه و مقداری که کاربر برای سایز فونت تو تنظیمات گوشی مشخص می‌کنه در این عدد ضرب میشه.بر اساس تعریف‌های بالا وقتی بخوایم شکلی رو توی صفحه نمایش بدیم باید سایزش بر حسب پیکسل مشخص باشه. کاری که ما انجام میدیم اینه که در فایل XML شکل رو بر اساس واحد DP یا SP قرار میدیم و با فرمول زیر ابعادش بر حسب پیکسل محاسبه میشه.px = dp * (dpi/number_of_pixles)مشکل یا چالشبه قول تریماگاسی، Obvious. مشکل این بود که محتوای صفحات حتی در نمایشگرهای کوچک هم به راحتی قابل نمایش باشن. یک راه کلی استفاده از دیزاین‌های متفاوت بر اساس رزولوشن‌های متفاوته ولی این راه حل همیشه پاسخگوی نیاز ما نیست و تو مواردی نمیتونیم اجزای رو عوض کنیم. مثلا در صفحه درخواست سفر و صفحه سفر، داشتن دیزاین‌های متفاوت برای رزولوشن‌های متفاوت عملا برای ما امکان‌پذیر نبود.با استفاده از واحدهای DP و SP مشکل پیکسل‌ها و رزولوشن‌های مختلف برطرف میشه ولی مساله‌ی دیگه ابعاد متفاوت گوشی‌های اندرویده. راه حل متداول این مشکل پیاده‌سازی چند لایه‌ی مختلف برای یک صفحه بر اساس Smallest Screen Width هست و همونطور که اشاره کردم این کار منابع زیادی از نظر زمان و تعداد افراد مصرف می‌کنه.تو دیوایس‌های بزرگ فاصله‌ی بین اجزای صفحه خیلی زیاد می‌شد و نوشته‌ها هم با توجه به بزرگی صفحه نمایش کوچک می‌شدن. تو دیوایس‌های کوچک هم به خاطر زیاد بودن اجزای صفحه، آیتم‌ها مثل اسپاگتی در هم فرو می‌رفتن. زمان زیادی از اعضای تیم صرف رفع کردن مشکلات دیزاین تو سایزهای مختلف می‌شد.راه حل و ایده ایده‌ی کلی از SDP الهام گرفته شده. به جای داشتن چند لایه‌ی مختلف، چند دایمنشن(Dimention) مختلف تو Smallest Screen Width متفاوت نوشتیم.واحد جدیدی به اسم RSP یا Responsive Scalable Pixels و RDP یا Responsive Density-Independent Pixels ایجاد کردیم و اندازه‌ی مبنا رو ابعاد sw390dp در نظر گرفتیم و باقی swها رو با اختلاف مقیاس از این مبنا محاسبه کردیم.یعنی در دایمنشن‌های مبنا هر یک DP یا SP برابر با یک RDP یا RSP هست. https://gist.github.com/rvhamed/df280748eba23927bd68668ad4976b99 محاسبه‌ی باقی swها که بزرگتر از ۳۹۰ هستن به صورت زیر است :base = 390,  target = 420dp = (target/base) * rdp https://gist.github.com/rvhamed/6fae8483b3b7be845c4762e7b26e8607 و اما نتیجه‌ی کار؟در تصویر زیر ۴ گوشی مختلف رو می‌بینید، دو گوشی بالا پیاده‌سازی یک صفحه‌ی نمونه با سایزهای SP و DP هستند و دو گوشی پایین پیاده‌سازی همون صفحه با سایزهای RSP و RDP. با استفاده از این روش اپلیکیشن اسنپ روی گوشی‌های مختلف با ابعاد و رزولوشن‌های متفاوت تا حد زیادی به یک شکل دیده میشه.مقایسه DP با RDP  به عنوان آخرین نکته هم این رو بگم که ما ابعاد مستقیم رو داخل کد قرار نمیدیم و به جاش ابعاد رو داخل تم‌ها میذاریم و به صورت ویژگی یا attribute ازشون استفاده میکنیم. اینطوری ابعاد جدیدی که تعریف کردیم داخل کد نیست و کد تمیزتر به چشم میاد. https://gist.github.com/rvhamed/e25c89a3768f48959ad9083332c75d5a این مدل پیاده‌سازی در نسخه‌ی فعلی اسنپ استفاده نشده و قراره که در نسخه‌های بعدی ازش استفاده کنیم. در آخر یه تشکر ویژه هم از  سینا فرحزادی میکنم که کمک کرد این مقاله رو بنویسیم و نقش خیلی مهمی تو پیاده سازی این ایده داشت. ❤️و یک تشکر ویژه هم از نازنین دست سری میکنم که تو ویراستاری مقاله کمک شایانی کرد. ❤️امیدوارم این مقاله براتون مفید بوده باشه.  ☺️ اگه سوالی داشتین میدونین کجا پیدام کنین. همه جا!</description>
                <category>اسنپ</category>
                <author>hamed.rahimvand</author>
                <pubDate>Sun, 06 Feb 2022 11:10:56 +0330</pubDate>
            </item>
                    <item>
                <title>استفاده از تجزیه ماتریسی برای پیش‌بینی سرعت آفلاین در اسنپ</title>
                <link>https://virgool.io/snapp-eng/usage-of-matrix-factorization-to-predict-offline-speeds-at-snapp-aqszq458uajh</link>
                <description>تصویر ۱ - صفحه‌ی اصلی اپلیکیشن اسنپ: زمان تقریبی رسیدن که قبل و بعد از شروع سفر به کاربر نمایش داده می‌شود.مقدمهدر این مقاله سعی می‌کنیم به کمک هم، مفهوم و کاربرد تجزیه‌ی ماتریسی (Matrix Factorization) رو یاد بگیریم و ببینیم چطور توی اسنپ تونستیم از این روش برای پیش‌بینی سرعت آفلاین تو مسأله ترافیک استفاده کنیم.توی اسنپ یکی از اهداف مهم تیم نقشه محاسبه ETA یا همون «زمان تقریبی رسیدن» هست. ما از ETA در قسمت‌های مختلف اپلیکیشن و سرویس‌های اسنپ (مانند پیشنهاد سفر، قیمت‌گذاری سفر و نمایش به کاربر) استفاده می‌کنیم.به عنوان مثال، به ازای هر سفر یک راننده، یک ETA بر اساس سرعت‌هایی که تو طول معابر اون سفر ثبت شده محاسبه می‌شه. فاصله باقیمانده تا مقصد به سرعت‌های قبلی موجود تقسیم می‌شه تا زمان رسیدن تقریبی تخمین زده شه.ما از سرویسی به نام «Typical Speeds» در کنار سیستم پیشبینی بلادرنگ (real-time) سرعت خودمون استفاده می‌کنیم تا از وجود سرعت قابل‌ اطمینان تو ساعات مختلف روز برای محاسبه ETA مطمئن باشیم چرا که ممکنه در معبری خاص از معابر دخیل تو محاسبه ETA سرعتی ثبت نشده باشه. در نتیجه برای چنین شرایطی ما به سرعتی که معمولا راننده‌ها تو گذشته و تو اون ساعت خاص ازش عبور کردن استناد می‌کنیم و ازش استفاده می‌کنیم.همونطور که می‌دونید ترافیک در طی روزها و ساعت‌های مختلف ممکنه ثابت نباشه و سیستم Typical Speeds ما ممکنه بر اساس عواملی مانند ترافیک غیرمنتظره یا تصادف دستخوش تغییر بشه، این‌جاست که دو سرویس پیش‌بینی آفلاین و بلادرنگ ما به کمک هم حالت‌های مختلف ترافیک رو پوشش می‌دن.برای اینکه ETA قابل اعتمادی تولید کنیم لازمه که توی ماتریس معبر-سرعت‌مون در بازه‌های زمانی مختلف روز و هفته کم‌ترین سرعت خالی رو داشته باشیم. همونطور که در بالا اشاره کردیم ممکنه معابری در سطح کشور وجود داشته باشه که راننده‌های اسنپ از اون رد نشدن یا کم‌تر رد شدن و مشکلی که برای ما بوجود میاد، وجود ماتریس پراکنده‌ای (تنک یا اسپارس)، مثل شکل زیر، بر اثر تولید نشدن سرعت در بازه‌های زمانی مختلفه که ما به کمک روش تجزیه‌ی ماتریسی تونستیم اون رو حل کنیم.تصویر ۲ - نمونه‌ای از یک ماتریس ۳ در ۳ سرعت (با ابعاد معابر و زمان) که دو عدد از خونه‌های آن خالی می‌باشد. آشنایی با مفهوم تجزیه‌ی ماتریسی (Matrix Factorization)تجزیه‌ی ماتریسی یک مدل تعبیه‌سازی (Embedding Model) به‌حساب میاد و سعی می‌کنه با تجزیه ماتریس اصلی به حاصل‌ضرب دو ماتریس با ابعاد پایین‌تر رنک ماتریس رو کم کنه. رنک یک ماتریس به معنای تعداد سطر یا ستون‌های مستقل خطی‌ اون ماتریسه. استفاده اصلی این روش توی بحث سیستم‌های توصیه‌کننده (Recommender System) هستش که سعی‌ می‌کنه یک‌سری ويژگی که ما‌به‌ازای بیرونی (Latent Factor) ندارند رو پیدا کنه و ضرب ماتریس‌های شکسته شده فاصله کمینه‌ای با ماتریس اصلی داشته باشه (تا ماتریس‌های تا حد امکان مشابه تولید بشه). حالا با ضرب خونه‌های ماتریس‌های تجزیه شده تو هم می‌شه خونه‌های خالی تو ماتریس اصلی رو پیش‌بینی کرد.اگر دوست دارین با جزئیات ریاضی این روش بیشتر آشنا شین پیشنهاد می‌دم این لینک رو بخونید.تصویر ۳ - نمونه‌ای از تجزیه ماتریس User-item در سیستم‌های توصیه‌کننده. مثلا در مثال بالا می‌تونین ببینین که یه ماتریس User-item داریم که سطرهاش کاربرای سیستم توصیه‌کننده‌مون هستن و ستون‌هامون لیستی از آیتم‌های مختلف. هر خونه از این ماتریس امتیازاتی هستش (۱ تا ۱۰) که کاربرای مختلف به آیتم‌های (فیلم، محصول، معبر و ..) مختلف دادن. اما ممکنه بعضی از خونه‌های این ماتریس به دلیل عدم مواجهه آیتمی توسط کاربر خالی باشن و ما بخوایم با استفاده از روش معرفی شده پیش‌بینیشون کنیم و برای توصیه کردن ازشون استفاده کنیم. پس برای بدست اوردن مقدار ۸ تو خونه پایین (که فرض ما اینه که از ابتدا وجود نداره)، سطر آبی رنگ ماتریس Q رو در ستون‌ آبی ماتریس P (که ماتریس‌های تجزیه شدنمون توسط تکنیک تجزیه‌ی ماتریسی هستن) ضرب می‌کنیم تا مقدار ۸ بوجود بیاد و اینطوری ما یک پیش‌بینی از امتیاز مورد نظرمون داریم.روش پیشنهادیتو مسأله ما این بار بجای وجود ماتریسی با ویژگی‌های کاربر و آیتم، ماتریس پراکنده و بزرگی داشتیم از زمان‌های مختلف هفته و معابر مختلف شهر‌های ایران. توی خونه‌های این ماتریس سرعت‌ها به ازای زمان و معبر ثبت شدند که از حرکات رانندگان اسنپ که توی سفر هستند بدست میاد. منظور از پراکنده بودن ماتریس (sparsity) اینه که ممکنه ما برای خیلی از زمان‌های مختلف توی معابر مختلف هیچ سرعت ثبت شده‌ای نداشته باشیم اما برای محاسبه زمان تقریبی رسیدن بهشون نیاز داریم. این‌جاست که با کمک تکنیک تجزیه‌ی ماتریسی تونستیم مقادیر خونه‌های خالی رو پیش‌بینی کنیم. اما چطور؟ به کمک چه ابزاری؟تصویر ۴ - نمونه ماتریس فضا-زمان. خب از اول گفتن لازم نیست چرخ رو دوباره اختراع کنیم. اما باید حواسمون باشه که ممکنه چرخی که شما سابقه استفاده رو داشتین چرخ دوچرخه بوده اما این‌بار چرخ کامیون مورد نیازتونه. اینجاست که ورود مسأله به کلان‌داده یا همون Big Data باعث می‌شه ما بریم سمت انتخاب چرخ درست برای حل مسأله با ابعاد خودمون.همونطور که محمد توی مقاله خودش در ارتباط با «نمایش نقاط پرتکرار برای مسافران اسنپ» توضیح داد، ما از Apache Spark برای پردازش داده‌های عظیم استفاده می‌کنیم. فرض کنید ماتریسی که ما به ازای هر هفته پردازش می‌کنیم ممکنه ابعاد میلیونی داشته باشه و طبیعاتاً استفاده از روش‌های موازی‌سازی مثل اسپارک خیلی کارمون رو راحت‌تر می‌کنه (البته که چالش‌های شیرین خودش رو هم ایجاد می‌کنه و همچنان بحث مدیریت منابع یک دغدغه اصلی برای کار با کلان‌داده‌هاست که جلوتر چند نمونه‌اش رو بررسی کردیم).ابزاری که توی آپاچی اسپارک با هدف تجزیه‌ی ماتریسی تعبیه شده رو توی کتاب‌خونه MLlib اسپارک تونستیم پیدا کنیم. همونطور که اسمش قابل‌حدس هست، این کتاب‌خونه حاوی الگوریتم‌های مختلف یادگیری ماشینه که بصورت کامل با اسپارک پیاده‌سازی شدن و برای پردازش‌ داده‌های حجیم بهینه‌ان.الگوریتم ALS (Alternating Least Square) یک روش پیاده‌سازی بصورت موازی تجزیه‌ی ماتریسی هستش که تو سال ۲۰۰۹ تونست توی مسابقه‌ای که نتفلیکس برای پیدا کردن بهترین الگوریتم برای استفاده توی سیستم‌های توصیه‌کننده برگزار کرد مقام اول رو کسب کنه و پیاده‌سازی کاملش توی کتاب‌خونه نام‌برده وجود داره.تو مسأله ما، بعد از گرفتن داده‌های هفته‌های مورد نظر، ما شروع به تمیزکاری‌های اولیه داده می‌کنیم. مثل حذف داده‌های پرت که تو چند مرحله و با اعمال چند فیلتر و منطق انجام می‌شه. بعد از حذف داده‌های پرت ما یک ماتریس پیش‌پردازش شده آماده برای ورود به مدل ALS مون داریم که ستون‌هاش شامل بازه‌های زمانی هفتگی و سطرهای اون شامل آی‌دی‌های منحصر بفرد معابرمون هستش. هر خونه این ماتریس هم ممکنه شامل یک سرعت از قبل ثبت شده باشه یا نباشه (که هدف در نهایت اینه هیچ خونه‌ای از این ماتریس خالی نمونه).مدل ALS هم مثل هر مدلی یک‌سری هایپرپارامتر داره که شامل رنک ماتریس ALS، حداکثر تکرار بهینه‌ساز تابع هدف و پارامتر رگرسیون برای جلوگیری از بیش‌برازش هستش که برای حفظ سادگی مقاله از توضیح بیشترشون خودداری می‌کنم و شمارو به این لینک برای مطالعه بیشتر ارجاع می‌دم.برای پیدا کردن بهترین پارامترها با داشتن داده‌های فعلی‌مون، از روش Grid Search استفاده کردیم که پیاده‌سازی اسپارکی اون رو می‌تونین این‌جا پیدا کنید.تو این روش ابتدا یک آرایه از مقادیری به ازای هر هایپرپارامتر که حس کردیم می‌تونن کاندیداهایی خوبی باشن به‌عنوان ورودی به این کلاس دادیم و بعد یه روش ارزیابی مثل در اوردن خطای MAE استفاده کردیم و در کنار CrossValidator، که وظیفه تجزیه داده‌های ورودی به k بخش رو داره که هر دفعه 1/k مجزا رو به عنوان داده تست استفاده می‌کنه، استفاده کردیم و در نهایت با مقایسه خطای هر مدل بهترین مدل با کم‌ترین خطا رو پیدا کردیم.حالا با داشتن بهترین مدل، داده ورودیمون رو به مدل دادیم و از نتیجش برای پیش‌بینی سرعت‌های جدید استفاده کردیم.تصویر ۵ - خیابان سعیدی در نقشه اسنپ. بیاین یک مثال از دنیای واقعی رو به کمک هم بررسی کنید. فرض کنین می‌خوایم بررسی کنیم این پیش‌بینی‌ها برای خیابون سعیدی (خیابون کنار شرکتمون) چطور عمل کرده و برای ساعات مختلف روز چه پیش‌بینی‌هایی برای سرعت تولید کرده.مثلا مشاهده کردیم که برای بازه ۱۲ تا ۱۵ هر روز (به غیر از جمعه‌ها) یه الگوی مشخص وجود داره و سرعت از ۱۶ کیلومتر بر ساعت شروع می‌شه و رفته‌رفته با نزدیک شدن به ساعت‌های پایان کار اداره‌جات، این سرعت هم کاهش پیدا می‌کنه و تا ۱۰ کیلومتر بر ساعت می‌ره از نظر مدل ALS که نشون می‌ده تونسته درک درستی از ترافیک اون معبر در زمان‌های مختلف داشته باشه.برای اینکه از صحت این سرعت‌ها نیز مطمئن بشیم اون رو با چندتا از داده‌های سرعت واقعی ثبت شده توی سیستممون تو اون ساعت مقایسه کردیم و اعدادی که مشاهده کردیم با دقت خیلی خوبی نزدیک بودن که نتیجه‌اش رو می‌تونین در نمودار زیر ببینین:تصویر ۶ - نمودار سرعت‌های ثبت شده برای خیابان سعیدی از بازه ۹:۳۰ تا ۱۲:۳۰ و ۱۴:۳۰ الی ۱۵  (سرعت‌های واقعی ثبت شده) و از ۱۲:۳۰ تا ۱۴:۳۰ (سرعت‌های پیش‌بینی شده توسط مدل ALS) که مدل تونسته به خوبی توالی مقادیر سرعت و ترافیک رو یاد بگیره.بعد از اینکه یه بازه زمانی خاص از خیابون سعیدی توی یک روز خاص رو بررسی کردیم، رفتیم سراغ بررسی عملکرد مدلمون توی کل هفته.تصویر ۷ - نمودار مقایسه مقادیر سرعت واقعی ثبت شده و سرعت پیش‌بینی شده توسط مدل ALS طی یک هفته مربوط به خیابان سعیدی.همونطور که تو نمودار بالا می‌بینین، در مقایسه با سرعت‌های واقعی ثبت شده طی اون هفته، مدل ما تونسته با دقت بالایی مقادیر سرعت و جریان ترافیک رو تشخیص بده و به ما اطمینان بده که می‌تونیم از پیش‌بینی‌هاش به‌عنوان رویکرد جدید برای Typical Speedsمون استفاده کنیم و سرعت‌های ناموجود رو موجود کنیم و دقت ETAمون رو هم متعاقباً افزایش بدیم.پایانممنون که پست رو تا انتها خوندید. خوشحال می‌شم اگر نظر یا سؤالی دارید، توی کامنت‌ها با من در میون بگذارید. از مهدیه زینالی هم برای فراهم کردن تصویرسازی‌های زیبای این پست بسیار ممنونم.در ضمن، اگه حس می‌کنین از پروژه‌هایی شبیه به این خوشتون میاد و در نتیجه علاقه دارید به تیم ما ملحق بشید، خوشحال می‌شیم که رزومه‌هاتون رو از طریق آدرس engineering@snapp.cab برای ما ارسال کنید. مراقب خودتون باشید!</description>
                <category>اسنپ</category>
                <author>طاده الکسانی</author>
                <pubDate>Thu, 16 Dec 2021 14:27:15 +0330</pubDate>
            </item>
                    <item>
                <title>سردرگمی‌های کار کردن با گوگل-آنالاتیکز جدید (Google Analytics)</title>
                <link>https://virgool.io/snapp-eng/%D8%B3%D8%B1%D8%AF%D8%B1%DA%AF%D9%85%DB%8C-%D9%87%D8%A7%DB%8C-%DA%A9%D8%A7%D8%B1-%DA%A9%D8%B1%D8%AF%D9%86-%D8%A8%D8%A7-%DA%AF%D9%88%DA%AF%D9%84-%D8%A2%D9%86%D8%A7%D9%84%D8%A7%D8%AA%DB%8C%DA%A9%D8%B2-%D8%AC%D8%AF%DB%8C%D8%AF-google-analytics-xjzy42jgjhij</link>
                <description>احتمالا با این موضوع برخورد کردید که وقتی توی پلنینگ تسکی در مورد یک قابلیت جدید اپلیکیشن مطرح می‌شه خیلی‌ها اشتیاق و علاقه نشون می‌دن ولی وقتی راجع به اندازه‌گیری میزان استفاده از همون قابلیت صحبت می‌شه معمولا دولوپرها از برقرار کردن ارتباط چشمی با شما خودداری می‌کنن و ساکت می‌شن و کسی دوست نداره که روی این موضوع کار کنه. توی این چند سالی که کار کردم کمتر برنامه‌نویسی رو دیدم که به این مبحث علاقه‌ای داشته باشه ولی واقعیت اینه که اگر بخواید ویژگی جدیدی ارائه کنید که از عملکردش مطمئن باشید، باید بحث آمار‌گیری رو هم در نظر بگیرید. از نظر من علت این که خیلی از برنامه‌نویس‌ها به این مبحث علاقه‌ای ندارن، سردرگمی‌هاییه که همراه با این پیاده‌سازی‌ها وجود داره. هدف من از این مقاله درمیون گذاشتن چالش‌هاییه که خودم بهشون بر خوردم تا شاید این سردرگمی ها رو برای شما شما کمتر کنم.البته این رو هم بگم، اشتباه نکنید! من مخالف استفاده از ابزار های آنالاتیکزی مثل گوگل آنالیتیکز نیستم. بر‌عکس، خیلی روی این ابزار تاکید دارم و موارد زیادی بوده که تونستم با استفاده از همین ابزار مشکلات پیاده‌سازی خودم رو پیدا کنم. مثلا توی اسنپ هر باری که اپلیکیشن مسافر (Passenger PWA) رو رلیز می‌کنیم، علاوه بر همه تست‌هایی که داریم، من با استفاده از گزارش‌های ریل‌تایم گوگل‌-آنالاتیکز مطمئن می‌شم که مشکلی وجود نداره و مسافر‌ها بدون مشکل به سفرشون ادامه می‌دن. فکر کنم به اندازه کافی تاکید کرده باشم که چقدر این ابزار می‌تونه به شما کمک کنه و بهتر باشه بریم سراغ سردرگمی‌هایی که صحبتش رو کرده بودم.توجه: خوندن این مقاله رو به کسایی پیشنهاد می‌کنم که تجربه پیاده سازی گوگل-آنالاتیکز رو داشته باشن یا در حال انجام این پیاده سازی باشن. سردرگمی شماره یک: تحریم‌هاتحریم‌های گوگلمتأسفانه این مورد بیشتر اعصاب‌خوردیه تا سردرگمی و فقط محدود به گوگل-آنالاتیکز نیست. بیشتر کاربرهای ایرانی تو زندگی روزمره‌شون با این مورد برخورد دارن. چیزی که اینجا اعصاب‌خوردی رو بیشتر می‌کنه اینه که از قدم اول شروع کارتون با این مشکل مواجه می‌شید. یکی از دردسر‌های رایج، تداخل وی‌پی‌ان شخصی با وی‌پی‌ان شرکته. ممکنه شما نتونید این دو تا رو هم زمان کانکنت کنید. برای حل این مشکل بهترین راه حل استفاده از  یک وی‌پی‌ان بروزر بیس هست. این وی‌پی‌ان‌ها فقط روی همون برزوری که دارید ازش استفاده می‌کنید، تاثیر می‌گذارن و تداخلی با وی‌پی‌ان شرکت شما ندارن. از اون بدتر شرایطیه که محصولتون موقعی که وی‌پی‌ان خارجی وصل باشه بالا نیاد و می‌تونید تصور کنید که توی چه لوپی گیر می‌کنید. بهترین راه‌حل برای این مشکل، استفاده از یک وی‌پی‌ان ساکسه که به ابزاری مثل Charles یا FoxyProxy وصل می‌شه. هر دو ابزار به شما این امکان رو می‌دن که یه بخش مشخصی از نتورکتون رو از داخل پروکسی عبور بدید. پس مراقب این موضوع هم باشید چون انجام همه این کارها می‌تونه خیلی زمان‌بر باشه. سردرگمی شماره دو: داکیومنتیشن پیچیدهاگه کلمه «Google Analytics Documents» رو توی گوگل سرچ کنید با این صحنه رو‌به‌رو می‌شید:داکیومنت‌های مختلف گوگل برای آنالاتیکزمعمولا کسایی که تسک رو برای شما تعریف می‌کنن مشخص نمی‌کنن که شما باید از کدوم داکیومنت استفاده کنید. و طبیعیه کسی که این عکس رو ببینه اول باید تحقیق کنه که فرق این روش‌ها با هم چیه و به نظرم اینم از اون کار‌هاییه که کمتر کسی بهش علاقه داره. گوگل چهار لینک روی این بخش از داکیومنتیشنش گذاشته که کلمه Web و gtag.js  دقیقا یک لینک دارن. واقعا هدفشون رو از این کار نمی‌دونم. شاید می‌خواستن که بیشتر از gtag استفاده بشه ولی با این کار فقط من رو گیج‌تر از قبل کردن. دو لینک دیگه هم دست کمی از قبلی‌ها نداره. analytics.js نسخه قدیمی‌تر گوگل-آنالاتیکز و Google Analytics 4 properties یه داکیومنت دیگه برای gtag هست که یکی از عجیب‌ترین چیز‌هاییه که توی داکیومنت‌ها بهش برخوردم. بعدا راجع این دو داکیومنت مشابه براتون بیشتر توضیح می‌دم. ای کاش گوگل یه همچین صفحه ای برای شروع داکیومنتش گذاشته بود!این عکس ساختگیست و وجود خارجی نداردبه نظرم اگه اینطوری داکیومنت رو شروع می‌کردن، خیلی راحت‌تر می‌تونستم تصمیم بگیرم که از کدوم داکیومنت باید اطلاعاتی که می‌خوام رو پیدا کنم. بخش گوگل-تگ-منیجر (لینک سوم) رو به صورت فرضی اضافه کردم و هیچ ارتباطی با Google Analytics 4 properties نداره.از این حرف‌ها بگذریم، خیلی خلاصه برای شما توضیح می‌دم که هر کدوم از این داکیومنت‌ها به چه درد می‌خوره.۱. اگر از قبل، نسخه سه آنالاتیکز رو روی محصولتون نصب دارید و می‌خواید پیاده‌سازیتون رو کامل‌تر کنید یا این که اگر می‌خواید از قابلیت هایی از آنالاتیکز استفاده کنید که هنوز توی نسخه ۴ پیاده سازی نشدن (پیشنهاد می‌کنم حتما اول راجع به این موضوع تحقیق کنید) از داکیومنت Analytics.js استفاده کنید. این کتابخونه کارش برقراری ارتباط بین محصول شما با یک پراپرتی نسخه ۳ گوگل آنالاتیکزه. ۲. اگر گوگل-تگ-منیجر ندارید و می‌خواید آنالاتیکز نسخه ۴ رو پیاده‌سازی کنید، از داکیومنت gtag استفاده کنید. این ابزار جدید گوگل برای لود کردن گوگل-آنالاتیکز نسخه ۴ به بعده. البته gtag چیز‌های دیگه‌ای رو هم می‌تونه به محصول شما وصل کنه و صرفاً برای آنالاتیکز ساخته نشده. ۳. اگر تگ منیجر دارید و می‌خواید از نسخه سه یا چهار آنالاتیکز استفاده کنید بهتره که اول داکیومنت جی‌تگ(نسخه چهار)‌ یا analytics.js (نسخه سه) رو مطالعه کنید و بعد هم داکیومنت خود تگ منیجر رو بخونید(البته بعید می‌دونم از داکیومنت تگ منیجر خیلی چیزی یاد بگیرید. به نظرم توی یوتوب آموزش های گوگل-تگ-منیجر رو ببینید، خیبی بهتره) و تا جایی که امکان داره از زدن custom HTML script مخصوصا برای نسخه چهار خودداری کنید و از امکانات خود گوگل-تگ-منیجر برای این کار استفاده کنید. نمی‌دونم چرا گوگل، سکشن google analytics 4 properties رو اضافه کرده و توضیحات تقریبا یکسانی رو توی دو داکیومنت گذاشته. مثلا برای فرستادن page view این دو داکیومنت موجوده:نحوه فرستادن page view در داکیومنت gtagو همون توضیح با یکم اختلاف در فیلد‌ها، در داکیومنت Google Analytics v4 Properties:نحوه فرستادن page view در داکیونت GA 4 propertiesوجود این اختلاف‌ها منو خیلی به شک انداخت. فکر می‌کردم شاید دارم اشتباه می‌کنم و گاهی چون با سرچ به این داکیومنت‌ها می‌رسیدم، فکر می‌کردم که داکیومنت قبلی رو با دقت نخوندم و یا فراموش کردم در حالی که هر دو این داکیومنت‌ها یک کار رو توضیح می‌دن.یک مورد دیگه از این اختلاف ها، وجود تفاوت های جزئی در اسکریپت نصب گوگل آنالاتیکز در دو داکیومنته:  مقایسه اسنیپت نصب Google Analytics در دو داکیومنتحتی اسنیپت‌های ارائه شده برای نصب هم دقیقا توی داکیومنت ها یکی نیست و گاهی از کلماتی مثل Measurement ID به جای GA Measurement ID استفاده شده که شاید برای کسی که تازه وارد این موضوع شده باشه، این سو تفاهم رو پیش بیاره که gtag یک آی‌دی متفاوت از آنالاتیکز داره.پیشنهاد من اینه که فقط یکی از دو داکیومنت gtag یا Google analytics 4 properties رو مطالعه کنید و جاهایی که به پاسخ سوالتون نرسیدید، اولین کاری که می‌کنید این باشه که همون بخش رو در صورت وجود توی داکیومنت دیگه پیدا کنید و مطالعه کنید. چون در حال حاضر این اختلاف‌ها وجود داره.سردرگمی شماره سه: Gtag در مقابل گوگل-تگ-منیجراین یکی از اولین اشتباه‌های من موقع پیاده سازی ابتداییم بود. من دقیقا نمی‌دونستم که هدف استفاده از gtag چیه و فکر می‌کردم مشابه analytics.js باشه و به صورت مسخره ای با استفاد از گوگل-تگ-منیجر اسکریپت gtag رو به صفحه اضافه کردم و بعد سعی کردم که آنالاتیکز رو با gtag لود کنم. این کار  عملیه ولی بعد از مدتی دست و پا زدن این داکیومنت رو خوندم:داکیومنت انتخاب بین GTM و gtagبا این که بخش زرد رنگ پایین داکیونت توضیح داده که نباید با استفاده از custom HTML tag، اسکریپت gtag رو به سایتتون اضافه کنید. من این کار رو قبل از خوندن این داکیومنت انجام دادم و تونستم کارمو راه بندازم. مشکل اینجاست که گوگل این روش رو به طور رسمی پشتیبانی نمی‌کنه و این مدل پیاده‌سازی ممکنه در آینده کار نکنه و شما مجبور بشید همه چیز رو تغییر بدید. توی داکیومنت نوشته که اگر می‌خواید از تگ منیجر برای لود کردن مثلا آنالاتیکز نسخه ۴ استفاده کنید، باید به جای custom HTML tag از قابلیت های پیش ساخته خود GTM استفاده کنید. گیج‌کننده ترین بخش این ماجرا اینه که بعد از استفاده از تگ های پیش‌ساخته گوگل-تگ-منیجر، می‌بینید که اسکریپت gtag به صفحتون اضافه می‌شه و اگه داکیومنت رو دقیق‌تر بخونیم توی یک بخش خیلی زیر پوستی به این موضوع اشاره می‌کنه:اشاره به استفاده GTM از gtagاضافه شدن gtag توسط گوگل-تگ-منیجر، باعث شد که من این برداشت اشتباه رو بکنم که خودم اجازه دارم با کاستوم اسکریپت هرطوری که دوست دارم از داخل گوگل-تگ-منیجر، gtag رو کنترل کنم (که به نظرم باید همین طور می‌بود) ولی ظاهرا فعلا گوگل خیلی تمایلی نداره که شما این کار رو انجام بدید و ممکنه که بخوان در آینده این پیاده سازی‌های داخلی خودشون رو تغییر بدن. لزومی نداره که بین گوگل-تگ-منیجر و gtag حتما یکی رو انتخاب کنید. ولی دقت کنید که هیچ وقت gtag رو با استفاده از custom HTML script های گوگل-تگ-منیجر به محصولتون اضافه نکنید و اگر می‌خواید gtag رو با استفاده از گوگل-تگ-منیجر داشته باشید حتما از امکانات پیش ساخته گوگل-تگ-منیجر استفده کنید تا دردسر کمتری داشته باشید. سردرگمی شماره چهار: Debuggingاگر قبلا با آنالاتیکز کار کرده باشید، به احتمال خیلی زیاد از اکستنشن دیباگ گوگل آنالاتیکز هم استفاده کردید. البته شما لزوماً نباید این اکستنشن رو نصب کنید ولی پیشنهادم اینه که این کار رو بکنید چون روش های دیگه‌ی انجام این کار خیلی زمان‌برن و آورده خاصی ندارن. اسم این اکستنشن معروف که خیلی به دیباگ شما کمک می‌کنه Google Analytics Debugger و خیلی وقته که توسط تیم گوگل دولوپ شده. کاری که این اکستنشن انجام می‌ده اینه که به گوگل آنالاتیکز یا gtag می‌گه که لطفا هر اتفاقی می‌افته رو برای کاربر من لاگ کن. دیباگر گوگل آنالاتیکزهمون طور که احتمالا قبلا هم دیدید، لاگ‌های خیلی مفیدی از این دیباگر توی کنسول شما ایجاد می‌شه. برای مثال این لاگ آنالاتیکز نسخه ۳ هست که روی اپلیکیشن مسافر اسنپ نصب می‌شه:لاگ دیباگر آنالاتیکز ۳همه چیز خیلی تمیز و خواناست و من واقعا از لاگی که تولید می‌کنه، راضی‌ام. ظاهرا گوگل، سورس کد همین دیباگر رو توی نسخه ۴ آنالاتیکز هم استفاده کرده. منتها به نظر میاد این کار خیلی به درستی انجام نشده و نسخه ۴ آنالاتیکز در حالی رلیز شده که هنوز ارتباطش با لاگر دیباگ مود درست پیاده‌سازی نشده. علتش رو نمی‌دونم ولی شاید به خاطر ددلاین رلیز آنالاتیکز ۴ این کار رو کرده باشن. در هر صورت نتایجی که این کار به بار آورده برای برنامه نویس‌ها جالب نیست. برای مثال خیلی از فیلد‌های داخل لاگ درست پر نمی‌شن.لاگ دیباگر آنالاتیکز ۴دیدن فیلد‌های undefined توی لاگ های نسخه ۴ آنالاتیکز چیز عجیبی نیست. من اول فکر می‌کردم که موقع پیاده‌سازی اشتباهی انجام دادم و این باعث شده که gtag نتونه تشخیص بده باید اطلاعات جمع‌آوری شده رو به کجا بفرسته ولی بعد متوجه شدم این مشکل از سمت خود گوگل هست چون من ایونت‌هایی که ارسال می‌شد رو توی پنل آنالاتیکز دریافت می‌کردم. سه تغییر بزرگ و مثبت نسخه جدید آنالاتیکزدرسته که نسخه جدید گوگل آنالاتیکز (به نظر من)‌ هنوز خیلی آماده و تکمیل نیست ولی همین نسخه اولیه هم یه مزایایی داره که گفتنش خالی از لطف نیست. می‌خوام سه موردی که برای من به عنوان یک برنامه نویس جذاب بود رو براتون تشریح کنم. نکته اول اینه که توی این نسخه سعی شده فاصله بین موبایل-اپ و وب به حداقل برسه. قبلا کاستوم ایونت‌های آنالاتیکز چند فیلد محدود داشتن:‌ category, action, label and value. توی نسخه جدید این فیلد ها رو به انتخاب خودتون می‌سازید. این ویژگی خیلی دست منو توی انتخاب نوع ایونت‌هام و ساختن کدی که قابل درک‌تر باشه، کمک کرد و همینطور داکیومنت‌های بین اپلیکیشن نیتیو و تحت وب می‌تونه مشترک باشه.نکته دوم دیتا‌هاییه که خود آنالاتیکز به صورت خودکار از سایت شما جمع‌آوری می‌کنه:‌جمع‌آوری خودکار اطلاعات ابتداییاین قابلیت می‌تونه به خیلی از اپلیکیشن‌هایی که پیاده سازی ساده‌ای دارن این کمک رو بکنه که هر چیز جزئی‌ای رو داخل کد پیاده‌سازی نکنن. نکته سوم که به نظرم مهم‌ترین و کاربردی‌ترین بخش گوگل آنالاتیکز جدید برای برنامه‌نویس‌هاست، دیباگر ریل تایمه. یکی از بزرگترین دردسر‌های کار کردن با گوگل آنالاتیکز اینه که ممکنه تا ۲۴ ساعت طول بکشه تا دیتای شما پردازش بشه. این یعنی هر بار یک تغییر توی پیاده‌سازی‌تون می‌دید ممکنه تا ۲۴ ساعت نتونید از نتایج تغییراتتون اطمینان داشته باشید. با قابلیت دیباگ آنالاتیکز جدید شما می‌تونید تا حد خوبی به صورت زنده ببینید که قراره چه چیز هایی توی دیتابیس آنالاتیکز شما ذخیره بشه. دیباگر ریل تایم گوگل آنالاتیکز ۴به نظرم این یکی از بهترین ویژگی‌های گوگل آنلاتیکز چهاره و کار دولوپر رو خیلی راحت‌تر می‌کنه. امیدوارم تونسته باشم با اشاره به این موضوع‌ها حداقل یک سرنخ از مشکلاتی که ممکنه باهاش رو‌به‌رو بشید، بهتون داده باشم. اگه بخوام مهم ترین چیز، که موقع کار کردن با گوگل آنالاتیکز یاد گرفتم رو بهتون بگم اینه که: صبور باشید و بی‌خیال نشید، این سردرگمی‌ها برای همه کسایی که با این ابزار کار می‌کنن یه روزی پیش اومده. </description>
                <category>اسنپ</category>
                <author>ایمان محمدی</author>
                <pubDate>Sun, 17 Oct 2021 15:11:35 +0330</pubDate>
            </item>
                    <item>
                <title>استفاده از absolute import در پروژه‌های CRA</title>
                <link>https://virgool.io/snapp-eng/cra-absolute-import-yeva6ol2apr3</link>
                <description>آدرس‌دهی برای دسترسی به فایل‌های مختلف پروژه موضوع جدانشدنی در همه زبان‌های برنامه‌نویسیه که همه ما همه روزه داریم ازش استفاده می‌کنیم. در این مقاله میخوایم راجع به روش‌های مختلف آدرس‌دهی در پروژه‌های CRA و نحوه استفاده از اون‌ها صحبت کنیم. برای اینکار از یک پروژه خام که با create-react-app ساخته شده و یک صفحه، چند کامپوننت و چند تابع کاربردی به اون اضافه شده استفاده میکنیم. (گیتهاب پروژه) ساختار پوشه src پروژه نهایی رو میتونید در عکس زیر مشاهده کنید:ساختار پوشه srcrelative import و معایب آنپروژه‌های CRA به صورت پیش‌فرض برای آدرس‌دهی از relative import استفاده می‌کنند. در این روش باید آدرس فایل مدنظر رو نسبت به جایی که هستیم بنویسیم. برای مثال فرض کنید که صفحه Home نیاز به کامپوننت Content داره و با توجه به ساختار فعلی پروژه، برای استفاده از این کامپوننت ما باید از جایی که پوشه صفحه Home قرار داره دو مرحله در درخت فایل‌هامون به عقب برگردیم و از اونجا آدرس این فایل‌ رو بنویسیم.در این روش کد ما به این شکل درمیاد:استفاده از relative importاما مشکلات این روش چیه و چرا بهتره ازش استفاده نکنیم:۱- اگه ساختار فایل‌های ما خیلی تودرتو یا به اصطلاح nested باشه آدرس فایل‌ها خیلی طولانی میشه و مجبور میشیم چند مرحله در درخت فایل‌ها به عقب برگردیم و بعد از اون مجددا چندین مرحله به جلو بریم تا به فایلی که مد نظرمونه برسیم.۲- همونطور که گفته شد آدرس‌ها نسبت به فایلی که در اون هستیم نوشته میشه و زمانی که قصد جابه‌جایی فایلی رو داشته باشیم، این وابستگی مشکل‌ساز میشه. به عنوان مثال اگر در آینده تصمیم بگیریم فایل صفحه Home رو جابه‌جا کنیم، باید همه آدرس‌هایی که در اون نوشته شده رو به‌روزرسانی کنیم.absolute import، روش جایگزیناینجاست که absolute import به ما کمک میکنه تا این مشکلات رو برطرف کنیم. برای استفاده از این روش یک پوشه از پروژه رو به عنوان مرجع آدرس‌دهی‌ تعریف میکنیم و همه آدرس‌ها رو نسبت به این پوشه می‌نویسیم. در واقع همچنان داریم از روش قبلی استفاده میکنیم با این تفاوت که آدرس فایل‌هارو نسبت به پوشه مرجع می‌نویسیم. این روش به ما کمک میکنه که از برگشت‌های زیاد در درخت فایل‌ها جلوگیری کنیم و با توجه به این که پوشه مرجع ما ثابته با جا‌به‌جا کردن یک فایل نیازی به به‌روزرسانی آدرس‌های داخل فایل درحال جابه‌جایی نداریم.دقت کنید که همچنان نیاز هست که آدرس‌هایی که به فایل در حال جابه‌جا شدن در پروژه اشاره می‌کنند رو به‌روزرسانی کنیم و این امر اجتناب ناپذیره. اما با توجه به این که در این روش آدرس هر فایل هنگام استفاده در هرجایی از پروژه یکسانه، با استفاده از روش جستجو و جایگزینی می‌توان همه آدرس‌ها رو همزمان به‌روزرسانی کرد.فرض کنید پوشه src رو به عنوان پوشه مرجع در پروژه تعریف کنیم، در این صورت آدرس‌‌دهی‌های تکه کد بالا به شکل پایین تغییر پیدا میکنه:استفاده از absolute importبا توجه به ساختار پروژه فعلی، در نگاه اول تفاوت چندانی بین این دو روش دیده نمیشه اما با بزرگ‌تر شدن پروژه به کدهایی مثل کد زیر برمیخوریم و اینجاست که برتری‌های absolute import رو بیشتر حس میکنیم:EwwwwTadaaaتعریف پوشه مرجع در پروژه‌های CRAهمون طور که قبل‌تر اشاره شد پروژه‌های CRA به صورت پیش‌فرض از relative import استفاده می‌کنند. برای تعیین کردن پوشه مرجع فقط کافیه به فایل jsconfig.json یا tsconfig.json که داخل روت پروژه قراره داره این تکه کد رو اضافه کنید. در صورتی که برای ساختن پروژه از قالب تایپ‌اسکریپ CRA استفاده کرده باشید، فایل tsconfig.json از قبل موجود است و فقط نیاز به اضافه کردن baseUrl دارید در غیر این صورت اگر هیچکدام از این دو فایل از قبل وجود نداشتند فایل jsconfig.json رو در روت پروژه ایجاد کنید و تکه کد پایین رو به اون اضافه کنید.بعد از اضافه کردن baseUrl می‌توانید از روش absolute import که در بالا بهش اشاره شد استفاده کنید.نکته: تا قبل از نسخه چهارم CRA این کار رو میشد با مقداردهی NODE_PATH داخل فایل env. هم انجام داد که این روش از نسخه ۴ به بعد منسوخ شد:نحوه تعریف پوشه مرجع قبل از CRA نسخه 4کامیت مرتبط با این تغییراتalias و نحوه استفاده از آنبه طور کلی در دنیای برنامه‌نویسی ما اکثرا با دستورات پرتکرار و گاها طولانی درگیر هستیم. در این شرایط می‌توان برای راحتی استفاده به جای این دستورات اسم‌های کوتاه‌تری تعریف کرد. به این اسم‌ها معمولا alias گفته می‌شود. این موضوع در رابطه با آدرس‌های پرتکرار و بعضا طولانی هم صدق میکنه و ما می‌توانیم با استفاده از alias برای این آدرس‌ها اسم‌های کوتاه‌تر و ساده‌تری تعریف کنیم. برای مثال:برای تعریف alias در پروژه‌های CRA ما نیاز داریم که کمی تغییرات روی تنظیمات webpack اعمال کنیم. با توجه به این که CRA به صورت پیش‌فرض اجازه دستکاری تنظیمات webpack رو نمیده، باید از پکیج‌هایی مثل craco یا پکیج‌های مشابه استفاده کنیم.نکته: روش دیگه دستکاری کردن تنظیمات webpack هم eject کردن پروژه هست که پیشنهاد نمیشه و باید به عنوان آخرین گزینه در نظر گرفته بشه.برای استفاده از craco باید ابتدا به وسیله دستور زیر اون رو به پروژه اضافه کنیم:نصب cracoبعد از این مرحله باید اسکریپت‌های فایل package.json پروژه رو به صورت زیر به‌روزرسانی کنیم که پروژه از تنظیمات craco استفاده کنه:به‌روزرسانی دستورات package.jsonبرای اضافه شدن alias‌ها به تنظیمات webpack باید یک فایل در روت پروژه به اسم craco.config.js بسازیم و کد زیر رو بهش اضافه کنیم:اضافه کردن تنظیمات cracoبا استفاده از تغییراتی که گفته شد می‌توان از alias‌ها در پروژه استفاده کرد اما ادیتورها و IDE‌ها این alias‌هارو شناسایی نمی‌کنند و مسیرهای درست رو به ما پیشنهاد نمی‌دهند. برای حل این مشکل باید تکه کد زیر رو به فایل‌های jsconfig.json یا tsconfig.json اضافه کنیم:اضافه کردن مسیرها به jsconfig.jsonبا استفاده از این روش می‌تونیم آدرس هامون رو به صورت زیر بنویسیم:آدرس‌دهی با استفاده از aliasاین روش در مقایسه با absolute import نیاز به تنظیمات بیشتری داره اما برتری آن قابلیت اضافه کردن alias های مختلف به پروژه است. به عنوان مثال این روش به ما اجازه تعریف یک alias دیگه در کنار @ به اسم components@ رو میده که به src/components اشاره میکنه.دقت کنید که هر alias جدید باید به هر دو فایل craco.config.js و jsconfig.json اضافه بشه.برای استفاده همزمان از alias و eslint، باید alias به تنظیمات eslint هم اضافه بشه تا به خطاهای مربوط به آدرس‌دهی برنخوریم.کامیت مرتبط با این تغییراتسخن آخردر این مقاله به معایب relative import اشاره شد و دو روش جایگزین برای حل این مشکلات رو معرفی کردیم. درنهایت با توجه به این که absolute import نیاز به نصب پکیج جدید و یا تنظیمات اضافه نداره پیشنهاد شخصی من استفاده از روش اوله. پروژه CRA هم برای کوتاه کردن و تمیزتر شدن آدرس‌دهی ها استفاده از absolute import رو پیشنهاد کرده.ممنونم از اینکه زمان گذاشتید و این مقاله رو تا انتها خوندید. خوشحال میشم برای بهبود این نوشته و نوشته‌های بعدی پیشنهادات و نظرات خودتون رو در بخش نظرات و یا از طریق ایمیل engineering@snapp.cab به دست من برسونید.منابع https://github.com/mrastiak/cra-absolute-import  https://create-react-app.dev/docs/importing-a-component/#absolute-imports  https://github.com/facebook/create-react-app/blob/main/CHANGELOG.md#removed-typescript-flag-and-node_path-support  https://github.com/gsoft-inc/craco عکس‌های این مقاله با استفاده از این محصول زیبا ساخته شده: https://ray.so/ </description>
                <category>اسنپ</category>
                <author>آرمین عیاری</author>
                <pubDate>Mon, 11 Oct 2021 09:29:02 +0330</pubDate>
            </item>
                    <item>
                <title>نمایش نقاط پرتکرار برای مسافران اسنپ</title>
                <link>https://virgool.io/snapp-eng/%D9%86%D9%85%D8%A7%DB%8C%D8%B4-%D9%86%D9%82%D8%A7%D8%B7-%D9%BE%D8%B1%D8%AA%DA%A9%D8%B1%D8%A7%D8%B1-%D8%A8%D8%B1%D8%A7%DB%8C-%D9%85%D8%B3%D8%A7%D9%81%D8%B1%D8%A7%D9%86-%D8%A7%D8%B3%D9%86%D9%BE-n9pzjaym2wgo</link>
                <description>توی این پست می‌خوام در مورد یک پروژه‌ی جالب و بسیار کاربردی که توی تیم نقشه پیاده سازی کردیم، توضیح بدم.توی context اسنپ میشه به نقشه به عنوان ابزاری برای انتخاب مبدأ و مقصد سفر نگاه کرد. با این حساب، یک متریک خیلی خوب برای سنجش کیفیت سرویس‌ها می‌تونه «مدت زمان» تلاش کاربر برای فیکس کردن مبدأ و مقصد باشه: این مدت زمان هرچی کمتر باشه، بهتره! در نتیجه واضحه که ما دوست داریم سرویس‌های جدیدی داشته باشیم که به کاهش این زمان کمک کنن.ایده‌ای که توی این پست مطرح می‌کنم، قابلیتی هست که نقاط پرتکرار هر کاربر رو به عنوان مبدأ و مقصد سفرها بهش پیشنهاد می‌ده. یعنی تاریخچه‌ی سفرهای کاربر رو طی چند هفته‌ی اخیر بررسی می‌کنه و می‌بینه چندین سفر به مقصد یک نقطه‌ی خاص داشته، یا از یک مکان زیاد درخواست اسنپ داده؛ پس «با احتمال زیادی» در آینده باز هم می‌خواد از/به اون نقطه سفر کنه؛ پس اون نقطه رو توی قسمت جدیدی که توی اپ اسنپ بهش اختصاص دادیم، به کاربر نمایش می‌دیم. پر واضحه که وقتی دسترسی کاربر به چنین نقاطی رو راحت می‌کنیم، زحمت پیدا کردن اون مکان رو برای کاربر کم می‌کنیم و در نتیجه متریک «زمان فیکس کردن» کاهش پیدا می‌کنه.اسم این قابلیت (feature) جدید توی اپ رو گذاشتیم نقاط پرتکرار (Frequent Pins). البته قبل از این قابلیت دیگری هم داشتیم به نام نقاط مورد علاقه (Favorite Places). منتها فرق مهمی که این دو تا با هم دارن، اینه که کاربر نقاط «مورد علاقه»ش رو دستی توی اپلیکیشن وارد می‌کنه، ولی نقاط «پرتکرار» براش اتوماتیک تشخیص داده می‌شه؛ یعنی اگه کاربری حواسش نبود یا وقت نداشت که یک نقطه‌ی مهم مثل خونه و محل کارش رو توی نقاط مورد علاقه‌اش وارد کنه، ما این خلأ رو براش پر می‌کنیم. پس توی ادامه‌ی پست حواستون به تفاوت نقاط «پرتکرار» و «مورد علاقه» باشه که خدایی نکرده گمراه نشید. :)تصویر ۱ - صفحه‌ی اصلی اپلیکیشن اسنپ: دو نقطه‌ی پرتکرار رو در زیر باکس جستجو مشاهده می‌کنید. نقاط پرتکرار چگونه به دست می‌آیند؟حالا که احساس کردیم یک ایده‌ی قشنگ می‌تونه به بهبود متریک‌های ما بیانجامه، تلاش می‌کنیم تا به بهترین شکل پیاده سازی‌ش کنیم. طراحی کلی به این شکل هست:۱. به ازای هر کاربر، همه‌ی سفرهای یک بازه‌ی مشخص (مثلاً یک ماه اخیر) و همه‌ی نقاط «مورد علاقه»شون رو از پایگاه داده بازخوانی می‌کنیم.۲. لیستی از همه‌ی مبدأ و مقصدهای کاربر (همه‌ی پین‌های فیکس شده) رو از سفرهاش به دست میاریم و نقاط «مورد علاقه» رو از این لیست حذف می‌کنیم؛ چون نقاط «پرتکرار» قراره از روی این لیست محاسبه بشن، بنابراین نمی‌خوایم اگه یک نقطه مورد علاقه بین شون هست، مجدد جزو نقاط پرتکرار به کاربر پیشنهاد بشه.۳. از روی این لیست و با استفاده از متدهای هوش مصنوعی لیستی از نقاط پرتکرار رو به دست میاریم که حدس می‌زنیم کاربر به احتمال زیاد در آینده باز هم سراغشون خواهد رفت.۴. در نهایت این نقاط پرتکرار رو توی اپلیکیشن برای کاربر سرو می‌کنیم.اما پیاده سازی این طراحی ما رو با چه چالش‌هایی مواجه می‌کنه؟ در ادامه می‌ریم سراغش.پردازش داده‌های حجیمبار پردازشی زیاد محاسبه‌ی نقاط پرتکرار برای چندین میلیون کاربر فعال اسنپ اولین چالشیه که توجه هرکسی رو به خودش جلب می‌کنه. این حجم پردازش اگه بخواد با راه حل‌های دم دستی مثل حلقه‌ی for :) انجام بشه، اولاً خیلی طول می‌کشه، ثانیا مقیاس پذیر نیست؛ یعنی اگه فرضاً کاربرهای اسنپ دو برابر بشه، یا بخوایم توی الگوریتم‌مون کوچکترین تغییری ایجاد کنیم، مثلا آدرس هر نقطه‌ی پرتکرار رو هم کنار مختصات جغرافیایی‌ش ذخیره کنیم، ممکنه طول پردازش به سادگی از بیشترین حد قابل قبول بیشتر بشه! مثلا فرض کنید بخوایم هر شب نقاط پرتکرار رو حساب کنیم، ولی کل پردازش سه روز طول بکشه.راه حل شناخته شده‌ی این مشکل «پردازش موازی» هست. محاسبه‌ی نقاط پرتکرار برای هر کاربر می‌تونه به طور کاملاً مستقل انجام بشه. پس خیلی بهتره که به جای یک پردازشگر سری که محاسبات رو برای هر کاربر تک به تک انجام می‌ده، از ۱۰ تا پردازشگر موازی استفاده کنیم. با این کار منابع مصرفی‌مون مثل حافظه و CPU ده برابر می‌شه، اما در ازاش سرعت هم ۱۰ برابر می‌شه. پس پردازش موازی یک tradeoff بین منابع مصرفی و زمانه.حالا از چه روشی برای پیاده سازی این پردازش موازی استفاده کنیم؟ می‌تونیم سیستم رو multi-thread یا multi-process پیاده سازی کنیم. اگه بخوایم از راحتی docker container ها برای توسعه و deployment هم  بهره ببریم، میشه آرایه‌ای از container ها رو بالا آورد که پین‌های هر کاربر رو از یک سیستم stream processor مثل Kafka یا RabbitMQ و به کمک الگوی طراحی معماری Publish-Subscribe دریافت می‌کنن. اما ما به دنبال ساده ترین راهیم! یکی از framework های محبوب در حوزه‌ی Big Data و پردازش‌های حجیم Spark هست که در زبان‌های برنامه نویسی Java, Scala و Python می‌تونید ازش استفاده کنید. خیلی ساده بخوام توضیح بدم، Spark همه‌ی کارهای زیرساختی داده‌های حجیم رو خودش انجام میده و فقط کافیه بهش بگیم که چی می‌خوایم.اجازه بدید بیشتر راجع به Spark صحبت کنم. فرض کنید لیستی از هر نوع اطلاعاتی که دوست دارید، توی حافظه (RAM) یا در یک پایگاه داده موجوده و می‌خواید روی هر عضو این لیست پردازشی انجام بدید؛ مثلاً همه‌ی عناصر رو با هم جمع کنید، یا بشمرید ببینید توی چند تا سطر از یک رمان ۱۰۰۰ صفحه‌ای کلمه‌ی مشخصی به کار رفته. مادامی که پردازش مورد نظر شما به واحدهای اجرا شونده‌ی موازی قابل افراز باشه، می‌تونید از طریق واسط‌های برنامه نویسی اسپارک آنالیز مورد نظرتون رو پیاده سازی کنید. حالا این کد می‌تونه روی یک زیرساخت کلاستر (مثلا Kubernetes) اجرا بشه، به این صورت که اول یک driver node و بعد به تعدادی که از قبل براش configure شده (مثلا ۱۰ تا) executor node بالا میاد. بعد اسپارک اطلاعات رو تبدیل می‌کنه به داده ساختاری به نام Resilient Distributed Dataset یا به طور خلاصه RDD و سپس از طریق driver این RDD رو «توزیع» می‌کنه روی executor ها تا هر کدوم پردازش‌های لازم رو انجام بدن و نتیجه رو برگردونن به driver. در نهایت هم driver جمع بندی می‌کنه و تمام!تصویر ۲ - معماری اسپارک: برای هر application که روی این بستر اجرا بشه، یک driver و چندین executor به طور موازی اجرا می‌شن. (منبع: سایت Apache Spark)تشخیص نقاط پرتکرارچطور از تاریخچه‌ی مبدأ و مقصدهای کاربر می‌تونیم در هر زمان بهترین پیشنهاد رو به کاربر ارائه بدیم؟ نزدیک بودن زمان پین قبلی اهمیت بیشتری داره، یا تعداد دفعاتی که از اون پین استفاده شده؟ در حال حاضر ما برای سادگی کار فاکتور زمان رو دخیل نمی‌کنیم و فقط طول و عرض جغرافیایی نقاط رو در بازه‌ی زمانی مشخص (مثلا یک ماه گذشته) نگاه می‌کنیم.برای این که بفهمیم بهترین predictor نقاط پرتکرار چیه، می‌تونیم مدل‌های مختلف Machine Learning رو طراحی و ارزیابی کنیم. مدلی که ما در نهایت استفاده کردیم، DBSCAN در کتابخانه‌ی Scikit Learn پایتون هست که جزو الگوریتم‌های خوشه‌بندی (Clustering) از دسته‌ی الگوریتم‌های یادگیری بدون ناظر (Unsupervised Learning) هست. DBSCAN مبدأ و مقصدهای کاربر رو دریافت می‌کنه و می‌بینه کجاها چگالی یا تراکم نقطه‌ها زیاده تا میانگین اون نقاط رو به عنوان یک نقطه‌ی پرتکرار به ما معرفی کنه. مرز بین دسته‌ها هم با مناطق کم تراکم مشخص می‌شن. ما برای DBSCAN حداقل تعداد پین‌های لازم برای یک دسته رو برابر ۲ و حداکثر فاصله‌ی بین دو پین رو برای این که به عنوان همسایگی هم شناخته بشن، حدود ۱۰۰ متر تعیین کردیم.شاید براتون سؤال بشه که چرا به جای DBSCAN از الگوریتم معروفی مثل K-Means استفاده نکردیم؟ باید بگم که K-Means دو تا ویژگی داره که به درد مورد ما نمی‌خورد: اول این که «تعداد» دسته‌ها حین یادگیری به دست نمیاد و باید از قبل به صورت یک hyperparameter براش تعیین بشه، ولی اتفاقاً ما می‌خوایم تعداد دسته‌ها حین یادگیری به دست بیاد؛ دوم هم این که دسته‌های K-Means الزاما شبیه چندضلعی‌های محدب ساخته می‌شن، ولی DBSCAN چون به «تراکم» توجه می‌کنه و نه فقط «فاصله»، کاری به توپولوژی دسته‌ها نداره و می‌تونن هر شکل دلخواهی داشته باشند.توضیحش سخت شد، بهتره از تصاویر کمک بگیریم؛ از قدیم گفتن یک تصویر بهتر از هزار کلمه است! توی تصاویر ۳ و ۴ می‌تونید خروجی دسته بندی الگوریتم‌های DBSCAN و K-Means رو با هم مقایسه کنید. بیاین فرض کنیم هر کدوم از دسته‌های DBSCAN یک کوچه یا خیابون هستند و شما در یک ماه گذشته در نقاط مختلف این کوچه‌ها مبدأ و مقصد انتخاب کردید. حالا فکر کنم با من موافق باشید که مرکز دسته‌های DBSCAN نماینده‌های بهتری برای نقاط پرتکرار هستن، تا مرکز دسته‌های K-Means که انگار بعضی از کوچه‌ها رو با هم قاطی کرده.تصویر ۳ - دسته بندی DBSCAN (منبع: Towards Data Science)تصویر ۴ - دسته بندی K-Means (منبع: Towards Data Science)ریلیز نهاییحالا که پیاده سازی سمت بک اند و پردازش‌ها رو مفصل براتون توضیح دادم، وقتشه که اپلیکیشن اسنپ یه آپدیت بده و از این قابلیت جذاب رونمایی کنه! روشی که برای انتشار (release) نهایی یک قابلیت برای کاربر نهایی (end user) مرسومه، A/B Testing نام داره. این تست همون طور که از اسمش پیداست، کاربرهای اپ رو به دو دسته‌ی آ و ب تقسیم می‌کنه (به ترتیب مثلا شامل ۹۰٪ و ۱۰٪ کاربرها) و قابلیت رو سمت سرور فقط برای دسته‌ی ب فعال می‌کنه.مزیت این روش اینه که از اونجایی که یک قابلیت جدید احتمال داره اونقدری که ما فکرش رو می‌کردیم، خوب از آب در نیاد و تجربه‌ی کاربری ناخوشایندی رو رقم بزنه، در نتیجه بهتره که بی گدار به آب نزنیم و گام به گام کاربرا رو باهاش آشنا کنیم. به مرور اگه احساس کردیم قابلیت جدید حال کاربرها رو بهتر می‌کنه، سهم دسته‌ی ب رو زیاد می‌کنیم تا این که نهایتا به ۱۰۰٪ برسه و اون موقع ریلیز نهایی می‌شه.آمار و متریک‌هاما ابتدای هر هفته این سرویس رو روی بستر Spark اجرا می‌کنیم. اجراش بیش از ۱۰ ساعت طول می‌کشه (بیشتر این زمان به خاطر محاسبه آدرس برای هر نقطه‌ی پرتکراره) و برای بیش از ۱۳ میلیون کاربر فعال اسنپ، حدود ۱۰ میلیون نقطه پرتکرار به دست میاد.و اما ببینیم متریک‌ها چطور تغییر کردند؟ما برای مانیتور کردن کاربران از سرویس آنالیتیکز AppMetrica استفاده می‌کنیم. کار با اپ‌متریکا به این صورته که برای هر رفتار کاربر در اپلیکیشن که می‌خوایم ازش مطلع بشیم، مثل مشاهده‌ی صفحه اول نقشه، کلیک روی نقاط «مورد علاقه»، نقاط «پرتکرار» و ... اصطلاحاً یک رویداد  (event) در پنل مدیریتی اپ‌متریکا تعریف می‌کنیم و در مواقع لازم از سمت کلاینت اون رویداد رو ارسال می‌کنیم. این رویدادها در طول روز از تمامی کاربران اسنپ دریافت و در سرور اپ‌متریکا ذخیره و تجمیع می‌شن.حالا می‌تونیم با تحلیل انبوه رویدادهای ذخیره شده، متریک‌های جذاب و متنوعی رو استخراج کنیم که در این جا به طور خلاصه به ۲ تا از مهم ترین اون‌ها اشاره می‌کنم:مدت زمانی که طول می‌کشه تا کاربر مبدأ یا مقصد رو تعیین کنه. (فاصله‌ی زمانی بین ارسال رویدادهای «نمایش صفحه» تا «انتخاب پین»)نسبت میزان استفاده از نقاط «پرتکرار» به «نقاط مورد علاقه». (مقایسه تعداد رویدادهای کلیک نقاط پرتکرار و مورد علاقه.)سرتون رو درد نیارم! برای کاربرهایی که این قابلیت براشون فعال شده بود، متریک اول ۷٪ بهبود پیدا کرد و  متریک دوم در فاز مبدأ و مقصد به ترتیب ۶۴٪ و ۴۰٪ گزارش شد که نسبت قابل ملاحظه‌ای بود و در نتیجه پروژه‌ی نقاط پرتکرار تبدیل به یکی از موفق ترین پروژه‌های تیم نقشه شد.کارهای بیشترچطور می‌تونیم این پروژه رو باز هم بهتر کنیم؟ راه‌های مختلفی ممکنه به ذهن برسه.برای مثال، حالا که فهمیدیم نقاط پرتکرار این قدر کاربرد دارند، چرا تبدیل به نقاط مورد علاقه‌ی کاربر نشن؟ این طوری همیشه توی اپ برای کاربر ذخیره باقی می‌مونن؛ و فرضاً اگه مدتی سفرهای کاربر به اون نقطه کمتر شد، پیشنهاد اون نقطه حذف نمی‌شه و همیشه می‌تونه به فاصله‌ی یک کلیک اون نقطه رو انتخاب کنه. پس می‌تونیم این قابلیت رو سمت کلاینت پیاده سازی کنیم که کاربر بتونه نقاط پرتکرارش رو تبدیل به نقاط مورد علاقه‌ش بکنه.کار دیگه‌ای که میشه انجام داد، امتحان مدل‌های متنوع‌تر یادگیری ماشین برای این پروژه است. مثلا در این پروژه ما فقط از DBSCAN استفاده کردیم، در حالی که مدل‌های Ensemble با کنار هم قرار دادن چند مدل ضعیف‌تر می‌تونن یک مدل پیش‌بینی کننده‌ی قوی رو ایجاد کنن. خوبه که تلاش کنیم دقت پیش‌بینی مدل رو با امتحان مدل‌های مختلف یا تنظیم hyperparameter ها بیشتر کنیم.پایان!ممنون که پست رو تا انتها خوندید. خوشحال می‌شم که این پست رو لایک کنید و اگه نظر یا سؤالی دارید، توی کامنت‌ها با من در میون بگذارید. در ضمن، اگه حس می‌کنین از پروژه‌هایی شبیه به این خوشتون میاد و در نتیجه علاقه دارید به تیم ما ملحق بشید، خوشحال می‌شیم که رزومه‌هاتون رو از طریق آدرس engineering@snapp.cab برای ما ارسال کنید.</description>
                <category>اسنپ</category>
                <author>محمد مصباح</author>
                <pubDate>Wed, 23 Jun 2021 19:51:17 +0430</pubDate>
            </item>
                    <item>
                <title>طراحی و پیاده‌سازی Automated Engagement Campaigns جهت بهبود نرخ ثبت‌نام و استفاده‌ی کاربران از اپلیکیشن اسنپ!</title>
                <link>https://virgool.io/snapp-eng/%D8%B7%D8%B1%D8%A7%D8%AD%DB%8C-%D9%88-%D9%BE%DB%8C%D8%A7%D8%AF%D9%87-%D8%B3%D8%A7%D8%B2%DB%8C-automated-engagement-campaigns-%D8%AC%D9%87%D8%AA-%D8%A8%D9%87%D8%A8%D9%88%D8%AF-%D9%86%D8%B1%D8%AE-%D8%AB%D8%A8%D8%AA-%D9%86%D8%A7%D9%85-%D9%88-%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%DB%8C-%DA%A9%D8%A7%D8%B1%D8%A8%D8%B1%D8%A7%D9%86-%D8%A7%D8%B2-%D8%A7%D9%BE%D9%84%DB%8C%DA%A9%DB%8C%D8%B4%D9%86-%D8%A7%D8%B3%D9%86%D9%BE-lxsxqw4dzyih</link>
                <description>جذب کاربر جدید تنها بخش کوچکی از پروسه رشد کسب و کارهاست. یکی از مهم‌ترین ویژگی‌های یک کسب و کار موفق نگهداری از کاربر و تمرکز بر خلق ارزش برای تشویق او به بازگشت مجدد و استفاده‌ی بیشتر از محصولات ارائه شده است. این مفهوم را می‌توان در یک جمله خلاصه کرد: Retention is the new growth! اسنپ سرویس‌های متعددی را در قالب مفهوم سوپراپ ارائه می‌دهد، مانند درخواست خودرو، سفارش غذا، خرید بلیت سفر، پرداخت قبوض و …. عده‌ی زیادی از کاربران هنگام دانلود اپلیکیشن از سرویس‌های موجود آگاهند، اما گروهی دیگر نیاز به صرف زمان جهت جستجو در اپلیکیشن و مشاهده‌ی خدمات و پیشنهادات ارائه شده دارند. برای افزایش آگاهی کاربران از ارزش‌های پیشنهادی سوپراپ، تصمیم بر پیاده‌سازی پروسه‌ای اتوماتیک گرفته شد تا همراهی و افزایش آگاهی در تمامی بخش‌های Lifecycle کاربر موجب افزایش Engagement با سرویس‌های مختلف شود.اولین چالش پیش رو، مشاهده و جمع آوری اطلاعات رفتاری کاربران در اپلیکیشن و هدایت آن‌ها در مسیر طراحی شده برای رسیدن به هدف است. این چالش برای تیم‌های بازاریابی و محصول اسنپ! شامل جمع‌آوری اطلاعات عملکرد کاربر از طریق تعریف ایونت‌های مناسب، مطالعه‌ی رفتار، درک نیازها و هدایت کاربر به بهترین نحو برای رفع این نیازهاست. افزایش Engagement کاربر با استفاده از تعریف ایونت‌های مختلف و استفاده از آن‌ها در طراحی Journeyپس از پیاده‌سازی و جمع‌آوری ایونت‌های مورد نظر در هنگام استفاده‌ی کاربر از اپلیکیشن و تحلیل داده‌ها، اهداف مختلفی برای طراحی مسیر کاربر و استفاده از Automated Campaigns انتخاب شد که سه نمونه از آن‌ها را در ادامه مرور خواهیم کرد.1. افزایش نرخ ثبت‌نام کاربرانثبت‌نام اولین قدم در چرخه‌ی عمر کاربر است. در صورتی که کاربران پس از طی زمان مشخصی پس از نصب برنامه اقدام به ثبت نام نکنند، نوتیفیکیشن زیر را دریافت می کنند. پیاده‌سازی این Journey موجب 21% افزایش در تعداد ثبت‌نام بعد از نصب شد.2. بهبود نرخ تبدیل کاربر به مشتری با تشویق به انجام اولین تراکنشبعد از ثبت‌نام، کاربران تشویق به استفاده از سرویس‌های موجود در سوپراپ مانند درخواست خودرو، سفارش غذا، پرداخت قبوض و... می‌شوند. برای انجام این کار بعد از پیاده‌سازی و بررسی دقیق ایونت‌های مرتبط، Journey زیر طراحی شد.بعد از دریافت ایونت ثبت‌نام، سیستم منتظر دریافت ایونت‌های دیگر از سمت کاربر خواهد بود. چنانچه کاربر خود اقدام به استفاده از سرویس‌ها کند، Journey برای او به پایان می‌رسد. اما چنانچه در زمان مشخصی این ایونت‌ها دریافت نشد، نوتیفیکیشن‌های متفاوتی برای کاربر ارسال می‌شود و او را تشویق به درخواست اولین خودرو، پرداخت اولین قبض و یا سفارش اولین غذا می‌کند. در ادامه چند نمونه از پیام‌های ارسالی را مشاهده می‌کنید. همچنین برای اطمینان از استفاده‌ی موفقیت آمیز کاربر از سرویس انتخابی و تکمیل فرآیند سفارش، کمپین‌های Card Abandonment طراحی و پیاده‌سازی شد. طراحی این کمپین‌ها، نیاز به آنالیز فانل فروش سرویس‌های مختلف دارد. در تصویر زیر نمونه‌ای از فانل فروش بلیت هواپیما را مشاهده می‌کنید که با تعریف ایونت‌های متناسب ایجاد و آنالیز می‌شود.آنالیز فانل به ما کمک می‌کند تا میانگین زمان تبدیل (Average Time to Convert) اندازه‌گیری شود و این زمان بهترین گزینه برای ارسال نوتیفیکیشن مرتبط است. برای مثال چنانچه آنالیز فانل نشان دهد که برای کاربر حداقل 2 ساعت زمان می‌برد تا بلیت خود را خریداری کند، ارسال هر پیامی قبل از دو ساعت استراتژی نامناسبی است و ممکن است در او این احساس بوجود آید که اسنپ! بیش از اندازه درحال ترغیب کاربر به خرید است. با آنالیز فانل فروش و استفاده از کمپین‌های Automated برای بازگشت کاربران به سبد خرید در سرویس اسنپ فود، بیش از 13% بهبود مشاهده شد. همچنین 13.13% و 17.13% بهبود در بازگشت کاربران به سبد خرید در سرویس‌های هتل و پرواز به دست آمد.3. بازگرداندن کاربرانی که اپلیکیشن را حذف کرده‌اندبرخی از کاربران ممکن است علاقه‌ی خود به استفاده از اسنپ! را از دست داده و اپلیکیشن را حذف کنند. برای برگرداندن این افراد تیم مارکتینگ اسنپ به بررسی و جمع‌آوری اطلاعات قابل دسته‌بندی مانند چنل‌های جذب کاربر، مدل دستگاه مورد استفاده و کمپین‌هایی پرداخت که منجر به بیشترین تعداد حذف اپلیکیشن شده‌اند. همچنین برای درک بهتر دلایل حذف اپلیکیشن، تصمیم به دریافت بازخورد از کاربران گرفته شد. بعد از دریافت این بازخوردها، علاوه بر بهبود تجربه کاربری محصول، کمپین‌هایی برای تشویق کاربران از دست رفته به نصب دوباره اپلیکیشن طراحی گردید. در ادامه نمونه‌ای از این کمپین‌ها را مشاهده می‌کنید که در قالب Trigger-based Journey پیاده‌سازی می‌شود.این Journey با دریافت ایونت App Uninstalled آغاز می‌شود. سپس سیستم 7 روز منتظر دریافت ایونت باز کردن اپلیکیشن می‌ماند. چنانچه کاربر در این هفت روز اپ را مجددا نصب و اجرا کند Journey به پایان می‌رسد. در غیر این صورت اس ام اسی دریافت می‌کند تا تشویق به نصب مجدد اپلیکیشن و استفاده از سرویس‌های مختلف آن شود. نمونه‌ای از این اس ام اس را در تصویر زیر مشاهده می‌کنید.پیاده‌سازی این Journey منجر به 44.6% بهبود در نرخ بازنصب اپلیکیشن گردید.نتیجه:با استفاده از رویکردی جامع برای کمک به Onboarding کاربر و تلاش در جهت بازگشت و ایجاد چسبندگی به استفاده از اسنپ! نتایج خیره‌کننده‌ی زیر به دست آمد.21% رشد در تعداد ثبت‌نام13% رشد در نرخ نسبت اولین درخواست خودرو به ثبت‌نام25% بهبود در آگاهی کاربران جدید نسبت به سرویس‌های مختلف اسنپ13% رشد در سفارش غذا در میان کاربرانی که سبد خرید خود را ترک کرده‌اند13.1% رشد در رزرو هتل در میان کاربرانی که سبد خرید خود را ترک کرده‌اند17.1% رشد در خرید بلیت هواپیما در میان کاربرانی که سبد خرید خود را ترک کرده‌اند44.6% بهبود در بازنصب اپلیکیشناین مقاله بخشی از مطالعه‌ی منتشر شده توسط شرکت WebEngage، ارائه دهنده‌ی سرویس‌های Marketing Automation در خصوص موفقیت‌های اسنپ! در این حوزه است. در صورت تمایل می توانید برای مشاهده‌ی مقاله‌ی کامل اینجا کلیک کنید.</description>
                <category>اسنپ</category>
                <author>علیرضا چراغزاده</author>
                <pubDate>Wed, 23 Jun 2021 14:15:09 +0430</pubDate>
            </item>
            </channel>
</rss>