İstifadəçilərimiz yorulmadan bir-birlərinə mesaj yazır.
Bu kifayət qədər çoxdur. Bütün istifadəçilərin bütün mesajlarını oxumaq üçün yola çıxsanız, bu, 150 min ildən çox vaxt aparacaq. Bir şərtlə ki, kifayət qədər qabaqcıl oxucusunuz və hər mesaja bir saniyədən çox vaxt sərf etməyin.
Belə bir həcmli məlumatla onu saxlamaq və əldə etmək üçün məntiqin optimal şəkildə qurulması çox vacibdir. Əks halda, o qədər də gözəl olmayan bir anda hər şeyin tezliklə pis gedəcəyi aydınlaşa bilər.
Bizim üçün bu an il yarım əvvəl gəldi. Buna necə gəldik və sonda nə oldu - biz sizə sıra ilə deyirik.
Ümumi məlumat
İlk tətbiqdə VKontakte mesajları PHP backend və MySQL-in kombinasiyası üzərində işləyirdi. Bu, kiçik bir tələbə veb saytı üçün tamamilə normal bir həlldir. Bununla belə, bu sayt nəzarətsiz böyüdü və özü üçün məlumat strukturlarının optimallaşdırılmasını tələb etməyə başladı.
2009-cu ilin sonunda ilk mətn mühərriki anbarı yazılmış və 2010-cu ildə mesajlar ona köçürülmüşdür.
Mətn mühərrikində mesajlar siyahılarda saxlanılırdı - bir növ "poçt qutuları". Hər bir belə siyahı bir uid tərəfindən müəyyən edilir - bütün bu mesajların sahibi olan istifadəçi. Mesaj bir sıra atributlara malikdir: həmsöhbətin identifikatoru, mətn, əlavələr və s. "Qutu" daxilində mesaj identifikatoru local_id-dir, heç vaxt dəyişmir və yeni mesajlar üçün ardıcıl təyin olunur. "Qutular" müstəqildir və mühərrik daxilində bir-biri ilə sinxronlaşdırılmır, aralarındakı əlaqə PHP səviyyəsində baş verir. Siz mətn mühərrikinin məlumat strukturuna və imkanlarına daxildən baxa bilərsiniz
Bu, iki istifadəçi arasında yazışmalar üçün kifayət idi. Təxmin et, sonra nə oldu?
2011-ci ilin may ayında VKontakte bir neçə iştirakçı ilə söhbətlər təqdim etdi - çoxlu söhbət. Onlarla işləmək üçün biz iki yeni klaster yaratdıq - üzv-çatlar və chat-üzvləri. Birincisi istifadəçilərin söhbətləri haqqında məlumatları, ikincisi isə söhbətlər vasitəsilə istifadəçilər haqqında məlumatları saxlayır. Siyahıların özlərinə əlavə olaraq, buraya, məsələn, dəvət edən istifadəçi və söhbətə əlavə olunduğu vaxt daxildir.
“PHP, gəlin söhbətə mesaj göndərək” istifadəçi deyir.
“Buyurun, {username}”, PHP deyir.
Bu sxemin mənfi cəhətləri var. Sinxronizasiya hələ də PHP-nin məsuliyyətidir. Böyük söhbətlər və onlara eyni vaxtda mesaj göndərən istifadəçilər təhlükəli hekayədir. Mətn mühərrikinin nümunəsi uid-dən asılı olduğundan, söhbət iştirakçıları eyni mesajı müxtəlif vaxtlarda ala bilər. Tərəqqi dayansaydı, bununla yaşaya bilərdi. Amma bu baş verməyəcək.
2015-ci ilin sonunda icma mesajlarını işə saldıq, 2016-cı ilin əvvəlində isə onlar üçün API istifadəyə verdik. İcmalarda böyük chatbotların meydana çıxması ilə hətta yük paylanmasını unutmaq mümkün oldu.
Yaxşı bir bot gündə bir neçə milyon mesaj yaradır - hətta ən çox danışan istifadəçilər bununla öyünə bilməz. Bu o deməkdir ki, bu cür botların yaşadığı mətn mühərrikinin bəzi nümunələri maksimum dərəcədə əziyyət çəkməyə başladı.
2016-cı ildə mesaj mühərrikləri 100 chat-üzv və üzv-çat nümunəsi və 8000 mətn mühərrikidir. Onlar hər biri 64 GB yaddaşa malik min serverdə yerləşdirilib. İlk təcili tədbir olaraq yaddaşı daha 32 GB artırdıq. Proqnozları təxmin etdik. Kəskin dəyişikliklər olmasa, bu, təxminən bir il üçün kifayət edərdi. Ya hardware əldə etməlisiniz, ya da verilənlər bazalarını optimallaşdırmalısınız.
Arxitekturanın təbiətinə görə, yalnız avadanlığı çoxaltmaq mənasızdır. Yəni, avtomobillərin sayını ən azı iki dəfə artırmaq - açıq-aydın, bu, kifayət qədər bahalı yoldur. Biz optimallaşdıracağıq.
Yeni konsepsiya
Yeni yanaşmanın mərkəzi mahiyyəti söhbətdir. Söhbətdə onunla əlaqəli mesajların siyahısı var. İstifadəçinin söhbətlərinin siyahısı var.
Tələb olunan minimum iki yeni verilənlər bazasıdır:
- söhbət mühərriki. Bu söhbət vektorlarının deposudur. Hər bir söhbətin ona aid mesaj vektoru var. Hər bir mesajın mətni və söhbət daxilində unikal mesaj identifikatoru var - chat_local_id.
- istifadəçi mühərriki. Bu, istifadəçilərin vektorlarının saxlanmasıdır - istifadəçilərə bağlantılar. Hər bir istifadəçinin peer_id vektoru (həmsöhbətlər - digər istifadəçilər, multi-chat və ya icmalar) və mesajların vektoru var. Hər bir peer_id-də ona aid mesajların vektoru var. Hər bir mesajın chat_local_id və həmin istifadəçi üçün unikal mesaj ID-si var - user_local_id.
Yeni klasterlər TCP-dən istifadə edərək bir-biri ilə əlaqə qurur - bu, sorğuların ardıcıllığının dəyişməməsini təmin edir. Sorğuların özləri və onlar üçün təsdiqlər sabit diskdə qeyd olunur - beləliklə, nasazlıqdan və ya mühərriki yenidən işə saldıqdan sonra istənilən vaxt növbənin vəziyyətini bərpa edə bilərik. İstifadəçi mühərriki və söhbət mühərriki hər biri 4 min parça olduğundan, klasterlər arasında sorğu növbəsi bərabər paylanacaq (lakin əslində heç biri yoxdur - və çox tez işləyir).
Verilənlər bazamızda disklə işləmək əksər hallarda ikili dəyişikliklər jurnalının (binlog), statik anlıq görüntülərin və yaddaşdakı qismən təsvirin birləşməsinə əsaslanır. Gün ərzində edilən dəyişikliklər binlog-a yazılır və cari vəziyyətin anlıq görüntüsü vaxtaşırı yaradılır. Snapshot məqsədlərimiz üçün optimallaşdırılmış məlumat strukturlarının toplusudur. O, başlıqdan (şəklin metaindeksi) və metafayllar dəstindən ibarətdir. Başlıq daimi olaraq RAM-da saxlanılır və snapshotdan məlumatların harada axtarılacağını göstərir. Hər bir metafayl zamanın yaxın nöqtələrində lazım ola biləcək məlumatları ehtiva edir, məsələn, bir istifadəçi ilə əlaqəli. Snapshot başlığından istifadə edərək verilənlər bazasını sorğuladığınız zaman tələb olunan metafayl oxunur və sonra snapshot yaradıldıqdan sonra binlogda baş verən dəyişikliklər nəzərə alınır. Bu yanaşmanın faydaları haqqında daha çox oxuya bilərsiniz
Eyni zamanda, sabit diskdəki məlumatlar gündə yalnız bir dəfə dəyişir - Moskvada gecə gec saatlarda, yük minimal olduqda. Bunun sayəsində (diskdəki strukturun gün ərzində sabit olduğunu bilərək) vektorları sabit ölçülü massivlərlə əvəz edə bilərik - və bunun sayəsində yaddaş qazanırıq.
Yeni sxemdə mesaj göndərilməsi belə görünür:
- PHP backend mesaj göndərmək tələbi ilə istifadəçi mühərriki ilə əlaqə saxlayır.
- istifadəçi mühərriki sorğunu istədiyiniz chat-mühərriki nümunəsinə etibar edir, o, istifadəçi mühərrikinə chat_local_id - bu söhbət daxilində yeni mesajın unikal identifikatoruna qayıdır. Daha sonra chat_engine mesajı söhbətdəki bütün alıcılara yayımlayır.
- user-engine chat-engine-dən chat_local_id alır və user_local_id-i PHP-yə qaytarır - bu istifadəçi üçün unikal mesaj identifikatoru. Bu identifikator daha sonra, məsələn, API vasitəsilə mesajlarla işləmək üçün istifadə olunur.
Ancaq əslində mesaj göndərməklə yanaşı, daha bir neçə vacib şeyi həyata keçirməlisiniz:
- Alt siyahılar, məsələn, söhbət siyahısını açarkən gördüyünüz ən son mesajlardır. Oxunmamış mesajlar, etiketli mesajlar (“Vacib”, “Spam” və s.).
- Söhbət mühərrikində mesajların sıxılması
- İstifadəçi mühərrikində mesajların keşləşdirilməsi
- Axtarış (bütün dialoqlar vasitəsilə və müəyyən bir dialoq daxilində).
- Real vaxt yeniləməsi (Longpolling).
- Mobil müştərilərdə keşləməni həyata keçirmək üçün tarixçənin saxlanması.
Bütün alt siyahılar sürətlə dəyişən strukturlardır. Onlarla işləmək üçün istifadə edirik
Mesajlar sıxışdırmaq üçün faydalı olan çoxlu sayda məlumatı, əsasən mətni ehtiva edir. Hətta bir fərdi mesajı dəqiq şəkildə arxivdən çıxara bilməyimiz vacibdir. Mesajları sıxışdırmaq üçün istifadə olunur
Çatlardan daha az istifadəçi olduğundan, təsadüfi giriş disk sorğularını chat-mühərrikində saxlamaq üçün biz mesajları istifadəçi mühərrikində keşləyirik.
Mesaj axtarışı istifadəçi mühərrikindən bu istifadəçinin söhbətlərini ehtiva edən bütün söhbət mühərriki nümunələrinə diaqonal sorğu kimi həyata keçirilir. Nəticələr istifadəçi mühərrikinin özündə birləşdirilir.
Yaxşı, bütün detallar nəzərə alındı, yalnız yeni bir sxemə keçmək qalır - və tercihen istifadəçilər bunu fərq etmədən.
Məlumat miqrasiyası
Beləliklə, bizdə istifadəçinin mesajlarını saxlayan mətn mühərriki və çoxlu söhbət otaqları və onlarda olan istifadəçilər haqqında məlumatları saxlayan iki qrup chat-üzvləri və üzv-çatlar var. Bundan yeni istifadəçi mühərrikinə və söhbət mühərrikinə necə keçmək olar?
Köhnə sxemdəki üzv söhbətləri əsasən optimallaşdırma üçün istifadə edilmişdir. Biz ondan lazımi məlumatları tez bir zamanda chat-üzvlərinə ötürdük və o, artıq miqrasiya prosesində iştirak etmədi.
Çat üzvləri üçün növbə. Buraya 100 nümunə daxildir, chat-mühərrikində isə 4 min. Məlumatları ötürmək üçün onu uyğunlaşdırmaq lazımdır - bunun üçün chat üzvləri eyni 4 min nüsxəyə bölündü və sonra söhbət mühərrikində chat üzvlərinin binloqunun oxunması işə salındı.
İndi chat-mühərriki chat üzvlərindən çoxlu söhbət haqqında bilir, lakin iki həmsöhbətlə dialoqlar haqqında hələ heç nə bilmir. Bu cür dialoqlar istifadəçilərə istinadla mətn mühərrikində yerləşir. Burada məlumatları "baş-başa" götürdük: hər bir söhbət mühərriki nümunəsi bütün mətn mühərriki nümunələrindən lazım olan dialoqun olub olmadığını soruşdu.
Əla - chat-mühərriki çoxlu söhbətlərin nə olduğunu bilir və hansı dialoqların olduğunu bilir.
Çox söhbətli söhbətlərdə mesajları birləşdirməlisiniz ki, hər bir söhbətdə mesajların siyahısını əldə edəsiniz. Birincisi, söhbət mühərriki bu söhbətdən bütün istifadəçi mesajlarını mətn mühərrikindən alır. Bəzi hallarda onların sayı kifayət qədər çoxdur (yüz milyonlara qədər), lakin çox nadir istisnalarla söhbət tamamilə RAM-a uyğun gəlir. Hər biri bir neçə nüsxədə sıralanmamış mesajlarımız var - axı, hamısı istifadəçilərə uyğun gələn müxtəlif mətn mühərriki nümunələrindən götürülüb. Məqsəd mesajları çeşidləmək və lazımsız yer tutan nüsxələrdən xilas olmaqdır.
Hər bir mesajın göndərildiyi vaxtı və mətni ehtiva edən vaxt möhürü var. Çeşidləmə üçün vaxtdan istifadə edirik - biz multichat iştirakçılarının ən köhnə mesajlarına göstəricilər yerləşdiririk və artan vaxt damğasına doğru irəliləyərək nəzərdə tutulan nüsxələrin mətnindən hashləri müqayisə edirik. Nüsxələrin eyni hash və vaxt damğasına sahib olması məntiqlidir, lakin praktikada bu həmişə belə olmur. Yadınızdadırsa, köhnə sxemdə sinxronizasiya PHP tərəfindən həyata keçirilirdi - nadir hallarda isə eyni mesajın göndərilmə vaxtı müxtəlif istifadəçilər arasında fərqlənirdi. Bu hallarda, biz özümüzə vaxt damğasını redaktə etməyə icazə verdik - adətən bir saniyə ərzində. İkinci problem müxtəlif alıcılar üçün mesajların fərqli sıralanmasıdır. Belə hallarda biz müxtəlif istifadəçilər üçün müxtəlif sifariş variantları ilə əlavə nüsxənin yaradılmasına icazə verdik.
Bundan sonra, multichatdakı mesajlar haqqında məlumat istifadəçi mühərrikinə göndərilir. Və burada idxal olunan mesajların xoşagəlməz xüsusiyyəti gəlir. Normal əməliyyatda mühərrikə gələn mesajlar ciddi şəkildə user_local_id tərəfindən artan qaydada sıralanır. Köhnə mühərrikdən istifadəçi mühərrikinə idxal edilən mesajlar bu faydalı xüsusiyyətini itirdi. Eyni zamanda, testin rahatlığı üçün onlara tez daxil olmaq, onlarda nəsə axtarmaq və yenilərini əlavə etmək lazımdır.
Biz idxal edilmiş mesajları saxlamaq üçün xüsusi məlumat strukturundan istifadə edirik.
Ölçü vektorunu təmsil edir hamı haradadır - fərqlidir və elementlərin xüsusi sırası ilə azalan ardıcıllıqla düzülür. İndekslərlə hər bir seqmentdə elementlər sıralanır. Belə strukturda elementin axtarışı vaxt tələb edir vasitəsilə ikili axtarışlar. Elementin əlavəsi amortizasiya olunur .
Beləliklə, məlumatları köhnə mühərriklərdən yenilərinə necə ötürəcəyimizi anladıq. Amma bu proses bir neçə gün çəkir - və çətin ki, bu günlər ərzində istifadəçilərimiz bir-birlərinə yazmaq vərdişindən əl çəksinlər. Bu müddət ərzində mesajları itirməmək üçün biz həm köhnə, həm də yeni klasterlərdən istifadə edən iş sxeminə keçirik.
Məlumat chat üzvlərinə və istifadəçi mühərrikinə yazılır (köhnə sxemə görə normal işdə olduğu kimi mətn mühərrikinə deyil). user-engine chat-engine sorğusunu proksiləşdirir - və burada davranış bu söhbətin artıq birləşdirilib-qovuşmamasından asılıdır. Əgər söhbət hələ birləşdirilməyibsə, söhbət mühərriki mesajı özünə yazmır və onun işlənməsi yalnız mətn mühərrikində baş verir. Əgər söhbət artıq chat mühərrikinə birləşdirilibsə, o, chat_local_id-i istifadəçi mühərrikinə qaytarır və mesajı bütün alıcılara göndərir. istifadəçi mühərriki bütün məlumatları mətn mühərrikinə proksiləşdirir - belə ki, nəsə baş verərsə, biz həmişə köhnə mühərrikdə bütün cari məlumatları saxlayaraq geri dönə bilərik. text-engine user_local_id-i qaytarır, istifadəçi mühərriki onu saxlayır və arxa tərəfə qaytarır.
Nəticədə keçid prosesi belə görünür: biz boş istifadəçi-mühərriki və chat-motor klasterlərini birləşdiririk. chat-engine bütün söhbət üzvlərinin binlogunu oxuyur, sonra yuxarıda təsvir edilən sxemə uyğun olaraq proxying başlayır. Köhnə məlumatları köçürür və iki sinxronlaşdırılmış klaster alırıq (köhnə və yeni). Qalan tək şey oxunu mətn mühərrikindən istifadəçi mühərrikinə keçmək və proksiinqi söndürməkdir.
Tapıntılar
Yeni yanaşma sayəsində mühərriklərin bütün performans göstəriciləri təkmilləşdirildi və məlumatların ardıcıllığı ilə bağlı problemlər həll edildi. İndi biz mesajlarda yeni funksiyaları tez bir zamanda tətbiq edə bilərik (və artıq bunu etməyə başlamışıq - söhbət iştirakçılarının maksimum sayını artırdıq, yönləndirilmiş mesajlar üçün axtarış həyata keçirdik, sabitlənmiş mesajları işə saldıq və hər istifadəçiyə düşən mesajların ümumi sayına limiti artırdıq) .
Məntiqdəki dəyişikliklər həqiqətən çox böyükdür. Və qeyd etmək istərdim ki, bu, heç də həmişə nəhəng bir komanda və saysız-hesabsız kod sətirləri tərəfindən bütün illərin inkişafı demək deyil. chat-motoru və istifadəçi mühərriki ilə birlikdə mesajın sıxılması üçün Huffman, Splay ağacları və idxal edilmiş mesajlar üçün struktur kimi bütün əlavə hekayələr 20 min sətirdən az koddur. Və onlar cəmi 3 ay ərzində 10 tərtibatçı tərəfindən yazılmışdır (lakin nəzərə almağa dəyər ki,
Üstəlik, serverlərin sayını iki dəfə artırmaq əvəzinə, biz onların sayını yarıya qədər azaltdıq - indi istifadəçi mühərriki və söhbət mühərriki 500 fiziki maşında yaşayır, yeni sxemdə isə yükləmə üçün böyük boşluq var. Avadanlıqlara çox pul qənaət etdik - təxminən 5 milyon dollar + əməliyyat xərclərinə ildə 750 min dollar.
Biz ən mürəkkəb və genişmiqyaslı problemlər üçün ən yaxşı həll yollarını tapmağa çalışırıq. Bizdə onların çoxu var və buna görə də verilənlər bazası şöbəsində istedadlı tərtibatçılar axtarırıq. Əgər belə problemlərin həllini sevirsinizsə və bilirsinizsə, alqoritmlər və məlumat strukturları haqqında mükəmməl biliyə sahibsinizsə, sizi komandaya qoşulmağa dəvət edirik. Bizimlə əlaqə saxlayın
Bu hekayə sizin haqqınızda olmasa belə, tövsiyələrə dəyər verdiyimizi nəzərə alın. Dostuna danış
Mənbə: www.habr.com