‫Welcome back.
‫In the previous lecture, we finished addressing all the functional requirements for the Scalable Discussion
‫Forum.
‫As a quick reminder, we delegated the task of creating and maintaining the user data to the user service,
‫the creation and storing of posts and comments to the posts and comments service, the voting to the
‫voting service and the efficient ranking of posts by popularity in the last 24 hours to the ranking
‫service.
‫However, this design doesn't address any scalability, performance or really any non functional requirement
‫that we committed to.
‫So let's go ahead and address all of them one by one.
‫First, let's start with scalability.
‫As the first step, we can place a load balancer in front of the Web app service and each one of the
‫other services.
‫This will help us scale out our system, depending on the traffic to our system and each service independently.
‫Then we can place an API gateway to decouple the frontend code from the system's internal structure.
‫This will allow us to make changes internally and increase our organizational scalability.
‫On the posts and comments database side.
‫As we store more posts and comments, our database instance may run out of space.
‫It also may not be able to handle the traffic from so many users.
‫So the solution will be sharding our posts and comments collection across different database charts.
‫This way we can scale to as many posts or comments as we want to by adding more instances and distributing
‫our data across all those instances.
‫Now let's think about how we can chart our data.
‫When it comes to posts, the solution is very simple.
‫We can chart the posts by applying a hash function on the post ID, which should evenly distribute the
‫posts across different charts.
‫On the other hand, when it comes to charting comments, we need to think about it a bit harder.
‫If we simply start by comment ID using a hash function, we will evenly distribute the comments among
‫different charts.
‫However, that means that comments from the same post will end up on completely different computers
‫and getting them to the user will result in an inefficient broadcast operation to all our database charts.
‫On the other hand, if we chart the comments by post ID, we will have all the comments for the same
‫post on the same chart.
‫This would make getting the comments for a given post a lot faster.
‫The problem with this approach though, is if one popular post grows to so many comments that they can't
‫fit on a single shard, we wont be able to split them.
‫It will also likely become a performance bottleneck because too many operations will go to the same
‫database shard.
‫So one way we can solve this is by charting on a compound index of a post ID and the comment ID.
‫And instead of using a hash function will use a range sharding strategy, which many NoSQL databases
‫support.
‫We can think of a compound index as a separate data structure that sorts our data first by the first
‫key, which is the post ID, and then by the second key, which is the common ID.
‫This way, using your range sharding strategy, the database splits the data into ranges and distributes
‫them across different charts.
‫With this approach, there is a high likelihood that comments from the same post will end up on the
‫same shard.
‫However, if one of the posts gets too large in terms of number of comments, the database can still
‫split those ranges and put comments of the same post on different shards.
‫This way we get a good balance between scalability and performance.
‫Since each request for a list of consecutive comments for a given post will likely result in a call
‫to one shard or maybe two shards at the worst case.
‫So now that we addressed the scalability requirements, let's talk about performance.
‫One potential performance bottleneck is our image blob store.
‫Every time a user loads a post of a news feed with the most popular posts, we need to load images from
‫our system.
‫Even if those images aren't very big, they are still much bigger than text.
‫So loading those images on the browser can definitely add a lot of latency.
‫So to solve this problem, we can utilize a global network of seed and servers.
‫By using a pull model with a very long title.
‫Images of popular posts will be distributed among those seed and edge servers, which are located very
‫close to users in different geographical locations.
‫This reduces the traffic load on our system and also reduces the page load time for the users significantly.
‫Additionally, if we have static HTML pages, we can offload them to a CDN, which will reduce the load
‫on our web app service and also reduce the time that all of our pages load on the user's device.
‫On the API gateway level, we can also introduce caching at peak times when a small group of posts is
‫viewed by many users simultaneously.
‫We can store those posts in a cache and update this cache every few minutes.
‫This will also reduce the response time to our users and goes hand in hand with our decision to prioritize
‫availability over consistency.
‫Now we can also make quite a few performance optimizations on the database side.
‫For example, we can add an index on the post ID which will make searching for a particular post in
‫a constant time instead of scanning through all our collection of posts.
‫Similarly, we can use the same compound index we created for charting the comments using the post ID
‫and comments ID this way, Requesting a batch of comments for a given post will also be much faster.
‫Of course, we can also use indexing for the other databases, but we will skip this and move on to
‫the next problem.
‫When it comes to votes.
‫We also have a performance problem here.
‫As we recall, the votes are stored in the vote service database as individual records for each vote.
‫However, when we show a post or a comment in the UI, we want to show the overall total number of up
‫votes and down votes for each entity.
‫So what we can do is introduce a message broker between the voting service and the post and comment
‫service.
‫Additionally, we can add two fields in the post and comments collections, one for total upvotes and
‫one for total downvotes.
‫So any time a user upvotes or downvotes opposed, the voting service will add that entry to its own
‫database and publish it as an event to the post and Common service.
‫Then the Post and Common Service can consume this event and update the total number of upvotes for a
‫given post which will store with the post record.
‫This approach really helps if we have a short but sudden traffic spike when many people start voting
‫on the same post or several posts simultaneously.
‫Since we can buffer those votes in the message broker and we can make those updates slowly and complete
‫them after that traffic spike is over.
‫Of course, during this transition, users will not get the most up to date values for the total number
‫of upvotes or downvotes, but that's okay because we prioritized availability over consistency when
‫we gather our nonfunctional requirements.
‫Finally, now that we have the total number of upvotes and downvotes stored together with each post
‫and comment, we can load that data easily and efficiently to the user.
‫Now let's talk about fault tolerance and high availability.
‫To ensure we don't lose any data.
‫We can introduce database replication if a database instance or database shard crashes.
‫Similarly, every message broker data store and microservice is replicated, adding to our overall system's
‫full tolerance.
‫In addition, we can run our system across multiple data centers in different geographical locations
‫and utilize a global load balancing service.
‫This way we can always fall over to another region if there is a natural disaster in one of those regions.
‫Of course, as an added benefit, this also improves our performance as our system gets physically closer
‫to our users on different continents.
‫Finally, we prioritize availability over consistency when it comes to data, so our database is configured
‫to be available and partition tolerant.
‫And that's in addition to all the caching and other optimizations we have already made in our system.
‫Now, when it comes to your ability, we get that for free by using database replication and periodic
‫backups to ensure we don't lose any data.
‫So to recap, we successfully addressed all the nonfunctional requirements and updated our system to
‫have all the quality attributes we committed to.
‫We use techniques like load balancing and database SHARDING to address the scalability requirements
‫and also used an API gateway to decouple the front end from the internal structure of our system.
‫We use techniques like CDN caching and database indexing to address the performance constraints.
‫For high availability.
‫We utilize database replication and overall redundancy as well as a message broker as a buffer for storing
‫events that can be processed synchronously.
‫In addition, we also deployed our system in multiple data centers to prevent a single location from
‫becoming a single point of failure.
‫Later by either configuring or choosing database technologies that prioritize availability over consistency.
‫We could provide our users with data at all times, even if it's not the most up to date data.
‫And finally, by using database replication across multiple instances and data centers, as well as
‫rolling backups, we guaranteed durability so our system never loses any data.