We can store 2 types of data. One is the raw data for reconciliation and data processing replay, and the other is the aggregated data for fast queries.
It is what we get from input files. Based on the estimation, we have 10,000 QPS, while we don't often read it for calculation, so it is only write-heavy. Considering the size may grow with time, we can select No-SQL database, e.g. Cassandra, to achieve optimized write performance and easy horizontal scalability.
It stores aggregated and formatted data based on functional requirements.
There're 2 parts: aggregated data and filters.
Aggregated data is downstream of raw data, so it is also write-heavy. In addition, it is for fast query, so it is also read-heavy. We can also consider the same database as raw data to meet read-write performance and scalability requirements.
Filters data is more static and small, so we can use SQL database.
Explain how the request flows from end to end in your high level design. Also you could draw a sequence diagram using the diagramming tool to enhance your explanation...
In addition to avoiding overload during peak periods, message queues can also help guarantee data delivery exactly once and avoid duplication.
The aggregator processes data based on the timestamp. We can either select the timestamp from the client or the system timestamp when we receive the event. Client timestamps may have fraud, but it is more accurate. The system timestamps are more reliable, but there will be delay. Data accuracy is more important, so we would choose the client timestamp.
As we design, we aggregate data every minute, so we can use a fixed/tumbling window. In addition, because delay cannot be eliminated, it may happen that an event that falls within minute X is received 5 seconds later. To relieve the situation, we can do watermark to extend the time window a bit, like 15 seconds. The downside is this approach will increase the latency, also, it can't resolve the problem entirely if the delay is huge.
Explain any trade offs you have made and why you made certain tech choices...
There could be duplications due to server failures. If aggregator successfully processes data from offset x and gets ack from the downstream message queue, but it fails to send ack back to the first message queue. After system recovers, aggregator will reprocess the data and send it to the second message queue.
To resolve this issue, we can add s3 between aggregator and the second message queue to store the offset processed, like the sequence diagram. To be noted, we need a distributed transaction to cover steps 4-6. If any step within it fails, we rollback everything.
If the server is down, aggregated data in memory will be lost. Or if there is any bug, we also need a recalculation from raw data. But it is hard to process from beginning as the data is huge. To resolve this, we can snapshot system status besides data offset we talked about above.
What are some future improvements you would make? How would you mitigate the failure scenario(s) you described above?