减少Django序列化时间

时间:2018-11-26 15:24:31

标签: django django-models django-rest-framework

我正在查询大约100,000行,每行大约40列。这些列是float,integer,datetime和char的组合。

查询时间约为2秒,序列化需要40秒或更长时间,而响应建立也需要2秒左右。

我想知道如何减少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 :(得分:2)

简而言之:使用缓存

恕我直言,我不认为序列化程序本身存在问题。但是问题在于数据的大小。

我已经对具有给定模型(不包括POSTGIS部分)的100K行的序列化程序进行了一些测试,发现平均而言,序列化的数据是在本地计算机中以18秒的时间生成的。我测试了django的默认serializer,但花了大约20秒钟才得到10万行。

DRF序列化器 Django序列化器之间的并排比较: Side By Side Compraison

因此,由于 FK 关系不是很重要,并且我也使用prefetch_related进行了测试,因此它也没有太大改进。

因此,我想说,我们需要在其他地方进行改进。恕我直言,我认为这里的瓶颈是数据库。因此,您可以在其中进行一些改进,例如Index Caching(仅供参考:我不是这方面的专家,我不知道它是否可行;也许值得一试)。

但是,还有一种更好的方法是使用in memory storage来缓存数据。您可以使用Redis

redis 中存储/检索10万行还比DB Query花费更少的时间(大约2秒)。 我的本地计算机上的屏幕截图:

Redis Store and Retrieve time

您可以尝试这样:

  1. 首先,使用超时将Json数据存储在Redis中。因此,经过一段时间后,redis数据将被删除并再次从DB加载。
  2. 调用API时,首先检查Redis中是否存在该API,然后再从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中的数据进行定期更新。这意味着,我们将定义一个新的celery任务,该任务将定期在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()(同步运行)来强制celery重新加载缓存。

此外,您可以使用Redis的许多替代方法进行缓存,例如memcacheelastic 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) 

答案 1 :(得分:0)

我怀疑“设备”是这里的关键问题,如果未正确连接,它将在数据库中查询您拥有的每条记录。作为测试,您可以在排除列表中添加“设备”,以查看性能是否提高。