In this article we will explore and show the difference between 2 native events. According to Magento devdocs:

The events and observers are based on the publish-subscribe pattern. Using events and observers, you can run your custom code in response to a specific event.

Given this information we already know that we need to look into how the events are dispatched. The magic happens in the abstract model class \Magento\Framework\Model\AbstractModel however in order to fully understand the difference we will have to go deeper.

_save_after

The first event occurs in afterSave function and dispatches the event with the event prefix which usually is enough to identify exact entity.

public function afterSave()
{
	...
    $this->_eventManager->dispatch($this->_eventPrefix . '_save_after', $this->_getEventData());
    ...
}

For example EAV entity model class extends the abst \Magento\Eav\Model\Entity\Attribute defines the eventPrefix as eav_entity_attribute. Which means that after saving the event the event will be eav_entity_attribute_save_after.

The event is dispatched in \Magento\Framework\Model\ResourceModel\Db\AbstractDb::save after the save action has been executed. The save operation happens using a database transaction and this event does not wait for the transaction to be committed.

_save_commit_after

The save_commit_after event is dispatched just a few lines after the save_after event however the difference is that the event is not dispatched right away but is executed as a callback. Here is how the callback is added:

$this->addCommitCallback([$object, 'afterCommitCallback'])->commit();
\Magento\Framework\Model\ResourceModel\Db\AbstractDb::save

And here is how the event callback is defined.

public function afterCommitCallback()
{
    $this->_eventManager->dispatch($this->_eventPrefix . '_save_commit_after', $this->_getEventData());
    return $this;
}

And the final bit to complete this is the commit function.

public function commit()
{
    $this->getConnection()->commit();
    /**
     * Process after commit callbacks
     */
    if ($this->getConnection()->getTransactionLevel() === 0) {
        $callbacks = CallbackPool::get(spl_object_hash($this->getConnection()));
        foreach ($callbacks as $callback) {
            try {
                call_user_func($callback);
            } catch (\Exception $e) {
                $this->getLogger()->critical($e);
            }
        }
    }

    return $this;
}
\Magento\Framework\Model\ResourceModel\AbstractResource::commit

The function does 2 things:

  1. executes the commit function (by default this is the Mysql PDO implementation)
  2. runs through the defined callbacks and calls the appropriate functions

Step #2 is the point when the _save_commit_after_ is executed. It is possible that there will be an error while committing the transaction - in this case the event will not be called. This is useful in case you need to observe cases when data is actually saved and not care about higher level actions before the data is actually persisted.

Hopefully this gives you enough information to differentiate the events and use them appropriately.