38-წუთიანი ღრმა სესია, რომელიც იქიდან იწყება, სადაც შესავალი დეკი დასრულდა. ის გულისხმობს, რომ მასშტაბირება, ქეშირება, რეპლიკაცია, რიგები და CAP უკვე იცით სისტემური დიზაინიდან მასშტაბისთვის — და მიდის რთულ ნაწილზე: როგორ შეთანხმდნენ ბევრი მანქანა, ბევრ რეგიონში, მაშინ როცა ქსელიც და მანქანებიც განუწყვეტლივ ავარდება.
ნაწილი 1 CAP-ით დასრულდა: როცა ქსელი იყოფა, ირჩევთ C-ს ან A-ს. ეს დეკი ერთი დონით ქვემოთ იწყება. თანმიმდევრულობა გადამრთველი არაა — ეს გარანტიების კიბეა, სადაც ყოველი შემდეგი წინაზე სუსტი და იაფია. თითოეული მონაცემისთვის სწორი საფეხურის არჩევა — სწორედ ესაა მთელი ხელოვნება. თუ დანაწილებასა და რეპლიკაციის ჩამორჩენას გახსენება სჭირდება, ეს შესავალი დეკია.
კიბეზე ქვემოთ ყოველი მოდელი ნაკლებ დალაგებას კრძალავს — და ნაკლები კოორდინაცია სჭირდება, ამიტომ უფრო სწრაფი და ხელმისაწვდომია.
წაკითხვა t1-ის შემდეგ იწყება, ამიტომ ლინეარიზებადობა აიძულებს, დააბრუნოს ახალი მნიშვნელობა 1.
გლობალური წესრიგი იშვიათად გჭირდებათ; გჭირდებათ, რომ თქვენივე ხედვა იყოს აზრიანი. ეს სესიის დონის დაპირებები eventual საცავის თავზე ზის და სწორედ იმ სიმპტომებს კურნავს, რომლებსაც მომხმარებლები ამჩნევენ — ჩვეულებრივ სესიის ერთ რეპლიკაზე მიწებებით ან ვერსიის ტოკენის თვალყურის დევნებით.
პროფილის განახლების შემდეგ თქვენ ახალ მნიშვნელობას ხედავთ შემდეგ წაკითხვაზე — მაშინაც კი, თუ სხვა მომხმარებლები ცოტა ხანს ვერ ხედავენ. ყველაზე ხშირად გამოტოვებული გარანტია, რომლის უკანაც დგას ბაგის რეპორტი "შევინახე და გაქრა".
როგორც კი მნიშვნელობა დაინახეთ, აღარასდროს ნახავთ უფრო ძველს — გვერდის განახლებამ უფრო ჩამორჩენილ რეპლიკაზე დროში მოგზაურობა არ უნდა გამოიწვიოს.
ჩაწერებს ნამდვილი წესრიგის პრეფიქსად ხედავთ — არასდროს პასუხს იმ შეტყობინებამდე, რომელსაც ის პასუხობს. სწორედ ეს დაპირება ინარჩუნებს ჩატების წაკითხვადობას.
პრაქტიკული წესი: ნაგულისხმევად აირჩიეთ eventual + სესიის გარანტიები, ლინეარიზებადობა კი მხოლოდ იმ რამდენიმე ინვარიანტზე დახარჯეთ, რომელიც არასდროს უნდა მოიხაროს — ბალანსები, მარაგის რაოდენობა, უნიკალური მომხმარებლის სახელები, ლიდერის ვინაობა.
ძლიერი თანმიმდევრულობა, ერთი ლიდერი, დაფიქსირებული ლოგი: სიღრმეში ყველა მათგანი კონსენსუსამდე დაიყვანება — როცა ბევრი კვანძი ერთ მნიშვნელობაზე თანხმდება, მიუხედავად ავარიებისა და დანაკარგიანი ქსელისა. ეს არის მზიდი ალგორითმი ყველა იმ საკოორდინაციო სისტემისა, რომელზეც დამოკიდებული ხართ.
Raft ამოცანას ორ ამბად ყოფს: აირჩიე ერთი ლიდერი, შემდეგ კი ამ ლიდერს მიაცემინე მხოლოდ-დამატებადი ლოგის რეპლიკაცია. ყოველი გადაწყვეტილება უმრავლესობის ქვორუმზე დგას — 2f+1 კვანძით f ავარიას გადაიტანთ, რადგან ნებისმიერი ორი უმრავლესობა ერთმანეთს გადაფარავს.
5-დან 3 ხმა უმრავლესობაა — ლიდერი ერთი კვანძის ჩავარდნისასაც ძალაში რჩება.
// leader-only write path; followers just append function onClientWrite(cmd: Command): void { log.append({ term, cmd }) // 1 · append locally sendAppendEntries(followers, log) // 2 · replicate (RPC) if (acks >= majority()) { // 3 · quorum = N/2 + 1 commitIndex = log.lastIndex // durable once a majority has it applyToStateMachine(cmd) // 4 · apply IN LOG ORDER } }
Paxos პირველი გაჩნდა და იმავე უსაფრთხოებას ამტკიცებს, ოღონდ ლიდერისა და ლოგის ნაცვლად წამომყენებლების, მიმღებებისა და შემსწავლელების ტერმინებში. ერთი მნიშვნელობა ორ რაუნდში ირჩევა:
n და უმრავლესობას სთხოვს, დაპირდნენ, რომ ყველაფერს დაბალს უგულებელყოფენ. მიმღებები პასუხობენ იმ მნიშვნელობით, რომელიც უკვე მიიღეს.n-ს მნიშვნელობასთან ერთად (თუ ასეთი არსებობს, უკვე მიღებულთაგან ყველაზე მაღალს). თუ უმრავლესობა მას მიიღებს, მნიშვნელობა სამუდამოდ არჩეულია.ჰგავს წინადადების მიღებას კრებაზე: ჯერ დარწმუნდი, რომ სიტყვა შენია (უმრავლესობა მოგისმენს), მერე კი ხმას მიეცი — და უმრავლესობის "კი" მას სავალდებულოს ხდის.
Raft-ს თითქმის არასდროს ახორციელებთ თავად. ის იჯარით აიღება: პატარა, ძლიერად თანმიმდევრული საცავი, რომელიც ყველა დანარჩენისთვის ლიდერის იჯარებს, კონფიგურაციას, ბლოკირებებსა და სერვისების რეესტრებს ინახავს.
Raft-ზე დაშენებული გასაღები-მნიშვნელობის საცავი; Kubernetes-ის ტვინი.
ვეტერანი: znodes-ის იერარქია ZAB პროტოკოლზე.
Raft KV პლუს სერვისების აღმოჩენა, ჯანმრთელობის შემოწმებები და მრავალ-დატაცენტრი.
როგორ ავირჩიო: Kubernetes-ზე etcd უკვე გაშვებული გაქვთ — გამოიყენეთ ის. Consul აირჩიეთ მაშინ, როცა სერვისების აღმოჩენა და მრავალ-DC გჭირდებათ, და არა მხოლოდ კოორდინაცია. ZooKeeper-ს ძირითადად მაშინ მიმართეთ, როცა ძველ JVM სისტემებთან ინტეგრაცია გჭირდებათ, რომლებიც მას კვლავ ელოდებიან.
ერთი ACID ტრანზაქცია სერვისებს შორის ნიშნავს, რომ ერთ ნელ სერვისს ყველას ბლოკირებების დაჭერა შეუძლია. თანამედროვე პასუხი უფრო დიდი ტრანზაქცია არაა — ეს არის თითო სერვისის ლოკალური ტრანზაქციები, ერთმანეთზე შეკერილი კომპენსაციით, საიმედო მოვლენებითა და იდემპოტენტურობით.
2PC ატომურია, მაგრამ მბლოკავი — კოორდინატორის ავარია PREPARE-ის შემდეგ ყველა მონაწილეს აყინავს.
საგა არის ლოკალური ტრანზაქციების თანმიმდევრობა, სადაც თითოეულს მაკომპენსირებელი ქმედება აქვს, რომელიც მას აზრობრივად აბრუნებს უკან. თუ ნაბიჯი ჩავარდა, დასრულებული ნაბიჯების კომპენსაციებს უკუღმა უშვებთ. არც გლობალური ბლოკირებები, არც ბლოკირება — მაგრამ არც იზოლაცია, ამიტომ უნდა დაგეგმოთ ისე, რომ შუალედური მდგომარეობები ხილვადი იქნება.
// each step pairs a forward action with its undo const saga = [ { do: reserveStock, undo: releaseStock }, { do: chargeCard, undo: refundCard }, { do: bookCourier, undo: cancelCourier }, ] // fail at step i → run undo for i-1 … 0, newest first
საგები და მოვლენები გულისხმობს, რომ საიმედოდ შეგიძლიათ "განაახლოთ DB და გამოაქვეყნოთ შეტყობინება". ორივეს ატომურად ორ სისტემაში ვერ გააკეთებთ — მათ შორის ავარია და ისინი ერთმანეთს დაშორდება. ტრანზაქციული outbox მოვლენას იმავე მონაცემთა ბაზის ტრანზაქციაში წერს, რომელშიც მდგომარეობის ცვლილებაა, შემდეგ კი რელეი მას აგზავნის.
// ONE local transaction writes BOTH rows — atomic await db.tx(async (t) => { await t.insert("orders", order) // business state await t.insert("outbox", { // event, same tx topic: "order.placed", payload: order, }) }) // a relay tails the outbox (poll or WAL/CDC) → broker → marks sent
მოვლენა მდგომარეობასთან ერთად ატომურად ფიქსირდება; რელეი მას სულ მცირე ერთხელ მიაწვდის — არასდროს დაკარგული ან ფანტომური მოვლენა.
სულ მცირე ერთხელ მიწოდება და კლიენტის ხელახალი მცდელობები ნიშნავს, რომ ყოველი ჩაწერა შეიძლება ორჯერ მოვიდეს. იდემპოტენტურობის გასაღები — კლიენტის მიერ მოწოდებული უნიკალური id თითო ლოგიკურ ოპერაციაზე — სერვერს საშუალებას აძლევს, გამეორება ამოიცნოს და იგივე შედეგი დააბრუნოს, ბარათის ხელახლა დახარჯვის ნაცვლად.
// client sends Idempotency-Key: <uuid> with the request async function charge(key: string, amount: number) { const ok = await store.setNX(key, { status: "pending" }) // claim it if (!ok) return await store.wait(key) // replay → saved result const result = await gateway.charge(amount) await store.set(key, { status: "done", result }, { ttl: "24h" }) return result }
setNX / უნიკალურობის შეზღუდვა) — ის პარალელურ ხელახალ მცდელობებს ალაგებს, ისე რომ ზუსტად ერთი სრულდება.კონტინენტებს შორის შემოვლა ~100–200 ms-ია, რამდენიც არ უნდა დახარჯოთ — ეს ფიზიკაა, და არა ბიუჯეტი. გადადით მრავალ რეგიონზე და ყოველი სინქრონული გლობალური ჩაწერა ამ ბაჟს გადაიხდის. ამიტომ ირჩევთ: ან ჩაწერები ერთ რეგიონში ჩამწკრივოთ, ან რეგიონებმა დამოუკიდებლად წერონ და კონფლიქტები შეათანხმონ.
N რეპლიკის დროს, თუ ჩაწერა W-ს ეხება და წაკითხვა R-ს, სადაც R + W > N, ყოველი წაკითხვა უახლეს ჩაწერას კვეთს — რეგულირებადი თანმიმდევრულობა ერთი ლიდერის გარეშე, რომელსაც გადართვა დასჭირდებოდა.რადგან R + W > N, წაკითხვის ქვორუმს ყოველთვის აქვს საერთო კვანძი ჩაწერის ქვორუმთან — ანუ ახალი მნიშვნელობა ყოველთვის მისაწვდომია.
W=N უსაფრთხო ჩაწერისთვის, R=1 სწრაფი წაკითხვისთვის, ან ორივე დააბალანსეთ.ლიდერის გარეშე პარალელური ჩაწერები აუცილებლად დაუპირისპირდება ერთმანეთს. გჭირდებათ დეტერმინისტული, რიგისგან დამოუკიდებელი გზა მათი შერწყმისთვის — თორემ მონაცემებს უხმაუროდ დაკარგავთ.
შეინახეთ ის ჩაწერა, რომელსაც უმაღლესი დროის ნიშნული აქვს. სასაცილოდ მარტივია — და მეორე პარალელურ ჩაწერას ყრის. ქეშებისა და ონლაინ სტატუსისთვის კარგია; საშიშია ყველაფრისთვის, რასაც ვერ დაკარგავთ.
თითო კვანძის ვერსიის ვექტორი ყოველ ჩაწერას ნიშნავს, ისე რომ საცავს შეუძლია პარალელურობის აღმოჩენა (არცერთი არაა მეორეზე ადრე მომხდარი) და კონფლიქტების ამოტანა — შემდეგ კი აპლიკაციის ლოგიკა ან შერწყმა წყვეტს.
მონაცემთა ტიპები, რომელთა შერწყმის დამთხვევაც მათემატიკურად გარანტირებულია — არც ცენტრალური არბიტრი, არც დაკარგული განახლებები. ყველაზე ძლიერი პასუხი, როცა ის თქვენს მონაცემებს ერგება.
თითო კვანძზე მაქსიმუმის აღება არაფერს კარგავს და ერთსა და იმავე შედეგამდე მიდის, როგორი რიგითაც არ უნდა გადაიკვეთოს შერწყმები — სწორედ ესაა CRDT-ის გარანტია.
გლობალურად გარეგნულად თანმიმდევრული SQL, დალაგებული TrueTime-ით (GPS + ატომური საათები).
Spanner-ის იდეა ატომური საათების გარეშე — ჰიბრიდული ლოგიკური საათები და serializable იზოლაცია, Postgres-თან თავსებადი.
ულიდერო, რეგულირებადი თანმიმდევრულობის ფართო-სვეტოვანი / KV საცავები, აგებული ჩაწერის მოცულობისთვის.
როგორ ავირჩიო: გჭირდებათ გლობალური ძლიერი თანმიმდევრულობა SQL-ით და GCP-ზე ხართ → Spanner. გინდათ იგივე, ოღონდ პორტატული და Postgres-ის ფორმის → CockroachDB. გჭირდებათ ჩაწერის უკიდურესი მასშტაბი და რეგულირებად/eventual თანმიმდევრულობას იტანთ → Cassandra ან Dynamo-ს სტილის საცავი.
უსაზღვრო მოთხოვნა ყოველთვის იპოვის თქვენს ყველაზე სუსტ კომპონენტს. სიხშირის ლიმიტი ზღუდავს იმას, რისი გამოგზავნაც გამომძახებლებს შეუძლიათ; უკუწნევა გაჯერებულ კომპონენტს აძლევს საშუალებას, ზემოთ მდგარებს შენელება სთხოვოს — იმის ნაცვლად, რომ ჩუმად აგროვოს რიგი, სანამ არ ჩავარდება.
ტოკენები r ტემპით წვეთავს; ნახტომი ვედროს ცლის, შემდეგ კი გამომძახებლები შევსების ტემპამდე ნელდებიან.
დათვალეთ მოთხოვნები საათის ყოველ წუთზე. ტრივიალურია, მაგრამ საზღვარზე გადამჯდარ 2× ნახტომს უშვებს — ორი სრული ფანჯარა ზედიზედ.
შეინახეთ ყოველი მოთხოვნის დროის ნიშნული (მაგალითად, Redis-ის sorted set-ში) და ბოლო 60 წამი ზუსტად დათვალეთ. ზუსტია, მაგრამ მეხსიერება ტრაფიკთან ერთად იზრდება.
წინა ფანჯრის რაოდენობა აწონეთ იმით, რამდენად შორს ხართ მიმდინარეში. საზღვრის ნახტომს O(1) მეხსიერებით აგლუვებს — ჩვეულებრივი არჩევანი პროდაქშენში.
ერთი კვანძის მეხსიერებაში მყოფი მთვლელი მთელ ფლოტზე არ მუშაობს — გჭირდებათ საერთო, ატომური საცავი. Redis ნაგულისხმევია: ქვემოთ მოცემული ყოველი მიდგომა ერთ ატომურ ოპერაციად სრულდება, ამიტომ პარალელური მოთხოვნები ლიმიტს ვერ გაუსწრებენ.
INCR თითო ფანჯრის გასაღებზე, TTL-ით.როგორ ავირჩიო: ნაგულისხმევად აიღეთ Lua-ს token bucket — ნახტომებიანი, O(1), ატომური. დაეშვით უბრალო INCR-ზე, როცა უხეში ჭერიც საკმარისია, ხოლო sorted-set ფანჯარა მხოლოდ მაშინ გამოიყენეთ, როცა მართლა ზუსტი რაოდენობები გჭირდებათ. ძალიან მაღალ RPS-ზე ლიმიტი ლოკალურად დააწესეთ, თითო კვანძის მცირე ბიუჯეტით, და პერიოდულად დაასინქრონეთ Redis-თან — ცოტა სიზუსტეს შეყოვნებაზე გაცვლით.
სავსე შეზღუდული რიგი უკუწნევის სიგნალია: შეანელეთ პროდიუსერი ან ჩამოიშორეთ ის, რაც არ ეტევა.
განაწილებულ სისტემაში ჩავარდნები კასკადურად ვრცელდება: ნელი ქვედა სერვისი თრედებს იკავებს, ეს თრედები გამომძახებელს აჩერებენ, გაჩერება კი სტეკზე მაღლა ადის, სანამ ყველაფერი "ჩავარდნილი" არ გახდება. ეს ოთხი პატერნი აფეთქების რადიუსს ზღუდავს.
N ჩავარდნის შემდეგ იხსნება; გაგრილების შემდეგ ერთი half-open შემოწმება წყვეტს, დაიხუროს თუ არა ისევ.
მიეცით ყოველ დამოკიდებულებას საკუთარი თრედების/კავშირების პული, რომ ერთმა დატბორილმა დამოკიდებულებამ დანარჩენები არ დააშიმშილოს — როგორც წყალგაუმტარი განყოფილებები გემის კორპუსში. ნელი სერვისი საკუთარ ტიხარში იხრჩობა და არა მთელ გემში.
გაიმეორეთ მხოლოდ იდემპოტენტური გამოძახებები, ექსპონენციალური backoff-ითა და jitter-ით, რომ ათასმა კლიენტმა ერთდროულად არ გაიმეოროს და აღდგენად სერვისს ხელახლა არ დაეცეს ხროვად. საერთო მცდელობები შეზღუდეთ გამეორებების ბიუჯეტით.
გაჯერებისას ჩამოაგდეთ ყველაზე ნაკლებად მნიშვნელოვანი სამუშაო — დაშვების კონტროლი კარებთანვე. მომსახურებული გადახდა და ჩამოგდებული ანალიტიკის პინგი ორივეს ჩავარდნას სჯობს. დააწყვილეთ ყოველ გამოძახებაზე ტაიმაუტთან/ვადასთან.
მთელი მსოფლიოს მომხმარებლები ანგარიშებს შორის ფულს გადარიცხავენ. მკაცრი ინვარიანტი: არცერთი ბალანსი არასდროს ხდება უარყოფითი და ფული არც იქმნება, არც იკარგება — რეგიონებს, ხელახალ მცდელობებსა და გახლეჩებს შორისაც კი. ამ დეკის ყოველი ინსტრუმენტი თავის ადგილს იმსახურებს.
თავად რეესტრი ერთადერთი ადგილია, სადაც ძლიერი თანმიმდევრულობაა საჭირო. ბალანსები გლობალურად თანმიმდევრულ SQL საცავში მოათავსეთ (Spanner / CockroachDB), რომ დებეტი და კრედიტი ერთ serializable ტრანზაქციაში დაფიქსირდეს. დანარჩენი ყველაფერი უფრო სუსტიც შეიძლება იყოს.
გადარიცხვა მოიცავს რეესტრს, თაღლითობის შემოწმებისა და შეტყობინებების სერვისებს. გაუშვით ის საგად, კომპენსაციებით, და ყოველი მოვლენა ტრანზაქციული outbox-ით გამოუშვით, რომ ავარიამ ნაბიჯი არასდროს დაკარგოს ან გააორმაგოს.
კლიენტი ყოველ გადარიცხვაზე იდემპოტენტურობის გასაღებს აგზავნის. ტაიმაუტის შემდეგ ხელახალი მცდელობა შენახულ შედეგს აბრუნებს — ფული ზუსტად ერთხელ გადაინაცვლებს, რამდენჯერაც არ უნდა მოვიდეს მოთხოვნა.
token bucket ლიმიტერი გეითვეიზე ბოროტად გამოყენებას ზღუდავს; circuit breaker-ები და ტიხრები თაღლითობის სერვისს იზოლირებს; backoff jitter-ით და დატვირთვის ჩამოშორება პიკს კასკადში გადაზრდის საშუალებას არ აძლევს.
ხუთი შეკითხვა თანმიმდევრულობაზე, კონსენსუსზე, განაწილებულ ტრანზაქციებზე, გეო-რეპლიკაციასა და საიმედოობაზე — მყისიერი პასუხი, ავტორიზაციის გარეშე.
ნავიგაცია ← → ღილაკებით ან სქროლით · უკან ბიბლიოთეკაში