1

Imagine I have the next models (do not mind the field types, example purpose):

class County(model.Model):
   country = models.TextField()

class City(model.Model):
   country = models.ForeignKey(Country)
   city    = models.TextField()

class Person(model.Model):
   city = models.ForeignKey(City)
   name = models.TextField()

and the corresponding serializations of these models

class CountrySerializer(serializers.HyperlinkedModelSerializer):
   class Meta:
      model  = Country
      fields = ('country')

class CitySerializer(serializers.HyperlinkedModelSerializer):
   country = CountrySerializer()

   class Meta:
      model  = City
      fields = ('city', 'country')

class PersonSerializer(serializers.HyperlinkedModelSerializer):
   city = CitySerializer()

   class Meta:
      model  = Person
      fields = ('name', 'city')

Ok, so I'm using the Django REST Framework to build an API. One of the endpoints is /person that returns all the persons.

def get(request):
   persons = Person.objects.all()
   data = PersonSerializer(persons, many = True)
   return JsonResponse(data.data, safe = False) 

The thing is: it takes so long to do the serialization of persons because of the ForeignKeys.

My main goal is to make the request go as fast as possible tweaking this code, models or serializers.

The easy [1] but, obviously not the best, is to store the actual strings of city and country inside the Person model, and only serialize those, so the actual foreign keys are only used for querying and filtering purposes. But in that case, the scaling of that code is going to be awful(?).

I also tried using indexes but the improvement was not the one I was looking for.

Any suggestions or recommendations?

In my real case, I have a "Person" model with 4 different foreign keys models (one of which has a foreign key model inside it) and the time difference between the natural way and the scrappy way [1] is ~7 s vs ~1 ms, respectively (what I consider a huge improvement).

Details:

  • Approx. 1000 "persons" and 500 per "foreign keys" objects in my database.
  • I also use a cache database, so the subsequent requests are not that bad (more or less a 25% of the initial), but the first one is awful...

Settings:

  • Django: 1.11
  • Python: 3.6
2pac
  • 73
  • 2
  • 6

1 Answers1

1

Quite a relatable problem (haha). With a basic lookup like this, you'll have:

  • 1 database call to fetch all the Person objects (the .all lookup)

Within the serializer, you'll then have:

  • For each person, 1 database call to fetch their city
    • For each city you fetch, add 1 database call to fetch that city's country

You could reduce this with select_related:

Person.objects.select_related('city', 'city__country')

You could also use a values lookup to grab only the fields you want, and handle the resulting flat dictionary in your view or in a SerializerMethodField. How to combine select_related() and value()? (2016)

souldeux
  • 3,615
  • 3
  • 23
  • 35
  • Thank you! I added a `@property` to get only the city name in the "`Person`" model + used the `select_related` when querying for the persons and it worked just fine. – 2pac Jun 04 '19 at 17:28