Η αθέατη πλευρά του swap στο Go runtime
Η διαχείριση μνήμης σε συστήματα Linux με ενεργοποιημένο swap συχνά θεωρείται διαφανής, όμως η αλληλεπίδραση με τον garbage collector (GC) του Go αποδεικνύει το αντίθετο. Κατά τη διάρκεια του GC, το runtime εκτελεί κρίσιμες λειτουργίες όπως το sweep termination και το mark termination, οι οποίες απαιτούν άμεση πρόσβαση στα εσωτερικά metadata του συστήματος.
Η αρχιτεκτονική παγίδα των metadata
Το Go runtime αποθηκεύει metadata σε περιοχές μνήμης που δεν αποδεσμεύονται (non-freed). Όταν ο kernel, υπό συνθήκες πίεσης μνήμης, αποφασίζει να μεταφέρει σελίδες στο swap βάσει παλαιότητας (LRU), τα metadata αυτά μπορεί να καταλήξουν στον δίσκο. Όταν το GC ξεκινά τη διαδικασία stop-the-world, επιχειρεί να διαβάσει αυτά τα δεδομένα, προκαλώντας major page faults. Η διαδικασία ανάκτησης από το swap (do_swap_page, bio submission, disk I/O) είναι εξαιρετικά χρονοβόρα, μετατρέποντας ένα pause μικροδευτερολέπτων σε ένα spike 40ms.
Επιπτώσεις στο runtime και το throughput
Το πρόβλημα δεν είναι μόνο η διάρκεια του pause, αλλά το γεγονός ότι κατά τη διάρκεια του stop-the-world, όλα τα goroutines σταματούν. Αν ένα goroutine ανέμενε I/O, η αδυναμία του runtime να το εξυπηρετήσει κατά το pause μπορεί να οδηγήσει σε αλυσιδωτές καθυστερήσεις. Παρόλο που το allocation latency (το κόστος ανάθεσης μνήμης) είναι επίσης αυξημένο λόγω swap, αυτό παραμένει εντοπισμένο στο συγκεκριμένο goroutine, ενώ το metadata pause επηρεάζει ολόκληρο το application.
Συμπεράσματα για production περιβάλλοντα
Η έρευνα, η οποία περιλαμβάνει αναλυτικά BPF scripts και μετρήσεις σε σύγχρονους kernels, καταδεικνύει ότι η στρατηγική του kernel για το swap δεν έχει επίγνωση των αναγκών του Go GC. Για κρίσιμα συστήματα, η αποφυγή του swap ή η χρήση τεχνικών όπως το mlock για τα κρίσιμα τμήματα της μνήμης του runtime παραμένει η ασφαλέστερη οδός για τη διατήρηση προβλέψιμου latency.