Сократить время сериализации Django - PullRequest
0 голосов
/ 26 ноября 2018

Я делаю запросы для примерно 100 000 строк с примерно 40 столбцами в каждой.Столбцы имеют комбинацию с плавающей точкой, целым числом, датой и временем.

Время запроса составляет около двух секунд, а сериализация занимает сорок или более секунд, тогда как построение ответа также составляет около двух секунд.

Мне интересно, как я могу сократить время сериализации для моделей Django?

Вот моя модель:

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)

, а это сериализатор:

class TelematicsDataSerializer(serializers.ModelSerializer):

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

Ответы [ 2 ]

0 голосов
/ 01 декабря 2018

Короче: используйте Cache

ИМХО, я не думаю, что есть проблема с самим сериализатором.Но проблема заключается в размере данных.

Я провел некоторое тестирование с сериализатором с 100K строк на вашей модели (без части POSTGIS) и обнаружил, что в среднем сериализованные данные генерируются за 18 секундместная машина.Я тестировал стандартный сериализатор django , но для получения строк 100K потребовалось около 20 секунд.

Сравнение между сериализатором DRF и Django Serializer : Side By Side Compraison

Итак, поскольку отношение FK не очень важно, и я также проверил с prefetch_related, оно не принесло значительных улучшений.или.

Так что, я бы сказал, нам нужно улучшить в другом месте.ИМХО, я думаю узким местом здесь является БД.Таким образом, вы можете внести некоторые улучшения, такие как Индексное кэширование (к вашему сведению: я не эксперт в этом, я не знаю, возможно ли это или нет; возможно, стоит попробовать).

Но есть еще лучший подход - использовать in memory storage для кэширования данных.Вы можете использовать Redis .

Сохранение / извлечение 100K строк в redis также занимает значительно меньше времени, чем запрос к БД (около 2 секунд).Скриншот с моего локального компьютера:

Redis Store and Retrieve time

Вы можете попробовать вот так:

  1. Сначала сохраните данные Json вRedis с таймаутом.Таким образом, через некоторое время данные redis будут стерты и загружены из БД снова.
  2. Когда вызывается API, сначала проверьте, существует ли он в Redis, если он есть, а затем отправьте из Redis
  3. Иначе, подайте с Seralizer и снова сохраните JSON в Redis.

Пример кодирования:

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))

У этого решения есть два основных недостатка.

  1. Строки, которые вставляются между тайм-аутом (скажем, один час), затем не будут отправлены в ответе
  2. Если Redis пуст, то для отправки потребуется более 40 секундответ пользователю.

Чтобы преодолеть эти проблемы, мы можем ввести что-то вроде celery , которое будет периодически обновлять данные в Redis.То есть мы определим новую задачу сельдерея, которая будет периодически загружать данные в Redis и удалять старые.Мы можем попробовать вот так:

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

А в представлении:

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))

Таким образом, пользователь всегда будет получать данные из кеша.У этого решения есть и обратная сторона: пользователь может получить не самые последние строки данных (если они находятся между интервалами времени задачи сельдерея).Но допустим, что вы хотите заставить celery перезагрузить кэш, используя load_cache.apply_async() (для асинхронного запуска) или load_cache.apply() (для синхронного запуска).

Кроме того, вы можете использовать множество альтернатив Redis для кэширования, например memcache, elastic search и т. Д.


Эксперимент:

Поскольку размер данных огромен, возможно, вы можете сжать данные при хранении и распаковать их при загрузке.Но это снизит производительность, не уверен, на какой маржеВы можете попробовать вот так:

Сжатие

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

Декомпрессия

import pickle
import gzip

....

compressed_data = pool.get(KEY)
binary_data = gzip.decompress(compressed_data)
data = pickle.loads(binary_data) 
0 голосов
/ 29 ноября 2018

Я подозреваю, что ключевым моментом здесь является «устройство», если оно не будет правильно соединено, оно будет запрашивать базу данных для каждой имеющейся у вас записи.В качестве теста вы можете добавить «устройство» в список исключений, чтобы увидеть, улучшается ли производительность.

...