REL04-BP01 Identify the kind of distributed systems you depend on
Distributed systems can be synchronous, asynchronous, or batch. Synchronous systems must process requests as quickly as possible and communicate with each other by making synchronous request and response calls using HTTP/S, REST, or remote procedure call (RPC) protocols. Asynchronous systems communicate with each other by exchanging data asynchronously through an intermediary service without coupling individual systems. Batch systems receive a large volume of input data, run automated data processes without human intervention, and generate output data.
Desired outcome: Design a workload that effectively interacts with synchronous, asynchronous, and batch dependencies.
Common anti-patterns:
-
Workload waits indefinitely for a response from its dependencies, which could lead to workload clients timing out, not knowing if their request has been received.
-
Workload uses a chain of dependent systems that call each other synchronously. This requires each system to be available and to successfully process a request before the whole chain can succeed, leading to potentially brittle behavior and overall availability.
-
Workload communicates with its dependencies asynchronously and rely on the concept of exactly-once guaranteed delivery of messages, when often it is still possible to receive duplicate messages.
-
Workload does not use proper batch scheduling tools and allows concurrent execution of the same batch job.
Benefits of establishing this best practice: It is common for a given workload to implement one or more style of communication between synchronous, asynchronous, and batch. This best practice helps you identify the different trade-offs associated with each style of communication to make your workload able to tolerate disruptions in any of its dependencies.
Level of risk exposed if this best practice is not established: High
Implementation guidance
The following sections contain both general and specific implementation guidance for each kind of dependency.
General guidance
-
Make sure that the performance and reliability service-level objectives (SLOs) that your dependencies offer meet the performance and reliability requirements of your workload.
-
Use AWS observability services
to monitor response times and error rates to make sure your dependency is providing service at the levels needed by your workload. -
Identify the potential challenges that your workload may face when communicating with its dependencies. Distributed systems come with a wide range of challenges
that might increase architectural complexity, operational burden, and cost. Common challenges include latency, network disruptions, data loss, scaling, and data replication lag. -
Implement robust error handling and logging to help you troubleshoot problems when your dependency experiences issues.
Synchronous dependency
In synchronous communications, your workload sends a request to its dependency and blocks the operation waiting for a response. When its dependency receives the request, it tries to handle it as soon as possible and sends a response back to your workload. A significant challenge with synchronous communication is that it causes temporal coupling, which requires your workload and its dependencies to be available at the same time. When your workload needs to communicate synchronously with its dependencies, consider the following guidance:
-
Your workload should not rely on multiple synchronous dependencies to perform a single function. This chain of dependencies increases overall brittleness because all dependencies in the pathway need to be available in order for the request to complete successfully.
-
When a dependency is unhealthy or unavailable, determine your error handling and retry strategies. Avoid using bimodal behavior. Bimodal behavior is when your workload exhibits different behavior under normal and failure modes. For more details on bimodal behavior, see REL11-BP05 Use static stability to prevent bimodal behavior.
-
Keep in mind that failing fast is better than making your workload wait. For instance, the AWS Lambda Developer Guide describes how to handle retries and failures when you invoke Lambda functions.
-
Set timeouts when your workload calls its dependency. This technique avoids waiting too long or waiting indefinitely for a response. For helpful discussion of this issue, see Tuning AWS Java SDK HTTP request settings for latency-aware Amazon DynamoDB applications
. -
Minimize the number of calls made from your workload to its dependency to fulfill a single request. Having chatty calls between them increases coupling and latency.
Asynchronous dependency
To temporally decouple your workload from its dependency, they should communicate asynchronously. Using an asynchronous approach, your workload can continue with any other processing without having to wait for its dependency, or chain of dependencies, to send a response.
When your workload needs to communicate asynchronously with its dependency, consider the following guidance:
-
Determine whether to use messaging or event streaming based on your use case and requirements. Messaging
allows your workload to communicate with its dependency by sending and receiving messages through a message broker. Event streaming allows your workload and its dependency to use a streaming service to publish and subscribe to events, delivered as continuous streams of data, that need to be processed as soon as possible.
-
Messaging and event streaming handle messages differently so you need to make trade-off decisions based on:
-
Message priority: message brokers can process high-priority messages ahead of normal messages. In event streaming, all messages have the same priority.
-
Message consumption: message brokers ensure that consumers receive the message. Event streaming consumers must keep track of the last message they have read.
-
Message ordering: with messaging, receiving messages in the exact order they are sent is not guaranteed unless you use a first-in-first-out (FIFO) approach. Event streaming always preserves the order in which the data was produced.
-
Message deletion: with messaging, the consumer must delete the message after processing it. The event streaming service appends the message to a stream and remains in there until the message's retention period expires. This deletion policy makes event streaming suitable for replaying messages.
-
-
Define how your workload knows when its dependency completes its work. For instance, when your workload invokes a Lambda function asynchronously, Lambda places the event in a queue and returns a success response without additional information. After processing is complete, the Lambda function can send the result to a destination, configurable based on success or failure.
-
Build your workload to handle duplicate messages by leveraging idempotency. Idempotency means that the results of your workload do not change even if your workload is generated more than once for the same message. It is important to point out that messaging
or streaming services will redeliver a message if a network failure occurs or if an acknowledgement has not been received. -
If your workload does not get a response from its dependency, it needs to resubmit the request. Consider limiting the number of retries to preserve your workload's CPU, memory, and network resources to handle other requests. The AWS Lambda documentation shows how to handle errors for asynchronous invocation.
-
Leverage suitable observability, debugging, and tracing tools to manage and operate your workload's asynchronous communication with its dependency. You can use Amazon CloudWatch
to monitor messaging and event streaming services. You can also instrument your workload with AWS X-Ray to quickly gain insights for troubleshooting problems.
Batch dependency
Batch systems take input data, initiate a series of jobs to process it, and produce some output data, without manual intervention. Depending on the data size, jobs could run from minutes to, in some cases, several days. When your workload communicates with its batch dependency, consider the following guidance:
-
Define the time window when your workload should run the batch job. Your workload can set up a recurrence pattern to invoke a batch system, for example, every hour or at the end of every month.
-
Determine the location of the data input and the processed data output. Choose a storage service, such as Amazon Simple Storage Services (Amazon S3)
, Amazon Elastic File System (Amazon EFS), and Amazon FSx for Lustre, that allows your workload to read and write files at scale. -
If your workload needs to invoke multiple batch jobs, you could leverage AWS Step Functions
to simplify the orchestration of batch jobs that run in AWS or on-premises. This sample project demonstrates orchestration of batch jobs using Step Functions, AWS Batch , and Lambda. -
Monitor batch jobs to look for abnormalities, such as a job taking longer than it should to complete. You could use tools like CloudWatch Container Insights to monitor AWS Batch environments and jobs. In this instance, your workload would stop the next job from beginning and inform the relevant staff of the exception.
Resources
Related documents:
-
The Amazon's Builder Library: Challenges with distributed systems
-
AWS Lambda Developer Guide: Error handling and automatic retries in AWS Lambda
-
Tuning AWS Java SDK HTTP request settings for latency-aware Amazon DynamoDB applications
-
Amazon Kinesis Data Streams Developer Guide: Handling Duplicate Records
-
Amazon Simple Queue Service Developer Guide: Available CloudWatch metrics for Amazon SQS
-
AWS Samples on GitHub: AWS Step functions Complex Orchestrator App
-
AWS Batch User Guide: AWS Batch CloudWatch Container Insights
Related videos:
Related tools: