Senior Software Development Engineer
Cars24Senior Software Development Engineer
Razorpay
PHP

AWS

SFTP

Golang

MySQL

Java

Postgres
.jpg)
Grafana

Oracle

Redis

Git

Prometheus
.png)
Coralogix

Kubernetes
.png)
Docker

Prometheus

AWS

SFTP

Redis

AWS EventBridge
My name is Pratik, and I have joined around 2021 in April. It's been about 3 years since I joined Reserve as a full-time employee. Earlier, it was an internship of 15 months, but it was a kind of internship, and I was working like a full-time employee only. In this, I have worked mainly on three things. Firstly, many cards, where we used to maintain the card payment features and fix any bugs if reported by any merchant. The second was tokenization. There, we used to basically migrate the safeguard flow, like, from local to network tokenization. The third team is the IA team, internationalization. So, we are working on migrating Razorpay outside India. By outside India, I mean basically generalizing the code base of Razorpay so that it can start supporting multi-currency payments as well, and we can onboard any new merchant outside India as well. So they can support their local payment methods, local currencies, local settlements, everything. This is a kind of work I have done in Razorpay. Apart from work, my hobbies are playing badminton, going for trekking, going for cycling on weekends, and basically editing books. This is the kind of hobby I'm trying to develop. Apart from this, I'm from a very small town called Sadora. It's on the border of Haryana and Himachal, and it lies in Haryana. It's a privilege. I can go to Himachal anytime, like 15 or 20 minutes away from my home. So, that's it from me.
Yeah, to achieve this, we can use multi-threading. So, basically, there are 2 things we can do. With AWS, we can do HPA, so horizontal pod autoscaling. So, basically, we can scale the pods whenever it's required on the basis of load itself. So, this is a kind of auto-scaling we can do on infra level of things. And, if you want to do on coding side of things, we can apply multi-threading as well. So, if we combine auto-scaling plus multi-threading, it will be a good combination to basically support the scale. And, also, we should take care of which processes are synchronous and which are asynchronous. So, if we go with distinguishing between asynchronous and synchronous processes, then, yeah, we can save a lot of time because if any process is asynchronous, then we can just read it on any response to our consumer or client. And, in the background, we can keep completing that task. So, I guess, yeah, that's one of the options we can do. So, as I told, 3 options: auto-scaling, multi-threading, and distinguishing between synchronous and asynchronous tasks. And, also, one more thing we can do is we can distinguish between the DB, like read DB and write DB. So, some DBs have good speed in writing any entry, but some DBs support good reading time. So, on that basis, we can distinguish between the DB as well. So, I guess, that will fulfill our requirement.
To optimize your slow rolling API due to database, Bottle, etc. Yeah, so I'd like to mention one thing here. Normally, whenever we trigger an API, it normally hits the DB. But in certain scenarios, we can take advantage of Redis. So, if we use Redis, we can decrease the reliability of the system on the main DB. One solution is using Redis cache, and another is using offsets while fetching any items from the DB. If we're loading a page that requires only 50 to 60 items, we don't need to fetch all the items from the table and then take the first 50 or 60. Instead, we should use offsets to set and ensure we're taking only the items we need, and we're aligned with the numbering as well. The two solutions can be Redis, separating a read and write DB, and using offsets. This will reduce the load on the DB, and our API can work comparatively faster.
Audio flow for channel, lower mutex is in. Go like and vice versa. Your text is over channels. Okay. I guess the answer here would be I would like to prep for new taxes, because it will help us in synchronizing the resource that we are using in the process. So if we use channels, then we rely on the channels to give us some output, and on the basis of that, we close the thread. So like, if anything fails in between, it can kind of create a state of inconsistency. But if we go with mutexes, then we can do forced management way better than we can do in channels. Because if we pass the mutex, if we can pass unblocking the resource in the defer, and we can just start blocking the resource with the help of mutex. So we can make sure that even if the function or some code executes incompletely, then there is some part of code that will execute every time. So by that, we can free the resource. But in case of a channel, we are relying on the channel only, and once we get something from the input from the channel, then only we will perform some action. I believe that using mutex is way more better than using channels in calling.
So, yeah. Two practices I normally follow, and I will divide this problem into two steps for me. First, I will discuss when to throw an error and how to throw an error. Normally, I prefer to return a custom error in case of any validation failure, exception handling, or anything, and define what the reason of the error is. Not to explain properly, but just give a headline so that a person who is reading can understand, okay. This might be the root cause of this error. I also try to add proper tracing in those scenarios. If there is some failure and I need to check the logs, okay, this is the failure. The logs should clearly tell me what's the reason of the failure. Instead of going to check in the DB, going into multiple entries, doing multiple iterations, and then finding out, okay, this is the reason. Instead, the logs should tell me. This is one of the practices I follow. Second, when I write some external calls, there is a possibility that the external gateway API, whatever it is, might be down. I always try to handle the panic and all those scenarios in the default condition so that if there is some unhandled exception thrown, I should be able to catch that. I should be able to test that first. If we can utilize a default value, we don't fail the whole process. This is a practice I follow. This makes sure that if the external API is down, we will get some alerts. There might be a state of data inconsistency for some time, but we can fix that. Depending on the importance of the operation, we make sure whether we need to fail this or not, but handling such kind of errors ensures that we get alerts in proper time and during runtime only. We are not relying on some other third party or something. We will get the alert in real-time, and we will be able to work on them proactively.
Okay, coming to solid design principles, there are two principles mainly I follow. One is single responsibility, and the other two are open-closed also and dependency inversion. So, if we follow two or three principles in the SOLID rules, we kind of follow all the rules. But these are three rules I mainly focus on. Single responsibility, so that if I am creating any function, I am not putting too much responsibility into that function. And if in any scenario I need, if my business logic is very complex, then I make sure that I create the function and the parent function is just calling the function that is below that one layer, like all the functions below the second, third layer, the parent function shouldn't care about that. So, this is the kind of practice I follow while applying the single responsibility thing. And second is open-closed. So, in the open-closed, I try to make sure that I am using interfaces while creating anything, interface and structure, so that I can create a hierarchy that okay, this is the parent, this is the child, and on the basis of that I can create basically a child-parent kind of structure where there are multiple children and only one parent, and whatever object I can assign in that parent should be substitutable. So, I basically assign the function according to that. And now, coming to this dependency inversion. So, whenever any core, server, I need to create, so normally what we used to do is, as I mentioned in the open-closed principle, we use interface and structure combined way. So, in creating any core or something, like creating any encapsulated object, so there, instead of passing the object, we pass the interface, like here the receiver object will be an interface, but the passing object will be a structure. So, we don't need to change the structure of basically that encapsulated object, and we can pass whatever kind of structure we want to pass in their constructor. So, by this, whatever new class we introduce, here the higher-level module is not depending on the lower-level module. Whatever we want to change in the lower-level module, we can, and the higher-level module won't be affected with that. So, these are the main three principles I use in the SOLID design rules.
Okay. Yeah. Here. So mainly, we are checking first what response we are getting from the get method of the STTK module. And if it contains the proper response, then it means that this API is working fine, and we are good to go with this. And here, if you see, if we are receiving a proper response, then we have added this response's body. So whenever we are closing this function, we will be closing the body as well. And if we don't close the body, then this resource will remain open, and there might be a possibility that any new thread or process won't be able to access this resource. So if we don't do this step, it will be an issue. That's a good thing we have done. And now, if we go to the next step, it's if we are checking whether the error is not equal to nil or not. If yes, then we return the error. It means that nothing is open, so we don't need to close anything. And now, we are checking whether the body that we have received from the API can be decoded or not, whether the format that we are expecting it to send is the same data or not from that API. So if that is the case, then we will return an error. Otherwise, we will return data. But if we are getting an error while decoding the data, we will return an error, but that effort will also be executed in that case, and the body will be closed. So any new resource can also access this particular resource.
Yeah, the problem is, the append function normally returns the new slice. So we need to store that new slice somewhere. Here, we are appending the this n into the events, but we are not basically storing that slice somewhere. This append function is returning the new slice where n is appended in the events array, but we need to basically create some variable which can store the reference to that slice. So events should be, like, events equal to append then perform the operation. This is a problem in this code, and we can fix that.
for optimizing golang garbage collection in a service with high memory usage patterns, yeah so here are three things we can do. first, we can make sure that whenever we open any resource, we close it immediately if we're not closing it, that's a very big issue because it will lead to high memory usage. second, we can consume the cache by using it, so we can rely on our main db as little as possible. third, we can take advantage of cdns. if we know what kind of data is used by our consumers in which areas, we can deploy cdns in those places and have the respective consumers fetch data from cdns first. if the data is not available, they can go to the db. it's the same way AWS works, first going to cloud front and then to as3 if it's not present. we can apply similar practices here as well. also, there are certain db practices we can use, such as partitioning and sharding. if we divide the db into multiple pieces, we can use patterns like this: if the request pattern matches this, go to this partition; if it matches this, go to this partition. by making these patterns, we can decrease the load on the db and reduce the time it takes to perform db operations. these are practices we can take into use and they will help reduce high memory usage patterns in a microservice.
Yeah, so mainly say Kubernetes is a containerization platform. With the help of Kubernetes, we can create pods, services, network layers, all the infrastructure that Kubernetes provides, and it works really well with AWS. So if we use Kubernetes and integrate with AWS for our microservices, when the load increases, our number of replicas can scale. And with that, there will be a very good advantage that we are able to reduce the cost and serve more load whenever required. So in Razorpay, we also do that during the IPL times, we increase the number of replicas and whenever it's required, we reduce that as well. So that dynamic handling can help us in reducing the cost as well as increasing the scale whenever it's required, and serving high traffic. And I guess there are multiple facilities Kubernetes provides, such as storing secrets and creating network layers between multiple services. And if we have multiple services, the endpoints we expose can work like a load balancer as well. So there are multiple things Kubernetes provides, and if combined with AWS, it can be a very good asset for any workload to scale and take advantage of HPA to serve traffic.
What's your AWS SDK? Actually, I haven't done that till now.