Publicado em: segunda-feira, 27 de jan de 2025
Oragon.RabbitMQ 1.1 – Reduzindo Alocações

Então esse foi o dia que queimei a lingua!

Uma das criticas que faço às publicações que falam sobre ganhos absurdos de 50%, 80% e até mais de 100% de performance onde todas as variáveis se mantiveram as mesmas, é que afinal: Ganhamos 100% ou deixamos de perder 50%?

Como o velho ditado “A estatística é a arte de torturar os números até que eles confessem o que você quer demonstrar”.

Bem, eu não passei batido à minha própria critica! Hoje vou falar sobre a redução nas alocações de respostas.

O Oragon RabbitMQ é uma implementação de Minimal Api para o consumo de filas do RabbitMQ. Talvez eu pense algum dia em mudar o nome e criar.

Seguindo o AMQP temos 3 tipos de resposta padrão e outras customizadas

  • Respostas Padrão
    • RejectResult
    • NackResult
    • AckResult
  • Respostas Customizadas
    • ReplyResult
    • ComposableResult

As respostas padrão RejectResult e NackResult , diferente do AckResult, possuem um boolean no construtor que define se há reenfileiramento ou não.

Revisitando a implementação, me incomodou a necessidade de criar uma instância de RejectResult e NackResult , ou AckResult para cada mensagem processada.

Obviamente a Microsoft possui o mesmo problema com os Results das Minimal API’s, então fui ver como a Microsoft resolve o problema por lá, para entender se existia alguma ideia genial ou se era basicamente um cache mesmo.

Em resumo, nas Minimal API’s (Microsoft.AspNetCore.Http.Results) a classe

Microsoft.AspNetCore.Http.<strong>Results</strong>
Microsoft.AspNetCore.Http.Results interage com
Microsoft.AspNetCore.Http.
Microsoft.AspNetCore.Http.
TypedResults
TypedResults
que por sua vez usa o cache implementado em
Microsoft.AspNetCore.Http.<strong>ResultsCache </strong>
Microsoft.AspNetCore.Http.ResultsCache que por sua vez faz cache em memória de 1 instância para cada status HTTP. De 101, passando por 200, até 511.

Então sempre que você não customiza a resposta, um

StatusCodeHttpResult
StatusCodeHttpResult, que está em ResultCache é devolvido, já que essa classe implementa tanto
IResult
IResult quanto
IStatusCodeHttpResult
IStatusCodeHttpResult.

Eu não precisava dessa separação tão rígida em Helper (Results), Factory (TypedResults) e Cache (ResultsCache).

Aqui implementei as 3 responsabilidades em AmqpResults e continuou simples, principalmente pela pouca necessidade de manutenção e ficou assim.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
namespace Oragon.RabbitMQ.Consumer.Actions;
/// <summary>
/// Construct and cache results
/// </summary>
public static class AmqpResults
{
private static readonly AckResult s_forSuccess = new();
private static readonly NackResult s_nackWithRequeue = new(true);
private static readonly NackResult s_nackWithoutRequeue = new(false);
private static readonly RejectResult s_rejectWithRequeue = new(true);
private static readonly RejectResult s_rejectWithoutRequeue = new(false);
/// <summary>
/// Return an AckResult to represents a AMQP Ack
/// </summary>
/// <returns></returns>
public static AckResult Ack() => AmqpResults.s_forSuccess;
/// <summary>
/// Return an NackResult to represents a AMQP Nack
/// </summary>
/// <returns></returns>
public static NackResult Nack(bool requeue) =>
requeue
? AmqpResults.s_nackWithRequeue
: AmqpResults.s_nackWithoutRequeue;
/// <summary>
/// Return an RejectResult to represents a AMQP Reject
/// </summary>
/// <returns></returns>
public static RejectResult Reject(bool requeue) =>
requeue
? AmqpResults.s_rejectWithRequeue
: AmqpResults.s_rejectWithoutRequeue;
/// <summary>
/// Return a ReplyResult to represents a AMQP Reply
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="objectToReturn"></param>
/// <returns></returns>
public static ReplyResult<T> Reply<T>(T objectToReturn) => new(objectToReturn);
/// <summary>
/// Return a ComposableResult to execute multiple steps
/// </summary>
/// <param name="results"></param>
/// <returns></returns>
public static ComposableResult Compose(params IAmqpResult[] results) => new(results);
}
namespace Oragon.RabbitMQ.Consumer.Actions; /// <summary> /// Construct and cache results /// </summary> public static class AmqpResults { private static readonly AckResult s_forSuccess = new(); private static readonly NackResult s_nackWithRequeue = new(true); private static readonly NackResult s_nackWithoutRequeue = new(false); private static readonly RejectResult s_rejectWithRequeue = new(true); private static readonly RejectResult s_rejectWithoutRequeue = new(false); /// <summary> /// Return an AckResult to represents a AMQP Ack /// </summary> /// <returns></returns> public static AckResult Ack() => AmqpResults.s_forSuccess; /// <summary> /// Return an NackResult to represents a AMQP Nack /// </summary> /// <returns></returns> public static NackResult Nack(bool requeue) => requeue ? AmqpResults.s_nackWithRequeue : AmqpResults.s_nackWithoutRequeue; /// <summary> /// Return an RejectResult to represents a AMQP Reject /// </summary> /// <returns></returns> public static RejectResult Reject(bool requeue) => requeue ? AmqpResults.s_rejectWithRequeue : AmqpResults.s_rejectWithoutRequeue; /// <summary> /// Return a ReplyResult to represents a AMQP Reply /// </summary> /// <typeparam name="T"></typeparam> /// <param name="objectToReturn"></param> /// <returns></returns> public static ReplyResult<T> Reply<T>(T objectToReturn) => new(objectToReturn); /// <summary> /// Return a ComposableResult to execute multiple steps /// </summary> /// <param name="results"></param> /// <returns></returns> public static ComposableResult Compose(params IAmqpResult[] results) => new(results); }
namespace Oragon.RabbitMQ.Consumer.Actions;

/// <summary>
/// Construct and cache results
/// </summary>
public static class AmqpResults
{

    private static readonly AckResult s_forSuccess = new();
    private static readonly NackResult s_nackWithRequeue = new(true);
    private static readonly NackResult s_nackWithoutRequeue = new(false);
    private static readonly RejectResult s_rejectWithRequeue = new(true);
    private static readonly RejectResult s_rejectWithoutRequeue = new(false);



    /// <summary>
    /// Return an AckResult to represents a AMQP Ack
    /// </summary>
    /// <returns></returns>
    public static AckResult Ack() => AmqpResults.s_forSuccess;


    /// <summary>
    /// Return an NackResult to represents a AMQP Nack
    /// </summary>
    /// <returns></returns>
    public static NackResult Nack(bool requeue) =>
        requeue
        ? AmqpResults.s_nackWithRequeue
        : AmqpResults.s_nackWithoutRequeue;

    /// <summary>
    /// Return an RejectResult to represents a AMQP Reject
    /// </summary>
    /// <returns></returns>
    public static RejectResult Reject(bool requeue) =>
        requeue
        ? AmqpResults.s_rejectWithRequeue
        : AmqpResults.s_rejectWithoutRequeue;


    /// <summary>
    /// Return a ReplyResult to represents a AMQP Reply
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="objectToReturn"></param>
    /// <returns></returns>
    public static ReplyResult<T> Reply<T>(T objectToReturn) => new(objectToReturn);

    /// <summary>
    /// Return a ComposableResult to execute multiple steps
    /// </summary>
    /// <param name="results"></param>
    /// <returns></returns>
    public static ComposableResult Compose(params IAmqpResult[] results) => new(results);


}

Assim segui a mesma estratégia da Microsoft e reduzi drasticamente a criação de novas instâncias para cada mensagem consumida.

Isso já pode ser visto na versão 1.1 que foi disponibilizada na última semana.

Exemplos de Uso

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
AmqpResults.Ack();
AmqpResults.Nack(requeue: true);
AmqpResults.Nack(requeue: false);
AmqpResults.Reject(requeue: true);
AmqpResults.Reject(requeue: false);
AmqpResults.Ack(); AmqpResults.Nack(requeue: true); AmqpResults.Nack(requeue: false); AmqpResults.Reject(requeue: true); AmqpResults.Reject(requeue: false);
AmqpResults.Ack();

AmqpResults.Nack(requeue: true);
AmqpResults.Nack(requeue: false);

AmqpResults.Reject(requeue: true);
AmqpResults.Reject(requeue: false);

Os demais usos como ReplyResult e ComposableResult em comento em algum espaço apropriado.

Oragon.RabbitMQ

Quality Gate Status
Bugs
Code Smells
Coverage
Duplicated Lines (%)
Reliability Rating
Security Rating
Technical Debt
Maintainability Rating
Vulnerabilities
GitHub last commit
NuGet Downloads
GitHub Repo stars
Roadmap

Official Release

NuGet Version

Others

GitHub Tag
GitHub Release
MyGet Version

0 comentários

Enviar um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Este site utiliza o Akismet para reduzir spam. Saiba como seus dados em comentários são processados.

Lives

Fique de olho nas lives

Fique de olho nas lives no meu canal do Youtube, no Canal .NET e nos Grupos do Facebook e Instagram.

Aceleradores

Existem diversas formas de viabilizar o suporte ao teu projeto. Seja com os treinamentos, consultoria, mentorias em grupo.