Skip to content
AH

Ahmedin Hasanovic

1 article

July 8, 2025

Building a Fault-Tolerant Architecture for High-Volume APIs

Software Development

Building a Fault-Tolerant Architecture for High-Volume APIs

Introduction High-volume APIs with fault tolerance provide ongoing availability and dependability even in the event of component failures, such as hardware malfunctions, software flaws, network outages, or human error [1].  Maintaining smooth user experiences is essential, particularly in dispersed microservices architectures [1]. Fault tolerance highlights a system's ability to withstand unavoidable faults without suffering major interruptions. High availability, often supported by fault tolerance mechanisms, aims to ensure minimal downtime and consistent service delivery [2]. Core Principles of Fault-Tolerant API Design Proactive Failure Anticipation Instead of trying to eliminate the possibility of failure, the focus should shift to creating a system that can handle these situations and continue to operate or recover with minimal disruption [5]. Redundancy and Replication Component redundancy: Duplicating essential system components, which ensures seamless continuation if one fails [5]. Service replication: Running multiple instances of services across distinct nodes to handle requests, which enhances availability and performance [5]. Data replication: Keeping multiple copies of data ensures availability even during localized outages [6]. Isolation Isolation keeps services apart, thereby preventing cascading failures. The failure of one service should not impact other unrelated services, thereby maintaining the general system resilience [1]. Architectural Considerations for Fault Tolerance Microservices architecture Microservices architecture has become the go-to method for creating systems that are highly available, scalable, and agile. By separating each service, microservices improve fault tolerance by limiting the impact of a failure in one service on others. Resilience is increased by reducing single points of failure through decentralized deployment and independent scalability [1]. Service Meshes and Messaging Systems The software layer that manages all communication between services in applications is called a service mesh. Retries, circuit breakers, and timeouts are just a few of the fault tolerance capabilities that service meshes can provide at the infrastructure level, frequently without requiring changes to the application code itself. Apache Kafka, a distributed messaging system, can provide asynchronous communication between microservices. Apache Kafka can provide temporary resilience by buffering messages during service outages, provided that consumer offsets are managed correctly and data retention policies align with recovery expectations. Key Strategies and Patterns Retry Mechanism In distributed systems, temporary failures are frequent and must be handled by retry techniques [1]. With this pattern, a failed request is automatically re-attempted a specified number of times before being abandoned. For temporary problems like network outages or brief service outages, retrying can be especially useful [7]. It is important to ensure that the operations being retried are idempotent, meaning that performing that operation multiple times has the same effect as performing it once. Defining a retry configuration for a service call, using Resilience4j library in Java is presented below: // Define retry configuration for external service calls RetryConfig retryConfig = RetryConfig.custom() .maxAttempts(10) // Maximum number of retry attempts .waitDuration(Duration.ofMillis(500)) // Wait time between retry attempts .retryExceptions(IOException.class, TimeoutException.class) .build(); // Create a Retry instance based on the above configuration Retry retry = Retry.of("externalServiceRetry", retryConfig); // Log each retry attempt with its details retry.getEventPublisher().onRetry(event -> logger.info("Retry attempt #{} due to: {}", event.getNumberOfRetryAttempts(), event.getLastThrowable() != null ? event.getLastThrowable().getMessage() : "unknown error")); Resilience4j is set up in the example above to try a failed service up to ten times, with a 500 ms pause in between each attempt. Additionally, an event publisher is set up to track the number of attempts and a suitable message for each unsuccessful execution. Timeouts In distributed systems, timeouts are a key tactic for minimizing resource monopolization and infinite blocking [8]. Setting a timeout for API requests ensures that the calling service will not wait indefinitely for a response from a potentially failing or slow dependency. If a service fails, fallback methods offer a backup plan. This could entail calling a different API endpoint, returning cached data, or giving a default or stubbed response [4]. Enhanced timeout implementation, using Resilience4j library in Java is presented below: // Define configuration for TimeLimiter with a specified timeout TimeLimiterConfig timeLimiterConfig = TimeLimiterConfig.custom() .timeoutDuration(Duration.ofSeconds(30)) // Timeout after 30 seconds .build(); // Create a TimeLimiter instance based on the configuration TimeLimiter timeLimiter = TimeLimiter.of("externalServiceTimeout", timeLimiterConfig); // ThreadPoolBulkhead manages thread pools, isolating resources used for this operation ThreadPoolBulkhead threadPoolBulkhead = ThreadPoolBulkhead.ofDefaults("externalServiceBulkhead"); // Scheduler for managing asynchronous tasks required by TimeLimiter ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); In this example, calls to potentially slow or hanging services are automatically aborted after a 30-second timeout. ThreadPoolBulkhead is used to manage asynchronous execution by executing activities in distinct thread pools since TimeLimiter, which specifies the timeout handling, operates asynchronously. Circuit Breaker The Circuit Breaker pattern is crucial for avoiding cascading failures in distributed systems [1]. It works through detecting frequent service failures and momentarily stopping requests to that service [1]. This allows the failing service time to recover by preventing the caller application from repeatedly attempting an operation that is likely to fail [12]. Like an electrical circuit breaker, it "trips" to prevent additional requests to the problematic service when the number of failures surpasses a predetermined threshold when the number of failures exceeds an established limit [4]. Circuit Breaker configuration, using Resilience4j library in Java is presented below: // Define Circuit Breaker configuration to manage failing service calls CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom() .failureRateThreshold(50) // Opens circuit after 50% failure rate .waitDurationInOpenState(Duration.ofSeconds(30)) // Circuit stays open for 30 seconds .slidingWindowSize(3) // Evaluates failure rate over the last 3 calls .build(); // Create CircuitBreaker instance based on the configuration CircuitBreaker circuitBreaker = CircuitBreaker.of("externalServiceCircuitBreaker", circuitBreakerConfig); // Log state transitions (closed, open, half-open) circuitBreaker.getEventPublisher().onStateTransition(event -> logger.info("Circuit Breaker state transition: {}", event.getStateTransition())); After a 50% failure rate threshold is reached, this configuration halts requests to a troubled service, giving it time to recover before restarting requests. The recovery period is set at 30 seconds. Combined Fault-Tolerance Example The following code illustrates how to integrate all the mentioned fault tolerance patterns into a unified robust solution: // Create a supplier calling an unreliable external operation Supplier<String> unreliableSupplier = () -> { try { return unreliableOperation(); // Potentially unreliable external call } catch (Exception e) { throw new RuntimeException(e); } }; // Decorate the supplier with multiple fault-tolerance mechanisms Supplier<CompletionStage<String>> decoratedSupplier = Decorators.ofSupplier(unreliableSupplier) .withThreadPoolBulkhead(threadPoolBulkhead) // Isolates resources to prevent thread exhaustion .withCircuitBreaker(circuitBreaker) // Stops calls after repeated failures .withTimeLimiter(timeLimiter, scheduler) // Prevents indefinite waiting .withRetry(retry, scheduler) // Retries transient errors automatically .decorate(); return decoratedSupplier.get(); Call for this service method is provided below: externalService.callExternalService() .thenAcceptAsync(result -> { logger.info("Got: {}", result); }).exceptionally(ex -> { logger.error("Failed",ex); }); This combined strategy significantly increases reliability through providing resilient calls that automatically handle periodic failures, slow responses, and persistent service problems. Testing and Monitoring for Fault Tolerance Testing the fault tolerance APIs requires a thorough approach to guarantee that the resilience mechanisms in place operate as planned in all kinds of failure scenarios [1]: Unit Testing: Confirming the resilience measures of each component separately Integration Testing: Confirming resilience measures operate as intended across service boundaries Load and Stress Testing: Confirming resilience measures operate as intended in situations with high traffic and peak load, where malfunctions are more likely to happen Chaos Engineering: Introducing controlled failures based on specific hypotheses, validating whether resilience mechanisms behave as expected under real-world engineering Best Practices and Recommendations Resilience pattern configuration management requires careful attention to detail. The service level agreements (SLA) of API clients should guide the configuration of timeouts and retry strategies [9]. Depending on the criticality and intended behavior of each dependency, specific parameters, such as timeout durations and the number of retry attempts, should be customized [4]. To enable modifications without requiring code redeployments, these configurations should ideally be externalized [4]. Careful Configuration Management: The service level agreements (SLA) of API clients should guide the configuration of timeouts and retry tactics [9]. Each dependency should have its own individual parameters, including timeout durations and the number of retry attempts [4]. To enable modifications without needing code redeployments, these configurations should preferably be externalized [9]. Ensure Idempotency: To prevent unexpected side effects, retry mechanisms should be idempotent, especially for actions that change system state [7]. Effective Error Handling: Descriptive error messages that include background information on the failure should be included in API responses. It is essential to distinguish between temporary and permanent failures [9]. For debugging and analysis, extensive error logging with proper details is necessary [1]. Balance Performance and Resilience: Retries and timeouts are examples of mechanisms that might add latency and use more resources. To achieve the required degree of robustness without compromising the API’s speed, these patterns must be configured appropriately. Conclusion For high-volume APIs to be reliable and provide a satisfying user experience, fault tolerance must be included in the architecture as a fundamental requirement, not just an optional feature [4]. A proactive and thorough strategy for managing failures is necessary due to the complexity and scale of modern API systems, especially those that utilize microservices [4]. As noted, to build fully robust systems, a mix of several fault tolerance techniques, such as redundancy, load balancing, circuit breakers, retries, bulkheads, timeouts, and fallbacks, is often necessary [4]. Furthermore, rigorous testing and continuous monitoring are indispensable for validating the effectiveness of these mechanisms and ensuring the ongoing health and stability of the API. Ultimately, designing for resilience in high-volume API systems is a continuous process that requires a comprehensive strategy, integrating fault tolerance concepts across the API's whole lifecycle to ensure that it can reliably satisfy user demands even in the face of unavoidable failures. Literature Medium.com: https://medium.com/cloud-native-daily/fault-tolerance-in-microservices-architecture-patterns-principles-and-techniques-explained-20cfa3d7f98f#:~:text=This%20is%20very%20important%20when,even%20if%20something%20goes%20wrong [6.4.2025.] Splunk.com: https://www.splunk.com/en_us/blog/learn/fault-tolerance.html [6.4.2025.] ScaleComputing.com: https://www.scalecomputing.com/resources/fault-tolerance-vs-high-availability [6.4.2025.] Netflix Tech Blog: https://netflixtechblog.com/fault-tolerance-in-a-high-volume-distributed-system-91ab4faae74a [6.4.2025.] Xavor, Principles of Fault Tolerance in Microservices: https://www.xavor.com/blog/principles-of-fault-tolerance-in-microservices/ [6.4.2025.] GeeksforGeeks, Fault Tolerance in Distributed systems: https://www.geeksforgeeks.org/fault-tolerance-in-distributed-system/ [7.4.2025.] Building Resilient Systems with API Retry Mechanisms in Node.js & Express: https://medium.com/@devharshgupta.com/building-resilient-systems-with-api-retry-mechanisms-in-node-js-a-guide-to-handling-failure-d6d9021b172a [7.4.2025.] Building Robust APIs: Best Practices for Fault Tolerance and Resilience: https://sunandip.medium.com/building-robust-apis-best-practices-for-fault-tolerance-and-resilience-f47058bf711d [7.4.2025.] Blogs Mulesoft, Application Network Fault Tolerance: https://blogs.mulesoft.com/dev-guides/application-network-fault-tolerance/ [7.4.2025] Designing Fault-Tolerant APIs in Syncloop: https://www.syncloop.com/blogs/designing-faulttolerant-apis-in-syncloop.html [7.4.2025.] GeeksforGreeks - Retry Pattern in Microservices: https://www.geeksforgeeks.org/retry-pattern-in-microservices/ [8.4.2025] What is Circuit Breaker Pattern in Microservices? - GeeksforGeeks: https://www.geeksforgeeks.org/what-is-circuit-breaker-pattern-in-microservices/ [8.4.2025] Using Resilience4j in Spring Boot: A Comprehensive Guide - GUVI: https://www.guvi.com/blog/using-resilience4j-in-spring-boot/ [10.4.2025.] AWS: What is Service Mesh? https://aws.amazon.com/what-is/service-mesh/ [10.4.2025.] Scalability and Fault Tolerance in Microservices using Apache Kafka, by Platform Engineers: https://medium.com/@platform.engineers/scalability-and-fault-tolerance-in-microservices-using-apache-kafka-650ca42e95ad [10.4.2025.] "Building a Fault-Tolerant Architecture for High-Volume APIs" Tech Bite was brought to you by Ahmedin Hasanović, Junior Software Engineer at Atlantbh. (more…)

Ready to Achieve More?

We’ll help you reach your goals quickly with an easy and straightforward process to kick off our collaboration. Here’s what happens next.

STEP 1

Discovery Call

Let’s chat to understand your company, project needs, and answer any questions along the way.

STEP 2

Free Consultation

Work closely with our experts to explore the right solutions for your business.

STEP 3

Collaboration Proposal

We'll recommend the best strategy for your goals, ensuring you get the most from our expertise.

STEP 4

30-Day Cancellation
Policy Contract

Spoiler: It’s Never Been Used

Enjoy peace of mind while we deliver excellence from day one—our track record speaks for itself.

Services you're interested in (Optional)