There are two ways to render an entity through the Entity API
Either do
$nodes = \Drupal::entityTypeManager()->getStorage('node')->loadMultiple([1]);
$build['nodes'] = \Drupal::entityTypeManager()->getViewBuilder('node')->viewMultiple($nodes, 'teaser');
or
$nodes = \Drupal::entityTypeManager()->getStorage('node')->loadMultiple([1]);
foreach ($nodes as $node) {
$build['nodes'][] = \Drupal::entityTypeManager()->getViewBuilder('node')->view($node, 'teaser');
}
The problem with viewMultiple() is that render cache is bypassed in a subtle way. Even though you won't see any changes, the pre_render callback will call all entity build/view/alter hooks .. so if anything is expensive going on there, well, you're doing it again even though you have a hit.
I've attached a simple module which you can use to see the problem.
1. turn on and make sure you have a node with id 1
2. turn off page and dynamic page cache, make sure no other caching mechanism is in place
3. surf to /test
4. check that the entity is render cache
5. open testing.module and uncomment the 'die' line (line 9)
6. reload page, everything is fine
7. open src/TestController and comment the viewMultiple() line and uncomment the foreach
8. reload: wsod ..