<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های مسیح یگانه</title>
        <link>https://virgool.io/feed/@masihyeganeh</link>
        <description></description>
        <language>fa</language>
        <pubDate>2026-06-13 10:00:48</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/10920/avatar/ZVwHyD.jpeg?height=120&amp;width=120</url>
            <title>مسیح یگانه</title>
            <link>https://virgool.io/@masihyeganeh</link>
        </image>

                    <item>
                <title>اثر انگشت صوتی</title>
                <link>https://virgool.io/@masihyeganeh/%D8%A7%D8%AB%D8%B1-%D8%A7%D9%86%DA%AF%D8%B4%D8%AA-%D8%B5%D9%88%D8%AA%DB%8C-rsugfvrhixke</link>
                <description>در این پست قصد دارم به بهانه پیاده سازی سرویس تکراری‌زدایی صوتی (Audio Deduplication) مرور کوتاهی بر اثر انگشت گیری صوتی (Audio Fingerprinting) داشته باشم و داستان کوتاه پیاده‌سازی یکی از الگوریتم‌های تشخیص صدای تکراری برای سرویس‌مان را شرح دهم. شاید مهم‌ترین کاربرد این پست، آشتی دادن بیشتر خوانندگان با اتفاقات سطح پایین تر در الگوریتم‌ها و فرمول‌های ریاضی و مهم‌تر از آن، معرفی برخی منابع برای مطالعه بیشتر باشد.توجه : به دلیل کم بودن سواد من در این مبحث خاص، انتظار هر نوع اشتباهی را داشته باشید و حتماً اگر موردی دیدید، به من گوشزد کنید تا اصلاح کنم. نکته مهم این است که تلاش کنیم در مباحثی که در آن خبره نیستیم هم ورود کنیم تا حداقل مطالب جدیدتر یاد بگیریم.مقدمهدر استارت‌آپ پادکستی که در آن مشغول هستم (که به تازگی با دنباله‌روی از Spotify پادکست‌های ویدیویی نیز ارائه می‌دهد)، چند مشکل داریم:تمام یا بخش کوتاهی از بعضی از Track ها در Track های دیگر استفاده می‌شود که در تعریف استفاده منصفانه (Fair Use) نمی‌گنجد.بعضی از Track ها عیناً کپی می‌شوند و با اینکه ادعای حق‌تکثیر از طرف منتشر کننده اصلی مطرح نمی‌شود ولی شانس این را پیدا می‌کنند که از Track اصلی بیشتر به کاربران نشان داده شوند.بعضی کاربر ها تلاش می‌کنند این کپی بودن را با تغییر عکس، اسم، توضیح، یا با برش و تغییر اندازه و نوشتن متن روی ویدیو یا برش و تغییر کیفیت و … صدا، مخفی کنند.در هنگام جستجو، کاربر با تعداد زیادی از کپی‌های همان Track مواجه می‌شود که یا عیناً کپی همدیگر اند یا تغییر مختصری کرده اند و روی تجربه کاربر تاثیر منفی می‌گذارند.و …این مشکلات یک راه حل مشترک دارند. کافیست به نحوی مشخصه‌ای در Track پیدا کنیم که منحصر به فرد بوده و بتوان با آن مشخصه، آن Track را جستجو کرد. اگر یک انسان بخواهد این کار را انجام دهد، احتمالاً دنبال کلماتی که در Track گفته می‌شود می‌رود (مثلاً متن شعر) یا به دنبال ریتم یا بیت خاصی از آن که به گوشش برجسته تر می‌آید می‌گردد. اما ماشین چطور؟ اصلاً ماشین چطور یک آهنگ را می‌فهمد؟ ماشین چطور می‌تواند Track تکراری را تشخیص دهد؟صدا، موج، فرکانس و … در دنیای دیجیتالبرای اینکه بهتر متوجه شویم میکروفون و بلندگو چطور کار می‌کنند و این داده‌ها چطور در کامپیوتر ذخیره می‌شوند، بد نیست از یک ویدیوی کوتاه کمک بگیریم: https://aparat.com/v/G0ul3 البته دیرین دیرین منظورم نیست. اینجا قرار بود این ویدیو قرار بگیرد که ویرگول علاقه‌ای به نمایش دادن ویدیو‌های یوتوب ندارد!همانطور که در ویدیو مشخص است، صدا صرفاً به صورت مقدار متغیر دامنه موجیست که در واحد زمان ذخیره می‌شود. البته این صدای آنالوگ (Analog) است. کامپیوتر گزینه مناسبی برای ذخیره و تحلیل مقادیر پیوسته نیست و در عوض این مقادیر را در بازه‌های ثابت و کوتاه مدت نمونه گیری می‌کند (Sampling) و به ازای هر بازه، یک عدد نگهداری می‌کند. بنابراین یک Track به این صورت ذخیره می‌شود:نمودار گسسته زمان-دامنههمانطور که مشاهده می‌کنید، کامپیوتر از یک صوت، این داده‌ها را دارد و به نظر می‌رسد نهایت برداشتی که از آن می‌توان کرد این است که صدا در چه بازه‌هایی بلند است و در چه بازه‌هایی بلند نیست. اما در اصل، این موج بی‌نظم، در حقیقت از حاصل جمع تعداد بسیار زیادی موج با طول موج ثابت بدست آمده و در حقیقت فرمولی وجود دارد که بتوان این موج را به ریز موج‌های سازنده اش شکست. که این کار به عجیبی بدست آوردن شیر و یخ و توت‌فرنگی از اسموتی است!اگر در دانشگاه ریاضی مهندسی پاس کرده باشید، حتماً با فوریه آشنا هستید. اما چه پاس کرده باشید و چه نه، توصیه می‌کنم ویدیو‌های کانال ۳ آبی ۱ قهوه‌ای (3Blue1Brown) برای سری فوریه و تبدیل فوریه را حتماً قبل از ادامه تماشا کنید تا در جدیدی در زندگی برایتان گشوده شود!ماجرا از جایی جذاب می‌شود که تبدیل فوریه را روی این آهنگ اجرا کنیم و به جای نمودار دو بعدی بالا، نمودار سه بعدی زیر حاصل شود:نمودار زمان-فرکانس-دامنه (اسپکتروگرام)در این نمودار یا طیف‌نگاره (اسپکتروگرام)، محور x زمان است، محور y نشان دهنده فرکانس‌های مختلف ریز موج‌های تولید کننده موج اصلی و در محور z که مقدار آن از تیره به روشن مشخص است، نشان دهنده دامنه آن ریز موج در آن زمان است.متاسفانه قابلیت نهفتن (embed) یک iframe دیگر نوشته‌های ویرگول وجود ندارد و نمی‌توانید در همین صفحه با این تبدیل بازی کنید ولی توصیه می‌کنم در این صفحه رفته این تبدیل را با میکروفون خودتان تجربه کنید.این نمودار کاربرد‌های زیادی در زمینه‌های موسیقی، زبانشناسی، سونار (sonar)، رادار، پردازش گفتار، لرزه‌شناسی و سایر موارد دارد. به طور مثال می‌توان با کمک این نمودار، یک موسیقی را ویرایش کرد، یا با کمک آن، گفتار را به متن و با معکوس کردن این روش، متن را به گفتار تبدیل کرد و … اما نکته مهم این نمودار برای کاربرد مورد نظر ما این بود که طیف‌نگاره یک صوت، در صورتی که بلندی صدا کم و زیاد شود، منبع صدا دور شود و بعضی نویز‌ها اضافه شوند، همچنان اشتراکاتی با طیف‌نگاره صوت اصلی دارد. توصیه می‌کنم درستی این جمله را در طیف‌نگاره‌ای که قبلاً معرفی شد، بررسی نمایید.استخراج اثر انگشتتا اینجای کار مشخص شد که طیف‌نگاره نامزد مناسبی برای استخراج اثر انگشت است. حالا چالش استخراج یک مجموعه داده حداقلی است که بتوان به کمک آن، یک صوت مشابه به صوت مرجع را با درصدی تحمل خطا پیدا کرد. پس یک مصالحه (trade-off) باید بین حجم داده‌ها، تحمل خطا و دقت انجام شود. مثلاً ممکن است برای یک Track حدود ۵ مگابایتی نیاز باشد ۲۵ مگابایت داده اثر انگشت ذخیره شود که دقت بالایی داشته باشد ولی مطلوب نیست. یا به همین ترتیب ممکن است داده‌ای ذخیره شود که اگر مقدار کمی نویز در آن وجود داشته باشد، تشابه پیدا نشود که این هم مطلوب نیست. یا در بدترین حالت، هر صوتی که ارتباطی با صوت مرجع نداشته باشد، پیدا شود. درصد تحمل خطا به مورد استفاده (use-case) نیز بستگی دارد؛ به طور مثال ممکن است نیاز باشد در یک فیلم که در آن چند نفر در حال صحبت باشند و یک موسیقی دارای حق‌تکثیر در پس‌زمینه در حال پخش باشد نیز تشخیص داده شود.مرسوم‌ترین پیش‌پردازش ممکن برای استخراج اثر انگشت، شکستن این نمودار بسیار بلند، به پنجره‌هایی (window) با طول ثابت به صورت سر خوردن (sliding window) یا پریدن (hopping window) است که شاید ترجمه خوبی نکرده باشم ولی منظور این است که به فرض اگر ثانیه ۰ تا ۳ را یک پنجره در نظر بگیریم، آیا پنجره بعدی از ثانیه ۳ شروع می‌شود یا از جایی بین ۰ و ۳، که حالت اول hopping window و حالت دوم sliding window است. البته در حالت واقعی، طول پنجره خیلی کوچکتر از ۳ ثانیه و در حد میلی‌ثانیه در نظر گرفته می‌شود. اما اینکه این طول چقدر باشد نیز مشمول همان مصالحه بالا می‌شود.حالا مساله خیلی کوچکتر می‌شود. در همان مثال قبل که یک صوت یک بار در طیف‌نگاره‌ای که معرفی شد پخش شود و دفعه بعد با تغییر جزئی مثل تغییر کیفیت پخش شود و به پنجره کوتاهی شکسته شود، حالا مساله فقط پیدا کردن شباهت بین دو تصویر است که تقریباً به همدیگر شباهت دارند.برای این مساله با تکنیک‌های مختلفی، راه حل‌های متفاوتی پیشنهاد شده. به عنوان مقال Shazam از معروف‌ترین سایت‌هایی که همین سرویس را ارائه می‌دهد، پیشنهاد می‌کند که در این تصویر، پر رنگ ترین نقاط را به صورت یک تار عنکبوت به هم وصل کنیم و مختصات آن را نسبت به اندازه پنجره ذخیره نماییم:شیوه استخراج اثر انگشت توسط Shazamکه طبیعتاً ذخیره مختصات چند پاره خط به هم پیوسته، فضای ذخیره سازی بسیار کمتری از خود تصویر می‌گیرد. این روش به اعوجاج (distortion) هایی مانند نویز سفید (white noise) مقاوم است. و اینکه چه تعداد از نقاط پر رنگ انتخاب شوند دقیقاً به همان مصالحه بر می‌گردد.یا همزمان با آن، فیلیپس (Philips) به جای انتخاب نقاط پر رنگ، به دنبال تغییرات در زمان و فرکانس بود و نموداری به این صورت استخراج می‌کرد:شیوه استخراج اثر انگشت توسط Philipsو در نهایت تنها مقادیری را به صورت باینری ذخیره می‌کرد که در آن اختلاف از یک آستانه (threshold) بیشتر باشد. این روش به صورت طبیعی به فرکانس‌های بالاتر بیشتر حساس بود تا فرکانس‌هایی پایین که تغییر کمتری دارند. این روش به نویز‌های انفجاری کمتر حساس بود تا نویز‌های پیوسته.شیوه‌های دیگری نیز همچون استفاده از هوش مصنوعی برای استخراج بهترین خصوصیات (features) و … مورد استفاده قرار می‌گیرد که بعضی شیوه‌ها مقاوم به نویز انفجاری هستند، بعضی مقاوم به نویز پیوسته، بعضی مقاوم به اعوجاج (distortion)، بعضی مقاوم به کند یا تند شدن سرعت پخش هستند، بعضی قابلیت تشخیص با زمزمه کردن (humming) را دارند و … که بنابر مورد استفاده، باید یکی را انتخاب کرد.برای ما مقاومت در برابر تغییر‌های شدید مثل کند کردن و ایجاد نویز‌های بلند و طولانی یا هر تغییری که باعث شود کاربر به آن گوش نکند، مهم نبود و طبیعتاً آن Track ها به صورت طبیعی از دور خارج می‌شدند. برای ما صرفاً کپی کردن مهم بود، با تاثیراتی که آپلود مجدد و پردازش مجدد ممکن است بر کیفیت Track بگذارد، قطع کردن بعضی از بخش‌ها و اضافه کردن صداهایی مانند اضافه کردن Watermark صوتی (مانند «ارائه ای از ...» یا «... تقدیم می‌کند»).روشی که در نهایت مناسب مورد استفاده ما بود، روش دیگری بود که Shazam منتشر کرده بود که لینک آن در بخش منابع قرار داده شده. این روش خیلی مشابه روش اولیست که معرفی شد و در آن قله‌های طیف‌نگاره استخراج می‌شوند، سپس بلندترین آنها یا پررنگ ترین آنها انتخاب شده و خطوطی از آن نقطه به سایر قله‌ها به صورت زیر رسم می‌شود:شیوه انتخابی استخراج اثر انگشتدر بخش A (مربع بالا، چپ) یک پنجره خاص از یک طیف‌نگاره قابل مشاهده است که قله‌های آن در بخش B (مربع پایین، چپ) استخراج شده که شبیه صور فلکی در آسمان شب است، سپس از میان قله‌ها، بلندترین قله با نام Anchor Point انتخاب شده و همانطور که در بخش C (مربع بالا، راست) قابل مشاهده است، پاره خطی از Anchor Point به تک تک آن قله‌ها رسم می‌شود. به هر کدام از این پاره خط ها، یک اثر انگشت (Fingerprint) گفته می‌شود. در نهایت در بخش D (مربع پایین، راست) می‌توان دید که هر کدام از این پاره خط ها مختصات منحصر به فردی دارند که برای ذخیره آنها به بهینه‌ترین شکل می‌توانند قابل استفاده قرار گیرند. برای ذخیره یک پاره خط، چند راه ساده وجود دارد که ذخیره مختصات x و y نقاط شروع و پایان یکی از آنهاست ولی به دلیل بسیار کوتاه بودن طول پنجره، می‌توان از یکی از اعداد محور x طرف نظر کرد و پاره خط را با مختصات Anchor Point و مقدار y یا فرکانس قله و Δx یا Δt نشان داد.ذخیره اثر انگشت‌هاشیوه ذخیره سازی نیز به این شکل خواهد بود که از کنار هم قرار دادن این ۴ عدد، می‌توان به یک Hash با طول ۳۲ بیت رسید که نشان‌دهنده اثر انگشت است و در یک ستون جدول با نام fingerprint ذخیره می‌شود. در ستون دیگر به نام time_offset زمان شروع پنجره به صورت عدد ۳۲ بیتی ذخیره می‌شود و در ستون آخر با نام file_id، نام یا مسیر یا شناسه یا Hash فایل (مثلاً با یک تابع ساده مانند Jenkins one_at_a_time hash، آن هم به صورت یک عدد ۳۲ بیتی) ذخیره می‌شود. بسته به نوع پایگاه داده (database) این اعداد می‌توانند به صورت علامت دار، بی علامت، ستونی، درختی یا … ذخیره شوند که هر کدام مزیت خودش را دارد. به طور مثال در پایگاه داده Postgresql که از اعداد بدون علامت پشتیبانی نمی‌کند، داده‌ها به این شکل ذخیره می‌شوند:تعداد ۱۰ عدد از اثر انگشت‌های یک پنجره از یک Track که در پایگاه داده Postgresql ذخیره شده اندشیوه دیگری که برای ذخیره اشاره شد، استفاده از درخت است. اگر با عدد fingerprint یک درخت تشکیل شود که برگ‌های آن لیستی از فایل‌ها و زمان پنجره آنها در کنار هم به صورت یک عدد ۶۴ بیتی باشد، زمان جستجو و پیدا کردن Track مشابه را به شدت کاهش می‌دهد اما زمان وارد کردن داده‌ها را کند تر می‌کند.در شیوه‌ای مشابه می‌توان از پایگاه داده‌های کلید و مقداری (key-value) مانند redis استفاده کرد و مقدار fingerprint را به عنوان کلید و لیستی از فایل و زمان شروع پنجره ها را به عنوان مقدار در آن قرار داد. این پایگاه داده حتی می‌تواند یک File System باشد و نام هر فایل، همان اثر انگشت و مقدار داخل آن، همان لیست قبل به صورتی که هر خط شامل یک فایل و زمان شروع پنجره باشد.تطابق صوتیبعد از ذخیره اثر انگشت‌های Track ها، حالا باید روشی یافت تا یک Track دلخواه را به سیستم داد و Track های مشابه را استخراج کرد. شیوه‌های تطابق صوتی دو Track نیز فراوان هستند ولی انتخاب آن کاملاً وابسته به شیوه اثر انگشت گیری است. برای شیوه اثر انگشت گیری مطرح شده، نحوه تطابق صوتی به این صورت است که به همان شیوه قبل، فایل صوتی به پنجره‌هایی با طول ثابت تقسیم شده، اثر انگشت‌های آن پنجره که در حقیقت Hash هایی از مختصات چند پاره خط هستند استخراج می‌شوند. این Hash ها در پایگاه داده جستجو می‌شوند و متناظر با آنها، لیستی از فایل‌ها به همراه زمان شروع پنجره آنها بدست می‌آید. به این ترتیب برای هر پنجره از فایل مورد نظر، لیستی از فایل و زمان شروع‌های دیگر بدست می‌آید که می‌توان تعداد تکرار آنها را برای تمام اثر انگشت‌های پنجره مورد نظر بدست آورد.به عنوان مثال اگر فایل های audio1 و audio2 و … تا audio10 را قبلاً در پایگاه داده ذخیره کرده باشیم و فایل query را مورد بررسی قرار دهیم، برای دو پنجره اول ممکن است اطلاعاتی شبیه به این بدست بیاید:برای پنجره شماره ۱ از فایل query:تعداد ۴ اثر انگشت از پنجره شماره ۶ فایل audio1تعداد ۱۲ اثر انگشت از پنجره شماره ۴ فایل audio2تعداد ۲ اثر انگشت از پنجره شماره ۸ فایل audio3تعداد ۲۰ اثر انگشت از پنجره شماره ۹ فایل audio4برای پنجره شماره ۲ فایل query:تعداد ۵ اثر انگشت از پنجره شماره ۷ فایل audio1تعداد ۱۴ اثر انگشت از پنجره شماره ۵ فایل audio2تعداد ۱۹ اثر انگشت از پنجره شماره ۱۰ فایل audio4در این مرحله، برای حذف نتایج مثبت کاذب (False Positive)، آنهایی که تعداد اثر انگشت منطبق شده شان کمتر از یک عدد از پیش تعریف شده در تنظیمات (مثلاً کمتر از ۱۰) باشد را حذف می‌کنیم. بنابراین audio1 و audio3 از نتایج حذف می‌شوند. بعد از این مرحله، خود اثر انگشت‌ها بی‌معنی می‌شوند و صرفاً پنجره‌های منطبق شده، برای پیدا کردن تشابه استفاده می‌شوند.تا اینجا پنجره‌هایی که بین دو فایل مشترک بودند را پیدا کردیم و می‌توان در همین مرحله با درصد پنجره‌های تطابق یافته نسبت به کل پنجره‌ها، درصد تشابه دو فایل را بدست آورد. اما می‌توان کار را یک مرحله بیشتر پیش برد و توالی‌های مشترک بین Track ها را پیدا کرد تا دقیقاً مشخص باشد کدام بخش از یک موسیقی دارای حق‌تکثیر، در کدام بازه از Track دیگر استفاده شده تا بتوان گزارش دقیق‌تر داد یا به صورت خودکار، آن بخش از Track را بی‌صدا (mute) کرد.به نظر می‌رسد که مساله حالا پیدا کردن بلندترین زیردنباله مشترک (LCS) باشد، ولی یک نکته مهم در داده‌های بالا هست که شاید کار را راحت تر کند و از نظر کارایی، بهتر عمل کند؛ با توجه به لیست بالا، طبیعی است که اگر پنجره شماره ۱ فایل query با پنجره ۴ فایل audio1 منطبق شده باشد، در صورتی فایل audio1 همچنان در این توالی قرار دارد که پنجره شماره ۲ فایل query با پنجره ۵ فایل audio1 تطابق داشته باشد. یا به عبارت دیگر، در پنجره شماره ۱ فایل query، پنجره ای از فایل audio1 پیدا شده که با آن اندازه ۳ پنجره فاصله دارد (پنجره ۴ منهای پنجره ۱) و در پنجره شماره ۲ فایل query نیز پنجره ای از فایل audio1 پیدا شده که با آن اندازه ۳ پنجره فاصله دارد (پنجره ۵ منهای پنجره ۱). پس می‌توان از اختلاف پنجره‌های دو فایل برای ترکیب کردن پنجره‌ها استفاده کرد تا به توالی‌هایی از دو فایل رسید که با یکدیگر منطبق باشند.اگر از شماره فایل‌ها به همراه اختلاف پنجره‌های دو فایل فاکتور بگیریم، به یک دیکشنری می‌رسید که مقادیر آن، صرفاً لیستی از شماره پنجره‌هاست. حال می‌توان در هر لیست، پنجره‌هایی که در حداکثر فاصله مشخصی از هم (مقدار آن به صورت پیشفرض در تنظیمات داده می‌شود) هستند را با همدیگر ترکیب کرد و به زیردنباله‌هایی رسید. سپس این دیکشنری را دوباره به لیستی از شماره Track و اختلاف پنجره‌های دو فایل و زیردنباله ها به صورت flat تبدیل کرد و دوباره از شماره Track ها فاکتور گرفت و عملیات قبل را دوباره انجام داد تا بتوان مطمئن شد که تمام توالی‌‌های فایل که حداکثر فاصله مشخصی دارد (این مقدار هم به صورت پیشفرض در تنظیمات داده می‌شود و با قبلی یکسان نیست)، با همدیگر ترکیب شده و بلندترین توالی مشترک را بدست آورده باشند. با ضرب کردن طول دنباله در طول زمانی ثابت پنجره، نتیجه می‌شود لیستی از بلندترین توالی‌های فایل‌های داخل پایگاه داده با فایل query.در صورت نیاز به بدست آوردن درصد تشابه دو فایل در این مرحله می‌توان مجموع نسبت توالی‌های مشترک دو فایل به طول زمانِ طولانی‌ترین فایل از این دو را حساب کرد و به صورت درصد نمایش داد.پیاده‌سازیبرای پیاده‌سازی، دو راه پیش رو بود: اختراع دوباره چرخ و استفاده از یک چرخ مناسب و ایجاد تغییرات در آن. در واقعیت تقریباً همیشه گزینه اختراع دوباره چرخ منتفی است، چون هیچ شرکتی بودجه، زمان و تمایل به این کار ندارد، اما همین پست هم خود، اختراع دوباره چرخ است!به هر حال، از بین پیاده‌سازی‌های موجود، که تعداد آن نیز کم نیست، Olaf توجه ما را جلب کرد. داستان آن به پدری بر می‌گردد که می‌خواست برای دخترش لباسی طراحی کند که هرگاه آهنگ انیمیشن Frozen پخش شد، لباس شروع به چشمک زدن بکند و تمام این اتفاقات به صورت نهفته (embedded) در یک میکروکنترلر با کمترین منابع ممکن و به بهینه‌ترین شکل ممکن پیاده شود.این پروژه تقریباً همان مسیری را در پیش گرفته که در بالا توضیح داده شد، با اندکی تفاوت و اندکی اشکالات. به عنوان نمونه، برای ذخیره‌سازی از دیتابیس LMDB استفاده می‌کند که همان ساختار درختی ای است که توضیح داده شد. پیاده سازی آن اشکالاتی نیز دارد، مثلاً Track هایی یافتیم که طول آنها چند دقیقه بود، ولی شباهتی که بین آنها پیدا می‌کرد، چند هزار ساعت بود!اما حالا ما برای شروع یک کد به زبان C داشتیم و چون C زبان مرسوم شرکت نیست، تصمیم گرفتیم ابتدا یک wrapper روی آن بنویسیم و سپس تکه تکه بخش های مختلف آن را به زبان Go بازنویسی (port) کنیم. همچنین تصمیم گرفته شد که از LMDB استفاده نکنیم، زیرا به دلیلی که قبلاً توضیح داده شد، رفته رفته سرعت درج اطلاعات در آن کندتر و کندتر می‌شود.تمام ایده‌هایی که در بالا توضیح داده شد و تمام پایگاه داده‌ها در آن گنجانده شد و نتیجه این شد:ساختار فایل‌های پروژه نهایی با زبان Goبهینه بودن کد برای ما اهمیت بسیار بالایی داشت، زیرا فقط با یک آهنگ Frozen سر و کار نداشتیم و باید جوابگوی روزانه چند صد Track ای که به سیستم اضافه می‌شد، می‌بود. در هر مرحله نیز تست‌هایی انجام شد که مطمئن باشیم بازنویسی درست انجام شده و نتایج ما با Olaf یکی است و در نهایت از آن نیز بهتر است.این پروژه، دو فایل باینری تولید می‌کند که یکی عیناً مانند Olaf عمل می‌کند و دیگری از یک صف، Track هایی که باید پردازش شوند و به پایگاه داده اضافه شوند را می‌گیرد، پردازش می‌کند، Track های تکراری یا تقریباً تکراری را پیدا می‌کند و در یک جدول در پایگاه داده ذخیره می‌کند و سپس اثر انگشت‌های فایل پردازش شده را به پایگاه داده می‌فزاید.با استفاده از این کد، تا به حال حدود یک میلیون عدد از Track ها بررسی شدند که مشخص شد حدود ۸.۳۰٪ از آنها کپی بودند. همچنین اطلاعات دقیقی از این پردازش بدست آمد که تمام اهداف ذکر شده در ابتدای پست را ممکن ساخت.منابعHow does Audio Fingerprinting workShazam : An Industrial Strength Audio Search AlgorithmAn industrial-strength audio search algorithm (2003)Seeing Through Sound: Acoustic FingerprintingOLAF: Overly Lightweight Acoustic FingerprintingOlaf - Acoustic fingerprinting on the ESP32 and in the BrowserPanako: a scalable acoustic fingerprinting system handling time-scale and pitch modificationAudio Fingerprinting with Python and NumpyA Fingerprint for AudioAudio fingerprinting and recognition in .NET</description>
                <category>مسیح یگانه</category>
                <author>مسیح یگانه</author>
                <pubDate>Sat, 18 Sep 2021 14:11:37 +0430</pubDate>
            </item>
                    <item>
                <title>یک میلیارد ردیف در ساعت</title>
                <link>https://virgool.io/@masihyeganeh/%DB%8C%DA%A9-%D9%85%DB%8C%D9%84%DB%8C%D8%A7%D8%B1%D8%AF-%D8%B1%D8%AF%DB%8C%D9%81-%D8%AF%D8%B1-%D8%B3%D8%A7%D8%B9%D8%AA-wdcdsgbvy2sk</link>
                <description>در این پست بلاگ قصد داریم به بهانه مهاجرت (Migration) پایگاه داده (Database) زمان پخش کاربران یک استارت‌آپ پادکست کانادایی به سرور جدید، مرور کوتاهی بر راه حل انتخابی‌مان برای ذخیره این داده‌ها داشته باشیم و در کنار آن به معرفی اجمالی پایگاه داده ClickHouse و روش اعمال مهاجرت‌مان بپردازیم.مقدمهپس از تشکیل تیم هوشمندسازی و تلاش برای ارائه راه‌حل‌هایی برای هوشمندسازی سرویس با استفاده از مدل‌های یادگیری عمیق (Deep Learning)، مهم ترین نیازمندی، حجم عظیمی از داده بود که این مدل‌ها بتوانند بر اساس‌شان آموزش ببینند. داده‌های مختلفی از بخش‌های مختلف سایت/اپ می‌توان استخراج کرد که بتوان برای هوشمندسازی از آنها استفاده کرد؛ از نظرات کاربران برای پادکست‌ها گرفته تا جریان کلیک‌های کاربر در صفحه (که در پست دیگری در آینده به آن پرداخته خواهد شد). اما یکی از حیاتی‌ترین این داده‌ها، زمان پخش کاربران است که می‌توان با تخمین خوبی، به میزان رضایت کاربر از پادکست ترجمه شود و برای ارائه پادکست‌ها بر اساس سلیقه کاربر مورد استفاده قرار گیرد.زمان پخش چند صد هزار نفر کاربر همزمان (Concurrent) سرویس، هر دقیقه به صورت متناوب از گوشی‌ها، کامپیوترها و تلویزیون‌ها و … به سمت سرورهای ما ارسال می‌شود که دریافت و ذخیره‌سازی آن به صورتی که حجم عظیم و سرعت بالای تولید آن، مشکلی برای ذخیره‌سازی و کوئری کردن بهینه داده‌ها ایجاد نکند، یکی از چالش‌های این پروژه بود که انتخاب یک پایگاه داده مناسب بخش مهمی از راه حل بود.ما معتقدیم که یک راهکار نهایی (Silver Bullet) برای ذخیره و بازیابی همه انواع داده‌ها وجود ندارد و بر اساس شرایط مختلف مانند مورد استفاده (Use Case)، نوع داده، سرعت تولید داده، حجم داده، نسبت نوشتن به خواندن داده، زمان کوئری و … باید راهکار ذخیره‌سازی خاصی را انتخاب کرد. ما بر اساس شرایط از تکنولوژی‌های مختلفی استفاده می‌کنیم، از ذخیره‌سازی ساده به عنوان فایل‌های CSV برای آموزش (Training) برخی از مدل‌های هوش مصنوعی گرفته تا Redis برای داده‌های کوچک و Cache کردن داده‌ها، MySQL برای اطلاعات رابطه‌ای، MongoDB برای داده‌های بدون ساختار خاص، ElasticSearch برای داده‌هایی که نیاز به جستجوی تمام‌متن (Full-Text Search) دارند، Graylog برای ذخیره و مدیریت لاگ‌ها، Prometheus و Grafana برای ذخیره و نمایش اطلاعات پایش (Monitoring) سرویس‌ها، Kafka و KSQL برای ذخیره و پردازش جریان رخداد‌های سمت کاربر و …فهرست برخی از ابزارها و سرویس‌های داده حجیم (Big Data) در سال ۲۰۲۰معرفی پایگاه داده ClickHouseپایگاه داده ClickHouse یک پایگاه داده ساختارمند (Structured) ستونی (Column-oriented) سریع برای پردازش تحلیلی آنلاین (OLAP) است که با دستورات SQL کار می‌کند و توسط Yandex ساخته و متن‌باز شده است.برخی از ویژگی‌های ClickHouse:ذخیره‌سازی ستونی: این شیوه ذخیره‌سازی با ایجاد ستون‌هایی با طول داده ثابت امکان دسترسی سریع مانند آرایه را ایجاد می‌کنند که در صورت اندیس‌گذاری (Indexing)، دسترسی را بسیار سریع می‌کنند. در این شیوه فقط ستون‌هایی که در کوئری موجود هستند بررسی می‌شوند. از مزایای دیگر این روش می‌توان به محلی بودن داده‌ها (Locality of data) و امکان موازی سازی اشاره کرد، به این صورت که هر ستون را یک پروسس بررسی کند.استفاده بهینه از انواع دیسک: ClickHouse طوری طراحی شده که به راحتی روی Hard Disk های معمولی کار کند ولی اگر SSD یا Ram اضافی در اختیارش قرار گیرد، بیشترین بهره را از آنها می‌برد. همچنین می‌توان قابلیت آرشیو کردن در آن تعریف کرد که داده‌های تازه‌تر و پرکاربردتر را در SSD ذخیره کند و در صورت قدیمی شدن داده‌ها، آنها را به HDD منتقل کند.فشرده‌سازی داده‌ها: علاوه بر کدک (Codec) های ذخیره‌سازی عام‌منظور (مثل LZ4, LZHC و ZSTD)، برای داده‌هایی با نوع خاص از کدک‌های خاص منظور دیگر همچون Delta, DoubleDelta, Gorilla و T64 پشتیبانی می‌کند.پردازش برداری: در صورت پشتیبانی CPU از دستورات SIMD یا Single Instruction Multiple Data همچون SSE4.2 از آن بهره می‌گیرد تا پردازش را با سرعت بیشتری انجام دهد.پردازش موازی: از تمام هسته (Core) های CPU بهره می‌برد.پردازش توزیع‌شده: در صورت اختصاص بیش از یک سرور، از قابلیت پردازش توزیع‌شده روی همه سرورها بهره می‌برد.پشتیبانی از SQL: به دلیل آشنایی دراز مدت افراد تیم با SQL و موجود بودن ابزارهای فراوان برای کار کردن با آن، این قابلیت باعث تطبیق سریعتر تیم با این پایگاه داده می‌شود.قابلیت یکپارچه‌سازی (Integrity) بالا: قابلیت اتصال و یکپارچه‌سازی به پایگاه داده‌های دیگر مثل MySQL، سیستم‌های پردازش جریان (Stream Processing) مثل Kafka و … به طوری که این اتصالات در پایگاه داده به عنوان یک جدول دیده می‌شوند و می‌توان با آن عملیات‌های فراوانی از جمله Join انجام داد.داده‌های تغییر ناپذیر (Immutable Data): قابلیت ویرایش داده‌ها در ClickHouse به صورت ساده وجود ندارد و این ویژگی امکان استفاده از پایگاه داده بدون قفل (Locking) را ایجاد می‌کند.ذخیره‌سازی فیزیکی داده‌ها روی دیسک به صورت مرتب شده بر اساس کلید اصلیپشتیبانی از تکثیر داده‌ها (Replication) در عین حفظ یکپارچگی (Integrity) آناین تصاویر برای درک بهتر تفاوت پردازش کوئری پایگاه داده ستونی در مقابل ردیفی گویا هستند:پردازش کوئری در پایگاه داده ردیفیپردازش کوئری در پایگاه داده ستونیاین پایگاه داده تفاوت‌هایی با پایگاه داده‌های سنتی دارد که در ابتدا می‌تواند گیج کننده باشد. به طور مثال:قابلیت تعریف قید (Constraint) یکتا (Unique) وجود ندارد.کلید اصلی وجود ندارد و در شرایطی Sorting Key نقش آن را ایفا می‌کند.قابلیت ویرایش به راحتی وجود ندارد (وجود دارد ولی به دلیل پرهزینه بودن این عملیات، ترجیحاً استفاده نمی‌شوند) و برای بروز نگه داشتن داده‌های ویرایش شده باید از موتور (Engine) خاصی از جداول استفاده شود.داده‌ها در تمام مدت نهایی نیستند و در مورد زمان نهایی شدن آن نمی‌توان با قاطعیت صحبت کرد.دو مورد آخر نیاز به توضیح دارند چون مفاهیم جدیدی نسبت به پایگاه داده‌های سنتی هستند. ClickHouse برای ذخیره‌سازی جداول، موتورهای مخلفی را معرفی کرده که از خانواده MergeTree هستند که ایده پشت آن، همانند LSM است و به این صورت کار می‌کند که داده‌های جدید در دسته‌هایی (Batch) به پایگاه داده وارد می‌شوند و در هنگام ورود در Log هایی نگه داشته می‌شوند. در این مرحله داده‌ها مرتب شده نیستند ولی ترتیب آنها را زمان ورود داده‌ها مشخص می‌کند.ورود داده‌ها به ClickHouseسپس این بخش از داده‌های جدید با Sorting Key مرتب می‌شوند و منتظر ادغام شدن (Merge) می‌شود.داده‌های جدید در انتظار ادغامسپس در زمان نامعلومی* در پس‌زمینه، این بخش‌های با هم ادغام می‌شوند و تغییرات لازم روی داده‌ها اعمال می‌شود و بر اساس Sorting Key مرتب و نهایی می‌شوند.داده‌هایی که در پس‌زمینه ادغام می‌شونددر این مرحله داده‌ها در شکل نهایی ثبت شدند و این عملیات برای دسته‌های بعدی تکرار می‌شود.داده‌های نهایی شده در ClickHouseتوسط این مکانیزم، ClickHouse می‌تواند بدون نیاز به قفل کردن، داده‌ها را بروز نگه دارد و در هر لحظه به سریعترین حالت ممکن به کوئری‌ها پاسخ دهد. اما برای روشن‌تر شدن شیوه بروز رسانی داده‌های تغییر یافته، باید با سایر اعضای خانواده MergeTree آشنا شویم:موتور ReplaceingMergeTree: این نوع جدول، در هنگام عملیات ادغام، آخرین نسخه داده‌هایی که Sorting Key یکسان دارند را نگه می‌دارد و بقیه را حذف می‌کند. در زمان تعریف جدول باید یک Field را مشخص کرد که از روی آن بتوان نسخه جدیدتر را تشخیص داد (به طور مثال Field زمان ورود داده).موتور SummingMergeTree: این نوع جدول، در هنگام عملیات ادغام، Field های مشخص شده داده‌های مختلف که Sorting Key یکسان دارند را با یکدیگر جمع می‌کند. در زمان تعریف جدول باید این Field ها را مشخص کرد. اگر مشخص نشود، تمام فیلد‌های عددی جمع می‌شوند.موتور CollapsingMergeTree: در این نوع جدول باید یک فیلد علامت (Sign) تعریف شود که فقط مقدار ۱ و ۱- می‌گیرد و در هنگام عملیات ادغام، داده‌هایی که Sorting Key یکسان دارند و یک بار با علامت ۱ و یک بار با علامت ۱- موجود باشند را حذف می‌کند.موتور VersionedCollapsingMergeTree: این نوع جدول، دقیقاً مشابه CollapsingMergeTree است با این تفاوت که یک Field نسخه (Version) نیز به آن اضافه می‌شود و فقط داده‌های هم‌نسخه با هم مقایسه می‌شوند.موتور AggregatingMergeTree: در این نوع جدول بیشترین قابلیت‌ها وجود دارد و می‌توان مشخص کرد داده‌هایی که Sorting Key یکسان دارند را با چه فرمولی ادغام نماید. به طور مثال می‌توان مشخص کرد که مقدار یک فیلد را با مقدار‌های قبلی جمع کند، یکی را تفریق کند، در یکی بیشینه (Maximum) حساب کند، در یکی آخرین مقدار* دیده شده را نگه دارد و …توسط این موتورها، ClickHouse می‌تواند بدون نیاز به قفل یا ارائه عملیات Update، داده‌ها را فقط با عملیات Insert بروز نگه دارد.برای آشنایی بیشتر با پایگاه داده ClickHouse به وب‌سایت رسمی یا مستندات آن مراجعه کنید.دلایل انتخاب ClickHouse و تجربیات ما از آنما در تیم جوان هوشمندسازی، در اولین روزهای کاری سال ۱۳۹۹ (حدود ۹ ماه پیش از نگارش این پست) پایگاه داده ClickHouse را در سرورهای شرکت راه‌اندازی کردیم و تا زمان نگارش این پست، بیش از یک میلیارد ردیف ادغام شده از داده‌های پخش روزانه کاربران جمع‌آوری کردیم. این داده‌ها علاوه بر خوراک رسانی به مدل‌های هوش مصنوعی، مصارف آماری دیگری هم برای ما داشتند. به طور مثال برای تست‌های A/B، تحلیل‌های BI و …برخی از دلایلی که ما این پایگاه داده را انتخاب کردیم:ویژگی‌هایی که ClickHouse ارائه می‌کند نه تنها برای مدل‌های هوش مصنوعی ما مناسب است، بلکه نیازهای دیگر ما از جمله مصارف آماری را برطرف می‌کند.سرعت، سرعت و سرعت. هم کوئری‌هایی که برای مدل‌های هوش مصنوعی خوراک آماده می‌کنند، هم کوئری‌های آماری ما بسیار کوئری‌های پیچیده‌ای هستند که در RDBMS های سنتی چندین دقیقه زمان نیاز دارند اما در ClickHouse با وجود نرخ ورود اطلاعات پخش که در روز به ۳ هزار ردیف در ثانیه می‌رسد، این کوئری‌ها در چند میلی‌ثانیه اجرا می‌شوند. به عنوان نمونه اگر این یک میلیارد ردیف اطلاعات پخش را با اطلاعات پادکست‌ها Join کنیم و با کاربر و پادکست Group کنیم و با Having آنهایی که بیشتر از درصد خاصی از پادکست را دیده اند جدا کنیم و لیستی Comma Separate شده از آن بسازیم، در ClickHouse حدود ۲۰ ثانیه طول می‌کشد. این عدد در MySQL بالای یک ساعت است.با استفاده از خانواده MergeTree بسیاری از پیچیدگی‌های مرسوم برای زمان‌بندی ویرایش داده‌ها و قفل کردن ردیف‌ها عملاً از بین رفته و تنها با عملیات درج (Insert) می‌توانیم به همان نتیجه برسیم.برخی از اطلاعات مورد نیاز در کوئری‌های ما (مانند اطلاعات پادکست‌ها) در پایگاه داده‌های دیگری مثل MySQL ذخیره شدند و ClickHouse قابلیت اتصال و حتی اجرای عملیات Join با MySQL را داراست و به عنوان نمونه با استفاده از این قابلیت بدون نیاز به همگام سازی (Synchronization) داده‌های MySQL و ClickHouse می‌توانیم درصد پخش و درصد پخش مفید را حساب کنیم.فشرده‌سازی داده‌ها دلیل مهم دیگر انتخاب ما بود. به طوری که پس از مهاجرت از MySQL به ClickHouse حجم اشغال شده دیسک به یک دهم کاهش پیدا کرد.این پایگاه داده از انواع خوشه بندی (Clustering) و پردازش توزیع شده پشتیبانی می‌کند و به راحتی تکثیر (Replicate) می‌شود و می‌توان قابلیت دسترسی بالا (High Availability) را از آن انتظار داشت.با پشتیبانی از دستورات MySQL، سرعت تطبیق بالایی به تیم می‌دهد.این پایگاه داده قبلاً امتحان خود را در Yandex Metrica پس داده است و این گزینه بسیاری از پایگاه داده‌های مشابه را به راحتی از دور خارج می‌کند زیرا هیچگاه زیر بار واقعی تست نشده‌اند (Battle Tested).اما در کنار نقاط قوت فراوان، هم نقاط ضعفی وجود دارد، هم نکاتی که باید به آنها توجه شود:ترجیح درج داده‌ها به صورت دسته‌ای و تا حد ممکن تجمیع نشده: در صورت درج داده‌ها به صورت تکی، کارایی ClickHouse به شدت پایین می‌آید و ترجیح می‌دهد داده‌ها را به صورت دسته‌ای دریافت کند. اما بهتر است در صورت امکان داده‌ها تجمیع نشده باشند و کاملاً خام وارد سیستم شوند و با استفاده از توابع فراوان موجود، در جدول دیگری تجمیع شوند تا اصل داده‌ها به صورت دست نخورده باقی بماند تا در صورت تغییر نیازمندی‌های کسب و کار، بتوان تجمیع جدیدی با قواعد دیگری روی آن اعمال شود.انجام عملیات ادغام در زمان نامعلوم و عدم امکان برنامه‌ریزی برای آن: به نقل از مستندات رسمی:Data deduplication occurs only during a merge. Merging occurs in the background at an unknown time, so you can’t plan for it. Some of the data may remain unprocessed. Although you can run an unscheduled merge using the OPTIMIZE query, don’t count on using it, because the OPTIMIZE query will read and write a large amount of data.عدم وجود تضمین برای پردازش تمام ردیف‌ها: در نقل قول قبل، به این موضوع نیز اشاره شده که ممکن است بعضی از ردیف‌ها بعد از ادغام حذف نشوند و برای درست کردن آن نمی‌توان روی final یا optimize هم حساب کرد چون کل سیستم را به شدت کند می‌کنند. البته با توجه به روابط منطقی داده‌ها می‌توان با استفاده از عملگرهایی مانند max یا sum داده‌های تجمیع نشده را عملاً بی اثر کرد.پردازش بدون ترتیب ردیف‌ها: به دلیل پردازش موازی، داده‌ها به هیچ ترتیب خاصی پردازش نمی‌شوند و در نتیجه توابعی مثل «اولین»، «آخرین» و … نادقیق می‌شوند. بنابراین توابع first و last با نام‌های anyFirst و anyLast تعریف شده اند که مشخص کننده این مشکل باشند.عدم پشتیبانی از تراکنش (Transaction): بنابر ذات OLAP بودن پایگاه داده، انتظار داشتن این قابلیت هم نمی‌رود.مستندات ضعیف: برخی از مستندات رسمی ClickHouse نهایت یک پاراگراف متن هستند و هیچ اطلاعات خاصی ندارند. البته که همه مستندات به این شکل نیستند.معماری سیستم استخراج و ذخیره‌سازی اطلاعات زمان پخشمعماری ساده شده سیستم ذخیره‌سازی اطلاعات زمان پخشمعماری سیستم ذخیره سازی اطلاعات زمان پخش در حقیقت بسیار ساده است. پخش‌کننده (Player) هایی که در دستگاه‌های مختلف داریم به صورت متناوب هر دقیقه مجموع دقایق پخش و ثانیه فعلی پادکستی که کاربران در حال گوش کردن به آن هستند را به سرورهای ما ارسال می‌کنند که به واسطه یک HA Proxy به دست خوشه ای از یک کد نوشته شده به زبان Go می‌رسد که وظیفه اعتبارسنجی درخواست را دارد و در صورت معتبر تشخیص دادن، اطلاعات زمان پخش را به یک Redis منتقل می‌کند و در آن تجمیع می‌کند. سپس یک خوشه دیگر از کدهای به زبان Go به نام WALL·E دسته دسته اطلاعات پخش‌های کاربران را به ClickHouse ارسال می‌کنند. این اطلاعات در پایگاه داده ClickHouse در یک جدول از نوع SummingMergeTree جمع آوری می‌شدند که Field مربوط به دقیقه پخش را به صورت روزانه جمع می‌کرد. بنابراین در این مدت یک میلیارد ردیف داده کاربر-پادکست-روز داشتیم. این ساختار که تمام بخش‌های آن به دقت پایش می‌شود، به خوبی پاسخگوی نیازهای ما بود اما به دلایلی نیاز دیدیم که ساختار بهتری داشته باشیم و داده‌ها را مهاجرت دهیم.یکی از دلایل مهاجرت این بود که این پایگاه داده برای ما در فاز تحقیقات و امکان‌سنجی و آشنایی بود و به همین دلیل روی یک ماشین مجازی (Virtual Machine) بالا آمده بود و حالا که حجم دیتای خوبی دارد و از پس تست‌های ما بر آمده، نیاز بود سرور اختصاصی خود را با دیسک قابل انبساط (Expansion) داشته باشد تا بنابر نیاز، افزایش یابد.از دلایل دیگر، تغییر در Field های داده بود که بعضی دیگر کاربرد نداشتند، بعضی جدید اضافه شده بودند و بعضی نیاز به بهینه‌سازی بیشتری داشتند. به طور مثال Field هایی که مشخص شد مقدارهای نسبتاً ثابتی دارند را از نوع LowCardinality تعریف کردیم.همچنین احساس نیاز به جدولی کردیم که تجمیع بیشتری از روی این جدول فعلی انجام دهد و به جای اطلاعات روزانه، اطلاعات پخش کلی کاربر را ثبت کند و Field هایی همچون درصد پخش داشته باشد. برای این مورد از Materialized View استفاده کردیم که به هنگام درج داده‌ها در جدول اصلی، تجمیع شده آن را در این جدول ثانویه اضافه می‌کند.دلیل دیگر، بروزرسانی خود ClickHouse بود که در این مدت امکانات جدیدی اضافه کرده بود و بسیاری از مشکلات آن برطرف شده بود.سناریوی مهاجرتحالا که مطمئن بودیم مهاجرتی با این تعداد تغییر داریم، نیاز به سناریویی دقیق برای اجرای آن حس می‌شد که شامل چند بخش اساسی باشد:بروزرسانی ClickHouseانتقال داده‌های موجود به سرور جدیدانتقال داده‌های جداول با ساختار قدیم به جداول با ساختار جدیدحفظ یکپارچگی کل سیستم‌ها با نسخه جدید، انتقال نرم به سیستم جدید و وجود امکان بازگشت (Revert) در صورت بروز خطاتغییر سرویس‌های استفاده کننده از ClickHouse برای اتصال به سرور جدیدابتدا جدیدترین نسخه ای از ClickHouse که به اندازه کافی پایدار (Stable) باشد را انتخاب کردیم و تمام سیاهه تغییرات (Changelog) بین نسخه نصب شده قدیمی و نسخه جدید را به دقت مطالعه کردیم تا هر نوع ناسازگاری با داده‌های قدیمی را کشف کنیم که خوشبختانه مشکلی وجود نداشت.در این مرحله در یک ماشین مجازی نسخه جدید را نصب و تنظیم و راه‌اندازی کردیم و سپس همه راه‌های موجود را امتحان کردیم تا بهترین را پیدا کنیم. به عنوان نمونه فایل‌های داده ClickHouse قدیمی را به ماشین مجازی جدید منتقل کردیم و جای فایل‌های قدیم قرار دادیم (با تمام عجیب بودن این روش، در مستندات ClickHouse تقریباً چنین روشی مطرح شده) و همه چیز به درستی کار کرد ولی راه درستی به نظر نمی‌رسید زیرا باید پایگاه داده سیستمی را هم منتقل می‌کردیم که انتقال آن بین دو نسخه از نظر ما درست نبود. روش‌های دیگر را هم امتحان کردیم ولی در نهایت ClickHouse-Copier را انتخاب کردیم. این کد نوشته شده به زبان Go در کنار مسیر داده‌های ClickHouse یک پوشه با نام backup ایجاد میکند و نسخه پشتیبان از داده‌های فعلی را به صورت Hard Link به داده‌های اصلی در آن ذخیره می‌کند که هم فضای اضافه نمی‌گیرد، هم به راحتی می‌توان فایل‌ها را به مسیر جدید منتقل کرد که در مورد استفاده ما با rsync انجام شد. اما در تمام مستندات پشتیبان‌گیری این پایگاه داده اشاره شده که نباید در زمان پشتیبان‌گیری، داده جدیدی در سیستم درج شود.To get a consistent copy, the data in the source tables and partitions should not change during the entire process.در معماری سیستم، سرویس WALL·E می‌تواند برای مدتی از کار بی‌افتد و مشکلی برای سیستم پیش نیاید، زیرا داده‌های در Redis تجمیع می‌شوند. اما این زمان نمی‌تواند زیاد باشد زیرا این Redis گنجایش نگهداری داده‌های زیاد را ندارد. پس زمان اجرای مهاجرت باید ساعتی انتخاب شود که سیستم کمترین ترافیک را دارد و از روی نمودار ترافیک، این بازه بین ساعت ۶ تا ۸ صبح است که می‌توان در آن سرویس WALL·E را متوقف کرد، مهاجرت را انجام داد و سپس آن را به کار انداخت.توزیع زمانی تعداد کاربران آنلاینبرای انتقال داده‌ها از جداول با ساختار قدیم به جدید، نیاز بود یک کوئری دقیق INSERT INTO SELECT نوشته شود که تمام ملاحظات ساختار جدید را در نظر بگیرد و Field های اضافی را حذف کند، مقدار معقولی برای Field های جدید در نظر بگیرد و همزمان با اجرای Join روی جداول دیگر، داده‌هایی که موجود نبودند را پر کند.اینها تغییرات مربوط به خود ClickHouse بود اما نیاز بود دو سرویس دیگر که با Go نوشته شده بودند هم تغییر کنند که ضمن حفظ سازگاری با حالت قبلی (Backward Compatibility)، بتوانند از پس درخواست‌ها از نوع جدید نیز برآیند تا بتوان مهاجرت نرمی را انجام داد و در صورت بروز هر نوع خطا بتوان به حالت قبل برگشت. اولین بخش واقعی مهاجرت، همین بخش بود که از مدت‌ها قبل از شروع مهاجرت، این دو سرویس به طوری تغییر کردند که از پس هر دو نوع درخواست برآیند و مشکلی برای آنها پیش نیاید.مرحله آخر هم تغییر آدرس اتصال پایگاه داده سرویس‌هایی بود که از ClickHouse استفاده می‌کنند .این عملیات دو بار به صورت صفر تا صد روی ماشین مجازی انجام شد و زمان بندی‌ها بدست آمد و تمام مشکلات دیده شد و تقریباً برای تمام مراحل راه حل ثانویه (Plan B) و راهکار برگشت به حالت قبل نوشته شد تا در زمان مهاجرت اصلی مشکلی بوجود نیاید که برای آن آمادگی وجود ندارد. همچنین در تمام مراحل باید پایش سیستم‌ها و خواندن دقیق Log ها انجام شود.زمانبندی مراحل مهاجرت اصلیساعت ۰۷:۰۰ - سرویس WALL·E متوقف شدساعت ۰۷:۰۱ - ایجاد جداول جدید در ClickHouse جدیدساعت ۰۷:۰۳ - شروع عملیات پشیبان‌گیری توسط ClickHouse-Copierساعت ۰۷:۰۵ - پایان پشتیبان‌گیری و شروع کپی داده‌های پشتیبان‌گیری شده به سرور جدید توسط rsyncساعت ۰۷:۰۷ - پایان کپی گیریساعت ۰۷:۰۸ - برگرداندن (Restore) داده‌های پشتیبان‌گیری شدهساعت ۰۷:۰۹ - شروع انتقال داده‌ها از جدول قدیم به جدید توسط کوئری INSERT INTO SELECTساعت ۰۷:۴۵ - پایان انتقال داده‌هاElapsed: 2194.532 sec. Processed 1.01 billion rows, 101.02 GB (461,970 rows/s., 46.03 MB/s.)ساعت ۰۷:۴۶ - راه‌اندازی مجدد سرویس WALL·E و شروع تست‌ها (در Redis هیچ تجمع خاصی رخ نداد)ساعت ۰۷:۵۰ - پایان تست‌های از قبل مشخص شدهساعت ۰۷:۵۲ - خوشه سرویس Stats Gateway جدید بارگذاری شد و به ساختار جدید انتقال پیدا کردساعت ۰۷:۵۳ - پایان موفقیت آمیز تمام تست‌های مشخص شده سیستم از صفر تا صدساعت ۰۷:۵۴ - پایش و رصد Log ها برای مشکلات غیر منتظرهساعت ۰۷:۵۹ - پایانمنابعhttp://mattturck.com/wp-content/uploads/2020/09/2020-Data-and-AI-Landscape-Matt-Turck-at-FirstMark-v1.pdfhttps://clickhouse.tech/docs/en/https://altinity.com/blog/clickhouse-materialized-views-illuminated-part-1https://altinity.com/blog/2018/8/22/clickhouse-copier-in-practice</description>
                <category>مسیح یگانه</category>
                <author>مسیح یگانه</author>
                <pubDate>Tue, 09 Feb 2021 10:46:30 +0330</pubDate>
            </item>
                    <item>
                <title>درباره فیلترینگ اینترنت ایران</title>
                <link>https://virgool.io/InfoSec/%D9%81%DB%8C%D9%84%D8%AA%D8%B1%D9%86%D8%AA-tfj9o6jtbjtt</link>
                <description>(سعی شده این مطلب ساده نوشته بشه که برای افراد غیر متخصص هم مفید باشه. پس اگر دیدید جایی دقت کافی رو نداره، بدونید برای سادگی به این شکل گفته شده)بحث فیلترینگ و دخالت دولت ها تو اینترنت چیزی نیست که فقط تو ایران باشه. خیلی جاها نگران دخالت دولت/حکومت/سرویس‌دهنده/بقیه تو اینترنت هستن. این هایی که گفتم فقط می‌تونن تو اطلاعات رمزنگاری نشده دست ببرن. برای همین ssl و بعداً tls اومد که نمودش برای ما آدرس هاییه که با https شروع شده و ارتباط رو رمزنگاری می‌کنن و یکپارچگی ارتباط رو تضمین می‌کنن.آدرس هایی که با https شروع میشن و امن هستنتقریباً میشه گفت هیچ راه ساده ای نیست که بشه توی اینجور آدرس ها دست برد. یا حداقل قبلاً راه ساده ای نبود.حالا نمی‌خوام خیلی وارد ریزش بشم ولی در کل چند جای ارتباطات اینترنتی هست که هنوز رمزنگاری نشده مونده. یکی DNS ئه. توضیحش اینه که کامپیوتر یک کم با ما فرق می‌کنه. ما با آدرس راحتیم، کامپیوتر با IP. مثلاً وقتی شما مثلاً آدرس youtube.com رو می‌زنید، کامپیوتر اینو دوست نداره. دوست داره شما به جاش بهش 172.217.3.46 رو بگید که آدرس سرور گوگله که یوتوب هم روشه. حالا برای اینکه اینو بفهمه، مفهوم DNS بوجود اومد که در حقیقت یه چیزی تو مایه های ۱۱۸ خودمونه که زنگ می‌زنیم میگیم شماره فلانی که ساکن فلان جاست رو بده و اون شماره رو میده. تلفن ها هم به جای آقای فلانی، با شماره تلفن راحت تر هستن. حالا این DNS ها آدرس رو می‌گیرن و IP رو بر می‌گردونن.حالا مشکل DNS اینه که رمزنگاری نشده س و وقتی آدرس youtube.com رو بپرسید، به راحتی حکومت شنودش می‌کنه و به جای IP سرور گوگل، براتون IP پیوند ها رو می‌فرسته.صفحه ای که هم مون باهاش آشنا هستیم برای حل شدن مشکل این بخش میشه dnscrypt.info رو نصب کرد که اون درخواست های DNS رو رمزنگاری می‌کنه و دیگه نمیشه شنودش کرد یا توش تغییرات ایجاد کرد. این رو نصب کنید، یه بخش بزرگی از مشکل فیلترینگ حل میشه. آموزشش نصبش از محدوده این نوشته خارجه اما برای ویندوز حداقل یه کلاینت سر راست داره که راحت میشه فهمید چجوری باید باهاش کار کرد. برای هر سیستم عامل و دستگای هم یه نسخه داره. حتا اگر مودم تون پشیبانی کنه می‌تونید روی مودم نصبش کنید که خب بهترین گزینه اینه اگر امکانش باشه.حالا با فرض اینکه مشکل DNS حل شده، بخش رمزنگاری نشده دیگه آدرس هایی هستن که با https شروع نمیشن و دست دوستان بازه تا هر چیزی که دلشون می‌خواد رو توش پیدا کنن و تشخیص بدن که به جاش پیوند ها رو ببینید بهتره. مثلاً یه زمانی می‌خواستم فلش نصب کنم و اسم فایلش flashinstall.exe بود و فیلتر میشد چون توش lashi بود. ?‍♂️راه حل اینجور صفحه ها فقط فیلترشکن های عادی خودمونه که با تشکر از حکومت، الان حتا اون پیرزنی که تو یه دهات دور افتاده تو یه اتاق خالی با یه لامپ نشسته هم می‌دونه چیه و داره ش.اما بحث اصلیم صفحه هایی هست که با https شروع میشه و تقریباً الان کل وب داره ازش استفاده می‌کنه و خیلی سخته آدرسی رو پیدا کنی که فیلتر باشه و با https شروع نشه. حداقل برای من خیلی سخته براش مثالی بدم.اینجور ارتباط های رمزنگاری شده تا یه مدت قبل به هیچ وجه قابل دستکاری نبودن ولی الان هستن. البته دستکاری نه، همونطور که گفتم https یکپارچی ارتباط رو تضمین می‌کنه. کاری که می‌تونن بکنن، قطع دسترسی به این سایت هاست. دلیل این ماجرا چیزیه به اسم SNI یا Server Name Indication که تو اولین مرحله ارتباط که رمزنگاری نشده س، آدرس سروری که بهش درخواست دادین رو می‌فرسته. دلیل داره واسه این کار ولی خب باعث شده آدرس سایت مورد نظرتون توسط دوستان قابل رویت باشه و بتونن توش خرابکاری کنن.یعنی حکومت اول توافقات برای ارتباط و قبل از شروع رمزنگاری، آدرس youtube.com رو می‌بینه و تو توافقات و اصطلاحاً handshaking اختلال ایجاد می‌کنه و در نتیجه صفحه پیوند ها رو نمی‌بینید ولی چیز دیگه ای هم نمی‌بینید. ارور می‌بینید.با ایجاد اختلال در handshaking ارتباط رمزنگاری شده، این صفحه رو می‌بینیمبرای اثبات ادعام، از دستورهای openssl استفاده می‌کنم که کمک می‌کنه این نوع ارتباطات امن رو بررسی کنیم. تو حالتی که servername رو ننویسیم، اصلاً SNI رو نمی‌فرسته و می‌بینید که ارتباط کامل و بدون مشکل انجام میشهارتباط بدون دستکاری بدون ارسال SNIحالا اگر servername رو بهش بدم و همون youtube.com رو بدم، می‌بینید که ارتباط با مشکل مواجه میشه. یعنی حکومت دیده می‌خوام به یوتوب وصل بشم و ارتباطم رو مختل می‌کنهارتباط دستکاری شده با ارسال SNIحالا اگر چیزی که توی SNI می‌فرستیم چرت و پرت باشه چی؟ اگه همینجوری الکی بهش chiztube.com بدم، می‌بینید که به زیبایی میاد. البته این خیلی اشتباهه و اکثر سرور ها نباید به این جواب درستی بدن یا باید گواهی‌نامه پیشفرض شون رو بدن ولی یوتوب پسر خوبی بود و جواب داد. می‌بینید که ادعام با این اثبات میشهارتباط بدون دستکاری با ارسال SNI بی ربط و فیلتر نشدهخب حالا چاره چیه؟ نمی‌دونم. راه اصلی اینه که منتظر TLS های ورژن 1.3 و 1.4 باشیم که SNI رو رمز میکنه. (البته مثکه توی TLS 1.3 خبری از این قضیه نیست)راه دیگه اینه که SNI رو غیر فعال کنیم که الان کار سختیه. مثلاً قبلاً ایشون یه پلاگین برای فایرفاکس نوشته بودن که غیرفعالش می‌کردhttp://madynes.loria.fr/Research/Software#toc2اما خب قدیمیه و الان دیگه کار نمی‌کنه.راه دیگه اینکه که دست به کار شیم و خودمون براش کد بنویسیم که SNI رو همه جا موقتاً غیر فعال کنیم. بله، می‌دونم که با غیر فعال شدن SNI بعضی از سایت ها دیگه درست کار نمی‌کنن و گواهی‌نامه اشتباه میدن و بلا بلا بلا، ولی خب چاره چیه؟ این مشکل ها رو میشه سمت سرویس گیرنده چشم پوشی کرد و ارتباط رو ادامه داد.این بخشیه که از دوستان دولوپر می‌خوام که پاچه هاشونو بزنن بالا و وارد این کثافت بشن و درستش کنن.مچکرمپ.ن. کله گنده ها دارن روش سعی می‌کنن https://twitter.com/grittygrease/status/1018566026320019457پ.پ.ن. به نظر میاد کلادفلر داره یه حرکت هایی می‌زنهhttps://blog.cloudflare.com/encrypted-sni/پ.پ.پ.ن. کلادفلر حرکت قبلی رو بی‌خیال شده و دنبال حرکت جدیدیه:https://blog.cloudflare.com/encrypted-client-hello/که قراره در فایرفاکس ۸۵ اضافه بشه:https://blog.mozilla.org/security/2021/01/07/encrypted-client-hello-the-future-of-esni-in-firefox/</description>
                <category>مسیح یگانه</category>
                <author>مسیح یگانه</author>
                <pubDate>Thu, 14 Jun 2018 02:36:41 +0430</pubDate>
            </item>
            </channel>
</rss>