Ο εργάτης έπεσε. Ο συγχρονισμός όχι.

Σκοτώστε τον εργάτη στη μέση μιας παρτίδας και ακολουθήστε μία επαφή: ποιος δρομέας αποθηκεύτηκε, γιατί η επαναλαμβανόμενη εγγραφή δεν καταλήγει δύο φορές, ποιος κατέχει τον συγχρονισμό όσο επανεκκινεί. Έπειτα, τα δύο περιστατικά που έδειξαν ότι η ανάκαμψη ήταν μόνο η μισή δουλειά.

ανάκαμψη συγχρονισμού μετά από αποτυχία

Τι συμβαίνει σε έναν συγχρονισμό σε εξέλιξη όταν ο εργάτης πέφτει στη μέση μιας παρτίδας;

Ο δρομέας μετακινείται μόνο στο τέλος μιας καθαρής παρτίδας, οπότε ένας εργάτης που σκοτώνεται στη μέση της παρτίδας τον αφήνει στην αρχή της και η επόμενη εκτέλεση διαβάζει την ίδια σελίδα. Το κλειδί παράδοσης κάθε εγγραφής προκύπτει από τον δρομέα της σελίδας και τη θέση της, και ένα μητρώο ολοκληρωμένων εγγραφών επτά ημερών εμποδίζει την επανάληψη να καταλήξει δύο φορές. Μία μίσθωση ανά συγχρονισμό εμποδίζει δύο εκτελέσεις να δουλεύουν ταυτόχρονα. Αυτό ήταν το εύκολο μισό. Ένας συγχρονισμός αποτύγχανε, εκτέλεση μετά από εκτέλεση, πριν τον θέσει κάτι σε παύση, και ένας χώρος εργασίας έμενε σιωπηλός, με σφάλματα και καμία επιτυχία, πριν το προσέξει κάτι. Γι' αυτό υπάρχει πλέον ένας ασφαλειοδιακόπτης που θέτει σε παύση μετά από πέντε διαδοχικές αποτυχίες και μια ωριαία ειδοποίηση για είκοσι τέσσερις ώρες χωρίς επιτυχία.

Μάθετε περισσότερα
Σε αυτή τη σελίδα

Σε μια ομιλία που δημοσίευσε το AI Engineer στις 3 Οκτωβρίου 2026, ένας εκπρόσωπος σχέσεων με προγραμματιστές (developer advocate) της Crusoe ξεκίνησε μια εργασία εκπαίδευσης PyTorch σε δύο κόμβους A100. Έπειτα είπε στο ίδιο το σύστημα παρακολούθησης της επίδειξης ότι μία GPU είχε βγάλει σφάλμα XID 79. Ο κόμβος σημειώθηκε ως εκτός λειτουργίας και η εργασία ακυρώθηκε. Η διεργασία έλαβε SIGTERM και «έως δύο λεπτά για να χειριστεί αυτό το SIGTERM και είτε να αποθηκεύσει τα σημεία ελέγχου της είτε να αδειάσει τα logs της». Ένας εφεδρικός κόμβος μπήκε στη θέση του, και «η εργασία του slurm θα επανεκκινήσει. Θα φορτώσει το σημείο ελέγχου και θα συνεχίσει την εκπαίδευση από εκεί που σταμάτησε». Λίγο λιγότερο από δεκαπέντε λεπτά από άκρη σε άκρη. Κανείς δεν κλήθηκε σε επιφυλακή.

Ένας συγχρονισμός είναι πολύ μικρότερο ζώο από μια εκπαίδευση. Το ερώτημα είναι το ίδιο. Τι χρειάζεται μια εκτέλεση ώστε ο θάνατος του εργάτη να κοστίζει λεπτά και κανένα διπλότυπο;

Αυτή είναι η απάντηση για τον δικό εκτελεστή συγχρονισμών του Tajo, όπως διαβάζεται από τον κώδικα και όχι από ένα φυλλάδιο. Το Reverse ETL περιγράφει το σχήμα της δουλειάς. Αυτό είναι ό,τι συμβαίνει όταν η δουλειά πεθαίνει.

Ακολουθήστε μία επαφή

Μια εκτέλεση διαβάζει την πηγή μία σελίδα τη φορά, από έναν αποθηκευμένο δρομέα. Ας πούμε τον δρομέα της σελίδας c3 και την επαφή μας την 212η εγγραφή σε αυτή τη σελίδα. Τρία πράγματα ισχύουν για αυτή την επαφή πριν γραφτεί οτιδήποτε.

Πρώτον, έχει κλειδί παράδοσης. Το κλειδί είναι ένα hash του αναγνωριστικού του συγχρονισμού, της πηγής, του δρομέα της σελίδας, της θέσης στη σελίδα και της ίδιας της εγγραφής. Ο δρομέας και η εγγραφή μπαίνουν στο hash και δεν ενσωματώνονται ποτέ στο κλειδί, οπότε το κλειδί δεν περιέχει καμία διεύθυνση email.

Δεύτερον, η εγγραφή περνά από ένα μητρώο και όχι κατευθείαν στον προορισμό. Πριν κληθεί ο προορισμός, το κλειδί δεσμεύεται ως «σε εκκρεμότητα». Όταν η εγγραφή πετύχει, η γραμμή σημειώνεται ως ολοκληρωμένη και διατηρείται για επτά ημέρες.

Τρίτον, ο δρομέας δεν έχει μετακινηθεί. Μετακινείται μόνο στο τέλος μιας παρτίδας, μόνο αν κάθε εγγραφή της παρτίδας έφτασε, και μόνο αφού η εκτέλεση αποδείξει ότι εξακολουθεί να κατέχει τον συγχρονισμό.

Τώρα σκοτώστε τον εργάτη ανάμεσα στην εγγραφή και στο σημείο ελέγχου.

Με ένα SIGTERM, κάθε εργάτης ουράς ολοκληρώνει την τρέχουσα δουλειά του μέσα σε μία κοινή προθεσμία δεκαπέντε δευτερολέπτων, και η διεργασία τερματίζεται μετά από είκοσι πέντε δευτερόλεπτα, ό,τι κι αν εκτελείται ακόμη· το systemd της δίνει σαράντα πέντε πριν τη σκοτώσει. Μια εκτέλεση που δεν τελείωσε μέσα σε αυτό το παράθυρο εξακολουθεί να είναι σημειωμένη ως σε εξέλιξη, και ο επόμενος εργάτης την παραλαμβάνει μέσω του μηχανισμού της ουράς για την ανάκτηση κολλημένων εργασιών. Σε έναν απότομο τερματισμό δεν υπάρχει καθόλου ολοκλήρωση. Σε κάθε περίπτωση ισχύουν τα ίδια δύο γεγονότα. Ο προορισμός έχει την επαφή μας. Ο δρομέας μας λέει ακόμη c3.

Η επανάληψη

Η επόμενη εκτέλεση ξεκινά από το c3, διαβάζει την ίδια σελίδα, παράγει το ίδιο κλειδί για την 212η εγγραφή και ρωτά το μητρώο. Το μητρώο απαντά: ολοκληρώθηκε. Ο προορισμός δεν καλείται· το αποθηκευμένο αποτέλεσμα αναπαράγεται και η εκτέλεση προχωρά. Στη μαζική διαδρομή, όπου το endpoint εισαγωγής του Brevo δέχεται μια ολόκληρη σελίδα σε μία κλήση, ο ίδιος έλεγχος εκτελείται ανά κλειδί πριν συνταχθεί το αίτημα. Μια σελίδα στην οποία κάθε κλειδί έχει ήδη ολοκληρωθεί δεν στέλνει τίποτα.

Αυτό είναι όλο το κόλπο, και αξίζει να πούμε γιατί δουλεύει. Το κλειδί προκύπτει από τον δρομέα της σελίδας στην αρχή της παρτίδας, όχι από την εκτέλεση που έτυχε να τρέχει. Μια κατάρρευση, μια νέα προσπάθεια εξήντα δευτερόλεπτα αργότερα και μια προγραμματισμένη εκτέλεση αύριο παράγουν όλες το ίδιο κλειδί για την ίδια εγγραφή. Το μητρώο δεν νοιάζεται ποια από αυτές ρωτά.

Τώρα το κόστος. Το μητρώο ξεχνά μετά από επτά ημέρες. Το σχόλιο του ίδιου του κώδικα λέει ότι οι επτά ημέρες καλύπτουν μια καθυστερημένη νέα προσπάθεια και μια σύντομη διακοπή λειτουργίας, και αυτό είναι όλο όσο ισχυρίζεται. Μια επανάληψη από δρομέα παλαιότερο από αυτό γράφει ξανά. Και μια εκτέλεση που διακόπτεται απότομα μπορεί να περιμένει. Η μίσθωση της νεκρής εκτέλεσης κρατά έως δεκαπέντε λεπτά, και σε έναν προγραμματισμένο συγχρονισμό μια εκτέλεση της οποίας η μίσθωση είναι ελεύθερη μαζεύεται και επαναπρογραμματίζεται μόνο αν ξεκίνησε πάνω από μία ώρα νωρίτερα. Η επίδειξη της Crusoe επανέρχεται σε λιγότερο από δεκαπέντε λεπτά. Η δική μας όχι πάντα.

Ποιος κατέχει τον συγχρονισμό όσο επανεκκινεί

Κάθε στιγμή, έναν συγχρονισμό τον κατέχει μία εκτέλεση. Η εκτέλεση παίρνει μια μίσθωση στο Redis με το δικό της token, διάρκεια ζωής δεκαπέντε λεπτών και ανανέωση κάθε πέντε λεπτά. Πριν από κάθε σημείο ελέγχου επιβεβαιώνει ότι κρατά ακόμη αυτή τη μίσθωση. Αν όχι, σταματά. Η εκτέλεση καταγράφεται με την απλή πρόταση που θα διαβάσει ο άνθρωπος: «The run lost its slot part-way through and stopped to avoid writing twice.» (η εκτέλεση έχασε τη θέση της στη μέση και σταμάτησε για να μη γράψει δύο φορές). Μια δεύτερη εκτέλεση που συγκρούεται με έναν ζωντανό κάτοχο δεν επαναλαμβάνεται. Τελειώνει με το «Another run of this sync was already in progress» (μια άλλη εκτέλεση αυτού του συγχρονισμού ήταν ήδη σε εξέλιξη), και η επόμενη προγραμματισμένη εκτέλεση ξεκινά κανονικά.

Και η ευθύνη για τη διόρθωση είναι καταγεγραμμένη. Κάθε κωδικός αποτυχίας αντιστοιχεί σε μία πρόταση για το τι συνέβη, μία για το τι να γίνει στη συνέχεια, και σε έναν υπεύθυνο: εσάς ή το Tajo. Ένας προορισμός που απέρριψε κάθε εγγραφή επειδή η αντιστοίχιση είναι λάθος είναι δική σας υπόθεση. Μια χαμένη μίσθωση είναι δική μας. Η σελίδα της εκτέλεσης δείχνει το επόμενο βήμα· ο οδηγός ανάκτησης συγχρονισμού αναφέρει ποιο από τα δύο ισχύει.

Ως εδώ, αυτό είναι το εύκολο μισό. Μια κατάρρευση κοστίζει μία επανάληψη και τίποτα δεν καταλήγει δύο φορές. Δύο περιστατικά μάς δίδαξαν το άλλο μισό.

Αποτυχία μετά από αποτυχία

Ένας συγχρονισμός αποτύγχανε ξανά και ξανά. Όχι σε στενό βρόχο: μία φορά ανά διάστημα του χρονοπρογραμματιστή, εκτέλεση μετά από εκτέλεση, με το ίδιο μήνυμα κάθε φορά, ότι ένα προσαρμοσμένο αναγνωριστικό δεν μπορεί να περιέχει άνω και κάτω τελεία. Η εκτέλεση δεν έφτασε ποτέ στον προορισμό. Πέθαινε ενώ έμπαινε στην ουρά, πριν τη δει οποιοσδήποτε εκτελεστής, οπότε μια ειδοποίηση συνδεδεμένη με τον εκτελεστή δεν είχε ποτέ σε τι να αντιδράσει.

Τίποτα δεν παρακολουθούσε το σερί. Αυτό είναι όλο το εύρημα.

Η διόρθωση είναι ένας ασφαλειοδιακόπτης. Μετά από κάθε καταγεγραμμένη αποτυχία διαβάζει τις τελευταίες πέντε ολοκληρωμένες εκτελέσεις του συγχρονισμού από την τελευταία φορά που άλλαξε ο συγχρονισμός. Αν και οι πέντε είναι σφάλματα, θέτει τον συγχρονισμό σε παύση, γράφει μία γραμμή ελέγχου (audit) με αιτία circuit_breaker_tripped και στέλνει μία κρίσιμη ειδοποίηση. Το σερί προκύπτει από τις αποθηκευμένες εκτελέσεις, όχι από έναν μετρητή στον εργάτη. Μετρά μια αποτυχία που καταγράφηκε από οποιαδήποτε διαδρομή, συμπεριλαμβανομένης της αποτυχίας κατά την εισαγωγή στην ουρά που προκάλεσε το περιστατικό. Το σχόλιο του κώδικα λέει ότι θα είχε σταματήσει εκείνο το περιστατικό στην πέμπτη εκτέλεση. Μία επιτυχία μηδενίζει το σερί. Η άρση της παύσης οπλίζει ξανά τον ασφαλειοδιακόπτη αντί να τον ενεργοποιεί ξανά πάνω στις παλιές γραμμές. Ο οδηγός του connector για το Shopify λέει το ίδιο σε μία γραμμή: μετά από πέντε διαδοχικές αποτυχημένες εκτελέσεις ο συγχρονισμός τίθεται μόνος του σε παύση και η σελίδα του δείχνει το τελευταίο σφάλμα.

Αυτό που ο ασφαλειοδιακόπτης δεν κάνει είναι να διορθώσει οτιδήποτε. Η Crusoe αντικαθιστά τον κόμβο και επανεκκινεί την εργασία χωρίς να ρωτήσει. Το Tajo σταματά και ρωτά: ένας συγχρονισμός σε παύση μένει σε παύση μέχρι ένας άνθρωπος να διορθώσει την αιτία και να τον δημοσιεύσει ξανά. Η προσεκτική προεπιλογή είναι σκόπιμη: μια αυτόματη νέα προσπάθεια ενός συγχρονισμού που γράφει σε εγγραφές πελατών είναι η ίδια μια εγγραφή.

Η σιωπή που κανείς δεν είδε

Η ένσταση είναι δίκαιη και λέει: «Ένας ασφαλειοδιακόπτης που θέτει σε παύση μετά από πέντε αποτυχίες θα είχε πιάσει και το δεύτερο περιστατικό. Γιατί να φτιάξουμε δεύτερο φύλακα;» Επειδή το δεύτερο περιστατικό δεν έμοιαζε με αποτυχία. Για μεγάλο διάστημα ένας χώρος εργασίας είχε έναν ενεργό συγχρονισμό, έναν σωρό γραμμών σφάλματος που μεγάλωνε και ούτε μία επιτυχία, ποτέ, και τίποτα δεν ειδοποίησε. Κάθε σήμα που υπήρχε ήταν συνδεδεμένο με δραστηριότητα: μια εκτέλεση απέτυχε, έφτασε ένα webhook, ξεπεράστηκε ένας προϋπολογισμός. Μηδέν επιτυχίες δεν είναι γεγονός. Είναι η απουσία γεγονότος, και ένας ασφαλειοδιακόπτης τρέχει μόνο όταν καταγράφεται αποτυχία. Ένας χρονοπρογραμματιστής που είναι νεκρός, μια ουρά που έχει κολλήσει, ένας εργάτης που δεν ξεκινά ποτέ: αυτά δεν καταγράφουν τίποτα, και ένας ασφαλειοδιακόπτης δεν ξυπνά ποτέ.

Η λαβή νεκρού ανθρώπου μιας ατμομηχανής (dead man’s handle) δουλεύει ανάποδα. Ο μηχανοδηγός πρέπει να την πατά συνεχώς, και η σιωπή σταματά το τρένο. Η εκδοχή για τον συγχρονισμό τρέχει μία φορά την ώρα, δεκατρία λεπτά μετά την ακριβή ώρα. Για κάθε χώρο εργασίας με ενεργό συγχρονισμό κάνει μία ερώτηση: πέτυχε κάποια εκτέλεση τις τελευταίες είκοσι τέσσερις ώρες; Ένας χώρος εργασίας του οποίου ο παλαιότερος ενεργός συγχρονισμός ενεργοποιήθηκε πριν από λιγότερο από μία ημέρα μένει ήσυχος· δεν έχει προλάβει ακόμη μια ημέρα για να πετύχει. Ένας παλαιότερος και σιωπηλός λαμβάνει μία κρίσιμη ειδοποίηση. Η ειδοποίηση λέει ποια σιωπή είναι: εκτελέσεις καταγράφηκαν αλλά καμία δεν πετυχαίνει, ή δεν καταγράφηκε καμία εκτέλεση. Ενεργοποιείται μία φορά ανά περιστατικό. Ένας δείκτης στο Redis την κρατά κλειστή μέχρι την πρώτη επιτυχημένη εκτέλεση, μετά καθαρίζεται, ώστε το επόμενο περιστατικό να ειδοποιήσει ξανά.

Και οι δύο φύλακες μπήκαν σε ένα commit, εκ των υστέρων. Το σχόλιο του ασφαλειοδιακόπτη λέει ότι θα είχε σταματήσει το πρώτο περιστατικό στην πέμπτη εκτέλεση. Το σχόλιο του διακόπτη νεκρού ανθρώπου λέει ότι ένας συγχρονισμός που ενεργοποιήθηκε πριν από πολύ καιρό με μηδέν εκτελέσεις πρέπει να ειδοποιήσει. Κανένας από τους δύο δεν υπήρχε όταν χρειαζόταν. Τους γράψαμε μετά, με τα δύο περιστατικά καταγεγραμμένα στην κεφαλίδα του αρχείου, για να μην ξεχάσει κανείς γιατί.

Τι έχει χτιστεί και τι όχι

Χτισμένα, τη στιγμή που γράφεται το άρθρο:

  • Ο δρομέας αποθηκεύεται στο τέλος κάθε καθαρής παρτίδας, και ξανά σε κατάρρευση.
  • Το κλειδί παράδοσης κάθε εγγραφής προκύπτει από τον δρομέα της σελίδας και τη θέση της, και ελέγχεται σε ένα μητρώο επτά ημερών πριν από κάθε κλήση στον προορισμό.
  • Μία μίσθωση ανά συγχρονισμό, με διάρκεια ζωής δεκαπέντε λεπτών.
  • Πέντε προσπάθειες ανά εκτέλεση, με αυξανόμενη αναμονή που ξεκινά από τα εξήντα δευτερόλεπτα.
  • Ένας ασφαλειοδιακόπτης που θέτει τον συγχρονισμό σε παύση μετά από πέντε διαδοχικές αποτυχίες και καταγράφει τον λόγο.
  • Μια ωριαία ειδοποίηση για είκοσι τέσσερις ώρες χωρίς επιτυχία.

Δεν έχουν χτιστεί, και αξίζει να το ξέρετε πριν βασιστείτε σε αυτό:

  • Ο διακόπτης νεκρού ανθρώπου παρακολουθεί τον χώρο εργασίας, όχι τον συγχρονισμό. Ένας υγιής συγχρονισμός κρύβει έναν σιωπηλό αδελφό συγχρονισμό στον ίδιο χώρο εργασίας.
  • Η ειδοποίησή του φτάνει στους χειριστές του Tajo: πάντα μια γραμμή στο log, και Sentry, webhook ή email όπου έχουν ρυθμιστεί. Δεν εμφανίζεται στη σελίδα του συγχρονισμού. Η παύση του ασφαλειοδιακόπτη εμφανίζεται.
  • Τίποτα δεν επανεκκινεί έναν συγχρονισμό σε παύση. Το κάνει ένας άνθρωπος.
  • Η ανάκαμψη μετά από απότομο τερματισμό δεν είναι γρήγορη. Η μίσθωση μπορεί να κρατήσει δεκαπέντε λεπτά και η σάρωση περιμένει μία ώρα.
  • Και τα δύο όρια, πέντε αποτυχίες και είκοσι τέσσερις ώρες, είναι ρυθμίσεις της εγκατάστασης. Δεν μπορείτε να τα ορίσετε ανά συγχρονισμό.

Μια μακριά αλυσίδα σχεδόν τέλειων βημάτων εξακολουθεί να αποτυγχάνει· η διέξοδος είναι να ορίσετε την τελική κατάσταση και να ελέγχετε γι’ αυτήν. Αυτό το άρθρο είναι το λειτουργικό μισό εκείνου. Ορίστε τι αφήνει πίσω της μια νεκρή εκτέλεση, και ελέγχετε για το τίποτα που αφήνει πίσω της η σιωπή.

Η καταληκτική φράση της Crusoe ήταν ότι «οι αποτυχίες είναι αναπόφευκτες». Η δική μας πάει ένα βήμα πιο πέρα. Η κατάρρευση ήταν το εύκολο μισό, επειδή άφησε γραμμές. Η σιωπή ήταν το μισό που έπρεπε να ψάξουμε.

Έχετε Ερωτήσεις; Έχουμε Απαντήσεις

Τι συμβαίνει σε έναν συγχρονισμό σε εξέλιξη όταν ο εργάτης πέφτει στη μέση μιας παρτίδας;

Ο δρομέας εξακολουθεί να δείχνει την αρχή της διακοπείσας παρτίδας, επειδή το Tajo τον μετακινεί μόνο αφού μια παρτίδα ολοκληρωθεί καθαρά. Η επόμενη εκτέλεση διαβάζει ξανά την ίδια σελίδα. Κάθε εγγραφή φέρει ένα κλειδί παράδοσης που προκύπτει από τον δρομέα της σελίδας και τη θέση της, και ένα μητρώο κρατά κάθε ολοκληρωμένη εγγραφή για επτά ημέρες, οπότε οι εγγραφές που έχουν ήδη γραφτεί αναπαράγονται από το μητρώο αντί να σταλούν στον προορισμό δεύτερη φορά.

Γιατί ένας επαναλαμβανόμενος συγχρονισμός δεν γράφει την ίδια επαφή δύο φορές;

Πριν από κάθε κλήση στον προορισμό, η εκτέλεση ελέγχει το κλειδί παράδοσης της εγγραφής στο μητρώο. Το κλειδί είναι ένα hash του συγχρονισμού, της πηγής, του δρομέα της σελίδας, της θέσης στη σελίδα και της ίδιας της εγγραφής, οπότε μια κατάρρευση, μια νέα προσπάθεια ένα λεπτό αργότερα και μια προγραμματισμένη εκτέλεση την επόμενη ημέρα παράγουν το ίδιο κλειδί για την ίδια εγγραφή. Ένα κλειδί που έχει ήδη σημειωθεί ως ολοκληρωμένο μέσα στο παράθυρο των επτά ημερών δεν αποστέλλεται ξανά.

Τι είναι μια ειδοποίηση τύπου διακόπτη νεκρού ανθρώπου για έναν συγχρονισμό;

Ένας έλεγχος που ενεργοποιείται από την απουσία και όχι από την αποτυχία. Μία φορά την ώρα, το Tajo εξετάζει κάθε χώρο εργασίας με ενεργό συγχρονισμό και ειδοποιεί όταν καμία εκτέλεση δεν έχει πετύχει τις τελευταίες είκοσι τέσσερις ώρες, συμπεριλαμβανομένης της περίπτωσης όπου δεν καταγράφηκε καμία εκτέλεση. Ενεργοποιείται μία φορά ανά περιστατικό και οπλίζεται ξανά μετά την πρώτη επιτυχημένη εκτέλεση. Υπάρχει επειδή μια ειδοποίηση αποτυχίας βλέπει μόνο τις αποτυχίες που καταγράφηκαν.

Ζητήστε πρώιμη πρόσβαση

Συμπληρώστε το μικρό σας όνομα και τη διεύθυνση email σας. Θα σας στείλουμε σύνδεσμο για να επιβεβαιώσετε το αίτημα πρόσβασης στο Tajo.

Αποκτήστε Brevo

Αυτή η σελίδα ως Markdown: worker-died-sync-didnt.md

Φόρτωση Markdown…
Άνοιγμα στο Claude Άνοιγμα στο ChatGPT