The name is misleading twice over. It has nothing to do with fuzzy matching, the trigram-similarity, close-enough-spelling sense that people reach for pg_trgm to get. And it is not really a search limit either. The fuzziness is in the size of the result set, and what this parameter actually does is instruct PostgreSQL to give you fewer rows than match your query, chosen at random, without telling you.
That makes it close to unique in the GUC catalog. Plenty of parameters trade one resource against another, and a few trade durability for speed. This is the one that trades correctness for speed.
Integer, default 0 meaning no limit, context user, valid up to 2147483647.
What it does
Set it to a non-zero value and a GIN index scan will stop returning the complete set of matching rows. The documentation is direct about the mechanics: the returned set is a subset of the whole result set, chosen at random, and “soft” means the actual number of rows returned could differ somewhat from the number you asked for, depending on the query and the quality of the system’s random number generator.
Read that again, because both halves matter. It is a random subset, not the first N rows, so you cannot reason about which rows you got. And the count is approximate, so you cannot reason about how many you got either. Two identical executions of the same query can return different rows.
Why it exists
GIN was built to make full-text search scale, and the parameter comes from that world. The problem it addresses is specific and real. A full-text query containing a common word can match an enormous fraction of the corpus, and, as the documentation observes, such a large result set is usually not even useful to the person who asked for it. Nobody paging through search results is going to reach row 400,000.
The cost of that query is not in the index. The documentation is careful to note that the index search itself is very fast. The expense is everything after: fetching all those matching tuples from the heap and sorting them. If you are ranking by relevance, you cannot skip that work, because computing ts_rank for a row requires the row. A LIMIT 10 on the outside does not save you, since you have to rank all two million matches before you know which ten are the best.
gin_fuzzy_search_limit cuts that knot by refusing to produce all two million in the first place. You rank a random twenty thousand instead, and for a query on a word that appears in a third of your documents, the top ten of a random sample is arguably as good an answer as the true top ten. The documentation suggests values in the thousands, specifically the 5000 to 20000 range, as working well in practice.
That is a coherent argument, and in the narrow case it was designed for, it holds.
The problem with it now
The parameter was designed when GIN was essentially the full-text index. That is not the world we are in. GIN now backs jsonb containment, array containment, hstore, and pg_trgm similarity searches, among others, and the great majority of those queries are not relevance-ranked searches where an approximate answer is fine. They are lookups where the caller expects the rows that match.
And the setting is not scoped to the query you had in mind. It applies to GIN index scans, all of them, for the duration of whatever scope you set it in. Put it in postgresql.conf and every @> containment lookup in the database starts silently dropping matching rows.
The failure mode that should worry you most is aggregation. SELECT count(*) FROM docs WHERE tsv @@ to_tsquery('...') with a non-zero fuzzy limit does not return the number of matching documents. It returns roughly the fuzzy limit, or some number near it, and it does so without an error, a warning, or anything in EXPLAIN to indicate that the answer is a sample. Any SUM, AVG, or EXISTS sitting on top of a GIN scan has the same problem. A report that silently undercounts is worse than a report that times out, because the timeout tells you something is wrong.
What to do instead
Leave it at 0, and reach for the tools that reduce the result set honestly.
The first is making the query more selective. A search that matches a third of the corpus is usually a query problem before it is a performance problem, and websearch_to_tsquery with additional terms, or a stopword configuration that excludes the words causing the blowup, addresses the cause rather than the symptom.
The second is ts_rank on a bounded candidate set produced by something other than chance: filter by a date range, a category, an owner, whatever your application actually knows, so that the ranking runs over a set you chose rather than one the random number generator chose.
The third, if relevance ranking over very large result sets is central to what you are building, is the RUM index from Postgres Professional, which stores ranking information inside the index so that ordered retrieval does not require fetching and sorting every match. That is the structural fix for the problem gin_fuzzy_search_limit papers over.
And if none of those are available and you genuinely have the original case, a public search box over a large document corpus where approximate relevance is acceptable and nobody is counting anything, then set it per session or per role for exactly that workload. Never in postgresql.conf, and never anywhere it can reach a query whose answer someone will treat as a fact.
The summary
This is a parameter that answers a real question from 2006 in a way that has aged into a hazard, because the index type it applies to has grown far beyond the use case that justified it. Its behavior is not a bug and the documentation describes it accurately. But a setting that makes queries return a random subset of their matching rows, silently, across an entire index type, belongs to one specific workload and no others.
Leave it at 0.