The keypool is ordered, not just a set of keys being watched. This allows for addresses to be given out in derivation order, as well as maintaining the gap limit. When an address is used, all keys that come before it will also be marked as used. This is done to ensure that already used addresses are not accidentally reused.
So when you give out and use a random address in the range, you end up having all of the previous keys in the range be marked as used.
Removing keys from the keypool is purely for giving addresses out to the user and to be able to generate further addresses. It does not effect the watching behavior; keys removed from the keypool will still be watched.
The reason that you see a performance hit is because removing from the keypool deletes a record from the database in addition to writing several scripst to the database. Furthermore, if the keypool size drops below the configured keypool size due to these removals, the keypool will be refilled which will involve additional database writes. These all acquire a lock on the wallet which prevents other threads from accessing the wallet, and writing all of this data (especially tens of thousands of keys) can take a long time.
To avoid this problem, I highly suggest giving out addresses in order, not randomly. Otherwise you may not want to add the keys to the keypool although that means that you will lose the keypool refilling to keep the range full.
watch
for those addresses?deriveaddresses
.