The normal usage pattern I'm seeing is that instead of making "sibling aware" queries to retrieve values from storage, custom field callers iterate over their objects and issue queries individually. There are comments to this effect littered throughout the codebase.
In most contexts, when you're viewing or interacting with custom fields, you're doing so with a single object, so these inefficiencies don't usually stack up. There are a few notable exceptions, mainly differential API calls. Each of these API methods will execute ~20 + 2N queries when called:
- https://secure.phabricator.com/conduit/method/differential.query/
- https://secure.phabricator.com/conduit/method/differential.revision.search/
That's not so, so, so terrible, until you start writing your own custom fields that have storage backing. A typical API call for revisions results in ~800 queries being run on our production instance right now. Couple that with the fact that our engineers love to hammer the API and...
I've screwed around a little with replacing custom field data exposed as "fields" in conduit with "attachments", using engine extensions to sneak them into misbehaving search engines. Interestingly enough, search engine attachments are given the opportunity to avoid issuing N+1 but engine extensions are not.