@@ -12,46 +12,43 @@ namespace Elastic.Documentation.Search;
1212/// <summary>
1313/// Elasticsearch gateway for the documentation changes feed.
1414/// Queries last_updated > since with search_after cursor pagination.
15- /// Uses Point In Time (PIT) for consistent pagination across requests.
15+ /// Uses a shared Point In Time (PIT) for consistent pagination across requests.
1616/// </summary>
17- public partial class ChangesGateway ( ElasticsearchClientAccessor clientAccessor , ILogger < ChangesGateway > logger )
18- : IChangesGateway
17+ public partial class ChangesGateway (
18+ ElasticsearchClientAccessor clientAccessor ,
19+ SharedPointInTimeManager pitManager ,
20+ ILogger < ChangesGateway > logger
21+ ) : IChangesGateway
1922{
20- private const string PitKeepAlive = "5m" ;
21-
2223 public async Task < ChangesResult > GetChangesAsync ( ChangesRequest request , Cancel ctx = default )
2324 {
2425 var fetchSize = request . PageSize + 1 ;
2526
2627 try
2728 {
28- var pitId = await ResolvePitId ( request . Cursor ? . PitId , ctx ) ;
29+ var pitId = await pitManager . GetPitIdAsync ( ctx ) ;
2930
3031 var response = await Search ( request , pitId , fetchSize , ctx ) ;
3132
32- if ( ! response . IsValidResponse )
33+ if ( ! response . IsValidResponse && IsExpiredPit ( response ) )
3334 {
34- if ( IsExpiredPit ( response ) && request . Cursor ? . PitId is not null )
35- {
36- LogPitExpired ( logger ) ;
37- pitId = await OpenPit ( ctx ) ;
38- var updatedCursor = request . Cursor with { PitId = pitId } ;
39- response = await Search ( request with { Cursor = updatedCursor } , pitId , fetchSize , ctx ) ;
40- }
35+ LogPitExpired ( logger ) ;
36+ await pitManager . HandleExpiredPitAsync ( pitId , ctx ) ;
37+ pitId = await pitManager . GetPitIdAsync ( ctx ) ;
38+ response = await Search ( request , pitId , fetchSize , ctx ) ;
39+ }
4140
42- if ( ! response . IsValidResponse )
43- {
44- var reason = response . ElasticsearchServerError ? . Error . Reason ?? "Unknown" ;
45- throw new InvalidOperationException (
46- $ "Elasticsearch changes query failed (HTTP { response . ApiCallDetails ? . HttpStatusCode } ): { reason } "
47- ) ;
48- }
41+ if ( ! response . IsValidResponse )
42+ {
43+ var reason = response . ElasticsearchServerError ? . Error . Reason ?? "Unknown" ;
44+ throw new InvalidOperationException (
45+ $ "Elasticsearch changes query failed (HTTP { response . ApiCallDetails ? . HttpStatusCode } ): { reason } "
46+ ) ;
4947 }
5048
51- // Use the PIT ID from the response if available, as ES may return a new one
52- var responsePitId = response . PitId ?? pitId ;
49+ pitManager . RefreshKeepAlive ( ) ;
5350
54- return BuildResult ( response , request . PageSize , responsePitId ) ;
51+ return BuildResult ( response , request . PageSize ) ;
5552 }
5653 catch ( Exception ex )
5754 {
@@ -60,33 +57,6 @@ public async Task<ChangesResult> GetChangesAsync(ChangesRequest request, Cancel
6057 }
6158 }
6259
63- private async Task < string > ResolvePitId ( string ? existingPitId , Cancel ctx )
64- {
65- if ( ! string . IsNullOrEmpty ( existingPitId ) )
66- return existingPitId ;
67-
68- return await OpenPit ( ctx ) ;
69- }
70-
71- private async Task < string > OpenPit ( Cancel ctx )
72- {
73- var response = await clientAccessor . Client . OpenPointInTimeAsync (
74- clientAccessor . SearchIndex ,
75- r => r . KeepAlive ( PitKeepAlive ) ,
76- ctx
77- ) ;
78-
79- if ( ! response . IsValidResponse )
80- {
81- throw new InvalidOperationException (
82- $ "Failed to open PIT: { response . ElasticsearchServerError ? . Error . Reason ?? "Unknown" } "
83- ) ;
84- }
85-
86- LogPitOpened ( logger , response . Id ) ;
87- return response . Id ;
88- }
89-
9060 private async Task < SearchResponse < DocumentationDocument > > Search (
9161 ChangesRequest request , string pitId , int fetchSize , Cancel ctx
9262 ) =>
@@ -95,7 +65,7 @@ await clientAccessor.Client.SearchAsync<DocumentationDocument>(s =>
9565 _ = s
9666 . Size ( fetchSize )
9767 . TrackTotalHits ( t => t . Enabled ( false ) )
98- . Pit ( p => p . Id ( pitId ) . KeepAlive ( PitKeepAlive ) )
68+ . Pit ( p => p . Id ( pitId ) . KeepAlive ( SharedPointInTimeManager . PitKeepAlive ) )
9969 . Query ( q => q . Range ( r => r
10070 . Date ( dr => dr
10171 . Field ( f => f . LastUpdated )
@@ -132,7 +102,7 @@ private static bool IsExpiredPit(SearchResponse<DocumentationDocument> response)
132102 || response . ElasticsearchServerError ? . Error . Reason ? . Contains ( "point in time" , StringComparison . OrdinalIgnoreCase ) == true
133103 || response . ElasticsearchServerError ? . Error . Reason ? . Contains ( "No search context found" , StringComparison . OrdinalIgnoreCase ) == true ;
134104
135- private static ChangesResult BuildResult ( SearchResponse < DocumentationDocument > response , int pageSize , string pitId )
105+ private static ChangesResult BuildResult ( SearchResponse < DocumentationDocument > response , int pageSize )
136106 {
137107 var hits = response . Hits . ToList ( ) ;
138108 var hasMore = hits . Count > pageSize ;
@@ -167,7 +137,7 @@ private static ChangesResult BuildResult(SearchResponse<DocumentationDocument> r
167137 : default ( long ? ) ;
168138
169139 if ( epochMs is not null && sortUrl . TryGetString ( out var url ) )
170- nextCursor = new ChangesPageCursor ( epochMs . Value , url ! , pitId ) ;
140+ nextCursor = new ChangesPageCursor ( epochMs . Value , url ! ) ;
171141 }
172142 }
173143
@@ -178,9 +148,6 @@ private static ChangesResult BuildResult(SearchResponse<DocumentationDocument> r
178148 } ;
179149 }
180150
181- [ LoggerMessage ( Level = LogLevel . Debug , Message = "Opened new PIT: {PitId}" ) ]
182- private static partial void LogPitOpened ( ILogger logger , string pitId ) ;
183-
184151 [ LoggerMessage ( Level = LogLevel . Warning , Message = "PIT expired or not found, opening a new one and retrying with existing search_after position" ) ]
185152 private static partial void LogPitExpired ( ILogger logger ) ;
186153}
0 commit comments