Redis
 sql >> Base de Dados >  >> NoSQL >> Redis

Execução paralela com StackExchange.Redis?


Atualmente, seu código está usando a API síncrona (StringSet ), e está sendo carregado por 10 threads simultaneamente. Isso não representará nenhum desafio apreciável para o SE.Redis - funciona muito bem aqui. Eu suspeito que realmente é um tempo limite em que o servidor demorou mais do que você gostaria para processar alguns dos dados, provavelmente também relacionados ao alocador do servidor. Uma opção, então, é simplesmente aumentar um pouco o tempo limite . Não muito... tente 5 segundos em vez do 1 segundo padrão. Provavelmente, a maioria das operações está funcionando muito rápido de qualquer maneira.

Com relação à aceleração:uma opção aqui é não esperar - ou seja, manter dados de pipeline. Se você está satisfeito em não verificar todas as mensagens para um estado de erro, então uma maneira simples de fazer isso é adicionar , flags: CommandFlags.FireAndForget ao final do seu StringSet ligar. Em meus testes locais, isso acelerou o exemplo de 1 milhão em 25% (e suspeito que muito do resto do tempo é gasto na serialização de strings).

O maior problema que tive com o exemplo de 10 milhões foi simplesmente a sobrecarga de trabalhar com o exemplo de 10 milhões - especialmente porque isso consome grandes quantidades de memória tanto para o redis-server e o aplicativo, que (para emular sua configuração) estão na mesma máquina. Isso cria pressão de memória concorrente, com pausas de GC etc no código gerenciado. Mas talvez o mais importante:simplesmente leva uma eternidade para começar a fazer qualquer coisa . Consequentemente, refatorei o código para usar o yield return paralelo geradores em vez de uma única lista. Por exemplo:
    static IEnumerable<Person> InventPeople(int seed, int count)
    {
        for(int i = 0; i < count; i++)
        {
            int f = 1 + seed + i;
            var item = new Person
            {
                Id = f,
                Name = Path.GetRandomFileName().Replace(".", "").Substring(0, appRandom.Value.Next(3, 6)) + " " + Path.GetRandomFileName().Replace(".", "").Substring(0, new Random(Guid.NewGuid().GetHashCode()).Next(3, 6)),
                Age = f % 90,
                Friends = ParallelEnumerable.Range(0, 100).Select(n => appRandom.Value.Next(1, f)).ToArray()
            };
            yield return item;
        }
    }

    static IEnumerable<T> Batchify<T>(this IEnumerable<T> source, int count)
    {
        var list = new List<T>(count);
        foreach(var item in source)
        {
            list.Add(item);
            if(list.Count == count)
            {
                foreach (var x in list) yield return x;
                list.Clear();
            }
        }
        foreach (var item in list) yield return item;
    }

com:
foreach (var element in InventPeople(PER_THREAD * counter1, PER_THREAD).Batchify(1000))

Aqui, o propósito de Batchify é garantir que não estamos ajudando muito o servidor, levando um tempo considerável entre cada operação - os dados são inventados em lotes de 1000 e cada lote é disponibilizado muito rapidamente.

Eu também estava preocupado com o desempenho do JSON, então mudei para o JIL:
    public static string ToJSON<T>(this T obj)
    {
        return Jil.JSON.Serialize<T>(obj);
    }

e então, apenas por diversão, movi o trabalho JSON para o lote (para que os loops de processamento reais:
 foreach (var element in InventPeople(PER_THREAD * counter1, PER_THREAD)
     .Select(x => new { x.Id, Json = x.ToJSON() }).Batchify(1000))

Isso diminuiu um pouco mais os tempos, então posso carregar 10M em 3 minutos e 57 segundos, uma taxa de 42.194 rops. A maior parte desse tempo é, na verdade, processamento local dentro do aplicativo. Se eu alterar para que cada thread carregue o mesmo item ITEMS / THREADS vezes, isso muda para 1 minuto e 48 segundos - uma taxa de 92.592 rops.

Não tenho certeza se respondi alguma coisa realmente, mas a versão curta pode ser simplesmente "experimente um tempo limite mais longo; considere usar acionar e esquecer).