Retry Policy — πολιτική επαναλαμβανόμενων αιτημάτων — ένα σύνολο κανόνων που καθορίζει πότε και πώς μια κινητή εφαρμογή επαναλαμβάνει αυτόματα αποτυχημένες κλήσεις δικτύου. Σε ασταθή σύνδεση ή προσωρινά σφάλματα διακομιστή, μια σωστή πολιτική επανάληψης αυξάνει την αξιοπιστία της εφαρμογής χωρίς τη συμμετοχή του χρήστη. Σύμφωνα με έρευνα της Google Developer Relations (2025), η σωστή υλοποίηση του Retry Policy μειώνει το ποσοστό των χαμένων αιτημάτων κατά 40-60% σε κινητές εφαρμογές με συχνές λειτουργίες δικτύου.
Κύρια σημεία
Retry Policy — είναι μια στρατηγική λογισμικού που καθορίζει τη συμπεριφορά του πελάτη όταν αποτυγχάνει ένα αίτημα δικτύου: ποια σφάλματα να επαναλαμβάνει, πόσες φορές, με ποια καθυστέρηση και πότε να σταματά τις προσπάθειες. Σε κινητές εφαρμογές, η πολιτική επανάληψης είναι κρίσιμης σημασίας λόγω της αστάθειας των κινητών δικτύων και πιθανών προσωρινών βλαβών από την πλευρά του διακομιστή.
Η βασική Retry Policy περιλαμβάνει τρεις παραμέτρους: τον μέγιστο αριθμό επαναλήψεων (maxRetries), την αρχική καθυστέρηση (baseDelay) και τη στρατηγική αύξησης της καθυστέρησης (backoff strategy). Επιπλέον, μπορεί να οριστεί μια λίστα κωδικών κατάστασης HTTP στους οποίους πρέπει να γίνεται επανάληψη και ένα χρονικό όριο για τη διακοπή όλων των προσπαθειών.
Σύμφωνα με το βιβλίο “Designing Data-Intensive Applications” του Martin Kleppmann, το 50% των βλαβών σε κατανεμημένα συστήματα είναι προσωρινές και διορθώνονται με μια εκ νέου προσπάθεια. Αυτό καθιστά το Retry Policy έναν από τους πιο αποτελεσματικούς και φθηνούς τρόπους αύξησης της ανοχής σφαλμάτων μιας κινητής εφαρμογής χωρίς αλλαγές στην αρχιτεκτονική του διακομιστή.
Προσωρινά σφάλματα (retriable) — ο μόνος τύπος βλάβης στον οποίο πρέπει να αντιδρά το Retry Policy. Αυτά περιλαμβάνουν χρονικές υπερβάσεις σύνδεσης (SocketTimeoutException), προσωρινή μη διαθεσιμότητα διακομιστή (HTTP 503, 502) και σφάλματα DNS. Μόνιμα σφάλματα — HTTP 400, 401, 403, 404 — δεν έχει νόημα να επαναλαμβάνονται, καθώς υποδεικνύουν πρόβλημα στο αίτημα, όχι στο δίκτυο ή τον διακομιστή.
Σύμφωνα με έρευνα του AWS Architecture Blog, η σωστή ταξινόμηση σφαλμάτων σε retriable και non-retriable είναι η πιο σημαντική απόφαση κατά τον σχεδιασμό του Retry Policy. Η επανάληψη ενός μη idempotent αιτήματος με HTTP 401 μπορεί να οδηγήσει σε αποκλεισμό λογαριασμού, και η επανάληψη HTTP 400 στη δημιουργία διπλότυπων δεδομένων. Πάντα να διαμορφώνετε ρητά τη λίστα κωδικών για επανάληψη.
Fixed interval — η απλούστερη στρατηγική: κάθε επανάληψη εκτελείται μετά το ίδιο χρονικό διάστημα. Για παράδειγμα, με καθυστέρηση 2 δευτερολέπτων, η εφαρμογή επαναλαμβάνει το αίτημα μετά από 2, 2, 2 δευτερόλεπτα. Το Fixed interval είναι απλό στην υλοποίηση και προβλέψιμο, αλλά δημιουργεί ομοιόμορφο φόρτο στον διακομιστή σε μαζικές βλάβες.
Incremental interval — η καθυστέρηση αυξάνεται γραμμικά με κάθε επανάληψη: πρώτη επανάληψη μετά από 1 δευτερόλεπτο, δεύτερη μετά από 2, τρίτη μετά από 3 και ούτω καθεξής. Αυτή η στρατηγική δίνει στον διακομιστή περισσότερο χρόνο για ανάκαμψη σε επαναλαμβανόμενες βλάβες, αλλά παραμένει προβλέψιμη για πολλούς πελάτες που αποτυγχάνουν ταυτόχρονα.
| Στρατηγική | Τύπος καθυστέρησης | Σωρευτικός χρόνος (3 προσπάθειες) | Εφαρμογή |
|---|---|---|---|
| Fixed | delay = D | 3 × D | Απλά σενάρια, τοπικές χρονικές υπερβάσεις |
| Incremental | delay = N × D | 6 × D | Σταδιακή μείωση φόρτου |
| Exponential | delay = D × 2^N | 7 × D | Μαζικές βλάβες, υπηρεσίες cloud |
| Exponential + Jitter | delay = random(0, D × 2^N) | μεταβλητός | Υψηλός φόρτος, μικροϋπηρεσίες |
Η επιλογή στρατηγικής εξαρτάται από τη φύση της εφαρμογής. Για εργασίες παρασκηνίου συγχρονισμού δεδομένων σε κινητές συσκευές, η στρατηγική exponential με jitter είναι βέλτιστη — δίνει την υψηλότερη πιθανότητα επιτυχίας με ελάχιστο φόρτο στον διακομιστή και τη συσκευή του χρήστη.
Exponential backoff — στρατηγική όπου η καθυστέρηση μεταξύ επαναλήψεων διπλασιάζεται με κάθε προσπάθεια. Εάν η αρχική καθυστέρηση είναι 1 δευτερόλεπτο, η ακολουθία καθυστερήσεων θα είναι 1, 2, 4, 8, 16 δευτερόλεπτα. Αυτό δίνει στον διακομιστή εκθετικά αυξανόμενο χρόνο για ανάκαμψη.
Jitter — τυχαία απόκλιση καθυστέρησης που αποτρέπει σύγχρονα επαναλαμβανόμενα αιτήματα από πολλούς πελάτες (πρόβλημα thundering herd). Χωρίς jitter, χίλιοι πελάτες με την ίδια Retry Policy θα επαναλαμβάνουν αιτήματα ταυτόχρονα, δημιουργώντας φόρτο αιχμής στον διακομιστή. Το Jitter κατανέμει τις επαναλήψεις στον χρόνο.
Τα coroutines του Kotlin επιτρέπουν την υλοποίηση exponential backoff με jitter χωρίς αποκλεισμό του κύριου νήματος. Η συνάρτηση retry από το kotlinx-coroutines δέχεται μια συνθήκη επανάληψης και ένα μπλοκ με το σώμα του αιτήματος, διαχειριζόμενη αυτόματα τις καθυστερήσεις και τον αριθμό προσπαθειών.
suspend fun RetryPolicy.executeWithRetry(
block: suspend () -> Result<T>
): Result<T> {
var lastError: Throwable? = null
repeat(maxRetries + 1) { attempt ->
try {
return block()
} catch (e: Exception) {
if (!isRetriable(e) || attempt == maxRetries) {
return Result.failure(e)
}
val delay = (baseDelayMs * (1 shl attempt))
.toLong()
val jitteredDelay = (delay * (0.5 + Random.nextDouble())).toLong()
delay(jitteredDelay)
lastError = e
}
}
return Result.failure(lastError!!)
}
Η συνάρτηση executeWithRetry δέχεται ένα lambda με κλήση δικτύου και την εκτελεί με exponential backoff και jitter. Εάν το σφάλμα δεν μπορεί να επαναληφθεί (non-retriable) ή έχει ξεπεραστεί ο μέγιστος αριθμός προσπαθειών, η συνάρτηση επιστρέφει σφάλμα. Η καθυστέρηση πολλαπλασιάζεται με έναν τυχαίο συντελεστή από 0,5 έως 1,5 για ομοιόμορφη κατανομή των επαναλήψεων.
Circuit Breaker — ένα σχέδιο σχεδίασης που αποτρέπει ατελείωτα επαναλαμβανόμενα αιτήματα σε παρατεταμένη μη διαθεσιμότητα υπηρεσίας. Όταν ο αριθμός σφαλμάτων υπερβεί το όριο, το Circuit Breaker μεταβαίνει σε κατάσταση OPEN και επιστρέφει αμέσως σφάλμα χωρίς να εκτελέσει το αίτημα, δίνοντας χρόνο στον διακομιστή για ανάκαμψη.
Σε κινητές εφαρμογές, το Circuit Breaker είναι ιδιαίτερα χρήσιμο σε μη διαθεσιμότητα API λόγω προγραμματισμένης συντήρησης ή βλαβών δικτύου παρόχου. Χωρίς αυτό, η εφαρμογή θα καταναλώνει μπαταρία και κίνηση σε ατελείωτες επαναλαμβανόμενες προσπάθειες, επιδεινώνοντας την εμπειρία χρήστη και μειώνοντας τη διάρκεια ζωής της μπαταρίας της συσκευής.
Τρεις καταστάσεις Circuit Breaker: CLOSED (κανονική λειτουργία, τα αιτήματα εκτελούνται), OPEN (άρνηση, τα αιτήματα αποκλείονται) και HALF_OPEN (δοκιμαστικό αίτημα για έλεγχο ανάκαμψης). Μετά από ένα καθορισμένο χρονικό όριο στην κατάσταση OPEN, ο διακόπτης μεταβαίνει σε HALF_OPEN και εκτελεί ένα αίτημα — σε επιτυχία επιστρέφει σε CLOSED, σε αποτυχία — σε OPEN.
class CircuitBreaker(
private val failureThreshold: Int = 3,
private val timeoutMs: Long = 30000
) {
private var state = State.CLOSED
private var failureCount = 0
private var lastFailureTime: Long = 0
suspend fun T.protect(block: suspend () -> T): T {
checkState()
return try {
val result = block()
onSuccess()
result
} catch (e: Exception) {
onFailure()
throw e
}
}
}
Η υλοποίηση Circuit Breaker σε Kotlin περιέχει μετρητή σφαλμάτων και χρονοδιακόπτη ανάκαμψης. Η μέθοδος protect ελέγχει την τρέχουσα κατάσταση πριν εκτελέσει το αποκλεισμένο αίτημα και ενημερώνει τον μετρητή σφαλμάτων σε βλάβες. Μετά την επίτευξη του ορίου failureThreshold, όλα τα αιτήματα απορρίπτονται αμέσως μέχρι τη λήξη του timeoutMs.
Τα κινητά δίκτυα έχουν χαρακτηριστικά που καθιστούν το Retry Policy ιδιαίτερα σημαντικό. Η εναλλαγή μεταξύ Wi-Fi και κινητών δεδομένων, η απώλεια σήματος στο μετρό και σήραγγες, οι προσωρινοί αποκλεισμοί σε επίπεδο παρόχου — όλα αυτά τα σενάρια οδηγούν σε αποτυχίες αιτημάτων που αντιμετωπίζονται επιτυχώς με επαναλαμβανόμενες προσπάθειες.
Στο Android, η βιβλιοθήκη Retrofit και το OkHttp παρέχουν ενσωματωμένο μηχανισμό RetryPolicy μέσω Interceptor. Στο iOS, το πρόβλημα λύνεται μέσω URLSessionConfiguration και προσαρμοσμένης εκπροσώπησης. Για ανάπτυξη cross-platform, το Ktor (KMP) περιέχει ενσωματωμένη υποστήριξη retry με διαμορφώσιμες στρατηγικές.
Combine — το πλαίσιο της Apple για αντιδραστικό προγραμματισμό. Ο τελεστής retry στο Combine επαναλαμβάνει τον publisher καθορισμένες φορές σε σφάλμα, αλλά δεν επιτρέπει τη διαμόρφωση της καθυστέρησης μεταξύ επαναλήψεων. Για πλήρες Retry Policy, χρησιμοποιείται προσαρμοσμένος συνδυασμός catch και flatMap με καθυστέρηση.
extension Publisher {
func retryWithBackoff(
retries: Int = 3,
baseDelay: TimeInterval = 1.0
) -> AnyPublisher<Output, Failure> {
return self.catch { error -> AnyPublisher in
guard retries > 0 else {
return Fail(error).eraseToAnyPublisher()
}
return Just(())
.delay(for: .seconds(baseDelay), scheduler: DispatchQueue.main)
.flatMap { self.retryWithBackoff(
retries: retries - 1,
baseDelay: baseDelay * 2
) }
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
}
Η επέκταση retryWithBackoff για Publisher στο Combine υλοποιεί exponential backoff μέσω αναδρομικής κλήσης με μείωση μετρητή και διπλασιασμό καθυστέρησης. Ο τελεστής delay δημιουργεί παύση μεταξύ επαναλήψεων, και το catch παγιδεύει το σφάλμα και αποφασίζει αν θα ξαναπροσπαθήσει ή θα επιστρέψει failure.
Πρώτο λάθος — επανάληψη αιτημάτων χωρίς έλεγχο idempotency. Εάν ο διακομιστής δημιούργησε έναν πόρο αλλά δεν επέστρεψε επιβεβαίωση λόγω βλάβης δικτύου, το επαναλαμβανόμενο αίτημα θα δημιουργήσει διπλότυπο. Για αιτήματα POST, χρησιμοποιείτε πάντα ένα idempotent κλειδί (Idempotency-Key) στην κεφαλίδα ή εφαρμόστε retry μόνο για GET, PUT και DELETE.
Δεύτερο λάθος — ατελείωτες επαναλήψεις (retry forever). Πάντα να ορίζετε έναν μέγιστο αριθμό προσπαθειών (3-5 για κινητές εφαρμογές) και ένα συνολικό χρονικό όριο για όλες τις προσπάθειες. Οι ατελείωτες επαναλήψεις εξαντλούν την μπαταρία και δημιουργούν παρασιτικό φόρτο στον διακομιστή, ειδικά κατά τη μετεγκατάσταση βάσεων δεδομένων ή αλλαγή API.
Τρίτο λάθος — αγνόηση του πλαισίου της εφαρμογής. Εάν ο χρήστης έκλεισε την εφαρμογή ή μεταπήδησε σε λειτουργία παρασκηνίου, τα ενεργά Retry Policy πρέπει να ακυρώνονται σωστά. Χρησιμοποιήστε coroutines με SupervisorScope ή Combine με κύκλο ζωής UI για αυτόματη ακύρωση επαναλήψεων όταν κλείνει η οθόνη.
Τέταρτο λάθος — μη καταγραφή επαναλαμβανόμενων προσπαθειών. Χωρίς καταγραφή δεν θα γνωρίζετε πόσα αιτήματα επαναλήφθηκαν, ποια σφάλματα προέκυψαν και πόσο αποτελεσματική είναι η Retry Policy σας. Προσθέστε μετρήσεις: αριθμός επαναλήψεων, επιτυχία μετά από επανάληψη, κατανομή καθυστερήσεων. Αυτά τα δεδομένα θα σας βοηθήσουν να ρυθμίσετε τις βέλτιστες παραμέτρους στρατηγικής για τη συγκεκριμένη εφαρμογή.
Συχνές ερωτήσεις
Ο βέλτιστος αριθμός επαναλήψεων — 3-5 προσπάθειες για τα περισσότερα σενάρια. Για συγχρονισμό παρασκηνίου επιτρέπονται 5-7 προσπάθειες, για διαδραστικά αιτήματα (π.χ. υποβολή φόρμας) — όχι περισσότερες από 3. Ο μεγαλύτερος αριθμός επαναλήψεων δεν αυξάνει την πιθανότητα επιτυχίας, αλλά καταναλώνει μπαταρία και κίνηση δεδομένων του χρήστη.
Exponential backoff — ο διπλασιασμός της καθυστέρησης μεταξύ επαναλαμβανόμενων προσπαθειών: 1 δευτερόλεπτο, 2, 4, 8, 16 και ούτω καθεξής. Εάν ο διακομιστής είναι υπερφορτωμένος, η σύντομη παύση μεταξύ των πρώτων επαναλήψεων του επιτρέπει να απαντήσει γρήγορα, και η αυξανόμενη παύση με κάθε επόμενη προσπάθεια δίνει όλο και περισσότερο χρόνο για ανάκαμψη.
Επαναλάβετε μόνο προσωρινά σφάλματα: 408 (Request Timeout), 429 (Too Many Requests), 502 (Bad Gateway), 503 (Service Unavailable), 504 (Gateway Timeout). Τα σφάλματα 4xx (εκτός 408 και 429) υποδεικνύουν προβλήματα πελάτη — η επανάληψή τους δεν έχει νόημα και μπορεί να είναι επικίνδυνη για τα δεδομένα του χρήστη.
Retry Policy διαχειρίζεται την επανάληψη ενός μεμονωμένου αιτήματος σε αποτυχία. Το Circuit Breaker διαχειρίζεται την κατάσταση σύνδεσης με την υπηρεσία: με τη συσσώρευση σφαλμάτων ανοίγει το κύκλωμα (OPEN) και δεν επιτρέπει νέα αιτήματα. Το Retry λειτουργεί σε επίπεδο μεμονωμένης κλήσης, το Circuit Breaker — σε επίπεδο ενσωμάτωσης με την υπηρεσία.
Για δοκιμή του Retry Policy, χρησιμοποιήστε NetworkInterceptor (OkHttp) σε Android και URLProtocol (URLSession) σε iOS για προσομοίωση βλαβών δικτύου. Ορίστε παραμέτρους: συχνότητα σφαλμάτων, διάρκεια μη διαθεσιμότητας και κωδικούς απόκρισης. Οι δοκιμές μονάδας με MockWebServer (OkHttp) ή OHHTTPStubs (iOS) ελέγχουν τη λογική επανάληψης χωρίς πραγματικό δίκτυο.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης