standard run locally google engine deploy app django google-app-engine django-models bigtable

django - run - google cloud platform python github



¿BigTable es lento o soy tonto? (5)

¿Mi modelo de datos está equivocado? ¿Estoy haciendo las búsquedas mal?

Sí y sí, me temo

En lo que respecta a su modelo de datos, la mejor manera de manejar esto es almacenar la suma contra 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 puedes hacer Memcache en 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.

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.

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 la 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 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á equivocado? ¿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).


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 animan a usar Memcache para hacerlo más rápido.


Incluso si menciona BigTable, creo que está implementando una base de datos relacional en SQL en la nube.

Tu 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 tablas? 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 superaba 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.


Podría ser un poco de ambos ;-)

Si realiza 400 consultas en la tabla de Premios, una para cada resultado devuelto para una consulta en la tabla de asignación, entonces esperaría que fuera doloroso. 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 sencillo (bueno, se veía bastante sencillo cuando eché un vistazo a los documentos de Memcache hace un tiempo. No he necesitado hacerlo) todavía).

Además, si aún no lo sabía, el for result in query.fetch(1000) es mucho más rápido que el 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 puede tener problemas cuando elimina 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 necesariamente hace 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 de almacén de datos en una sola solicitud http, 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.


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 demostrada aquí: Creación de aplicaciones complejas escalables