First approach is simpler and would result in adding 2-3 ms per transaction to fetch the fencing key. You can avoid the penalty if in the client code you don’t wait on the fencing value immediately, but Concurrently continue with other reads/writes, and then later check and abort the tx if the key has changed. If the fencing key changes are rare, this will not result in abortions, and thus a practically free operation.
Second approach, as I understand, is an optimization to cache the fencing key and the associated read version. This will work, but adds some complications where a client’s subsequent transactions may conflict with its older transactions, due to older read_version being used. This also results in bypassing ratelimiter and its ability, to some extent, to throttle down transactions under load.
How frequent are changes to fencing keys? If this is not very frequent then another optimization can be used: There is a feature by which you can get a fixed metadata key as part of every transaction for free.
You can transactionally change this global metadata key when changing any fencing key. Clients can compare changes to this metadata key against a locally cached value, and can then fetch the particular fencing key if the global metadata key has changed.