Without a persistent object cache a transient is two rows in wp_options — the value and its expiry — and setting one is two writes that are not atomic together.
$data = get_transient( 'turkerdev_report' );
if ( false === $data ) {
$data = turkerdev_build_report();
set_transient( 'turkerdev_report', $data, HOUR_IN_SECONDS );
}
// false means "absent", which a legitimately false value cannot be
// distinguished from. store a wrapper:
// set_transient( $k, array( 'v' => $value ), $ttl );
The false ambiguity is the practical trap and a wrapper array is the fix. Expired transients are only cleaned up on a scheduled event, so a site generating many of them accumulates rows indefinitely — and if the transient was autoloaded, those rows are read on every request until something deletes them. Setting a transient with a TTL over a day makes it autoload, which is a detail almost nobody knows.