← ყველა სტატია

Kafka 94 %-ით სავსე დისკზე, 17 საათის მარაგი და retention, რომელიც ორჯერ მეტს ინახავდა

ტრაფიკი 150-ჯერ გაიზარდა, ტომები კი არავის გადაუხედავს. უარესი: სეგმენტების ნაგულისხმევი როლინგის დროს 7-დღიანი retention სინამდვილეში 7–14 დღეს ნიშნავდა.

Gambchamp-ში ალერტი ამოქმედდა: Kafka თავის ტომებს ავსებდა. კითხვა ჩვეულებრივი იყო — დილამდე მოითმენს?

არ მოითმენდა. სამივე ბროკერი 39,1 GiB-დან 36,6-ზე იდგა, 94 %. ზრდა საშუალოდ დღეში 3,37 GiB იყო, საღამოს პიკში კი დღეში ~5 GiB-მდე. რჩებოდა დაახლოებით 17 საათი, ანუ დისკი შუაღამისას, პიკის დროს ამოიწურებოდა. retention-ის შემდეგი ავტომატური გაწმენდა მხოლოდ ორი დღის შემდეგ იყო მოსალოდნელი.

არაფერი გატეხილა

მიზეზი ორი იყო და არც ერთი გაუმართაობა არ ყოფილა.

ტრაფიკი დაახლოებით 150-ჯერ გაიზარდა. აგვისტოს დასაწყისში წამში ~5 შეტყობინებიდან პროდუქტის გაშვების შემდეგ წამში ~780 შეტყობინების პლატომდე. კონსიუმერების lag ნულოვანი იყო, ბროკერების ლოგებში შეცდომები არ იყო, under-replicated პარტიციები არ იყო. 40 GiB-იანი ტომები უბრალოდ სხვა დატვირთვისთვის იყო დაპროექტებული.

retention სინამდვილეში 7–14 დღე იყო და არა 7. ბროკერების კონფიგი retention-ს საერთოდ არ აწესებდა, Kafka-ს ნაგულისხმევი მნიშვნელობები მოქმედებდა: log.retention.hours=168 და log.roll.hours=168, 1 GiB-იანი სეგმენტებით. retention მხოლოდ დახურულ სეგმენტებს შლის. ჩვენი პარტიციები კვირაში რამდენიმე ასეულ მეგაბაიტს აგროვებდა და 1 GiB-მდე ვერ იზრდებოდა, ამიტომ სეგმენტი 7-დღიანი ტაიმერით იხურებოდა — და მხოლოდ მაშინ იწყებოდა მისი retention-ის ათვლა. აქედანაა „ხერხი“ დისკის გრაფიკზე: დიდი ვარდნა კვირაში ერთხელ, შუალედში არაფერი თავისუფლდება.

პრაქტიკული დასკვნა: ასეთი ტომი ვერ დაიგეგმება როგორც „7 დღე × სიჩქარე“. ამ ნაგულისხმევი მნიშვნელობებით — 14.

დროის მოგება

ტომები გაფართოებად gp3 კლასზე იყო, ამიტომ პირველი ნაბიჯი PVC-ის ონლაინ გაფართოება იყო 40-დან 100 GiB-მდე. გადატვირთვების გარეშე, under-replicated პარტიციების გარეშე, GitOps-აპლიკაცია სინქრონიზებული დარჩა — PVC-ები volumeClaimTemplates-იდან იქმნება და Git-ში არ არის, ამიტომ self-heal-ს დასაბრუნებელი არაფერი ჰქონდა. პირდაპირ 100, და არა 60, რადგან AWS ტომის შეცვლას ექვს საათში ერთხელ უშვებს.

მიზეზის აღმოფხვრა

მეორე დღეს გაირკვა, რომ 7-დღიანი retention ბექენდის არქიტექტორების რეალური მოთხოვნა იყო. ამან გამოსწორების აზრი შეაბრუნა: სისტემა ნაკლებს კი არ ინახავდა, არამედ მოთხოვნილზე თითქმის ორჯერ მეტს. სეგმენტების როლინგი ყოველ 12 საათში (log.roll.ms=43200000) რეალურ შენახვას 7–7,5 დღეს უბრუნებს log.retention.hours-ის შეცვლის გარეშე.

4-ჯერადი ზრდის პროგნოზით სტაციონარი გამოდიოდა ~185 GiB არსებული კონფიგით და ~99 GiB 12-საათიანი სეგმენტებით, ამიტომ სამიზნე ტომი 200 GiB გახდა. გამტარუნარიანობა, IOPS, ფაილის დესკრიპტორები და CPU ზღვრებისგან შორს იყო — ჭერი მოცულობაში იყო და არა წარმადობაში.

სამი ხაფანგი გზაზე

  • არასწორი გასაღები, რომელზეც „Completed“ გვიპასუხეს. ჯერ log.segment.ms დავაყენეთ — ბროკერს ასეთი პარამეტრი არ აქვს (ტოპიკის დონეზე მას segment.ms ჰქვია). kafka-configs.sh-მა Completed updating default config უპასუხა და არაფერი შეცვალა. სწორი სახელია log.roll.ms, რეალურად რა მოქმედებს, მხოლოდ --describe --all აჩვენებს.
  • დინამიკური override ფაილს ფარავს. ბროკერის დინამიკური კონფიგი server.properties-ზე პრიორიტეტულია. სანამ override მოხსნილი არ არის, Git-ში არსებული მნიშვნელობა დეკორატიულია — ხოლო თუ ძალიან ადრე მოხსნი, როლინგი ცოტა ხნით 168 საათს დაუბრუნდება.
  • StatefulSet-ის „უსაფრთხო“ ხელახალმა შექმნამ ყველა ბროკერი გადატვირთა. ცოცხალ pod template-ში დარჩენილი იყო ძველი ანოტაცია kubectl.kubernetes.io/restartedAt, რომელიც Git-ის რენდერში არ იყო — ჰეში შეიცვალა და ყველა ბროკერი გადაიტვირთა. სათითაოდ, შტატურად, მაგრამ დაუგეგმავად.

ცვლილებების შემდეგ: 200 GiB-იანი ტომები 21 %-ით დაკავებული, ყველაფერი Git-იდან მოდის, სეგმენტები დღეში ორჯერ იცვლება.

დასკვნები

  • შევსებამდე დრო პიკური სიჩქარით დაითვალეთ და retention-ის შემდეგ ვარდნას შეადარეთ. თუ ვარდნა უფრო გვიანაა — ლოდინი არ შეიძლება.
  • მოთხოვნა, რომელიც ნაგულისხმევს დაემთხვა, ჩაუწერელი მოთხოვნაა. retention ცალსახად დააყენეთ, თუნდაც მნიშვნელობა ნაგულისხმევის ტოლი იყოს.
  • kafka-configs.sh წარმატებას უცნობ გასაღებებზეც აცხადებს. შეამოწმეთ --describe --all-ით.
  • delete sts --cascade=orphan-მდე შეამოწმეთ, ხომ არ არის pod template-ში restartedAt ანოტაცია.