diff --git a/src/backend/utils/adt/like.c b/src/backend/utils/adt/like.c
index 66ee5638f6a..5fc823b8fbc 100644
--- a/src/backend/utils/adt/like.c
+++ b/src/backend/utils/adt/like.c
@@ -168,13 +168,21 @@ LikeMatchText(const char *s, int slen, const char *p, int plen,
 {
 	LikeBMHState *state = flinfo->fn_extra;
 	int			result;
+	int			searched;
 
 	if (state != NULL && state->mode == LIKE_BMH_GENERIC)
 		return GenericMatchText(s, slen, p, plen, collation);
 
-	result = like_bmh_match(s, slen, p, plen, flinfo, collation);
+	result = like_bmh_match(s, slen, p, plen, flinfo, collation, &searched);
 	if (result == LIKE_BMH_FALLBACK)
-		return GenericMatchText(s, slen, p, plen, collation);
+	{
+		/* in UTF-8, resume at a character boundary */
+		if (GetDatabaseEncoding() == PG_UTF8)
+			while (searched > 0 && (s[searched] & 0xC0) == 0x80)
+				searched--;
+		return GenericMatchText(s + searched, slen - searched, p, plen,
+								collation);
+	}
 
 	return result;
 }
diff --git a/src/backend/utils/adt/like_bmh.c b/src/backend/utils/adt/like_bmh.c
index 0f9f812b40b..d35c4371039 100644
--- a/src/backend/utils/adt/like_bmh.c
+++ b/src/backend/utils/adt/like_bmh.c
@@ -218,26 +218,45 @@ like_bmh_init(const char *p, int plen, FmgrInfo *flinfo, Oid collation)
 	return state;
 }
 
+/*
+ * Horspool skips only one byte per alignment when the haystack consists of
+ * few distinct bytes, and a literal that mismatches near its start then costs
+ * literal_len comparisons per alignment.  Bound the bytes compared beyond the
+ * guard byte to slen / 2; past that, report how much text was fully rejected
+ * so the caller can let the generic matcher finish the row.
+ */
 static int
 like_bmh_search(const char *s, int slen, const char *literal, int literal_len,
-				const int *skip_table)
+				const int *skip_table, int *searched)
 {
 	int			pos = literal_len - 1;
+	int			budget = slen / 2;
 
 	while (pos < slen)
 	{
-		int			i = literal_len - 1;
-		int			text_pos = pos;
 		unsigned char last = (unsigned char) s[pos];
 
-		while (i >= 0 && literal[i] == s[text_pos])
+		if (literal[literal_len - 1] == s[pos])
 		{
-			i--;
-			text_pos--;
+			int			i = literal_len - 2;
+			int			text_pos = pos - 1;
+
+			while (i >= 0 && literal[i] == s[text_pos])
+			{
+				i--;
+				text_pos--;
+			}
+			if (i < 0)
+				return LIKE_TRUE;
+
+			budget -= literal_len - 1 - i;
+			if (budget < 0)
+			{
+				/* every alignment ending at or before pos is rejected */
+				*searched = pos - literal_len + 2;
+				return LIKE_BMH_FALLBACK;
+			}
 		}
-		if (i < 0)
-			return LIKE_TRUE;
-
 		pos += skip_table[last];
 	}
 
@@ -247,7 +266,8 @@ like_bmh_search(const char *s, int slen, const char *literal, int literal_len,
 /* Keep variable-pattern revalidation out of the stable-pattern hot path. */
 static pg_noinline int
 like_bmh_match_revalidate(const char *s, int slen, const char *p, int plen,
-						  LikeBMHState *state, Oid collation)
+						  LikeBMHState *state, Oid collation,
+						  int *searched)
 {
 	LikeBMHRevalidateState *revalidate_state;
 
@@ -274,7 +294,7 @@ like_bmh_match_revalidate(const char *s, int slen, const char *p, int plen,
 
 	return like_bmh_search(s, slen, revalidate_state->buf,
 						 revalidate_state->literal_len,
-						 revalidate_state->skip_table);
+						 revalidate_state->skip_table, searched);
 }
 
 /*
@@ -283,21 +303,25 @@ like_bmh_match_revalidate(const char *s, int slen, const char *p, int plen,
  */
 int
 like_bmh_match(const char *s, int slen, const char *p, int plen,
-			   FmgrInfo *flinfo, Oid collation)
+			   FmgrInfo *flinfo, Oid collation, int *searched)
 {
 	LikeBMHState *state = flinfo->fn_extra;
 	LikeBMHSearchState *search_state;
 
+	*searched = 0;
+
 	if (state == NULL)
 		state = like_bmh_init(p, plen, flinfo, collation);
 
 	if (unlikely(state->mode != LIKE_BMH_SEARCH))
-		return like_bmh_match_revalidate(s, slen, p, plen, state, collation);
+		return like_bmh_match_revalidate(s, slen, p, plen, state, collation,
+										 searched);
 
 	search_state = (LikeBMHSearchState *) state;
 	if (unlikely(search_state->collation != collation))
 		return LIKE_BMH_FALLBACK;
 
 	return like_bmh_search(s, slen, search_state->literal,
-						 search_state->literal_len, search_state->skip_table);
+						 search_state->literal_len, search_state->skip_table,
+						 searched);
 }
diff --git a/src/include/utils/like_bmh.h b/src/include/utils/like_bmh.h
index 66fdca0a7bf..a3b312273d3 100644
--- a/src/include/utils/like_bmh.h
+++ b/src/include/utils/like_bmh.h
@@ -29,7 +29,11 @@ typedef struct LikeBMHState
 	LikeBMHMode mode;
 } LikeBMHState;
 
+/*
+ * If the result is LIKE_BMH_FALLBACK, *searched is the number of leading
+ * bytes that were fully rejected by the BMH search.
+ */
 extern int	like_bmh_match(const char *s, int slen, const char *p, int plen,
-						   FmgrInfo *flinfo, Oid collation);
+						   FmgrInfo *flinfo, Oid collation, int *searched);
 
 #endif							/* LIKE_BMH_H */
diff --git a/src/test/regress/expected/like_bmh.out b/src/test/regress/expected/like_bmh.out
index e8007048cae..54e6c222311 100644
--- a/src/test/regress/expected/like_bmh.out
+++ b/src/test/regress/expected/like_bmh.out
@@ -289,5 +289,43 @@ FROM (VALUES ('xxclassxx'::name, '%class%'::name),
  xxotherxx | %other% | t
 (3 rows)
 
+-- Budget fallback: repetitive text, with both miss and hit outcomes.
+SELECT repeat('a', 1024) LIKE '%~aaaaaaaaaaaaaaa%' AS f;
+ f 
+---
+ f
+(1 row)
+
+SELECT repeat('a', 1024) || '~aaaaaaaaaaaaaaa' LIKE '%~aaaaaaaaaaaaaaa%' AS t;
+ t 
+---
+ t
+(1 row)
+
+SELECT repeat('ab', 512) || 'xab' LIKE '%xabab%' AS f;
+ f 
+---
+ f
+(1 row)
+
+SELECT repeat('ab', 512) || 'xabab' LIKE '%xabab%' AS t;
+ t 
+---
+ t
+(1 row)
+
+-- UTF-8: resume position inside multibyte text.
+SELECT repeat('あ', 512) || 'xあ' LIKE '%xあい%' AS f;
+ f 
+---
+ f
+(1 row)
+
+SELECT repeat('あ', 512) || 'xあい' LIKE '%xあい%' AS t;
+ t 
+---
+ t
+(1 row)
+
 DROP FUNCTION like_bmh_plpgsql_like(text, text);
 DROP FUNCTION like_bmh_plpgsql_like_name(name, name);
diff --git a/src/test/regress/sql/like_bmh.sql b/src/test/regress/sql/like_bmh.sql
index ccd2d432175..696ea305dfb 100644
--- a/src/test/regress/sql/like_bmh.sql
+++ b/src/test/regress/sql/like_bmh.sql
@@ -138,5 +138,14 @@ FROM (VALUES ('xxclassxx'::name, '%class%'::name),
 			 ('xxclassxx'::name, '%other%'::name),
 			 ('xxotherxx'::name, '%other%'::name)) AS v(s, p);
 
+-- Budget fallback: repetitive text, with both miss and hit outcomes.
+SELECT repeat('a', 1024) LIKE '%~aaaaaaaaaaaaaaa%' AS f;
+SELECT repeat('a', 1024) || '~aaaaaaaaaaaaaaa' LIKE '%~aaaaaaaaaaaaaaa%' AS t;
+SELECT repeat('ab', 512) || 'xab' LIKE '%xabab%' AS f;
+SELECT repeat('ab', 512) || 'xabab' LIKE '%xabab%' AS t;
+-- UTF-8: resume position inside multibyte text.
+SELECT repeat('あ', 512) || 'xあ' LIKE '%xあい%' AS f;
+SELECT repeat('あ', 512) || 'xあい' LIKE '%xあい%' AS t;
+
 DROP FUNCTION like_bmh_plpgsql_like(text, text);
 DROP FUNCTION like_bmh_plpgsql_like_name(name, name);
