43-წუთიანი სამუშაო სესია დიდი სისტემების მოძრავ ნაწილებზე — როგორ გავშალოთ ისინი განიერად, სად დავაქეშიროთ, როგორ უძლებს მონაცემთა ბაზა ზრდას, რატომ ვეყრდნობით რიგებს, რა კომპრომისებს გვახვევს თავს CAP თეორემა და როგორ დავინახოთ მთელი სურათი, როცა ის უკვე პროდაქშენში მუშაობს.
ერთი მანქანა, რომელიც ათას მომხმარებელს ემსახურება, მარტივია. რთული მაშინ იწყება, როცა ტრაფიკი, მონაცემები და ჩავარდნილი მანქანების რაოდენობა ერთდროულად იზრდება. წინ მდგარი ყოველი გადაწყვეტილება ერთ რესურსს მეორეზე ცვლის — უფასო მოგება არ არსებობს, არსებობს მხოლოდ თქვენს ციფრებზე მორგებული სწორი კომპრომისი.
ტრაფიკი ერთ წელიწადში ორი რიგით შეიძლება გაიზარდოს — დიზაინი უნდა მოიხაროს და არა გატყდეს.
p99 = ყოველი 100 მოთხოვნიდან ყველაზე ნელი 1. მომხმარებელი საშუალოს კი არა, კუდს გრძნობს — ამიტომ სწორედ ის უნდა გააუმჯობესოთ.
არცერთ მანქანას არ აქვს 100% აფთაიმი. მასშტაბზე ყოველთვის რაღაც ვარდება — ეს გაითვალისწინეთ.
სიმძლავრე ფულს ღირს. იმარჯვებს ყველაზე იაფი დიზაინი, რომელიც SLO-ს აკმაყოფილებს, და არა ყველაზე მძლავრი.
წინ მდგარი თითქმის ყველა ხერხი სიღრმეში ერთი და იგივე სვლაა: დახარჯეთ მეხსიერება, ფული ან თანმიმდევრულობა, რომ სანაცვლოდ იყიდოთ შეყოვნება, გამტარუნარიანობა ან ხელმისაწვდომობა. ქეში, მაგალითად, ხარჯავს დამატებით მეხსიერებას (და ოდნავ მოძველებული მონაცემების რისკს იღებს), რომ წაკითხვები ბევრად სწრაფი გახადოს.
მეტი დატვირთვის გასაძლებად მხოლოდ ორი გზაა: გაზარდე მანქანა ან დაამატე მანქანები. პირველი მარტივია და ჭერი აქვს; მეორეს ჭერი არ აქვს, სამაგიეროდ განაწილებულობასთან შეხვედრას გაიძულებთ.
ბალანსერი ამოწმებს ფლოტის ჯანმრთელობას და მკვდარ ნოუდს გვერდს უვლის — კლიენტები ვერაფერს ამჩნევენ.
დატვირთვის ბალანსერი ფლოტის წინ მდგარი მოძრაობის ინსპექტორია, რომელიც წყვეტს, რომელ ნოუდზე წავიდეს ყოველი მოთხოვნა. შეგიძლიათ საკუთარი გაუშვათ ან ღრუბელს გააკეთებინოთ ეს თქვენ ნაცვლად.
ვებ სერვერი, რომელიც ამავე დროს რევერს-პროქსი და ბალანსერია.
პროქსი, აგებული ერთი საქმისთვის: ტრაფიკის ბალანსირება და მეტი არაფერი.
თქვენი ღრუბლის საკუთარი ბალანსერი (AWS ELB/ALB, GCP, Azure).
როგორ ავირჩიოთ: ღრუბელში დაიწყეთ მართული დატვირთვის ბალანსერით — ის თავად მასშტაბირდება და დაპაჩვა არასდროს გჭირდებათ. nginx-ზე ან HAProxy-ზე მაშინ ჩამოდით, როცა საკუთარი მარშრუტიზაციის წესები გჭირდებათ ან პროვაიდერებს შორის პორტაბელური გინდათ დარჩეთ.
ჰორიზონტალური მასშტაბირება ნიშნავს, რომ სადღაც მანქანებს ქირაობთ. სამივე დიდი ღრუბელი აქირავებს გამოთვლას, მართულ მონაცემთა ბაზებს, რიგებსა და დატვირთვის ბალანსერებს — ისინი ძირითადად სიმწიფით, ფასითა და იმით განსხვავდებიან, თუ რომელ დამატებას აკეთებენ ყველაზე კარგად.
Amazon Web Services — ყველაზე ძველი და ყველაზე დიდი.
Google Cloud — ძლიერია მონაცემებსა და Kubernetes-ში (რომელიც Google-მა შექმნა).
Microsoft-ის ღრუბელი — ნაგულისხმევი არჩევანი, თუ უკვე Microsoft-ის სამყაროში ცხოვრობთ.
როგორ ავირჩიოთ: ძირითად საკითხებში ისინი თითქმის ტოლია, ამიტომ აირჩიეთ ის, რომელიც თქვენმა გუნდმა უკვე იცის ან სადაც თქვენი დანარჩენი ინსტრუმენტები ცხოვრობს. მძიმე მონაცემებისა და ML-ის სამუშაოსთვის გადაიხარეთ GCP-სკენ, Azure-სკენ — თუ Microsoft-ის სამყაროში ხართ, AWS-სკენ კი მაშინ, როცა ყველაზე ფართო მენიუ და დასაქმების ყველაზე მეტი ვარიანტი გინდათ.
ქეში ძვირი შედეგების ასლს იქვე ინახავს, სადაც ისინი საჭიროა, ამიტომ წაკითხვების უმეტესობა ნელ გზას საერთოდ გვერდს უვლის. ეს შეყოვნებაზე ყველაზე ძლიერი ერთი ინსტრუმენტია — და ყველაზე ადვილი, რომ შეუმჩნევლად არასწორად გამოიყენოთ.
სტატიკური რესურსები და საჯარო პასუხები, დაქეშირებული edge-ზე, მომხმარებელთან ახლოს. ყველაზე იაფი შესაძლო მოხვედრა — მოთხოვნა თქვენს სერვერებამდე საერთოდ არ აღწევს.
ცხელი ობიექტები და შეკითხვების შედეგები საერთო ქეშში (Redis, Memcached) აპლიკაციასა და ბაზას შორის. სამუშაო ცხენივით შრე, რომელსაც უმეტესობა „ქეშში“ გულისხმობს.
ბაზის საკუთარი ბუფერული პული და მატერიალიზებული ხედები. სასარგებლოა, მაგრამ თქვენ არ აკონტროლებთ — და ქსელის გადასვლებს არ ამცირებს.
cache-aside: ჯერ ქეშს ჰკითხეთ; აცდენისას წაიკითხეთ ბაზა და პასუხი TTL-ით დაწერეთ უკან.
async function getUser(id: string) { let u = await cache.get(`user:${id}`) if (u) return u // hit — done u = await db.findUser(id) // miss — slow path await cache.set(`user:${id}`, u, { ttl: 300 }) return u // backfill for next time }
თითქოს ყოველდღიურად საჭირო ფაილებს მაგიდაზე ინახავთ და ყოველ ჯერზე არქივის ოთახში არ დადიხართ.
ქეშს ასლი უჭირავს; როცა წყარო იცვლება, ასლი ტყუილად იქცევა. ჩაწერისას წაშალეთ (ან განაახლეთ) გასაღები, რომ შემდეგმა წაკითხვამ ხელახლა წამოიღოს. „მხოლოდ ორი რთული რამ არსებობს… ერთი ქეშის ინვალიდაციაა.“
უსაფრთხოების ბადე: ყოველ ჩანაწერს N წამში ვადა გასდის, მაშინაც კი, თუ ინვალიდაცია დაგავიწყდათ. მოკლე TTL = უფრო ახალი, სამაგიეროდ მეტი აცდენა; გრძელი TTL = უფრო სწრაფი, სამაგიეროდ უფრო ძველი. მოარგეთ მონაცემს.
როცა პოპულარულ გასაღებს ვადა გასდის, ათასი მოთხოვნა ერთსა და იმავე წამს აცდება და ხროვად ეცემა მონაცემთა ბაზას. თავი დაიცავით მოკლე ჩაკეტვით, იმით, რომ მხოლოდ ერთი მოთხოვნა წამოიღებს მონაცემს და დანარჩენები დაელოდებიან („გაერთიანება“), ან TTL-ში მცირე შემთხვევითობის („jitter“) ჩამატებით, რომ გასაღებებს ერთდროულად არ გაუვიდეს ვადა.
მეხსიერებაში მომუშავე საცავი, რომელიც მონაცემებს RAM-ში ინახავს მიკროწამიანი წაკითხვისთვის.
ყველაზე მინიმალური ვარიანტი: სწრაფი გასაღები → მნიშვნელობის სტრიქონული ქეში.
edge-ქეშები (Cloudflare, Fastly, CloudFront) და ჰოსტირებული Redis (ElastiCache, Upstash).
როგორ ავირჩიოთ: ნაგულისხმევად მიმართეთ Redis-ს — მისი მონაცემთა ტიპები და არჩევითი პერსისტენტულობა მაშინვე ამართლებს, როგორც კი უბრალო ძებნაზე მეტი დაგჭირდებათ. Memcached აირჩიეთ მხოლოდ მაშინ, როცა ყველაზე მარტივი სტრიქონული ქეში გინდათ, სტატიკური და საჯარო კონტენტი კი CDN-ზე გაიტანეთ, რომ თქვენს სერვერებს საერთოდ არ შეეხოს.
ჩვეულებრივ პირველი, რაც ტყდება, მონაცემთა ბაზაა — რადგან მას მდგომარეობა უჭირავს, მდგომარეობის განაწილება კი რთულია. ორი სვლა შორს მიგიყვანთ: რეპლიკაცია წაკითხვის დატვირთვისა და ხელმისაწვდომობისთვის, და დანაწილება ჩაწერის დატვირთვისა და სუფთა ზომისთვის.
ჩაწერები მთავარზე მიდის; იქიდან ისინი რეპლიკებზე მიედინება, რომლებიც წაკითხვებს ემსახურება. წაკითხვის სიმძლავრეს ამრავლებთ და ცხელ სარეზერვოსაც იღებთ — თუ მთავარი მოკვდა, რეპლიკა მის ადგილს იკავებს (failover).
მთავარი ჩაწერებს წამკითხავ რეპლიკებზე გადასცემს; რეპლიკები წაკითხვებს ემსახურება და failover-ისას მთავრის ადგილს იკავებს.
აირჩიეთ შარდის გასაღები (მაგ. user_id); მისი ჰეში ან დიაპაზონი წყვეტს, რომელ ნოუდს ეკუთვნის თითოეული სტრიქონი. ყოველ შარდს მონაცემების ნაწილი უჭირავს და საკუთარ ჩაწერებს იღებს — ამიტომ ჩაწერის გამტარუნარიანობა და საერთო ზომა შარდების რაოდენობასთან ერთად იზრდება.
// route by a hash of the shard key function shardFor(userId: string, n: number): number { return hash(userId) % n // → which node owns this user } // all of one user's rows live together on one shard // → single-user queries stay on a single node
მკაცრი თანმიმდევრულობა, ტრანზაქციები, მოქნილი ad-hoc შეკითხვები და join-ები. სწორი ნაგულისხმევი არჩევანი — აპლიკაციების უმეტესობა კარგად მორგებულ, რეპლიცირებულ SQL ბაზას ვერასდროს გადაასწრებს.
აგებულია ჰორიზონტალური შარდინგისთვის, უზარმაზარი ჩაწერის მოცულობისა და მარტივი, წინასწარ ცნობილი წვდომის პატერნებისთვის. join-ებსა და (ხშირად) მკაცრ თანმიმდევრულობას სუფთა მასშტაბზე ცვლით.
ცერის წესი: დაიწყეთ რელაციურით. NoSQL-ს მაშინ მიმართეთ, როცა კონკრეტულ მასშტაბზე კონკრეტული წვდომის პატერნი ამას ითხოვს — და არა ნაგულისხმევად.
როცა მოთხოვნა ნელ სამუშაოს იწვევს — ელფოსტის გაგზავნა, ვიდეოს კოდირება, ბარათიდან თანხის ჩამოჭრა — მისი იქვე შესრულება მომხმარებელს აჩერებს და ორი სერვისის აფთაიმს ერთმანეთზე აბამს. რიგი გაძლევთ საშუალებას სამუშაო ახლა მიიღოთ და მალე შეასრულოთ.
async function signup(req) { const u = await db.createUser(req) await email.sendWelcome(u) // 800ms, may fail await billing.provision(u) // 2s, may be down return ok(u) // user waits for ALL of it } // email outage → signup outage
async function signup(req) { const u = await db.createUser(req) await queue.publish("user.created", u) // instant return ok(u) // user is done now } // workers handle email + billing independently // email outage → a delay, not an outage
დაამატეთ მუშები, რომ რიგი უფრო სწრაფად დაიცალოს; რიგი პიკებს შთანთქავს, ამიტომ პროდიუსერები არასდროს იბლოკება.
მოვლენების მდგრადი, მხოლოდ-დამატებადი ლოგი, რომლის გამეორებაც ბევრ მკითხველს შეუძლია.
კლასიკური შეტყობინებების ბროკერი მოქნილი მარშრუტიზაციის წესებით.
სრულად მართული რიგი — გასაშვები სერვერები არ არსებობს.
როგორ ავირჩიოთ: ჩვეულებრივი ფონური სამუშაოებისთვის ყველაზე ნაკლებ შრომას მართული რიგი (SQS) ან RabbitMQ მოითხოვს. Kafka მაშინ აირჩიეთ, როცა მოვლენების უზარმაზარი ნაკადი გაქვთ, რამდენიმე დამოუკიდებელი კონსიუმერი, ან ისტორიის გამეორება გჭირდებათ — და მისი მართვის საშუალება გაქვთ.
აქამდე ყოველი ხერხი მანქანებს ამატებდა, მანქანები კი ქსელით საუბრობენ, რომელიც აუცილებლად დაკარგავს შეტყობინებებს. CAP თეორემა გვეუბნება, რაზე უნდა თქვათ უარი, როცა ეს მოხდება — და ეს არჩევითი არაა.
B ვერ იგებს A-ს ჩანაწერს. CP პასუხზე უარს ამბობს; AP მოძველებული 4-ით პასუხობს.
"CA" მხოლოდ მაშინ არსებობს, როცა გახლეჩა არ არსებობს — ანუ ერთი ნოუდია. როგორც კი გაანაწილებთ, P სავალდებულო ხდება, ამიტომ ყოველი რეალური განაწილებული სისტემა CP ან AP-ია. თანამედროვე საცავები არჩევანს თითოეულ ოპერაციაზეც კი გაძლევენ (აქ ძლიერი წაკითხვები, იქ სწრაფი).
განაწილებული სისტემა ნაწილ-ნაწილ, ჩუმად და ღამის სამ საათზე ვარდება. თუ გარედან ვერ ხვდებით, რა გაფუჭდა და სად, ყოველი ინციდენტი გამოცნობის თამაშად იქცევა. დაკვირვებადობა სწორედ ის სამართავი პანელია ყველაფრისთვის, რაც ახლა ავაწყეთ.
იაფი დროითი მწკრივები — მოთხოვნების სიხშირე, შეცდომების %, p99 შეყოვნება, რიგის სიღრმე, CPU. მათ აჯამებთ, ტენდენციებს უყურებთ და გაფრთხილებებს აწყობთ. Prometheus + Grafana კანონიკური სტეკია.
სტრუქტურირებული ჩანაწერები მოვლენებზე — თითო სტრიქონი თითო მომხდარზე, საკმარისი კონტექსტით, რომ შემდეგ მოძებნოთ. Loki, ELK სტეკი ან მართული ლოგების საცავი.
ერთი მოთხოვნის სრული გზა სერვისებს შორის, თითოეული გადასვლის დროით — ერთადერთი გზა იმის დასადგენად, რომელმა სერვისმა შეჭამა შეყოვნება. OpenTelemetry → Jaeger, ან APM, როგორიც Dynatrace-ია.
სერვისები სამ ნაკადს კოლექტორს აწვდიან, ის კი დაშბორდებსა და გაფრთხილებებს კვებავს; გარე შემმოწმებელი აფთაიმს ფლოტის გარედან ამოწმებს.
როგორც მანქანის სამართავი პანელი: სიჩქარე, საწვავი, ძრავის ნათურა — სიმპტომები, რომლებზეც მოქმედებთ, და არა ათასი სენსორი მათ უკან. გამოძახება სიმპტომზე მოდის, არა ისეთ მიზეზზე, როგორიც „CPU 80%“-ია.
ღია დაშბორდების შრე. მიმართეთ Prometheus-ზე (მეტრიკები), Loki-ზე (ლოგები) ან ტრეისებზე და ააწყეთ ის ერთი ეკრანი, რომელსაც მორიგე უყურებს — ოქროს სიგნალები, SLO-ს წვა, რიგის სიღრმე — და იქვე მიაბით გაფრთხილების წესები.
სრული სტეკის, ძირითადად ავტომატური დაკვირვებადობა: აგენტს დებთ, ის სერვისებს თავად აღმოაჩენს და განაწილებულ ტრეისებს ერთმანეთს უკავშირებს, ძირეული მიზეზის AI-დახმარებით ძიებით. ეს არის „იყიდე და ნუ ააგებ“ პოლუსი — Datadog და New Relic მისი თანატოლებია.
შემოწმებები თქვენი ქსელის გარედან — ყოველ წუთს ჯანმრთელობის ენდპოინტს აკითხავს, გაფრთხილებას აგზავნის, როცა ის მიუწვდომელია ან ნელია, და საჯარო სტატუსის გვერდს კვებავს. Pingdom, UptimeRobot, Better Stack. პასუხობს ერთადერთ კითხვას, რომელიც მომხმარებელს აინტერესებს: მუშაობს?
მოტყუებით პატარა ამოცანა, რომელიც ყველა შრეს ეხება, რაზეც ვისაუბრეთ — POST-ით გრძელ URL-ს აგზავნით და მოკლე კოდს იღებთ; GET /code კი გადამისამართებას აკეთებს. წაკითხვებით დატვირთული, შეყოვნებაზე მგრძნობიარე და არასდროს უნდა დაკარგოს ერთი შესაბამისობაც.
აიღეთ უნიკალური 64-ბიტიანი id (მრიცხველიდან ან id-სერვისიდან) და base62-ით დააკოდეთ მოკლე სტრიქონად. შეინახეთ code → longUrl ბაზაში — ეს არის ჭეშმარიტების წყარო.
function shorten(longUrl: string): string { const id = ids.next() // unique 64-bit const code = base62(id) // "3Bk9" — short & unique db.put(code, longUrl) return `short.ly/${code}` }
ძებნა შექმნას მრავალჯერ აღემატება. ბაზის წინ დააყენეთ cache-aside შრე code-ზე; შესაბამისობები უცვლელია, ამიტომ ქეშირებული ჩანაწერები არასდროს ძველდება. გადამისამართება მეხსიერებიდან გასცით.
async function resolve(code: string): Promise<string | null> { return await cache.get(code) ?? await db.get(code) // miss → backfill } // immutable mapping → no invalidation problem
code-ზე გადამისამართებების უმეტესობას მეხსიერებიდან გასცემს; უცვლელობა ამას სრულიად მარტივს ხდის.code-ით მაშინ, როცა ერთი ნოუდი გასაღებების სივრცეს ვეღარ იტევს.მოხვდით ქეშში (მწვანე) და დააბრუნეთ; აცდენა რეპლიკამდე ჩადის; კლიკები ასინქრონულად რიგში მიდის.
"მასშტაბირება არ არსებობს, არსებობს მხოლოდ კომპრომისები — დაასახელეთ ის, რომელსაც თქვენ დებთ."
— მთელი საუბარი, შეკუმშული
ხუთი სწრაფი კითხვა მასშტაბირებაზე, ქეშირებაზე, მონაცემთა ბაზებსა და CAP-არჩევანზე — მყისიერი პასუხი, ავტორიზაციის გარეშე.
ნავიგაცია ← → ღილაკებით ან სქროლით · ნაწილი 2: მოწინავე სისტემური დიზაინი → · უკან ბიბლიოთეკაში