2011-11-16 11 views
6

Tengo una herramienta de béisbol que permite a los usuarios analizar las estadísticas históricas de bateo de un jugador. Por ejemplo, ¿cuántos éxitos tiene A-Rod en los últimos 7 días durante las condiciones nocturnas? Quiero ampliar el cronograma para que un usuario pueda analizar las estadísticas de bateo de un jugador hasta 365 días. Sin embargo, hacerlo requiere una optimización del rendimiento seria. Aquí están mis conjunto actual de modelos:Actualización masiva de registros - optimización del rendimiento

class AtBat < ActiveRecord::Base 
    belongs_to :batter 
    belongs_to :pitcher 
    belongs_to :weather_condition 

    ### DATA MODEL ### 
    # id 
    # batter_id 
    # pitcher_id 
    # weather_condition_id 
    # hit (boolean) 
    ################## 
end 

class BattingStat < ActiveRecord::Base 
    belongs_to :batter 
    belongs_to :recordable, :polymorphic => true # e.g., Batter, Pitcher, WeatherCondition 

    ### DATA MODEL ### 
    # id 
    # batter_id 
    # recordable_id 
    # recordable_type 
    # hits7 
    # outs7 
    # at_bats7 
    # batting_avg7 
    # ... 
    # hits365 
    # outs365 
    # at_bats365 
    # batting_avg365 
    ################## 
end 

class Batter < ActiveRecord::Base 
    has_many :batting_stats, :as => :recordable, :dependent => :destroy 
    has_many :at_bats, :dependent => :destroy 
end 

class Pitcher < ActiveRecord::Base 
    has_many :batting_stats, :as => :recordable, :dependent => :destroy 
    has_many :at_bats, :dependent => :destroy 
end 

class WeatherCondition < ActiveRecord::Base 
    has_many :batting_stats, :as => :recordable, :dependent => :destroy 
    has_many :at_bats, :dependent => :destroy 
end 

En aras de mantener mi cuestión en un plazo razonable, que me narro lo que estoy haciendo para actualizar la tabla batting_stats en lugar de copiar un montón de código. Comencemos con 7 días.

  1. Recupera todos los registros de at_bat en los últimos 7 días.
  2. iterar sobre cada at_bat disco ...
  3. Dado un registro at_bat, agarra la masa asociada y WEATHER_CONDITION asociado, encontrar el registro correcto batting_stat (BattingStat.find_or_create_by_batter_and_recordable (bateador, WEATHER_CONDITION), a continuación, actualizar el registro batting_stat.
  4. Repetir . Paso 3 para bateador y el lanzador (Recordable)

Pasos 1-4 se repite para otros períodos de tiempo, así - 15 días, 30 días, etc.

Ahora imaginar cómo laborioso esto sería a ejecute un script todos los días para hacer estas actualizaciones si tuviera que expandir los períodos de tiempo de un mangeable 7/15/30 a 7/15/30/45/60/90/180/365.

Así que mi pregunta es ¿cómo te acercarías a conseguir que esto funcione al más alto nivel de rendimiento?

+0

He creado un sistema similar para una aplicación de golf. Estoy dispuesto a compartir, pero requiere una explicación bastante extensa. ¿Estás dispuesto a modificar tu arquitectura o solo estás buscando una manera de optimizar la arquitectura que tienes actualmente? – mnelson

+0

Sería muy apreciado escuchar cómo lo hiciste. Dispuesto a actualizar Arch, pero con problemas en el camino. – keruilin

+0

¿Con cuántos registros está tratando? No puede haber tantos puntos de datos para el béisbol, seguramente (¿cientos de miles?). ¿No puedes simplemente guardar el lote en la memoria, posiblemente cortado por jugador en un mapa si es necesario y calcularlo todo sobre la marcha? –

Respuesta

3

AR no está diseñado para hacer un procesamiento masivo como este. Probablemente sea mejor que haga sus actualizaciones por lotes al ingresar al SQL propiamente dicho y haciendo un INSERT FROM SELECT (o tal vez usando una gema que hizo esto por usted)

1

Esencialmente necesita almacenar los datos de tal forma que pueda desconectar el último día y reemplazarlo por un nuevo primer día de modo tal que no tenga que recalcular el total.

Una forma de hacerlo sería almacenar el valor anterior de la suma y restar el valor del último día y luego agregar el nuevo valor del día y luego dividir por 15/30/90/365 lo que sea.

Eso convierte 366 operaciones en 3. ¿Ahora está leyendo desde la base de datos más lento que 363 operaciones?

Esto también le ahorra las iteraciones, por lo que todo lo que necesita hacer es verificar cada día qué condiciones del clima deben actualizarse.

0

Tenemos un problema similar con la carga por lotes de 600,000 registros de alquiler en EE. UU. datos cada semana. Para procesar cada registro en serie tomaría más de 24 horas. Pero no era necesariamente la base de datos lo que constituía el cuello de botella: aunque cada inserción requería un tiempo fijo, la actividad no convertía la base de datos en maxxed/pegged/flatlined.

Sabía que dividir un archivo en registros de cadenas individuales era fácil y rápido. En nuestro caso, el archivo de entrada tenía forma de XML, y usé un Java StringTokenizer simple para dividir el archivo en las ... etiquetas.

Eso rápidamente me dio una gran cantidad de fragmentos XML que contenían la información de propiedades de alquiler que necesitaba analizar e importar.

Luego utilicé la convención Java ThreadPoolExecutor/FutureTask/Callable para crear un grupo de 20 subprocesos que tomara cada fragmento XML como entrada, extraiga los datos relevantes y realice las inserciones de la base de datos. No sé cuál sería el equivalente de su arquitectura, pero supongo que hay algo similar.

Al final, pude ajustar el tamaño del subproceso de subprocesos para maximizar el rendimiento del registro mediante la supervisión de la carga del servidor de base de datos bajo diferentes condiciones de prueba. Nos decidimos por un tamaño de subproceso de 25.

0

Cuando he tenido que hacer este tipo de trabajo antes, descubro mis referencias de SQL y me refresco la idea de cómo hacer actualizaciones complejas. Por lo general, puedes hacer muchas actualizaciones de forma breve con una buena consulta. Además, debería poder encontrar asistencia directa con la consulta (publique su esquema y las consultas iniciales en un momento si son realmente grandes)

Tuve que generar un valor counter_cache recientemente, y antes de hacerlo como un grupo de código ruby ​​carga de los padres y contando sus hijos, le di esta consulta un tiro:

UPDATE rates r SET children_count = child_counts.my_count from (SELECT parent_id, count(*) as my_count FROM rates GROUP BY parent_id having parent_id is not null) as child_counts where child_counts.parent_id = r.id; 

que actualizó 200k filas en tan sólo unos segundos

Si no puede hacerlo en una consulta, y si es una operación de una sola vez, puede dividir su proceso en 2 pasos. Primero haga los pesados ​​y almacene los resultados en una nueva tabla, luego lea de esa tabla y haga la actualización final. Tuve que hacer una agregación masiva de datos recientemente, y todo el trabajo pesado tomó 2 días de procesamiento y cálculos. El resultado se colocó en una nueva tabla con una identificación de fila relacionada y un total final. En producción, entonces solo tenía un guión rápido que leía de esa nueva tabla y actualizaba las filas relacionadas. Esto también me permitió detenerme y reiniciar desde donde lo había dejado, y verificar previamente los resultados antes de la actualización del producto. Además, hizo que la actualización del producto fuera realmente rápida.

Al hacer esto, también aprendí que es importante hacer su trabajo en tandas si es posible y comprometer la transacción con la mayor frecuencia posible/de forma segura, para que no se aferre a una transacción grande por demasiado tiempo .

Cuestiones relacionadas