Flush join indexes when memory is constrained

Add a separate limit for index memory usage that is independent of the task
memory limit. This removes the risk of the flushable memory usage causing
another operator to run out of memory.

Loader will attempt to load new data and add to existing cache.  If that
fails, it flushes the cache and tries again.  If that fails, it loads only
the single required request for the loader thread.
We should assert that this is the first time we set the index snapshot. It's an error if its set multiple times.

  checkState(indexSnapshotReference.compareAndSet(null, indexSnapshot), ...)