From 5517aa29f9b042dad84c181879bb7eab18912c13 Mon Sep 17 00:00:00 2001 From: Maxime Delporte Date: Sun, 16 Nov 2025 10:37:09 +0100 Subject: [PATCH] Adding 'IP-based Rate Limiting' sub section. --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index 8f7a890..a95db88 100644 --- a/README.md +++ b/README.md @@ -370,6 +370,26 @@ I'd recommend trying to return and gracefully handle unexpected errors in most c _A panic typically means something went unexpectedly wrong. Mostly we use it to fail fast on errors that shouldn't occur during normal operation and that we aren't prepared to handle gracefully._ +### IP-based Rate Limiting + +Using a global rate limiter can be useful when you want to enforce a strict limit on the total rate of requests to your API, and you don't care where the requests are coming from. But it's generally more common to want an individual rate limiter for each client, so that one bad client making too many requests doesn't affect all the others. + +A conceptually straightforward way to implement this, is to create an in-memory _map of rate limiters_, using the IP address for each client as the map key. + +Each time a new client makes a request to our API, we will initialize a new rate limiter and add it to the map. For any subsequent requests, we will retrieve the client's rate limiter from the map and check whether the request is permitted by calling its **Allow()** method. + +But there's one thing to be aware of: by default, maps are not safe for concurrent use. This is a problem for us because our **rateLimit()** middleware may be running in multiple goroutines at the same time (remember, Go's **http.Server** handles each HTTP request in its own goroutine). + +From the [Go blog](https://go.dev/blog/maps): + +`` +Maps are not safe for concurrent use: it's not defined what happens when you read and write to them simultaneously. If you need to read from and write to a map from concurrently executing goroutines, the accesses must be mediated by some kind of synchronization mechanism. +`` + +So, to get around this, we'll need to synchronize access to the map of rate limiters using a [sync.Mutex](http://golang.org/pkg/sync/#Mutex) (a mutual exclusion lock), so that only one goroutine is able to read or write to the map at any moment in time. + +**Important**: How mutexes work, and how to use them, can be quite confusing if you haven't encountered them before and it's impossible to fully explain in a few short sentences. A much more detailed article is available here - [Understanding Mutexes](https://www.alexedwards.net/blog/understanding-mutexes) - which provides a proper explanation. If you're not already confident with mutexes, I highly recommand reading this before you continue. + ### Performance **json.MarshalIndent()** takes 65% longer to run and uses around 30% more memory than **json.Marshal()**, as well as making two more heap allocations. Those figures will change depending on what you're encoding, but they're fairly indicative of the performance impact.