14 Σεπτεμβρίου
Είχαμε περάσει εβδομάδες περιμένοντας αυτή τη μέρα. Ο πάροχος των residential proxy μας είχε εξαφανιστεί λόγω αποτυχίας χρέωσης τον Ιούλιο, και για δύο μήνες ένα συνεχώς μεγαλύτερο κομμάτι από τις 1.375 πηγές μας έκανε crawl υποβαθμισμένα ή καθόλου — αιτήματα που τερμάτιζαν με timeout, IP που μπλοκάρονταν στη μέση του session, το είδος της αργής αιμορραγίας που δεν εμφανίζεται ως διακοπή λειτουργίας αλλά ως έναν αριθμό που συνεχώς μικραίνει, χωρίς να το προσέξει κανείς μέχρι να γίνει ντροπιαστικό. Ανανεώσαμε. Νέο pool, 20.000 εναλλασσόμενες residential γραμμές session, με δοκιμή κάθε exit πριν αφήσουμε οτιδήποτε να πλησιάσει το production. Το 100% της δοκιμής πέρασε. ΗΑΕ, Γερμανία, Ιαπωνία — όλα live, όλα καθαρά.
Έτσι τρέξαμε ένα επιτηρούμενο crawl, το είδος όπου κάποιος πραγματικά παρακολουθεί το dashboard αντί να εμπιστεύεται το cron. Περίμενα το αντίθετο από καταστροφή.
Αυτό που πήραμε ήταν μια βάση δεδομένων που είχε χάσει 84.401 εγγραφές σε ένα και μόνο πέρασμα, με 102 πηγές να έχουν πτώση πάνω από 25% σε σχέση με το προηγούμενο snapshot. Χειρότερο από τους δύο μήνες στέρησης proxy που μόλις είχαμε διορθώσει.
Η λάθος θεωρία, εν συντομία
Η πρώτη μου σκέψη — και θα την παραδεχτώ ανοιχτά — ήταν ότι το νέο pool ήταν κατά κάποιον τρόπο χειρότερο απ' όσο έδειχνε η δοκιμή. Ίσως τα IP των session να έφταναν σε rate limit στη μέση του crawl με έναν τρόπο που μια εφάπαξ δοκιμή δεν θα έπιανε. Είναι μια λογική θεωρία. Είναι επίσης λάθος, και το να την πιστεύεις έστω για μισή μέρα μας κόστισε χρόνο που δεν είχαμε, γιατί το cron του crawl παραμένει σε παύση όσο αποδεικνύεις μια τέτοια θεωρία — δεν θέλεις ένα κακό πέρασμα να γράψει πάνω από ένα καλό snapshot ενώ ακόμα μαντεύεις.
Το στοιχείο που κατέρριψε τη θεωρία ήταν η dubizzle. Μία από τις πηγές μας με τον μεγαλύτερο όγκο στα ΗΑΕ, είχε στην πραγματικότητα αντλήσει 32.595 αγγελίες κατά τη διάρκεια αυτού του περάσματος — έναν απόλυτα υγιή αριθμό, με τα proxy να δουλεύουν ξεκάθαρα — και μετά απέρριψε σχεδόν όλες. Όχι αποτυχία άντλησης. Αντλήθηκαν, και μετά πετάχτηκαν.
16 Σεπτεμβρίου: βρίσκοντας το πραγματικό bug
Εκεί σταμάτησε να είναι ιστορία proxy και έγινε ιστορία αντικατάστασης εγγραφών. Το επίπεδο αποθήκευσής μας κάνει snapshot κάθε πηγή ως πλήρη αντικατάσταση του <site>_cars_for_sale.csv σε κάθε πέρασμα — μόνο τρέχουσες αγγελίες, αντικατάσταση όλου του πίνακα. Αυτή είναι η σωστή συμπεριφορά όταν ένα crawl ολοκληρώνεται. Είναι καταστροφική όταν ένα crawl λήγει στη μέση και ο scraper γράφει όποια μερική παρτίδα κατάφερε να συλλέξει σαν να ήταν το πλήρες snapshot. Μια πηγή που συνήθως κρατά 9.000 ενεργές αγγελίες αντικαθίσταται με τις 1.200 που άντλησε πριν λήξει ο χρόνος, και οι υπόλοιπες 7.800 εξαφανίζονται — όχι επειδή πουλήθηκαν τα αυτοκίνητα, αλλά επειδή ο δικός μας κώδικας αποφάσισε ότι μια μισοτελειωμένη άντληση ήταν μια πλήρης.
Τα proxy δεν μας έλεγαν ψέματα. Η λογική των retry ήταν το πρόβλημα, και ήταν σιωπηλά προβληματική για πολύ καιρό, απλώς καλυμμένη από ένα πιο υγιές pool που παλιά ολοκλήρωνε τις αντλήσεις πριν σημασία είχε το sweep.
Να τι κόστισε στην πραγματικότητα αυτό το sweep, μόλις κάναμε τους υπολογισμούς:
| Παλιό sweep retry | Διορθωμένο sweep | |
|---|---|---|
| Επαναλήψεις ανά proxy | 3 | 1 |
| Καθυστέρηση ανά προσπάθεια | 1s | — |
| Ενεργό όριο σε ένα pool των 50 proxy | ~56 λεπτά ανά URL | Συνολικό budget 120 δευτερολέπτων |
| Συμπεριφορά σε timeout | γράφει τη μερική παρτίδα ως πλήρες snapshot | η πηγή παραλείπεται, διατηρείται το προηγούμενο snapshot |
Πενήντα έξι λεπτά για να εγκαταλείψεις ένα μόνο URL. Πολλαπλασίασε αυτό σε ένα crawl που αγγίζει πάνω από χίλιες πηγές και παίρνεις ακριβώς το είδος της σιωπηλής, ανομοιόμορφα κατανεμημένης αποτυχίας που είδαμε — μερικές πηγές μια χαρά, 102 από αυτές κατεστραμμένες, τίποτα στα logs να φωνάζει «διακοπή proxy» επειδή, τεχνικά, δεν υπήρχε καμία.
25 Σεπτεμβρίου: η διόρθωση, και το κομμάτι που σε αφορά
Το patch που κυκλοφόρησε εννιά μέρες αργότερα έκανε δύο αφανή πράγματα. Περιόρισε τα μαθηματικά του retry ώστε μια κολλημένη άντληση να αποτυγχάνει γρήγορα αντί να καταναλώνει το μεγαλύτερο μέρος μιας ώρας, και — αυτό είναι που στην πραγματικότητα αποτρέπει την απώλεια δεδομένων — μια μερική άντληση δεν αντικαθιστά πλέον το πλήρες σύνολο εγγραφών. Αν μια πηγή καταφέρει να αντλήσει μόνο 1.200 από τις συνήθεις 9.000 αγγελίες της, κρατάμε το προηγούμενο πλήρες snapshot αντί να το αντικαθιστούμε με ένα κλάσμα του. Την ίδια μέρα, προστέθηκε μια δεύτερη αλλαγή που γράφει μια ορατή σήμανση CRAWL_PAUSED την οποία ελέγχει η παρακολούθησή μας πριν ειδοποιήσει κανέναν για μια πτώση — έτσι μια σκόπιμη παύση διαβάζεται ως παύση, όχι ως συναγερμός.
Γιατί να νοιάζεται έναν αναγνώστη που ψάχνει για ένα μεταχειρισμένο Hyundai για όλο αυτό; Επειδή οι ενδείξεις τιμής κάτω από την αγορά που δημοσιεύουμε — 12.823 από αυτές ζωντανές στον ιστότοπο αυτή την εβδομάδα — είναι αξιόπιστες μόνο αν η ομάδα σύγκρισης με την οποία συγκρίνουμε μια αγγελία είναι η πραγματική, πλήρης περιφερειακή αγορά και όχι ένα κομμάτι της περικομμένο από timeout. Ένα σήμα ευκαιρίας χτισμένο πάνω σε ένα snapshot που έχασε τα τρία τέταρτα του συνόλου σύγκρισής του δεν είναι σήμα ευκαιρίας, είναι θόρυβος με πράσινη ετικέτα. Προτιμάμε να σταματήσουμε ένα crawl για εννιά μέρες παρά να δημοσιεύσουμε αυτό.
Το σύνολο δεδομένων σήμερα βρίσκεται στις 1.108.535 αγγελίες σε 73 περιφέρειες και 1.375 πηγές — μεγαλύτερο απ' όσο ήταν πριν ξεκινήσει όλο αυτό, με τα proxy ανανεωμένα και το bug αντικατάστασης εγγραφών κλειστό. Αλλά ο αριθμός στον οποίο συνεχώς επιστρέφω δεν είναι το σύνολο. Είναι το 84.401 — το χάσμα ανάμεσα σε ένα crawl που έδειχνε υγιές στο επίπεδο των proxy και σε ένα που ήταν στην πραγματικότητα.



