DESIGN PATTERN in CLOUD COMPUTING #2
Cloud Design Patterns
Welcome to the second article, ‘Design patterns in cloud computing’. Here we are going to study more cloud design patterns that are going to be helpful in the efficient development of cloud applications.
05. Pipes & Filters Pattern
Imagine you have an application that needs to process data from different sources, apply various transformations, and then pass the results to the core business logic of the application. Traditionally, you might implement this as a single monolithic module, where all processing tasks are tightly coupled within the same codebase. However, this approach makes it challenging to refactor, optimize, or reuse code, especially when similar tasks are required elsewhere in the application.
The Pipes and Filters pattern offers a solution to these challenges. Instead of a monolithic approach, you decompose the processing into a series of discrete components called filters. Each filter performs a single task and communicates with the others using standardized data formats. These filters can then be connected together into a pipeline, creating a flexible and modular architecture.
With Pipes and Filters, you break down complex processing into small, independent tasks. Each filter focuses on a specific operation, making it easier to understand and manage the overall system.
Filters communicate with each other using standardized data formats. This consistency simplifies integration and allows for the easy addition or removal of filters as processing requirements change. You can assemble filters into pipelines according to your application’s needs. This flexibility allows you to rearrange processing steps, add new filters, or remove existing ones without rewriting the entire codebase.
When should you use the Pipes and Filters pattern?
- Use it when your application’s processing tasks can be decomposed into discrete, independent steps.
- When the scalability requirements of various processing steps differ.
- When you need the flexibility to change the order of processing steps or add/remove steps.
- When distributing processing across different servers or environments, it is beneficial.
- When you require a reliable solution that can handle failures gracefully.
However, this pattern may not be suitable for situations where processing steps are tightly coupled or when excessive context or state information is needed for each step. In such cases, a different architectural approach may be more appropriate.
This design pattern can be used with load balancing. “Pipes & Filters Pattern with Load Balancing” is a cloud design pattern used to process data in a distributed and scalable way. It’s particularly useful when you have an application that can be broken down into multiple steps, and each step may have different needs in terms of scalability, flexibility, and processing power.
Imagine you have a big task to accomplish, like analyzing a lot of data. Instead of doing it all in one go, you break it down into smaller, manageable steps. For example, you might first filter the data to remove irrelevant information, then perform some calculations, and finally generate a report. This is what we call ‘decomposing the application’.
Each of these steps might require different levels of resources. Filtering the data might not need much computing power, while the calculations could be very resource-intensive. Some steps might need to handle a lot of work quickly, while others can take their time.
This pattern allows you to be flexible in how you process the data. You can change or replace any step without affecting the others. For example, you might decide to use a different algorithm for calculations, and it won’t disrupt the filtering or reporting steps. Instead of running all these steps on a single computer, you can distribute them across multiple machines or cloud servers. This means you can handle a large amount of data efficiently by spreading the work.
To make sure no single step gets overwhelmed with too much work, you use load balancing. It’s like having a traffic cop direct cars at an intersection. The load balancer ensures that each step gets just the right amount of data to process, preventing bottlenecks.
Before we move on to the next design pattern, let’s have a clear idea about this ‘load balancing’ concept.
Load Balancing
Imagine you have a group of workers, and some tasks are easy, while others are hard. Load balancing is like making sure everyone gets a fair amount of work. Some workers might be really busy, while others have lighter loads, but we want to keep things balanced. The idea is to use your workers efficiently. You don’t want anyone sitting around doing nothing, and you don’t want anyone working so hard that they can’t keep up.
By distributing the work evenly, you make sure the team gets as much done as possible. It’s like making sure all your workers are productive and that your team’s overall performance is great.
You also want to prevent any one worker from getting overloaded with too much work. That can slow everything down. So, load balancing keeps an eye on the work and makes sure no one is overwhelmed.
There are a couple of ways to do this:
- Task Queues: Think of it like a to-do list. You have a list of tasks, and each worker takes on the next task when they’re ready. It’s like having a bunch of workers in an assembly line, and they each grab the next item to work on.
- Work Stealing: Here, each worker has their own list of tasks. When one worker finishes their work, they can “steal” a task from another worker who has more tasks than they can handle. It’s a bit like sharing the load among the team.
06. Sharding Pattern
Before we understand the pattern, let’s have an idea of what sharding is. This is a technology that relates to storing data in databases.
What is database sharding?
Database sharding is a technique used to improve the performance and scalability of large databases in cloud-based systems. Instead of storing all data in a single database, sharding divides it into smaller, more manageable pieces called shards. Each shard contains a subset of the data. This approach distributes the database workload across multiple servers or nodes, reducing the strain on any one server and allowing the system to handle a higher volume of data and requests efficiently. Sharding is like breaking a big puzzle into smaller pieces, making it easier to put together and significantly enhancing a database’s speed and capacity.
When dealing with massive data volumes, traditional database systems often struggle to meet the demands of modern applications. This is where sharding comes to the rescue.
Let’s understand this with real-world example. In the context of an expanding e-commerce platform, product data can be sharded by categories, customer data by geographical regions, and orders by date ranges. Each shard functions as a self-contained mini-database, enabling quicker and more efficient retrieval of information for improved user experiences.
Why this sharding pattern is important?
- Enhanced Scalability: Sharding distributes the data across multiple shards, allowing the system to grow horizontally. As your data volume increases, you can simply add more shards, ensuring scalability without compromising performance.
- Optimized Resource Utilization: By distributing data and computation across multiple shards, each shard handles a fraction of the workload. This results in more efficient resource utilization, making it possible to serve a larger user base with the same infrastructure.
- Improved Performance: Sharding reduces the load on individual shards, enabling faster data retrieval and processing. This translates into reduced query times and better overall system performance.
Sharding employs various strategies to divide data effectively.
- Lookup Strategy: In this approach, data requests are mapped to specific shards using a shard key. The shard key could be based on attributes like user IDs, regions, or any relevant criteria. This ensures that related data is stored in the same shard, optimizing query performance.
2. Range Strategy: Range sharding groups data items with similar characteristics into the same shard. For example, all user records with last names starting with ‘A’ to ‘F’ could be stored in one shard, while ‘G’ to ‘L’ goes to another. This approach simplifies data organization and retrieval.
3. Hash Strategy: Hashing data attributes determines the shard where data will reside. Each data item is hashed, and the resulting hash value determines the shard it belongs to. This strategy evenly distributes data across shards, ensuring a balanced workload.
07. Valet Key Pattern
In the digital world, this pattern is used when you have important data stored online, and you want others to access it, but you want to control what they can do. So, you give them a “valet key” that only lets them do specific things, like read or write data, and only for a limited time.
This is handy because it saves your computer from working too hard, and it’s safer because you’re not giving full access to everyone. You can even cancel the valet key if you need to stop them from accessing your stuff.
Key considerations include managing the token’s validity and access level, controlling user behavior, validating uploaded data, and auditing operations. The valet key can be invalidated by the application, and its scope can range from entire data tables to specific items or containers, depending on the data store’s capabilities.
This pattern is valuable when you want to minimize resource consumption, optimize performance, and reduce operational costs.
It’s particularly useful for scenarios involving frequent data uploads or downloads, limited compute resources, or data stored remotely. However, it may not be suitable if the application requires extensive data processing before storage, strict audit trails, or size limitations on data transfers.
08. Gate-Keeper Pattern
The Gatekeeper Pattern is a security design strategy that works like a protective barrier for applications and services.
In the digital world, applications, and services often have to be based on customer requests. If bad actors break into the system, they can exploit the security system and potentially compromise data and services.
Gatekeeper pattern places a custom gatekeeper instance between the client and the application. This gatekeeper acts as a security checkpoint, carefully examining incoming requests. If the request does not meet the security standards, it is rejected, and the gateway controller blocks the request from reaching the application or service. These additional layers increase security and reduce the risk of malicious access.
The main features of this model are:
Controlled Authentication: All requests must be carefully checked by the gate controller to allow strict rules to pass.
Limited Risk Exposure: Unlike the main application, the gateway controller has no access to credentials or keys. Even if the gateway controller is compromised, the attacker cannot directly access critical services or data.
Appropriate Security: Gatekeepers operate with limited permissions, while critical applications are up and running with the permissions needed to access storage. This isolation ensures that the gateway controller cannot directly affect the application even if it is compromised.
When to use this design pattern:
This pattern is suitable for protection against attacks on applications that process sensitive data or perform power-critical operations. In a distributed application, it is necessary to separate the authentication application from the main function for convenience and security.
While the Gatekeeper pattern adds an extra layer of security, it will slightly impact performance due to the extra work.
09. Circuit Breaker Pattern
Imagine you are calling a friend. Sometimes calls drop or take a long time to connect. These interactions can be frustrating. Similar problems may arise in the software field, especially in distributed systems such as cloud computing. Your application will try to connect to remote services, and sometimes these connections fail for various reasons, such as slow network connections, delays, or temporary service unavailability.
Some of these issues resolve themselves quickly, but others may take longer. Constantly returning to create a connection that will not only waste valuable resources, but will also affect the performance of your application. It’s like repeating a phone number you know you can’t connect to. This circuit breaker pattern brings a solution to this scenario.
How It Works?
Circuit breaker pattern is a design pattern that work similar to the functionality of an electrical circuit breaker in your home. It is designed to increase the power and performance of applications, especially when dealing with remote services. It works like this:
Closed state:
Like the circuit breaker trips in the “off” state, the circuit breaker pattern first allows the request to take the action you want to perform. It keeps track of all faults that occur during this time.
Open State:
Circuit Breaker pattern switches to “Open State” if it detects a fault that exceeds a certain threshold in a given time. In this case, it acts as a gatekeeper, instantly blocking any request. This quick response prevents your application from wasting time and resources on things that might fail.
Half-Open State:
After a predetermined time in the “Open” state, the circuit breaker pattern will switch to the “Half-Open” state. Here it carefully allows certain requests to be fulfilled and makes sure that they are fulfilled. If this test request is successful, it is assumed that the problem causing the malfunction has been resolved and the switch has returned to the “close” state, allowing normal operation. However, if one of these test requests fails, it will revert to “open” and initiate a new delay.
This “half-open” state is important to prevent your application from flooding the backend. It can only handle some requests when the service returns to normal. Flooding it with work during recovery could lead to timeouts or further failures.
When invoking an operation through a circuit breaker, your application must be prepared to handle exceptions that may be raised if the operation is unavailable. The handling of these exceptions will be application-specific and may include strategies like temporarily degrading functionality, invoking alternative operations, or informing users to try again later.
Not all exceptions are created equal. Some will fail more than others. Circuit breaker pattern can be designed to detect the type of exception and adjust their codes accordingly.
Logging:
A circuit breaker must be logged without request and can be requested appropriately. This logging helps administrators monitor the functionality of the protected operation and can serve as a valuable tool for diagnosing issues.
Testing Failed Operations:
Instead of relying solely on a timer to switch from the “open” to the “half-open” state, a circuit breaker can periodically ping the remote service to check if it has become available again. This can involve attempting to invoke the operation that previously failed or using a specialized health-check operation provided by the service.
Manual Override:
Consider providing a manual reset option for administrators to forcibly close a circuit breaker or place it in the “open” state. This can be valuable in situations where the recovery time for a failing operation is highly variable.
Replaying Failed Requests:
Consider recording details of failed requests in the “open” state and replaying them when the remote resource becomes available again. This can help minimize the impact of failures.
Inappropriate Timeouts on External Services:
Be aware that a circuit breaker may not fully protect applications from operations that fail in external services with lengthy timeouts. If the timeout is excessively long, many threads may be blocked before the circuit breaker detects the failure.
The Circuit Breaker Pattern is ideal for below situations.
Your application is dependent on remote services or resources that may experience temporary or long-term failure. Preventing services from overloading is important to maintain overall system stability and performance.
However, it may not be suitable for the following situations. Your application primarily accesses local private resources (e.g., in-memory data structures), as using a circuit breaker in such environments could add unnecessary overhead.
I hope you get a clear idea of these cloud design patterns. Let’s discuss some more design patterns in the next article.