What is Sharding and Partitioning in Database Management ?

Maximizing Efficiency with Sharding and Partitioning in Database Management.

Taranjit Kaur
Code Like A Girl

--

Partitioning and sharding are both techniques used in database management to divide and manage large datasets, but they are used in slightly different contexts.

If you have dedicated time to studying techniques for creating scalable database architecture, you’ve likely come across the terms “sharding” and “partitioning.” While these approaches may seem similar initially, they have distinct characteristics and serve different purposes. What sets these two concepts apart?

What is Sharding ?

Sharding is a database management technique used to improve the performance and scalability of large-scale distributed databases.

In a sharded database, data is horizontally partitioned into smaller, more manageable pieces called shards. Each shard is essentially a subset of the entire dataset, and it is stored on a separate server or database instance.

Why do we need Sharding?

The goal of sharding is to distribute the workload and queries across multiple servers, allowing the database system to handle a larger volume of data and a higher number of transactions.

This is particularly important for applications and services that experience rapid growth and need to scale horizontally to accommodate increased demand.

What are the key concepts in Sharding ?

  • Shards: As mentioned, shards are the individual partitions of data. Each shard typically contains a portion of the dataset and is stored on a separate server or node.
  • Shard Key: The shard key is a critical component in sharding. It is used to determine how data is distributed among the shards.
  • Distribution and Query Routing: When a query is made to the database, the system uses the shard key to determine which shard or shards need to be accessed to fulfill the query. This allows for parallel processing of queries across multiple shards.
  • Scalability: Sharding provides a way to scale horizontally by adding more servers or nodes to the system. As the data and workload increase, additional shards and servers can be added to distribute the load.
  • Fault Tolerance: Sharding can also enhance fault tolerance. If one server or shard fails, the remaining shards can continue to operate independently. Redundancy and replication can be employed to ensure data availability even in the event of a hardware failure.

What are the advantages of Sharding ?

  • Scalability: Sharding provides horizontal scalability, allowing the database to handle increased data and workload by adding more servers. This makes it well-suited for applications with rapidly growing datasets.
  • Improved Performance: Sharding enables parallel processing of queries across multiple servers, distributing the workload and improving overall system performance, especially for read and write operations.
  • High Availability: Sharding can enhance fault tolerance and high availability. If one shard or server fails, the remaining shards can continue to operate independently, minimizing the impact of failures.
  • Elasticity: Sharding allows for dynamic scaling, making it easier to adapt to changes in data volume and user load. New shards and servers can be added or removed as needed.
  • Isolation of Data: Sharding isolates data across different servers, reducing the likelihood of contention and hotspots. This can lead to better resource utilization and improved performance.
  • Cost Efficiency: Sharding can be cost-effective, especially in cloud environments, as you can scale resources based on actual demand. This contrasts with vertical scaling, where upgrading a single server can be more expensive.

What are the disadvantages of Sharding ?

  • Complexity: Sharding introduces complexity in terms of system design, implementation, and maintenance. Managing a sharded database requires careful consideration of the application’s requirements and potential challenges.
  • Data Distribution Challenges: Choosing an appropriate shard key is crucial. Poorly chosen shard keys can lead to uneven data distribution (hotspots), affecting performance and defeating the purpose of sharding.
  • Join Operations: Performing join operations across shards can be complex and may involve additional coordination. Some distributed databases struggle with efficient handling of complex queries involving multiple shards.
  • Data Consistency: Ensuring data consistency across multiple shards can be challenging. Implementing distributed transactions and maintaining ACID properties becomes more complex compared to a single-node database.
  • Migration and Resharding: Adding or removing shards (resharding) can be a complex and resource-intensive process. This operation requires careful planning to avoid downtime and data migration challenges.
  • Increased Latency: Inter-shard communication may introduce latency compared to a non-sharded system. The need to coordinate data across multiple servers can impact response times for certain queries.
  • Limited Support in Some Database Systems:Not all database systems natively support sharding. Implementing sharding might require using specialized databases or implementing custom sharding logic.
  • Security Challenges: Ensuring consistent and robust security measures across shards can be challenging. Access controls, encryption, and auditing need to be carefully managed in a sharded environment.

When to Shard a Table ?

Deciding when to shard a table depends on various factors related to the specific requirements and characteristics of your application. Here are some scenarios where sharding a table might be beneficial:

  • Large Data Volume: If your table is expected to grow to a size that could impact query performance or exceed the capacity of a single server, sharding may be considered.
  • High Query Load: If your application experiences a high volume of queries that cannot be efficiently handled by a single server, sharding can distribute the query load across multiple servers.
  • Horizontal Scalability Needs: When your application requires horizontal scalability to handle increased user load, data volume, or both. Sharding enables you to add more servers to the system as needed.
  • Uneven Data Distribution: If your dataset exhibits uneven access patterns or if certain data ranges are accessed more frequently than others, sharding can help distribute the workload more evenly.
  • Performance Bottlenecks: If you identify specific performance bottlenecks in your database system, such as slow query response times or high write contention, sharding can be a solution to address these issues.
  • Geographic Distribution: When you need to distribute data across different geographic regions or data centers to reduce latency and improve the user experience.
  • Cost Considerations: If vertical scaling (upgrading a single server) becomes prohibitively expensive, and you want a more cost-effective way to scale resources based on demand.
  • Data Isolation Requirements: If there are requirements for isolating certain types of data or user groups to specific servers for security or regulatory compliance reasons.
  • Data Lifecycle Management: If your application requires efficient management of data with different lifecycles (e.g., archiving old data), sharding can aid in organizing and managing data partitions.
  • Future Growth Considerations: If you anticipate significant future growth in both data volume and user load, sharding can be implemented as a proactive measure to handle the increasing demands.

When not to shard a Table ?

While sharding can offer significant benefits in terms of scalability and performance for certain applications, it might not be the best solution for every scenario. Here are some situations in which you might consider not sharding a table:

  • Small or Stable Datasets: If your dataset is relatively small and not expected to grow significantly, or if the application’s workload is stable and well within the capacity of a single server, sharding may introduce unnecessary complexity.
  • Simplicity and Maintainability: If the simplicity of managing a single, non-sharded database instance meets the current and foreseeable requirements of your application, introducing sharding might be an overcomplication.
  • Low Query Load or Performance Requirements: If your application experiences low query loads or if the existing database infrastructure meets performance requirements without the need for sharding, introducing sharding may not be justified.
  • Cost Considerations: If the cost of vertically scaling (upgrading hardware) is reasonable and meets the current needs of your application, sharding might not be necessary. Sharding introduces complexity and may not be cost-effective for all scenarios.
  • Simple Access Patterns: If your application has straightforward access patterns and queries that do not put a significant load on the database, sharding may be unnecessary. Sharding is often more beneficial in scenarios with complex access patterns or high query loads.
  • Limited Resources for Implementation: If your team has limited resources for implementing and managing a sharded environment, and the current infrastructure is sufficient for meeting application requirements, it might be more practical to avoid sharding.
  • Risk of Hotspots or Uneven Data Distribution: If choosing a suitable shard key is challenging, and there’s a risk of creating hotspots or uneven data distribution, sharding might not be the best solution. Poorly chosen shard keys can impact performance.
  • Transactional Consistency Requirements: If your application requires strong transactional consistency across multiple tables or if complex transactions involving multiple shards are common, sharding may introduce challenges in maintaining consistency.
  • Frequent Schema Changes: If your application undergoes frequent schema changes, managing these changes across multiple shards can be complex. Non-sharded databases may be easier to modify and maintain in such scenarios.
  • Existing Investments in Vertical Scaling: If your organization has made significant investments in vertical scaling (upgrading existing hardware) and it meets current requirements, there may be less immediate incentive to adopt sharding.

What is Partitioning ?

Partitioning in a database refers to the process of dividing a large database table into smaller, more manageable pieces called partitions. Each partition is essentially a subset of the table’s data, and these partitions can be stored and managed independently.

Partitioning is a database management technique used to improve performance, manageability, and maintenance operations.

Why do we need Partitioining?

Partitioning in a database offers several advantages, and its use is driven by specific needs and considerations in the context of database management. Here are some key reasons why partitioning is employed in databases:

  • Improved Query Performance: Partitioning allows the database system to operate on smaller subsets of data, reducing the amount of data that needs to be scanned for a particular query. This can lead to improved query performance, especially for operations that involve range queries or access a specific partition.
  • Efficient Data Maintenance: Maintenance operations, such as backup, restore, and index rebuilds, can be more efficient when performed on individual partitions rather than the entire table. This is particularly beneficial for large tables with historical data where only a subset of partitions may need to be processed during maintenance tasks.
  • Scalability and Distribution: Partitioning is a key strategy for achieving scalability. By distributing data across multiple partitions or servers, it becomes easier to scale the database system to handle larger datasets and increased query loads.
  • Historical Data Management: For applications that involve historical data, partitioning based on a timestamp allows for efficient management of old data. Archiving or purging old data becomes simpler, and queries can be optimized to target specific time ranges.
  • Reduced Locking and Contention: In some cases, partitioning can reduce locking and contention issues. By isolating data into partitions, transactions that only involve a specific partition are less likely to conflict with transactions on other partitions.
  • Optimized Indexing: Partitioning can be used to optimize indexing strategies. Indexes can be created on a per-partition basis, allowing for more efficient index maintenance and improved query performance.
  • Geographic Distribution: In distributed database systems, partitioning can be used to distribute data across different geographic locations or data centers. This helps reduce latency and improve the user experience for geographically distributed applications.
  • Parallel Processing: With partitioning, queries and transactions can be parallelized across multiple partitions or servers. This parallel processing capability is valuable for handling large volumes of data and improving overall system performance.
  • Cost-Effective Scaling: For systems hosted in cloud environments or environments with limited resources, partitioning can provide a cost-effective way to scale resources based on demand. Adding or removing partitions can be more economical than vertical scaling (upgrading a single server).
  • Security and Access Control: Partitioning can be used to enforce security and access control policies. For example, sensitive data can be stored in a separate partition with more restrictive access controls.

What are the key concepts in Partitioning ?

Here are some key aspects of partitioning in a database:

  • Partition Key: A partition key is a column or set of columns in the table based on which the data is divided into partitions. The choice of a partition key is crucial and should be based on the access patterns and requirements of the queries.
  • Types of Partitioning:

Range Partitioning: Data is divided into partitions based on a specified range of values.

List Partitioning: Data is partitioned based on discrete values or ranges of values.

Hash Partitioning: Data is distributed across partitions based on a hash function applied to a chosen column.

Composite Partitioning: Combining multiple partitioning methods.

  • Benefits of Partitioning:

Improved Performance: Partitioning can enhance query performance by allowing the database system to operate on smaller, more specific sets of data.

Efficient Data Maintenance: Operations such as backup, restore, and index rebuilds can be performed on individual partitions, making maintenance tasks more efficient.

Scalability: Partitioning can be used as a strategy for horizontal scalability, especially in scenarios where adding more servers or nodes is not feasible.

What are the advantages of Partitioining ?

Partitioning in a database provides several advantages that contribute to improved performance, manageability, and scalability. Here are some key advantages of partitioning:

  • Enhanced Query Performance: Partitioning allows for more efficient query performance by enabling the database system to scan and process smaller subsets of data. Queries that involve range conditions or access specific partitions can be significantly faster.
  • Efficient Data Maintenance: Maintenance operations, such as backups, restores, and index rebuilds, can be performed more efficiently at the partition level. This reduces the impact on the entire table, making maintenance tasks quicker and less resource-intensive.
  • Scalability: Partitioning is a key strategy for achieving scalability. By distributing data across multiple partitions or servers, the database system can handle increased data volumes and query loads, providing a scalable solution.
  • Historical Data Management: Partitioning based on a timestamp or date allows for efficient management of historical data. Old data can be archived or purged by dealing with specific partitions, simplifying the maintenance of large historical datasets.
  • Reduced Locking and Contention: Partitioning can help reduce locking and contention issues, especially in scenarios where transactions are limited to specific partitions. Transactions that involve different partitions are less likely to conflict with each other.
  • Optimized Indexing: Partitioning allows for more efficient indexing strategies. Indexes can be created on a per-partition basis, improving index maintenance and query performance. This is particularly beneficial for large tables with diverse access patterns.
  • Parallel Processing: Partitioning facilitates parallel processing of queries and transactions across multiple partitions or servers. This parallelism enhances overall system performance and enables more effective utilization of resources.
  • Geographic Distribution: In distributed database systems, partitioning can be used to distribute data across different geographic locations or data centers. This helps reduce latency and improve the user experience for geographically distributed applications.
  • Cost-Effective Scaling: For systems hosted in cloud environments or environments with limited resources, partitioning provides a cost-effective way to scale resources based on demand. Adding or removing partitions can be more economical than vertical scaling (upgrading a single server).
  • Improved Security and Access Control: Partitioning can be utilized to enforce security and access control policies. For example, sensitive data can be isolated in specific partitions with more restrictive access controls, enhancing data security.
  • Data Isolation and Hotspot Prevention: Partitioning helps in isolating data, preventing hotspots, and distributing the workload evenly. This contributes to better resource utilization and prevents specific partitions from becoming performance bottlenecks.
  • Optimized Storage Management: Partitions can be stored on different storage devices or filegroups, allowing for optimized storage management. Frequently accessed or critical partitions can be placed on high-performance storage.

What are the disadvantages of Partitioning ?

While partitioning in a database provides several advantages, it also comes with certain disadvantages and challenges. Here are some key disadvantages of partitioning:

  • Complexity: Implementing and managing a partitioned database system can be complex. The additional complexity arises in areas such as system design, query optimization, and data maintenance.
  • Choosing an Appropriate Partition Key: Selecting a suitable partition key is critical, and choosing the wrong key can lead to uneven data distribution (hotspots) or negatively impact query performance. Identifying the right partition key requires careful consideration of access patterns and query requirements.
  • Join Operations: Performing join operations across partitions can be challenging. Complex queries involving joins between tables residing in different partitions may require additional coordination and can be less efficient.
  • Data Consistency Across Partitions: Ensuring consistent data across multiple partitions can be complex, especially in distributed database systems. Implementing distributed transactions and maintaining ACID properties may become more challenging.
  • Migration and Resharding: Adding or removing partitions (resharding) can be a resource-intensive process. The migration of data between partitions may require downtime and careful planning to avoid disruptions.
  • Increased Latency: Inter-partition communication can introduce latency compared to a non-partitioned system. Coordination between partitions for certain queries or transactions may impact response times.
  • Limited Support in Some Database Systems: Not all database systems natively support partitioning. Implementing partitioning may require custom logic or the use of specific databases that provide native support.
  • Security Challenges: Ensuring consistent and robust security measures across partitions can be challenging. Access controls, encryption, and auditing must be carefully managed in a partitioned environment.
  • Storage Overhead: Partitioning can introduce additional storage overhead. Each partition may have its own indexes, metadata, and data files, leading to increased storage requirements compared to a non-partitioned system.
  • Complexity in Query Optimization: Optimizing queries for a partitioned system can be more complex. Query planners need to consider the distribution of data across partitions, and suboptimal query plans may be generated if not properly tuned.
  • Limited Benefit for Small Datasets: The benefits of partitioning may not be significant for small datasets or databases with relatively low query loads. In such cases, the overhead introduced by partitioning might outweigh the advantages.
  • Vendor-Specific Implementation: The implementation of partitioning may vary between database vendors. Migrating to a different database system or changing the partitioning strategy may require substantial effort and coordination.

When to Partition a Table ?

Partitioning a table is a decision that should be based on careful consideration of the specific requirements and characteristics of your application and database. Here are some scenarios when partitioning a table might be beneficial:

  • Large Datasets: If your table contains a large amount of data that is growing over time, partitioning can help manage and query the data more efficiently. It is particularly useful when the dataset becomes challenging to handle within a single database server.
  • Time-Based Data: For tables that store time-based data, such as logs, events, or historical records, partitioning based on a timestamp can be beneficial. This allows for efficient querying of data within specific time ranges and facilitates data archiving.
  • Frequent Data Archiving or Purging: If your application requires frequent archiving or purging of old data, partitioning can simplify these operations. You can perform these tasks on specific partitions, making data management more efficient.
  • Range Queries: When your application frequently performs range queries on a specific column, such as date ranges or numeric ranges, partitioning based on that column can significantly improve query performance.
  • Access Patterns: Analyze the access patterns of your application. If certain subsets of data are queried more frequently than others, partitioning can help isolate and optimize the access to these subsets.
  • Improved Query Performance: If your application is experiencing performance issues with certain types of queries, partitioning can be considered as a strategy to optimize query performance, especially for large tables.
  • Horizontal Scalability Needs: When your application is expected to grow, and you need a scalable solution to handle increased data volume and query loads. Partitioning allows for horizontal scalability by distributing data across multiple servers.
  • Data Distribution Across Geographic Locations: In scenarios where your application is distributed across different geographic locations or data centers, partitioning can be used to organize and manage data in a way that reduces latency for local queries.
  • Maintenance Operations: If your application involves frequent maintenance operations, such as backups or index rebuilds, and you want to minimize the impact of these operations on the entire table, partitioning can be advantageous.
  • Hotspot Prevention: When there is a risk of uneven data distribution (hotspots) due to specific access patterns or data characteristics, partitioning can help distribute the workload more evenly.
  • Join Operations Across Tables: If your application frequently performs join operations across large tables, partitioning can be considered to optimize these operations by narrowing down the search space.
  • Compliance and Security Requirements: If your application has specific compliance or security requirements, partitioning can be used to isolate and apply different security measures to specific partitions, ensuring that sensitive data is appropriately protected.

When not to Partition ?

While partitioning can offer various benefits in terms of improved performance, manageability, and scalability, it may not be necessary or suitable for every database or application. Here are some scenarios when you might consider not partitioning a table:

  • Small Datasets: If your dataset is relatively small and fits comfortably within the capacity of a single database server, the overhead introduced by partitioning may not be justified. Partitioning is generally more beneficial for large datasets.
  • Simple Access Patterns: If your application has straightforward access patterns, and queries do not involve complex range queries, partitioning might not provide significant performance advantages. The benefits of partitioning become more apparent with certain types of access patterns.
  • Limited Query Load: If your application has a low query load, and the existing database infrastructure meets performance requirements without the need for partitioning, the additional complexity introduced by partitioning may not be necessary.
  • Static or Stable Datasets: If your dataset is relatively static or stable over time, and there are no significant changes in data volume or access patterns, the benefits of dynamic data management introduced by partitioning may not be fully realized.
  • Cost Considerations: If the cost of vertically scaling (upgrading hardware) is reasonable and meets the current and foreseeable needs of your application, partitioning might not be a cost-effective solution. Partitioning can introduce complexity and additional resource requirements.
  • Frequent Schema Changes: If your application undergoes frequent schema changes, adding or removing partitions and managing these changes across multiple partitions can be complex. Non-partitioned databases may be easier to modify and maintain in such scenarios.
  • Limited Resources for Implementation: If your team has limited resources for implementing and managing a partitioned environment, and the current infrastructure meets application requirements, it might be more practical to avoid partitioning.
  • Limited Benefits for Certain Workloads: For certain workloads that do not involve frequent range queries, historical data management, or complex access patterns, the benefits of partitioning may be limited, and the additional complexity may not be justified.
  • No Geographic Distribution Requirements: If your application does not require geographic distribution of data across different locations or data centers, the advantages of partitioning for optimizing local queries may not be relevant.
  • Straightforward Data Maintenance: If your application does not involve frequent data maintenance operations, and backup, restore, and index rebuild tasks can be efficiently performed on the entire table, the benefits of partitioning for maintenance may be minimal.
  • Limited Historical Data: If your application does not deal with significant amounts of historical data and does not require efficient management of data over time, the advantages of partitioning for historical data archiving may not be relevant.
  • Single-Node Deployment: If your database is deployed on a single node or server and there are no plans for horizontal scaling, partitioning may not be necessary. Vertical scaling (upgrading the existing server) might be a more straightforward solution in such cases.

Scenario

Let us build a theoretical database for a retail store.

When our database is deployed in production, it gains significant traction at 200 writes per second (WPS).

Imagine that our product suddenly becomes viral, resulting in a fivefold increase in load, which means we now have to handle 1000 WPS. We scale our database vertically to handle the increased load to achieve this.

Assume that we have achieved product-market fit (PMF) and are experiencing even greater traction, requiring us to handle 1500 WPS.

However, upon checking the database console, we discovered that further vertical scaling is impossible. This is the point where horizontal scaling becomes relevant.

Retail Store Database

Scaling the database horizontally

We know one database server can handle 1000 WPS, but we need to handle 1500 WPS, so we split the data into half and split it across two databases such that each database owns half of the data and all the writes for that data goes to that particular instance.

This way, each server will get 750 WPS, which it can easily handle, and owns 50% of the data. Thus by adding more database servers, we handled 1500 WPS (more than a single machine could handle).

Each database server in the above architecture is called a Shard, while the data is said to be partitioned. Overall, a database is sharded, and the data is partitioned.

Partitioning involves dividing a database table into multiple sets or groups. There are two main types of partitioning:

  1. Horizontal partitioning: This method divides the database based on rows, splitting the table into separate sections.
Horizontal partitioning(Image borrowed from Microsoft.com)
  1. Vertical partitioning: This approach involves creating distinct partitions for the columns of the database.
Vertical Partitioning(Image borrowed from Microsoft.com)

Comparison of database sharding and partitioning:

Database sharding and partitioning are both strategies used to divide and manage large datasets in a database, but they have key differences in their implementation and goals. Here’s a comparison between database sharding and partitioning:

Database Sharding:

  • Sharding: Sharding involves horizontally partitioning a database across multiple servers or nodes, where each server is responsible for a subset of the data. Each shard operates independently, and the combination of all shards forms the complete dataset.
  • Goal: The primary goal of sharding is to achieve horizontal scalability. Sharding allows a database to distribute the load and data across multiple servers, enabling it to handle increased data volume and query loads.
  • Data Distribution: Sharding distributes data across different physical or logical servers. Each shard is a separate database with its own subset of the dataset.
  • Use Cases: Sharding is commonly used in scenarios with extremely large datasets or high query loads. It is prevalent in applications with rapidly growing data that cannot be efficiently managed on a single server.
  • Partition Key: Sharding requires the selection of a shard key, which determines how data is distributed across shards. The choice of a shard key is crucial to avoid hotspots and uneven data distribution.
  • Complexity: Sharding introduces higher complexity compared to partitioning, as it involves managing multiple independent databases, handling inter-shard communication, and addressing potential challenges with data consistency.
  • Maintenance: Maintenance operations, such as backup, restore, and data migration, can be more challenging in a sharded environment. Careful planning is required to avoid disruptions during shard management.
  • Example: E-commerce Platform: Sharding might be used in an e-commerce platform to distribute customer data based on geographic regions, where each shard handles customers from a specific region.

Database Partitioning:

  • Partitioning: Partitioning involves dividing a single database table into smaller, more manageable pieces called partitions. Each partition is a subset of the table’s data and can be stored and managed independently.
  • Goal: The primary goals of partitioning are to improve query performance, enhance data maintenance operations, and provide a more manageable structure for large tables.
  • Data Distribution: Partitioning occurs within a single table, and each partition contains a specific range or subset of the data based on a chosen partition key.
  • Use Cases: Historical Data: Partitioning is commonly used in scenarios involving historical data, logs, or time-series data. It is beneficial for efficient data archiving, purging, and querying based on time ranges.
  • Partition Key: Partitioning relies on a partition key, which is a column or set of columns in the table. The choice of the partition key depends on the access patterns and requirements of the queries.
  • Complexity: Partitioning introduces a moderate level of complexity, depending on the chosen partitioning strategy. However, it is generally less complex than sharding.
  • Maintenance: Maintenance operations, such as backup and index rebuilds, can be more efficient in a partitioned environment. Operations can be performed on individual partitions without affecting the entire table.
  • Example:Financial Application: Partitioning might be used in a financial application to partition a large transactions table based on the date, allowing for efficient querying and maintenance of recent and historical transaction data.

Commonalities:

  • Scalability: Both sharding and partitioning contribute to scalability, allowing databases to handle increased data volume and query loads.
  • Performance: Both strategies aim to improve performance by optimizing queries and reducing the amount of data that needs to be scanned for a particular operation.
  • Data Management: Both sharding and partitioning help in efficient data management, whether it’s organizing historical data, optimizing index strategies, or distributing data across servers.
  • Key Selection: Both sharding and partitioning involve the selection of keys (shard key or partition key) based on the application’s requirements.
  • Use of Multiple Servers: Both strategies involve the use of multiple servers or nodes to distribute and manage data.

Conclusion

In summary, while both sharding and partitioning share some common goals, their primary focus and implementation differ. Sharding primarily target horizontal scalability across multiple servers, while partitioning focuses on improving performance and manageability within a single database server. The choice between sharding and partitioning depends on the specific needs, growth patterns, and characteristics of the application.

Thanks for the read. Do clap👏 and follow if you find it useful😊.

“Keep learning and keep sharing knowledge.”

--

--