Searching a large number of unavailable shards through e.g. the `*` pattern while a large cluster is recovering from a full restart or so, leads to extremely large responses containing an exception for each shard.


![](https://user-images.githubusercontent.com/6490959/193701098-868888bc-cdc7-4104-a32a-d9c84d74aef2.png)

This example shows a 375M on heap response for ~25k unavailable shards being returned. Over the wire, this serialises with a similar size and we'd have 700M+ peak heap usage for a search request when the valid search response in a green cluster might be much smaller than this.
It seems to me that we could mostly resolve this by not returning the stack trace in this specific case of the shard not available exception (it doesn't seem valuable for users and we made a similar fix around unavailable shards in snapshots state responses)? 
server/src/main/java/org/elasticsearch/action/search/AbstractSearchAsyncAction.java
return true;
    }
    private static boolean assertExecuteOnStartThread() {
        
        
        final StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
        assert stackTraceElements.length >= 6 : stackTraceElements;
        int index = 0;
        assert stackTraceElements[index++].getMethodName().equals("getStackTrace");
        assert stackTraceElements[index++].getMethodName().equals("assertExecuteOnStartThread");
        assert stackTraceElements[index++].getMethodName().equals("performPhaseOnShard");
        if (stackTraceElements[index].getMethodName().equals("performPhaseOnShard")) {
            assert stackTraceElements[index].getClassName().endsWith("CanMatchPreFilterSearchPhase");
            index++;
        }
        assert stackTraceElements[index].getClassName().endsWith("AbstractSearchAsyncAction");
        assert stackTraceElements[index++].getMethodName().equals("run");
        assert stackTraceElements[index].getClassName().endsWith("AbstractSearchAsyncAction");
        assert stackTraceElements[index++].getMethodName().equals("executePhase");
        assert stackTraceElements[index].getClassName().endsWith("AbstractSearchAsyncAction");
        assert stackTraceElements[index++].getMethodName().equals("start");
        assert stackTraceElements[index].getClassName().endsWith("AbstractSearchAsyncAction") == false;
        return true;
    }
    protected void performPhaseOnShard(final int shardIndex, final SearchShardIterator shardIt, final SearchShardTarget shard) {
        /*
         * We capture the thread that this phase is starting on. When we are called back after executing the phase, we are either on the
         * same thread (because we never went async, or the same thread was selected from the thread pool) or a different thread. If we
         * continue on the same thread in the case that we never went async and this happens repeatedly we will end up recursing deeply and
         * could stack overflow. To prevent this, we fork if we are called back on the same thread that execution started on and otherwise