Methodology
Load testing was performed against our preview environment using scrubbed data from real interactions with the chatbot. All conversations included at least five guest interactions to simulate actual engagement. All client/server interactions were accounted for and reproduced faithfully, including non-critical functionality such as rating responses.
Differences from Production
The most notable difference with load testing was using a zero second interval between polling calls. Normally polling is performed in one second intervals, though we decided to remove the delay between calls so the entire test could run within time limits. Being faithful to the source data, the same number of polling calls are made, however, these are now back-to-back with now pause between. This - in our opinion - will produce equal, if not greater, stress on the server.
Another challenge was scaling the application in a natural manner. The chatbot server, being hosted in Google App Engine, has built in scaling capabilities. This scaling handles normal web traffic well, where traffic will ramp up over a period of time. The testing performed, in contrast, spiked load in a less-than-natural manner, resulting in "hard starts". To counteract the spikes, we tuned the scaling parameters so that we had more idle instances to "smooth out" the initial spike. Some of the changes are candidates for inclusion in the mainline code base.
We also encountered some issues with our Dialogflow projects. All our environments were configured for Dialogflow Essentials Edition, which had an upgraded quota of 3,000 text queries per minute, which surpassed. As such, we submitted a request to have this quota increased to 6,000 per minute which allowed us to proceed without the results being tainted by errors.
Lastly, we increased capacity of the database in our preview environment. This was to determine the role the database played in scalability. The result was favourable and will be very likely implemented in production before any new chatbots are added to the platform.
Configuration | Preview | Production |
|---|---|---|
| vCPUs | 4 | 1 |
| Memory | 15GB | 3.75GB |
| Max Connections | 8000 | 4000 |
Results
Overview
During our test, we were able to simulate about 11,000 concurrent users
The primary optimization, adding client-side caching to OPTIONS calls, reduced the number of requests and thus the number of instances required
With 2.1, we had 1,713 created instances and 344 active instances. The optimizations slashed these number significantly.
As a direct result, we reduce the number of active database connections from about 2,600 to 660.
The overall CPU utilization was well in the safe zone at 33%, meaning we should be able to scale even higher than observed.
Data
| module | method | endpoint | minimum latency | average latency | maximum latency | average pending | maximum pending | calls |
|---|---|---|---|---|---|---|---|---|
| v1 | GET | /v1/listen | 0.005814 | 0.064754 | 15.684197 | 0.013804 | 15.052087 | 2381618 |
| v1 | PUT | /v1/talk | 0.003332 | 0.100979 | 13.303746 | 0.047544 | 12.099215 | 8638 |
| v1 | PUT | /v1/trigger | 0.056535 | 0.289955 | 13.907069 | 0.078147 | 12.056342 | 5785 |
| v1 | OPTIONS | /v1/listen | 0.002227 | 0.264699 | 14.162539 | 0.159387 | 10.457785 | 1731 |
| v1 | OPTIONS | /v1/product/{product}/loader | 0.003509 | 0.072225 | 6.844845 | 0.029410 | 2.467902 | 1432 |
| v1 | POST | /v1/start | 0.030852 | 0.364308 | 15.948447 | 0.213212 | 14.874535 | 1432 |
| v1 | OPTIONS | /v1/start | 0.003129 | 0.312106 | 15.026576 | 0.220793 | 10.451347 | 1432 |
| v1 | GET | /v1/client/configuration | 0.003403 | 0.093802 | 10.787438 | 0.044880 | 10.191730 | 1432 |
| v1 | OPTIONS | /v1/client/configuration | 0.003501 | 0.081803 | 1.991555 | 0.037026 | 1.810825 | 1432 |
| v1 | GET | /v1/product/{product}/status | 0.003214 | 0.088996 | 4.020589 | 0.043021 | 3.951815 | 1432 |
| v1 | OPTIONS | /v1/product/{product}/status | 0.003137 | 0.079331 | 2.879372 | 0.034322 | 2.860465 | 1432 |
| v1 | GET | /v1/product/{product}/config | 0.004115 | 0.082810 | 2.906476 | 0.037763 | 2.475531 | 1432 |
| v1 | OPTIONS | /v1/product/{product}/config | 0.003633 | 0.073426 | 9.224409 | 0.029035 | 1.227639 | 1432 |
| v1 | GET | /v1/product/{product}/loader | 0.004097 | 0.072809 | 4.460998 | 0.032217 | 4.269055 | 1432 |
| v1 | OPTIONS | /v1/talk | 0.002294 | 0.131691 | 10.329461 | 0.072742 | 10.153406 | 1418 |
| v1 | OPTIONS | /v1/goodbye | 0.002119 | 0.055697 | 9.386118 | 0.031516 | 9.159125 | 1221 |
| v1 | POST | /v1/goodbye | 0.025190 | 0.116825 | 3.276963 | 0.017951 | 0.998088 | 1221 |
| v1 | OPTIONS | /v1/trigger | 0.003009 | 0.248369 | 13.620989 | 0.138863 | 10.348752 | 1074 |
| v1 | GET | /_ah/warmup | 7.988969 | 12.215853 | 18.610605 | 0.000000 | 0.000000 | 356 |
| v1 | PUT | /v1/feedback/message/{messageid} | 0.042882 | 0.189455 | 10.626019 | 0.017763 | 0.184453 | 206 |
| v1 | OPTIONS | /v1/feedback/message/{messageid} | 0.002656 | 0.067476 | 0.799274 | 0.033715 | 0.675436 | 201 |
| v1 | POST | /v1/product/{product}/webhook/dialogflow | 0.042807 | 0.815533 | 2.895538 | 0.028044 | 0.651854 | 143 |
| v1 | GET | /v1/cron/sessioncleanup | 0.046256 | 0.100638 | 0.663077 | 0.000515 | 0.030924 | 60 |
Latency and Pending values are in seconds
Analysis
By caching the results of CORS Pre-flight requests, we were able to significantly decrease overhead. This, in turn, increased our concurrent user support by nearly 15%. Additionally, we've also reduced demands on the database as a side effect.
One thing we did notice is that this wasn't a 100% successful test. There was one session that had failed to establish as the /v1/start call had been aborted as it took too long to warmup an instance to handle it. I believe this was the result of the servlet taking more than ten seconds to start, and as such, addressed the start up time in a future release.
Given that we were unable to cause any apparent bottleneck in the system (eg: database CPU utilization), one can assume that we've hit the limitation of our current load testing framework. We will need to determine a better approach for future load tests.



