Remix.run Logo
DarkNova6 2 days ago

I seriously don't get why Django ORM is using the Active Record pattern. This is such a stupid footgun that trivially causes horrible performance and BEGS you to cause n+1 problems.

Never in my life did I have a problem with lazy loading causing unbearable performance until I joined a Python Django team. I really tried to find sympathy for the "dynamically typed" folks (please spare me saying Python is technically statically typed), but coming from writing apps and backends in Java, Swift, C#, Objective-C and PHP, Python with Django was the worst experience bar none.

I worked on the project for 10 months, could at least refactor the project to something semi-sane where obvious mistakes (which would not be possible in other languages) could not happen. Then along comes a "good Python dev" and threw it all out of the window and start doing SQL queries all over the place (typically 3-6 lines long), remove the domain objects and cause the same problems I started with to begin with. But his approach was saying that the other developers were "not good enough".

Yeah, have fun with schema changes going forward. Good riddance.

senko a day ago | parent | next [-]

Yes, if you attempt to use Django like you'd use your typical Java, Swift, C#, or Objective-C framework, you're not going to have a good time.

I've seen the horrors Java devs start doing on a Python project when trying to "fix" things, where by "fix" they mean use patterns they had to in previous gigs.

It's a different world.

DarkNova6 a day ago | parent [-]

Having basic defensive programming, some simple classes instead of dicts everywhere and avoiding n+1 is a "different world"?

senko a day ago | parent [-]

Just asking these questions underscores lack of understanding how things are usually done in Django and Python in general.

In Django you'd typically use simple classes (models or forms, or even dataclasses nowadays) more than dicts everywhere; n+1 is trivially avoidable (as another sibling comment points out, and you also have multiple packages that autodetect such cases if you've missed them).

Python in general has a more "consenting adults" than "defensive programming" attitude (which doesn't mean exessive coupling or spaghetti, but the approach is different from the Java or C# mindset).

There's no one THE correct style of programming.

matsemann 16 hours ago | parent [-]

> Just asking these questions underscores lack of understanding how things are usually done in Django and Python in general.

No, it doesn't. It's fair to criticize the consequences of this approach to coding.

JodieBenitez a day ago | parent | prev | next [-]

> BEGS you to cause n+1 problems

select_related, prefetch_related. n+1 problems be gone.

DarkNova6 a day ago | parent | next [-]

You misunderstand. And that is exactly the problem.

We did do that and that's why our queries ended up being several lines long. But if you missed just one model? You openly walk a knife again.

It's a mess and it only gets longer and longer. I ended the project with having some proper aggregates, only for that to be thrown out of the window by the guy after me.

infamia a day ago | parent | next [-]

If you want to just prefetch everything throughout a project or just on a per-Queryset basis, all that is coming in the next release of Django.

https://docs.djangoproject.com/en/dev/releases/6.1/#model-fi...

That's the great thing about Django, it's been around so long and the quality bar is so high that eventually all the major rough edges get sanded away usually in a really well considered manner.

JodieBenitez a day ago | parent | prev [-]

> our queries ended up being several lines long

Which is... perfectly normal for non-trivial needs.

> I ended the project with having some proper aggregates, only for that to be thrown out of the window by the guy after me.

How is that a Django problem though ? Sounds like a skill issue on your successor.

I get what you say, there's plenty of debates about ActiveRecord vs. AnythingElse, but in the end this one has its use and obviously has served many of us just fine. Different strokes... you know the drill.

lozenge a day ago | parent [-]

I think there's an argument for throwing AttributeError instead of silently going to N+1 behaviour.

JodieBenitez 18 hours ago | parent [-]

See sibling comment about fetch modes in coming 6.1 (https://docs.djangoproject.com/en/dev/topics/db/fetch-modes/), you can have a FieldFetchBlocked.

ranger_danger a day ago | parent | prev [-]

It still selects all fields by default. Very often I have to use defer() or only() to get rid of expensive columns like blob/text that are rarely used and greatly hurt performance when grabbing them.

Then it got to where I had to make a reflective function that I use like Model.objects.defer(*all_fields_except(Model, ['field1', 'field2'])), and then add another all_fields_except() for every select_related and prefetch_related.

Even save() by default re-writes every single field. You have to use save(update_fields=['field1']) instead.

ErroneousBosh a day ago | parent | prev [-]

What would you have used instead?