Redis
 sql >> Database >  >> NoSQL >> Redis

Esecuzione parallela con StackExchange.Redis?

Attualmente, il tuo codice utilizza l'API sincrona (StringSet ), e viene caricato da 10 thread contemporaneamente. Ciò non rappresenterà una sfida apprezzabile per SE.Redis:qui funziona perfettamente. sospetto che si tratta davvero di un timeout in cui il server ha impiegato più tempo di quanto vorresti elaborare alcuni dati, molto probabilmente anche relativi all'allocatore del server. Un'opzione, quindi, è semplicemente aumentare un po' il timeout . Non molto... prova 5 secondi invece di 1 secondo predefinito. È probabile che la maggior parte delle operazioni funzioni comunque molto velocemente.

Per quanto riguarda l'accelerazione:un'opzione qui è non aspettare - cioè mantenere i dati di pipeline. Se ti accontenti di non controllare ogni singolo messaggio per uno stato di errore, un modo semplice per farlo è aggiungere , flags: CommandFlags.FireAndForget alla fine del tuo StringSet chiamata. Nei miei test locali, questo ha accelerato l'esempio 1M del 25% (e sospetto che gran parte del resto del tempo venga effettivamente speso nella serializzazione delle stringhe).

Il problema più grande che ho riscontrato con l'esempio da 10 milioni è stato semplicemente il sovraccarico di lavorare con l'esempio da 10 milioni - soprattutto perché questo richiede enormi quantità di memoria sia per il redis-server e l'applicazione, che (per emulare la tua configurazione) si trova sulla stessa macchina. Ciò crea una pressione di memoria competitiva, con pause GC ecc. nel codice gestito. Ma forse ancora più importante:semplicemente ci vuole un'eternità per iniziare a fare qualsiasi cosa . Di conseguenza, ho rifattorizzato il codice per utilizzare il yield return parallelo generatori piuttosto che un singolo elenco. Ad esempio:

    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;
    }

con:

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

Qui, lo scopo di Batchify è garantire che non stiamo aiutando troppo il server impiegando un tempo apprezzabile tra ogni operazione:i dati vengono inventati in batch da 1000 e ogni batch viene reso disponibile molto rapidamente.

Ero anche preoccupato per le prestazioni di JSON, quindi sono passato a JIL:

    public static string ToJSON<T>(this T obj)
    {
        return Jil.JSON.Serialize<T>(obj);
    }

e poi, solo per divertimento, ho spostato il lavoro JSON nel batch (in modo che l'elaborazione effettiva si ripeta:

 foreach (var element in InventPeople(PER_THREAD * counter1, PER_THREAD)
     .Select(x => new { x.Id, Json = x.ToJSON() }).Batchify(1000))

Questo ha abbassato un po' di più i tempi, quindi posso caricare 10 milioni in 3 minuti e 57 secondi, una velocità di 42.194 rops. La maggior parte di questo tempo è in realtà un'elaborazione locale all'interno dell'applicazione. Se lo cambio in modo che ogni thread carichi lo uguale articolo ITEMS / THREADS volte, questo cambia in 1 minuto e 48 secondi, una velocità di 92.592 rops.

Non sono sicuro di aver risposto a qualcosa in realtà, ma la versione breve potrebbe essere semplicemente "prova un timeout più lungo; considera l'utilizzo del fuoco e dimentica).