With an estimation of roughly 1,000,000 ads per day and a 1,000,000 ad watchers per hour.
Even assuming that each ad watcher per hour. Clicks. Once on a any given ad, so we'll end up getting at least 100,000 clicks per hour. That translates to. 100,000 / 3600. Which is roughly 28 clicks per second. This is a very short estimate. Uh, you are likely to see closer to 100 to 1000 clicks per second, but for now let's just expect 100 clicks per second at least. So yeah, each click, when we are capturing each click, we are potentially capturing things like IP address, the lab device type on which the click happened. Uh, the surface. Watching Surface for the click for the art click whether it was on a umm the browser type. And. All these let's just assume that we have at least a bytes information. But uh, for each click that we are capturing and translating that to roughly 1000 clicks per second, you have at least thousand bytes per second, which is 1 KB per second now if you. To translate that to an hour that is 3600 KB which is 3.6 MB per hour. Now even if there are 10000 clicks per second for any ad, this would translate to 36 MB per hour so storage size would not be a big issue.
So the high level design flow chart at least is ready the client, which is basically in this case the user who clicks on the ads resides. Interacts with the Click Add service which is behind an API gateway that takes care of the rate limiting and the Click Add API which is basically a POST request as we discussed during the API design, captures all the information like. Umm, the art. Surface the art. The browser type, IP address, etcetera, location, etc etc and. Uh, since the right volume is going to be high, you are, we are expecting millions and millions of clicks across millions and millions of ads. We need not, this service need not directly write into our Cassandra. Uh uh, which is the right optimized Cassandra, uh, data database? Instead, it can write into a message message stream or a message queue like Kafka Kafka. Provides temporal storage for us in this case in the sense that retains at least by default provides 7 day storage for it's for the messages, so we get immediate hot or warm storage. Capabilities through Kafka already in here and then from Kafka our consumers can write into the Cassandra database which is going to act as the source of truth. Any pre now come before coming to the pre computed views. Let's talk about the read path. The read path essentially is going to be through the get metrics API, which is primarily be going to be used when we watch any when when. Uh, not the consumer, but the dashboard system user basically uses a dashboard again which resides behind the rate API gateway and through the GET metrics. API from the Postgres database which is a separate read database which has all these precomputed views. Will, uh, will serve the umm, metrics for our. Dashboard from the kafka stream to the Postgres database. We'll capture using flink jobs all these precomputed views.
When the user_id is present in the click event - that is when it is not an anonymous event - the user ids are periodically updated in the redis cache which is an in memory store for any demography look up. This gets translated to the kafka event stream for any fixed dimension pre computed views.
We can start of with a SQL database like Postgres for the precomputed views which provides the necessary indexing capabilities to index based on geospatial indexes, by country or other dimensions. Since we accepted eventual consistency as a non functional requirement, and since ad clicks are likely to be going to high volume stream of data, the read and write databases need to be separated as we do not want to overload our server. For our dashboard metrics a separate read database with precomputed views can be a separate stream. For frequently viewed dimensions, we can store the precomputed views in redis cache with its own in memory store and with short ttl. The ttl could be set to 5 minutes ,
So a write optimized database like cassandra can be used. The cassandra schema would be: {click_id, ad_name, ipaddress, location, ad_consumer_id, click_browser, click_device, click_timestamp}. The partition key would be on ad_name. This would enable querying any custom queries directly from the write db to be specific for the ad. However when there are viral ads, we run in to hot key problem. The viral ad which will receive the most clicks will all run in to one particular partition in our write DB. This can be avoided by adding a bucketed salt in to our ad_name. The way this works is the partition is decided usually by hashing our ad_name to identify which partition will carry our data. So when hashing this the ad_name can be added with a bucketed salt - which could be the hour of the day so that the partition for the same ad will be different based on the salt. This addresses the hot clicks.