Yes, indeed, it is a risky operation, but... I've a long experience in message analysis.
Of course, I've accidentally cleaned up correct records several times :-)
We've a bit more comfortable situation because as s default we assign different databases to a groups of processes and even to a single but complicated process. This way it is much easier to backup an exchange DB in case if something goes wrong.
We don't use ProcessManager, we've our own portal for task management and we treat it as a kind of reference system. Usually I check the correctness of records in both systems to be sure not removing any valid records. I very often prepare report with list of tasks to check their validity by users to whom have been attached to. If task cannot be executed I estimate if this a logic bug and it can be corrected by modifications in a model or it is a data error (for instance wrong value or even a non-existence of required variable) and it can't be corrected. Even in case of data errors, sometimes, if it is a really important task, I try to repair it by reading it from queue, deserializing in Visual Studio, correcting the broken data, serializing and storing it again in queue. This is absolutely a handicraft but it works :-)
The processes with broken tasks can be deleted (aborted) or just enough is to wait for their timeout. But sometimes waiting for timeout is not a good idea - for instance: designer doesn't handled it in proper way or task was designed to autoregenerate in case of timeout - then instance must be deleted or will be active forever. Except the space in database it is also a an additional load to background processing - such tasks must also have timeouts processed. During process designing we put an accent on forcing its tasks to end in a reasonable time to avoid long lasting processing.
I'm curious - how old are the oldest messages from different queue types in your exchange database? It's not specially secret information.
select mstp.QueueType, COUNT(*), MIN(mstp.MessagePostedDate)
from
(
select ms.*, RIGHT(ms.QueuePhrase, LEN(ms.QueuePhrase) - CHARINDEX('.', ms.QueuePhrase)+1) as QueueType
from (select MessageId, QueueName, MessagePostedDate, RIGHT(QueueName, 20) as "QueuePhrase"
from [Messages])as ms
) as mstp
group by mstp.QueueType
order by 2 desc;