When should hook_post_update_NAME() be implemented instead of hook_update_N()? What I found so far is that certain actions are unsafe to be performed inside hook_update_N(), but I didn't find any documentation saying which those actions are.

When should I implement hook_post_update_NAME() instead of hook_update_N()?
Normally when you update Drupal core locally you perform the following:
composer update
drush updb
drush cex

Let's say that there's a core module implementing hook_update_N() to update some content or config – an update that triggers entity load and entity save somehow. Now let's say that in some other core module inside some other hook_update_N() are updates to the database schema which in the end handles how this content or config is actually stored. How can you reliably ensure that the schema updates run before the content/config updates, especially when some users update their site skipping some few minor releases at once?
So hook_post_update_NAME() was introduced to guarantee all schema/structure/format updates have run first and now can be relied on when needing to update content or config on top of them.

[Issue] Add hook_post_update_NAME() for data value updates to reliably run after data format updates
[Change record] hook_post_update_NAME() introduced to change content after hook_update_N() runs


Now let me also tell you that the most reliable hook for simple content updates from a custom module is hook_deploy_NAME(). This hook was introduced in Drush 10.3.0 together with the new drush deploy command. Using this new command to replace the old drush updb &amp;&amp; drush cim deployment routines all hook_deploy_NAME() implementations will be picked up after configuration import. Which totally makes sense when you want to populate a new field which has only been created and added to config in the same release.

[Issue] Add hook_post_config_import_NAME after config import