پیشنهاد می کنم متن گزارش اولیه سال 2020 توسط گئورگی رایلوف "WAL-G: فرصت های جدید و گسترش جامعه" را بخوانید.
نگهبانان منبع باز در حین رشد با چالش های زیادی روبرو می شوند. چگونه بیشتر و بیشتر ویژگیهای مورد نیاز را بنویسیم، مشکلات بیشتر و بیشتری را برطرف کنیم و بتوانیم درخواستهای بیشتر و بیشتری را مشاهده کنیم؟ با استفاده از WAL-G (ابزار پشتیبان گیری برای PostgreSQL) به عنوان مثال، به شما خواهم گفت که چگونه این مشکلات را با راه اندازی دوره ای در زمینه توسعه منبع باز در دانشگاه حل کردیم، چه دستاوردهایی به دست آوردیم و به کجا خواهیم رفت.
سلام مجدد به همه! من یک توسعه دهنده Yandex از یکاترینبورگ هستم. و امروز در مورد WAL-G صحبت خواهم کرد.
عنوان گزارش نمی گوید که چیزی در مورد پشتیبان گیری است. آیا کسی می داند WAL-G چیست؟ یا همه میدونن؟ اگر نمی دانید دست خود را بلند کنید. لعنتی، شما به گزارش آمدید و نمی دانید در مورد چیست.
بگذارید امروز به شما بگویم چه اتفاقی خواهد افتاد. این اتفاق می افتد که تیم ما مدت زیادی است که پشتیبان گیری می کند. و این یکی دیگر از گزارشهای مجموعهای است که در آن درباره نحوه ذخیره دادهها به صورت ایمن، ایمن، راحت و کارآمد صحبت میکنیم.
در سری های قبلی گزارش های زیادی توسط آندری بورودین و ولادیمیر لسکوف وجود داشت. ما خیلی بودیم. و ما سالها در مورد WAL-G صحبت می کنیم.
clck.ru/F8ioz —
clck.ru/Ln8Qw —
این گزارش از آنجایی که بیشتر در مورد بخش فنی بود، کمی متفاوت از سایر گزارشها خواهد بود، اما در اینجا در مورد چگونگی مواجهه با مشکلات مرتبط با رشد جامعه صحبت خواهم کرد. و چگونه به یک ایده کوچک رسیدیم که به ما کمک می کند با این موضوع کنار بیاییم.
چند سال پیش، WAL-G یک پروژه نسبتا کوچک بود که ما از Citus Data دریافت کردیم. و ما فقط آن را گرفتیم. و توسط یک نفر توسعه داده شد.
و فقط WAL-G نداشت:
- پشتیبان گیری از یک ماکت.
- هیچ نسخه پشتیبان افزایشی وجود نداشت.
- هیچ پشتیبان WAL-Delta وجود نداشت.
- و هنوز چیزهای زیادی گم شده بود.
در طول این چند سال، WAL-G بسیار رشد کرده است.
و تا سال 2020، همه موارد فوق قبلاً ظاهر شده اند. و آنچه اکنون داریم به این اضافه شد:
- بیش از 1 ستاره در GitHub.
- 150 چنگال.
- حدود 15 PR باز.
- و بسیاری از مشارکت کنندگان دیگر.
- و مسائل را همیشه باز کنید. و این در حالی است که ما به معنای واقعی کلمه هر روز به آنجا می رویم و کاری در مورد آن انجام می دهیم.
و ما به این نتیجه رسیدیم که این پروژه به توجه بیشتری نیاز دارد، حتی زمانی که خودمان نیازی به پیاده سازی هیچ چیزی برای سرویس پایگاه داده مدیریت شده خود در Yandex نداریم.
و جایی در پاییز 2018، ایده ای به ذهن ما رسید. معمولاً تیم راههای مختلفی برای توسعه برخی ویژگیها یا رفع اشکالها در صورت نداشتن دست کافی دارد. به عنوان مثال، می توانید توسعه دهنده دیگری را استخدام کنید و به او پول بدهید. یا می توانید برای مدتی کارآموز بگیرید و مقداری حقوق نیز به او بپردازید. اما هنوز گروه زیادی از افراد وجود دارند که برخی از آنها واقعاً می دانند چگونه کد بنویسند. شما فقط همیشه نمی دانید کد با چه کیفیتی است.
به این موضوع فکر کردیم و تصمیم گرفتیم برای جذب دانشجو تلاش کنیم. اما دانش آموزان با ما در همه چیز شرکت نخواهند کرد. آنها فقط بخشی از کار را انجام خواهند داد. و آنها، برای مثال، آزمایش می نویسند، اشکالات را برطرف می کنند، ویژگی هایی را اجرا می کنند که بر عملکرد اصلی تأثیر نمی گذارد. عملکرد اصلی ایجاد پشتیبان و بازیابی نسخه پشتیبان است. اگر در ایجاد یک نسخه پشتیبان اشتباه کنیم، با از دست دادن اطلاعات مواجه خواهیم شد. و البته هیچ کس این را نمی خواهد. همه می خواهند همه چیز بسیار امن باشد. بنابراین، البته، ما نمیخواهیم به کدهایی که کمتر از کد خود به آن اعتماد داریم، اجازه دهیم. یعنی هر کد غیر بحرانی چیزی است که ما می خواهیم از کارگران اضافی خود دریافت کنیم.
در چه شرایطی روابط عمومی دانشجویی پذیرفته می شود؟
- آنها باید کد خود را با تست پوشش دهند. همه چیز باید در CI اتفاق بیفتد.
- و همچنین 2 بررسی را مرور می کنیم. یکی از آندری بورودین و یکی از من.
- و علاوه بر این، برای بررسی اینکه آیا این چیزی در سرویس ما خراب نمی شود، من به طور جداگانه اسمبلی را با این commit آپلود می کنم. و در تستهای سرتاسر بررسی میکنیم که هیچ چیز شکست نمیخورد.
دوره ویژه متن باز
کمی در مورد اینکه چرا این مورد نیاز است و چرا این، به نظر من، ایده جالبی است.
برای ما سود واضح است:
- دست های اضافی می گیریم.
- و در بین دانش آموزان باهوشی که کد هوشمند می نویسند به دنبال کاندیدایی برای تیم هستیم.
چه فایده ای برای دانش آموزان دارد؟
آنها ممکن است کمتر آشکار باشند، زیرا دانش آموزان حداقل برای کدی که می نویسند پولی دریافت نمی کنند، بلکه فقط برای سوابق دانشجویی خود نمره دریافت می کنند.
از آنها در این مورد پرسیدم. و به قول آنها:
- تجربه مشارکت کننده در متن باز.
- یک خط در CV خود دریافت کنید.
- خود را ثابت کنید و در Yandex مصاحبه کنید.
- عضو GSoC شوید.
- +1 دوره ویژه برای کسانی که می خواهند کد بنویسند.
من در مورد چگونگی ساختار دوره صحبت نمی کنم. من فقط می گویم که WAL-G پروژه اصلی بود. ما همچنین پروژه هایی مانند Odyssey، PostgreSQL و ClickHouse را در این دوره گنجانده ایم.
و نه تنها در این دوره مشکل دادند، بلکه دیپلم و دروس هم دادند.
در مورد سود برای کاربران چیست؟
حالا بیایید به قسمتی برویم که بیشتر به آن علاقه دارید. این چه فایده ای برای شما دارد؟ نکته این است که دانش آموزان بسیاری از باگ ها را برطرف کردند. و ما ویژگی های درخواستی را که از ما خواسته بودید ایجاد کردیم.
و اجازه دهید در مورد چیزهایی که مدتهاست می خواستید و محقق شده اند به شما بگویم.
پشتیبانی از جدولها فضای جداول در WAL-G احتمالاً از زمان انتشار WAL-G انتظار میرفت، زیرا WAL-G جانشین ابزار پشتیبان دیگری WAL-E است که در آن پشتیبانگیری از پایگاه داده با جدولهای فضایی پشتیبانی میشد.
اجازه دهید به طور خلاصه به شما یادآوری کنم که چیست و چرا همه آن مورد نیاز است. به طور معمول، تمام داده های Postgres شما یک دایرکتوری را در سیستم فایل اشغال می کند که پایه نامیده می شود. و این دایرکتوری از قبل شامل تمام فایل ها و زیر شاخه های مورد نیاز Postgres است.
Tablespace ها دایرکتوری هایی هستند که حاوی داده های Postgres هستند، اما خارج از دایرکتوری پایه قرار ندارند. اسلاید نشان می دهد که tablespac ها خارج از دایرکتوری پایه قرار دارند.
این برای خود Postgres چگونه به نظر می رسد؟ یک زیر شاخه جداگانه pg_tblspc در دایرکتوری پایه وجود دارد. و حاوی پیوندهای نمادین به دایرکتوری هایی است که در واقع حاوی داده های Postgres خارج از دایرکتوری پایه هستند.
وقتی از همه اینها استفاده می کنید، ممکن است برای شما این دستورات چیزی شبیه به این باشد. یعنی یک جدول در یک جدول مشخص شده ایجاد می کنید و می بینید که الان کجاست. این دو خط آخر هستند که دو دستور آخر نامیده می شوند. و در آنجا مشخص است که راهی وجود دارد. اما در واقعیت، این راه واقعی نیست. این مسیر پیشوندی از دایرکتوری پایه به tablespace است. و از آنجا با یک پیوند نمادین مطابقت داده می شود که به داده های واقعی شما منتهی می شود.
ما از همه اینها در تیم خود استفاده نمیکنیم، اما بسیاری از کاربران WAL-E دیگر از آن استفاده کردند که به ما نوشتند که میخواهند به WAL-G بروند، اما این مانع آنها شد. این اکنون پشتیبانی می شود.
ویژگی دیگری که دوره ویژه ما برای ما به ارمغان آورد، کچاپ است. افرادی که احتمالاً بیشتر با اوراکل کار کرده اند تا با Postgres در مورد catchup می دانند.
به طور خلاصه در مورد چیست. توپولوژی خوشه در سرویس ما معمولاً ممکن است چیزی شبیه به این باشد. ما استاد داریم ماکتی وجود دارد که گزارشهای پیشنویس را از آن پخش میکند. و ماکت به استاد می گوید که در حال حاضر روی کدام LSN است. و در جایی به موازات این، لاگ را می توان بایگانی کرد. و علاوه بر بایگانی لاگ، نسخه های پشتیبان نیز به ابر ارسال می شود. و نسخه پشتیبان دلتا ارسال می شود.
مشکل از چی میتونه باشه؟ هنگامی که یک پایگاه داده نسبتاً بزرگ دارید، ممکن است معلوم شود که ماکت شما بسیار از Master عقب می ماند. و آنقدر عقب است که هرگز نمی تواند به او برسد. این مشکل معمولا باید به نحوی حل شود.
و ساده ترین راه این است که ماکت را حذف کنید و دوباره آن را آپلود کنید، زیرا هرگز جواب نمی دهد و مشکل باید حل شود. اما این زمان بسیار طولانی است، زیرا بازیابی کل یک نسخه پشتیبان از پایگاه داده 10 ترابایتی زمان بسیار بسیار طولانی است. و ما می خواهیم در صورت بروز چنین مشکلاتی، همه اینها را در سریع ترین زمان ممکن انجام دهیم. و این دقیقا همان چیزی است که catchup برای آن است.
Catchup به شما این امکان را می دهد که از نسخه های پشتیبان دلتا استفاده کنید که به این ترتیب در فضای ابری ذخیره می شوند. شما می گویید که نسخه عقب افتاده در حال حاضر روی کدام LSN است و آن را در دستور catchup مشخص می کنید تا یک نسخه پشتیبان دلتا بین آن LSN و LSN که خوشه شما در حال حاضر در آن قرار دارد ایجاد کنید. و پس از آن شما این پشتیبان را به ماکتی که عقب مانده بود بازیابی می کنید.
پایگاه های دیگر
دانش آموزان نیز به یکباره ویژگی های زیادی را برای ما به ارمغان آوردند. از آنجایی که در Yandex ما نه تنها Postgres را میپزیم، بلکه MySQL، MongoDB، Redis، ClickHouse را نیز داریم، در برخی مواقع باید بتوانیم با بازیابی لحظهای برای MySQL نسخه پشتیبان تهیه کنیم و فرصتی برای آپلود وجود داشته باشد. آنها را به ابر
و ما می خواستیم این کار را به روشی مشابه آنچه WAL-G انجام می دهد انجام دهیم. و ما تصمیم گرفتیم آزمایش کنیم و ببینیم همه چیز چگونه به نظر می رسد.
و در ابتدا بدون اینکه این منطق را به اشتراک بگذارند، کد را در فورک نوشتند. دیدند که ما نوعی مدل کار داریم و می تواند پرواز کند. سپس ما فکر کردیم که جامعه اصلی ما postgresists هستند، آنها از WAL-G استفاده می کنند. و بنابراین باید به نحوی این قسمت ها را از هم جدا کنیم. یعنی وقتی کد Postgres را ویرایش میکنیم، MySQL را نمیشکنیم؛ وقتی MySQL را ویرایش میکنیم، Postgres را نمیشکنیم.
اولین ایده در مورد نحوه جداسازی این ایده استفاده از همان رویکردی بود که در پسوندهای PostgreSQL استفاده می شود. و در واقع، برای تهیه یک نسخه پشتیبان از MySQL باید نوعی کتابخانه پویا را نصب کنید.
اما در اینجا عدم تقارن این رویکرد بلافاصله قابل مشاهده است. وقتی از Postgres نسخه پشتیبان تهیه می کنید، یک نسخه پشتیبان معمولی برای Postgres روی آن قرار می دهید و همه چیز خوب است. و برای MySQL معلوم می شود که یک نسخه پشتیبان برای Postgres نصب می کنید و همچنین یک کتابخانه پویا برای MySQL برای آن نصب می کنید. یه جورایی عجیب به نظر میاد ما هم همینطور فکر کردیم و به این نتیجه رسیدیم که این راه حل مورد نیاز ما نیست.
ساخت های مختلف برای Postgres، MySQL، MongoDB، Redis
اما به نظر می رسد این به ما اجازه داد تا به تصمیم درستی برسیم - مجامع مختلف را برای پایگاه های مختلف اختصاص دهیم. این امر امکان جداسازی منطق مرتبط با پشتیبانگیری از پایگاههای داده مختلف را فراهم کرد که به API مشترکی که WAL-G پیادهسازی میکند دسترسی خواهند داشت.
این قسمتی است که خودمان نوشتیم - قبل از اینکه مشکلات را به دانش آموزان بدهیم. یعنی دقیقاً این قسمتی است که آنها می توانند کار اشتباهی انجام دهند، بنابراین ما تصمیم گرفتیم که بهتر است چنین کاری انجام دهیم و همه چیز درست می شود.
بعد از آن مشکلات را مطرح کردیم. آنها بلافاصله برچیده شدند. دانشجویان ملزم به حمایت از سه پایگاه بودند.
این MySQL است که بیش از یک سال است که با استفاده از WAL-G از آن نسخه پشتیبان تهیه کرده ایم.
و اکنون MongoDB به تولید نزدیک می شود، جایی که آنها در حال تکمیل آن با یک فایل هستند. در واقع چارچوب همه اینها را نوشتیم. سپس دانش آموزان چیزهای قابل اجرا نوشتند. و سپس آنها را به حالتی می رسانیم که بتوانیم آن را در تولید بپذیریم.
این مشکلات به نظر نمی رسید که دانش آموزان نیاز به نوشتن ابزارهای پشتیبان کامل برای هر یک از این پایگاه داده ها داشته باشند. ما همچین مشکلی نداشتیم مشکل ما این بود که میخواستیم بازیابی لحظهای داشته باشیم و میخواستیم در فضای ابری نسخه پشتیبان تهیه کنیم. و از دانش آموزان خواستند کدی بنویسند که این مشکل را حل کند. دانشآموزان از ابزارهای پشتیبانگیری از قبل موجود استفاده کردند، که به نوعی پشتیبانگیری میکنند، و سپس همه آنها را با WAL-G به هم چسباندند، که همه آنها را به ابر ارسال کرد. و آنها همچنین بازیابی نقطه در زمان را به این اضافه کردند.
دانش آموزان چه چیز دیگری آوردند؟ آنها پشتیبانی از رمزگذاری Libsodium را به WAL-G آوردند.
ما همچنین سیاست های ذخیره سازی پشتیبان داریم. اکنون می توان نسخه های پشتیبان را به عنوان دائمی علامت گذاری کرد. و به نوعی برای سرویس شما راحت تر است که فرآیند ذخیره آنها را خودکار کند.
نتیجه این آزمایش چه بود؟
بیش از 100 نفر در ابتدا در این دوره ثبت نام کردند. در ابتدا نگفتم که دانشگاه یکاترینبورگ دانشگاه فدرال اورال است. آنجا همه چیز را اعلام کردیم. 100 نفر ثبت نام کردند در واقع، افراد بسیار کمتری شروع به انجام کاری کردند، حدود 30 نفر.
حتی تعداد کمتری از افراد دوره را تکمیل کردند، زیرا لازم بود برای کدهایی که از قبل وجود دارد، تست بنویسیم. و همچنین برخی از اشکال را برطرف کنید یا برخی از ویژگی ها را ایجاد کنید. و برخی از دانش آموزان همچنان دوره را تعطیل کردند.
در حال حاضر، در طول این دوره، دانشجویان حدود 14 مشکل را برطرف کرده و 10 ویژگی در اندازه های مختلف ساخته اند. و به نظر من این جایگزینی کامل یک یا دو توسعه دهنده است.
از جمله اینکه ما مدارک تحصیلی و درسی صادر کردیم. و 12 نفر دیپلم گرفتند. 6 نفر از آنها قبلاً در "5" از خود دفاع کرده اند. آنهایی که ماندند هنوز محافظی نداشتند، اما فکر می کنم همه چیز برای آنها هم خوب خواهد بود.
برنامه های آینده
چه برنامه ای برای آینده داریم؟
حداقل آن درخواست های ویژگی که قبلاً از کاربران شنیده ایم و می خواهیم انجام دهیم. این:
- نظارت بر صحت ردیابی خط زمانی در آرشیو پشتیبان خوشه HA. شما می توانید این کار را با WAL-G انجام دهید. و من فکر می کنم ما دانش آموزانی خواهیم داشت که این موضوع را انجام خواهند داد.
- ما قبلاً فردی داریم که مسئول انتقال پشتیبانگیری و WAL بین ابرها است.
- و ما اخیراً ایدهای منتشر کردهایم که میتوانیم با باز کردن نسخههای پشتیبان افزایشی بدون بازنویسی صفحات و بهینهسازی آرشیوهایی که به آنجا ارسال میکنیم، سرعت WAL-G را حتی بیشتر کنیم.
می توانید آنها را در اینجا به اشتراک بگذارید
این گزارش برای چه بود؟ علاوه بر این، اکنون علاوه بر 4 نفری که از این پروژه حمایت می کنند، دست های دیگری نیز داریم که تعداد آنها بسیار زیاد است. به خصوص اگر در یک پیام شخصی برای آنها بنویسید. و اگر از دادههای خود نسخه پشتیبان تهیه میکنید و این کار را با استفاده از WAL-G انجام میدهید یا میخواهید به WAL-G بروید، ما به راحتی میتوانیم خواستههای شما را برآورده کنیم.
این یک کد QR و یک لینک است. می توانید آنها را مرور کنید و تمام خواسته های خود را بنویسید. به عنوان مثال، ما برخی از باگ ها را رفع نمی کنیم. یا واقعاً یک ویژگی می خواهید، اما به دلایلی هنوز در هیچ نسخه پشتیبان از جمله ما وجود ندارد. حتما در این مورد بنویسید.
پرسش
سلام! با تشکر از گزارش! سوال در مورد WAL-G، اما نه در مورد Postgres. WAL-G از MySQL نسخه پشتیبان تهیه می کند و یک نسخه پشتیبان اضافی را فراخوانی می کند. اگر نصب های مدرن را روی CentOS انجام دهیم و اگر MySQL را نصب کنید، MariDB نصب خواهد شد. از نسخه 10.3 پشتیبان اضافی پشتیبانی نمی شود، پشتیبان گیری MariDB پشتیبانی می شود. با این کار چطوری؟
در حال حاضر ما سعی نکرده ایم از MariDB نسخه پشتیبان تهیه کنیم. ما درخواست هایی برای پشتیبانی FoundationDB داشته ایم، اما به طور کلی، اگر چنین درخواستی وجود داشته باشد، می توانیم افرادی را پیدا کنیم که این کار را انجام دهند. آنقدرها هم که فکر می کنم طولانی یا سخت نیست.
عصر بخیر با تشکر از گزارش! سوال در مورد ویژگی های جدید بالقوه. آیا آماده هستید که WAL-G با نوارها کار کند تا بتوانید از نوارها نسخه پشتیبان تهیه کنید؟
پشتیبان گیری در ذخیره سازی نوار ظاهرا یعنی؟
بله.
آندری بورودین وجود دارد که بهتر از من می تواند به این سوال پاسخ دهد.
(آندری) بله، ممنون از سوال! ما درخواستی برای انتقال یک نسخه پشتیبان به نوار از فضای ذخیرهسازی ابری داشتیم. و برای این
با تشکر از گزارش! روند توسعه جالب پشتیبان گیری یک بخش جدی از عملکرد است که باید به خوبی توسط آزمایش ها پوشش داده شود. وقتی عملکرد را برای پایگاههای اطلاعاتی جدید پیادهسازی کردید، آیا دانشآموزان تستها را هم مینوشتند یا خودتان تستها را مینویسید و سپس اجرا را به دانشآموزان میدهید؟
دانش آموزان هم تست می نوشتند. اما دانشجویان بیشتر برای ویژگی هایی مانند پایگاه داده های جدید نوشتند. تست های ادغام نوشتند. و تست های واحد را نوشتند. اگر ادغام انجام شود، یعنی در حال حاضر، این یک اسکریپت است که به صورت دستی اجرا می کنید یا مثلاً cron آن را انجام می دهد. یعنی فیلمنامه آنجا خیلی واضح است.
دانش آموزان تجربه زیادی ندارند. آیا بررسی زمان زیادی می برد؟
بله، بررسی ها زمان زیادی می برد. یعنی معمولاً وقتی چندین committer به یکباره می آیند و می گویند من این کار را کردم، آن کار را انجام دادم، پس باید فکر کنید و حدود نیم روز را کنار بگذارید تا بفهمید آنها در آنجا چه نوشته اند. زیرا کد باید با دقت خوانده شود. مصاحبه ای نداشتند. ما آنها را به خوبی نمی شناسیم، بنابراین زمان قابل توجهی می گیرد.
با تشکر از گزارش! قبلاً آندری بورودین اظهار داشت که archive_command در WAL-G باید مستقیماً فراخوانی شود. اما در مورد نوعی کارتریج خوشه ای، برای تعیین گره ای که شفت ها را از آن ارسال می کنیم، به منطق اضافی نیاز داریم. چگونه این مشکل را خودتان حل می کنید؟
مشکل شما اینجا چیست؟ فرض کنید یک ماکت همزمان دارید که با آن یک نسخه پشتیبان تهیه می کنید؟ یا چی؟
(آندری) واقعیت این است که در واقع WAL-G برای استفاده بدون اسکریپت پوسته در نظر گرفته شده است. اگر چیزی کم است، پس بیایید منطقی را که باید در WAL-G باشد اضافه کنیم. در مورد اینکه آرشیو باید از کجا بیاید، ما معتقدیم که بایگانی باید از استاد فعلی در خوشه باشد. آرشیو کردن از یک ماکت ایده بدی است. سناریوهای احتمالی مختلفی با مشکلات وجود دارد. به ویژه، مشکلات بایگانی جدول زمانی و هرگونه اطلاعات اضافی. با تشکر از سوال!
(توضیح: ما از شر اسکریپت های پوسته خلاص شدیم
عصر بخیر! با تشکر از گزارش! من به ویژگی catchup که در مورد آن صحبت کردید علاقه مند هستم. ما با موقعیتی مواجه شدیم که یک ماکت پشت سر بود و نمی توانست به آن برسد. و من توضیحی از این ویژگی در اسناد WAL-G پیدا نکردم.
Catchup به معنای واقعی کلمه در 20 ژانویه 2020 ظاهر شد. ممکن است اسناد به کار بیشتری نیاز داشته باشد. خودمان می نویسیم و خیلی خوب نمی نویسیم. و شاید باید از دانش آموزان بخواهیم آن را بنویسند.
آیا قبلا منتشر شده است؟
درخواست کشش قبلاً مرده است، یعنی آن را بررسی کردم. من این را روی یک خوشه آزمایشی امتحان کردم. تا کنون موقعیتی نداشته ایم که بتوانیم این را در یک نمونه رزمی آزمایش کنیم.
چه زمانی باید انتظار داشت؟
من نمی دانم. یک ماه صبر کنید، حتما بررسی می کنیم.
منبع: www.habr.com