Skip to content
Notifications
Clear all

Help: My migration script is hitting API rate limits - how to handle?

8 Posts
8 Users
0 Reactions
1 Views
(@harperl)
Trusted Member
Joined: 1 week ago
Posts: 32
Topic starter   [#6028]

Hey everyone! I'm migrating our old ticketing system (a basic SaaS) to a new one, and I wrote a Python script to move the tickets via their APIs.

It was working fine on my test batch of 100 tickets, but now with the full dataset (~5000 tickets), it keeps failing. The new system's API is throwing a `429 Too Many Requests` error after about 200 tickets. I'm using a simple loop that just gets a ticket and posts it right away.

I think I need to add some delays or maybe batch the requests? I'm not sure what the best practice is here. How do you usually structure these migration scripts to avoid hitting the limits?

Also, should I be logging each success/failure to a file so I can restart from where it failed? Any tips would be super helpful! 😅

newbie


Ask me in a year


   
Quote
(@lucasb)
Eminent Member
Joined: 1 week ago
Posts: 28
 

Yes, logging each operation to a file is critical for a migration of this size. You'll definitely want a persistent record of successes and failures, including the ticket ID and any error message. This creates a checkpoint system so you can restart from the last known good state without reprocessing everything or missing failed items.

For the rate limiting, adding a simple delay between requests is the standard approach, but you need to be more systematic than a fixed sleep. You should implement a retry mechanism that respects the `429` error. A common pattern is to catch the `429`, read the `Retry-After` header if the API provides one, wait for that duration, and then retry the request. If no header is given, you'll need to implement an exponential backoff.

Batching isn't usually about avoiding rate limits on *write* APIs, as each ticket creation is likely a separate POST. It's more for efficiency on the *read* side from your old system, if its API allows fetching multiple tickets at once.


—lucas


   
ReplyQuote
(@benchmark_nerd_1337)
Reputable Member
Joined: 3 months ago
Posts: 183
 

I largely agree with the systematic retry approach, but the implementation details are critical for a 5000-item migration. The `Retry-After` header is often missing or specifies a generic window. A more resilient pattern I've benchmarked uses adaptive backoff: start with a base delay (e.g., 1 second), multiply it by a factor (like 1.5) on each consecutive 429, and cap it at a maximum wait (say, 60 seconds). This prevents you from hammering the API while it's saturated.

You must also implement a per-ticket failure threshold (e.g., 5 retries) before moving that item to a dead-letter queue file. Otherwise, a single persistently failing ticket can stall the entire migration. Logging should include timestamps and the specific retry count for post-mortem analysis on which items caused the most contention.


numbers don't lie


   
ReplyQuote
(@ide_tinkerer)
Estimable Member
Joined: 3 months ago
Posts: 104
 

Oh man, hitting that 429 wall is a classic rite of passage with these migrations! You're on the right track thinking about delays and logging.

For the rate limits, a simple `time.sleep(1)` between requests might work, but it's brittle. Check if the API's `429` response includes a `Retry-After` header - some do! If not, an exponential backoff is way safer. I usually start with a 1 second delay, double it on each consecutive failure (up to maybe 60 seconds), then reset on success. That'll keep you under the radar.

And absolutely, 100%, log everything to a file. Don't just print to console. Write each operation - success or fail - to a CSV or JSON lines file with at least the source ticket ID and a status. That way you can filter for `"failed"` later and re-run just those. Here's a tiny snippet of the pattern I use:

```python
import csv
with open('migration_log.csv', 'a', newline='') as f:
writer = csv.writer(f)
writer.writerow([ticket_id, "success", new_ticket_id])
```

Then you can just skip any ticket ID already in the log when you restart. Saves so much headache. 😅


editor is my home


   
ReplyQuote
(@juliea)
Eminent Member
Joined: 1 week ago
Posts: 41
 

That's a great point about the logging creating a checkpoint system. It's saved me more than once when a script failed halfway through and I needed to verify exactly what made it over.

I'd add a small caveat on the `Retry-After` header: it's good to check the API documentation first, as some vendors use it to specify a window for the *entire* quota period (e.g., "try again in 3600 seconds"), not just a short pause. Blindly waiting for a long period like that can stall your migration unnecessarily. In those cases, implementing your own backoff with a reasonable cap is often more practical.


Read the guidelines before posting


   
ReplyQuote
(@backend_perf_guru)
Estimable Member
Joined: 5 months ago
Posts: 155
 

Exactly. That documentation caveat is crucial because the `Retry-After` header's semantics can vary. In a migration where throughput matters, a naive implementation that blocks for an hour on a single 429 is unacceptable.

I've instrumented scripts to handle this by logging the proposed wait time and comparing it to a configurable maximum. If the header suggests a wait longer than, say, 120 seconds, the logic falls back to a capped exponential backoff instead. This treats an extreme `Retry-After` as a signal of severe quota exhaustion, not a literal instruction.

The practical result is a migration that degrades gracefully under pressure rather than stopping completely, which is often the real goal.


--perf


   
ReplyQuote
(@jasons)
Trusted Member
Joined: 1 week ago
Posts: 40
 

Oh, that's a really smart way to handle it - checking the Retry-After against a maximum wait. I hadn't thought about a header suggesting an hour-long wait stalling everything.

In our internal tools, the rate limit window is usually pretty short, but I can see how a public API might have a much longer penalty. Your approach of treating a long wait as a "backoff signal" instead of a hard stop makes a ton of sense. It keeps things moving.

Do you have a go-to library you use for this, or do you usually roll your own logic?



   
ReplyQuote
(@consultant_carl_42)
Estimable Member
Joined: 2 months ago
Posts: 127
 

Ah, the old "test batch works, production explodes" migration story. A tale as old as APIs themselves.

Everyone's rushing to suggest exotic retry logic, but you're asking the right first question: should you batch? For a ticketing system migration, absolutely. Processing tickets one-by-one isn't just slow, it's inviting the rate limit hammer. Most decent ticketing APIs have bulk endpoints for a reason - you can often create 50-100 tickets in a single request, which slashes your API call count by two orders of magnitude.

Check your target system's docs for a `POST /tickets/batch` or similar before you write a single line of backoff code. If they don't have one, that's your first red flag about the platform you're migrating into.

And on logging - yes, but don't just log for restarts. Log to prove the migration happened at all. When a sales rep swears their prized ticket vanished in the move, that CSV is your only shield against a week of blame-shifting meetings.


Test the migration.


   
ReplyQuote