We also encountered issues with higher segment numbers (over 500k - 1Million), as segment balancing algorithms, etc. seem to eat up all ressources (CPU and memory). Regarding heap I can recommend just looking at the JVM memory graphs. If the saw tooth (memory getting freed by GC and then slowly fills up again) is very large (50% of your memory "height") then you are wasting memory. If the sawtooth is medium (e.g. about 30% of the memory), then it's perfect. If the sawtooth is small (smaller than 10% of total memory) then you should give it more memory. We don't have direct JVM memory (e.g. via jmx exporter), but use Imply Clarity for memory monitoring.
That said, for our setup 16g heap was enough for a stable operation of about 300 - 500k segments.
I cannot overstate the importance of compactions and checking that segment sizes are around 300-700MB size (or if you have very small rows, then max. 5 Mio. rows). We had lots of scattered segments due to late arrival of data and this can blow up your segment count ten or 100 times. This also affects query performance greatly.
Compactions are as important as ingestions for cluster health.