Venky Ganti0:15
Hello, my name is Venky Ganti. Today, along with my co-founder Rahul Lahiri, I am going to discuss how we can shift testing and production left. In doing so, we enable engineering teams to prevent API regressions and detect them early in the development life cycle.
There are two big trends: continuous testing driven by DevOps and shift left, as well as the adoption of microservices architecture. These two trends are actually driving engineering teams to take on additional responsibility.
You take a typical engineering team. There are three test goals before they release a new piece of software or a new version of their software. The first goal is to prevent regressions to existing usage. The second goal is to test and debug new functionality and ideally also automate tests for that new functionality to prevent regressions in future. The third goal is infrastructure testing: performance, load testing, resilience of the infrastructure itself or the application, security testing, and so on. Because of the pressures of release deadlines, teams rarely get to spend a lot of time on this.
This adds up. The further they need to spend, it is hugely demanding on their time. Typically, more than 25% of all engineering effort in a team is spent on creating these automated test suites. Despite all this effort, the coverage of this regression test tends to be limited because of a variety of constraints, whether it's timeliness or the additional new capabilities they need to deliver for the business to function. All of this limits the amount of testing coverage they can get. The consequences: it slows engineering velocity, whether because of the effort they have to put in in the current release or because of bugs that arise in production.
Satisfaction or even churn. Because of the criticality of this regression testing issue and the amount of effort required, a common approach is that consumer tech companies such as Google, Facebook, and Twitter have come up with a variant which is often called testing in production. The key insight here is to leverage ongoing user activity to validate new versions of the software. In one variant, which is called canarying, a fraction of the traffic is diverted to the new version, whereas most of the traffic goes on to the production version. Then they observe top-line metrics.
Between a primary production version and the candidate build, the functional responses from the candidate build are then compared with what is coming out from the primary production version. This comparison has to be functionally possible. In that context, the candidate version has to have the exact snapshot of all the backend infrastructure that the primary is running on. Thus, they are able to compare for functional correctness. Both these approaches have the following problem: they are fairly late in the development cycle. The builds have to be released into production where the users can be exposed to that.
It takes a long time to converge. Because both the candidate and the primary versions, in both canarying and diff testing, have to run on exactly identical snapshots of the databases, the infrastructure tends to be pretty costly.
Now the question still stays: can we leverage user activity, especially past user activity, in the context of enterprise applications to shift this testing in production idea to the left? If you are able to do that, then there are significant benefits. The first one is that we are able to get 100% functional regression test coverage right in our CI pipeline.
State consistency between when we captured user activity and when we are testing. So what are the approaches for reconciling this state consistency? A common approach is to snapshot data just before capturing traffic. We snapshot the entire databases, all the databases that the application relies on, and then immediately start capturing the traffic for an uninterrupted period of time. When we have to test, we actually restore the data snapshots back to that particular snapshot and start replaying the traffic. Thus, we are able to compare what is happening with the test exactly with what used to happen in production. That's one way. The main problem here is that the infrastructure cost is still very high.
Fidelity, where the simulator is much lighter weight than all the production databases that you need to bring up. The question is: is this even possible, especially in our current scenarios where responses to identical requests can be non-equal? In the sense, if you take a stock ticker service, you ask for the same stock price, let's say Microsoft stock price, every few seconds you get a different response. In that context, how can we actually simulate the state with 100% fidelity? You have to really understand the context of the caller as well as the callee before you are able to simulate that.
Our insight here is that you can actually leverage tracing of the application to simulate.
In a production system, let's say, when we are capturing the usage, we inject a tracer ID at the gateway request, much like the Jaeger and Zipkin open tracing libraries, as well as APM products such as New Relic and AppDynamics. The same trace ID that is injected at the gateway request is propagated to the downstream services for that request, which spawns off requests to service B and service C and to the database. They all carry the same trace ID. Now, all of this usage data along with the tracing is stored in a usage database. Now when we have to test, we bring up the service A.
Simulators get this request instead of the actual services. They get the request parameters as well as the trace ID, and they can use both the request parameters and the trace ID to identify the exact response that they need to send back. So if this particular trace ID corresponds to the Microsoft stock price which receives 131.5 as a response, the trace ID will tell the simulators that for this particular trace ID, even though there are four different candidates that we can respond with, this is the precise category that we need to pick to simulate the context exactly. And that is what enables us to simulate the stateful components with 100% fidelity.
In your CI pipelines itself, so let me illustrate that with this once more. You have your application. We put in agents at various levels of the application, whether it is the gateway or between services or from the service to the database. We capture this API traffic along with the trace IDs in our usage database. And when you have a new set of versions of these services that you need to test, our usage drivers drive traffic to the gateway and re-inject the trace IDs that were introduced in production. And the simulators, all these agents have the ability to simulate the backend stateful services and databases. So consequently, your harness is super.
System under test, and compare with the responses that were observed in production. And you now have 100% regression test coverage with very, very little engineering effort in terms of test automation. Consequently, you get 100% regression coverage and you are able to prevent regressions or detect regressions fairly early in your development cycle. So the coverage is pretty high. The effort that you need to put in terms of test automation is much, much lower. Because of this, you would get high engineering velocity and high quality.
Now I am going to hand off to my co-founder Rahul Lahiri.