webentwicklung-frage-antwort-db.com.de

Reduzieren Sie die Django-Serialisierungszeit

Ich führe Abfragen für ungefähr 100.000 Zeilen mit jeweils ungefähr 40 Spalten durch. Die Spalten bestehen aus einer Kombination von float, integer, datetime und char.

Die Abfragezeit beträgt ungefähr zwei Sekunden, und die Serialisierung dauert mindestens vierzig Sekunden, während die Antworterstellung ebenfalls ungefähr zwei Sekunden dauert.

Ich frage mich, wie ich die Serialisierungszeit für Django-Modelle reduzieren kann.

Hier ist mein Modell:

class TelematicsData(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)

    device = models.ForeignKey(Device, on_delete=models.CASCADE, null=True)

    created_date = models.DateTimeField(auto_now=True)

    analog_input_01 = models.FloatField(null=True)
    analog_input_02 = models.FloatField(null=True)
    analog_input_03 = models.FloatField(null=True)
    analog_input_04 = models.FloatField(null=True)
    analog_input_05 = models.FloatField(null=True)
    analog_input_06 = models.FloatField(null=True)

    device_temperature = models.FloatField(null=True)
    device_voltage = models.FloatField(null=True)
    vehicle_voltage = models.FloatField(null=True)

    absolute_acceleration = models.FloatField(null=True)
    brake_acceleration = models.FloatField(null=True)
    bump_acceleration = models.FloatField(null=True)
    turn_acceleration = models.FloatField(null=True)
    x_acceleration = models.FloatField(null=True)
    y_acceleration = models.FloatField(null=True)
    z_acceleration = models.FloatField(null=True)

    cell_location_error_meters = models.FloatField(null=True)
    engine_ignition_status = models.NullBooleanField()

    gnss_antenna_status = models.NullBooleanField()
    gnss_type = models.CharField(max_length=20, default='NA')
    gsm_signal_level = models.FloatField(null=True)
    gsm_sim_status = models.NullBooleanField()

    imei = models.CharField(max_length=20, default='NA')
    movement_status = models.NullBooleanField()
    peer = models.CharField(max_length=20, default='NA')

    position_altitude = models.IntegerField(null=True)
    position_direction = models.FloatField(null=True)
    position_hdop = models.IntegerField(null=True)
    position_latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True)
    position_longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True)
    position_point = models.PointField(null=True)
    position_satellites = models.IntegerField(null=True)
    position_speed = models.FloatField(null=True)
    position_valid = models.NullBooleanField()

    shock_event = models.NullBooleanField()
    hardware_version = models.FloatField(null=True)
    software_version = models.FloatField(null=True)

    record_sequence_number = models.IntegerField(null=True)
    timestamp_server = models.IntegerField(null=True)
    timestamp_unix = models.IntegerField(null=True)
    timestamp = models.DateTimeField(null=True)
    vehicle_mileage = models.FloatField(null=True)

    user_data_value_01 = models.FloatField(null=True)
    user_data_value_02 = models.FloatField(null=True)
    user_data_value_03 = models.FloatField(null=True)
    user_data_value_04 = models.FloatField(null=True)
    user_data_value_05 = models.FloatField(null=True)
    user_data_value_06 = models.FloatField(null=True)  
    user_data_value_07 = models.FloatField(null=True)  
    user_data_value_08 = models.FloatField(null=True)

und das ist der serializer:

class TelematicsDataSerializer(serializers.ModelSerializer):

    class Meta:
        model = TelematicsData
        geo_field = ('position_point')
        #fields = '__all__'
        exclude = ['id']
6
Afeez Aziz

Kurz: Cache verwenden

IMHO, ich glaube nicht, dass es ein Problem mit dem Serializer selbst gibt. Das Problem ist jedoch die Datenmenge.

Ich habe einige Tests mit Serializer mit 100 KB Zeilen mit Ihrem Modell (ohne den POSTGIS-Teil) durchgeführt und festgestellt, dass die serialisierten Daten im Durchschnitt in 18 Sekunden auf dem lokalen Computer generiert werden. Ich habe Djangos Standard Serializer getestet, aber es dauerte ungefähr 20 Sekunden, um 100K-Zeilen zu erhalten.

Ein Nebeneinander Vergleich zwischen DRF Serializer und Django Serializer :  Side By Side Compraison

Da die BeziehungFKnicht sehr wichtig ist und ich sie auch mit prefetch_related getestet habe, hat sie sich auch nicht wesentlich verbessert.

Also, ich würde sagen, wir müssen uns woanders verbessern. IMHO, ich denke der Engpass hier ist die DB. Sie können dort also einige Verbesserungen vornehmen, wie zum Beispiel Index Caching (Zu Ihrer Information: Ich bin kein Experte in diesem Bereich, ich weiß nicht, ob es möglich ist oder nicht; vielleicht ist es einen Versuch wert).

Es gibt jedoch einen noch besseren Ansatz: Verwenden Sie in memory storage, um die Daten zwischenzuspeichern. Sie können Redis verwenden.

Das Speichern/Abrufen von 100K-Zeilen in redis dauert ebenfalls etwas kürzer als die DB-Abfrage (ca. 2 Sekunden). Ein Screenshot von meinem lokalen Computer:

 Redis Store and Retrieve time

Sie können es so versuchen:

  1. Speichern Sie zuerst die Json-Daten in Redis mit einer Zeitüberschreitung. Nach einer gewissen Zeit werden die Redis-Daten gelöscht und erneut aus der DB geladen.
  2. Wenn eine API aufgerufen wird, prüfen Sie zunächst, ob sie in Redis vorhanden ist, und stellen Sie sie dann über Redis bereit
  3. Andernfalls servieren Sie vom Seralizer und speichern Sie den JSON erneut in Redis.

Codierungsbeispiel:

import json
import redis


class SomeView(APIView):
    def get(self, request, *args, **kwargs):
        Host = getattr(settings, "REDIS_Host", 'localhost')  # assuming you have configuration in settings.py
        port = getattr(settings, "REDIS_PORT", 6379)
        KEY = getattr(settings, "REDIS_KEY", "TELE_DATA")
        TIME_OUT = getattr(settings, "REDIS_TIMEOUT", 3600)
        pool=redis.StrictRedis(Host, port)
        data = pool.get(KEY)
        if not data:
             data = TelematicsDataSerializer(TelematicsData.objects.all(), many=True).data
             pool.set(KEY, json.dumps(data), e=TIME_OUT)
             return Response(data)
        else:
            return Response(json.loads(data))

Es gibt zwei Hauptnachteile dieser Lösung.

  1. Die Zeilen, die zwischen dem Timeout eingefügt werden (sagen wir, seine eine Stunde), dann wird es nicht in der Antwort gesendet
  2. Wenn das Redis leer ist, dauert es mehr als 40 Sekunden, um die Antwort an den Benutzer zu senden.

Um diese Probleme zu lösen, können wir so etwas wie cellery einführen, das die Daten in Redis regelmäßig aktualisiert. Das heißt, wir definieren eine neue Sellerie-Aufgabe, die regelmäßig Daten in Redis lädt und die älteren entfernt. Wir können es so versuchen:

from celery.task.schedules import crontab
from celery.decorators import periodic_task


@periodic_task(run_every=(crontab(minute='*/5')), name="load_cache", ignore_result=True)  # Runs every 5 minute
def load_cache():
    ...
    pool=redis.StrictRedis(Host, port)
    json_data = TelematicsDataSerializer(TelematicsData.objects.all(), many=True).data
    pool.set(KEY, json.dumps(data))  # No need for timeout

Und in der Ansicht:

class SomeView(APIView):
    def get(self, request, *args, **kwargs):
        Host = getattr(settings, "REDIS_Host", 'localhost')  # assuming you have configuration in settings.py
        port = getattr(settings, "REDIS_PORT", 6379)
        KEY = getattr(settings, "REDIS_KEY", "TELE_DATA")
        TIME_OUT = getattr(settings, "REDIS_TIMEOUT", 3600)
        pool=redis.StrictRedis(Host, port)
        data = pool.get(KEY)
        return Response(json.loads(data))

Der Benutzer erhält also immer Daten aus dem Cache. Es gibt auch einen Nachteil für diese Lösung, da der Datenbenutzer möglicherweise nicht die neuesten Zeilen hat (wenn sie zwischen der Intervallzeit der Sellerie-Aufgabe liegen). Angenommen, Sie möchten Sellerie zwingen, den Cache mit load_cache.apply_async() (asynchron ausgeführt) oder load_cache.apply() (synchron ausgeführt) neu zu laden.

Sie können auch viele Alternativen zu Redis zum Zwischenspeichern verwenden, z. B. memcache, elastic search usw.


Experimental:

Da die Datenmenge sehr groß ist, können Sie die Daten möglicherweise beim Speichern komprimieren und beim Laden dekomprimieren. Aber es wird die Leistung reduzieren, nicht sicher, um welche Spanne. Sie können es so versuchen:

Kompression

import pickle
import gzip

....

binary_data = pickle.dumps(data)
compressed_data = gzip.compress(binary_data)
pool.set(KEY, compressed_data)  # No need to use JSON Dumps

Dekompression

import pickle
import gzip

....

compressed_data = pool.get(KEY)
binary_data = gzip.decompress(compressed_data)
data = pickle.loads(binary_data) 
2
ruddra

Ich vermute, dass "Gerät" das Hauptproblem ist, wenn es nicht korrekt verbunden ist, wird die Datenbank nach jedem Datensatz abgefragt, den Sie haben. Als Test können Sie der Ausschlussliste "Gerät" hinzufügen, um festzustellen, ob sich die Leistung verbessert.

0
Du D.