2009-06-05 19 views
25

Básicamente tengo el modelo clásico de muchos a muchos. Un usuario, un premio y un mapeo de tabla "muchos a muchos" entre usuarios y premios.¿BigTable es lento o soy tonto?

Cada usuario tiene del orden de 400 premios y cada premio se otorga a aproximadamente la mitad de los usuarios.

Quiero iterar sobre todos los premios del usuario y resumir sus puntos. En SQL, sería una combinación de tabla entre muchos y muchos y luego recorrería cada una de las filas. En una máquina decente con una instancia de MySQL, 400 filas no deberían ser un gran problema.

En el motor de aplicación estoy viendo alrededor de 10 segundos para hacer la suma. La mayor parte del tiempo se gasta en el almacén de datos de Google. Aquí están las primeras pocas filas de cProfile

 
    ncalls tottime percall cumtime percall filename:lineno(function) 
     462 6.291 0.014 6.868 0.015 {google3.apphosting.runtime._apphosting_runtime___python__apiproxy.Wait} 
     913 0.148 0.000 1.437 0.002 datastore.py:524(_FromPb) 
    8212 0.130 0.000 0.502 0.000 datastore_types.py:1345(FromPropertyPb) 
     462 0.120 0.000 0.458 0.001 {google3.net.proto._net_proto___parse__python.MergeFromString} 

¿Mi modelo de datos está mal? ¿Estoy haciendo las búsquedas mal? ¿Es esto una deficiencia que tengo que lidiar con el almacenamiento en caché y el bulkupdating (que sería un dolor real en el culo).

+9

+1 LOL. Me encanta el título de esta pregunta! – Elijah

+1

¿BigTable de Google no es básicamente una tabla hash? – balpha

+0

Esa función superior como Espera ... ¿por qué estás pasando 6 segundos en espera? – workmad3

Respuesta

20

podría ser un poco de ambas cosas ;-)

Si estás haciendo 400 consultas en la tabla de premios, uno para cada resultado devuelto por una consulta en la tabla de asignación, entonces yo esperaría que para ser dolorosa . El límite de 1000 resultados en las consultas está ahí porque BigTable cree que devolver 1000 resultados está en el límite de su capacidad para operar en un tiempo razonable. De acuerdo con la arquitectura, esperaría que las 400 consultas fueran mucho más lentas que una consulta que arrojó 400 resultados (400 log N vs. (log M) + 400).

La buena noticia es que en GAE, memcaching una única tabla de acceso que contiene todos los premios y sus valores de puntos es bastante simple (bueno, parecía bastante simple cuando eché un vistazo a los documentos de Memcache hace un tiempo. necesitaba hacerlo todavía).

Además, si no lo sabía, for result in query.fetch(1000) es mucho más rápido que for result in query, y está limitado a 1000 resultados de cualquier manera. Las ventajas de este último son (1) podría ser más rápido si rescatas temprano, y (2) si Google alguna vez aumenta el límite más allá de 1000, obtiene el beneficio sin un cambio de código.

También es posible que tenga problemas al eliminar un usuario (o un premio). Encontré en una prueba que podía eliminar 300 objetos dentro del límite de tiempo. Esos objetos eran más complejos que sus objetos de mapeo, teniendo 3 propiedades y 5 índices (incluyendo los implícitos), mientras que su tabla de mapeo probablemente solo tenga 2 propiedades y 2 índices (implícitos). [Editar: me di cuenta de que hice esta prueba antes de saber que db.delete() puede tomar una lista, que probablemente sea mucho más rápida].

BigTable no hace necesariamente las cosas que las bases de datos relacionales están diseñadas para funcionar bien. En cambio, distribuye bien los datos en muchos nodos. Pero casi todos los sitios web funcionan bien con un cuello de botella en un solo servidor db, y por lo tanto no estrictamente necesitan lo que hace BigTable.

Otra cosa: si realiza 400 consultas en el almacén de datos en una sola solicitud HTTP, entonces encontrará que acierta la cuota fija de su almacén de datos mucho antes de alcanzar su cuota fija de solicitud.Por supuesto, si estás dentro de las cuotas, o si estás golpeando algo más primero, entonces esto podría ser irrelevante para tu aplicación. Pero la relación entre las dos cuotas es algo así como 8: 1, y lo tomo como una pista de lo que Google espera que sea mi modelo de datos.

+4

Excelentes consejos. Parece que debería pasar a los modelos django normales y almacenarlo todo en MySQL hasta que llegue a un problema de escala. –

+2

Si sus datos son mejores en MySQL que en BigTable, creo que debe preguntarse por qué está utilizando el motor de aplicaciones. Si hay una buena razón ("hosting gratuito", por ejemplo), entonces supongo que sí, pero a mí me parece un truco. BigTable (y en general la distribución en la nube de Google) es probablemente la única diferencia técnica interesante entre GAE y cualquier pila de LAMP antigua. –

+4

O podría reconsiderar sus modelos. Con el almacén de datos appengine, no desea iterar filas durante una solicitud, en su lugar, sacar una fila rápidamente. Una forma de hacerlo es mantener sus totales/subtotales/agregados actualizados en el momento de la escritura, no en el tiempo de lectura. Otra forma de hacerlo es ejecutar procesos en segundo plano (ya sea con su cron o remote_api) para actualizar totales/subtotales/agregados de forma asincrónica. – dar

0

Google BigTable se ejecuta en Google Distributed File System.

Los datos se distribuyen. Tal vez 400 filas mysql todavía tienen mejor, pero para datos más grandes, Google BigTable podría ser más rápido.

Creo que es por eso que nos alientan a usar Memcache para hacerlo más rápido.

19

¿El modelo de datos es el correcto? ¿Estoy haciendo las búsquedas incorrectas?

Sí y sí, me temo.

En lo que respecta a su modelo de datos, la mejor manera de manejar esto es almacenar la suma en el registro del usuario y actualizarla cuando un usuario gana/pierde un premio. Realmente no tiene sentido contar su puntuación cada vez que la gran mayoría de las veces se mantendrá sin cambios. Si hace que la entidad "User Award" escriba una entidad secundaria del "Usuario", puede actualizar el puntaje e insertar o eliminar la entrada UserAward en una sola transacción atómica, asegurando que su recuento sea siempre exacto.

onebyone señala que puede recuperar la memoria de la mesa de premios. Es una buena idea, pero dada la cantidad limitada de datos, una mejor aún es almacenarla en la memoria local. Los miembros globales persisten entre las solicitudes HTTP, y como supongo que no actualiza la tabla de premios con frecuencia, no tiene que preocuparse demasiado por la invalidación de la memoria caché. Simplemente cárguelo en la primera solicitud (o incluso codifíquelo en su fuente). Si cambia la lista de premios, la implementación de una nueva actualización menor restablecerá todas las instancias, lo que ocasionará que se vuelvan a cargar.

Para las búsquedas, tenga en cuenta que un costo sustancial de realizar operaciones de almacenamiento de datos es el tiempo de ida y vuelta. Una operación get(), que busca 1 o más registros por ID (¡puede realizar un lote!) Toma alrededor de 20-40 ms. Una consulta, sin embargo, toma alrededor de 160-200ms. Por lo tanto, el poder de la desnormalización.

+0

Gracias. Simplifiqué mi problema un poco por el bien de esta pregunta. Actualizo los premios bastante, así como los premiados. Y para devolver los "UserAwards" voy a necesitar más información que solo los puntos. Me gustaría el icono para el premio, y probablemente el título. ¿Su lote se obtiene() en las referencias? Cuando tengo mis 400 filas de UserAward y empiezo a caminar para obtener el userAward.award, ¿recibirá la identificación y las lotes? Eso podría ser el salvavidas allí mismo. –

+0

No es posible procesar por lotes la resolución de resolución de la propiedad de la manera "natural". Lo que puede hacer es llamar a myent.properties() ['propname']. Get_value_for_datastore (myent) para recuperar la clave, lo que le permite agrupar las cosas. Incluso si actualiza los premios mucho, aún sugeriría almacenarlos todos en la memoria local o en Memcache, con alguna forma de invalidar el caché. Su otra opción es usar un ListProperty (referencia) de premios en cada entidad. Si no necesita buscar a los usuarios por sus premios, puede establecer indexed = False para disminuir la sobrecarga, también. –

1

Un idioma importante del motor de aplicaciones es que el almacenamiento es barato, pero el tiempo nunca es excedente. Parece que la mejor manera de hacer muchas o muchas relaciones en el motor de la aplicación es simplemente almacenar la información en ambos lados. IE un usuario tiene una lista de premios y cada premio tiene una lista de usuarios. Para buscar todos los premios que un usuario tiene, simplemente consulta la tabla de premios para un determinado usuario.

Esta idea está bien demostrado aquí: Building Scalable Complex Apps

0

Incluso se menciona BigTable, creo que va a implementar una base de datos relacional en la nube SQL.

Su modelo está bien, es la forma correcta de hacer algo como esto. No veo una buena razón para des-normalizar los agregados en la tabla de usuarios.

Creó índices para la unión rápida de la tabla. Es bastante simple. Es posible que necesite índices BTree para todos los campos que implican la unión de tablas. No es necesario indexar el campo de agregación (del que se toma la SUMA). Básicamente, ambas claves externas de la tabla N: N deben indexarse. Si esas claves foráneas se refieren a la clave primaria de otras dos tablas, eso es suficiente.

Por encima del orden de 100 filas, un simple índice de BTree en claves externas puede tener un aumento decente y notable en el rendimiento.

Estoy ejecutando una base de datos en CloudSQL donde algunas tablas de borde tienen más de 2 millones de registros. Solo después de los 2,5 millones de registros estoy considerando cierta desnormalización, y también hay algunos índices adicionales, y todavía se están agregando para la suma. De lo contrario, estaría haciendo actualizaciones innecesarias al campo SUM siempre que se agreguen nuevos registros.

Solo cuando la tabla superó el millón de registros, tuvimos que considerar el uso de una réplica de lectura.Y fue entonces cuando pudimos distinguir entre procesos que solo leen algunas tablas y no escrituras.

Si está utilizando Django, tenga cuidado cuando implemente LIMIT según su documentación; porque es muy engañoso. Cuando [: 100] (empalme) en un conjunto de registros, no es lo que espera en el SQL que realmente se envía al servidor SQL. Me fue muy difícil descifrarlo. Django no es una buena opción cuando planeas hacer algo que genere a gran escala. Pero en el orden de 1000 registros, estaría bien.

Cuestiones relacionadas