¿Métodos eficientes para almacenar decenas de millones de objetos para consultas, con un alto número de inserciones por segundo?

14

Esta es básicamente una aplicación de registro / conteo que cuenta el número de paquetes y el tipo de paquete, etc. en una red de chat p2p. Esto equivale a aproximadamente 4-6 millones de paquetes en un período de 5 minutos. Y debido a que solo tomo una "instantánea" de esta información, solo estoy eliminando paquetes de más de 5 minutos cada cinco minutos. Por lo tanto, el máximo de artículos que estarán en esta colección es de 10 a 12 millones.

Debido a que necesito hacer 300 conexiones a diferentes superpeladores, es posible que cada paquete intente insertarse al menos 300 veces (lo cual es probablemente la razón por la que mantener estos datos en la memoria es la única opción razonable).

Actualmente, he estado usando un Diccionario para almacenar esta información. Pero debido a la gran cantidad de elementos que trato de almacenar, tengo problemas con el montón de objetos grandes y la cantidad de uso de memoria crece continuamente con el tiempo.

Dictionary<ulong, Packet>

public class Packet
{
    public ushort RequesterPort;
    public bool IsSearch;
    public string SearchText;
    public bool Flagged;
    public byte PacketType;
    public DateTime TimeStamp;
}

He intentado usar mysql, pero no pude estar al día con la cantidad de datos que necesito insertar (mientras verificaba que no fuera un duplicado), y eso fue mientras utilizaba las transacciones.

Intenté con mongodb, pero el uso de la CPU para eso fue una locura y tampoco se mantuvo.

Mi problema principal surge cada 5 minutos, porque elimino todos los paquetes que tienen más de 5 minutos y tomo una "instantánea" de estos datos. Como uso consultas LINQ para contar el número de paquetes que contienen un determinado tipo de paquete. También estoy llamando a una consulta distinta () sobre los datos, donde elimino 4 bytes (dirección IP) de la clave del par de claves, y la combino con el valor de requestingport en el Valor de la clave de parentesco y la uso para obtener un número distinto de compañeros de todos los paquetes.

La aplicación actualmente tiene alrededor de 1.1 GB de uso de memoria, y cuando se llama a una instantánea puede llegar al doble de uso.

Ahora bien, esto no sería un problema si tengo una cantidad increíble de ram, pero el vm con el que tengo esta ejecución está limitado a 2GB de ram en este momento.

¿Hay alguna solución fácil?

    
pregunta Josh 14.03.2012 - 08:01

5 respuestas

11

En lugar de tener un diccionario y buscar entradas en el diccionario que sean demasiado antiguas; tiene 10 diccionarios. Cada 30 segundos más o menos, crea un nuevo diccionario "actual" y descarta el diccionario más antiguo sin ninguna búsqueda.

A continuación, cuando deseche el diccionario más antiguo, coloque todos los objetos antiguos en una cola FILO para más adelante, y en lugar de usar "nuevo" para crear nuevos objetos, saque un objeto viejo de la cola FILO y use un método para reconstruir el objeto antiguo (a menos que la cola de objetos antiguos esté vacía). Esto puede evitar muchas asignaciones y una gran cantidad de gastos generales de recolección de basura.

    
respondido por el Brendan 15.03.2012 - 22:23
3

El primer pensamiento que viene a la mente es por qué esperas 5 minutos. ¿Podría hacer las instantáneas con más frecuencia y así reducir la gran sobrecarga que ve en el límite de 5 minutos?

En segundo lugar, LINQ es excelente para código conciso, pero en realidad LINQ es azúcar sintáctica en C # "regular" y no hay garantía de que genere el código más óptimo. Como ejercicio, puede intentar reescribir los puntos importantes sin LINQ, es posible que no mejore el rendimiento, pero tendrá una idea más clara de lo que está haciendo y facilitará el trabajo de creación de perfiles.

Otra cosa a considerar son las estructuras de datos. No sé qué hace con sus datos, pero ¿podría simplificar los datos que almacena de alguna manera? ¿Podría usar una cadena o una matriz de bytes y luego extraer partes relevantes de esos elementos cuando los necesite? ¿Podría usar una estructura en lugar de una clase e incluso hacer algo malo con stackalloc para reservar memoria y evitar las ejecuciones de GC?

    
respondido por el Steve Haigh 15.03.2012 - 15:04
3

Enfoque simple: intente memcached .

  • Está optimizado para ejecutar tareas como esta.
  • Puede reutilizar la memoria de repuesto en cajas menos ocupadas, no solo en su caja dedicada.
  • Tiene un mecanismo de caducidad de caché incorporado, que es perezoso, por lo que no hay contratiempos.

El inconveniente es que está basado en la memoria y no tiene ninguna persistencia. Si una instancia está inactiva, los datos se han ido. Si necesita persistencia, serialice los datos usted mismo.

Enfoque más complejo: intente Redis .

El inconveniente es que es un poco más complejo.

    
respondido por el 9000 15.03.2012 - 22:49
1

No tiene que almacenar todos los paquetes para las consultas que ha mencionado. Por ejemplo, contador de tipo de paquete:

Necesitas dos arreglos:

int[] packageCounters = new int[NumberOfTotalTypes];
int[,] counterDifferencePerMinute = new int[6, NumberOfTotalTypes];

La primera matriz realiza un seguimiento de cuántos paquetes en diferentes tipos. La segunda matriz realiza un seguimiento de cuántos paquetes más se han agregado en cada minuto, por lo que sabe cuántos paquetes deben eliminarse en cada intervalo de minutos. Espero que puedan ver que la segunda matriz se usa como una cola FIFO redonda.

Entonces, para cada paquete, se realizan las siguientes operaciones:

packageCounters[packageType] += 1;
counterDifferencePerMinute[current, packageType] += 1;
if (oneMinutePassed) {
  current = (current + 1) % 6;
  for (int i = 0; i < NumberOfTotalTypes; i++) {
    packageCounters[i] -= counterDifferencePerMinute[current, i];
    counterDifferencePerMinute[current, i] = 0;
}

En cualquier momento, el contador puede recuperar los contadores de paquetes al instante y no almacenamos todos los paquetes.

    
respondido por el Codism 15.03.2012 - 23:15
1

(Sé que esta es una pregunta antigua, pero la encontré mientras buscaba una solución a un problema similar donde el pase de recolección de basura de la segunda generación estaba deteniendo la aplicación durante varios segundos, por lo que grabé para otras personas en situaciones similares) .

Use una estructura en lugar de una clase para sus datos (pero recuerde que se trata como un valor con semántica de paso por copia). Esto elimina un nivel de búsqueda, el gc tiene que hacer cada paso de marca.

Use matrices (si conoce el tamaño de los datos que está almacenando) o Lista - que usa matrices internamente. Si realmente necesita el acceso aleatorio rápido, use un diccionario de índices de matriz. Esto elimina otro par de niveles (o una docena o más si está utilizando un SortedDictionary) para que el gc tenga que buscar.

Dependiendo de lo que esté haciendo, la búsqueda de una lista de estructuras puede ser más rápida que la búsqueda en el diccionario (debido a la localización de la memoria): perfil para su aplicación en particular.

La combinación de struct & list reduce significativamente el uso de memoria y el tamaño del barrido del recolector de basura.

    
respondido por el Malcolm 03.02.2014 - 17:26

Lea otras preguntas en las etiquetas