3
votes

Pourquoi un service Web asp.net prend-il autant de mémoire?

J'ai un service Web .net (Web API 2) qui accepte les requêtes pour obtenir des données d'une base de données. Les données renvoyées ne sont pas très volumineuses, seulement 1 à 3 ko généralement (environ 1 à 15 lignes de la base de données). Je l'ai configuré dans IIS et y ai effectué des tests en masse, où il est beaucoup touché pendant une heure environ, il reçoit donc plusieurs milliers de demandes. Lorsque je regarde le w3wp.exe (processus IIS Worker), la mémoire ne cesse d'augmenter jusqu'à ce qu'elle atteigne plus de 5 Go et que l'utilisation du processeur se rapproche de 100%, puis le service Web cesse de fonctionner. Le serveur a 16 Go de RAM, nous l'avons juste augmenté de 8. Est-ce que 5 Go de mémoire est normal? Cela semble beaucoup, j'aurais supposé que la collecte des ordures gérerait mieux cela. Je n'ai pas beaucoup d'expérience dans l'utilisation du moniteur de performances ou d'autres choses pour résoudre des problèmes comme celui-ci.

Des suggestions sur ce que je peux examiner? Peut-il y avoir une fuite de mémoire? Dois-je essayer de recycler le pool d'applications lorsqu'il atteint une certaine quantité de mémoire?

Mise à jour - nous avons un autre service Web .net qui n'accède même pas à une base de données ou à des fichiers externes ou quoi que ce soit, et il agit de la même manière, la mémoire ne cesse d'augmenter. Notre plan est de configurer probablement le pool d'applications pour que chacun soit recyclé toutes les x minutes ou lorsqu'il atteint une certaine quantité de mémoire.

Voici un exemple de fonction typique de l'API:

< pré> XXX


11 commentaires

Essayez de récupérer des patients avec des objets ado.net normaux, comme des dbsets ou des lecteurs de données et autres. Il semble que le problème ne soit pas lié aux deux méthodes ci-dessus.


@isaeid Je suppose que je pourrais essayer ça. J'utilise NPoco, un ORM open source.


@isaeid Cela ne ferait probablement qu'empirer les choses. DataSet est notoirement lourd en mémoire. En ce qui concerne un lecteur de données, les Micro ORM n'utilisent pas autant de mémoire sur eux.


Plutôt que de deviner ce qui utilise votre mémoire, vous devriez peut-être enquêter activement. Profil de votre utilisation de la mémoire à l'aide d'un outil. Cela vous dira ce qui utilise toute la mémoire.


@mason J'essaye ça, je suis tellement débutant, je ne suis pas sûr de ce qui est le mieux. Je regarde Processor Explorer (en regardant l'onglet de performances .net de w3wp.exe), mais je n'ai rien trouvé qui me frappe ou je ne sais quoi faire des résultats. Quelle est la hauteur trop élevée? Qu'est-ce que cela signifie que le% du temps en GC n'est que de 1 ou 2, est-ce mauvais? La taille du tas de génération 2 est plus grande que la taille de tas de génération 1, mais je ne sais pas si elles sont trop grandes.


@mason, oui, l'ensemble de données consomme beaucoup de mémoire, mais pas aussi haut que ci-dessus, et Kelly peut essayer avec un lecteur de données ou d'autres orms, probablement le problème vient de pocco orm


@isaeid Je doute fortement que le problème soit avec NPoco. Quoi qu'il en soit, plutôt que de simplement adopter l'approche du fusil de chasse consistant à échanger des choses au hasard, la bonne chose à faire est d'enquêter sur ce qui est en mémoire à l'aide d'un profileur.


@Kelly Process Explorer peut vous dire combien de mémoire est utilisée par l'application, mais il ne vous dira pas comment cette mémoire est allouée. Ce que vous devez trouver, c'est un profileur pour la mémoire .NET. Je suis sûr que si vous faites une recherche sur le Web pour cela, vous trouverez de nombreux outils.


J'ai une idée que c'est peut-être votre cartographie qui cause les performances, pourriez-vous essayer de faire _mapper.Map > (lst_Activation)


@penleychan J'ai essayé ça, ça n'a pas fait de différence. En fait, je ne faisais cela que sur cette classe uniquement, toutes les autres fonctions faisant le mappage avaient cette meilleure «syntaxe»


J'ai utilisé Process Explorer et ANTS Memory Profiler de Redgate, mais ils ne me révèlent pas grand-chose sauf que System.Threading est une classe identifiée comme un coupable potentiel. Je retourne Tasks comme type de retour aux fonctions mais je ne fais pas moi-même de multithreading explicite.


3 Réponses :


0
votes

Que contient l'objet Activation? Si l'activation a de nombreux enfants, pendant le mappage, elle peut essayer de remplir tous les enfants et enfants des enfants et sur ... Si vous utilisez le chargement différé EF, EF créera une sous-requête pour chaque population enfant.


2 commentaires

Dans ce cas, la récupération de la base de données se fait directement et avec les instructions de SQl, et comme vous pouvez le voir, les données associées ne sont pas récupérées avec la commande sql.


L'activation a plusieurs champs, pas d'enfants d'enfants. Ne pas utiliser le chargement paresseux



-1
votes

Semble être un blocage en raison de la non-utilisation de ConfigureAwait dans la bibliothèque,

[Route("patient/{patientKey}/activations")]
public async Task<IHttpActionResult> GetActivations(int patientKey)
{
     try
     {
          ActivationList actList = new ActivationList();

          actList.Activations = await _actProc.GetByPatient(patientKey).ConfigureAwait(false);

          return Ok<ActivationList>(actList);
     }
     catch (Exception ex)
     {
         return new BadRequestWithInfoResult(Translators.makeXML<ErrorReturn>(CreateError(ex)));
     }
}

public async Task<List<Activation>> GetByPatient(long patientKey)
{
     using (var dbConn = _database.CreateConnection())
     {
          List<DBActivation> lst_Activation = await dbConn.FetchAsync<DBActivation>("select * from fact.Activation where PatientKey = " + patientKey.ToString()).ConfigureAwait(false);

          List<Activation> Activations = lst_Activation.Select(x => _mapper.Map<Activation>(x)).ToList<Activation>();              

          return Activations;
     }
}


1 commentaires

Je ne pense pas que ConfigureAwait n'est pas nécessaire ici. Le service fonctionne correctement pour des milliers de demandes (fait très rapidement), puis envoie le message http 503 «service indisponible» et cesse de fonctionner jusqu'à ce que le pool d'applications soit recyclé. Essayer de voir comment je peux l'amener à mieux gérer sa propre mémoire.



0
votes

Réponse OP - J'ai trouvé du code offensant (et non, ce n'est pas normal pour un service Web comme celui-ci d'utiliser plus de 5 Go de mémoire). Il y a quelque temps, en essayant de faire mes propres espaces de noms dans le xml renvoyé par le service Web, j'avais ajouté la classe CustomNamespaceXmlFormatter spécifiée dans cet article sous la réponse de @Konamiman: Supprimer l'espace de noms dans xml Le commentateur ci-dessous mentionne un problème de fuite de mémoire. Bien que ANTS Memory Profiler n'ait jamais montré mon service Web générant plusieurs assemblys dynamiques, j'ai néanmoins mis à jour le code pour utiliser quelque chose de similaire à un modèle singleton pour créer les instances de XmlSerializer comme ci-dessous et maintenant mon utilisation de la mémoire est bien sous contrôle (et diminue en fait quand le traitement des requêtes est terminé!).

public class CustomNamespaceXmlFormatter : XmlMediaTypeFormatter
{
    private readonly string defaultRootNamespace;

    public CustomNamespaceXmlFormatter() : this(string.Empty)
    {
    }

    public CustomNamespaceXmlFormatter(string defaultRootNamespace)
    {
        this.defaultRootNamespace = defaultRootNamespace;
    }

    public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
    {
        if (type == typeof(String))
        {
            //If all we want to do is return a string, just send to output as <string>value</string>
            return base.WriteToStreamAsync(type, value, writeStream, content, transportContext);
        }
        else
        {
            XmlRootAttribute xmlRootAttribute = (XmlRootAttribute)type.GetCustomAttributes(typeof(XmlRootAttribute), true)[0];
            if (xmlRootAttribute == null)
                xmlRootAttribute = new XmlRootAttribute(type.Name)
                {
                    Namespace = defaultRootNamespace
                };
            else if (xmlRootAttribute.Namespace == null)
                xmlRootAttribute = new XmlRootAttribute(xmlRootAttribute.ElementName)
                {
                    Namespace = defaultRootNamespace
                };

            var xns = new XmlSerializerNamespaces();
            xns.Add(string.Empty, xmlRootAttribute.Namespace);

            return Task.Factory.StartNew(() =>
            {
                //var serializer = new XmlSerializer(type, xmlRootAttribute); **OLD CODE**
                var serializer = XmlSerializerInstance.GetSerializer(type, xmlRootAttribute);
                serializer.Serialize(writeStream, value, xns);                    
            });
        }
    }
}

public static class XmlSerializerInstance
{
    public static object _lock = new object();
    public static Dictionary<string, XmlSerializer> _serializers = new Dictionary<string, XmlSerializer>();
    public static XmlSerializer GetSerializer(Type type, XmlRootAttribute xra)
    {
        lock (_lock)
        {
            var key = $"{type}|{xra}";
            if (!_serializers.TryGetValue(key, out XmlSerializer serializer))
            {
                if (type != null && xra != null)
                {
                    serializer = new XmlSerializer(type, xra);
                }

                _serializers.Add(key, serializer);
            }

            return serializer;
        }
    }
}


0 commentaires