La gestión de bases de datos es uno de los aspectos más cruciales del desarrollo de back-end. Una base de datos correctamente optimizada puede ayudar a reducir el tiempo de respuesta y, por lo tanto, conducir a una mejor experiencia de usuario.
En este artículo, discutiremos las formas de optimizar la velocidad de la base de datos en las aplicaciones de Django. Aunque no profundizaremos en cada concepto individualmente, por lo tanto, consulte la documentación oficial de Django para obtener detalles completos.
Comprender los conjuntos de consultas en Django es la clave para la optimización, por lo tanto, recuerde lo siguiente:
len()
, count()
, etc. Asegúrese de hacer el mejor uso de ellos.
La indexación de bases de datos es una técnica para acelerar las consultas mientras se recuperan registros de una base de datos. A medida que la aplicación aumenta de tamaño, puede ralentizarse y los usuarios lo notarán, ya que llevará mucho más tiempo obtener los datos necesarios. Por lo tanto, la indexación es una operación no negociable cuando se trabaja con grandes bases de datos que generan un gran volumen de datos.
La indexación es un método para clasificar una gran cantidad de datos en función de varios campos. Cuando crea un índice en un campo de una base de datos, crea otra estructura de datos que contiene el valor del campo, así como un puntero al registro con el que está relacionado. Luego, esta estructura de índice se ordena, lo que hace posible las búsquedas binarias.
Por ejemplo, aquí hay un modelo de Django llamado Venta:
# models.py from django.db import models class Sale(models.Model): sold_at = models.DateTimeField( auto_now_add=True, ) charged_amount = models.PositiveIntegerField()
La indexación de la base de datos se puede agregar a un campo en particular mientras se define un modelo Django de la siguiente manera:
# models.py from django.db import models class Sale(models.Model): sold_at = models.DateTimeField( auto_now_add=True, db_index=True, #DB Indexing ) charged_amount = models.PositiveIntegerField()
Si ejecuta las migraciones para este modelo, Django creará un índice de base de datos en la tabla Ventas y se bloqueará hasta que se complete el índice. En una configuración de desarrollo local, con una pequeña cantidad de datos y muy pocas conexiones, esta migración puede parecer instantánea, pero cuando hablamos del entorno de producción, hay grandes conjuntos de datos con muchas conexiones simultáneas que pueden causar tiempos de inactividad como obtener un bloqueo y crear un índice de base de datos puede llevar mucho tiempo.
También puede crear un índice único para dos campos como se muestra a continuación:
# models.py from django.db import models class Sale(models.Model): sold_at = models.DateTimeField( auto_now_add=True, db_index=True, #DB Indexing ) charged_amount = models.PositiveIntegerField() class Meta: indexes = [ ["sold_at", "charged_amount"]]
El almacenamiento en caché de la base de datos es uno de los mejores enfoques para obtener una respuesta rápida de una base de datos. Garantiza que se realicen menos llamadas a la base de datos, evitando la sobrecarga. Una operación de almacenamiento en caché estándar sigue la siguiente estructura:
Django proporciona un mecanismo de almacenamiento en caché que puede usar diferentes backends de almacenamiento en caché como Memcached y Redis que le permiten evitar ejecutar las mismas consultas varias veces.
Memcached es un sistema en memoria de código abierto que garantiza proporcionar resultados almacenados en caché en menos de un milisegundo. Es fácil de configurar y escalar. Redis, por otro lado, es una solución de almacenamiento en caché de código abierto con características similares a Memcached. La mayoría de las aplicaciones fuera de línea emplean datos previamente almacenados en caché, lo que significa que la mayoría de las consultas nunca llegan a la base de datos.
Las sesiones de usuario deben guardarse en un caché en su aplicación Django y, dado que Redis mantiene los datos en el disco, todas las sesiones de los usuarios que iniciaron sesión se originan en el caché en lugar de en la base de datos.
Para usar Memcache con Django, necesitamos definir lo siguiente:
ip:port
donde ip
es la dirección IP del daemon de Memcached y port
es el puerto en el que se ejecuta Memcached, o la URL que apunta a su instancia de Redis, usando el esquema apropiado.
Para habilitar el almacenamiento en caché de la base de datos con Memcached, instale pymemcache
usando pip usando el siguiente comando:
pip install pymemcache
Luego, puede configurar los ajustes de caché en su settings.py
de la siguiente manera:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache', 'LOCATION': '127.0.0.1:11211', } }
En el ejemplo anterior, Memcached se ejecuta en el puerto localhost (127.0.0.1) 11211, utilizando el enlace pymemcache
:
De manera similar, para habilitar el almacenamiento en caché de la base de datos usando Redis, instale Redis usando pip usando el siguiente comando:
pip install redis
Luego configure los ajustes de caché en su settings.py
agregando el siguiente código:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.redis.RedisCache', 'LOCATION': 'redis://127.0.0.1:6379', } }
Memcached y Redis también se pueden usar para almacenar tokens de autenticación de usuarios. Debido a que cada persona que inicia sesión debe proporcionar un token, todos estos procedimientos pueden resultar en una sobrecarga importante de la base de datos. El uso de tokens en caché dará como resultado un acceso a la base de datos considerablemente más rápido.
Un conjunto de consultas en Django, por lo general, almacena en caché su resultado cuando ocurre la evaluación y para cualquier otra operación con ese conjunto de consultas, primero verifica si hay resultados almacenados en caché. Sin embargo, cuando usa iterator()
, no verifica el caché y lee los resultados directamente de la base de datos, ni guarda los resultados en el conjunto de consultas.
Ahora, usted debe estar preguntándose cómo esto es útil. Considere un conjunto de consultas que devuelve una gran cantidad de objetos con mucha memoria para almacenar en caché, pero debe usarse solo una vez, en tal caso, debe usar un iterator()
.
Por ejemplo, en el siguiente código, todos los registros se obtendrán de la base de datos y luego se cargarán en la memoria y luego iteraremos a través de cada uno:
queryset = Product.objects.all() for each in queryset: do_something(each)
Mientras que si usamos iterator()
, Django mantendrá abierta la conexión SQL y leerá cada registro, y llamará a do_something()
antes de leer el siguiente registro:
queryset = Product.objects.all().iterator() for each in queryset: do_something(each)
Django crea una nueva conexión de base de datos para cada solicitud y la cierra una vez que se completa la solicitud. Este comportamiento es causado por CONN_MAX_AGE
, que tiene un valor predeterminado de 0. Pero, ¿cuánto tiempo debe configurarse? Eso está determinado por el volumen de tráfico en su sitio; cuanto mayor sea el volumen, más segundos se requieren para mantener la conexión. Por lo general, se recomienda comenzar con un número bajo, como 60.
Debe envolver sus opciones adicionales en OPTIONS
, como se detalla en el
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'dashboard', 'USER': 'root', 'PASSWORD': 'root', 'HOST': '127.0.0.1', 'PORT': '3306', 'OPTIONS': { 'CONN_MAX_AGE': '60', } } }
Las expresiones de consulta definen un valor o un cálculo que se puede utilizar en una operación de actualización, creación, filtrado, ordenación, anotación o agregación.
Una expresión de consulta incorporada de uso común en Django es la expresión F. Veamos cómo funciona y puede ser útil.
Nota: estas expresiones están definidas en django.db.models.expressions
y django.db.models.aggregates
, pero por conveniencia, están disponibles y normalmente se importan desde django.db.models
.
En la API de Django Queryset, las expresiones F()
se utilizan para referirse directamente a los valores de campo del modelo. Le permite hacer referencia a los valores de los campos del modelo y realizar acciones en la base de datos sobre ellos sin tener que buscarlos en la base de datos y en la memoria de Python. En su lugar, Django emplea el objeto F()
para producir una frase SQL que define la actividad necesaria de la base de datos.
Por ejemplo, digamos que queremos aumentar el precio de todos los productos en un 20 %, entonces el código se vería así:
products = Product.objects.all() for product in products: product.price *= 1.2 product.save()
Sin embargo, si usamos F()
, podemos hacer esto en una sola consulta de la siguiente manera:
from django.db.models import F Product.objects.update(price=F('price') * 1.2)
select_related()
y prefetch_related()
Django proporciona select_related()
y prefetch_related()
para optimizar sus conjuntos de consultas al minimizar el número de solicitudes de base de datos.
Según la documentación oficial de Django:
select_related()
"sigue" las relaciones de clave externa, seleccionando datos adicionales de objetos relacionados cuando ejecuta su consulta.
prefetch_related()
realiza una búsqueda separada para cada relación y realiza la "unión" en Python.
select_related()
Usamos select_related()
cuando el elemento que se va a seleccionar es un solo objeto, lo que significa un campo ForeignKey
hacia adelante, OneToOne
y OneToOne
hacia atrás.
Puede usar select_related()
para crear una sola consulta que devuelva todos los objetos relacionados para una sola instancia para conexiones uno-muchos y uno-a-uno. Cuando se realiza la consulta, select_related()
recupera cualquier dato adicional de objeto relacionado de las relaciones de clave externa.
select_related()
funciona generando una unión SQL e incluye las columnas del objeto relacionado en la expresión SELECT
. Como resultado, select_related()
devuelve elementos relacionados en la misma consulta de base de datos.
Aunque select_related()
produce una consulta más sofisticada, los datos adquiridos se almacenan en caché, por lo que el manejo de los datos obtenidos no requiere ninguna solicitud de base de datos adicional.
La sintaxis simplemente se ve así:
queryset = Tweet.objects.select_related('owner').all()
prefetch_related()
Por el contrario, prefetch_related()
se utiliza para conexiones de muchos a muchos y de muchos a uno. Produce una sola consulta que incluye todos los modelos y filtros que se proporcionan en la consulta.
La sintaxis simplemente se ve así:
Book.objects.prefetch_related('author').get(id=1).author.first_name
NOTA: Las relaciones ManyToMany no deben manejarse con SQL porque pueden aparecer muchos problemas de rendimiento cuando se trata de tablas grandes. Es por eso que el método prefetch_related une tablas dentro de Python evitando hacer grandes uniones SQL.
Lea sobre la diferencia entre select_related()
y prefetch_related()
en detalle aquí .
bulk_create()
y bulk_update()
bulk_create()
es un método que crea la lista de objetos proporcionada en la base de datos con una consulta. De manera similar, bulk_update()
es un método que actualiza los campos dados en las instancias del modelo proporcionadas con una consulta.
Por ejemplo, si tenemos un modelo de publicaciones como se muestra a continuación:
class Post(models.Model): title = models.CharField(max_length=300, unique=True) time = models.DateTimeField(auto_now_add=True) def __str__(self): return self.title
Ahora, digamos que queremos agregar múltiples registros de datos a este modelo, entonces podemos usar bulk_create()
así:
#articles articles = [Post(title="Hello python"), Post(title="Hello django"), Post(title="Hello bulk")] #insert data Post.objects.bulk_create(articles)
Y la salida se vería así:
>>> Post.objects.all() <QuerySet [<Post: Hello python>, <Post: Hello django>, <Post: Hello bulk>]>
Y si queremos actualizar los datos, podemos usar bulk_update()
así:
update_queries = [] a = Post.objects.get(id=14) b = Post.objects.get(id=15) c = Post.objects.get(id=16) #set update value a.title="Hello python updated" b.title="Hello django updated" c.title="Hello bulk updated" #append update_queries.extend((a, b, c)) Post.objects.bulk_update(update_queries, ['title'])
Y la salida se vería así:
>>> Post.objects.all() <QuerySet [<Post: Hello python updated>, <Post: Hello django updated>, <Post: Hello bulk updated>]>
En este artículo, cubrimos los consejos para optimizar el rendimiento de la base de datos, reducir los cuellos de botella y ahorrar recursos en una aplicación Django.
Espero que lo haya encontrado util. ¡Sigue leyendo!