我正在查询大约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']
答案 0 :(得分:2)
恕我直言,我不认为序列化程序本身存在问题。但是问题在于数据的大小。
我已经对具有给定模型(不包括POSTGIS部分)的100K行的序列化程序进行了一些测试,发现平均而言,序列化的数据是在本地计算机中以18秒的时间生成的。我测试了django的默认serializer,但花了大约20秒钟才得到10万行。
因此,由于 FK 关系不是很重要,并且我也使用prefetch_related
进行了测试,因此它也没有太大改进。
因此,我想说,我们需要在其他地方进行改进。恕我直言,我认为这里的瓶颈是数据库。因此,您可以在其中进行一些改进,例如Index Caching(仅供参考:我不是这方面的专家,我不知道它是否可行;也许值得一试)。
但是,还有一种更好的方法是使用in memory storage
来缓存数据。您可以使用Redis。
在 redis 中存储/检索10万行还比DB Query花费更少的时间(大约2秒)。 我的本地计算机上的屏幕截图:
您可以尝试这样:
编码示例:
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))
此解决方案有两个主要缺点。
为克服这些问题,我们可以引入类似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的许多替代方法进行缓存,例如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)
答案 1 :(得分:0)
我怀疑“设备”是这里的关键问题,如果未正确连接,它将在数据库中查询您拥有的每条记录。作为测试,您可以在排除列表中添加“设备”,以查看性能是否提高。