Mise à l'échelle à 5M RPM

Dec 01 2022
Chez Trendyol, nous avons la responsabilité de servir les pages de contenu et les données. Nous servons également des données de contenu en vrac pour les favoris, les collections ou même les pages de paiement.

Chez Trendyol, nous avons la responsabilité de servir les pages de contenu et les données. Nous servons également des données de contenu en vrac pour les favoris, les collections ou même les pages de paiement. Bientôt, nous interviendrons chaque fois que vous verrez les écrans ci-dessous.

Quels sont nos besoins ou charges prévues ?

Comme prévu, nous obtenons généralement plus de trafic pendant les saisons de magasinage ou les périodes d'événements/réductions. Alors que nous obtenons environ 1 million de tours par minute (requêtes par minute) au cours d'une journée typique, la charge monte à environ 3 millions de tours par minute en période de pointe. À cette époque, nous ne pouvions pas répondre précisément aux exigences. Notre temps de réponse doublait par rapport à d'habitude, et nous avions même plus de délais d'attente que nous ne pouvions ignorer.

Notre prochain objectif et nos attentes étaient d'atteindre au moins 8 millions de tours par minute avec, au maximum, les mêmes ressources. Le problème était donc que nous ne pouvions pas évoluer autant que nous en avions besoin, sans parler de ce que nous attendions ensuite.

Qui aime les défis ? Oui, certainement nous :)

Comment avons-nous trouvé la cause du problème ?

Nous avons donc fait un profilage pour comprendre quelle partie du code prend le plus de temps et utilise le plus de ressources. Tout d'abord, nous avons utilisé un profileur Java pour affiner les parties à suivre. Après cela, nous avons ajouté des traces New Relic personnalisées dans le code. Lorsque nous avons examiné les résultats, nous avons constaté qu'une partie de code spécifique était la plus consommatrice de temps et de ressources.

Cette partie était une logique de conversion pour convertir le document Couchbase en données dont nous avons besoin pour exécuter la logique métier et servir de masse. Il fonctionnait pour chaque demande et chaque contenu. Nous avons commencé à réfléchir à la possibilité de faire cette logique de conversion de manière asynchrone. Nous pourrions exécuter cette logique une fois pour chaque contenu. Pour la plupart des pièces, la réponse était : OUI !

Il ne suffit pas de faire de son mieux ; vous devez savoir quoi faire, puis faire de votre mieux. —W Edwards Deming

Pouvons-nous gérer les données de manière asynchrone ?

Nous utilisons Couchbase comme source de données. Lorsque nous avons examiné comment convertir nos données Couchbase de manière asynchrone, nous avons constaté que Couchbase dispose d'un protocole de changement de base de données (DCP). DCP peut nous donner un flux pour les mutations. Nous pouvons donc écouter à partir de notre ancienne source de données, effectuer la conversion asynchrone et écrire dans notre nouvelle source de données. Vous pouvez consulter l'article d' Ahmet Hatipoglu si vous vous interrogez sur les détails.

Maintenant, il est temps de déterminer si nous pouvons optimiser les données.

Avons-nous besoin de toutes les données ?

Nous avons utilisé le même modèle de données pour la page de contenu et les demandes groupées. Pour les demandes groupées, plusieurs clients ont besoin de données différentes. Nous avions donc besoin d'une analyse pour comprendre qui a besoin de quelle partie des données. Nous avons fait une analyse complète avec tous nos clients ensemble.

Après l'analyse, nous avons réalisé que nous n'avions besoin que de certaines données et que presque tous nos clients avaient besoin de presque les mêmes pièces. Mais nous ne pouvions pas toucher au schéma car certains de nos clients utilisent directement la même source de données. Nous sommes étroitement liés. En conclusion, la séparation des sources de données pourrait être une étape vers une solution.

Qu'avons-nous maintenant jusqu'à présent ?

Un service qui écoute les modifications d'une source de données de contenu effectue la conversion lourde et écrit dans une nouvelle source de données. Et nous savons d'après l'analyse que nous n'avons besoin que de certaines données. Nous pouvons donc omettre toutes les pièces inutiles pendant que nous effectuons la conversion.

Es-tu prêt? Nous avons enregistré 77 % des données (1,28 To). Quel soulagement!

Et maintenant?

Nous avons besoin de quelque chose qui utilise les données que nous avons préparées. Nous voulions séparer les besoins de contenu en vrac et les besoins de la page de détail du produit simplement parce que leurs clients, leurs besoins d'échelle et leurs ensembles de règles métier diffèrent. Il est donc temps d'écrire un nouveau service.

Que peut-on faire différemment ?

Peut-être que les technologies que nous utilisons. L'ancien service utilisait Java 8 et Spring boot. Nous avons eu une expérience stable avec le Quarkus . Peut-être pourrions-nous réduire l'utilisation des ressources et le temps de démarrage pour être plus évolutifs. Ou nous utilisions Flux pour acquérir les documents de Couchbase, et peut-être pourrions-nous utiliser CompletableFuture pour changer. Après avoir répertorié nos options, nous avons développé de nombreuses applications POC et effectué de nombreux tests de charge. Mais les résultats auraient pu être meilleurs. Du moins pas pour l'effort dont nous allons avoir besoin. La recherche doit se poursuivre.

Au fait, je suis gopher depuis quatre ans. Je crois qu'on peut obtenir les documents efficacement avec des goroutines en parallèle. Alors j'ai voulu essayer ça. Ensuite, j'ai regardé le SDK Couchbase Go officiel, qui dispose d'une fonction "obtention de documents en masse". C'était génial! Les besoins étaient adaptés à l'utilisation de Go. Et je voulais essayer les deux options.

J'ai effectué des tests de charge sur les goroutines parallèles et la fonction de masse. Les temps de réponse et l'utilisation des ressources ont été considérablement améliorés pour les deux. La fonctionnalité en masse était meilleure que notre simple implémentation. Mais quelque chose n'allait pas. L'utilisation du réseau était quatre fois supérieure. Nous pourrions donner une configuration à la connexion Couchbase dans Java SDK qui permet la compression. Mais Go SDK n'a pas de configuration de compression dans ses options de configuration de cluster. J'ai donc raté la configuration en conséquence. Malheureusement, la fonction de compression ne peut pas être contrôlée via Couchbase Go SDK, contrairement à Couchbase Java SDK. J'ai donc examiné son code, effectué un débogage pénible et découvert que la compression pouvait être contrôlée via une variable de requête à la fin de la chaîne de connexion.

couchbase://{HOST_HERE}?compression=true

      
                

  • Nous avons un gain massif d'utilisation de la RAM et l'utilisation de la mémoire est passée de 800 Mo à 60 Mo par pod.
  • Le temps de réponse est réduit de moitié. 10 ms à 5 ms . Et il est plus stable face à un débit élevé.
  • L'utilisation du processeur est considérablement réduite. Pour atteindre 5M avec l'ancien service, nous avions besoin de plus de pods. Ce sera donc une comparaison de l'utilisation totale du processeur. Il s'agit désormais de 130 cœurs au lieu de 300 .
  • La taille totale du document est maintenant inférieure au quart d'avant, passant de 1,67 To à seulement 386 Go .
  • Naturellement, la charge du réseau est beaucoup plus faible.
  • Le temps de démarrage est maintenant de 85 millisecondes au lieu de 12 secondes !

Merci à Emre Odabas pour son soutien et ses encouragements dans la rédaction de cet article.