A hundred Redis commands issued one at a time cost a hundred network round trips. On the same host that is around a millisecond total; across an availability zone it is closer to a hundred, and the server was idle for almost all of it.
$pipe = $redis->multi(Redis::PIPELINE);
foreach ($ids as $id) {
$pipe->hGetAll("product:{$id}");
}
$results = $pipe->exec();
Pipelining sends the batch without waiting for each reply, so the cost becomes one round trip plus the server-side execution. It is not a transaction — commands are not atomic as a group and a failure part-way through does not roll anything back; MULTI/EXEC is the separate feature for that. Keep batches bounded, because both client and server buffer the whole thing in memory.