Skip to content

Conversation

robertolosanno-e2x
Copy link
Contributor

This PR implements rate limiting and automatic retry logic to handle Contentstack's API rate limits (10 requests/second). The current SDK fails immediately when hitting 429 errors, which blocks automation tools like Terraform that make concurrent requests.

Changes implemented:

  1. Rate Limiting Configuration: Added RateLimit, RateBurst, and MaxRetries fields to ClientConfig

    • Uses golang.org/x/time/rate for token bucket rate limiting
    • Defaults to 10 req/sec to comply with API limits
    • Configurable burst size (default: 10)
  2. Automatic Retry Logic: Added exponential backoff retry mechanism

    • Automatically retries 429 responses up to MaxRetries times (default: 3)
    • Uses simple exponential backoff: 1s, 2s, 4s, 8s, 16s, capped at 30s
    • Respects server Retry-After headers when provided
    • Context-aware (respects cancellation and timeouts)
  3. Enhanced Error Handling: Improved 429 error handling in processResponse

    • Provides informative error messages when all retries are exhausted
    • Maintains backward compatibility

Rationale:
This resolves blocking issues with Terraform and other automation tools that need to make concurrent API requests. Instead of failing immediately on rate limits, the SDK now handles them gracefully with automatic retries, making it much more reliable for production automation workflows.

Fixes #16

Related: terraform-provider-contentstack#3

  • NEW FEATURE: Added configurable rate limiting to prevent hitting API limits
  • NEW FEATURE: Added automatic retry logic with exponential backoff for 429 responses
  • ENHANCEMENT: Improved error handling for rate limit scenarios
  • BUG FIX: Fixed "Unhandled StatusCode: 429" errors that caused immediate failures
  • ENHANCEMENT: Added support for server Retry-After headers

This PR template provides a comprehensive description of the changes, links to the relevant issues, and clearly categorizes the improvements made to the SDK.

- Add configurable rate limiting (RateLimit, RateBurst)
- Add automatic retry logic with exponential backoff for 429 responses
- Add MaxRetries configuration option
- Implement proper 429 error handling in processResponse
- Use golang.org/x/time/rate for token bucket rate limiting
- Respect server Retry-After headers when provided
- Default to conservative 10 req/sec rate limit to comply with API limits
- Default to 3 retry attempts with 1s, 2s, 4s, 8s, 16s backoff (capped at 30s)

This resolves issues with Terraform and other automation tools that make
concurrent requests and frequently hit Contentstack's API rate limits.
@robertolosanno-e2x
Copy link
Contributor Author

Hi @demeyerthom,
I used a bit of AI help to get this feature developed, but I also run some tests locally using the following test

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/labd/contentstack-go-sdk/management"
)

func main() {
	// Example demonstrating rate limiting functionality
	fmt.Println("Testing Contentstack SDK Rate Limiting")
	fmt.Println("=====================================")

	// Create a client with custom rate limiting (5 requests per second for demo)
	cfg := management.ClientConfig{
		BaseURL:   "https://api.contentstack.io",
		AuthToken: "dummy-token", // This won't work for real requests, but demonstrates rate limiting
		RateLimit: 5.0,           // 5 requests per second
		RateBurst: 5,             // Allow burst of 5 requests
	}

	client, err := management.NewClient(cfg)
	if err != nil {
		log.Fatalf("Failed to create client: %v", err)
	}

	fmt.Printf("Created client with rate limit: %.1f requests/second, burst: %d\n",
		cfg.RateLimit, cfg.RateBurst)

	// Test rate limiting by making multiple requests quickly
	fmt.Println("\nMaking 10 rapid requests to demonstrate rate limiting...")

	start := time.Now()
	for i := 0; i < 10; i++ {
		requestStart := time.Now()

		// This will fail due to dummy token, but rate limiting will still apply
		_, err := client.Stack(&management.StackAuth{
			ApiKey:          "dummy-key",
			ManagementToken: "dummy-token",
		})

		elapsed := time.Since(requestStart)
		fmt.Printf("Request %d: took %v", i+1, elapsed)

		if err != nil {
			fmt.Printf(" (expected error: %v)", err)
		}
		fmt.Println()
	}

	totalElapsed := time.Since(start)
	fmt.Printf("\nTotal time for 10 requests: %v\n", totalElapsed)
	fmt.Printf("Average time per request: %v\n", totalElapsed/10)

	// With 5 requests/second rate limit, 10 requests should take at least ~2 seconds
	expectedMinTime := 2 * time.Second
	if totalElapsed >= expectedMinTime {
		fmt.Printf("✅ Rate limiting working correctly (took %v, expected >= %v)\n",
			totalElapsed, expectedMinTime)
	} else {
		fmt.Printf("⚠️  Rate limiting may not be working (took %v, expected >= %v)\n",
			totalElapsed, expectedMinTime)
	}

	fmt.Println("\nRate limiting test completed!")
}

And by connecting the TF provider to this SDK locally to check that the rate limit works on a real contenstack project.

- Remove retry-related fields and functions from client
- Move retry logic to terraform provider for better separation
- Simplify SDK to focus on rate limiting only
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add rate limiting and retry logic for handling API rate limits
1 participant