Rodeoclash

Rodeoclash

Your thoughts on best practices around Ecto preloads

I’m interested in how others work with preloads in Ecto - I suppose what I’m looking for is some vague best practices around how to treat them and I figured getting some insight into how others approach them might give me a few ideas I could start to apply.

Currently, I just preload in the function that works with the relationship. This works in the sense that I always have the data I need no matter where I call the function from, however, I can end up in situations where I do a lot of loads. The worst case being a function that is called from inside of a loop of some kind which preloads every iteration.

Obviously the solution is to move the preload up a level to the parent so that when the data structure is passed into the function with the preload, nothing is done as the relationship already exists.

The problem here is that remembering to call the preloads from caller functions becomes kind of a manual process (i.e. keeping one eye on the logs to see the explosion of SELECT’s come in!)

A few thoughts on this.

  1. Should I just be avoiding the Repo.preload form of the preload and relying on using preload when I construct my queries instead?

  2. Have I possibly missed some kind of tool that can detect looped preloads and warn me?

Apologies if this is too vague. It’s not a show stopper for how I’m programming, just very interested in getting a good “mental model” around how to handle preloads as it’s one thing I’ve never felt I’ve done very neatly when working with ecto.

Most Liked

sodapopcan

sodapopcan

This all thumbs up very thumbly with my thumby rules of thumb.

I used to make myself a :preload option where I’d just pass the named preloads. Like so:

def list_records(opts \\ []) do
  preloads = Keyword.get(opts, :preload, [])

  Record
  |> Repo.all()
  |> Repo.preload(preloads)
end

This is of course a bit leaky but the templates know these keys anyway so I don’t hate it. I would still do this in certain situations but now I often just call separate context functions. It leads to clearer and easier to managed views IMO as you have separate assigns for the pertinent data. It’s really situational, though, and honestly I’m still learning here.

There are times when one thing doesn’t make any sense without the other in which case I will just always preload. For example I had a project that had Artwork and Artist. There was no situation where fetching artwork, either a single record or in a list, would ever make sense without the artist, so that gets a perma-preloaded.

sbuttgereit

sbuttgereit

Sure. But the key is that you’re consciously recognizing a fact of the data’s nature: that there isn’t a case where having a record without its associations populated makes sense. Nothing wrong with that (or at least in principle nothing wrong with that).

What I recommend against is retrieving data for which you don’t have a well considered need.

I find in practice that I don’t use the optional :preload technique much myself. Like you, I typically will have functions built for specific purposes which eliminate the need for that approach. However, once I’m there, and if the query is sufficiently complex, I’ll have some private functions to deal with things taking advantage of the fact that Ecto allows us to compose queries (one of the bigger selling points in my opinion). So, if I have a number of different, but similar queries, and there is an identifiable complex base query behind them all, I’ll have a private function to just return the base Ecto query and then I’ll add to that in the more specialized functions including pre-loads additional joins, etc. as needed before sending it to the database. That way I’m not constantly writing variations of the same query and I stand a chance of easier maintenance if things change. But to be clear, there is a threshold where the query complexity has to be high enough for that composition approach to be beneficial. Trivial queries I find are just simpler to write the query whole.

sbuttgereit

sbuttgereit

Here’s my rule(s) of thumb for interacting with the database and Ecto preloads. Note that these are rules of thumb and my rule of thumb for rules of thumb are that there are no absolute rules of thumb (and I mean that absolutely).

  • Don’t retrieve data that you don’t need.

    While it can be convenient during development to have all the data and decide later if you’ll need it or not, you will inevitably end up with the kinds of issues that will lead you ask the question you are asking. Also the kinds of performance related issues induced by over-querying tend to become more pronounced with load, meaning that queries that are acceptable during development may become less acceptable once in production. Depending on the nature of the preloads, this may be more pronounced because the latency of round-trips to the database server will tend to be high compared to other sources of time spent in the application… this also tends to be one of those things masked in development as compared to production where developers frequently develop on local databases, but in production the application and database are often not running on the same host and will have additional network latencies.

    I don’t worry too much about over-querying fields of any one record, though for rarely needed fields I’ve started designating them (load_in_query: false) directly in the Ecto schema definitions.

  • I will typically write preloads as part of the query.

    When I write a query for a particular purpose, I usually known what data I need and whether or not I want the database to perform a join operation to retrieve the data or if multiple queries will be more efficient for pre-loading. If it varies or the query might be reusable, I may make the preloads optional for the caller (data = get_data(preloads: true) or something to that effect) so that I don’t over-query the database.

I can absolutely see where Repo.preload could be helpful in avoiding unnecessary data retrieval… maybe do a permission check using the data of a retrieved parent record and then if it passes, call Repo.preload to get the additional data. But I wouldn’t use it all the time since I can specify it in the query… that’s really how I see the correct use of that function… being sure you need that extra data before retrieving it.

Where Next?

Popular in Discussions Top

vans163
So useless benchmarks aside, Its possible to write a webserver that can serve 300k requests per second (perhaps more with optimizations)....
New
Rustixir
Hi everyone, im working on find best language/framework/system for high concurrency, high performance and stable performance after wor...
New
cvkmohan
The upcoming Phoenix 1.6 release looks very interesting. Became a habit to watch the commits - and - what they are bringing in. phx.gen...
New
jeramyRR
This is an interesting article to read. Elixir’s performance, like usual, is excellent. However, it seems like the high CPU usage is co...
New
mmmrrr
Just saw that dhh announced https://hotwire.dev/ Is it just me or is this essentially live view? :smiley: Although I like the “iFrame-e...
New
pillaiindu
I want to convert a Phoenix LiveView CRUD website to a CRUD mobile app. What do you think is the easiest way to do so?
New
CharlesO
Erlang :list.nth simple, but 1 - based nth(1, [H|_]) -> H; nth(N, [_|T]) when N > 1 -> nth(N - 1, T). Elixir Enum.at … coo...
New
und0ck3d
Hello everyone! A few days ago I’ve created a topic here about how people were creating CMSs with Elixir and Phoenix. I’ve been studying...
New
ben-pr-p
In general I’ve been sticking to this community style guide GitHub - christopheradams/elixir_style_guide: A community driven style guide ...
New
acrolink
How does the two languages compare when it comes to server side application development? Any experiences or ideas? Thank you.
New

Other popular topics Top

sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
985 42920 311
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a > b) do {:ok, "a"} end if (a < b) do {:ok, b} end if (a == b) do {:ok, "equa...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
Qqwy
Update: How to use the Blogs & Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 126479 1222
New

We're in Beta

About us Mission Statement